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

485
scripts/create_issues.py Normal file
View File

@@ -0,0 +1,485 @@
"""批量创建 Gitea 工单 — 项目下一步需求清单"""
import requests
import json
TOKEN = "fbc9ee7f96635793f4844187eac5c0e573480721"
BASE = "http://101.43.95.130:3001/api/v1/repos/admin/aiagent/issues"
HEADERS = {
"Authorization": f"token {TOKEN}",
"Content-Type": "application/json"
}
ISSUES = [
# ===== Phase 4: 容错与共享 =====
{
"title": "[Phase 4.1] 降级/回退链 — 模型/Agent 失败自动切换备用方案",
"body": """## 目标
主模型调用失败自动切换 fallback_llmAgent 执行失败自动切换 fallback_agent。
## 涉及改动
- AgentLLMConfig 添加 fallback_llm 字段
- Agent 节点 data 添加 fallback_agent 字段
- core.py LLM 调用失败后切换 fallback
- workflow_engine.py Agent 节点失败后切换 fallback_agent
## 验收
- 主模型不可用 → 自动切备用模型
- Agent 执行失败 → 自动切备选 Agent
- 所有备用方案都失败 → 抛出明确错误
## 预计工作量
4-6 小时""",
},
{
"title": "[Phase 4.2] Agent 间知识共享 — 打破记忆隔离",
"body": """## 目标
打破 Agent 记忆隔离Agent A 学到的知识 Agent B 也能检索使用。
## 方案
- 新增 GlobalKnowledge 模型content, embedding, source_agent_id, tags
- Agent 执行完成后自动提取关键知识写入全局知识池
- Agent 初始化时从全局知识池检索相关知识
- 自主进化创建的 Agent 自动继承创建者的知识
## 涉及文件
- models/agent.py — 新增 GlobalKnowledge 模型
- agent_runtime/memory.py — initialize() 添加全局知识加载
- agent_runtime/core.py — 执行完毕提取知识写入全局
## 预计工作量
8-12 小时""",
},
{
"title": "[Phase 4.3] Agent 异步执行 — 填空 execute_agent_task",
"body": """## 问题
backend/app/tasks/agent_tasks.py 中 execute_agent_task 当前是空壳占位符(返回 pending定时调度无法真正异步执行 Agent。
## 方案
1. 从 DB 加载 Agent 配置
2. 构造 AgentRuntime
3. 异步执行并更新 execution 记录
4. 完成通知(飞书/WebSocket/站内通知)
## 涉及文件
- tasks/agent_tasks.py — 实现 execute_agent_task
- tasks/scheduler_tasks.py — 调度触发真正的异步执行
## 预计工作量
4-6 小时""",
},
# ===== 监控告警前端 =====
{
"title": "[监控] 系统监控面板 — CPU/内存/磁盘 + 执行统计",
"body": """## 当前状态
后端 API 已完成GET /api/v1/monitoring/overview、GET /api/v1/monitoring/statistics
前端界面缺失。
## 需要完成
- 系统资源监控CPU、内存、磁盘
- 执行统计图表(成功率、执行时间、错误率)
- 实时执行状态看板
## 预计工作量
4-6 小时""",
},
{
"title": "[监控] 告警规则管理页面",
"body": """## 当前状态
后端 API 已完成GET/POST /api/v1/alert-rules、PUT/DELETE /api/v1/alert-rules/{id}
前端界面缺失。
## 需要完成
- 告警规则列表
- 告警规则创建/编辑表单(条件、阈值、通知方式)
- 启用/禁用开关
## 预计工作量
4-6 小时""",
},
{
"title": "[监控] 告警日志查看页面",
"body": """## 当前状态
后端 API 已完成GET /api/v1/alert-rules/{id}/logs
前端界面缺失。
## 需要完成
- 告警历史列表(时间、规则、级别、状态)
- 告警详情查看 + 筛选分页
- 告警通知配置邮件、Webhook、飞书
## 预计工作量
4-6 小时""",
},
# ===== DevOps / 生产就绪 =====
{
"title": "[DevOps] Docker 生产环境配置 + 多环境管理",
"body": """## 目标
从开发环境升级到生产就绪的容器化部署。
## 需要完成
- Docker Compose 生产配置(资源限制、健康检查、重启策略)
- 多环境配置管理dev / staging / prod
- 配置文件加密(敏感信息不提交 Git
- Nginx 反向代理 + HTTPS
## 预计工作量
8-12 小时""",
},
{
"title": "[DevOps] Prometheus + Grafana 监控集成",
"body": """## 目标
建立生产级指标收集和可视化。
## 需要完成
- Prometheus 指标收集(业务指标:执行数/成功率/耗时 + 系统指标CPU/内存/网络)
- Grafana 仪表板(系统监控 + 业务监控)
- FastAPI metrics 端点暴露
- Celery worker 指标收集
## 预计工作量
8-12 小时""",
},
{
"title": "[DevOps] ELK 日志聚合系统",
"body": """## 目标
集中化日志管理,替代本地 backend.log 文件。
## 需要完成
- Elasticsearch + Logstash + Kibana 容器化部署
- FastAPI 日志接入 Logstash
- Celery 任务日志接入
- 日志查询和分析界面Kibana
## 预计工作量
8-12 小时""",
},
{
"title": "[DevOps] CI/CD 流水线 — GitHub Actions",
"body": """## 目标
自动化测试→构建→部署流程。
## 需要完成
- 自动化测试流程pytest + vitest
- 代码质量检查ESLint + Pylint + Black
- 自动化 Docker 镜像构建
- 自动化部署到 staging 环境
## 预计工作量
10-15 小时""",
},
{
"title": "[安全] API 限流保护",
"body": """## 目标
防止 API 滥用,保护后端服务。
## 需要完成
- 全局限流(每 IP 每分钟 N 次)
- 用户级别限流(按 token/user_id
- Agent 对话接口更严格限流
- 限流响应头X-RateLimit-*
## 技术方案
可使用 slowapiFastAPI 限流库)或 Redis + 滑动窗口。
## 预计工作量
4-6 小时""",
},
{
"title": "[安全] 密钥管理 — 敏感信息加密存储",
"body": """## 问题
当前 API Key、数据库密码等敏感信息明文存储在 .env 文件中。
## 方案
- 使用环境变量 + Docker secrets 管理生产密钥
- 开发环境使用 .env.local加入 .gitignore
- 数据库存储的 API Key 使用 AES 加密
## 预计工作量
4-6 小时""",
},
# ===== 用户体验 =====
{
"title": "[UX] 工作流编辑器优化 — 自动布局 + 节点搜索",
"body": """## 目标
提升复杂工作流的编辑效率。
## 需要完成
- 节点自动布局(一键美化排列)
- 节点搜索/筛选(按名称、类型快速定位)
- 画布小地图minimap导航
- 节点分组/折叠功能
## 预计工作量
8-10 小时""",
},
{
"title": "[UX] Agent 快速测试功能",
"body": """## 目标
在 Agent 配置页面直接测试,不需跳转到对话页。
## 需要完成
- Agent 配置页嵌入测试对话面板
- 测试结果实时显示(思考链+工具调用+最终回答)
- 测试历史记录
- 快速切换模型对比测试
## 预计工作量
6-8 小时""",
},
{
"title": "[UX] Agent 使用统计和分析",
"body": """## 目标
量化每个 Agent 的使用情况。
## 需要完成
- 每个 Agent 调用次数、成功率、平均耗时
- Token 消耗统计
- 工具调用频次排行
- 按时间维度(日/周/月)展示趋势图
## 预计工作量
6-8 小时""",
},
{
"title": "[UX] 移动端适配",
"body": """## 目标
核心功能支持移动端访问。
## 需要完成
- 响应式布局优化
- 移动端 Agent 对话页面
- 移动端执行状态查看(只读)
- 移动端工作流查看(只读)
## 预计工作量
15-20 小时""",
},
# ===== 高级功能 =====
{
"title": "[高级] 主控台/应用商店 — 面向业务用户的一站式入口",
"body": """## 目标
让业务用户选模板→填参数→执行→看结果,不需了解工作流细节。
## 需要完成
- MainConsole 主入口页面
- TemplateMarket 模板市场(浏览、搜索、评分、收藏)
- ExecutionBoard 父子执行链看板
- 一键从模板创建并执行
## 预计工作量
15-20 小时""",
},
{
"title": "[高级] 统一 DSL — 场景可编程输入",
"body": """## 目标
让不同模板复用统一的输入契约,降低场景迁移成本。
## 方案
- 新增 scenario_dsl.py 定义标准输入:目标、约束、产物、验收标准
- 工作流入口处做 DSL 校验与标准化映射
- 所有模板脚本迁移到 DSL 输入
## 预计工作量
8-12 小时""",
},
{
"title": "[高级] 成本预算治理 — 按 Agent 设置预算阈值",
"body": """## 目标
防止 Agent 执行失控,支持组织级运营。
## 方案
- 增加预算控制max_steps、工具调用上限、token 估算上限
- 新增预算配置模型,按租户/项目/Agent 维度配置
- 预算超限告警与熔断
## 预计工作量
8-12 小时""",
},
{
"title": "[高级] 插件系统 — 第三方自定义节点 + 插件市场",
"body": """## 目标
让外部开发者可以开发自定义节点插件,扩展平台能力。
## 方案
- 插件注册机制(插件清单、入口文件、依赖声明)
- 自定义节点插件开发框架SDK + 脚手架)
- 插件市场(上传、下载、评分、版本管理)
- 插件安全沙箱
## 预计工作量
30-40 小时(大型功能,可分期交付)""",
},
{
"title": "[高级] 多租户支持 — SaaS 化",
"body": """## 目标
支持多个组织/团队独立使用平台,数据隔离。
## 方案
- 租户模型和 API
- 数据隔离(数据库级 / Schema 级 / 行级)
- 资源配额管理
- 租户管理界面
## 预计工作量
20-30 小时(大型功能)""",
},
# ===== 质量保障 =====
{
"title": "[测试] 单元测试覆盖率提升至 80%+",
"body": """## 当前状态
测试框架已搭建pytest + vitest但覆盖率较低。
## 需要完成
- 后端核心模块覆盖率提升workflow_engine、agent_runtime、core
- 前端核心组件测试AgentChat、WorkflowDesigner
- API 端点测试完善
## 预计工作量
20-30 小时(持续投入)""",
},
{
"title": "[测试] E2E 测试 — Playwright 核心流程",
"body": """## 目标
用 Playwright 覆盖核心用户流程的端到端测试。
## 测试场景
- 用户登录→创建 Agent→对话→查看结果
- 用户登录→创建工作流→添加节点→执行→查看日志
- Agent 编排→多 Agent 协作→查看执行链
## 预计工作量
10-15 小时""",
},
{
"title": "[测试] 性能压测 — Locust/k6",
"body": """## 目标
找到系统性能瓶颈,验证并发能力。
## 需要完成
- Locust/k6 压测脚本编写
- 压测场景:并发对话、并发工作流执行、并发 API 调用
- 性能瓶颈分析 + 输出性能测试报告
## 预计工作量
8-12 小时""",
},
{
"title": "[测试] 安全扫描 — OWASP ZAP + 依赖漏洞检查",
"body": """## 目标
发现并修复安全漏洞。
## 需要完成
- OWASP ZAP 扫描 Web 应用
- 依赖漏洞检查pip-audit / npm audit
- Docker 镜像漏洞扫描
- API 接口权限验证
## 预计工作量
8-12 小时""",
},
# ===== Agent 能力拓展 =====
{
"title": "[Agent] 多模态 Agent — 图片识别 + 语音输入",
"body": """## 目标
让 Agent 支持图片和语音输入,不只限于文本。
## 当前基础
tessdata 目录已存在OCR图片上传功能已有。
## 需要完成
- 图片理解 Agent上传图片→OCR 提取文字→Agent 分析)
- 语音转文字输入Whisper API 集成)
- 前端输入框添加图片/语音入口
## 预计工作量
12-16 小时""",
},
{
"title": "[Agent] Agent 协作工作台 — 可视化多 Agent 编排",
"body": """## 目标
用可视化方式设计和执行多 Agent 协作流程。
## 需要完成
- 协作流程图编辑器(拖拽 Agent 节点+连线)
- 4 种协作模式可视化配置
- 协作结果总览面板
- 协作模板保存和复用
## 预计工作量
12-16 小时""",
},
{
"title": "[Agent] Agent 技能商店 — 公共市场共享",
"body": """## 目标
让用户发布和共享 Agent、工具、工作流模板。
## 需要完成
- Agent 发布到公共市场(名称、描述、评分、下载数)
- 工具和工作流模板发布
- 评分和评论系统
- 分类浏览和搜索
## 预计工作量
15-20 小时""",
},
]
def main():
print(f"准备创建 {len(ISSUES)} 个工单到 admin/aiagent ...\n")
created = []
failed = []
for i, issue in enumerate(ISSUES):
payload = {
"title": issue["title"],
"body": issue["body"],
"assignee": "admin",
}
try:
r = requests.post(BASE, headers=HEADERS, json=payload, timeout=15)
if r.status_code == 201:
data = r.json()
num = data["number"]
print(f" [{i+1:2d}/{len(ISSUES)}] #{num} {issue['title']}")
created.append(num)
else:
print(f" [{i+1:2d}/{len(ISSUES)}] FAIL ({r.status_code}): {issue['title']}")
print(f" {r.text[:200]}")
failed.append(issue["title"])
except Exception as e:
print(f" [{i+1:2d}/{len(ISSUES)}] ERROR: {e}")
failed.append(issue["title"])
print(f"\n=== 完成 ===")
print(f"成功: {len(created)}")
print(f"失败: {len(failed)}")
if created:
print(f"工单编号: #{created[0]} ~ #{created[-1]}")
if failed:
print(f"失败列表: {failed}")
# 验证
print("\n--- 验证:当前所有 Open 工单 ---")
r = requests.get(
f"{BASE}?state=open&limit=50",
headers=HEADERS,
timeout=10
)
if r.status_code == 200:
issues = r.json()
print(f"{len(issues)} 个开放工单:")
for iss in issues:
print(f" #{iss['number']} [{iss['state']}] {iss['title']}")
if __name__ == "__main__":
main()

