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})")
|
||||
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':
|
||||
branch = result.get('branch', 'false')
|
||||
logger.info(f"[rjb] 条件节点分支过滤: node_id={next_node_id}, branch={branch}")
|
||||
# 移除不符合条件的边
|
||||
active_edges = [
|
||||
edge for edge in active_edges
|
||||
if not (edge['source'] == next_node_id and edge.get('sourceHandle') != branch)
|
||||
]
|
||||
# 只保留:1) 不是从条件节点出发的边,或 2) 从条件节点出发且sourceHandle匹配分支的边
|
||||
edges_to_remove = []
|
||||
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']:
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
:node-types="customNodeTypes"
|
||||
:connection-line-style="{ stroke: '#409eff', strokeWidth: 2.5, strokeDasharray: '5,5' }"
|
||||
:default-edge-options="{
|
||||
type: 'smoothstep',
|
||||
type: 'bezier',
|
||||
animated: true,
|
||||
selectable: true,
|
||||
deletable: true,
|
||||
@@ -100,7 +100,7 @@
|
||||
style: { stroke: '#409eff', strokeWidth: 2.5 },
|
||||
markerEnd: { type: 'arrowclosed', color: '#409eff', width: 20, height: 20 }
|
||||
}"
|
||||
:connection-line-type="'smoothstep'"
|
||||
:connection-line-type="'bezier'"
|
||||
:edges-focusable="true"
|
||||
:nodes-focusable="true"
|
||||
:delete-key-code="'Delete'"
|
||||
@@ -1902,7 +1902,7 @@ const onConnect = (connection: Connection) => {
|
||||
target: connection.target,
|
||||
sourceHandle: connection.sourceHandle || undefined,
|
||||
targetHandle: connection.targetHandle || undefined,
|
||||
type: 'smoothstep', // 使用平滑步进曲线(类似 Dify)
|
||||
type: 'bezier', // 使用贝塞尔曲线(平滑曲线)
|
||||
animated: true,
|
||||
selectable: true,
|
||||
deletable: true,
|
||||
@@ -2700,7 +2700,7 @@ watch(
|
||||
selectable: true,
|
||||
deletable: true,
|
||||
focusable: true,
|
||||
type: edge.type || 'smoothstep',
|
||||
type: edge.type || 'bezier',
|
||||
animated: true,
|
||||
style: {
|
||||
stroke: '#409eff',
|
||||
@@ -2955,7 +2955,7 @@ onMounted(async () => {
|
||||
selectable: true,
|
||||
deletable: true,
|
||||
focusable: true,
|
||||
type: edge.type || 'smoothstep',
|
||||
type: edge.type || 'bezier',
|
||||
animated: true,
|
||||
style: {
|
||||
stroke: '#409eff',
|
||||
@@ -3006,7 +3006,7 @@ onMounted(async () => {
|
||||
target: edge.target,
|
||||
sourceHandle: edge.sourceHandle,
|
||||
targetHandle: edge.targetHandle,
|
||||
type: edge.type || 'smoothstep',
|
||||
type: edge.type || 'bezier',
|
||||
animated: true,
|
||||
selectable: true,
|
||||
deletable: true,
|
||||
|
||||
@@ -166,6 +166,61 @@ export const useAgentStore = defineStore('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 {
|
||||
agents,
|
||||
currentAgent,
|
||||
@@ -178,6 +233,8 @@ export const useAgentStore = defineStore('agent', () => {
|
||||
deployAgent,
|
||||
stopAgent,
|
||||
duplicateAgent,
|
||||
setCurrentAgent
|
||||
setCurrentAgent,
|
||||
exportAgent,
|
||||
importAgent
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,10 +5,16 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<h2>Agent管理</h2>
|
||||
<el-button type="primary" @click="handleCreate">
|
||||
<el-icon><Plus /></el-icon>
|
||||
创建Agent
|
||||
</el-button>
|
||||
<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>
|
||||
创建Agent
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -70,12 +76,21 @@
|
||||
{{ formatDate(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="350" fixed="right">
|
||||
<el-table-column label="操作" width="480" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="handleEdit(row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑
|
||||
</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-icon><Setting /></el-icon>
|
||||
设计
|
||||
@@ -84,6 +99,10 @@
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
复制
|
||||
</el-button>
|
||||
<el-button link type="success" @click="handleExport(row)">
|
||||
<el-icon><Download /></el-icon>
|
||||
导出
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.status === 'draft' || row.status === 'stopped'"
|
||||
link
|
||||
@@ -164,6 +183,63 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</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>
|
||||
</MainLayout>
|
||||
</template>
|
||||
@@ -182,7 +258,11 @@ import {
|
||||
Setting,
|
||||
VideoPlay,
|
||||
VideoPause,
|
||||
CopyDocument
|
||||
CopyDocument,
|
||||
Upload,
|
||||
Download,
|
||||
UploadFilled,
|
||||
ChatDotRound
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useAgentStore } from '@/stores/agent'
|
||||
import type { Agent } from '@/stores/agent'
|
||||
@@ -225,6 +305,13 @@ const form = ref({
|
||||
})
|
||||
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 = {
|
||||
name: [
|
||||
@@ -331,6 +418,14 @@ const handleEdit = (agent: Agent) => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 使用Agent(跳转到设计器,那里有聊天界面)
|
||||
const handleUse = (agent: Agent) => {
|
||||
router.push({
|
||||
name: 'AgentDesigner',
|
||||
params: { id: agent.id }
|
||||
})
|
||||
}
|
||||
|
||||
// 设计
|
||||
const handleDesign = (agent: Agent) => {
|
||||
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 () => {
|
||||
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