--- author: 华钟民 created_at: 2026-05-23 purpose: 在 AI 配置管理页面为场景绑定表和接口工作流表各增加行内测试按钮,工作流测试独立走 endpoint 直调链路 --- # AI 配置行内测试功能设计 ## 1. 问题背景 当前 web-admin 后台「AI 配置管理」页面中: - **场景绑定 Tab**:只有顶部工具栏的全局「流式测试」按钮,需要手动选择场景 - **接口工作流 Tab**:没有任何测试功能 需要在两个表的每行操作列增加「测试」按钮,让用户直接对某一行进行测试。 ## 2. 设计方案 ### 2.1 场景绑定表 — 行内测试 - 每行操作列增加「测试」按钮 - 点击后打开现有测试对话框(`testDialog`),自动填入该行的 `sceneCode` - 后续流程不变,走 `sceneCode → resolveTarget → adapter` 链路 ### 2.2 接口工作流表 — 行内测试 工作流测试独立于场景,直接基于 endpoint 调用。 #### 后端新增 **`AiRuntimeRequest` DTO 增强**(`AiRuntimeRequest.java`): - 新增 `endpointId` 字段 - `fromPayload()` 中新增提取逻辑:`request.setEndpointId(payload.getString("endpointId"))` **`AiRuntimeService` 接口**: ```java // 非流式 endpoint 测试 AiRuntimeTestResponse testEndpoint(String endpointId, Map inputs); // 流式 endpoint 测试(SSE 回调) void invokeEndpointStream(String endpointId, Map inputs, Consumer consumer); ``` **`AiRuntimeServiceImpl` 实现**: - 根据 `endpointId` 查 endpoint(`getEnabledById`,null 时抛 `AI_ENDPOINT_DISABLED`) - 查 provider(`getEnabledById`,null 时抛 `AI_PROVIDER_DISABLED`) - 构造 `AiRuntimeRequest`,设置 `endpointId` 和 `inputs`,`sceneCode` 留空 - 通过 `enrichInputs()` 注入 userId(场景注入因 sceneCode 为空自动跳过) - 根据 `provider.getProviderType()` 查找对应的 `AiProviderAdapter`,调用 `adapter.stream()` - 记录 callLog,`sceneCode` 字段留空,`endpointCode` 填当前 endpoint 编码 - 复用 `AiStreamEvent` 事件体系和 `AiRuntimeTestResponse` 响应结构 **`AiRoutingController` 新增接口**: ```java @PostMapping("/endpoint/test") public Result endpointTest(@RequestBody JSONObject payload) { String endpointId = payload.getString("endpointId"); JSONObject inputs = payload.getJSONObject("inputs"); Map inputMap = inputs == null ? Map.of() : inputs; return Result.success(runtimeService.testEndpoint(endpointId, inputMap)); } @PostMapping("/endpoint/stream") public SseEmitter endpointStream(@RequestBody JSONObject payload) { String endpointId = payload.getString("endpointId"); JSONObject inputs = payload.getJSONObject("inputs"); Map inputMap = inputs == null ? Map.of() : inputs; SseEmitter emitter = new SseEmitter(0L); CompletableFuture.runAsync(() -> { runtimeService.invokeEndpointStream(endpointId, inputMap, event -> sendEvent(emitter, event)); emitter.complete(); }).exceptionally(error -> { sendEvent(emitter, AiStreamEvent.error("AI_ENDPOINT_TEST_INTERRUPTED", error.getMessage())); emitter.completeWithError(error); return null; }); return emitter; } ``` #### 前端新增 **类型定义**(`web-admin/src/types/aiconfig.ts`): ```typescript export interface AiEndpointRuntimeRequest { endpointId: string inputs: Record } ``` **API 层**(`web-admin/src/api/aiconfig.ts`): ```typescript // 非流式 endpoint 测试 export function testEndpointRuntime(data: AiEndpointRuntimeRequest) { return request({ url: '/ai/endpoint/test', method: 'post', data, timeout: 60000 }) } // 流式 endpoint 测试(复用 streamAiRuntime 的 SSE 解析逻辑,只是 endpoint 不同) export async function streamEndpointRuntime( data: AiEndpointRuntimeRequest, onEvent: (event: AiRuntimeStreamEvent, output: string) => void ) { // 内部实现与 streamAiRuntime 相同,只是 URL 为 /ai/endpoint/stream } ``` **UI 层**(`AiRoutingList.vue`): **场景绑定表改造**: - 操作列宽度从 `150` 调整为 `220` - 每行操作列增加「测试」按钮: ```vue 测试 ``` - 新增函数 `openSceneRuntimeTest(row)`:打开现有 `testDialog`,自动填入 `row.sceneCode` - 现有顶部工具栏的「流式测试」按钮保留不变 **接口工作流表改造**: - 操作列宽度从 `150` 调整为 `220` - 每行操作列增加「测试」按钮: ```vue 测试 ``` - 点击打开新对话框 `endpointTestDialog`,标题「接口工作流测试」 - 对话框内容: - 接口名称(只读文本,显示 `row.endpointName` + `row.endpointCode`) - 入参 JSON 框,**自动填入该 endpoint 的 `defaultInputs`**(如果有且为合法 JSON),否则填默认模板 `{}` - 非流式/流式测试结果展示区(复用现有 `` + `
` 样式)
  - 底部:「关闭」「非流式测试」「流式测试」按钮
- 使用独立的响应式状态变量:`endpointTesting`、`endpointTestResult`、`endpointNonStreamResult`、`endpointInputsJson`
- 两个对话框可共存,状态互不干扰

### 2.3 数据流

```
场景绑定行内测试:
  用户点击「测试」→ openSceneRuntimeTest(row) → testDialog 打开,sceneCode= row.sceneCode → 输入 params → 
  流式: streamAiRuntime({ sceneCode, inputs }) → /ai/runtime/stream → AiRuntimeService.invokeStream()
  非流式: testAiRuntime({ sceneCode, inputs }) → /ai/runtime/test → AiRuntimeService.test()