38
scripts/startup/start.sh Normal file
View File

@@ -0,0 +1,38 @@
#!/bin/bash
# 低代码智能体平台启动脚本
echo "🚀 启动低代码智能体平台..."
echo ""
# 检查Docker是否运行
if ! docker info > /dev/null 2>&1; then
echo "❌ Docker 未运行,请先启动 Docker"
exit 1
fi
# 检查docker-compose是否可用
if ! command -v docker-compose &> /dev/null; then
echo "❌ docker-compose 未安装,请先安装 docker-compose"
exit 1
fi
echo "📦 启动 Docker Compose 服务..."
docker-compose -f docker-compose.dev.yml up -d
echo ""
echo "⏳ 等待服务启动..."
sleep 5
echo ""
echo "✅ 服务启动完成!"
echo ""
echo "📍 访问地址:"
echo " - 前端: http://localhost:8038"
echo " - 后端API: http://localhost:8037"
echo " - API文档: http://localhost:8037/docs"
echo " - 健康检查: http://localhost:8037/health"
echo ""
echo "📋 查看日志: docker-compose -f docker-compose.dev.yml logs -f"
echo "🛑 停止服务: docker-compose -f docker-compose.dev.yml down"
echo ""

View File

@@ -0,0 +1,219 @@
@echo off
echo ==============================================
echo 低代码智能体平台 - Windows 启动脚本
echo ==============================================
echo.
REM 检查Python是否安装
python --version >nul 2>&1
if errorlevel 1 (
echo ❌ Python 未安装或未添加到系统PATH
echo 请安装 Python 3.11+ 并确保在PATH中
pause
exit /b 1
)
REM 检查Node.js是否安装
node --version >nul 2>&1
if errorlevel 1 (
echo ❌ Node.js 未安装或未添加到系统PATH
echo 请安装 Node.js 18+ 并确保在PATH中
pause
exit /b 1
)
REM 检查pnpm是否安装
pnpm --version >nul 2>&1
if errorlevel 1 (
echo ⚠️ pnpm 未安装,正在安装...
npm install -g pnpm
if errorlevel 1 (
echo ❌ pnpm 安装失败
pause
exit /b 1
)
)
echo ✅ 环境检查通过
echo.
REM 进入项目目录
cd /d "%~dp0"
echo ==============================================
echo 1. Redis 检查
echo ==============================================
echo.
REM 检查Redis服务是否运行
sc query Redis >nul 2>&1
if errorlevel 1 (
echo ❌ Redis 服务未运行
echo.
echo 请按以下步骤安装Redis
echo 1. 下载 Redis Windows 版本https://github.com/microsoftarchive/redis/releases
echo 2. 下载 Redis-x64-3.2.100.msi
echo 3. 运行安装程序,按照默认设置安装
echo 4. Redis 将作为 Windows 服务运行在 6379 端口
echo.
echo 安装完成后,请重新运行此脚本
pause
exit /b 1
) else (
echo ✅ Redis 服务正在运行
)
echo ==============================================
echo 2. 启动后端服务
echo ==============================================
echo.
REM 进入backend目录
cd backend
REM 检查虚拟环境
if not exist "venv\Scripts\activate" (
echo ⚠️ 虚拟环境不存在,正在创建...
python -m venv venv
if errorlevel 1 (
echo ❌ 虚拟环境创建失败
pause
exit /b 1
)
)
echo ✅ 虚拟环境检查通过
REM 激活虚拟环境并安装依赖
call venv\Scripts\activate
echo 📦 检查Python依赖...
pip list | findstr "fastapi" >nul
if errorlevel 1 (
echo ⚠️ 正在安装Python依赖...
pip install -r requirements.txt
if errorlevel 1 (
echo ❌ Python依赖安装失败
pause
exit /b 1
)
echo ✅ Python依赖安装完成
) else (
echo ✅ Python依赖已安装
)
echo.
echo 🔧 配置环境变量...
if not exist ".env" (
copy env.example .env >nul
echo ⚠️ 已创建 .env 文件,请检查配置
)
echo.
echo 🗄️ 运行数据库迁移...
alembic upgrade head
if errorlevel 1 (
echo ⚠️ 数据库迁移失败,继续启动...
)
echo.
echo 🌐 启动后端服务...
echo 后端服务将在 http://localhost:8037 启动
echo API文档http://localhost:8037/docs
echo.
start cmd /k "uvicorn app.main:app --host 0.0.0.0 --port 8037 --reload"
echo ⏳ 等待后端服务启动...
timeout /t 3 /nobreak >nul
echo.
echo ==============================================
echo 3. 启动 Celery Worker
echo ==============================================
echo.
echo 🔄 启动 Celery Worker...
start cmd /k "celery -A app.core.celery_app worker --loglevel=info"
echo ⏳ 等待 Celery Worker 启动...
timeout /t 2 /nobreak >nul
echo.
echo ==============================================
echo 4. 启动前端服务
echo ==============================================
echo.
REM 返回项目根目录
cd ..
REM 进入frontend目录
cd frontend
echo 📦 检查前端依赖...
if not exist "node_modules" (
echo ⚠️ 正在安装前端依赖...
pnpm install
if errorlevel 1 (
echo ❌ 前端依赖安装失败
pause
exit /b 1
)
echo ✅ 前端依赖安装完成
) else (
echo ✅ 前端依赖已安装
)
echo.
echo 🖥️ 启动前端服务...
echo 前端服务将在 http://localhost:3000 启动
echo.
start cmd /k "pnpm dev"
echo ⏳ 等待前端服务启动...
timeout /t 5 /nobreak >nul
echo.
echo ==============================================
echo 🎉 启动完成!
echo ==============================================
echo.
echo 服务访问地址:
echo 📍 前端界面: http://localhost:3000
echo 📍 后端API: http://localhost:8037
echo 📍 API文档: http://localhost:8037/docs
echo.
echo 服务状态:
echo ✅ Redis 服务: 运行中
echo ✅ 后端服务: 已启动
echo ✅ Celery Worker: 已启动
echo ✅ 前端服务: 已启动
echo.
echo 📋 重要提示:
echo 1. 首次访问需要注册新用户
echo 2. 保持所有命令行窗口打开
echo 3. 停止服务:关闭所有命令行窗口
echo.
echo ==============================================
echo.
REM 返回项目根目录
cd ..
echo 按任意键打开浏览器访问前端界面...
pause >nul
start http://localhost:3000
echo.
echo 脚本执行完成!
echo 按任意键退出...
pause >nul

