Files
happy-life-star/docs/superpowers/specs/2026-05-23-ai-config-row-test-design.md
T

175 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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<String, Object> inputs);
// 流式 endpoint 测试(SSE 回调)
void invokeEndpointStream(String endpointId, Map<String, Object> inputs, Consumer<AiStreamEvent> 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<AiRuntimeTestResponse> endpointTest(@RequestBody JSONObject payload) {
String endpointId = payload.getString("endpointId");
JSONObject inputs = payload.getJSONObject("inputs");
Map<String, Object> 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<String, Object> 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<string, any>
}
```
**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
<el-button link type="success" @click="openSceneRuntimeTest(row)">测试</el-button>
```
- 新增函数 `openSceneRuntimeTest(row)`:打开现有 `testDialog`,自动填入 `row.sceneCode`
- 现有顶部工具栏的「流式测试」按钮保留不变
**接口工作流表改造**
- 操作列宽度从 `150` 调整为 `220`
- 每行操作列增加「测试」按钮:
```vue
<el-button link type="success" :disabled="row.isEnabled !== 1" @click="openEndpointTest(row)">测试</el-button>
```
- 点击打开新对话框 `endpointTestDialog`,标题「接口工作流测试」
- 对话框内容:
- 接口名称(只读文本,显示 `row.endpointName` + `row.endpointCode`
- 入参 JSON 框,**自动填入该 endpoint 的 `defaultInputs`**(如果有且为合法 JSON),否则填默认模板 `{}`
- 非流式/流式测试结果展示区(复用现有 `<el-alert>` + `<pre>` 样式)
- 底部:「关闭」「非流式测试」「流式测试」按钮
- 使用独立的响应式状态变量:`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,确保三按钮(编辑/删除/测试)不重叠