#!/usr/bin/env python3 """ 创建或更新「学生作业管理助手」Agent:单链 Start → LLM → End。 侧重:记录作业项、截止日、优先级;跟进完成情况;温和督促与周回顾(不代写可提交的作业正文)。 用法: cd backend && .\\venv\\Scripts\\python.exe scripts/create_homework_manager_agent.py 环境变量: PLATFORM_BASE_URL, PLATFORM_USERNAME, PLATFORM_PASSWORD AGENT_NAME(默认 学生作业管理助手) HOMEWORK_LLM_PROVIDER / HOMEWORK_LLM_MODEL / HOMEWORK_LLM_TIMEOUT(可选) """ from __future__ import annotations import json import os import sys from typing import Any, Dict, List, Optional, Tuple import requests BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if BACKEND_DIR not in sys.path: sys.path.insert(0, BACKEND_DIR) BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/") USER = os.getenv("PLATFORM_USERNAME", "admin") PWD = os.getenv("PLATFORM_PASSWORD", "123456") AGENT_NAME = os.getenv("AGENT_NAME", "学生作业管理助手") PROVIDER = os.getenv( "HOMEWORK_LLM_PROVIDER", os.getenv("ENTERPRISE_LLM_PROVIDER", "deepseek") ) MODEL = os.getenv( "HOMEWORK_LLM_MODEL", os.getenv("ENTERPRISE_LLM_MODEL", "deepseek-chat") ) REQ_TIMEOUT = max( 30, int( os.getenv( "HOMEWORK_LLM_TIMEOUT", os.getenv("ENTERPRISE_LLM_TIMEOUT", "180") ) ), ) BUDGET_CONFIG = { "max_steps": 80, "max_llm_invocations": 6, "max_tool_calls": 20, } HOMEWORK_TOOLS = ["file_read", "text_analyze", "datetime", "json_process"] HOMEWORK_PROMPT = """你是「学生作业管理助手」,帮助学生**记作业**与**监督完成**,语气友好、具体、可执行。 【核心能力】 1. **记作业**:从用户自然语言中提取「科目 / 作业内容 / 截止日期与时间 / 老师要求要点 / 预估耗时」,整理成清单。 - 若用户用回形针**上传**了文件或照片,消息里会出现「相对工作区根路径」列表:**必须先调用 file_read**,用返回的 `content`(正文/OCR 文本)整理进作业清单,勿编造未读到的内容。 - 支持常见格式:纯文本/Markdown、**PDF**、**Word(.docx)**、**Excel(.xlsx)**、**照片**(作业拍照等,依赖 OCR;若工具返回需安装 Tesseract 等提示,请如实转告用户并仍可基于用户口述继续记作业)。 2. **监督完成**:根据清单追问进度(未开始/进行中/已完成);对临近截止的任务给**温和提醒**(不制造焦虑);可建议拆成小步骤与每日 15–30 分钟微习惯。 3. **周回顾**:用户要求时,用 json_process 或清晰表格输出本周完成率、延期项与下周优先三件事。 【原则】 - **不代写**可提交的作业正文、实验报告、论文等;可提供提纲、自检表、引用规范提示。 - 日期时间以用户所在语境为准;需要当前时间可借助工具 datetime。 - 不确定的信息(如具体截止时刻)先列出假设并请用户确认。 - 输出优先中文;列表用编号,便于复制到备忘录。 【交互习惯】 - 用户只说「记一下数学作业」时,主动追问截止日与具体要求(一次问 1–2 个点,避免审问感)。 - 用户汇报「做完了」时,确认是否需拍照/上传检查清单,并建议归档到下一条任务前的小结一句话。 """ def _sanitize_edges(edges: List[Dict[str, Any]]) -> List[Dict[str, Any]]: seen: set = set() out: List[Dict[str, Any]] = [] for e in edges or []: s, t = e.get("source"), e.get("target") if not s or not t or s == t: continue key = (s, t, e.get("sourceHandle") or "") if key in seen: continue seen.add(key) ne = dict(e) if not ne.get("targetHandle"): ne["targetHandle"] = "left" if not ne.get("id"): sh = ne.get("sourceHandle") or "r" ne["id"] = f"e_{s}_{t}_{sh}" out.append(ne) return out def build_workflow() -> Dict[str, Any]: llm_pos: Tuple[int, int] = (380, 220) nodes: List[Dict[str, Any]] = [ {"id": "start-1", "type": "start", "position": {"x": 80, "y": 220}, "data": {"label": "开始"}}, { "id": "llm-homework", "type": "llm", "position": {"x": llm_pos[0], "y": llm_pos[1]}, "data": { "label": "作业管理", "prompt": HOMEWORK_PROMPT, "provider": PROVIDER, "model": MODEL, "temperature": 0.3, "request_timeout": REQ_TIMEOUT, "enable_tools": True, "tools": list(HOMEWORK_TOOLS), "selected_tools": list(HOMEWORK_TOOLS), "max_tool_iterations": 10, }, }, {"id": "end-1", "type": "end", "position": {"x": llm_pos[0] + 260, "y": 220}, "data": {"label": "结束"}}, ] edges = _sanitize_edges( [ {"source": "start-1", "target": "llm-homework", "sourceHandle": "right", "targetHandle": "left"}, {"source": "llm-homework", "target": "end-1", "sourceHandle": "right", "targetHandle": "left"}, ] ) return {"nodes": nodes, "edges": edges} def _validate_local(wf: Dict[str, Any]) -> None: from app.services.workflow_validator import validate_workflow r = validate_workflow(wf.get("nodes") or [], wf.get("edges") or []) if not r.get("valid"): errs = r.get("errors") or [] raise ValueError("工作流校验失败: " + "; ".join(errs)) def _find_agent_id(h: Dict[str, str], name: str) -> Optional[str]: r = requests.get(f"{BASE}/api/v1/agents", params={"search": name, "limit": 80}, headers=h, timeout=45) if r.status_code != 200: return None for a in r.json() or []: if a.get("name") == name: return a.get("id") return None def main() -> int: wf = build_workflow() try: _validate_local(wf) except ValueError as e: print(e, file=sys.stderr) return 1 r = requests.post( f"{BASE}/api/v1/auth/login", data={"username": USER, "password": PWD}, headers={"Content-Type": "application/x-www-form-urlencoded"}, timeout=15, ) if r.status_code != 200: print("登录失败:", r.status_code, r.text[:500], file=sys.stderr) return 1 token = r.json().get("access_token") if not token: print("无 access_token", file=sys.stderr) return 1 h = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} desc = ( "学生作业管理助手:记作业(科目、内容、截止日)、跟进度、温和督促与周回顾;" "支持上传文件/照片后用 file_read 提取正文(文本、PDF、docx、xlsx、图片 OCR)与 json_process 整理;" f"默认模型 {PROVIDER}/{MODEL},单次执行内工具迭代上限 10。" ) existing = _find_agent_id(h, AGENT_NAME) if existing: ur = requests.put( f"{BASE}/api/v1/agents/{existing}", headers=h, json={ "description": desc, "workflow_config": wf, "budget_config": BUDGET_CONFIG, }, timeout=120, ) if ur.status_code != 200: print("更新失败:", ur.status_code, ur.text[:800], file=sys.stderr) return 1 print("已更新", AGENT_NAME, existing) print(json.dumps({"id": existing, "name": AGENT_NAME}, ensure_ascii=False)) return 0 cr = requests.post( f"{BASE}/api/v1/agents", headers=h, json={ "name": AGENT_NAME, "description": desc, "workflow_config": wf, "budget_config": BUDGET_CONFIG, }, timeout=120, ) if cr.status_code != 201: print("创建失败:", cr.status_code, cr.text[:800], file=sys.stderr) return 1 aid = cr.json()["id"] print("已创建", AGENT_NAME, aid) print(json.dumps({"id": aid, "name": AGENT_NAME}, ensure_ascii=False)) return 0 if __name__ == "__main__": raise SystemExit(main())