View File

@@ -0,0 +1,239 @@
# 低代码智能体平台 - Windows PowerShell 启动脚本
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host "低代码智能体平台 - Windows 启动脚本" -ForegroundColor Cyan
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host ""
# 检查Python是否安装
try {
$pythonVersion = python --version 2>&1
Write-Host "✅ Python 版本: $pythonVersion" -ForegroundColor Green
} catch {
Write-Host "❌ Python 未安装或未添加到系统PATH" -ForegroundColor Red
Write-Host "请安装 Python 3.11+ 并确保在PATH中"
pause
exit 1
}
# 检查Node.js是否安装
try {
$nodeVersion = node --version
Write-Host "✅ Node.js 版本: $nodeVersion" -ForegroundColor Green
} catch {
Write-Host "❌ Node.js 未安装或未添加到系统PATH" -ForegroundColor Red
Write-Host "请安装 Node.js 18+ 并确保在PATH中"
pause
exit 1
}
# 检查pnpm是否安装
try {
$pnpmVersion = pnpm --version
Write-Host "✅ pnpm 版本: $pnpmVersion" -ForegroundColor Green
} catch {
Write-Host "⚠️ pnpm 未安装,正在安装..." -ForegroundColor Yellow
npm install -g pnpm
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ pnpm 安装失败" -ForegroundColor Red
pause
exit 1
}
Write-Host "✅ pnpm 安装成功" -ForegroundColor Green
}
Write-Host "✅ 环境检查通过" -ForegroundColor Green
Write-Host ""
# 设置项目目录
$projectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $projectRoot
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host "1. Redis 检查" -ForegroundColor Cyan
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host ""
# 检查Redis服务是否运行
$redisService = Get-Service -Name "Redis" -ErrorAction SilentlyContinue
if ($null -eq $redisService -or $redisService.Status -ne "Running") {
Write-Host "❌ Redis 服务未运行" -ForegroundColor Red
Write-Host ""
Write-Host "请按以下步骤安装Redis" -ForegroundColor Yellow
Write-Host "1. 下载 Redis Windows 版本https://github.com/microsoftarchive/redis/releases"
Write-Host "2. 下载 Redis-x64-3.2.100.msi"
Write-Host "3. 运行安装程序,按照默认设置安装"
Write-Host "4. Redis 将作为 Windows 服务运行在 6379 端口"
Write-Host ""
Write-Host "安装完成后,请重新运行此脚本" -ForegroundColor Yellow
pause
exit 1
} else {
Write-Host "✅ Redis 服务正在运行 (状态: $($redisService.Status))" -ForegroundColor Green
}
Write-Host ""
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host "2. 启动后端服务" -ForegroundColor Cyan
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host ""
# 进入backend目录
Set-Location "$projectRoot\backend"
# 检查虚拟环境
if (-not (Test-Path "venv\Scripts\activate")) {
Write-Host "⚠️ 虚拟环境不存在,正在创建..." -ForegroundColor Yellow
python -m venv venv
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ 虚拟环境创建失败" -ForegroundColor Red
pause
exit 1
}
Write-Host "✅ 虚拟环境创建成功" -ForegroundColor Green
}
Write-Host "✅ 虚拟环境检查通过" -ForegroundColor Green
# 激活虚拟环境
$activateScript = "$projectRoot\backend\venv\Scripts\Activate.ps1"
if (Test-Path $activateScript) {
& $activateScript
} else {
Write-Host "❌ 虚拟环境激活脚本不存在: $activateScript" -ForegroundColor Red
pause
exit 1
}
Write-Host "📦 检查Python依赖..." -ForegroundColor Cyan
$fastapiInstalled = pip list | Select-String "fastapi"
if (-not $fastapiInstalled) {
Write-Host "⚠️ 正在安装Python依赖..." -ForegroundColor Yellow
pip install -r requirements.txt
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ Python依赖安装失败" -ForegroundColor Red
pause
exit 1
}
Write-Host "✅ Python依赖安装完成" -ForegroundColor Green
} else {
Write-Host "✅ Python依赖已安装" -ForegroundColor Green
}
Write-Host ""
Write-Host "🔧 配置环境变量..." -ForegroundColor Cyan
if (-not (Test-Path ".env")) {
Copy-Item env.example .env -ErrorAction SilentlyContinue
Write-Host "⚠️ 已创建 .env 文件,请检查配置" -ForegroundColor Yellow
}
Write-Host ""
Write-Host "🗄️ 运行数据库迁移..." -ForegroundColor Cyan
alembic upgrade head
if ($LASTEXITCODE -ne 0) {
Write-Host "⚠️ 数据库迁移失败,继续启动..." -ForegroundColor Yellow
}
Write-Host ""
Write-Host "🌐 启动后端服务..." -ForegroundColor Cyan
Write-Host "后端服务将在 http://localhost:8037 启动" -ForegroundColor Green
Write-Host "API文档http://localhost:8037/docs" -ForegroundColor Green
Write-Host ""
# 启动后端服务(新窗口)
Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd '$projectRoot\backend'; .\venv\Scripts\Activate.ps1; uvicorn app.main:app --host 0.0.0.0 --port 8037 --reload"
Write-Host "⏳ 等待后端服务启动..." -ForegroundColor Cyan
Start-Sleep -Seconds 3
Write-Host ""
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host "3. 启动 Celery Worker" -ForegroundColor Cyan
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "🔄 启动 Celery Worker..." -ForegroundColor Cyan
# Windows 下 prefork 池易卡住任务Redis 出现大量 unacked使用线程池更稳定
Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd '$projectRoot\backend'; .\venv\Scripts\Activate.ps1; celery -A app.core.celery_app worker --loglevel=info --pool=threads --concurrency=8"
Write-Host "⏳ 等待 Celery Worker 启动..." -ForegroundColor Cyan
Start-Sleep -Seconds 2
Write-Host ""
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host "4. 启动前端服务" -ForegroundColor Cyan
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host ""
# 返回项目根目录
Set-Location $projectRoot
# 进入frontend目录
Set-Location "$projectRoot\frontend"
Write-Host "📦 检查前端依赖..." -ForegroundColor Cyan
if (-not (Test-Path "node_modules")) {
Write-Host "⚠️ 正在安装前端依赖..." -ForegroundColor Yellow
pnpm install
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ 前端依赖安装失败" -ForegroundColor Red
pause
exit 1
}
Write-Host "✅ 前端依赖安装完成" -ForegroundColor Green
} else {
Write-Host "✅ 前端依赖已安装" -ForegroundColor Green
}
Write-Host ""
Write-Host "🖥️ 启动前端服务..." -ForegroundColor Cyan
Write-Host "前端服务将在 http://localhost:3000 启动" -ForegroundColor Green
Write-Host ""
# 启动前端服务(新窗口)
Start-Process powershell -ArgumentList "-NoExit", "-Command", "cd '$projectRoot\frontend'; pnpm dev"
Write-Host "⏳ 等待前端服务启动..." -ForegroundColor Cyan
Start-Sleep -Seconds 5
Write-Host ""
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host "🎉 启动完成!" -ForegroundColor Green
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "服务访问地址:" -ForegroundColor White
Write-Host " 📍 前端界面: http://localhost:3000" -ForegroundColor Yellow
Write-Host " 📍 后端API: http://localhost:8037" -ForegroundColor Yellow
Write-Host " 📍 API文档: http://localhost:8037/docs" -ForegroundColor Yellow
Write-Host ""
Write-Host "服务状态:" -ForegroundColor White
Write-Host " ✅ Redis 服务: 运行中" -ForegroundColor Green
Write-Host " ✅ 后端服务: 已启动" -ForegroundColor Green
Write-Host " ✅ Celery Worker: 已启动" -ForegroundColor Green
Write-Host " ✅ 前端服务: 已启动" -ForegroundColor Green
Write-Host ""
Write-Host "📋 重要提示:" -ForegroundColor White
Write-Host " 1. 首次访问需要注册新用户" -ForegroundColor Gray
Write-Host " 2. 保持所有PowerShell窗口打开" -ForegroundColor Gray
Write-Host " 3. 停止服务关闭所有PowerShell窗口" -ForegroundColor Gray
Write-Host ""
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host ""
# 返回项目根目录
Set-Location $projectRoot
Write-Host "是否要打开浏览器访问前端界面?(Y/N)" -ForegroundColor Cyan
$response = Read-Host
if ($response -eq "Y" -or $response -eq "y") {
Start-Process "http://localhost:3000"
}
Write-Host ""
Write-Host "脚本执行完成!" -ForegroundColor Green
Write-Host "按任意键退出..." -ForegroundColor Gray
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

