ai-platform-implementation-guide.md 21 KB

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)
  • delFlagstatus 字段类型为 String("0"/"1"),不是 int
  • 日期字段使用 java.util.Date
  • agentIdAiConversation 中是 Long 类型(FK 到 ai_agent_app.id),不是 String 业务 ID

2.3 Mapper 接口(10 个)

Mapper 泛型 模块
AiAgentEngineConfigMapper BaseMapperPlus<AiAgentEngineConfig, AiAgentEngineConfigVo> system-api
AiAgentAppMapper BaseMapperPlus<AiAgentApp, AiAgentApp> system-api
AiUsageLogMapper BaseMapperPlus<AiUsageLog, AiUsageLog> system-api
AiCardCategoryMapper BaseMapperPlus<AiCardCategory, AiCardCategory> system-api
AiCardDefinitionMapper BaseMapperPlus<AiCardDefinition, AiCardDefinition> system-api
AiCardActionLogMapper BaseMapperPlus<AiCardActionLog, AiCardActionLog> system-api
AiCardPluginMapper BaseMapperPlus<AiCardPlugin, AiCardPlugin> system-api
AiCardGrayConfigMapper BaseMapperPlus<AiCardGrayConfig, AiCardGrayConfig> system-api
AiConversationMapper BaseMapperPlus<AiConversation, AiConversation> mcp-api
AiCardInstanceMapper BaseMapperPlus<AiCardInstance, AiCardInstance> mcp-api

2.4 Service 接口(9 个)

位于 emoon-infra/emoon-modules-api/emoon-system-api/src/main/java/com/emoon/system/service/

每个 Service 接口提供标准的 queryByIdqueryListinsertupdatedeleteWithValidByIds 方法。


三、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 字段控制。

请求体

{
  "agentId": "agent_123456",
  "query": "帮我挂个内科的号",
  "conversationId": null,
  "stream": false,
  "userId": 10001,
  "inputs": {},
  "files": []
}

同步响应stream=false):

{
  "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 - 创建会话

请求体

{
  "agentId": "agent_123456",
  "userId": 10001,
  "conversationName": "挂号咨询"
}

响应

{
  "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 | 否 | 按用户筛选 |

响应

{
  "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 示例

curl -X GET 'http://localhost:8080/system/ai/engineConfig/list?pageNum=1&pageSize=10&engineType=dify' \
  -H 'Authorization: Bearer <token>'

响应

{
  "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 示例

curl -X GET 'http://localhost:8080/system/ai/engineConfig/listAll?engineType=dify' \
  -H 'Authorization: Bearer <token>'

响应

{
  "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 示例

curl -X GET 'http://localhost:8080/system/ai/engineConfig/1' \
  -H 'Authorization: Bearer <token>'

响应

{
  "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 类型)

curl -X POST 'http://localhost:8080/system/ai/engineConfig' \
  -H 'Authorization: Bearer <token>' \
  -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 直连大模型类型)

curl -X POST 'http://localhost:8080/system/ai/engineConfig' \
  -H 'Authorization: Bearer <token>' \
  -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 类型)

curl -X POST 'http://localhost:8080/system/ai/engineConfig' \
  -H 'Authorization: Bearer <token>' \
  -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 测试类型)

curl -X POST 'http://localhost:8080/system/ai/engineConfig' \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "projectId": 1,
    "configName": "本地Mock配置",
    "engineType": "mock",
    "configJson": "{\"mockResponse\":\"我是模拟回复,用于开发测试\"}",
    "status": "0"
  }'

响应

{
  "code": 200,
  "msg": "操作成功"
}

4.2.5 PUT /system/ai/engineConfig - 修改配置

权限ai:engineConfig:edit

cURL 示例

curl -X PUT 'http://localhost:8080/system/ai/engineConfig' \
  -H 'Authorization: Bearer <token>' \
  -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\"}"
  }'

响应

{
  "code": 200,
  "msg": "操作成功"
}

4.2.6 DELETE /system/ai/engineConfig/{ids} - 批量删除

权限ai:engineConfig:remove

cURL 示例(单个删除)

curl -X DELETE 'http://localhost:8080/system/ai/engineConfig/1' \
  -H 'Authorization: Bearer <token>'

cURL 示例(批量删除)

curl -X DELETE 'http://localhost:8080/system/ai/engineConfig/1,2,3' \
  -H 'Authorization: Bearer <token>'

响应

{
  "code": 200,
  "msg": "操作成功"
}

五、configJson 结构规范

ai_agent_engine_config.config_json 字段按引擎类型存储不同的 JSON 结构:

Dify 类型(engine_type = "dify"

{
  "baseUrl": "http://8.136.61.90/v1",
  "secretKey": "app-abc123xyz"
}
字段 说明
baseUrl Dify 实例调用地址(同实例内所有 agent 相同)
secretKey 该 agent 的专属 API 密钥(每个 agent 不同,是路由的唯一标识)

Direct 类型(engine_type = "direct"

{
  "baseUrl": "https://api.openai.com/v1",
  "apiKey": "sk-xxx",
  "model": "gpt-4o"
}
字段 说明
baseUrl 大模型 API 地址
apiKey API 密钥
model 指定模型

SpringAI 类型(engine_type = "spring_ai"

{
  "baseUrl": "https://api.openai.com/v1",
  "apiKey": "sk-xxx",
  "model": "gpt-4o",
  "temperature": 0.7,
  "maxTokens": 2000
}

Mock 类型(engine_type = "mock"

{
  "mockResponse": "我是模拟回复,用于开发测试"
}

六、OpenPlatform API 测试请求示例

以下示例使用 cURL 演示完整的调用流程。

6.1 准备签名

签名计算公式:MD5(requestId + "&" + timestamp + "&" + payload + "&" + privateKey)

# 变量准备
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 创建会话

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 同步对话

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 流式对话

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 查询会话列表

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 查询会话详情

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 管理相关菜单和权限