feat: add AI学习助手 agent (KG+RAG ideal) and renshenguo feishu bot
- Add AI学习助手 agent creation script with all 39 tools, 3-layer KG+RAG memory - Add renshenguo (人参果) feishu bot integration (app_service + ws_handler) - Register renshenguo WS client in main.py startup - Add RENSHENGUO_APP_ID / RENSHENGUO_APP_SECRET / RENSHENGUO_AGENT_ID config - Reorganize docs from root into docs/ subdirectories - Move startup scripts to scripts/startup/ - Various backend optimizations and tool improvements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
428
scripts/testing/test_agent_execution.py
Normal file
428
scripts/testing/test_agent_execution.py
Normal file
@@ -0,0 +1,428 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Agent工作流执行测试脚本
|
||||
用于测试Agent工作流的正常执行
|
||||
|
||||
与《工作流调用测试总结》一致:input_data 仅包含 query、USER_INPUT,便于 LLM 正确提取 user_query。
|
||||
|
||||
用法示例:
|
||||
python test_agent_execution.py
|
||||
python test_agent_execution.py <agent_id>
|
||||
python test_agent_execution.py <agent_id> "你好"
|
||||
python test_agent_execution.py --homework
|
||||
python test_agent_execution.py --homework --base-url http://127.0.0.1:8037
|
||||
python test_agent_execution.py --homework2
|
||||
python test_agent_execution.py --homework2 -m "记一下数学作业,周五交"
|
||||
python test_agent_execution.py --homework2 --base-url http://127.0.0.1:8037 --request-timeout 180 --max-wait 420
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import requests
|
||||
|
||||
DEFAULT_BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8037")
|
||||
|
||||
|
||||
def _ensure_utf8_stdio() -> None:
|
||||
"""Windows 默认 GBK 控制台打印含 emoji 的模型回复会报错,尽量切到 UTF-8。"""
|
||||
if sys.platform != "win32":
|
||||
return
|
||||
for name in ("stdout", "stderr"):
|
||||
stream = getattr(sys, name, None)
|
||||
if stream is not None and hasattr(stream, "reconfigure"):
|
||||
try:
|
||||
stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
_ensure_utf8_stdio()
|
||||
|
||||
DEFAULT_ANDROID_PROMPT = "生成一个导出androidlog的脚本"
|
||||
HOMEWORK_AGENT_NAME = "学生作业管理助手"
|
||||
HOMEWORK_DEFAULT_MESSAGE = "你好"
|
||||
|
||||
HOMEWORK2_AGENT_NAME = "学生作业管理助手2号"
|
||||
HOMEWORK2_DEFAULT_MESSAGE = """4.27号作业:
|
||||
1.学过的所有字母每个字母每个写3遍,抄写本课的单词和单词每个6遍(在孩子作业本上)
|
||||
2.读第38-40页课本,录视频打卡
|
||||
3.拼读第43页,拼读,录视频打卡
|
||||
4.完成app里面布置的绘本打卡"""
|
||||
|
||||
|
||||
def print_section(title: str) -> None:
|
||||
print("\n" + "=" * 80)
|
||||
print(f" {title}")
|
||||
print("=" * 80)
|
||||
|
||||
|
||||
def _login(
|
||||
base_url: str,
|
||||
username: str,
|
||||
password: str,
|
||||
timeout: int,
|
||||
) -> Optional[Dict[str, str]]:
|
||||
login_data = {"username": username, "password": password}
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{base_url}/api/v1/auth/login",
|
||||
data=login_data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
timeout=timeout,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
print(f"[FAIL] 登录失败: {response.status_code}")
|
||||
print(f"响应: {response.text[:800]}")
|
||||
return None
|
||||
token = response.json().get("access_token")
|
||||
if not token:
|
||||
print("[FAIL] 登录失败: 未获取到 token")
|
||||
return None
|
||||
print("[OK] 登录成功")
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
except Exception as e:
|
||||
print(f"[FAIL] 登录异常: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _find_agent_by_name(
|
||||
base_url: str,
|
||||
headers: Dict[str, str],
|
||||
name: str,
|
||||
timeout: int,
|
||||
) -> Optional[str]:
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{base_url}/api/v1/agents",
|
||||
headers=headers,
|
||||
params={"search": name, "limit": 100},
|
||||
timeout=timeout,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
print(f"[FAIL] 按名称查找 Agent 失败: {response.status_code}")
|
||||
print(response.text[:800])
|
||||
return None
|
||||
agents: List[Dict[str, Any]] = response.json() or []
|
||||
exact = [a for a in agents if (a.get("name") or "").strip() == name]
|
||||
pick = exact[0] if exact else (agents[0] if agents else None)
|
||||
if not pick:
|
||||
print(f"[FAIL] 未找到名为「{name}」的 Agent(search 无结果)")
|
||||
return None
|
||||
print(
|
||||
f"[OK] 使用 Agent: {pick.get('name')} (ID: {pick['id']}) "
|
||||
f"状态: {pick.get('status')}"
|
||||
)
|
||||
return str(pick["id"])
|
||||
except Exception as e:
|
||||
print(f"[FAIL] 查找 Agent 异常: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def _find_first_published_agent(
|
||||
base_url: str,
|
||||
headers: Dict[str, str],
|
||||
timeout: int,
|
||||
) -> Optional[str]:
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{base_url}/api/v1/agents",
|
||||
headers=headers,
|
||||
params={"status": "published", "limit": 10},
|
||||
timeout=timeout,
|
||||
)
|
||||
if response.status_code != 200:
|
||||
print(f"[FAIL] 获取 Agent 列表失败: {response.status_code}")
|
||||
print(response.text[:800])
|
||||
return None
|
||||
agents: List[Dict[str, Any]] = response.json() or []
|
||||
if not agents:
|
||||
print("[FAIL] 未找到可用的 Agent")
|
||||
print("请先创建并发布 Agent,或指定 agent_id / --agent-name / --homework")
|
||||
return None
|
||||
published_agents = [a for a in agents if a.get("status") == "published"]
|
||||
if published_agents:
|
||||
a = published_agents[0]
|
||||
print(f"[OK] 找到已发布的 Agent: {a['name']} (ID: {a['id']})")
|
||||
return str(a["id"])
|
||||
a = agents[0]
|
||||
print(
|
||||
f"[WARN] 使用 Agent: {a['name']} (ID: {a['id']}) - 状态: {a.get('status')}"
|
||||
)
|
||||
return str(a["id"])
|
||||
except Exception as e:
|
||||
print(f"[FAIL] 获取 Agent 列表异常: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def test_agent_execution(
|
||||
agent_id: Optional[str] = None,
|
||||
user_input: str = DEFAULT_ANDROID_PROMPT,
|
||||
*,
|
||||
base_url: str = DEFAULT_BASE_URL,
|
||||
username: str = "admin",
|
||||
password: str = "123456",
|
||||
agent_name: Optional[str] = None,
|
||||
request_timeout: int = 120,
|
||||
max_wait_time: int = 300,
|
||||
poll_interval: float = 2.0,
|
||||
) -> None:
|
||||
"""
|
||||
测试 Agent 执行
|
||||
|
||||
Args:
|
||||
agent_id: Agent ID;为 None 时按 agent_name 或已发布列表解析
|
||||
user_input: 用户输入(写入 query / USER_INPUT)
|
||||
base_url: API 根地址
|
||||
agent_name: 按名称精确匹配查找(配合 search 参数)
|
||||
request_timeout: 单次 HTTP 超时(秒)
|
||||
max_wait_time: 轮询最长等待(秒)
|
||||
poll_interval: 轮询间隔(秒)
|
||||
"""
|
||||
base_url = base_url.rstrip("/")
|
||||
print_section("Agent工作流执行测试")
|
||||
print(f"API: {base_url}")
|
||||
|
||||
print_section("1. 用户登录")
|
||||
headers = _login(base_url, username, password, request_timeout)
|
||||
if not headers:
|
||||
return
|
||||
|
||||
if not agent_id:
|
||||
print_section("2. 查找可用的 Agent")
|
||||
if agent_name:
|
||||
agent_id = _find_agent_by_name(
|
||||
base_url, headers, agent_name, request_timeout
|
||||
)
|
||||
else:
|
||||
agent_id = _find_first_published_agent(
|
||||
base_url, headers, request_timeout
|
||||
)
|
||||
if not agent_id:
|
||||
return
|
||||
else:
|
||||
print_section("2. 使用指定的 Agent")
|
||||
print(f"Agent ID: {agent_id}")
|
||||
|
||||
print_section("3. 执行 Agent 工作流")
|
||||
print(f"用户输入: {user_input}")
|
||||
|
||||
input_data = {"query": user_input, "USER_INPUT": user_input}
|
||||
execution_data = {"agent_id": agent_id, "input_data": input_data}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{base_url}/api/v1/executions",
|
||||
headers=headers,
|
||||
json=execution_data,
|
||||
timeout=request_timeout,
|
||||
)
|
||||
if response.status_code != 201:
|
||||
print(f"[FAIL] 创建执行任务失败: {response.status_code}")
|
||||
print(f"响应: {response.text[:2000]}")
|
||||
return
|
||||
execution = response.json()
|
||||
execution_id = execution["id"]
|
||||
print(f"[OK] 执行任务已创建: {execution_id}")
|
||||
print(f"状态: {execution.get('status')}")
|
||||
except Exception as e:
|
||||
print(f"[FAIL] 创建执行任务异常: {e}")
|
||||
return
|
||||
|
||||
print_section("4. 等待执行完成")
|
||||
start_time = time.time()
|
||||
final_loop_status: Optional[str] = None
|
||||
|
||||
while True:
|
||||
elapsed_time = time.time() - start_time
|
||||
if elapsed_time > max_wait_time:
|
||||
print(f"[FAIL] 执行超时(超过 {max_wait_time} 秒)")
|
||||
break
|
||||
try:
|
||||
status_response = requests.get(
|
||||
f"{base_url}/api/v1/executions/{execution_id}/status",
|
||||
headers=headers,
|
||||
timeout=request_timeout,
|
||||
)
|
||||
if status_response.status_code == 200:
|
||||
status = status_response.json()
|
||||
current_status = status.get("status")
|
||||
final_loop_status = current_status
|
||||
progress = status.get("progress", 0)
|
||||
print(
|
||||
f"[...] 执行中 状态={current_status} 进度={progress}%",
|
||||
end="\r",
|
||||
)
|
||||
if current_status == "completed":
|
||||
print("\n[OK] 执行完成")
|
||||
break
|
||||
if current_status == "failed":
|
||||
print("\n[FAIL] 执行失败")
|
||||
err = status.get("error") or status.get("error_message")
|
||||
if not err and status.get("failed_nodes"):
|
||||
fn = status["failed_nodes"][0]
|
||||
err = fn.get("error_message") or fn.get("error_type")
|
||||
print(f"错误信息: {err or '未知错误'}")
|
||||
break
|
||||
if current_status in ("cancelled", "awaiting_approval"):
|
||||
print(f"\n[WARN] 结束轮询: 状态={current_status}")
|
||||
break
|
||||
current_node = status.get("current_node")
|
||||
if current_node:
|
||||
nid = current_node.get("node_id")
|
||||
ntype = current_node.get("node_type")
|
||||
print(f"\n 当前节点: {nid} ({ntype})")
|
||||
time.sleep(poll_interval)
|
||||
except Exception as e:
|
||||
print(f"\n[FAIL] 获取执行状态异常: {e}")
|
||||
time.sleep(poll_interval)
|
||||
|
||||
print_section("5. 获取执行结果")
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{base_url}/api/v1/executions/{execution_id}",
|
||||
headers=headers,
|
||||
timeout=request_timeout,
|
||||
)
|
||||
if response.status_code == 200:
|
||||
execution = response.json()
|
||||
status = execution.get("status")
|
||||
output_data = execution.get("output_data")
|
||||
execution_time = execution.get("execution_time")
|
||||
err_msg = execution.get("error_message")
|
||||
|
||||
print(f"执行状态: {status}")
|
||||
if err_msg and status != "completed":
|
||||
print(f"服务端错误信息: {err_msg[:2000]}")
|
||||
if execution_time is not None:
|
||||
print(f"执行时间: {execution_time}ms")
|
||||
|
||||
print("\n输出结果:")
|
||||
print("-" * 80)
|
||||
if output_data:
|
||||
if isinstance(output_data, dict):
|
||||
text_output = (
|
||||
output_data.get("result")
|
||||
or output_data.get("output")
|
||||
or output_data.get("text")
|
||||
or output_data.get("content")
|
||||
or json.dumps(output_data, ensure_ascii=False, indent=2)
|
||||
)
|
||||
print(text_output)
|
||||
else:
|
||||
print(output_data)
|
||||
else:
|
||||
print("(无输出数据)")
|
||||
print("-" * 80)
|
||||
|
||||
if execution.get("logs"):
|
||||
print("\n执行日志:")
|
||||
for log in execution.get("logs", []):
|
||||
print(f" [{log.get('timestamp')}] {log.get('message')}")
|
||||
else:
|
||||
print(f"[FAIL] 获取执行结果失败: {response.status_code}")
|
||||
print(f"响应: {response.text[:2000]}")
|
||||
except Exception as e:
|
||||
print(f"[FAIL] 获取执行结果异常: {e}")
|
||||
|
||||
print_section("测试完成")
|
||||
if final_loop_status and final_loop_status != "completed":
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(
|
||||
description="Agent 工作流执行测试(input_data 与总结文档一致:query + USER_INPUT)"
|
||||
)
|
||||
p.add_argument("agent_id", nargs="?", default=None, help="Agent UUID(可选)")
|
||||
p.add_argument("user_input", nargs="?", default=None, help="用户消息(可选)")
|
||||
p.add_argument(
|
||||
"--homework",
|
||||
action="store_true",
|
||||
help=f"测试「{HOMEWORK_AGENT_NAME}」,默认发送「{HOMEWORK_DEFAULT_MESSAGE}」",
|
||||
)
|
||||
p.add_argument(
|
||||
"--homework2",
|
||||
action="store_true",
|
||||
help=f"测试「{HOMEWORK2_AGENT_NAME}」(快速档案+持久记忆);默认发送一段 4.27 样例作业",
|
||||
)
|
||||
p.add_argument(
|
||||
"-m",
|
||||
"--message",
|
||||
default=None,
|
||||
metavar="TEXT",
|
||||
help="与 --homework / --homework2 联用的用户话术(推荐,避免位置参数被当成 agent_id)",
|
||||
)
|
||||
p.add_argument(
|
||||
"--agent-name",
|
||||
default=None,
|
||||
help="按名称精确查找 Agent(未传 agent_id 时)",
|
||||
)
|
||||
p.add_argument("--base-url", default=DEFAULT_BASE_URL, help="API 根地址")
|
||||
p.add_argument("--username", default="admin")
|
||||
p.add_argument("--password", default="123456")
|
||||
p.add_argument("--request-timeout", type=int, default=120, help="单次 HTTP 超时秒数")
|
||||
p.add_argument("--max-wait", type=int, default=300, help="轮询最长等待秒数")
|
||||
p.add_argument("--poll-interval", type=float, default=2.0)
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = _parse_args()
|
||||
name: Optional[str] = None
|
||||
uid: Optional[str] = args.agent_id
|
||||
msg: str
|
||||
|
||||
if args.homework and args.homework2:
|
||||
print("[WARN] 同时指定 --homework 与 --homework2,优先使用 --homework2")
|
||||
if (args.homework or args.homework2) and args.agent_name:
|
||||
print("[WARN] 同时指定 --homework/--homework2 与 --agent-name,将使用 --agent-name 查找")
|
||||
|
||||
def _msg_from_homework_flags(default_v1: str, default_v2: str) -> str:
|
||||
if args.message is not None:
|
||||
return args.message
|
||||
if args.user_input is not None:
|
||||
return args.user_input
|
||||
return default_v2 if args.homework2 else default_v1
|
||||
|
||||
if args.agent_name:
|
||||
name = args.agent_name
|
||||
if args.homework2:
|
||||
msg = _msg_from_homework_flags(HOMEWORK_DEFAULT_MESSAGE, HOMEWORK2_DEFAULT_MESSAGE)
|
||||
elif args.homework:
|
||||
msg = _msg_from_homework_flags(HOMEWORK_DEFAULT_MESSAGE, HOMEWORK2_DEFAULT_MESSAGE)
|
||||
else:
|
||||
msg = (
|
||||
args.user_input
|
||||
if args.user_input is not None
|
||||
else DEFAULT_ANDROID_PROMPT
|
||||
)
|
||||
elif args.homework2:
|
||||
name = HOMEWORK2_AGENT_NAME
|
||||
msg = _msg_from_homework_flags(HOMEWORK_DEFAULT_MESSAGE, HOMEWORK2_DEFAULT_MESSAGE)
|
||||
elif args.homework:
|
||||
name = HOMEWORK_AGENT_NAME
|
||||
msg = _msg_from_homework_flags(HOMEWORK_DEFAULT_MESSAGE, HOMEWORK2_DEFAULT_MESSAGE)
|
||||
else:
|
||||
msg = (
|
||||
args.user_input
|
||||
if args.user_input is not None
|
||||
else DEFAULT_ANDROID_PROMPT
|
||||
)
|
||||
|
||||
test_agent_execution(
|
||||
agent_id=uid,
|
||||
user_input=msg,
|
||||
base_url=args.base_url,
|
||||
username=args.username,
|
||||
password=args.password,
|
||||
agent_name=name if not uid else None,
|
||||
request_timeout=args.request_timeout,
|
||||
max_wait_time=args.max_wait,
|
||||
poll_interval=args.poll_interval,
|
||||
)
|
||||
Reference in New Issue
Block a user