9
scripts/startup/stop.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
# 低代码智能体平台停止脚本
echo "🛑 停止低代码智能体平台服务..."
docker-compose -f docker-compose.dev.yml down
echo "✅ 服务已停止"

View File

@@ -0,0 +1,8 @@
#!/bin/bash
# 查看工作流调试日志的脚本
echo "正在查看后端日志(包含 [rjb] 调试信息)..."
echo "按 Ctrl+C 退出"
echo ""
docker-compose -f docker-compose.dev.yml logs -f backend | grep --line-buffered "\[rjb\]"

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())

View File

@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""
查看执行日志的脚本
用于诊断数据流转问题
"""
import sys
import os
import json
from datetime import datetime
# 添加项目路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
from app.core.database import SessionLocal
from app.models.execution import Execution
from app.models.execution_log import ExecutionLog
def format_json(data):
"""格式化JSON数据"""
if isinstance(data, dict):
return json.dumps(data, ensure_ascii=False, indent=2)
return str(data)
def main():
"""主函数"""
db = SessionLocal()
try:
# 获取最近的执行记录
print("=" * 80)
print("查找最近的Agent执行记录...")
print("=" * 80)
execution = db.query(Execution).filter(
Execution.agent_id.isnot(None)
).order_by(Execution.created_at.desc()).first()
if not execution:
print("❌ 没有找到执行记录")
return
print(f"\n✅ 找到执行记录: {execution.id}")
print(f" 状态: {execution.status}")
print(f" 执行时间: {execution.execution_time}ms")
print(f" 创建时间: {execution.created_at}")
# 显示输入数据
print("\n" + "=" * 80)
print("输入数据 (input_data):")
print("=" * 80)
if execution.input_data:
print(format_json(execution.input_data))
else:
print("(空)")
# 显示输出数据
print("\n" + "=" * 80)
print("输出数据 (output_data):")
print("=" * 80)
if execution.output_data:
print(format_json(execution.output_data))
else:
print("(空)")
# 获取执行日志
print("\n" + "=" * 80)
print("执行日志 (按时间顺序):")
print("=" * 80)
logs = db.query(ExecutionLog).filter(
ExecutionLog.execution_id == execution.id
).order_by(ExecutionLog.timestamp.asc()).all()
if not logs:
print("❌ 没有找到执行日志")
return
for i, log in enumerate(logs, 1):
print(f"\n[{i}] {log.timestamp.strftime('%Y-%m-%d %H:%M:%S')} [{log.level}]")
print(f" 节点: {log.node_id or '(无)'} ({log.node_type or '(无)'})")
print(f" 消息: {log.message}")
if log.data:
print(f" 数据:")
data_str = format_json(log.data)
# 只显示前500个字符
if len(data_str) > 500:
print(data_str[:500] + "...")
else:
print(data_str)
if log.duration:
print(f" 耗时: {log.duration}ms")
# 特别关注LLM节点的输入输出
print("\n" + "=" * 80)
print("LLM节点详细分析:")
print("=" * 80)
llm_logs = [log for log in logs if log.node_type == 'llm']
if llm_logs:
for log in llm_logs:
if log.message == "节点开始执行" and log.data:
print(f"\n节点 {log.node_id} 的输入数据:")
input_data = log.data.get('input', {})
print(format_json(input_data))
if log.message == "节点执行完成" and log.data:
print(f"\n节点 {log.node_id} 的输出数据:")
output_data = log.data.get('output', {})
print(format_json(output_data))
else:
print("❌ 没有找到LLM节点的日志")
except Exception as e:
print(f"❌ 错误: {str(e)}")
import traceback
traceback.print_exc()
finally:
db.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""
查看Switch节点日志的专用脚本
用于诊断Switch节点的分支过滤问题
"""
import sys
import os
import json
from datetime import datetime
# 添加项目路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
from app.core.database import SessionLocal
from app.models.execution import Execution
from app.models.execution_log import ExecutionLog
def format_json(data):
"""格式化JSON数据"""
if isinstance(data, dict):
return json.dumps(data, ensure_ascii=False, indent=2)
return str(data)
def main():
"""主函数"""
db = SessionLocal()
try:
# 获取最近的执行记录
print("=" * 80)
print("查找最近的Agent执行记录...")
print("=" * 80)
execution = db.query(Execution).filter(
Execution.agent_id.isnot(None)
).order_by(Execution.created_at.desc()).first()
if not execution:
print("❌ 没有找到执行记录")
return
print(f"\n✅ 找到执行记录: {execution.id}")
print(f" 状态: {execution.status}")
print(f" 执行时间: {execution.execution_time}ms")
print(f" 创建时间: {execution.created_at}")
# 获取执行日志
print("\n" + "=" * 80)
print("Switch节点相关日志:")
print("=" * 80)
logs = db.query(ExecutionLog).filter(
ExecutionLog.execution_id == execution.id
).order_by(ExecutionLog.timestamp.asc()).all()
if not logs:
print("❌ 没有找到执行日志")
return
# 筛选Switch节点相关的日志
switch_logs = []
for log in logs:
if log.node_type == 'switch' or 'Switch' in log.message or '[rjb] Switch' in log.message:
switch_logs.append(log)
if not switch_logs:
print("❌ 没有找到Switch节点相关的日志")
print("\n所有日志节点类型:")
node_types = set(log.node_type for log in logs if log.node_type)
for nt in sorted(node_types):
print(f" - {nt}")
return
print(f"\n找到 {len(switch_logs)} 条Switch节点相关日志:\n")
for i, log in enumerate(switch_logs, 1):
print(f"[{i}] {log.timestamp.strftime('%H:%M:%S.%f')[:-3]} [{log.level}]")
print(f" 节点: {log.node_id or '(无)'} ({log.node_type or '(无)'})")
print(f" 消息: {log.message}")
if log.data:
print(f" 数据:")
data_str = format_json(log.data)
# 显示完整数据
for line in data_str.split('\n'):
print(f" {line}")
if log.duration:
print(f" 耗时: {log.duration}ms")
print()
# 特别分析Switch节点的匹配和过滤过程
print("=" * 80)
print("Switch节点执行流程分析:")
print("=" * 80)
match_logs = [log for log in switch_logs if '匹配' in log.message]
filter_logs = [log for log in switch_logs if '过滤' in log.message]
if match_logs:
print("\n📊 匹配阶段:")
for log in match_logs:
if log.data:
data = log.data
print(f" 节点 {log.node_id}:")
print(f" 字段: {data.get('field', 'N/A')}")
print(f" 字段值: {data.get('field_value', 'N/A')}")
print(f" 匹配的分支: {data.get('matched_case', 'N/A')}")
print(f" 处理后的输入键: {data.get('processed_input_keys', 'N/A')}")
if filter_logs:
print("\n🔍 过滤阶段:")
for log in filter_logs:
if log.data:
data = log.data
print(f" 节点 {log.node_id}:")
print(f" 匹配的分支: {data.get('branch', 'N/A')}")
print(f" 过滤前边数: {data.get('edges_before', 'N/A')}")
print(f" 保留边数: {data.get('edges_kept', 'N/A')}")
print(f" 移除边数: {data.get('edges_removed', 'N/A')}")
# 检查意图理解节点的输出
print("\n" + "=" * 80)
print("意图理解节点输出分析:")
print("=" * 80)
intent_logs = [log for log in logs if log.node_id and 'intent' in log.node_id.lower()]
if intent_logs:
for log in intent_logs:
if log.message == "节点执行完成" and log.data:
print(f"\n节点 {log.node_id} 的输出:")
output = log.data.get('output', {})
print(format_json(output))
else:
print("❌ 没有找到意图理解节点的日志")
# 检查所有节点的输出(用于调试)
print("\n" + "=" * 80)
print("所有节点输出摘要:")
print("=" * 80)
node_outputs = {}
for log in logs:
if log.message == "节点执行完成" and log.node_id:
node_outputs[log.node_id] = log.data.get('output', {})
for node_id, output in node_outputs.items():
if isinstance(output, str) and len(output) > 100:
print(f"{node_id}: {output[:100]}...")
else:
print(f"{node_id}: {output}")
except Exception as e:
print(f"❌ 错误: {str(e)}")
import traceback
traceback.print_exc()
finally:
db.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,403 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
===========================================
📋 每日工作总结自动生成器
功能每天早上9点 → 生成今日工作总结 → 推送到指定渠道
===========================================
"""
import json
import os
import sys
import datetime
import smtplib
import requests
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from pathlib import Path
# ===========================================
# 📝 第一部分:配置区(请按需修改)
# ===========================================
class Config:
"""所有配置集中管理,方便修改"""
# --- 数据来源方式 ---
# mode = "manual" → 手动输入今日事项
# mode = "file" → 从文件读取今日事项
DATA_MODE = "manual"
# 当 DATA_MODE = "file" 时,读取此文件
DATA_FILE = "today_work_items.txt"
# --- 推送方式(可多选)---
# 支持wecom企业微信、dingtalk钉钉、email邮件
PUSH_CHANNELS = ["wecom"] # 可改为 ["dingtalk"] 或 ["email"]
# ----- 企业微信机器人 -----
WECOM_WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY_HERE"
# ----- 钉钉机器人 -----
DINGTALK_WEBHOOK = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN_HERE"
# ----- 邮件配置 -----
SMTP_SERVER = "smtp.qq.com"
SMTP_PORT = 465
SMTP_USER = "your_email@qq.com"
SMTP_PASSWORD = "your_auth_code" # 邮件授权码,非密码
MAIL_TO = ["boss@company.com", "yourself@company.com"]
# --- 总结模板 ---
SUMMARY_TEMPLATE = """
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📅 今日工作总结 · {date}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
👤 姓名:{name}
🏢 部门:{department}
📌 今日工作事项:
{work_items}
📊 工作总结:
{summary}
✅ 明日计划:
{tomorrow_plan}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⏰ 生成时间:{generate_time}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
"""
# ===========================================
# 🔧 第二部分:核心功能
# ===========================================
class WorkSummaryGenerator:
"""工作总结生成器"""
def __init__(self, name="张三", department="技术部"):
self.name = name
self.department = department
self.today = datetime.date.today()
self.tomorrow = self.today + datetime.timedelta(days=1)
def get_work_items_from_input(self):
"""方式一:手动输入今日工作事项"""
print("\n📝 请输入你今天的工作事项(每行一条,输入空行结束):")
print("完成了XX功能开发 / 修复了XXbug / 参加了XX会议\n")
items = []
while True:
line = input("").strip()
if not line:
break
items.append(line)
return items
def get_work_items_from_file(self, filepath):
"""方式二:从文件读取今日工作事项"""
try:
with open(filepath, "r", encoding="utf-8") as f:
items = [line.strip() for line in f if line.strip()]
return items
except FileNotFoundError:
print(f"⚠️ 文件 {filepath} 不存在,请先创建!")
return []
def generate_summary(self, work_items):
"""生成工作总结(基于规则+模板你也可以接入AI API"""
if not work_items:
return "今日暂无工作记录。"
# 统计信息
total_items = len(work_items)
# 简单分类统计(关键词匹配)
categories = {
"开发": 0, "修复": 0, "会议": 0, "文档": 0,
"测试": 0, "沟通": 0, "其他": 0
}
keywords_map = {
"开发": ["开发", "编码", "编程", "实现", "搭建", "构建"],
"修复": ["修复", "解决", "处理", "修改", "bug", "Bug"],
"会议": ["会议", "评审", "讨论", "沟通会", "晨会", "周会"],
"文档": ["文档", "文档", "方案", "设计", "PPT", "报告"],
"测试": ["测试", "调试", "自测", "联调", "验证"],
"沟通": ["沟通", "对齐", "同步", "协调", "对接"],
}
for item in work_items:
matched = False
for cat, keywords in keywords_map.items():
if any(kw in item for kw in keywords):
categories[cat] += 1
matched = True
break
if not matched:
categories["其他"] += 1
# 生成总结文本
summary_parts = [f"今日共完成 {total_items} 项工作,具体如下:\n"]
# 分类汇总
active_cats = {k: v for k, v in categories.items() if v > 0}
if active_cats:
summary_parts.append("📊 工作类型分布:")
for cat, count in active_cats.items():
bar = "" * count
summary_parts.append(f" {cat}{bar} {count}")
summary_parts.append("")
# 逐项列出
summary_parts.append("📋 详细工作记录:")
for i, item in enumerate(work_items, 1):
summary_parts.append(f" {i}. {item}")
# 评价与建议
summary_parts.append(f"\n💡 今日小结:今日工作效率{'较高' if total_items >= 5 else '正常'}"
f"建议明日继续保持{'/优化时间管理' if total_items < 3 else ''}")
return "\n".join(summary_parts)
def generate_tomorrow_plan(self):
"""生成明日计划(可扩展为从配置文件读取)"""
plan_items = [
"继续推进当前开发任务",
"同步项目进度",
"代码审查",
]
return "\n".join(f" {i+1}. {item}" for i, item in enumerate(plan_items))
def build_summary_text(self, work_items, summary):
"""组装完整的总结文本"""
tomorrow_plan = self.generate_tomorrow_plan()
formatted_items = "\n".join(f"{item}" for item in work_items)
now_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return Config.SUMMARY_TEMPLATE.format(
date=self.today.strftime("%Y年%m月%d"),
name=self.name,
department=self.department,
work_items=formatted_items if formatted_items else " (暂无记录)",
summary=summary,
tomorrow_plan=tomorrow_plan,
generate_time=now_str
)
def run(self):
"""主运行流程"""
print(f"\n{'='*50}")
print(f"📅 今日工作总结生成器")
print(f"📆 {self.today.strftime('%Y年%m月%d')}")
print(f"{'='*50}\n")
# 1⃣ 获取工作事项
if Config.DATA_MODE == "file":
items = self.get_work_items_from_file(Config.DATA_FILE)
else:
items = self.get_work_items_from_input()
if not items:
print("⚠️ 没有输入任何工作事项,将生成空总结。")
# 2⃣ 生成总结
summary = self.generate_summary(items)
final_text = self.build_summary_text(items, summary)
# 3⃣ 本地保存
self.save_to_file(final_text)
# 4⃣ 推送
pusher = MessagePusher()
pusher.push(final_text)
return final_text
def save_to_file(self, content):
"""保存到本地文件"""
filename = f"工作总结_{self.today.strftime('%Y%m%d')}.md"
with open(filename, "w", encoding="utf-8") as f:
f.write(content)
print(f"\n✅ 已保存到本地文件:{os.path.abspath(filename)}")
# ===========================================
# 📨 第三部分:消息推送
# ===========================================
class MessagePusher:
"""消息推送器"""
def push(self, content):
"""根据配置推送消息到各个渠道"""
for channel in Config.PUSH_CHANNELS:
if channel == "wecom":
self.push_wecom(content)
elif channel == "dingtalk":
self.push_dingtalk(content)
elif channel == "email":
self.push_email(content)
else:
print(f"⚠️ 未知推送渠道: {channel}")
def push_wecom(self, content):
"""推送到企业微信机器人"""
url = Config.WECOM_WEBHOOK
if "YOUR_KEY_HERE" in url:
print("⚠️ 跳过企业微信推送未配置Webhook地址")
return
data = {
"msgtype": "markdown",
"markdown": {"content": content}
}
try:
resp = requests.post(url, json=data, timeout=10)
result = resp.json()
if result.get("errcode") == 0:
print("✅ 企业微信推送成功!")
else:
print(f"⚠️ 企业微信推送失败: {result}")
except Exception as e:
print(f"⚠️ 企业微信推送异常: {e}")
def push_dingtalk(self, content):
"""推送到钉钉机器人"""
url = Config.DINGTALK_WEBHOOK
if "YOUR_TOKEN_HERE" in url:
print("⚠️ 跳过钉钉推送未配置Webhook地址")
return
data = {
"msgtype": "markdown",
"markdown": {
"title": "今日工作总结",
"text": content
}
}
try:
resp = requests.post(url, json=data, timeout=10)
result = resp.json()
if result.get("errcode") == 0:
print("✅ 钉钉推送成功!")
else:
print(f"⚠️ 钉钉推送失败: {result}")
except Exception as e:
print(f"⚠️ 钉钉推送异常: {e}")
def push_email(self, content):
"""通过邮件推送"""
if "your_email" in Config.SMTP_USER:
print("⚠️ 跳过邮件推送:未配置邮箱信息")
return
try:
msg = MIMEMultipart()
msg["From"] = Config.SMTP_USER
msg["To"] = ", ".join(Config.MAIL_TO)
msg["Subject"] = f"今日工作总结 - {datetime.date.today().strftime('%Y-%m-%d')}"
msg.attach(MIMEText(content, "plain", "utf-8"))
with smtplib.SMTP_SSL(Config.SMTP_SERVER, Config.SMTP_PORT) as server:
server.login(Config.SMTP_USER, Config.SMTP_PASSWORD)
server.sendmail(Config.SMTP_USER, Config.MAIL_TO, msg.as_string())
print("✅ 邮件推送成功!")
except Exception as e:
print(f"⚠️ 邮件推送异常: {e}")
# ===========================================
# 🚀 第四部分:程序入口
# ===========================================
def setup_windows_task():
"""生成Windows任务计划程序导入脚本"""
script_path = os.path.abspath(sys.argv[0])
python_path = sys.executable
cmd = f'''@echo off
echo ========================================
echo 设置每日9点自动运行工作总结脚本
echo ========================================
REM 请以管理员身份运行此.bat文件
schtasks /create /tn "每日工作总结" /tr "{python_path} {script_path}" /sc daily /st 09:00 /f
if %errorlevel%==0 (
echo ✅ 定时任务创建成功每天早上9:00自动运行。
) else (
echo ❌ 创建失败,请尝试以管理员身份运行。
)
pause
'''
bat_path = "setup_windows_task.bat"
with open(bat_path, "w", encoding="utf-8") as f:
f.write(cmd)
print(f"\n📁 Windows定时任务脚本已生成{os.path.abspath(bat_path)}")
print(" → 右键点击该文件,选择「以管理员身份运行」即可设置定时任务!")
def setup_linux_cron():
"""输出Linux crontab配置说明"""
script_path = os.path.abspath(sys.argv[0])
python_path = sys.executable
print("\n" + "="*50)
print("🐧 Linux/macOS 定时任务设置crontab")
print("="*50)
print(f"在终端中执行以下命令添加定时任务:")
print(f"\n crontab -e")
print(f"\n然后添加一行:")
print(f"\n 0 9 * * * {python_path} {script_path}")
print(f"\n保存退出即可!📌")
if __name__ == "__main__":
import sys
# 检查是否有命令行参数
if len(sys.argv) > 1:
arg = sys.argv[1].lower()
if arg == "--setup-win":
setup_windows_task()
sys.exit(0)
elif arg == "--setup-linux":
setup_linux_cron()
sys.exit(0)
print("""
╔══════════════════════════════════════════╗
║ 🍊 每日工作总结生成器 ║
║ ║
║ 用法: ║
║ python daily_work_summary.py ║
║ python daily_work_summary.py --setup-win ║
║ python daily_work_summary.py --setup-linux ║
╚══════════════════════════════════════════╝
""")
# 运行主程序
generator = WorkSummaryGenerator(name="张三", department="技术部")
result = generator.run()
print("\n" + "="*50)
print("🎉 工作总结生成完成!")
print("="*50)
# 生成本地定时任务设置脚本
if sys.platform == "win32":
setup_windows_task()
else:
setup_linux_cron()
print("\n💡 小提示:修改 config.py 中的配置可自定义推送到不同渠道~")

View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
调试Switch节点的详细脚本
"""
import sys
import os
import json
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
from app.core.database import SessionLocal
from app.models.execution import Execution
from app.models.agent import Agent
def main():
db = SessionLocal()
try:
# 获取最近的执行记录
execution = db.query(Execution).filter(
Execution.agent_id.isnot(None)
).order_by(Execution.created_at.desc()).first()
if not execution:
print("❌ 没有找到执行记录")
return
print(f"执行ID: {execution.id}")
print(f"状态: {execution.status}")
print()
# 获取Agent配置
agent = db.query(Agent).filter(Agent.id == execution.agent_id).first()
if not agent:
print("❌ 没有找到Agent")
return
workflow_config = agent.workflow_config
nodes = workflow_config.get('nodes', [])
edges = workflow_config.get('edges', [])
# 找到Switch节点
switch_node = None
for node in nodes:
if node.get('type') == 'switch':
switch_node = node
break
if not switch_node:
print("❌ 没有找到Switch节点")
return
print("=" * 80)
print("Switch节点配置:")
print("=" * 80)
print(f"节点ID: {switch_node['id']}")
print(f"字段: {switch_node['data'].get('field')}")
print(f"Cases: {json.dumps(switch_node['data'].get('cases', {}), ensure_ascii=False, indent=2)}")
print(f"Default: {switch_node['data'].get('default')}")
print()
# 找到从Switch节点出发的边
print("=" * 80)
print("从Switch节点出发的边:")
print("=" * 80)
switch_edges = [e for e in edges if e.get('source') == switch_node['id']]
for edge in switch_edges:
print(f"边ID: {edge.get('id')}")
print(f" sourceHandle: {edge.get('sourceHandle')}")
print(f" target: {edge.get('target')}")
print()
# 查看执行结果
print("=" * 80)
print("执行结果中的节点输出:")
print("=" * 80)
if execution.output_data and 'node_results' in execution.output_data:
node_results = execution.output_data['node_results']
if switch_node['id'] in node_results:
switch_result = node_results[switch_node['id']]
print(f"Switch节点输出: {json.dumps(switch_result, ensure_ascii=False, indent=2)}")
else:
print("❌ Switch节点没有输出结果")
else:
print("❌ 没有找到节点执行结果")
# 检查哪些分支节点执行了
print()
print("=" * 80)
print("执行了的分支节点:")
print("=" * 80)
if execution.output_data and 'node_results' in execution.output_data:
node_results = execution.output_data['node_results']
for edge in switch_edges:
target_id = edge.get('target')
if target_id in node_results:
print(f"{target_id} (sourceHandle: {edge.get('sourceHandle')})")
else:
print(f"{target_id} (sourceHandle: {edge.get('sourceHandle')}) - 未执行")
except Exception as e:
print(f"❌ 错误: {str(e)}")
import traceback
traceback.print_exc()
finally:
db.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""
发布Agent脚本
"""
import requests
import sys
BASE_URL = "http://localhost:8037"
def login(username="admin", password="123456"):
"""用户登录"""
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(f"❌ 登录失败: {response.status_code}")
return None, None
token = response.json().get("access_token")
if not token:
print("❌ 登录失败: 未获取到token")
return None, None
print(f"✅ 登录成功 (用户: {username})")
headers = {"Authorization": f"Bearer {token}"}
return token, headers
except Exception as e:
print(f"❌ 登录异常: {str(e)}")
return None, None
def deploy_agent(agent_id, headers):
"""发布Agent"""
try:
response = requests.post(
f"{BASE_URL}/api/v1/agents/{agent_id}/deploy",
headers=headers
)
if response.status_code == 200:
agent = response.json()
print(f"✅ Agent发布成功: {agent.get('name')} (状态: {agent.get('status')})")
return True
else:
print(f"❌ 发布失败: {response.status_code}")
print(f"响应: {response.text}")
return False
except Exception as e:
print(f"❌ 发布异常: {str(e)}")
return False
def find_agent_by_name(agent_name, headers):
"""通过名称查找Agent"""
try:
response = requests.get(
f"{BASE_URL}/api/v1/agents",
headers=headers,
params={"search": agent_name, "limit": 100}
)
if response.status_code != 200:
print(f"❌ 获取Agent列表失败: {response.status_code}")
return None
agents = response.json()
for agent in agents:
if agent.get("name") == agent_name:
return agent
return None
except Exception as e:
print(f"❌ 查找Agent异常: {str(e)}")
return None
if __name__ == "__main__":
agent_name = sys.argv[1] if len(sys.argv) > 1 else "知识库问答助手"
print(f"📝 发布Agent: {agent_name}\n")
# 登录
token, headers = login()
if not token:
sys.exit(1)
# 查找Agent
print(f"🔍 查找Agent: {agent_name}")
agent = find_agent_by_name(agent_name, headers)
if not agent:
print(f"❌ 未找到Agent: {agent_name}")
sys.exit(1)
print(f"✅ 找到Agent: {agent.get('name')} (ID: {agent.get('id')}, 状态: {agent.get('status')})")
# 发布Agent
print(f"\n🚀 发布Agent...")
if deploy_agent(agent.get('id'), headers):
print("\n✅ 完成!")
else:
sys.exit(1)

View File

@@ -0,0 +1,279 @@
#!/usr/bin/env python3
"""
读取 agent_test_cases.json或 --cases 指定),批量执行 Agent 并做简单断言。
规范见:(红头)agent测试用例文档.md
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
from typing import Any, Dict, List, Optional, Tuple
import requests
DEFAULT_CASES_FILE = "agent_test_cases.json"
def _ensure_utf8_stdio() -> None:
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()
def _login(base_url: str, username: str, password: str, timeout: float) -> Optional[Dict[str, str]]:
r = requests.post(
f"{base_url}/api/v1/auth/login",
data={"username": username, "password": password},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=timeout,
)
if r.status_code != 200:
print(f"[FAIL] 登录 {r.status_code}: {r.text[:500]}")
return None
token = r.json().get("access_token")
if not token:
print("[FAIL] 登录响应无 access_token")
return None
return {"Authorization": f"Bearer {token}"}
def _resolve_agent_id(
base_url: str,
headers: Dict[str, str],
agent: Dict[str, Any],
timeout: float,
) -> Optional[str]:
if agent.get("id"):
return str(agent["id"])
name = agent.get("name")
if not name:
print("[FAIL] agent 需包含 id 或 name")
return None
r = requests.get(
f"{base_url}/api/v1/agents",
headers=headers,
params={"search": name, "limit": 100},
timeout=timeout,
)
if r.status_code != 200:
print(f"[FAIL] 查找 Agent {r.status_code}: {r.text[:500]}")
return None
agents: List[Dict[str, Any]] = r.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] 未找到 Agent: {name}")
return None
print(f"[OK] Agent: {pick.get('name')} ({pick['id']}) status={pick.get('status')}")
return str(pick["id"])
def _extract_output_text(output_data: Any) -> str:
if output_data is None:
return ""
if isinstance(output_data, str):
return output_data
if isinstance(output_data, dict):
for key in ("result", "output", "text", "content"):
v = output_data.get(key)
if v is not None:
return v if isinstance(v, str) else str(v)
return json.dumps(output_data, ensure_ascii=False)
return str(output_data)
def _poll_until_terminal(
base_url: str,
headers: Dict[str, str],
execution_id: str,
max_wait: float,
poll_interval: float,
timeout: float,
) -> Tuple[str, Optional[Dict[str, Any]]]:
deadline = time.time() + max_wait
last_status = "unknown"
while time.time() < deadline:
sr = requests.get(
f"{base_url}/api/v1/executions/{execution_id}/status",
headers=headers,
timeout=timeout,
)
if sr.status_code != 200:
print(f"[WARN] status {sr.status_code}: {sr.text[:300]}")
time.sleep(poll_interval)
continue
body = sr.json()
last_status = str(body.get("status") or "")
if last_status in ("completed", "failed", "cancelled", "awaiting_approval"):
break
time.sleep(poll_interval)
dr = requests.get(
f"{base_url}/api/v1/executions/{execution_id}",
headers=headers,
timeout=timeout,
)
if dr.status_code != 200:
print(f"[FAIL] 获取执行详情 {dr.status_code}: {dr.text[:500]}")
return last_status, None
return last_status, dr.json()
def _check_expect(text: str, status: str, detail: Optional[Dict[str, Any]], expect: Dict[str, Any]) -> List[str]:
errors: List[str] = []
want_status = expect.get("status", "completed")
if status != want_status:
errors.append(f"状态期望 {want_status!r},实际 {status!r}")
if detail and status != "completed":
em = detail.get("error_message")
if em:
errors.append(f"error_message: {em[:500]}")
if not expect:
return errors
ci = bool(expect.get("case_insensitive"))
hay = text if not ci else text.lower()
for sub in expect.get("output_contains") or []:
s = sub if not ci else sub.lower()
if s not in hay:
errors.append(f"输出应包含 {sub!r}")
for sub in expect.get("output_not_contains") or []:
s = sub if not ci else sub.lower()
if s in hay:
errors.append(f"输出不应包含 {sub!r}")
return errors
def _run_one_case(
base_url: str,
headers: Dict[str, str],
defaults: Dict[str, Any],
case: Dict[str, Any],
) -> bool:
cid = case.get("id", "(no-id)")
title = case.get("name", "")
print("\n" + "-" * 60)
print(f"CASE {cid}" + (f"{title}" if title else ""))
req_timeout = float(case.get("request_timeout_sec", defaults.get("request_timeout_sec", 120)))
max_wait = float(case.get("max_wait_sec", defaults.get("max_wait_sec", 300)))
poll_iv = float(case.get("poll_interval_sec", defaults.get("poll_interval_sec", 2)))
agent_id = _resolve_agent_id(base_url, headers, case.get("agent") or {}, req_timeout)
if not agent_id:
return False
message = case.get("message")
if message is None:
print("[FAIL] 缺少 message")
return False
input_data: Dict[str, Any] = {"query": message, "USER_INPUT": message}
extra = case.get("input_extra")
if isinstance(extra, dict):
input_data = {**extra, **input_data}
er = requests.post(
f"{base_url}/api/v1/executions",
headers=headers,
json={"agent_id": agent_id, "input_data": input_data},
timeout=req_timeout,
)
if er.status_code != 201:
print(f"[FAIL] 创建执行 {er.status_code}: {er.text[:800]}")
return False
ex = er.json()
eid = ex["id"]
print(f"[OK] execution_id={eid}")
st, detail = _poll_until_terminal(base_url, headers, eid, max_wait, poll_iv, req_timeout)
text = _extract_output_text((detail or {}).get("output_data"))
expect = case.get("expect") or {}
errs = _check_expect(text, st, detail, expect)
if errs:
for e in errs:
print(f"[FAIL] {e}")
if text:
print("[OUTPUT_PREVIEW]")
print(text[:2000] + ("" if len(text) > 2000 else ""))
return False
print(f"[OK] 通过 status={st}")
if text:
print("[OUTPUT_PREVIEW]")
print(text[:1200] + ("" if len(text) > 1200 else ""))
return True
def main() -> int:
ap = argparse.ArgumentParser(description="批量运行 Agent 测试用例JSON")
ap.add_argument(
"--cases",
default=os.environ.get("AGENT_TEST_CASES", DEFAULT_CASES_FILE),
help=f"用例 JSON 路径(默认 {DEFAULT_CASES_FILE}",
)
ap.add_argument("--username", default=None)
ap.add_argument("--password", default=None)
ap.add_argument("--base-url", default=None, help="覆盖 defaults.base_url / API_BASE_URL")
args = ap.parse_args()
path = args.cases
if not os.path.isfile(path):
print(f"[FAIL] 找不到用例文件: {path}")
print("请先按 (红头)agent测试用例文档.md 创建 JSON或复制示例为 agent_test_cases.json")
return 2
with open(path, encoding="utf-8") as f:
spec = json.load(f)
defaults = spec.get("defaults") or {}
base_url = (
args.base_url
or defaults.get("base_url")
or os.environ.get("API_BASE_URL", "http://localhost:8037")
)
base_url = base_url.rstrip("/")
username = args.username or defaults.get("username", "admin")
password = args.password or defaults.get("password", "123456")
req_timeout = float(defaults.get("request_timeout_sec", 120))
print(f"API: {base_url}")
print(f"用例文件: {path}")
headers = _login(base_url, username, password, req_timeout)
if not headers:
return 3
cases: List[Dict[str, Any]] = spec.get("cases") or []
if not cases:
print("[FAIL] cases 为空")
return 4
ok, skip, fail = 0, 0, 0
for case in cases:
if case.get("enabled") is False:
print(f"\n[SKIP] {case.get('id', '?')}")
skip += 1
continue
if _run_one_case(base_url, headers, defaults, case):
ok += 1
else:
fail += 1
print("\n" + "=" * 60)
print(f"汇总: 通过 {ok} 失败 {fail} 跳过 {skip}")
return 0 if fail == 0 else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,43 @@
#!/bin/bash
# 开放8037和8038端口的脚本
echo "正在检查并开放端口 8037 和 8038..."
# 方法1: 使用firewalld (CentOS/RHEL 7+)
if command -v firewall-cmd &> /dev/null; then
echo "检测到 firewalld使用 firewalld 开放端口..."
sudo firewall-cmd --permanent --add-port=8037/tcp
sudo firewall-cmd --permanent --add-port=8038/tcp
sudo firewall-cmd --reload
echo "✅ firewalld 端口已开放"
exit 0
fi
# 方法2: 使用ufw (Ubuntu/Debian)
if command -v ufw &> /dev/null; then
echo "检测到 ufw使用 ufw 开放端口..."
sudo ufw allow 8037/tcp
sudo ufw allow 8038/tcp
echo "✅ ufw 端口已开放"
exit 0
fi
# 方法3: 使用iptables
echo "使用 iptables 开放端口..."
sudo iptables -I INPUT -p tcp --dport 8037 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 8038 -j ACCEPT
# 保存iptables规则根据系统不同
if [ -f /etc/redhat-release ]; then
# CentOS/RHEL
sudo service iptables save 2>/dev/null || sudo iptables-save > /etc/sysconfig/iptables
elif [ -f /etc/debian_version ]; then
# Debian/Ubuntu
sudo iptables-save > /etc/iptables/rules.v4 2>/dev/null || echo "请手动保存iptables规则"
fi
echo "✅ iptables 端口已开放"
echo ""
echo "⚠️ 注意:如果使用云服务器(如腾讯云、阿里云等),还需要在云控制台配置安全组规则:"
echo " - 开放入站规则TCP 8037"
echo " - 开放入站规则TCP 8038"

