# AI 开放平台实现记录文档 > 基于 `integrated-medical-agent-dify-card-solution.md` 第五章(数据库设计)和第十八章(工程模块设计)的实现成果。 --- ## 一、实现范围总览 本次实现分三个阶段完成,共涉及 **43 个文件**(新建 39 + 修改 4)。 | 阶段 | 内容 | 模块 | 文件数 | |------|------|------|--------| | Phase 1 | AgentEngine 引擎框架 + DO + Mapper + Service 接口 | mcp-api / system-api | 33 | | Phase 2 | OpenPlatform 对话 + 会话管理 API | openplatform | 10(新建) + 2(修改) | | Phase 3 | Admin 端引擎配置 CRUD 接口 | system-api / emoon-system | 4(新建) + 2(修改) | --- ## 二、Phase 1 - AgentEngine 引擎框架与基础设施 ### 2.1 AgentEngine 可插拔引擎框架 位于 `emoon-infra/emoon-modules-api/emoon-mcp-api/src/main/java/com/emoon/mcp/engine/` | 文件 | 说明 | |------|------| | `AgentEngine.java` | 引擎核心接口,定义 `chat()`(同步)、`chatStream()`(流式)、`getEngineType()`(路由标识) | | `AgentRequest.java` | 统一请求对象,包含 agentId、query、conversationId、engineConfig、inputs、files | | `AgentResponse.java` | 统一响应对象,包含 reply、cardKey、cardData、conversationId、messageId、usage、finished | | `AgentEngineFactory.java` | 引擎工厂,Spring 启动时自动收集所有 AgentEngine Bean,按 `getEngineType()` 路由 | **路由机制**: ``` AgentEngineFactory.getEngine("dify") -> DifyAgentEngine(待实现) AgentEngineFactory.getEngine("mock") -> MockAgentEngine(已有骨架) AgentEngineFactory.getEngine("direct") -> DirectLLMAgentEngine(待实现) ``` ### 2.2 DO 对象(8 个 AI 相关表) #### system-api 模块(6 个) 位于 `emoon-infra/emoon-modules-api/emoon-system-api/src/main/java/com/emoon/system/domain/` | DO 类 | 对应表 | 关键字段 | 说明 | |-------|--------|----------|------| | `AiAgentEngineConfig` | ai_agent_engine_config | id, configName, engineType, configJson, status | 引擎连接配置(baseUrl/apiKey 等存在 configJson 中) | | `AiAgentApp` | ai_agent_app | id, agentId, agentName, agentType, engineConfigId | 智能体元数据 | | `AiUsageLog` | ai_usage_log | agentId, engineType, promptTokens, totalTokens | 调用日志 | | `AiCardCategory` | ai_card_category | categoryKey, name, parentId, sortOrder | 卡片分类(树形) | | `AiCardDefinition` | ai_card_definition | cardKey, version, schemaJson, uiConfigJson | 卡片 UI 模板 | | `AiCardPlugin` | ai_card_plugin | pluginId, name, version, manifestJson, auditStatus | 第三方卡片插件 | #### mcp-api 模块(2 个) 位于 `emoon-infra/emoon-modules-api/emoon-mcp-api/src/main/java/com/emoon/mcp/domain/` | DO 类 | 对应表 | 关键字段 | 说明 | |-------|--------|----------|------| | `AiConversation` | ai_conversation | conversationId, agentId(Long), engineType, status | 会话记录 | | `AiCardInstance` | ai_card_instance | instanceId, cardKey, cardVersion, stateJson | 卡片实例 | **所有 DO 的共同特征**: - 继承 `TenantEntity`(含 tenantId)-> `BaseEntity`(含 createBy/updateBy/createTime/updateTime) - `delFlag`、`status` 字段类型为 `String`("0"/"1"),不是 int - 日期字段使用 `java.util.Date` - `agentId` 在 `AiConversation` 中是 `Long` 类型(FK 到 ai_agent_app.id),不是 String 业务 ID ### 2.3 Mapper 接口(10 个) | Mapper | 泛型 | 模块 | |--------|------|------| | `AiAgentEngineConfigMapper` | `BaseMapperPlus` | system-api | | `AiAgentAppMapper` | `BaseMapperPlus` | system-api | | `AiUsageLogMapper` | `BaseMapperPlus` | system-api | | `AiCardCategoryMapper` | `BaseMapperPlus` | system-api | | `AiCardDefinitionMapper` | `BaseMapperPlus` | system-api | | `AiCardActionLogMapper` | `BaseMapperPlus` | system-api | | `AiCardPluginMapper` | `BaseMapperPlus` | system-api | | `AiCardGrayConfigMapper` | `BaseMapperPlus` | system-api | | `AiConversationMapper` | `BaseMapperPlus` | mcp-api | | `AiCardInstanceMapper` | `BaseMapperPlus` | mcp-api | ### 2.4 Service 接口(9 个) 位于 `emoon-infra/emoon-modules-api/emoon-system-api/src/main/java/com/emoon/system/service/` 每个 Service 接口提供标准的 `queryById`、`queryList`、`insert`、`update`、`deleteWithValidByIds` 方法。 --- ## 三、Phase 2 - OpenPlatform 对话与会话管理 API ### 3.1 文件清单 位于 `emoon-openplatform/src/main/java/com/emoon/openplatform/` | 路径 | 说明 | |------|------| | `domain/dto/request/AgentChatRequest.java` | 对话请求 DTO | | `domain/dto/request/ConversationCreateRequest.java` | 创建会话请求 DTO | | `domain/dto/resp/AgentChatResponse.java` | 对话响应 DTO(同步 + SSE 共用) | | `domain/dto/resp/ConversationVo.java` | 会话视图对象 | | `util/SignUtil.java` | 签名验证工具(MD5,5 分钟有效期) | | `service/IConversationService.java` | 会话管理 Service 接口 | | `service/IAgentChatService.java` | 对话 Service 接口 | | `service/impl/ConversationServiceImpl.java` | 会话管理实现 | | `service/impl/AgentChatServiceImpl.java` | 对话核心编排实现 | | `controller/v1/AgentController.java` | REST 控制器(4 个接口) | **修改的文件**: | 路径 | 变更 | |------|------| | `pom.xml` | 新增 emoon-mcp-api、emoon-system-api 依赖 | | `enums/SystemEnum.java` | 新增 4 个错误码(AGENT_NOT_FOUND/AGENT_DISABLED/ENGINE_CONFIG_NOT_FOUND/CONVERSATION_NOT_FOUND) | ### 3.2 认证机制 所有 OpenPlatform API 使用请求头签名认证: | Header | 说明 | |--------|------| | `X-Emoon-Timestamp` | 毫秒时间戳 | | `X-Emoon-Request-Id` | 请求唯一标识(UUID) | | `X-Emoon-Public-Key` | 项目公钥(查 sys_project 表) | | `X-Emoon-Sign` | 签名值 | **签名算法**: ``` sign = MD5(requestId + "&" + timestamp + "&" + payload + "&" + privateKey).toLowerCase() ``` - `payload`:POST 请求为 JSON body 字符串,GET 请求为空字符串 - 时间戳有效期 5 分钟 ### 3.3 API 接口定义 #### 3.3.1 POST /api/v1/agent/chat - 智能体对话 支持同步和 SSE 流式两种模式,由 `stream` 字段控制。 **请求体**: ```json { "agentId": "agent_123456", "query": "帮我挂个内科的号", "conversationId": null, "stream": false, "userId": 10001, "inputs": {}, "files": [] } ``` **同步响应**(`stream=false`): ```json { "code": 200, "msg": "操作成功", "data": { "reply": "好的,我来帮您挂内科的号。请问您想挂哪个医生?", "cardKey": null, "cardData": null, "conversationId": "conv_a1b2c3d4-xxxx", "messageId": "msg_x1y2z3", "usage": { "promptTokens": 120, "completionTokens": 45, "totalTokens": 165 }, "finished": true } } ``` **SSE 流式响应**(`stream=true`): 返回 `text/event-stream`,每个事件的 data 字段是一个 `AgentChatResponse` JSON: ``` data: {"reply":"好的","conversationId":"conv_xxx","messageId":"msg_xxx","finished":false} data: {"reply":",我来帮您","conversationId":"conv_xxx","messageId":"msg_xxx","finished":false} data: {"reply":"挂内科的号。","conversationId":"conv_xxx","messageId":"msg_xxx","usage":{"promptTokens":120,"completionTokens":45,"totalTokens":165},"finished":true} ``` #### 3.3.2 POST /api/v1/conversation/create - 创建会话 **请求体**: ```json { "agentId": "agent_123456", "userId": 10001, "conversationName": "挂号咨询" } ``` **响应**: ```json { "code": 200, "msg": "操作成功", "data": { "conversationId": "conv_a1b2c3d4-xxxx", "agentId": 1, "conversationName": "挂号咨询", "status": "active", "messageCount": 0, "totalTokens": 0, "lastMessageTime": null, "createTime": "2026-03-26T12:00:00.000+00:00" } } ``` #### 3.3.3 GET /api/v1/conversation/list - 查询会话列表 **请求参数**(Query String): | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | agentId | String | 否 | 按智能体筛选 | | userId | Long | 否 | 按用户筛选 | **响应**: ```json { "code": 200, "msg": "操作成功", "data": [ { "conversationId": "conv_a1b2c3d4-xxxx", "agentId": 1, "conversationName": "挂号咨询", "status": "active", "messageCount": 5, "totalTokens": 820, "lastMessageTime": "2026-03-26T12:05:00.000+00:00", "createTime": "2026-03-26T12:00:00.000+00:00" } ] } ``` #### 3.3.4 GET /api/v1/conversation/{conversationId} - 查询会话详情 **路径参数**: | 参数 | 说明 | |------|------| | conversationId | 会话 UUID | **响应**:与列表中单个元素结构相同。 --- ## 四、Phase 3 - Admin 端引擎配置 CRUD ### 4.1 文件清单 **新建文件**: | 路径 | 说明 | |------|------| | `system-api/.../domain/bo/AiAgentEngineConfigBo.java` | 业务对象,`@AutoMapper` + 入参校验 | | `system-api/.../domain/vo/AiAgentEngineConfigVo.java` | 视图对象,`@AutoMapper` 自动转换 | | `system-api/.../service/impl/AiAgentEngineConfigServiceImpl.java` | Service 实现(分页/增/改/删/唯一校验) | | `emoon-system/.../controller/system/AiAgentEngineConfigController.java` | REST 控制器 | **修改文件**: | 路径 | 变更 | |------|------| | `system-api/.../service/IAiAgentEngineConfigService.java` | 重构为 Bo/Vo 模式,增加分页查询 | | `system-api/.../mapper/AiAgentEngineConfigMapper.java` | 第二泛型参数改为 `AiAgentEngineConfigVo` | ### 4.2 API 接口定义 基础路径:`/system/ai/engineConfig` 所有接口需要 Sa-Token 权限认证(管理后台登录态)。 #### 4.2.1 GET /system/ai/engineConfig/list - 分页查询 **请求参数**(Query String): | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | configName | String | 否 | 模糊匹配 | | engineType | String | 否 | 精确匹配:dify / direct / spring_ai / mock | | status | String | 否 | 精确匹配:0=启用 1=停用 | | projectId | Integer | 否 | 精确匹配 | | pageNum | Integer | 是 | 页码 | | pageSize | Integer | 是 | 每页大小 | **权限**:`ai:engineConfig:list` **cURL 示例**: ```bash curl -X GET 'http://localhost:8080/system/ai/engineConfig/list?pageNum=1&pageSize=10&engineType=dify' \ -H 'Authorization: Bearer ' ``` **响应**: ```json { "code": 200, "msg": "操作成功", "rows": [ { "id": 1, "projectId": 1, "configName": "导诊助手-Dify配置", "engineType": "dify", "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-abc123xyz\"}", "status": "0", "createTime": "2026-03-26T12:00:00.000+00:00", "updateTime": "2026-03-26T12:00:00.000+00:00" } ], "total": 1 } ``` #### 4.2.2 GET /system/ai/engineConfig/listAll - 不分页查询 用于下拉选择框。参数同 list 但不需要 pageNum/pageSize。 **权限**:`ai:engineConfig:list` **cURL 示例**: ```bash curl -X GET 'http://localhost:8080/system/ai/engineConfig/listAll?engineType=dify' \ -H 'Authorization: Bearer ' ``` **响应**: ```json { "code": 200, "msg": "操作成功", "data": [ { "id": 1, "projectId": 1, "configName": "导诊助手-Dify配置", "engineType": "dify", "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-abc123xyz\"}", "status": "0", "createTime": "2026-03-26T12:00:00.000+00:00", "updateTime": "2026-03-26T12:00:00.000+00:00" } ] } ``` #### 4.2.3 GET /system/ai/engineConfig/{id} - 查询详情 **权限**:`ai:engineConfig:query` **cURL 示例**: ```bash curl -X GET 'http://localhost:8080/system/ai/engineConfig/1' \ -H 'Authorization: Bearer ' ``` **响应**: ```json { "code": 200, "msg": "操作成功", "data": { "id": 1, "projectId": 1, "configName": "导诊助手-Dify配置", "engineType": "dify", "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-abc123xyz\"}", "status": "0", "createTime": "2026-03-26T12:00:00.000+00:00", "updateTime": "2026-03-26T12:00:00.000+00:00" } } ``` #### 4.2.4 POST /system/ai/engineConfig - 新增配置 **权限**:`ai:engineConfig:add` **cURL 示例(Dify 类型)**: ```bash curl -X POST 'http://localhost:8080/system/ai/engineConfig' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "projectId": 1, "configName": "导诊助手-Dify配置", "engineType": "dify", "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-abc123xyz\"}", "status": "0" }' ``` **cURL 示例(Direct 直连大模型类型)**: ```bash curl -X POST 'http://localhost:8080/system/ai/engineConfig' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "projectId": 1, "configName": "GPT-4o直连配置", "engineType": "direct", "configJson": "{\"baseUrl\":\"https://api.openai.com/v1\",\"apiKey\":\"sk-xxx\",\"model\":\"gpt-4o\"}", "status": "0" }' ``` **cURL 示例(SpringAI 类型)**: ```bash curl -X POST 'http://localhost:8080/system/ai/engineConfig' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "projectId": 1, "configName": "SpringAI配置", "engineType": "spring_ai", "configJson": "{\"baseUrl\":\"https://api.openai.com/v1\",\"apiKey\":\"sk-xxx\",\"model\":\"gpt-4o\",\"temperature\":0.7,\"maxTokens\":2000}", "status": "0" }' ``` **cURL 示例(Mock 测试类型)**: ```bash curl -X POST 'http://localhost:8080/system/ai/engineConfig' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "projectId": 1, "configName": "本地Mock配置", "engineType": "mock", "configJson": "{\"mockResponse\":\"我是模拟回复,用于开发测试\"}", "status": "0" }' ``` **响应**: ```json { "code": 200, "msg": "操作成功" } ``` #### 4.2.5 PUT /system/ai/engineConfig - 修改配置 **权限**:`ai:engineConfig:edit` **cURL 示例**: ```bash curl -X PUT 'http://localhost:8080/system/ai/engineConfig' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "id": 1, "configName": "导诊助手-Dify配置(更新)", "engineType": "dify", "configJson": "{\"baseUrl\":\"http://8.136.61.90/v1\",\"secretKey\":\"app-new-key-456\"}" }' ``` **响应**: ```json { "code": 200, "msg": "操作成功" } ``` #### 4.2.6 DELETE /system/ai/engineConfig/{ids} - 批量删除 **权限**:`ai:engineConfig:remove` **cURL 示例(单个删除)**: ```bash curl -X DELETE 'http://localhost:8080/system/ai/engineConfig/1' \ -H 'Authorization: Bearer ' ``` **cURL 示例(批量删除)**: ```bash curl -X DELETE 'http://localhost:8080/system/ai/engineConfig/1,2,3' \ -H 'Authorization: Bearer ' ``` **响应**: ```json { "code": 200, "msg": "操作成功" } ``` --- ## 五、configJson 结构规范 `ai_agent_engine_config.config_json` 字段按引擎类型存储不同的 JSON 结构: ### Dify 类型(`engine_type = "dify"`) ```json { "baseUrl": "http://8.136.61.90/v1", "secretKey": "app-abc123xyz" } ``` | 字段 | 说明 | |------|------| | baseUrl | Dify 实例调用地址(同实例内所有 agent 相同) | | secretKey | 该 agent 的专属 API 密钥(每个 agent 不同,是路由的唯一标识) | ### Direct 类型(`engine_type = "direct"`) ```json { "baseUrl": "https://api.openai.com/v1", "apiKey": "sk-xxx", "model": "gpt-4o" } ``` | 字段 | 说明 | |------|------| | baseUrl | 大模型 API 地址 | | apiKey | API 密钥 | | model | 指定模型 | ### SpringAI 类型(`engine_type = "spring_ai"`) ```json { "baseUrl": "https://api.openai.com/v1", "apiKey": "sk-xxx", "model": "gpt-4o", "temperature": 0.7, "maxTokens": 2000 } ``` ### Mock 类型(`engine_type = "mock"`) ```json { "mockResponse": "我是模拟回复,用于开发测试" } ``` --- ## 六、OpenPlatform API 测试请求示例 以下示例使用 cURL 演示完整的调用流程。 ### 6.1 准备签名 签名计算公式:`MD5(requestId + "&" + timestamp + "&" + payload + "&" + privateKey)` ```bash # 变量准备 TIMESTAMP=$(date +%s000) REQUEST_ID=$(uuidgen) PUBLIC_KEY="your-public-key" PRIVATE_KEY="your-private-key" # 计算签名(以创建会话为例) PAYLOAD='{"agentId":"agent_123456","userId":10001,"conversationName":"测试会话"}' SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&${PAYLOAD}&${PRIVATE_KEY}" | md5) ``` ### 6.2 创建会话 ```bash curl -X POST 'http://localhost:8080/api/v1/conversation/create' \ -H 'Content-Type: application/json' \ -H "X-Emoon-Timestamp: ${TIMESTAMP}" \ -H "X-Emoon-Request-Id: ${REQUEST_ID}" \ -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \ -H "X-Emoon-Sign: ${SIGN}" \ -d '{ "agentId": "agent_123456", "userId": 10001, "conversationName": "测试会话" }' ``` ### 6.3 同步对话 ```bash PAYLOAD='{"agentId":"agent_123456","query":"帮我挂个内科的号","conversationId":"conv_xxx","stream":false,"userId":10001}' SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&${PAYLOAD}&${PRIVATE_KEY}" | md5) curl -X POST 'http://localhost:8080/api/v1/agent/chat' \ -H 'Content-Type: application/json' \ -H "X-Emoon-Timestamp: ${TIMESTAMP}" \ -H "X-Emoon-Request-Id: ${REQUEST_ID}" \ -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \ -H "X-Emoon-Sign: ${SIGN}" \ -d '{ "agentId": "agent_123456", "query": "帮我挂个内科的号", "conversationId": "conv_xxx", "stream": false, "userId": 10001 }' ``` ### 6.4 SSE 流式对话 ```bash PAYLOAD='{"agentId":"agent_123456","query":"帮我挂个内科的号","stream":true,"userId":10001}' SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&${PAYLOAD}&${PRIVATE_KEY}" | md5) curl -N -X POST 'http://localhost:8080/api/v1/agent/chat' \ -H 'Content-Type: application/json' \ -H 'Accept: text/event-stream' \ -H "X-Emoon-Timestamp: ${TIMESTAMP}" \ -H "X-Emoon-Request-Id: ${REQUEST_ID}" \ -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \ -H "X-Emoon-Sign: ${SIGN}" \ -d '{ "agentId": "agent_123456", "query": "帮我挂个内科的号", "stream": true, "userId": 10001 }' ``` > `-N` 参数禁用 cURL 缓冲,用于实时接收 SSE 事件流。 ### 6.5 查询会话列表 ```bash SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&&${PRIVATE_KEY}" | md5) curl -X GET 'http://localhost:8080/api/v1/conversation/list?agentId=agent_123456&userId=10001' \ -H "X-Emoon-Timestamp: ${TIMESTAMP}" \ -H "X-Emoon-Request-Id: ${REQUEST_ID}" \ -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \ -H "X-Emoon-Sign: ${SIGN}" ``` > 注意:GET 请求的 payload 为空字符串,签名中对应 `&&` 部分。 ### 6.6 查询会话详情 ```bash SIGN=$(echo -n "${REQUEST_ID}&${TIMESTAMP}&&${PRIVATE_KEY}" | md5) curl -X GET 'http://localhost:8080/api/v1/conversation/conv_a1b2c3d4-xxxx' \ -H "X-Emoon-Timestamp: ${TIMESTAMP}" \ -H "X-Emoon-Request-Id: ${REQUEST_ID}" \ -H "X-Emoon-Public-Key: ${PUBLIC_KEY}" \ -H "X-Emoon-Sign: ${SIGN}" ``` --- ## 七、错误码 ### OpenPlatform 错误码(SystemEnum) | 错误码 | 枚举值 | 说明 | |--------|--------|------| | 3 | INVALID_PUBLIC_KEY | 公钥不正确 | | 6 | ILLEGAL_REQUEST | 签名验证失败 | | 8 | PROJECT_NOT_EXISTS | 项目不存在 | | 11 | PROJECT_NO_AGENT_PERMISSION | 项目无该智能体权限 | | 12 | AGENT_NOT_FOUND | 智能体不存在 | | 13 | AGENT_DISABLED | 智能体已停用 | | 14 | ENGINE_CONFIG_NOT_FOUND | 引擎配置不存在 | | 15 | CONVERSATION_NOT_FOUND | 会话不存在 | --- ## 八、关键设计决策记录 | 决策 | 说明 | |------|------| | SysProjectDo 无 tenantId | 使用默认值 `"000000"` | | agentId 在 AiConversation 中是 Long | FK 到 ai_agent_app.id(主键),API 层用 String 业务 ID 后在 Service 中转换 | | engineType 不在 AiAgentApp 上 | 通过 engineConfigId 关联 AiAgentEngineConfig 获取,避免冗余 | | configJson 用 String 存储 | 不做 JSON 类型映射,保持灵活性,不同引擎结构不同 | | delFlag/status 是 String 类型 | 与框架保持一致,比较用 `"0".equals(...)` | | SSE 超时设为 0 | `new SseEmitter(0L)` 表示无超时,由引擎自行控制结束 | | 签名算法使用 MD5 | 沿用现有项目签名方案(SignUtil) | | ServiceImpl 放在 system-api | 遵循项目现有架构,不单独分离 | --- ## 九、待实现功能 | 功能 | 说明 | |------|------| | DifyAgentEngine | 对接 Dify REST API 的引擎实现 | | DirectLLMAgentEngine | 直连大模型引擎实现(基于 SpringAI ChatClient) | | 消息历史存储 | 存储对话消息记录并提供查询接口 | | Admin 端智能体 CRUD | AiAgentApp 的增删改查管理接口 | | Admin 端卡片管理 | 卡片定义、分类、插件的管理接口 | | 前端菜单/权限配置 | 在 sys_menu 中添加 AI 管理相关菜单和权限 |