docs: 修复 AI 配置行内测试设计文档 - 补充 DTO/类型定义/UI 细节
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,10 @@ purpose: 在 AI 配置管理页面为场景绑定表和接口工作流表各增
|
|||||||
|
|
||||||
#### 后端新增
|
#### 后端新增
|
||||||
|
|
||||||
|
**`AiRuntimeRequest` DTO 增强**(`AiRuntimeRequest.java`):
|
||||||
|
- 新增 `endpointId` 字段
|
||||||
|
- `fromPayload()` 中新增提取逻辑:`request.setEndpointId(payload.getString("endpointId"))`
|
||||||
|
|
||||||
**`AiRuntimeService` 接口**:
|
**`AiRuntimeService` 接口**:
|
||||||
```java
|
```java
|
||||||
// 非流式 endpoint 测试
|
// 非流式 endpoint 测试
|
||||||
@@ -38,57 +42,99 @@ void invokeEndpointStream(String endpointId, Map<String, Object> inputs, Consume
|
|||||||
```
|
```
|
||||||
|
|
||||||
**`AiRuntimeServiceImpl` 实现**:
|
**`AiRuntimeServiceImpl` 实现**:
|
||||||
- 根据 `endpointId` 查 endpoint(需 enabled)→ 查 provider(需 enabled)→ 调 adapter
|
- 根据 `endpointId` 查 endpoint(`getEnabledById`,null 时抛 `AI_ENDPOINT_DISABLED`)
|
||||||
- 不做场景相关的输入注入(不注入 socialInsightContext 等)
|
- 查 provider(`getEnabledById`,null 时抛 `AI_PROVIDER_DISABLED`)
|
||||||
- 做基本的 userId 注入(从 UserContextHolder 获取)
|
- 构造 `AiRuntimeRequest`,设置 `endpointId` 和 `inputs`,`sceneCode` 留空
|
||||||
- 记录 callLog,但 sceneCode 字段为空或填 endpointCode
|
- 通过 `enrichInputs()` 注入 userId(场景注入因 sceneCode 为空自动跳过)
|
||||||
- 复用 `RuntimeTarget` 类的类似逻辑,新建 `EndpointTarget` 内部类
|
- 根据 `provider.getProviderType()` 查找对应的 `AiProviderAdapter`,调用 `adapter.stream()`
|
||||||
|
- 记录 callLog,`sceneCode` 字段留空,`endpointCode` 填当前 endpoint 编码
|
||||||
|
- 复用 `AiStreamEvent` 事件体系和 `AiRuntimeTestResponse` 响应结构
|
||||||
|
|
||||||
**`AiRoutingController` 新增接口**:
|
**`AiRoutingController` 新增接口**:
|
||||||
```java
|
```java
|
||||||
@PostMapping("/endpoint/test")
|
@PostMapping("/endpoint/test")
|
||||||
public Result<AiRuntimeTestResponse> endpointTest(@RequestBody JSONObject payload)
|
public Result<AiRuntimeTestResponse> endpointTest(@RequestBody JSONObject payload) {
|
||||||
// payload: { endpointId: "xxx", inputs: { ... } }
|
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")
|
@PostMapping("/endpoint/stream")
|
||||||
public SseEmitter endpointStream(@RequestBody JSONObject payload)
|
public SseEmitter endpointStream(@RequestBody JSONObject payload) {
|
||||||
// payload: { endpointId: "xxx", inputs: { ... } }
|
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`):
|
**API 层**(`web-admin/src/api/aiconfig.ts`):
|
||||||
```typescript
|
```typescript
|
||||||
// 非流式 endpoint 测试
|
// 非流式 endpoint 测试
|
||||||
export function testEndpointRuntime(data: { endpointId: string; inputs: Record<string, any> }) {
|
export function testEndpointRuntime(data: AiEndpointRuntimeRequest) {
|
||||||
return request({ url: '/ai/endpoint/test', method: 'post', data, timeout: 60000 })
|
return request({ url: '/ai/endpoint/test', method: 'post', data, timeout: 60000 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 流式 endpoint 测试
|
// 流式 endpoint 测试(复用 streamAiRuntime 的 SSE 解析逻辑,只是 endpoint 不同)
|
||||||
export async function streamEndpointRuntime(
|
export async function streamEndpointRuntime(
|
||||||
data: { endpointId: string; inputs: Record<string, any> },
|
data: AiEndpointRuntimeRequest,
|
||||||
onEvent: (event: AiRuntimeStreamEvent, output: string) => void
|
onEvent: (event: AiRuntimeStreamEvent, output: string) => void
|
||||||
)
|
) {
|
||||||
|
// 内部实现与 streamAiRuntime 相同,只是 URL 为 /ai/endpoint/stream
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**UI 层**(`AiRoutingList.vue`):
|
**UI 层**(`AiRoutingList.vue`):
|
||||||
- 接口工作流表操作列增加「测试」按钮
|
|
||||||
- 点击打开新对话框 `endpointTestDialog`,标题「接口测试」
|
|
||||||
- 对话框内容:
|
|
||||||
- 显示接口名称(只读)
|
|
||||||
- 入参 JSON 框,**自动填入该 endpoint 的 `defaultInputs`**(如果有),否则填默认模板 `{}`
|
|
||||||
- 非流式/流式测试结果展示区(和现有测试对话框一样的布局)
|
|
||||||
- 底部:「关闭」「非流式测试」「流式测试」按钮
|
|
||||||
|
|
||||||
- 场景绑定表操作列增加「测试」按钮
|
**场景绑定表改造**:
|
||||||
- 点击打开现有 `testDialog`,自动填入该行的 `sceneCode`
|
- 操作列宽度从 `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 数据流
|
### 2.3 数据流
|
||||||
|
|
||||||
```
|
```
|
||||||
场景绑定行内测试:
|
场景绑定行内测试:
|
||||||
用户点击「测试」→ openRuntimeTest(row.sceneCode) → testDialog 打开 → 输入 params →
|
用户点击「测试」→ openSceneRuntimeTest(row) → testDialog 打开,sceneCode= row.sceneCode → 输入 params →
|
||||||
流式: streamAiRuntime({ sceneCode, inputs }) → /ai/runtime/stream → AiRuntimeService.invokeStream()
|
流式: streamAiRuntime({ sceneCode, inputs }) → /ai/runtime/stream → AiRuntimeService.invokeStream()
|
||||||
非流式: testAiRuntime({ sceneCode, inputs }) → /ai/runtime/test → AiRuntimeService.test()
|
非流式: testAiRuntime({ sceneCode, inputs }) → /ai/runtime/test → AiRuntimeService.test()
|
||||||
|
|
||||||
@@ -98,18 +144,31 @@ export async function streamEndpointRuntime(
|
|||||||
非流式: testEndpointRuntime({ endpointId, inputs }) → /ai/endpoint/test → AiRuntimeService.testEndpoint()
|
非流式: 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. 文件清单
|
## 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/AiRuntimeService.java` |
|
||||||
| 修改 | `backend-single/src/main/java/com/emotion/service/impl/AiRuntimeServiceImpl.java` |
|
| 修改 | `backend-single/src/main/java/com/emotion/service/impl/AiRuntimeServiceImpl.java` |
|
||||||
| 修改 | `backend-single/src/main/java/com/emotion/controller/AiRoutingController.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/api/aiconfig.ts` |
|
||||||
| 修改 | `web-admin/src/views/aiconfig/AiRoutingList.vue` |
|
| 修改 | `web-admin/src/views/aiconfig/AiRoutingList.vue` |
|
||||||
|
|
||||||
## 4. 风险
|
## 4. 风险
|
||||||
|
|
||||||
- endpoint 测试不经过场景绑定,因此不会有场景相关的输入注入(如 socialInsightContext)。这是预期行为——endpoint 测试关注的是接口本身是否通
|
- endpoint 测试不经过场景绑定,因此不会有场景相关的输入注入(如 socialInsightContext)。这是预期行为——endpoint 测试关注的是接口本身是否通
|
||||||
- endpoint 的 `defaultInputs` 可能不完整,用户仍可手动修改入参
|
- endpoint 的 `defaultInputs` 可能不完整或格式不合法,对话框打开时会尝试 JSON.parse,失败时回退到 `{}`
|
||||||
|
- 接口工作流表的「测试」按钮在 `isEnabled !== 1` 时禁用,避免无效调用
|
||||||
- 新增两个后端接口,需确保权限校验和现有 `/ai/runtime/*` 接口一致(走同一个 Admin 鉴权)
|
- 新增两个后端接口,需确保权限校验和现有 `/ai/runtime/*` 接口一致(走同一个 Admin 鉴权)
|
||||||
|
- 操作列宽度从 150 调整到 220,确保三按钮(编辑/删除/测试)不重叠
|
||||||
|
|||||||
Reference in New Issue
Block a user