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:
174
scripts/testing/test_adb_tool.py
Normal file
174
scripts/testing/test_adb_tool.py
Normal 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)
|
||||
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,
|
||||
)
|
||||
69
scripts/testing/test_coding_agent_execution.py
Normal file
69
scripts/testing/test_coding_agent_execution.py
Normal 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()
|
||||
135
scripts/testing/test_database_query_tool.py
Normal file
135
scripts/testing/test_database_query_tool.py
Normal 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()
|
||||
175
scripts/testing/test_memory_functionality.py
Normal file
175
scripts/testing/test_memory_functionality.py
Normal 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()
|
||||
66
scripts/testing/test_output_variable_extraction.py
Normal file
66
scripts/testing/test_output_variable_extraction.py
Normal 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测试完成")
|
||||
368
scripts/testing/test_tool_calling_visualization.py
Normal file
368
scripts/testing/test_tool_calling_visualization.py
Normal 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()
|
||||
169
scripts/testing/test_workflow_data_flow.py
Normal file
169
scripts/testing/test_workflow_data_flow.py
Normal 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())
|
||||
529
scripts/testing/test_workflow_tool.py
Normal file
529
scripts/testing/test_workflow_tool.py
Normal 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()
|
||||
134
scripts/testing/test_zhini_kefu_6.py
Normal file
134
scripts/testing/test_zhini_kefu_6.py
Normal 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())
|
||||
Reference in New Issue
Block a user