android应用
This commit is contained in:
283
Agent使用说明.md
Normal file
283
Agent使用说明.md
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
# Agent使用说明
|
||||||
|
|
||||||
|
## 📋 概述
|
||||||
|
|
||||||
|
Agent部署后,状态变为"已发布"(published)或"运行中"(running),就可以被使用了。本文档介绍如何使用已发布的Agent。
|
||||||
|
|
||||||
|
## 🎯 使用方式
|
||||||
|
|
||||||
|
### 方式1:通过前端界面使用(推荐)
|
||||||
|
|
||||||
|
#### 步骤1:进入Agent设计器
|
||||||
|
1. 在Agent管理页面,找到已发布的Agent
|
||||||
|
2. 点击 **"使用"** 按钮(只有已发布或运行中的Agent才显示此按钮)
|
||||||
|
3. 系统会跳转到Agent设计器页面
|
||||||
|
|
||||||
|
#### 步骤2:在聊天界面使用
|
||||||
|
- Agent设计器页面右侧有一个聊天预览面板(AgentChatPreview)
|
||||||
|
- 在输入框中输入您的问题或需求
|
||||||
|
- 点击发送按钮或按 Enter 键发送
|
||||||
|
- Agent会执行工作流并返回结果
|
||||||
|
|
||||||
|
#### 功能特性:
|
||||||
|
- ✅ 实时对话:支持多轮对话
|
||||||
|
- ✅ 执行动画:左侧工作流会显示节点执行动画
|
||||||
|
- ✅ 自动滚动:新消息自动滚动到底部
|
||||||
|
- ✅ 清空对话:可以随时清空对话历史
|
||||||
|
|
||||||
|
### 方式2:通过API调用
|
||||||
|
|
||||||
|
#### API端点
|
||||||
|
```
|
||||||
|
POST /api/v1/executions
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 请求格式
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"agent_id": "agent-id",
|
||||||
|
"input_data": {
|
||||||
|
"query": "用户输入的问题",
|
||||||
|
"USER_INPUT": "用户输入的问题"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应格式
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "execution-id",
|
||||||
|
"agent_id": "agent-id",
|
||||||
|
"status": "pending",
|
||||||
|
"input_data": {...},
|
||||||
|
"created_at": "2024-01-19T12:00:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 获取执行结果
|
||||||
|
```
|
||||||
|
GET /api/v1/executions/{execution_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 轮询执行状态
|
||||||
|
```
|
||||||
|
GET /api/v1/executions/{execution_id}/status
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式3:使用测试工具
|
||||||
|
|
||||||
|
使用我们提供的测试工具 `test_workflow_tool.py`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 通过Agent名称和用户输入测试
|
||||||
|
python3 test_workflow_tool.py -a "Agent名称" -i "用户输入内容"
|
||||||
|
|
||||||
|
# 交互式输入
|
||||||
|
python3 test_workflow_tool.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 使用示例
|
||||||
|
|
||||||
|
### 示例1:前端界面使用
|
||||||
|
|
||||||
|
1. **打开Agent管理页面**
|
||||||
|
- 访问 `/agents` 路径
|
||||||
|
- 找到状态为"已发布"的Agent
|
||||||
|
|
||||||
|
2. **点击"使用"按钮**
|
||||||
|
- 系统跳转到Agent设计器页面
|
||||||
|
- 右侧显示聊天界面
|
||||||
|
|
||||||
|
3. **发送消息**
|
||||||
|
- 在输入框输入:"生成一个导出androidlog的脚本"
|
||||||
|
- 点击发送或按Enter键
|
||||||
|
|
||||||
|
4. **查看结果**
|
||||||
|
- 左侧工作流显示执行动画
|
||||||
|
- 右侧聊天界面显示Agent的回复
|
||||||
|
|
||||||
|
### 示例2:API调用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 登录获取token
|
||||||
|
curl -X POST http://localhost:8037/api/v1/auth/login \
|
||||||
|
-d "username=admin&password=123456"
|
||||||
|
|
||||||
|
# 2. 执行Agent
|
||||||
|
curl -X POST http://localhost:8037/api/v1/executions \
|
||||||
|
-H "Authorization: Bearer <your_token>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"agent_id": "agent-id",
|
||||||
|
"input_data": {
|
||||||
|
"query": "生成一个导出androidlog的脚本",
|
||||||
|
"USER_INPUT": "生成一个导出androidlog的脚本"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 3. 获取执行结果(使用返回的execution_id)
|
||||||
|
curl -X GET http://localhost:8037/api/v1/executions/{execution_id} \
|
||||||
|
-H "Authorization: Bearer <your_token>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例3:Python脚本调用
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# 登录
|
||||||
|
response = requests.post(
|
||||||
|
"http://localhost:8037/api/v1/auth/login",
|
||||||
|
data={"username": "admin", "password": "123456"}
|
||||||
|
)
|
||||||
|
token = response.json()["access_token"]
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
# 执行Agent
|
||||||
|
response = requests.post(
|
||||||
|
"http://localhost:8037/api/v1/executions",
|
||||||
|
headers=headers,
|
||||||
|
json={
|
||||||
|
"agent_id": "agent-id",
|
||||||
|
"input_data": {
|
||||||
|
"query": "用户问题",
|
||||||
|
"USER_INPUT": "用户问题"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
execution_id = response.json()["id"]
|
||||||
|
|
||||||
|
# 轮询获取结果
|
||||||
|
import time
|
||||||
|
while True:
|
||||||
|
response = requests.get(
|
||||||
|
f"http://localhost:8037/api/v1/executions/{execution_id}",
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
execution = response.json()
|
||||||
|
if execution["status"] == "completed":
|
||||||
|
print("结果:", execution["output_data"])
|
||||||
|
break
|
||||||
|
elif execution["status"] == "failed":
|
||||||
|
print("执行失败:", execution.get("error_message"))
|
||||||
|
break
|
||||||
|
time.sleep(2)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔑 权限说明
|
||||||
|
|
||||||
|
### 已发布Agent的使用权限
|
||||||
|
|
||||||
|
1. **所有者**:可以随时使用自己的Agent
|
||||||
|
2. **其他用户**:只有已发布(published)或运行中(running)状态的Agent才能被其他用户使用
|
||||||
|
3. **草稿状态**:只有所有者可以测试,其他用户无法使用
|
||||||
|
|
||||||
|
### API权限检查
|
||||||
|
|
||||||
|
从代码中可以看到:
|
||||||
|
```python
|
||||||
|
# 只有已发布的Agent可以执行,或者所有者可以测试
|
||||||
|
if agent.status not in ["published", "running"] and agent.user_id != current_user.id:
|
||||||
|
raise HTTPException(status_code=403, detail="Agent未发布或无权执行")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 前端界面说明
|
||||||
|
|
||||||
|
### Agent设计器页面布局
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Agent编排设计器 [测试运行] [发布] [返回] │
|
||||||
|
├──────────────────────┬──────────────────────────┤
|
||||||
|
│ │ │
|
||||||
|
│ 工作流编辑器 │ Agent聊天预览 │
|
||||||
|
│ (左侧) │ (右侧) │
|
||||||
|
│ │ │
|
||||||
|
│ - 节点可视化 │ - 对话界面 │
|
||||||
|
│ - 执行动画 │ - 消息历史 │
|
||||||
|
│ - 节点配置 │ - 输入框 │
|
||||||
|
│ │ - 实时响应 │
|
||||||
|
│ │ │
|
||||||
|
└──────────────────────┴──────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 聊天界面功能
|
||||||
|
|
||||||
|
1. **消息显示**
|
||||||
|
- 用户消息:右侧显示,蓝色背景
|
||||||
|
- Agent回复:左侧显示,白色背景
|
||||||
|
- 时间戳:每条消息显示发送时间
|
||||||
|
|
||||||
|
2. **输入功能**
|
||||||
|
- 文本输入:支持多行文本
|
||||||
|
- Enter发送:按Enter键发送消息
|
||||||
|
- Shift+Enter:换行
|
||||||
|
- 发送按钮:点击发送
|
||||||
|
|
||||||
|
3. **其他功能**
|
||||||
|
- 清空对话:清除所有消息历史
|
||||||
|
- 加载状态:显示"正在思考..."
|
||||||
|
- 执行动画:左侧工作流实时显示节点执行状态
|
||||||
|
|
||||||
|
## 🔧 常见问题
|
||||||
|
|
||||||
|
### Q1: 为什么看不到"使用"按钮?
|
||||||
|
|
||||||
|
**A:** "使用"按钮只对状态为"已发布"(published)或"运行中"(running)的Agent显示。如果Agent是"草稿"(draft)状态,需要先点击"部署"按钮发布。
|
||||||
|
|
||||||
|
### Q2: 如何知道Agent是否可用?
|
||||||
|
|
||||||
|
**A:** 查看Agent列表中的"状态"列:
|
||||||
|
- ✅ **已发布**(published):可以使用
|
||||||
|
- ✅ **运行中**(running):可以使用
|
||||||
|
- ⚠️ **草稿**(draft):只有所有者可以测试
|
||||||
|
- ⚠️ **已停止**(stopped):需要重新部署
|
||||||
|
|
||||||
|
### Q3: Agent执行失败怎么办?
|
||||||
|
|
||||||
|
**A:**
|
||||||
|
1. 检查Agent的工作流配置是否正确
|
||||||
|
2. 查看执行记录中的错误信息
|
||||||
|
3. 检查LLM节点的API密钥是否配置
|
||||||
|
4. 查看后端日志获取详细错误信息
|
||||||
|
|
||||||
|
### Q4: 如何查看Agent的执行历史?
|
||||||
|
|
||||||
|
**A:**
|
||||||
|
1. 访问"执行管理"页面(`/executions`)
|
||||||
|
2. 筛选Agent相关的执行记录
|
||||||
|
3. 点击执行记录查看详细信息
|
||||||
|
|
||||||
|
### Q5: 可以同时使用多个Agent吗?
|
||||||
|
|
||||||
|
**A:** 可以。每个Agent都是独立的,可以同时打开多个Agent设计器页面使用不同的Agent。
|
||||||
|
|
||||||
|
## 📚 相关文档
|
||||||
|
|
||||||
|
- [Agent管理功能使用说明](./Agent管理功能使用说明.md)
|
||||||
|
- [工作流调用测试总结](./工作流调用测试总结.txt)
|
||||||
|
- [API文档](./backend/API_DOCUMENTATION.md)
|
||||||
|
|
||||||
|
## 🎯 快速开始
|
||||||
|
|
||||||
|
1. **确保Agent已发布**
|
||||||
|
- 在Agent列表中,找到要使用的Agent
|
||||||
|
- 确认状态为"已发布"或"运行中"
|
||||||
|
|
||||||
|
2. **点击"使用"按钮**
|
||||||
|
- 跳转到Agent设计器页面
|
||||||
|
|
||||||
|
3. **开始对话**
|
||||||
|
- 在右侧聊天界面输入问题
|
||||||
|
- 点击发送或按Enter键
|
||||||
|
- 等待Agent执行并返回结果
|
||||||
|
|
||||||
|
4. **查看结果**
|
||||||
|
- 在聊天界面查看Agent的回复
|
||||||
|
- 在左侧工作流查看执行过程
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**最后更新**:2026-01-19
|
||||||
|
**适用版本**:v1.0+
|
||||||
180
androidExample/README.md
Normal file
180
androidExample/README.md
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
# Android Agent调用示例
|
||||||
|
|
||||||
|
这是一个Android示例项目,演示如何调用情感分析Agent。
|
||||||
|
|
||||||
|
## 📋 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
androidExample/
|
||||||
|
├── app/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ └── main/
|
||||||
|
│ │ ├── java/com/example/agentclient/
|
||||||
|
│ │ │ ├── MainActivity.kt
|
||||||
|
│ │ │ ├── AgentService.kt
|
||||||
|
│ │ │ ├── models/
|
||||||
|
│ │ │ │ ├── AgentRequest.kt
|
||||||
|
│ │ │ │ ├── AgentResponse.kt
|
||||||
|
│ │ │ │ └── ExecutionResponse.kt
|
||||||
|
│ │ │ └── utils/
|
||||||
|
│ │ │ └── ApiClient.kt
|
||||||
|
│ │ └── res/
|
||||||
|
│ │ ├── layout/
|
||||||
|
│ │ │ └── activity_main.xml
|
||||||
|
│ │ └── values/
|
||||||
|
│ │ └── strings.xml
|
||||||
|
│ └── build.gradle.kts
|
||||||
|
├── build.gradle.kts
|
||||||
|
├── settings.gradle.kts
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 配置API地址
|
||||||
|
|
||||||
|
在 `app/src/main/java/com/example/agentclient/utils/ApiClient.kt` 中修改:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
private const val BASE_URL = "http://your-server-ip:8037"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置Agent ID
|
||||||
|
|
||||||
|
在 `MainActivity.kt` 中修改:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
private val AGENT_ID = "your-agent-id" // 情感分析Agent的ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 运行项目
|
||||||
|
|
||||||
|
1. 使用Android Studio打开项目
|
||||||
|
2. 同步Gradle依赖
|
||||||
|
3. 运行到Android设备或模拟器
|
||||||
|
|
||||||
|
## 📱 功能特性
|
||||||
|
|
||||||
|
- ✅ 用户登录
|
||||||
|
- ✅ 调用Agent API
|
||||||
|
- ✅ 实时显示执行状态
|
||||||
|
- ✅ 显示Agent回复
|
||||||
|
- ✅ 错误处理
|
||||||
|
|
||||||
|
## 🔧 依赖库
|
||||||
|
|
||||||
|
- Retrofit2:网络请求
|
||||||
|
- OkHttp:HTTP客户端
|
||||||
|
- Gson:JSON解析
|
||||||
|
- Coroutines:异步处理
|
||||||
|
|
||||||
|
## 📝 使用说明
|
||||||
|
|
||||||
|
1. **登录**
|
||||||
|
- 应用启动后会自动登录(使用配置的用户名和密码)
|
||||||
|
- 登录成功后可以开始使用Agent
|
||||||
|
|
||||||
|
2. **发送消息**
|
||||||
|
- 在输入框中输入文本
|
||||||
|
- 点击"发送"按钮
|
||||||
|
- 等待Agent处理并返回结果
|
||||||
|
|
||||||
|
3. **查看结果**
|
||||||
|
- Agent的回复会显示在消息列表中
|
||||||
|
- 执行状态会实时更新
|
||||||
|
|
||||||
|
## 🔑 API说明
|
||||||
|
|
||||||
|
### 登录API
|
||||||
|
```
|
||||||
|
POST /api/v1/auth/login
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
|
||||||
|
username=admin&password=123456
|
||||||
|
```
|
||||||
|
|
||||||
|
### 执行Agent API
|
||||||
|
```
|
||||||
|
POST /api/v1/executions
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"agent_id": "agent-id",
|
||||||
|
"input_data": {
|
||||||
|
"query": "用户输入",
|
||||||
|
"USER_INPUT": "用户输入"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取执行状态API
|
||||||
|
```
|
||||||
|
GET /api/v1/executions/{execution_id}/status
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取执行结果API
|
||||||
|
```
|
||||||
|
GET /api/v1/executions/{execution_id}
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 示例:调用情感分析Agent
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// 1. 登录获取token
|
||||||
|
val token = agentService.login("admin", "123456")
|
||||||
|
|
||||||
|
// 2. 执行Agent
|
||||||
|
val execution = agentService.executeAgent(
|
||||||
|
agentId = "sentiment-analysis-agent-id",
|
||||||
|
userInput = "这个产品真的很棒!"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 3. 轮询获取结果
|
||||||
|
while (true) {
|
||||||
|
val status = agentService.getExecutionStatus(execution.id)
|
||||||
|
if (status.status == "completed") {
|
||||||
|
val result = agentService.getExecutionResult(execution.id)
|
||||||
|
println("情感分析结果: ${result.output_data}")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
delay(1000) // 等待1秒
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 构建要求
|
||||||
|
|
||||||
|
- Android Studio Hedgehog | 2023.1.1 或更高版本
|
||||||
|
- JDK 17 或更高版本
|
||||||
|
- Android SDK API 24 或更高版本
|
||||||
|
- Gradle 8.0 或更高版本
|
||||||
|
|
||||||
|
## 🔒 安全注意事项
|
||||||
|
|
||||||
|
1. **不要硬编码密码**:生产环境应该使用安全的认证方式
|
||||||
|
2. **使用HTTPS**:生产环境必须使用HTTPS
|
||||||
|
3. **Token管理**:妥善保管和刷新Token
|
||||||
|
4. **网络安全配置**:Android 9+需要配置网络安全策略
|
||||||
|
|
||||||
|
## 🐛 故障排除
|
||||||
|
|
||||||
|
### 问题1:网络连接失败
|
||||||
|
- 检查API地址是否正确
|
||||||
|
- 检查设备网络连接
|
||||||
|
- 检查服务器是否运行
|
||||||
|
|
||||||
|
### 问题2:登录失败
|
||||||
|
- 检查用户名和密码是否正确
|
||||||
|
- 检查服务器认证服务是否正常
|
||||||
|
|
||||||
|
### 问题3:Agent执行失败
|
||||||
|
- 检查Agent ID是否正确
|
||||||
|
- 检查Agent是否已发布
|
||||||
|
- 查看服务器日志获取详细错误信息
|
||||||
|
|
||||||
|
## 📚 相关文档
|
||||||
|
|
||||||
|
- [Agent使用说明](../Agent使用说明.md)
|
||||||
|
- [API文档](../backend/API_DOCUMENTATION.md)
|
||||||
70
androidExample/app/build.gradle.kts
Normal file
70
androidExample/app/build.gradle.kts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("org.jetbrains.kotlin.android")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.example.agentclient"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.example.agentclient"
|
||||||
|
minSdk = 24
|
||||||
|
targetSdk = 34
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Android Core
|
||||||
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
|
implementation("com.google.android.material:material:1.10.0")
|
||||||
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||||
|
implementation("androidx.activity:activity-ktx:1.8.1")
|
||||||
|
|
||||||
|
// Coroutines
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
||||||
|
|
||||||
|
// Retrofit & OkHttp
|
||||||
|
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||||
|
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
|
||||||
|
|
||||||
|
// Gson
|
||||||
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
|
|
||||||
|
// Testing
|
||||||
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.example.agentclient.models
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent执行请求模型
|
||||||
|
*/
|
||||||
|
data class AgentExecutionRequest(
|
||||||
|
@SerializedName("agent_id")
|
||||||
|
val agentId: String,
|
||||||
|
|
||||||
|
@SerializedName("input_data")
|
||||||
|
val inputData: Map<String, Any>
|
||||||
|
)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.example.agentclient.models
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行记录响应模型
|
||||||
|
*/
|
||||||
|
data class ExecutionResponse(
|
||||||
|
val id: String,
|
||||||
|
|
||||||
|
@SerializedName("agent_id")
|
||||||
|
val agentId: String?,
|
||||||
|
|
||||||
|
@SerializedName("workflow_id")
|
||||||
|
val workflowId: String?,
|
||||||
|
|
||||||
|
val status: String,
|
||||||
|
|
||||||
|
@SerializedName("input_data")
|
||||||
|
val inputData: Map<String, Any>?,
|
||||||
|
|
||||||
|
@SerializedName("output_data")
|
||||||
|
val outputData: Any?,
|
||||||
|
|
||||||
|
@SerializedName("created_at")
|
||||||
|
val createdAt: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.example.agentclient.models
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token响应模型
|
||||||
|
*/
|
||||||
|
data class TokenResponse(
|
||||||
|
@SerializedName("access_token")
|
||||||
|
val accessToken: String,
|
||||||
|
|
||||||
|
@SerializedName("token_type")
|
||||||
|
val tokenType: String = "bearer"
|
||||||
|
)
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.example.agentclient.utils
|
||||||
|
|
||||||
|
import com.example.agentclient.models.*
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import retrofit2.http.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API客户端配置
|
||||||
|
*
|
||||||
|
* 使用说明:
|
||||||
|
* 1. 修改 BASE_URL 为你的服务器地址
|
||||||
|
* 2. 确保服务器允许跨域请求(CORS)
|
||||||
|
*/
|
||||||
|
object ApiClient {
|
||||||
|
// TODO: 修改为你的服务器地址
|
||||||
|
private const val BASE_URL = "http://101.43.95.130:8037"
|
||||||
|
|
||||||
|
// 创建OkHttp客户端
|
||||||
|
private val okHttpClient = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// 创建Retrofit实例
|
||||||
|
private val retrofit = Retrofit.Builder()
|
||||||
|
.baseUrl(BASE_URL)
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// 创建API服务
|
||||||
|
val agentService: AgentService = retrofit.create(AgentService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent API接口
|
||||||
|
*/
|
||||||
|
interface AgentService {
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
*
|
||||||
|
* @param username 用户名
|
||||||
|
* @param password 密码
|
||||||
|
* @return Token响应
|
||||||
|
*/
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("/api/v1/auth/login")
|
||||||
|
suspend fun login(
|
||||||
|
@Field("username") username: String,
|
||||||
|
@Field("password") password: String
|
||||||
|
): TokenResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行Agent
|
||||||
|
*
|
||||||
|
* @param request Agent执行请求
|
||||||
|
* @param token 认证Token
|
||||||
|
* @return 执行记录
|
||||||
|
*/
|
||||||
|
@POST("/api/v1/executions")
|
||||||
|
suspend fun executeAgent(
|
||||||
|
@Body request: AgentExecutionRequest,
|
||||||
|
@Header("Authorization") token: String
|
||||||
|
): ExecutionResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取执行状态
|
||||||
|
*
|
||||||
|
* @param executionId 执行ID
|
||||||
|
* @param token 认证Token
|
||||||
|
* @return 执行状态
|
||||||
|
*/
|
||||||
|
@GET("/api/v1/executions/{execution_id}/status")
|
||||||
|
suspend fun getExecutionStatus(
|
||||||
|
@Path("execution_id") executionId: String,
|
||||||
|
@Header("Authorization") token: String
|
||||||
|
): ExecutionStatusResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取执行结果
|
||||||
|
*
|
||||||
|
* @param executionId 执行ID
|
||||||
|
* @param token 认证Token
|
||||||
|
* @return 执行详情
|
||||||
|
*/
|
||||||
|
@GET("/api/v1/executions/{execution_id}")
|
||||||
|
suspend fun getExecutionResult(
|
||||||
|
@Path("execution_id") executionId: String,
|
||||||
|
@Header("Authorization") token: String
|
||||||
|
): ExecutionDetailResponse
|
||||||
|
}
|
||||||
9
androidExample/build.gradle.kts
Normal file
9
androidExample/build.gradle.kts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
plugins {
|
||||||
|
id("com.android.application") version "8.1.0" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "1.9.0" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete::class) {
|
||||||
|
delete(rootProject.buildDir)
|
||||||
|
}
|
||||||
18
androidExample/settings.gradle.kts
Normal file
18
androidExample/settings.gradle.kts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "AgentClient"
|
||||||
|
include(":app")
|
||||||
@@ -397,3 +397,112 @@ async def duplicate_agent(
|
|||||||
|
|
||||||
logger.info(f"用户 {current_user.username} 复制了Agent: {original_agent.name} ({agent_id}) -> {new_agent.name} ({new_agent.id})")
|
logger.info(f"用户 {current_user.username} 复制了Agent: {original_agent.name} ({agent_id}) -> {new_agent.name} ({new_agent.id})")
|
||||||
return new_agent
|
return new_agent
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{agent_id}/export", status_code=status.HTTP_200_OK)
|
||||||
|
async def export_agent(
|
||||||
|
agent_id: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""导出Agent(JSON格式)"""
|
||||||
|
try:
|
||||||
|
agent = db.query(Agent).filter(Agent.id == agent_id).first()
|
||||||
|
|
||||||
|
if not agent:
|
||||||
|
raise NotFoundError("Agent", agent_id)
|
||||||
|
|
||||||
|
# 检查权限:read权限
|
||||||
|
if not check_agent_permission(db, current_user, agent, "read"):
|
||||||
|
raise HTTPException(status_code=403, detail="无权导出此Agent")
|
||||||
|
|
||||||
|
# 验证工作流配置
|
||||||
|
workflow_config = agent.workflow_config
|
||||||
|
if not workflow_config:
|
||||||
|
raise ValidationError("Agent工作流配置为空,无法导出")
|
||||||
|
|
||||||
|
export_data = {
|
||||||
|
"id": str(agent.id),
|
||||||
|
"name": agent.name,
|
||||||
|
"description": agent.description,
|
||||||
|
"workflow_config": workflow_config,
|
||||||
|
"version": agent.version,
|
||||||
|
"status": agent.status,
|
||||||
|
"exported_at": datetime.utcnow().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"用户 {current_user.username} 导出Agent: {agent.name} ({agent_id})")
|
||||||
|
return export_data
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"导出Agent失败: {str(e)}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/import", response_model=AgentResponse, status_code=status.HTTP_201_CREATED)
|
||||||
|
async def import_agent(
|
||||||
|
agent_data: Dict[str, Any],
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""导入Agent(JSON格式)"""
|
||||||
|
# 提取Agent数据
|
||||||
|
name = agent_data.get("name", "导入的Agent")
|
||||||
|
description = agent_data.get("description")
|
||||||
|
workflow_config = agent_data.get("workflow_config", {})
|
||||||
|
|
||||||
|
# 验证工作流配置
|
||||||
|
if not workflow_config:
|
||||||
|
raise ValidationError("Agent工作流配置不能为空")
|
||||||
|
|
||||||
|
nodes = workflow_config.get("nodes", [])
|
||||||
|
edges = workflow_config.get("edges", [])
|
||||||
|
|
||||||
|
if not nodes or not edges:
|
||||||
|
raise ValidationError("Agent工作流配置无效:缺少节点或边")
|
||||||
|
|
||||||
|
# 验证工作流
|
||||||
|
validation_result = validate_workflow(nodes, edges)
|
||||||
|
if not validation_result["valid"]:
|
||||||
|
raise ValidationError(f"导入的Agent工作流验证失败: {', '.join(validation_result['errors'])}")
|
||||||
|
|
||||||
|
# 重新生成节点ID(避免ID冲突)
|
||||||
|
node_id_mapping = {}
|
||||||
|
for node in nodes:
|
||||||
|
old_id = node["id"]
|
||||||
|
new_id = f"node_{len(node_id_mapping)}_{old_id}"
|
||||||
|
node_id_mapping[old_id] = new_id
|
||||||
|
node["id"] = new_id
|
||||||
|
|
||||||
|
# 更新边的源节点和目标节点ID
|
||||||
|
for edge in edges:
|
||||||
|
if edge.get("source") in node_id_mapping:
|
||||||
|
edge["source"] = node_id_mapping[edge["source"]]
|
||||||
|
if edge.get("target") in node_id_mapping:
|
||||||
|
edge["target"] = node_id_mapping[edge["target"]]
|
||||||
|
|
||||||
|
# 检查名称是否已存在
|
||||||
|
base_name = name
|
||||||
|
counter = 1
|
||||||
|
while db.query(Agent).filter(
|
||||||
|
Agent.name == name,
|
||||||
|
Agent.user_id == current_user.id
|
||||||
|
).first():
|
||||||
|
name = f"{base_name} (导入 {counter})"
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
# 创建Agent
|
||||||
|
agent = Agent(
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
workflow_config={
|
||||||
|
"nodes": nodes,
|
||||||
|
"edges": edges
|
||||||
|
},
|
||||||
|
user_id=current_user.id,
|
||||||
|
status="draft", # 导入的Agent默认为草稿状态
|
||||||
|
version=1
|
||||||
|
)
|
||||||
|
db.add(agent)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(agent)
|
||||||
|
return agent
|
||||||
|
|||||||
@@ -1936,11 +1936,29 @@ class WorkflowEngine:
|
|||||||
# 如果是条件节点,根据分支结果过滤边
|
# 如果是条件节点,根据分支结果过滤边
|
||||||
if node.get('type') == 'condition':
|
if node.get('type') == 'condition':
|
||||||
branch = result.get('branch', 'false')
|
branch = result.get('branch', 'false')
|
||||||
|
logger.info(f"[rjb] 条件节点分支过滤: node_id={next_node_id}, branch={branch}")
|
||||||
# 移除不符合条件的边
|
# 移除不符合条件的边
|
||||||
active_edges = [
|
# 只保留:1) 不是从条件节点出发的边,或 2) 从条件节点出发且sourceHandle匹配分支的边
|
||||||
edge for edge in active_edges
|
edges_to_remove = []
|
||||||
if not (edge['source'] == next_node_id and edge.get('sourceHandle') != branch)
|
edges_to_keep = []
|
||||||
]
|
for edge in active_edges:
|
||||||
|
if edge['source'] == next_node_id:
|
||||||
|
# 这是从条件节点出发的边
|
||||||
|
edge_handle = edge.get('sourceHandle')
|
||||||
|
if edge_handle == branch:
|
||||||
|
# sourceHandle匹配分支,保留
|
||||||
|
edges_to_keep.append(edge)
|
||||||
|
logger.info(f"[rjb] 保留边: {edge.get('id')} (sourceHandle={edge_handle} == branch={branch})")
|
||||||
|
else:
|
||||||
|
# sourceHandle不匹配或为None,移除
|
||||||
|
edges_to_remove.append(edge)
|
||||||
|
logger.info(f"[rjb] 移除边: {edge.get('id')} (sourceHandle={edge_handle} != branch={branch})")
|
||||||
|
else:
|
||||||
|
# 不是从条件节点出发的边,保留
|
||||||
|
edges_to_keep.append(edge)
|
||||||
|
|
||||||
|
active_edges = edges_to_keep
|
||||||
|
logger.info(f"[rjb] 条件节点过滤后: 保留{len(active_edges)}条边,移除{len(edges_to_remove)}条边")
|
||||||
|
|
||||||
# 如果是循环节点,跳过循环体的节点(循环体已在节点内部执行)
|
# 如果是循环节点,跳过循环体的节点(循环体已在节点内部执行)
|
||||||
if node.get('type') in ['loop', 'foreach']:
|
if node.get('type') in ['loop', 'foreach']:
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
:node-types="customNodeTypes"
|
:node-types="customNodeTypes"
|
||||||
:connection-line-style="{ stroke: '#409eff', strokeWidth: 2.5, strokeDasharray: '5,5' }"
|
:connection-line-style="{ stroke: '#409eff', strokeWidth: 2.5, strokeDasharray: '5,5' }"
|
||||||
:default-edge-options="{
|
:default-edge-options="{
|
||||||
type: 'smoothstep',
|
type: 'bezier',
|
||||||
animated: true,
|
animated: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
deletable: true,
|
deletable: true,
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
style: { stroke: '#409eff', strokeWidth: 2.5 },
|
style: { stroke: '#409eff', strokeWidth: 2.5 },
|
||||||
markerEnd: { type: 'arrowclosed', color: '#409eff', width: 20, height: 20 }
|
markerEnd: { type: 'arrowclosed', color: '#409eff', width: 20, height: 20 }
|
||||||
}"
|
}"
|
||||||
:connection-line-type="'smoothstep'"
|
:connection-line-type="'bezier'"
|
||||||
:edges-focusable="true"
|
:edges-focusable="true"
|
||||||
:nodes-focusable="true"
|
:nodes-focusable="true"
|
||||||
:delete-key-code="'Delete'"
|
:delete-key-code="'Delete'"
|
||||||
@@ -1902,7 +1902,7 @@ const onConnect = (connection: Connection) => {
|
|||||||
target: connection.target,
|
target: connection.target,
|
||||||
sourceHandle: connection.sourceHandle || undefined,
|
sourceHandle: connection.sourceHandle || undefined,
|
||||||
targetHandle: connection.targetHandle || undefined,
|
targetHandle: connection.targetHandle || undefined,
|
||||||
type: 'smoothstep', // 使用平滑步进曲线(类似 Dify)
|
type: 'bezier', // 使用贝塞尔曲线(平滑曲线)
|
||||||
animated: true,
|
animated: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
deletable: true,
|
deletable: true,
|
||||||
@@ -2700,7 +2700,7 @@ watch(
|
|||||||
selectable: true,
|
selectable: true,
|
||||||
deletable: true,
|
deletable: true,
|
||||||
focusable: true,
|
focusable: true,
|
||||||
type: edge.type || 'smoothstep',
|
type: edge.type || 'bezier',
|
||||||
animated: true,
|
animated: true,
|
||||||
style: {
|
style: {
|
||||||
stroke: '#409eff',
|
stroke: '#409eff',
|
||||||
@@ -2955,7 +2955,7 @@ onMounted(async () => {
|
|||||||
selectable: true,
|
selectable: true,
|
||||||
deletable: true,
|
deletable: true,
|
||||||
focusable: true,
|
focusable: true,
|
||||||
type: edge.type || 'smoothstep',
|
type: edge.type || 'bezier',
|
||||||
animated: true,
|
animated: true,
|
||||||
style: {
|
style: {
|
||||||
stroke: '#409eff',
|
stroke: '#409eff',
|
||||||
@@ -3006,7 +3006,7 @@ onMounted(async () => {
|
|||||||
target: edge.target,
|
target: edge.target,
|
||||||
sourceHandle: edge.sourceHandle,
|
sourceHandle: edge.sourceHandle,
|
||||||
targetHandle: edge.targetHandle,
|
targetHandle: edge.targetHandle,
|
||||||
type: edge.type || 'smoothstep',
|
type: edge.type || 'bezier',
|
||||||
animated: true,
|
animated: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
deletable: true,
|
deletable: true,
|
||||||
|
|||||||
@@ -166,6 +166,61 @@ export const useAgentStore = defineStore('agent', () => {
|
|||||||
currentAgent.value = agent
|
currentAgent.value = agent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导出Agent
|
||||||
|
const exportAgent = async (agentId: string) => {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`/api/v1/agents/${agentId}/export`)
|
||||||
|
|
||||||
|
if (!response.data) {
|
||||||
|
throw new Error('导出数据为空')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const dataStr = JSON.stringify(response.data, null, 2)
|
||||||
|
const dataBlob = new Blob([dataStr], { type: 'application/json;charset=utf-8' })
|
||||||
|
const url = URL.createObjectURL(dataBlob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
|
||||||
|
// 处理文件名:移除特殊字符,避免下载失败
|
||||||
|
const agentName = (response.data.name || 'agent')
|
||||||
|
.replace(/[<>:"/\\|?*]/g, '_') // 移除Windows不允许的字符
|
||||||
|
.replace(/\s+/g, '_') // 空格替换为下划线
|
||||||
|
.substring(0, 50) // 限制长度
|
||||||
|
|
||||||
|
link.download = `${agentName}_${Date.now()}.json`
|
||||||
|
link.style.display = 'none'
|
||||||
|
document.body.appendChild(link)
|
||||||
|
|
||||||
|
// 触发下载
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('导出Agent失败', error)
|
||||||
|
const errorMessage = error.response?.data?.detail || error.message || '导出失败'
|
||||||
|
throw new Error(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入Agent
|
||||||
|
const importAgent = async (agentData: any) => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await api.post('/api/v1/agents/import', agentData)
|
||||||
|
agents.value.unshift(response.data) // 添加到列表开头
|
||||||
|
return response.data
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
agents,
|
agents,
|
||||||
currentAgent,
|
currentAgent,
|
||||||
@@ -178,6 +233,8 @@ export const useAgentStore = defineStore('agent', () => {
|
|||||||
deployAgent,
|
deployAgent,
|
||||||
stopAgent,
|
stopAgent,
|
||||||
duplicateAgent,
|
duplicateAgent,
|
||||||
setCurrentAgent
|
setCurrentAgent,
|
||||||
|
exportAgent,
|
||||||
|
importAgent
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,11 +5,17 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h2>Agent管理</h2>
|
<h2>Agent管理</h2>
|
||||||
<el-button type="primary" @click="handleCreate">
|
<div>
|
||||||
|
<el-button @click="handleImport">
|
||||||
|
<el-icon><Upload /></el-icon>
|
||||||
|
导入Agent
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="handleCreate" style="margin-left: 10px">
|
||||||
<el-icon><Plus /></el-icon>
|
<el-icon><Plus /></el-icon>
|
||||||
创建Agent
|
创建Agent
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 搜索和筛选 -->
|
<!-- 搜索和筛选 -->
|
||||||
@@ -70,12 +76,21 @@
|
|||||||
{{ formatDate(row.created_at) }}
|
{{ formatDate(row.created_at) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="350" fixed="right">
|
<el-table-column label="操作" width="480" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button link type="primary" @click="handleEdit(row)">
|
<el-button link type="primary" @click="handleEdit(row)">
|
||||||
<el-icon><Edit /></el-icon>
|
<el-icon><Edit /></el-icon>
|
||||||
编辑
|
编辑
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="row.status === 'published' || row.status === 'running'"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="handleUse(row)"
|
||||||
|
>
|
||||||
|
<el-icon><ChatDotRound /></el-icon>
|
||||||
|
使用
|
||||||
|
</el-button>
|
||||||
<el-button link type="primary" @click="handleDesign(row)">
|
<el-button link type="primary" @click="handleDesign(row)">
|
||||||
<el-icon><Setting /></el-icon>
|
<el-icon><Setting /></el-icon>
|
||||||
设计
|
设计
|
||||||
@@ -84,6 +99,10 @@
|
|||||||
<el-icon><CopyDocument /></el-icon>
|
<el-icon><CopyDocument /></el-icon>
|
||||||
复制
|
复制
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button link type="success" @click="handleExport(row)">
|
||||||
|
<el-icon><Download /></el-icon>
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="row.status === 'draft' || row.status === 'stopped'"
|
v-if="row.status === 'draft' || row.status === 'stopped'"
|
||||||
link
|
link
|
||||||
@@ -164,6 +183,63 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 导入Agent对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="importDialogVisible"
|
||||||
|
title="导入Agent"
|
||||||
|
width="600px"
|
||||||
|
@close="handleImportDialogClose"
|
||||||
|
>
|
||||||
|
<el-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
:auto-upload="false"
|
||||||
|
:on-change="handleFileChange"
|
||||||
|
:file-list="fileList"
|
||||||
|
accept=".json"
|
||||||
|
drag
|
||||||
|
>
|
||||||
|
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||||
|
<div class="el-upload__text">
|
||||||
|
将JSON文件拖到此处,或<em>点击上传</em>
|
||||||
|
</div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">
|
||||||
|
只能上传JSON格式的Agent配置文件
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
|
||||||
|
<div v-if="importFileContent" style="margin-top: 20px;">
|
||||||
|
<el-alert
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div>
|
||||||
|
<div><strong>Agent名称:</strong> {{ importFileContent.name || '未命名' }}</div>
|
||||||
|
<div v-if="importFileContent.description" style="margin-top: 5px;">
|
||||||
|
<strong>描述:</strong> {{ importFileContent.description }}
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 5px;">
|
||||||
|
<strong>节点数:</strong> {{ importFileContent.workflow_config?.nodes?.length || 0 }}
|
||||||
|
<span style="margin-left: 20px;">
|
||||||
|
<strong>连接数:</strong> {{ importFileContent.workflow_config?.edges?.length || 0 }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="importDialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleConfirmImport" :loading="importing" :disabled="!importFileContent">
|
||||||
|
确认导入
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
</template>
|
</template>
|
||||||
@@ -182,7 +258,11 @@ import {
|
|||||||
Setting,
|
Setting,
|
||||||
VideoPlay,
|
VideoPlay,
|
||||||
VideoPause,
|
VideoPause,
|
||||||
CopyDocument
|
CopyDocument,
|
||||||
|
Upload,
|
||||||
|
Download,
|
||||||
|
UploadFilled,
|
||||||
|
ChatDotRound
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import { useAgentStore } from '@/stores/agent'
|
import { useAgentStore } from '@/stores/agent'
|
||||||
import type { Agent } from '@/stores/agent'
|
import type { Agent } from '@/stores/agent'
|
||||||
@@ -225,6 +305,13 @@ const form = ref({
|
|||||||
})
|
})
|
||||||
const currentAgentId = ref<string | null>(null)
|
const currentAgentId = ref<string | null>(null)
|
||||||
|
|
||||||
|
// 导入相关
|
||||||
|
const importDialogVisible = ref(false)
|
||||||
|
const fileList = ref<any[]>([])
|
||||||
|
const importFileContent = ref<any>(null)
|
||||||
|
const importing = ref(false)
|
||||||
|
const uploadRef = ref()
|
||||||
|
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const rules = {
|
const rules = {
|
||||||
name: [
|
name: [
|
||||||
@@ -331,6 +418,14 @@ const handleEdit = (agent: Agent) => {
|
|||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用Agent(跳转到设计器,那里有聊天界面)
|
||||||
|
const handleUse = (agent: Agent) => {
|
||||||
|
router.push({
|
||||||
|
name: 'AgentDesigner',
|
||||||
|
params: { id: agent.id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 设计
|
// 设计
|
||||||
const handleDesign = (agent: Agent) => {
|
const handleDesign = (agent: Agent) => {
|
||||||
router.push({
|
router.push({
|
||||||
@@ -427,6 +522,75 @@ const handleDelete = async (agent: Agent) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导出Agent
|
||||||
|
const handleExport = async (agent: Agent) => {
|
||||||
|
try {
|
||||||
|
await agentStore.exportAgent(agent.id)
|
||||||
|
ElMessage.success('Agent导出成功')
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('导出失败详情:', error)
|
||||||
|
const errorMessage = error.message || error.response?.data?.detail || '导出失败,请查看控制台获取详细信息'
|
||||||
|
ElMessage.error(errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示导入对话框
|
||||||
|
const handleImport = () => {
|
||||||
|
importDialogVisible.value = true
|
||||||
|
fileList.value = []
|
||||||
|
importFileContent.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件选择
|
||||||
|
const handleFileChange = (file: any) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = (e) => {
|
||||||
|
try {
|
||||||
|
const content = JSON.parse(e.target?.result as string)
|
||||||
|
importFileContent.value = content
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('文件格式错误,请上传有效的JSON文件')
|
||||||
|
fileList.value = []
|
||||||
|
importFileContent.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.readAsText(file.raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭导入对话框
|
||||||
|
const handleImportDialogClose = () => {
|
||||||
|
fileList.value = []
|
||||||
|
importFileContent.value = null
|
||||||
|
if (uploadRef.value) {
|
||||||
|
uploadRef.value.clearFiles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认导入
|
||||||
|
const handleConfirmImport = async () => {
|
||||||
|
if (!importFileContent.value) {
|
||||||
|
ElMessage.warning('请先选择要导入的文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
importing.value = true
|
||||||
|
try {
|
||||||
|
const agent = await agentStore.importAgent(importFileContent.value)
|
||||||
|
importDialogVisible.value = false
|
||||||
|
ElMessage.success('Agent导入成功')
|
||||||
|
await loadAgents()
|
||||||
|
// 跳转到Agent设计器
|
||||||
|
router.push({
|
||||||
|
name: 'AgentDesigner',
|
||||||
|
params: { id: agent.id }
|
||||||
|
})
|
||||||
|
} catch (error: any) {
|
||||||
|
ElMessage.error(error.response?.data?.detail || '导入Agent失败')
|
||||||
|
} finally {
|
||||||
|
importing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 提交表单
|
// 提交表单
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!formRef.value) return
|
if (!formRef.value) return
|
||||||
|
|||||||
231
test_agent_execution.py
Executable file
231
test_agent_execution.py
Executable file
@@ -0,0 +1,231 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Agent工作流执行测试脚本
|
||||||
|
用于测试Agent工作流的正常执行
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# API基础URL
|
||||||
|
BASE_URL = "http://localhost:8037"
|
||||||
|
|
||||||
|
def print_section(title):
|
||||||
|
"""打印分隔线"""
|
||||||
|
print("\n" + "=" * 80)
|
||||||
|
print(f" {title}")
|
||||||
|
print("=" * 80)
|
||||||
|
|
||||||
|
def test_agent_execution(agent_id: str = None, user_input: str = "生成一个导出androidlog的脚本"):
|
||||||
|
"""
|
||||||
|
测试Agent执行
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agent_id: Agent ID,如果为None则自动查找第一个已发布的Agent
|
||||||
|
user_input: 用户输入内容
|
||||||
|
"""
|
||||||
|
print_section("Agent工作流执行测试")
|
||||||
|
|
||||||
|
# 1. 登录获取token
|
||||||
|
print_section("1. 用户登录")
|
||||||
|
login_data = {
|
||||||
|
"username": "admin",
|
||||||
|
"password": "123456"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# OAuth2PasswordRequestForm需要form-data格式
|
||||||
|
response = requests.post(f"{BASE_URL}/api/v1/auth/login", data=login_data)
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"❌ 登录失败: {response.status_code}")
|
||||||
|
print(f"响应: {response.text}")
|
||||||
|
return
|
||||||
|
|
||||||
|
token = response.json().get("access_token")
|
||||||
|
if not token:
|
||||||
|
print("❌ 登录失败: 未获取到token")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("✅ 登录成功")
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 登录异常: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. 获取Agent列表(如果没有指定agent_id)
|
||||||
|
if not agent_id:
|
||||||
|
print_section("2. 查找可用的Agent")
|
||||||
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
f"{BASE_URL}/api/v1/agents",
|
||||||
|
headers=headers,
|
||||||
|
params={"status": "published", "limit": 10}
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
agents = response.json()
|
||||||
|
if agents:
|
||||||
|
# 优先选择已发布的Agent
|
||||||
|
published_agents = [a for a in agents if a.get("status") == "published"]
|
||||||
|
if published_agents:
|
||||||
|
agent_id = published_agents[0]["id"]
|
||||||
|
agent_name = published_agents[0]["name"]
|
||||||
|
print(f"✅ 找到已发布的Agent: {agent_name} (ID: {agent_id})")
|
||||||
|
else:
|
||||||
|
# 如果没有已发布的,使用第一个
|
||||||
|
agent_id = agents[0]["id"]
|
||||||
|
agent_name = agents[0]["name"]
|
||||||
|
print(f"⚠️ 使用Agent: {agent_name} (ID: {agent_id}) - 状态: {agents[0].get('status')}")
|
||||||
|
else:
|
||||||
|
print("❌ 未找到可用的Agent")
|
||||||
|
print("请先创建一个Agent并发布,或者指定agent_id参数")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print(f"❌ 获取Agent列表失败: {response.status_code}")
|
||||||
|
print(f"响应: {response.text}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 获取Agent列表异常: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 3. 执行Agent
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 201:
|
||||||
|
print(f"❌ 创建执行任务失败: {response.status_code}")
|
||||||
|
print(f"响应: {response.text}")
|
||||||
|
return
|
||||||
|
|
||||||
|
execution = response.json()
|
||||||
|
execution_id = execution["id"]
|
||||||
|
print(f"✅ 执行任务已创建: {execution_id}")
|
||||||
|
print(f"状态: {execution.get('status')}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 创建执行任务异常: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 4. 轮询执行状态
|
||||||
|
print_section("4. 等待执行完成")
|
||||||
|
max_wait_time = 300 # 最大等待5分钟
|
||||||
|
start_time = time.time()
|
||||||
|
poll_interval = 2 # 每2秒轮询一次
|
||||||
|
|
||||||
|
while True:
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
if elapsed_time > max_wait_time:
|
||||||
|
print(f"❌ 执行超时(超过{max_wait_time}秒)")
|
||||||
|
break
|
||||||
|
|
||||||
|
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)
|
||||||
|
print(f"⏳ 执行中... 状态: {current_status}, 进度: {progress}%", end="\r")
|
||||||
|
|
||||||
|
if current_status == "completed":
|
||||||
|
print("\n✅ 执行完成!")
|
||||||
|
break
|
||||||
|
elif current_status == "failed":
|
||||||
|
print(f"\n❌ 执行失败")
|
||||||
|
error = status.get("error", "未知错误")
|
||||||
|
print(f"错误信息: {error}")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 显示当前执行的节点
|
||||||
|
current_node = status.get("current_node")
|
||||||
|
if current_node:
|
||||||
|
print(f"\n 当前节点: {current_node.get('node_id')} - {current_node.get('node_name')}")
|
||||||
|
|
||||||
|
time.sleep(poll_interval)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 获取执行状态异常: {str(e)}")
|
||||||
|
time.sleep(poll_interval)
|
||||||
|
|
||||||
|
# 5. 获取执行结果
|
||||||
|
print_section("5. 获取执行结果")
|
||||||
|
try:
|
||||||
|
response = requests.get(
|
||||||
|
f"{BASE_URL}/api/v1/executions/{execution_id}",
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
execution = response.json()
|
||||||
|
status = execution.get("status")
|
||||||
|
output_data = execution.get("output_data")
|
||||||
|
execution_time = execution.get("execution_time")
|
||||||
|
|
||||||
|
print(f"执行状态: {status}")
|
||||||
|
if execution_time:
|
||||||
|
print(f"执行时间: {execution_time}ms")
|
||||||
|
|
||||||
|
print("\n输出结果:")
|
||||||
|
print("-" * 80)
|
||||||
|
if output_data:
|
||||||
|
if isinstance(output_data, dict):
|
||||||
|
# 尝试提取文本输出
|
||||||
|
text_output = (
|
||||||
|
output_data.get("output") or
|
||||||
|
output_data.get("text") or
|
||||||
|
output_data.get("content") or
|
||||||
|
output_data.get("result") 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"❌ 获取执行结果失败: {response.status_code}")
|
||||||
|
print(f"响应: {response.text}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 获取执行结果异常: {str(e)}")
|
||||||
|
|
||||||
|
print_section("测试完成")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 从命令行参数获取agent_id和user_input
|
||||||
|
agent_id = None
|
||||||
|
user_input = "生成一个导出androidlog的脚本"
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
agent_id = sys.argv[1]
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
user_input = sys.argv[2]
|
||||||
|
|
||||||
|
test_agent_execution(agent_id=agent_id, user_input=user_input)
|
||||||
444
test_workflow_tool.py
Executable file
444
test_workflow_tool.py
Executable file
@@ -0,0 +1,444 @@
|
|||||||
|
#!/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):
|
||||||
|
# 尝试提取文本输出
|
||||||
|
text_output = (
|
||||||
|
output_data.get("output") or
|
||||||
|
output_data.get("text") or
|
||||||
|
output_data.get("content") or
|
||||||
|
output_data.get("result") or
|
||||||
|
json.dumps(output_data, ensure_ascii=False, indent=2)
|
||||||
|
)
|
||||||
|
print(text_output)
|
||||||
|
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()
|
||||||
Reference in New Issue
Block a user