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

7.7 KiB
Raw Blame History

author, created_at, purpose
author created_at purpose
华钟民 2026-05-23 在 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 接口

// 非流式 endpoint 测试
AiRuntimeTestResponse testEndpoint(String endpointId, Map<String, Object> inputs);

// 流式 endpoint 测试(SSE 回调)
void invokeEndpointStream(String endpointId, Map<String, Object> inputs, Consumer<AiStreamEvent> consumer);

AiRuntimeServiceImpl 实现

  • 根据 endpointId 查 endpointgetEnabledByIdnull 时抛 AI_ENDPOINT_DISABLED
  • 查 providergetEnabledByIdnull 时抛 AI_PROVIDER_DISABLED
  • 构造 AiRuntimeRequest,设置 endpointIdinputssceneCode 留空
  • 通过 enrichInputs() 注入 userId(场景注入因 sceneCode 为空自动跳过)
  • 根据 provider.getProviderType() 查找对应的 AiProviderAdapter,调用 adapter.stream()
  • 记录 callLogsceneCode 字段留空,endpointCode 填当前 endpoint 编码
  • 复用 AiStreamEvent 事件体系和 AiRuntimeTestResponse 响应结构

AiRoutingController 新增接口

@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):

export interface AiEndpointRuntimeRequest {
  endpointId: string
  inputs: Record<string, any>
}

API 层web-admin/src/api/aiconfig.ts):

// 非流式 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
  • 每行操作列增加「测试」按钮:
<el-button link type="success" @click="openSceneRuntimeTest(row)">测试</el-button>
  • 新增函数 openSceneRuntimeTest(row):打开现有 testDialog,自动填入 row.sceneCode
  • 现有顶部工具栏的「流式测试」按钮保留不变

接口工作流表改造

  • 操作列宽度从 150 调整为 220
  • 每行操作列增加「测试」按钮:
<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> 样式)
    • 底部:「关闭」「非流式测试」「流式测试」按钮
  • 使用独立的响应式状态变量:endpointTestingendpointTestResultendpointNonStreamResultendpointInputsJson
  • 两个对话框可共存,状态互不干扰

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 行为

  • AiRuntimeRequestsceneCode 为空 → 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,确保三按钮(编辑/删除/测试)不重叠