接口工作流行内测试:
  用户点击「测试」→ openEndpointTest(row) → endpointTestDialog 打开 → defaultInputs 已填入 →
  流式: streamEndpointRuntime({ endpointId, inputs }) → /ai/endpoint/stream → AiRuntimeService.invokeEndpointStream()
  非流式: testEndpointRuntime({ endpointId, inputs }) → /ai/endpoint/test → AiRuntimeService.testEndpoint()
```

**endpoint 测试的 enrichInputs 行为**:
- `AiRuntimeRequest` 中 `sceneCode` 为空 → `enrichSceneInputs()` 跳过
- `userId` / `userName` / `userType` / `requestId` 仍通过 `withCurrentUser` + `enrichInputs` 注入
- `socialInsightContext` 等场景级注入不执行(因为 `sceneCode` 为空)

### 2.4 响应字段说明

`AiRuntimeTestResponse.sceneCode` 在 endpoint 测试中为空,前端在 `endpointTestDialog` 中显示 `endpointName` + `endpointCode` 替代。

## 3. 文件清单

| 操作 | 文件 |
|------|------|
| 修改 | `backend-single/src/main/java/com/emotion/dto/request/ai/AiRuntimeRequest.java` |
| 修改 | `backend-single/src/main/java/com/emotion/service/AiRuntimeService.java` |
| 修改 | `backend-single/src/main/java/com/emotion/service/impl/AiRuntimeServiceImpl.java` |
| 修改 | `backend-single/src/main/java/com/emotion/controller/AiRoutingController.java` |
| 修改 | `web-admin/src/types/aiconfig.ts` |
| 修改 | `web-admin/src/api/aiconfig.ts` |
| 修改 | `web-admin/src/views/aiconfig/AiRoutingList.vue` |

## 4. 风险

- endpoint 测试不经过场景绑定,因此不会有场景相关的输入注入(如 socialInsightContext)。这是预期行为——endpoint 测试关注的是接口本身是否通
- endpoint 的 `defaultInputs` 可能不完整或格式不合法,对话框打开时会尝试 JSON.parse,失败时回退到 `{}`
- 接口工作流表的「测试」按钮在 `isEnabled !== 1` 时禁用,避免无效调用
- 新增两个后端接口,需确保权限校验和现有 `/ai/runtime/*` 接口一致(走同一个 Admin 鉴权)
- 操作列宽度从 150 调整到 220,确保三按钮(编辑/删除/测试)不重叠