View File

@@ -0,0 +1,63 @@
#!/bin/bash
# 数据库迁移脚本
# 用于创建模板市场相关的数据库表
echo "=========================================="
echo "执行数据库迁移 - 模板市场"
echo "=========================================="
echo ""
# 数据库连接信息从config.py中获取
DB_HOST="gz-cynosdbmysql-grp-d26pzce5.sql.tencentcdb.com"
DB_PORT="24936"
DB_USER="root"
DB_NAME="agent_db"
SQL_FILE="backend/create_template_market_tables.sql"
# 检查SQL文件是否存在
if [ ! -f "$SQL_FILE" ]; then
echo "❌ SQL文件不存在: $SQL_FILE"
exit 1
fi
echo "📄 SQL文件: $SQL_FILE"
echo "🔗 数据库: $DB_NAME @ $DB_HOST:$DB_PORT"
echo ""
# 检查mysql命令是否可用
if ! command -v mysql &> /dev/null; then
echo "⚠️ mysql命令不可用请手动执行SQL脚本"
echo ""
echo "手动执行步骤:"
echo "1. 连接到数据库:"
echo " mysql -h $DB_HOST -P $DB_PORT -u $DB_USER -p $DB_NAME"
echo ""
echo "2. 执行SQL"
echo " source $(pwd)/$SQL_FILE;"
echo ""
exit 1
fi
# 提示输入密码
echo "请输入数据库密码:"
read -s DB_PASSWORD
echo ""
echo "正在执行SQL脚本..."
echo ""
# 执行SQL脚本
mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" < "$SQL_FILE"
if [ $? -eq 0 ]; then
echo ""
echo "✅ 数据库迁移完成!"
echo ""
echo "📊 验证表是否创建:"
mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" -e "SHOW TABLES LIKE 'workflow_template%'; SHOW TABLES LIKE 'template_%';"
else
echo ""
echo "❌ 数据库迁移失败,请检查错误信息"
exit 1
fi