Files
aiagent/backend/scripts/create_intelligent_tutor_agent.py
renjianbo df4fab1e6e feat: Agent 批量测试、作业助手与上传预览;Windows 启动脚本与文档- 新增 run_agent_test_cases 与示例 JSON、(红头)agent测试用例文档
- 扩展 test_agent_execution(--homework、UTF-8 控制台)
- 后端:uploads 预览、file_read、工作流与对话落盘等
- 前端:AgentChatPreview 与设计器相关调整
- 忽略 redis二进制、agent_workspaces、uploads、tessdata 等本机产物

Made-with: Cursor
2026-04-13 20:17:18 +08:00

202 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
创建或更新「智能助教」Agent单链 Start → LLM → End面向课程答疑、作业辅导与学习规划。
用法:
cd backend && .\\venv\\Scripts\\python.exe scripts/create_intelligent_tutor_agent.py
环境变量:
PLATFORM_BASE_URL, PLATFORM_USERNAME, PLATFORM_PASSWORD
AGENT_NAME默认 智能助教)
TUTOR_LLM_PROVIDER / TUTOR_LLM_MODEL / TUTOR_LLM_TIMEOUT可选覆盖默认 DeepSeek 与超时秒数)
"""
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("TUTOR_LLM_PROVIDER", os.getenv("ENTERPRISE_LLM_PROVIDER", "deepseek"))
MODEL = os.getenv("TUTOR_LLM_MODEL", os.getenv("ENTERPRISE_LLM_MODEL", "deepseek-chat"))
REQ_TIMEOUT = max(30, int(os.getenv("TUTOR_LLM_TIMEOUT", os.getenv("ENTERPRISE_LLM_TIMEOUT", "180"))))
BUDGET_CONFIG = {
"max_steps": 80,
"max_llm_invocations": 6,
"max_tool_calls": 24,
}
TUTOR_TOOLS = ["file_read", "text_analyze", "datetime", "json_process"]
TUTOR_PROMPT = """你是「智能助教」,面向高校/职业课程场景辅助学习与教学准备。
【能力】
- 概念讲解:用清晰结构(定义→要点→小例子)说明知识点。
- 习题辅导:给出**解题思路与关键步骤**,引导学生自己完成计算与证明;不要直接给出可照抄的整卷答案或替考内容。
- 学习规划:根据用户目标与可用时间,建议复习顺序与自检清单。
- 材料辅助:若用户提到本地课件/笔记路径,可用工具读取后基于原文摘要与答疑。
【边界】
- 不编造教材页码、不虚构课程政策;不确定时明确说明并建议向任课教师核实。
- 涉及实验安全、医疗、法律等高风险领域时提示寻求专业人士。
- 输出简洁,优先中文;需要公式时用 LaTeX 或纯文本均可读形式。
【输出】
- 先给结论或步骤概览,再展开细节;复杂问题分条编号。
"""
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-tutor",
"type": "llm",
"position": {"x": llm_pos[0], "y": llm_pos[1]},
"data": {
"label": "智能助教",
"prompt": TUTOR_PROMPT,
"provider": PROVIDER,
"model": MODEL,
"temperature": 0.35,
"request_timeout": REQ_TIMEOUT,
"enable_tools": True,
"tools": list(TUTOR_TOOLS),
"selected_tools": list(TUTOR_TOOLS),
"max_tool_iterations": 12,
},
},
{"id": "end-1", "type": "end", "position": {"x": llm_pos[0] + 260, "y": 220}, "data": {"label": "结束"}},
]
edges = _sanitize_edges(
[
{"source": "start-1", "target": "llm-tutor", "sourceHandle": "right", "targetHandle": "left"},
{"source": "llm-tutor", "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与文本分析"
f"默认模型 {PROVIDER}/{MODEL},单次执行内工具迭代上限 12。"
)
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())