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:
renjianbo
2026-05-06 01:37:13 +08:00
parent f33bc461ff
commit eabf90c496
171 changed files with 4906 additions and 445 deletions

View File

@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""
ADB工具验证脚本
用于直接测试 adb_log_tool 是否能正常调用 ADB 命令
"""
import asyncio
import json
import sys
import os
# 添加项目路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
from app.services.builtin_tools import adb_log_tool
async def test_adb_devices():
"""测试列出设备"""
print("=" * 60)
print("测试 1: 列出连接的设备 (adb devices)")
print("=" * 60)
try:
result = await adb_log_tool(command="devices")
result_data = json.loads(result)
print(f"✅ 执行成功")
print(f"结果:\n{json.dumps(result_data, ensure_ascii=False, indent=2)}")
return True
except Exception as e:
print(f"❌ 执行失败: {str(e)}")
return False
async def test_adb_logcat_recent():
"""测试获取最近日志"""
print("\n" + "=" * 60)
print("测试 2: 获取最近日志 (adb logcat -d -t 10)")
print("=" * 60)
try:
result = await adb_log_tool(
command="logcat",
max_lines=10
)
result_data = json.loads(result)
print(f"✅ 执行成功")
if "error" in result_data:
print(f"⚠️ 返回错误: {result_data['error']}")
else:
print(f"日志行数: {result_data.get('line_count', 0)}")
if result_data.get('logs'):
print(f"前3行日志预览:")
for i, log in enumerate(result_data['logs'][:3], 1):
print(f" {i}. {log[:100]}...")
return True
except Exception as e:
print(f"❌ 执行失败: {str(e)}")
return False
async def test_adb_logcat_with_filter():
"""测试带过滤的日志获取"""
print("\n" + "=" * 60)
print("测试 3: 获取错误级别日志 (adb logcat -d *:E -t 5)")
print("=" * 60)
try:
result = await adb_log_tool(
command="logcat",
level="E",
max_lines=5
)
result_data = json.loads(result)
print(f"✅ 执行成功")
if "error" in result_data:
print(f"⚠️ 返回错误: {result_data['error']}")
else:
print(f"错误日志行数: {result_data.get('line_count', 0)}")
if result_data.get('logs'):
print(f"错误日志预览:")
for i, log in enumerate(result_data['logs'][:3], 1):
print(f" {i}. {log[:100]}...")
return True
except Exception as e:
print(f"❌ 执行失败: {str(e)}")
return False
async def test_adb_shell():
"""测试执行shell命令"""
print("\n" + "=" * 60)
print("测试 4: 执行shell命令 (adb shell getprop ro.build.version.release)")
print("=" * 60)
try:
result = await adb_log_tool(
command="shell",
filter_tag="getprop ro.build.version.release"
)
result_data = json.loads(result)
print(f"✅ 执行成功")
if "error" in result_data:
print(f"⚠️ 返回错误: {result_data['error']}")
else:
print(f"命令输出:\n{result_data.get('output', '')}")
return True
except Exception as e:
print(f"❌ 执行失败: {str(e)}")
return False
async def test_invalid_command():
"""测试无效命令"""
print("\n" + "=" * 60)
print("测试 5: 测试无效命令 (验证错误处理)")
print("=" * 60)
try:
result = await adb_log_tool(command="invalid_command")
result_data = json.loads(result)
if "error" in result_data:
print(f"✅ 正确返回错误: {result_data['error']}")
return True
else:
print(f"⚠️ 应该返回错误但未返回")
return False
except Exception as e:
print(f"❌ 执行失败: {str(e)}")
return False
async def main():
"""主测试函数"""
print("\n" + "🔧 ADB工具验证测试")
print("=" * 60)
print("此脚本将测试 adb_log_tool 的各种功能")
print("请确保:")
print(" 1. 已安装 Android SDK Platform Tools")
print(" 2. adb 命令在 PATH 中")
print(" 3. 已连接 Android 设备或启动模拟器")
print("=" * 60)
print("\n开始测试...\n")
results = []
# 运行所有测试
results.append(("列出设备", await test_adb_devices()))
results.append(("获取最近日志", await test_adb_logcat_recent()))
results.append(("获取错误日志", await test_adb_logcat_with_filter()))
results.append(("执行shell命令", await test_adb_shell()))
results.append(("错误处理", await test_invalid_command()))
# 汇总结果
print("\n" + "=" * 60)
print("测试结果汇总")
print("=" * 60)
passed = sum(1 for _, result in results if result)
total = len(results)
for test_name, result in results:
status = "✅ 通过" if result else "❌ 失败"
print(f"{status} - {test_name}")
print(f"\n总计: {passed}/{total} 测试通过")
if passed == total:
print("\n🎉 所有测试通过ADB工具工作正常。")
return 0
else:
print(f"\n⚠️ 有 {total - passed} 个测试失败,请检查:")
print(" 1. ADB 是否正确安装")
print(" 2. 设备是否已连接 (运行 'adb devices' 检查)")
print(" 3. 设备是否已启用 USB 调试")
return 1
if __name__ == "__main__":
exit_code = asyncio.run(main())
sys.exit(exit_code)

View 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}」的 Agentsearch 无结果)")
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,
)

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
代码编程助手 — 工作流执行冒烟测试
与 test_agent_execution.py 相同调用链POST /api/v1/executions
input_data 仅含 query、USER_INPUT与《工作流调用测试总结》一致
用法示例:
python test_coding_agent_execution.py
python test_coding_agent_execution.py -m "你好"
python test_coding_agent_execution.py <agent_id>
python test_coding_agent_execution.py <agent_id> "写一个 hello world"
python test_coding_agent_execution.py --base-url http://127.0.0.1:8037 --max-wait 420
"""
from __future__ import annotations
import argparse
from test_agent_execution import DEFAULT_BASE_URL, test_agent_execution
CODING_AGENT_NAME = "代码编程助手"
DEFAULT_MESSAGE = "你好"
def _parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(
description=f'测试「{CODING_AGENT_NAME}」工作流执行(默认用户话术「{DEFAULT_MESSAGE}」)'
)
p.add_argument("agent_id", nargs="?", default=None, help="Agent UUID可选不传则按名称查找")
p.add_argument("user_input", nargs="?", default=None, help="用户消息(可选;等价于省略时使用默认值)")
p.add_argument(
"-m",
"--message",
default=None,
metavar="TEXT",
help=f'用户话术(优先于第二个位置参数;默认「{DEFAULT_MESSAGE}」)',
)
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=420, help="轮询最长等待秒数(编程助手可能多轮 LLM")
p.add_argument("--poll-interval", type=float, default=2.0)
return p.parse_args()
def main() -> None:
args = _parse_args()
msg = DEFAULT_MESSAGE
if args.message is not None:
msg = args.message
elif args.user_input is not None:
msg = args.user_input
test_agent_execution(
agent_id=args.agent_id,
user_input=msg,
base_url=args.base_url,
username=args.username,
password=args.password,
agent_name=None if args.agent_id else CODING_AGENT_NAME,
request_timeout=args.request_timeout,
max_wait_time=args.max_wait,
poll_interval=args.poll_interval,
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""
测试数据库查询工具
"""
import sys
import os
import asyncio
import json
# 添加项目路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
from app.services.builtin_tools import database_query_tool, _validate_sql_query
def test_sql_validation():
"""测试SQL验证功能"""
print("=" * 60)
print("测试SQL验证功能")
print("=" * 60)
test_cases = [
("SELECT * FROM users", True, "正常SELECT查询"),
("select * from users", True, "小写SELECT查询"),
("INSERT INTO users VALUES (1, 'test')", False, "INSERT查询应拒绝"),
("UPDATE users SET name='test'", False, "UPDATE查询应拒绝"),
("DELETE FROM users", False, "DELETE查询应拒绝"),
("DROP TABLE users", False, "DROP查询应拒绝"),
("SELECT * FROM users; DROP TABLE users", False, "多语句查询(应拒绝)"),
("SELECT * FROM users WHERE id = 1", True, "带WHERE的SELECT查询"),
("SELECT u.id, u.name FROM users u", True, "带别名的SELECT查询"),
]
for sql, expected, description in test_cases:
is_safe, error_msg = _validate_sql_query(sql)
status = "" if is_safe == expected else ""
print(f"{status} {description}")
print(f" SQL: {sql[:50]}...")
if not is_safe:
print(f" 错误: {error_msg}")
print()
async def test_database_query():
"""测试数据库查询功能"""
print("=" * 60)
print("测试数据库查询功能")
print("=" * 60)
# 测试1: 查询系统表(如果存在)
print("\n1. 测试查询系统表users表")
try:
result = await database_query_tool(
query="SELECT COUNT(*) as user_count FROM users LIMIT 1",
timeout=10
)
data = json.loads(result)
if data.get("success"):
print(f" ✅ 查询成功")
print(f" 结果: {json.dumps(data, ensure_ascii=False, indent=2)}")
else:
print(f" ❌ 查询失败: {data.get('error')}")
except Exception as e:
print(f" ⚠️ 查询异常: {str(e)}")
# 测试2: 测试SQL注入防护
print("\n2. 测试SQL注入防护")
try:
result = await database_query_tool(
query="INSERT INTO users (username) VALUES ('hacker')",
timeout=10
)
data = json.loads(result)
if not data.get("success") and "不允许" in data.get("error", ""):
print(f" ✅ SQL注入防护生效")
print(f" 错误信息: {data.get('error')}")
else:
print(f" ❌ SQL注入防护失效")
except Exception as e:
print(f" ⚠️ 异常: {str(e)}")
# 测试3: 测试复杂查询
print("\n3. 测试复杂SELECT查询")
try:
result = await database_query_tool(
query="SELECT id, username, email FROM users LIMIT 5",
timeout=10
)
data = json.loads(result)
if data.get("success"):
print(f" ✅ 查询成功")
print(f" 返回行数: {data.get('row_count', 0)}")
if data.get('data'):
print(f" 示例数据: {json.dumps(data['data'][0] if data['data'] else {}, ensure_ascii=False, indent=2)}")
else:
print(f" ❌ 查询失败: {data.get('error')}")
except Exception as e:
print(f" ⚠️ 查询异常: {str(e)}")
# 测试4: 测试超时控制
print("\n4. 测试超时控制(使用长时间查询)")
try:
result = await database_query_tool(
query="SELECT SLEEP(5) as test",
timeout=2
)
data = json.loads(result)
if "超时" in data.get("error", ""):
print(f" ✅ 超时控制生效")
else:
print(f" ⚠️ 超时控制未生效可能数据库不支持SLEEP函数")
except Exception as e:
print(f" ⚠️ 异常: {str(e)}")
def main():
"""主函数"""
print("\n" + "=" * 60)
print("数据库查询工具测试")
print("=" * 60 + "\n")
# 测试SQL验证
test_sql_validation()
# 测试数据库查询
print("\n")
asyncio.run(test_database_query())
print("\n" + "=" * 60)
print("测试完成")
print("=" * 60)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""
测试记忆功能
参考工作流调用测试总结.txt的测试方法
"""
import sys
sys.path.insert(0, 'backend')
from app.core.database import SessionLocal
from app.models.agent import Agent
from app.models.execution import Execution
from app.models.execution_log import ExecutionLog
import json
from datetime import datetime
def test_memory_functionality():
"""测试记忆功能"""
db = SessionLocal()
try:
# 获取智能聊天助手Agent
agent = db.query(Agent).filter(
Agent.name == '智能聊天助手(完整示例)'
).first()
if not agent:
print("❌ 未找到'智能聊天助手(完整示例)'Agent")
return
print(f"✅ 找到Agent: {agent.name} (ID: {agent.id})")
print("="*80)
# 获取最近的两次执行(应该对应两次对话)
executions = db.query(Execution).filter(
Execution.agent_id == agent.id
).order_by(Execution.created_at.desc()).limit(2).all()
if len(executions) < 2:
print(f"⚠️ 只找到 {len(executions)} 次执行需要至少2次执行来测试记忆功能")
print("请先进行两次对话测试")
return
print(f"\n找到 {len(executions)} 次执行记录")
print("="*80)
# 分析每次执行
for i, exec_record in enumerate(reversed(executions), 1): # 按时间正序
print(f"\n{'='*80}")
print(f"执行 {i}: {exec_record.id}")
print(f"输入: {exec_record.input_data}")
print(f"时间: {exec_record.created_at}")
print(f"状态: {exec_record.status}")
# 检查关键节点的数据流转
nodes_to_check = [
('cache-query', '查询记忆'),
('transform-merge', '合并上下文'),
('llm-question', '问题回答'),
('cache-update', '更新记忆'),
('llm-format', '格式化回复'),
('end-1', '最终输出')
]
for node_id, label in nodes_to_check:
# 查找节点执行完成的日志
log = db.query(ExecutionLog).filter(
ExecutionLog.execution_id == exec_record.id,
ExecutionLog.node_id == node_id,
ExecutionLog.message.like(f'节点 {node_id}%执行完成')
).first()
if not log:
# 如果没有执行完成的日志,查找开始执行的日志
log = db.query(ExecutionLog).filter(
ExecutionLog.execution_id == exec_record.id,
ExecutionLog.node_id == node_id,
ExecutionLog.message.like(f'节点 {node_id}%开始执行')
).first()
if log and log.data:
data_key = 'output' if '执行完成' in log.message else 'input'
data = log.data.get(data_key, {})
print(f"\n{label} ({node_id}):")
if isinstance(data, dict):
print(f" keys: {list(data.keys())}")
# 检查memory字段
if 'memory' in data:
memory = data['memory']
if isinstance(memory, dict):
print(f" ✅ memory存在keys: {list(memory.keys())}")
if 'conversation_history' in memory:
history = memory['conversation_history']
if isinstance(history, list):
print(f" ✅ conversation_history: {len(history)}")
if history:
print(f" 最新一条: {history[-1].get('content', '')[:50]}")
else:
print(f" ❌ conversation_history不是list: {type(history)}")
# 检查conversation_history字段可能在顶层
elif 'conversation_history' in data:
history = data['conversation_history']
if isinstance(history, list):
print(f" ✅ conversation_history在顶层: {len(history)}")
if history:
print(f" 最新一条: {history[-1].get('content', '')[:50]}")
# 对于end节点检查最终输出
if node_id == 'end-1' and 'output' in data:
output = data['output']
if isinstance(output, str):
print(f" ✅ 最终输出: {output[:200]}")
# 检查是否包含名字
if '老七' in output:
print(f" ✅ 输出中包含名字'老七'")
else:
print(f" ❌ 输出中不包含名字'老七'")
elif isinstance(data, str):
print(f" 输出类型: str, 内容: {data[:200]}")
if '老七' in data:
print(f" ✅ 输出中包含名字'老七'")
else:
print(f" ❌ 输出中不包含名字'老七'")
else:
print(f"\n{label} ({node_id}): ❌ 未找到执行日志")
# 检查最终输出
if exec_record.output_data:
output_data = exec_record.output_data
if isinstance(output_data, dict):
result = output_data.get('result', '')
if isinstance(result, str):
print(f"\n最终结果: {result[:200]}")
if '老七' in result:
print(f"✅ 最终结果中包含名字'老七'")
else:
print(f"❌ 最终结果中不包含名字'老七'")
# 检查Redis中的记忆数据
print(f"\n{'='*80}")
print("检查Redis中的记忆数据:")
try:
from app.core.redis_client import get_redis_client
redis_client = get_redis_client()
if redis_client:
keys = redis_client.keys('user_memory_*')
if keys:
for key in keys:
value = redis_client.get(key)
if value:
try:
memory_data = json.loads(value)
if 'conversation_history' in memory_data:
history = memory_data['conversation_history']
print(f"{key}: {len(history)} 条对话记录")
if history:
print(f" 最新一条: {history[-1].get('content', '')[:50]}")
except:
print(f" ⚠️ {key}: 无法解析JSON")
else:
print(" ❌ Redis中没有找到记忆数据")
else:
print(" ⚠️ Redis客户端不可用")
except Exception as e:
print(f" ⚠️ 检查Redis失败: {str(e)}")
print(f"\n{'='*80}")
print("测试完成")
finally:
db.close()
if __name__ == '__main__':
test_memory_functionality()

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""
测试output变量提取逻辑
"""
import sys
sys.path.insert(0, 'backend')
from app.services.workflow_engine import WorkflowEngine
# 模拟llm-format节点的输入数据
input_data = {
'right': {
'right': {
'right': '是的,我记得!根据我们之前的对话,你告诉我你的名字叫"老七"。我会在本次对话中记住这个名字,以便更好地为你提供帮助。如果你希望我用其他称呼,也可以随时告诉我。',
'query': '你还记得我的名字吗?'
},
'memory': {
'conversation_history': [],
'user_profile': {},
'context': {}
},
'query': '你还记得我的名字吗?'
},
'memory': {
'conversation_history': [],
'user_profile': {},
'context': {}
},
'query': '你还记得我的名字吗?'
}
# 创建WorkflowEngine实例
engine = WorkflowEngine("test", {"nodes": [], "edges": []})
# 测试_get_nested_value方法
print("测试_get_nested_value方法:")
value1 = engine._get_nested_value(input_data, 'output')
print(f" _get_nested_value(input_data, 'output'): {value1}")
# 测试output变量提取逻辑
print("\n测试output变量提取逻辑:")
right_value = input_data.get('right')
print(f" right_value类型: {type(right_value)}")
print(f" right_value: {str(right_value)[:100]}")
if right_value is not None:
if isinstance(right_value, str):
value = right_value
print(f" ✅ 从right字段字符串提取: {value[:100]}")
elif isinstance(right_value, dict):
current = right_value
depth = 0
while isinstance(current, dict) and depth < 10:
if 'right' in current:
current = current['right']
depth += 1
if isinstance(current, str):
value = current
print(f" ✅ 从right字段嵌套{depth}层)提取: {value[:100]}")
break
else:
break
else:
print(f" ❌ 无法提取字符串值")
print("\n测试完成")

View File

@@ -0,0 +1,368 @@
#!/usr/bin/env python3
"""
测试工具调用可视化功能
创建一个简单的Agent使用工具调用然后查看执行详情
"""
import requests
import json
import time
import sys
BASE_URL = "http://localhost:8037"
def print_section(title):
print("\n" + "=" * 80)
print(f" {title}")
print("=" * 80)
def print_info(message):
print(f" {message}")
def print_success(message):
print(f"{message}")
def print_error(message):
print(f"{message}")
def login():
"""用户登录"""
print_section("1. 用户登录")
login_data = {"username": "admin", "password": "123456"}
try:
response = requests.post(f"{BASE_URL}/api/v1/auth/login", data=login_data)
if response.status_code != 200:
print_error(f"登录失败: {response.status_code}")
return None
token = response.json().get("access_token")
if not token:
print_error("登录失败: 未获取到token")
return None
print_success(f"登录成功")
return {"Authorization": f"Bearer {token}"}
except Exception as e:
print_error(f"登录异常: {str(e)}")
return None
def create_test_workflow(headers):
"""创建测试工作流(使用工具调用)"""
print_section("2. 创建测试工作流")
workflow_data = {
"name": "工具调用可视化测试工作流",
"description": "用于测试工具调用可视化功能",
"nodes": [
{
"id": "start",
"type": "start",
"position": {"x": 100, "y": 200},
"data": {"label": "开始"}
},
{
"id": "llm-with-tools",
"type": "llm",
"position": {"x": 400, "y": 200},
"data": {
"label": "工具调用测试",
"provider": "deepseek",
"model": "deepseek-chat",
"temperature": 0.7,
"max_tokens": 1000,
"enable_tools": True,
"selected_tools": ["http_request", "datetime", "math_calculate"],
"prompt": """用户请求:{{input.query}}
请根据用户需求,选择合适的工具执行任务。可以使用以下工具:
- http_request: 发送HTTP请求
- datetime: 获取当前时间
- math_calculate: 执行数学计算
请分析用户需求,调用合适的工具,然后基于工具返回的结果生成回复。"""
}
},
{
"id": "end",
"type": "end",
"position": {"x": 700, "y": 200},
"data": {"label": "结束"}
}
],
"edges": [
{
"id": "e1",
"source": "start",
"target": "llm-with-tools",
"sourceHandle": "right",
"targetHandle": "left"
},
{
"id": "e2",
"source": "llm-with-tools",
"target": "end",
"sourceHandle": "right",
"targetHandle": "left"
}
]
}
try:
response = requests.post(
f"{BASE_URL}/api/v1/workflows",
headers=headers,
json=workflow_data
)
if response.status_code not in [200, 201]:
print_error(f"创建工作流失败: {response.status_code}")
print(f"响应: {response.text}")
return None
workflow = response.json()
print_success(f"工作流创建成功: {workflow.get('id')}")
return workflow
except Exception as e:
print_error(f"创建工作流异常: {str(e)}")
return None
def execute_workflow(headers, workflow_id, input_data):
"""执行工作流"""
print_section("3. 执行工作流")
execution_data = {
"workflow_id": workflow_id,
"input_data": input_data
}
try:
response = requests.post(
f"{BASE_URL}/api/v1/executions",
headers=headers,
json=execution_data
)
if response.status_code not in [200, 201]:
print_error(f"执行工作流失败: {response.status_code}")
print(f"响应: {response.text}")
return None
execution = response.json()
execution_id = execution.get("id")
print_success(f"执行已创建: {execution_id}")
return execution_id
except Exception as e:
print_error(f"执行工作流异常: {str(e)}")
return None
def wait_for_completion(headers, execution_id, timeout=60):
"""等待执行完成"""
print_section("4. 等待执行完成")
start_time = time.time()
while time.time() - start_time < timeout:
try:
response = requests.get(
f"{BASE_URL}/api/v1/executions/{execution_id}",
headers=headers
)
if response.status_code != 200:
print_error(f"获取执行状态失败: {response.status_code}")
return None
execution = response.json()
status = execution.get("status")
print_info(f"执行状态: {status}")
if status in ["completed", "failed"]:
print_success(f"执行完成,状态: {status}")
return execution
time.sleep(2)
except Exception as e:
print_error(f"获取执行状态异常: {str(e)}")
return None
print_error("执行超时")
return None
def get_execution_logs(headers, execution_id):
"""获取执行日志"""
print_section("5. 获取执行日志(包含工具调用信息)")
try:
response = requests.get(
f"{BASE_URL}/api/v1/execution-logs/executions/{execution_id}",
headers=headers,
params={"limit": 100}
)
if response.status_code != 200:
print_error(f"获取执行日志失败: {response.status_code}")
print(f"响应: {response.text}")
return None
logs = response.json()
print_success(f"获取到 {len(logs)} 条日志")
# 查找工具调用相关的日志
tool_call_logs = []
for log in logs:
if not log:
continue
data = log.get("data") or {}
if isinstance(data, str):
try:
data = json.loads(data)
except:
data = {}
if data.get("tool_name") or "工具" in log.get("message", ""):
tool_call_logs.append(log)
if tool_call_logs:
print_success(f"找到 {len(tool_call_logs)} 条工具调用日志")
print("\n工具调用日志详情:")
for i, log in enumerate(tool_call_logs, 1):
print(f"\n{i}. {log.get('message')}")
print(f" 时间: {log.get('timestamp')}")
print(f" 节点: {log.get('node_id')}")
data = log.get("data", {})
if data.get("tool_name"):
print(f" 工具名称: {data.get('tool_name')}")
print(f" 状态: {data.get('status')}")
if data.get("tool_args"):
print(f" 参数: {json.dumps(data.get('tool_args'), ensure_ascii=False, indent=2)}")
if data.get("duration"):
print(f" 耗时: {data.get('duration')}ms")
else:
print_info("未找到工具调用日志")
return logs
except Exception as e:
print_error(f"获取执行日志异常: {str(e)}")
return None
def get_node_execution_data(headers, execution_id, node_id):
"""获取节点执行数据"""
print_section(f"6. 获取节点执行数据 (节点: {node_id})")
try:
response = requests.get(
f"{BASE_URL}/api/v1/execution-logs/executions/{execution_id}",
headers=headers,
params={"node_id": node_id, "limit": 100}
)
if response.status_code != 200:
print_error(f"获取节点执行数据失败: {response.status_code}")
return None
logs = response.json()
print_success(f"获取到 {len(logs)} 条节点日志")
# 显示工具调用信息
tool_calls = []
for log in logs:
if not log:
continue
data = log.get("data") or {}
if isinstance(data, str):
try:
data = json.loads(data)
except:
data = {}
if data.get("tool_name"):
tool_calls.append({
"tool_name": data.get("tool_name"),
"status": data.get("status"),
"args": data.get("tool_args"),
"result": data.get("tool_result"),
"duration": data.get("duration"),
"timestamp": log.get("timestamp")
})
if tool_calls:
print_success(f"找到 {len(tool_calls)} 个工具调用")
for i, call in enumerate(tool_calls, 1):
print(f"\n工具调用 {i}:")
print(f" 工具: {call['tool_name']}")
print(f" 状态: {call['status']}")
print(f" 参数: {json.dumps(call['args'], ensure_ascii=False, indent=2) if call['args'] else ''}")
if call.get('result'):
result_preview = call['result'][:200] if len(call['result']) > 200 else call['result']
print(f" 结果预览: {result_preview}...")
print(f" 耗时: {call.get('duration', 'N/A')}ms")
print(f" 时间: {call['timestamp']}")
else:
print_info("该节点没有工具调用")
return logs
except Exception as e:
print_error(f"获取节点执行数据异常: {str(e)}")
return None
def main():
"""主函数"""
print_section("工具调用可视化功能测试")
# 1. 登录
headers = login()
if not headers:
return
# 2. 创建测试工作流
workflow = create_test_workflow(headers)
if not workflow:
return
workflow_id = workflow.get("id")
# 3. 执行工作流(使用不同的测试用例)
test_cases = [
{
"name": "测试HTTP请求工具",
"input": {"query": "请查询 https://api.github.com/users/octocat 的信息"}
},
{
"name": "测试时间工具",
"input": {"query": "现在是什么时间?"}
},
{
"name": "测试数学计算工具",
"input": {"query": "计算 123 * 456 的结果"}
}
]
for i, test_case in enumerate(test_cases, 1):
print_section(f"测试用例 {i}: {test_case['name']}")
# 执行工作流
execution_id = execute_workflow(headers, workflow_id, test_case["input"])
if not execution_id:
continue
# 等待完成
execution = wait_for_completion(headers, execution_id)
if not execution:
continue
# 获取执行日志
logs = get_execution_logs(headers, execution_id)
# 获取节点执行数据
node_logs = get_node_execution_data(headers, execution_id, "llm-with-tools")
print_success(f"测试用例 {i} 完成")
print("\n" + "-" * 80)
print_section("测试完成")
print_success("所有测试用例执行完成!")
print_info("请在前端查看执行详情,验证工具调用可视化功能:")
print_info(f"1. 打开执行详情页面: http://localhost:8038/executions/{execution_id}")
print_info("2. 点击节点查看节点执行详情")
print_info("3. 检查工具调用可视化是否正确显示")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,169 @@
#!/usr/bin/env python3
"""
工作流数据流转测试脚本
用于诊断"答非所问"问题
"""
import asyncio
import json
import sys
import os
# 添加项目路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
from app.services.workflow_engine import WorkflowEngine
from app.core.database import SessionLocal
def print_section(title):
"""打印分隔线"""
print("\n" + "=" * 80)
print(f" {title}")
print("=" * 80)
def print_data(label, data, indent=0):
"""格式化打印数据"""
prefix = " " * indent
print(f"{prefix}{label}:")
if isinstance(data, dict):
print(f"{prefix} {json.dumps(data, ensure_ascii=False, indent=2)}")
else:
print(f"{prefix} {data}")
async def test_workflow_data_flow():
"""测试工作流数据流转"""
print_section("工作流数据流转测试")
# 模拟一个简单的工作流
workflow_data = {
"nodes": [
{
"id": "start-1",
"type": "start",
"position": {"x": 100, "y": 100},
"data": {
"label": "开始"
}
},
{
"id": "llm-1",
"type": "llm",
"position": {"x": 300, "y": 100},
"data": {
"label": "LLM处理",
"provider": "deepseek",
"model": "deepseek-chat",
"prompt": "请处理用户请求。",
"temperature": 0.5,
"max_tokens": 1500
}
},
{
"id": "end-1",
"type": "end",
"position": {"x": 500, "y": 100},
"data": {
"label": "结束",
"output_format": "text"
}
}
],
"edges": [
{"id": "e1", "source": "start-1", "target": "llm-1"},
{"id": "e2", "source": "llm-1", "target": "end-1"}
]
}
# 模拟前端发送的输入数据
input_data = {
"query": "苹果英语怎么讲?",
"USER_INPUT": "苹果英语怎么讲?"
}
print_section("1. 初始输入数据")
print_data("input_data", input_data)
# 创建引擎不使用logger避免数据库依赖
engine = WorkflowEngine("test-workflow", workflow_data)
# 重写get_node_input方法添加详细日志
original_get_node_input = engine.get_node_input
def logged_get_node_input(node_id, node_outputs, active_edges=None):
print_section(f"获取节点输入: {node_id}")
print_data("node_outputs", node_outputs)
result = original_get_node_input(node_id, node_outputs, active_edges)
print_data(f"返回的input_data (for {node_id})", result)
return result
engine.get_node_input = logged_get_node_input
# 重写execute_node方法添加详细日志
original_execute_node = engine.execute_node
async def logged_execute_node(node, input_data):
node_id = node.get('id')
node_type = node.get('type')
print_section(f"执行节点: {node_id} ({node_type})")
print_data("节点配置", node.get('data', {}))
print_data("输入数据", input_data)
result = await original_execute_node(node, input_data)
print_data("执行结果", result)
# 如果是LLM节点特别关注prompt和输出
if node_type == 'llm':
print_section(f"LLM节点详细分析: {node_id}")
node_data = node.get('data', {})
prompt = node_data.get('prompt', '')
print_data("原始prompt", prompt)
print_data("输入数据", input_data)
# 模拟prompt格式化逻辑
if isinstance(input_data, dict):
# 检查是否有嵌套的input字段
nested_input = input_data.get('input')
if isinstance(nested_input, dict):
print("⚠️ 发现嵌套的input字段")
print_data("嵌套input内容", nested_input)
# 尝试提取user_query
user_query = None
for key in ['query', 'input', 'text', 'message', 'content', 'user_input', 'USER_INPUT']:
if key in nested_input:
user_query = nested_input[key]
print(f"✅ 从嵌套input中提取到user_query: {key} = {user_query}")
break
else:
# 从顶层提取
user_query = None
for key in ['query', 'input', 'text', 'message', 'content', 'user_input', 'USER_INPUT']:
if key in input_data:
value = input_data[key]
if isinstance(value, str):
user_query = value
print(f"✅ 从顶层提取到user_query: {key} = {user_query}")
break
return result
engine.execute_node = logged_execute_node
# 执行工作流
print_section("开始执行工作流")
try:
result = await engine.execute(input_data)
print_section("工作流执行完成")
print_data("最终结果", result)
except Exception as e:
print_section("执行出错")
print(f"错误: {str(e)}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(test_workflow_data_flow())

View File

@@ -0,0 +1,529 @@
#!/usr/bin/env python3
"""
工作流测试工具
支持通过Agent名称和用户输入来测试工作流执行
"""
import requests
import json
import time
import sys
import argparse
# API基础URL
BASE_URL = "http://localhost:8037"
def print_section(title):
"""打印分隔线"""
print("\n" + "=" * 80)
print(f" {title}")
print("=" * 80)
def print_info(message):
"""打印信息"""
print(f" {message}")
def print_success(message):
"""打印成功信息"""
print(f"{message}")
def print_error(message):
"""打印错误信息"""
print(f"{message}")
def print_warning(message):
"""打印警告信息"""
print(f"⚠️ {message}")
def login(username="admin", password="123456"):
"""
用户登录
Returns:
tuple: (success: bool, token: str or None, headers: dict or None)
"""
print_section("1. 用户登录")
login_data = {
"username": username,
"password": password
}
try:
response = requests.post(f"{BASE_URL}/api/v1/auth/login", data=login_data)
if response.status_code != 200:
print_error(f"登录失败: {response.status_code}")
print(f"响应: {response.text}")
return False, None, None
token = response.json().get("access_token")
if not token:
print_error("登录失败: 未获取到token")
return False, None, None
print_success(f"登录成功 (用户: {username})")
headers = {"Authorization": f"Bearer {token}"}
return True, token, headers
except requests.exceptions.ConnectionError:
print_error("无法连接到后端服务,请确保后端服务正在运行")
print_info(f"后端服务地址: {BASE_URL}")
return False, None, None
except Exception as e:
print_error(f"登录异常: {str(e)}")
return False, None, None
def find_agent_by_name(agent_name, headers):
"""
通过名称查找Agent
Args:
agent_name: Agent名称
headers: 请求头包含token
Returns:
tuple: (success: bool, agent: dict or None)
"""
print_section("2. 查找Agent")
print_info(f"搜索Agent: {agent_name}")
try:
# 搜索Agent
response = requests.get(
f"{BASE_URL}/api/v1/agents",
headers=headers,
params={"search": agent_name, "limit": 100}
)
if response.status_code != 200:
print_error(f"获取Agent列表失败: {response.status_code}")
print(f"响应: {response.text}")
return False, None
agents = response.json()
# 精确匹配名称
exact_match = None
for agent in agents:
if agent.get("name") == agent_name:
exact_match = agent
break
if exact_match:
agent_id = exact_match["id"]
agent_status = exact_match.get("status", "unknown")
print_success(f"找到Agent: {agent_name} (ID: {agent_id}, 状态: {agent_status})")
# 检查状态
if agent_status not in ["published", "running"]:
print_warning(f"Agent状态为 '{agent_status}',可能无法执行")
print_info("只有 'published''running' 状态的Agent可以执行")
return True, exact_match
# 如果没有精确匹配,显示相似的结果
if agents:
print_warning(f"未找到名称为 '{agent_name}' 的Agent")
print_info("找到以下相似的Agent:")
for agent in agents[:5]: # 只显示前5个
print(f" - {agent.get('name')} (ID: {agent.get('id')}, 状态: {agent.get('status')})")
else:
print_error(f"未找到任何Agent")
print_info("请检查Agent名称是否正确或先创建一个Agent")
return False, None
except Exception as e:
print_error(f"查找Agent异常: {str(e)}")
return False, None
def execute_agent(agent_id, user_input, headers):
"""
执行Agent工作流
Args:
agent_id: Agent ID
user_input: 用户输入内容
headers: 请求头
Returns:
tuple: (success: bool, execution_id: str or None)
"""
print_section("3. 执行Agent工作流")
print_info(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
)
if response.status_code != 201:
print_error(f"创建执行任务失败: {response.status_code}")
print(f"响应: {response.text}")
return False, None
execution = response.json()
execution_id = execution["id"]
status = execution.get("status")
print_success(f"执行任务已创建")
print_info(f"执行ID: {execution_id}")
print_info(f"状态: {status}")
return True, execution_id
except Exception as e:
print_error(f"创建执行任务异常: {str(e)}")
return False, None
def wait_for_completion(execution_id, headers, max_wait_time=300, poll_interval=2):
"""
等待执行完成
Args:
execution_id: 执行ID
headers: 请求头
max_wait_time: 最大等待时间(秒)
poll_interval: 轮询间隔(秒)
Returns:
tuple: (success: bool, status: str or None)
"""
print_section("4. 等待执行完成")
print_info(f"最大等待时间: {max_wait_time}")
print_info(f"轮询间隔: {poll_interval}")
start_time = time.time()
last_node = None
while True:
elapsed_time = time.time() - start_time
if elapsed_time > max_wait_time:
print_error(f"执行超时(超过{max_wait_time}秒)")
return False, "timeout"
try:
# 获取执行状态
status_response = requests.get(
f"{BASE_URL}/api/v1/executions/{execution_id}/status",
headers=headers
)
if status_response.status_code == 200:
status = status_response.json()
current_status = status.get("status")
progress = status.get("progress", 0)
current_node = status.get("current_node")
# 显示当前执行的节点
if current_node:
node_id = current_node.get("node_id", "unknown")
node_name = current_node.get("node_name", "unknown")
if node_id != last_node:
print_info(f"当前节点: {node_id} ({node_name})")
last_node = node_id
# 显示进度
elapsed_str = f"{int(elapsed_time)}"
print(f"⏳ 执行中... 状态: {current_status}, 进度: {progress}%, 耗时: {elapsed_str}", end="\r")
if current_status == "completed":
print() # 换行
print_success("执行完成!")
return True, "completed"
elif current_status == "failed":
print() # 换行
print_error("执行失败")
error = status.get("error", "未知错误")
print_error(f"错误信息: {error}")
return False, "failed"
time.sleep(poll_interval)
except KeyboardInterrupt:
print() # 换行
print_warning("用户中断执行")
return False, "interrupted"
except Exception as e:
print_error(f"获取执行状态异常: {str(e)}")
time.sleep(poll_interval)
def get_execution_result(execution_id, headers):
"""
获取执行结果
Args:
execution_id: 执行ID
headers: 请求头
Returns:
tuple: (success: bool, result: dict or None)
"""
print_section("5. 获取执行结果")
try:
response = requests.get(
f"{BASE_URL}/api/v1/executions/{execution_id}",
headers=headers
)
if response.status_code != 200:
print_error(f"获取执行结果失败: {response.status_code}")
print(f"响应: {response.text}")
return False, None
execution = response.json()
status = execution.get("status")
output_data = execution.get("output_data")
execution_time = execution.get("execution_time")
print_info(f"执行状态: {status}")
if execution_time:
print_info(f"执行时间: {execution_time}ms ({execution_time/1000:.2f}秒)")
print()
print("=" * 80)
print("输出结果:")
print("=" * 80)
if output_data:
if isinstance(output_data, dict):
# 如果 result 字段是字符串尝试解析它类似JSON节点的parse操作
if "result" in output_data and isinstance(output_data["result"], str):
try:
# 尝试使用 ast.literal_eval 解析Python字典字符串
import ast
parsed_result = ast.literal_eval(output_data["result"])
output_data = parsed_result
except:
# 如果解析失败尝试作为JSON解析
try:
parsed_result = json.loads(output_data["result"])
output_data = parsed_result
except:
pass
# 使用类似JSON节点的extract操作来提取文本
def json_extract(data, path):
"""类似JSON节点的extract操作使用路径提取数据"""
if not path or not isinstance(data, dict):
return None
# 支持 $.right.right.right 格式的路径
path = path.replace('$.', '').replace('$', '')
keys = path.split('.')
result = data
for key in keys:
if isinstance(result, dict) and key in result:
result = result[key]
else:
return None
return result
# 尝试使用路径提取:递归查找 right 字段直到找到字符串
def extract_text_by_path(data, depth=0, max_depth=10):
"""递归提取嵌套在right字段中的文本"""
if depth > max_depth:
return None
if isinstance(data, str):
# 如果是字符串且不是JSON格式返回它
if len(data) > 10 and not data.strip().startswith('{') and not data.strip().startswith('['):
return data
return None
if isinstance(data, dict):
# 优先查找 right 字段
if "right" in data:
right_value = data["right"]
# 如果 right 的值是字符串,直接返回
if isinstance(right_value, str) and len(right_value) > 10:
return right_value
# 否则递归查找
result = extract_text_by_path(right_value, depth + 1, max_depth)
if result:
return result
# 查找其他常见的输出字段
for key in ["output", "text", "content"]:
if key in data:
result = extract_text_by_path(data[key], depth + 1, max_depth)
if result:
return result
return None
return None
# 优先检查 result 字段JSON节点提取后的结果
if "result" in output_data and isinstance(output_data["result"], str):
text_output = output_data["result"]
else:
# 先尝试使用路径提取类似JSON节点的extract操作
# 尝试多个可能的路径
paths_to_try = [
"right.right.right", # 最常见的嵌套路径
"right.right",
"right",
"output",
"text",
"content"
]
text_output = None
for path in paths_to_try:
extracted = json_extract(output_data, f"$.{path}")
if extracted and isinstance(extracted, str) and len(extracted) > 10:
text_output = extracted
break
# 如果路径提取失败,使用递归提取
if not text_output:
text_output = extract_text_by_path(output_data)
if text_output and isinstance(text_output, str):
print(text_output)
print()
print_info(f"回答长度: {len(text_output)} 字符")
else:
# 如果无法提取显示格式化的JSON
print(json.dumps(output_data, ensure_ascii=False, indent=2))
else:
print(output_data)
else:
print("(无输出数据)")
print("=" * 80)
return True, execution
except Exception as e:
print_error(f"获取执行结果异常: {str(e)}")
return False, None
def main():
"""主函数"""
parser = argparse.ArgumentParser(
description="工作流测试工具 - 通过Agent名称和用户输入测试工作流执行",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
# 使用默认参数(交互式输入)
python3 test_workflow_tool.py
# 指定Agent名称和用户输入
python3 test_workflow_tool.py -a "智能需求分析与解决方案生成器" -i "生成一个导出androidlog的脚本"
# 指定用户名和密码
python3 test_workflow_tool.py -u admin -p 123456 -a "Agent名称" -i "用户输入"
"""
)
parser.add_argument(
"-a", "--agent-name",
type=str,
help="Agent名称如果不指定将交互式输入"
)
parser.add_argument(
"-i", "--input",
type=str,
help="用户输入内容(如果不指定,将交互式输入)"
)
parser.add_argument(
"-u", "--username",
type=str,
default="admin",
help="登录用户名(默认: admin"
)
parser.add_argument(
"-p", "--password",
type=str,
default="123456",
help="登录密码(默认: 123456"
)
parser.add_argument(
"--max-wait",
type=int,
default=300,
help="最大等待时间(秒,默认: 300"
)
parser.add_argument(
"--poll-interval",
type=float,
default=2.0,
help="轮询间隔(秒,默认: 2.0"
)
args = parser.parse_args()
# 打印标题
print("=" * 80)
print(" 工作流测试工具")
print("=" * 80)
# 1. 登录
success, token, headers = login(args.username, args.password)
if not success:
sys.exit(1)
# 2. 获取Agent名称
agent_name = args.agent_name
if not agent_name:
agent_name = input("\n请输入Agent名称: ").strip()
if not agent_name:
print_error("Agent名称不能为空")
sys.exit(1)
# 3. 查找Agent
success, agent = find_agent_by_name(agent_name, headers)
if not success or not agent:
sys.exit(1)
agent_id = agent["id"]
# 4. 获取用户输入
user_input = args.input
if not user_input:
user_input = input("\n请输入用户输入内容: ").strip()
if not user_input:
print_error("用户输入不能为空")
sys.exit(1)
# 5. 执行Agent
success, execution_id = execute_agent(agent_id, user_input, headers)
if not success:
sys.exit(1)
# 6. 等待执行完成
success, status = wait_for_completion(
execution_id,
headers,
max_wait_time=args.max_wait,
poll_interval=args.poll_interval
)
if not success:
if status == "timeout":
print_warning("执行超时,但可能仍在后台运行")
print_info(f"执行ID: {execution_id}")
print_info("可以通过API查询执行状态")
sys.exit(1)
# 7. 获取执行结果
success, result = get_execution_result(execution_id, headers)
if not success:
sys.exit(1)
# 完成
print_section("测试完成")
print_success("工作流测试成功完成!")
print_info(f"执行ID: {execution_id}")
print_info(f"Agent: {agent_name}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""
测试「知你客服6号」Agent登录 -> 创建执行 -> 轮询直到结束。
用法:
python test_zhini_kefu_6.py
python test_zhini_kefu_6.py --base-url http://127.0.0.1:8037
set PLATFORM_BASE_URL=... && python test_zhini_kefu_6.py
依赖: requests与项目其他测试脚本一致
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
import requests
# 知你客服6号本地平台 Agent 管理中的名称对应 ID若你环境不同请改此处或传 --agent-id
DEFAULT_AGENT_ID = "2acc84d5-814b-4d61-9703-94a4b117375f"
DEFAULT_MESSAGE = "你好"
def main() -> int:
if sys.platform == "win32" and hasattr(sys.stdout, "reconfigure"):
try:
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
except Exception:
pass
parser = argparse.ArgumentParser(description="测试知你客服6号发送一条用户消息并打印结果")
parser.add_argument(
"--base-url",
default=os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037"),
help="平台 API 根地址(默认 http://127.0.0.1:8037",
)
parser.add_argument("--username", default=os.getenv("PLATFORM_USERNAME", "admin"))
parser.add_argument("--password", default=os.getenv("PLATFORM_PASSWORD", "123456"))
parser.add_argument("--agent-id", default=os.getenv("ZHINI_6_AGENT_ID", DEFAULT_AGENT_ID))
parser.add_argument("--message", "-m", default=DEFAULT_MESSAGE, help="用户消息,默认:你好")
parser.add_argument(
"--user-id",
default="script_test_zhini6",
help="多轮记忆隔离用 user_id可选",
)
parser.add_argument("--timeout", type=int, default=180, help="轮询最长秒数")
parser.add_argument("--poll", type=float, default=0.8, help="轮询间隔秒")
args = parser.parse_args()
base = args.base_url.rstrip("/")
print("=" * 60)
print("知你客服6号 执行测试")
print(" base_url :", base)
print(" agent_id :", args.agent_id)
print(" message :", args.message)
print("=" * 60)
# 1. 登录
r = requests.post(
f"{base}/api/v1/auth/login",
data={"username": args.username, "password": args.password},
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
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
msg = args.message
body = {
"agent_id": args.agent_id,
"input_data": {
"query": msg,
"USER_INPUT": msg,
"user_id": args.user_id,
},
}
# 2. 创建执行
r2 = requests.post(f"{base}/api/v1/executions", headers=headers, json=body, timeout=30)
if r2.status_code != 201:
print("创建执行失败:", r2.status_code, r2.text[:1000], file=sys.stderr)
return 1
ex = r2.json()
eid = ex["id"]
print("已创建执行:", eid)
print("初始状态:", ex.get("status"))
# 3. 轮询
deadline = time.time() + args.timeout
status = ex.get("status", "pending")
while status in ("pending", "running") and time.time() < deadline:
time.sleep(args.poll)
rs = requests.get(f"{base}/api/v1/executions/{eid}", headers=headers, timeout=60)
if rs.status_code != 200:
print("查询执行失败:", rs.status_code, rs.text[:500], file=sys.stderr)
return 1
detail = rs.json()
status = detail.get("status", status)
print(" ...", status)
final = requests.get(f"{base}/api/v1/executions/{eid}", headers=headers, timeout=60).json()
status = final.get("status")
print()
print("最终状态:", status)
if final.get("error_message"):
print("错误信息:", final["error_message"])
od = final.get("output_data")
if od is not None:
print("output_data:")
print(json.dumps(od, ensure_ascii=False, indent=2))
else:
print("output_data: null")
return 0 if status == "completed" else 2
if __name__ == "__main__":
raise SystemExit(main())