From d59f015362372bf953b857c27b891924189d454e Mon Sep 17 00:00:00 2001 From: rjb <263303411@qq.com> Date: Tue, 20 Jan 2026 11:03:55 +0800 Subject: [PATCH] =?UTF-8?q?android=E5=BA=94=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Agent使用说明.md | 283 +++++++++++ androidExample/README.md | 180 +++++++ androidExample/app/build.gradle.kts | 70 +++ .../models/AgentExecutionRequest.kt | 14 + .../agentclient/models/ExecutionResponse.kt | 27 ++ .../agentclient/models/TokenResponse.kt | 14 + .../example/agentclient/utils/ApiClient.kt | 99 ++++ androidExample/build.gradle.kts | 9 + androidExample/settings.gradle.kts | 18 + backend/app/api/agents.py | 109 +++++ backend/app/services/workflow_engine.py | 26 +- .../WorkflowEditor/WorkflowEditor.vue | 12 +- frontend/src/stores/agent.ts | 59 ++- frontend/src/views/Agents.vue | 176 ++++++- test_agent_execution.py | 231 +++++++++ test_workflow_tool.py | 444 ++++++++++++++++++ 16 files changed, 1754 insertions(+), 17 deletions(-) create mode 100644 Agent使用说明.md create mode 100644 androidExample/README.md create mode 100644 androidExample/app/build.gradle.kts create mode 100644 androidExample/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.kt create mode 100644 androidExample/app/src/main/java/com/example/agentclient/models/ExecutionResponse.kt create mode 100644 androidExample/app/src/main/java/com/example/agentclient/models/TokenResponse.kt create mode 100644 androidExample/app/src/main/java/com/example/agentclient/utils/ApiClient.kt create mode 100644 androidExample/build.gradle.kts create mode 100644 androidExample/settings.gradle.kts create mode 100755 test_agent_execution.py create mode 100755 test_workflow_tool.py diff --git a/Agent使用说明.md b/Agent使用说明.md new file mode 100644 index 0000000..e97bbe4 --- /dev/null +++ b/Agent使用说明.md @@ -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 " \ + -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 " +``` + +### 示例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+ diff --git a/androidExample/README.md b/androidExample/README.md new file mode 100644 index 0000000..2b621e0 --- /dev/null +++ b/androidExample/README.md @@ -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 +Content-Type: application/json + +{ + "agent_id": "agent-id", + "input_data": { + "query": "用户输入", + "USER_INPUT": "用户输入" + } +} +``` + +### 获取执行状态API +``` +GET /api/v1/executions/{execution_id}/status +Authorization: Bearer +``` + +### 获取执行结果API +``` +GET /api/v1/executions/{execution_id} +Authorization: Bearer +``` + +## 🎯 示例:调用情感分析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) diff --git a/androidExample/app/build.gradle.kts b/androidExample/app/build.gradle.kts new file mode 100644 index 0000000..c3d86d5 --- /dev/null +++ b/androidExample/app/build.gradle.kts @@ -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") +} diff --git a/androidExample/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.kt b/androidExample/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.kt new file mode 100644 index 0000000..a88bb81 --- /dev/null +++ b/androidExample/app/src/main/java/com/example/agentclient/models/AgentExecutionRequest.kt @@ -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 +) diff --git a/androidExample/app/src/main/java/com/example/agentclient/models/ExecutionResponse.kt b/androidExample/app/src/main/java/com/example/agentclient/models/ExecutionResponse.kt new file mode 100644 index 0000000..4a405e2 --- /dev/null +++ b/androidExample/app/src/main/java/com/example/agentclient/models/ExecutionResponse.kt @@ -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?, + + @SerializedName("output_data") + val outputData: Any?, + + @SerializedName("created_at") + val createdAt: String +) diff --git a/androidExample/app/src/main/java/com/example/agentclient/models/TokenResponse.kt b/androidExample/app/src/main/java/com/example/agentclient/models/TokenResponse.kt new file mode 100644 index 0000000..f52042b --- /dev/null +++ b/androidExample/app/src/main/java/com/example/agentclient/models/TokenResponse.kt @@ -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" +) diff --git a/androidExample/app/src/main/java/com/example/agentclient/utils/ApiClient.kt b/androidExample/app/src/main/java/com/example/agentclient/utils/ApiClient.kt new file mode 100644 index 0000000..6eea4e5 --- /dev/null +++ b/androidExample/app/src/main/java/com/example/agentclient/utils/ApiClient.kt @@ -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 +} diff --git a/androidExample/build.gradle.kts b/androidExample/build.gradle.kts new file mode 100644 index 0000000..e2539ba --- /dev/null +++ b/androidExample/build.gradle.kts @@ -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) +} diff --git a/androidExample/settings.gradle.kts b/androidExample/settings.gradle.kts new file mode 100644 index 0000000..83e9ea9 --- /dev/null +++ b/androidExample/settings.gradle.kts @@ -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") diff --git a/backend/app/api/agents.py b/backend/app/api/agents.py index 8643a9b..510a944 100644 --- a/backend/app/api/agents.py +++ b/backend/app/api/agents.py @@ -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 diff --git a/backend/app/services/workflow_engine.py b/backend/app/services/workflow_engine.py index 63312b8..a0dd351 100644 --- a/backend/app/services/workflow_engine.py +++ b/backend/app/services/workflow_engine.py @@ -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']: diff --git a/frontend/src/components/WorkflowEditor/WorkflowEditor.vue b/frontend/src/components/WorkflowEditor/WorkflowEditor.vue index 2f1a8a7..b2b0e9a 100644 --- a/frontend/src/components/WorkflowEditor/WorkflowEditor.vue +++ b/frontend/src/components/WorkflowEditor/WorkflowEditor.vue @@ -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, diff --git a/frontend/src/stores/agent.ts b/frontend/src/stores/agent.ts index db169cf..ac67a06 100644 --- a/frontend/src/stores/agent.ts +++ b/frontend/src/stores/agent.ts @@ -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 } }) diff --git a/frontend/src/views/Agents.vue b/frontend/src/views/Agents.vue index 784b04a..d74e22c 100644 --- a/frontend/src/views/Agents.vue +++ b/frontend/src/views/Agents.vue @@ -5,10 +5,16 @@ @@ -70,12 +76,21 @@ {{ formatDate(row.created_at) }} - + + + + + + +
+ 将JSON文件拖到此处,或点击上传 +
+ +
+ +
+ + + +
+ + +
@@ -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(null) +// 导入相关 +const importDialogVisible = ref(false) +const fileList = ref([]) +const importFileContent = ref(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 diff --git a/test_agent_execution.py b/test_agent_execution.py new file mode 100755 index 0000000..0049a63 --- /dev/null +++ b/test_agent_execution.py @@ -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) diff --git a/test_workflow_tool.py b/test_workflow_tool.py new file mode 100755 index 0000000..644a20b --- /dev/null +++ b/test_workflow_tool.py @@ -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()