docs(ai): 修复 Spec Review 发现的 9 个问题
CRITICAL 修复: - 新增跨 crate 数据访问架构(erp-core HealthDataQuery trait) - 新增 4 个权限码声明(session.list/manage/history + chat.send) - 明确 Provider Function Calling 需中等程度重构,补充适配方案 IMPORTANT 修复: - 说明与 copilot_chat_logs 表的关系(并存不迁移) - 新增 ai_user_profiles 表(长期记忆/用户画像) - 定义 DisplayHint 枚举(5 种富消息类型) - Phase 0 验证标准修正为新端点 - 补充 Taro SSE 兼容层 + Web 从零构建,工期上调 - 新增 PII 脱敏规范(6 类字段处理规则) - 新增故障处理与降级章节 - Phase 0/2 工作量估算上调,总工期 16-23→20-27 天
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# AI Agent 突破口设计规格
|
||||
|
||||
> **日期:** 2026-05-18 | **状态:** Draft | **范围:** erp-ai Agent 改造
|
||||
> **总工期:** 16-23 天 | **方案:** ReAct Agent + Function Calling
|
||||
> **总工期:** 20-27 天 | **方案:** ReAct Agent + Function Calling
|
||||
|
||||
## 1. 背景与动机
|
||||
|
||||
@@ -71,7 +71,8 @@ HMS 健康管理平台综合评分 6.8/10,功能完整度 87%,但 AI 能力"
|
||||
|------|------|------|
|
||||
| Agent 状态管理 | Orchestrator 无状态,会话由 Handler 管理 | 简化 Orchestrator 职责,便于测试 |
|
||||
| Tool 执行模型 | 同步阻塞,单轮内多个 Tool Call 并行 | LLM 返回多个 call 时并行执行,减少延迟 |
|
||||
| Provider 复用 | 不改 `AiProvider` trait,在调用层传入 function definitions | 最小改动,兼容现有 3 个 Provider |
|
||||
| Provider 扩展 | 扩展 `GenerateRequest` 添加 tools/functions 字段,3 个 Provider 各自适配 | Function Calling 是核心能力,需中等程度重构每个 Provider 的请求/响应结构 |
|
||||
| 跨 crate 数据访问 | 在 erp-core 定义 `HealthDataQuery` trait,erp-health 实现,erp-ai 通过 trait 调用 | 保持模块边界,erp-ai 不直接依赖 erp-health |
|
||||
| 安全循环上限 | 单次对话最多 5 轮 Tool Call | 防止无限循环,控制成本 |
|
||||
| 分析调用模式 | Agent 内走非流式同步调用 | Agent 需要拿到完整结果再决策 |
|
||||
|
||||
@@ -103,7 +104,56 @@ pub struct ToolResult {
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Tool 清单
|
||||
### 3.2 跨 Crate 数据访问架构
|
||||
|
||||
erp-ai 不直接依赖 erp-health(保持模块边界)。数据查询类 Tool 通过以下机制访问健康数据:
|
||||
|
||||
**方案:在 erp-core 定义 `HealthDataQuery` trait,erp-health 实现**
|
||||
|
||||
```rust
|
||||
// erp-core 中定义
|
||||
#[async_trait]
|
||||
pub trait HealthDataQuery: Send + Sync {
|
||||
async fn query_vitals(&self, tenant_id: Uuid, patient_id: Uuid, days: i32) -> Result<Vec<VitalSummary>>;
|
||||
async fn query_lab_reports(&self, tenant_id: Uuid, patient_id: Uuid, limit: i32) -> Result<Vec<LabReportSummary>>;
|
||||
async fn query_patient_profile(&self, tenant_id: Uuid, patient_id: Uuid) -> Result<PatientProfile>;
|
||||
async fn query_appointments(&self, tenant_id: Uuid, patient_id: Uuid) -> Result<Vec<AppointmentSummary>>;
|
||||
async fn query_medication(&self, tenant_id: Uuid, patient_id: Uuid) -> Result<Vec<MedicationSummary>>;
|
||||
}
|
||||
|
||||
// 轻量 DTO 定义在 erp-core(只含 Tool 需要的字段,非完整 Entity)
|
||||
pub struct VitalSummary { pub indicator_type: String, pub value: f64, pub unit: String, pub recorded_at: DateTime }
|
||||
pub struct LabReportSummary { pub id: Uuid, pub report_date: DateTime, pub items: Vec<LabItemSummary> }
|
||||
pub struct LabItemSummary { pub indicator_name: String, pub value: f64, pub unit: String, pub is_abnormal: bool }
|
||||
pub struct PatientProfile { pub name: String, pub age: i32, pub gender: String, pub conditions: Vec<String> }
|
||||
pub struct AppointmentSummary { pub id: Uuid, pub department: String, pub doctor_name: String, pub scheduled_at: DateTime, pub status: String }
|
||||
pub struct MedicationSummary { pub name: String, pub dosage: String, pub frequency: String }
|
||||
```
|
||||
|
||||
**注册机制**:`AppState` 中持有 `Arc<dyn HealthDataQuery>`,erp-health 模块注册时注入实现。Agent Tool 通过 `ToolContext` 访问。
|
||||
|
||||
**对 erp-ai 模块中已有的分析能力**(analysis_service、copilot_engine 等),无需跨 crate,直接在 erp-ai 内部调用。
|
||||
|
||||
### 3.3 DisplayHint 定义
|
||||
|
||||
`DisplayHint` 用于给前端提供渲染提示,让 Tool 返回的数据不仅作为 LLM 上下文,还能以富消息形式展示给用户:
|
||||
|
||||
```rust
|
||||
pub enum DisplayHint {
|
||||
/// 体征数据卡片 — 前端渲染为小图表
|
||||
VitalCard { indicator_type: String, values: Vec<(DateTime, f64)>, unit: String },
|
||||
/// 化验报告摘要卡片
|
||||
LabReportCard { report_date: DateTime, abnormal_count: usize },
|
||||
/// 操作确认 — 前端渲染为确认按钮
|
||||
ActionConfirm { action_type: String, summary: String, confirm_payload: serde_json::Value },
|
||||
/// 风险等级提示
|
||||
RiskAlert { level: String, message: String },
|
||||
/// 纯文本(默认)
|
||||
Text,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Tool 清单
|
||||
|
||||
#### 第一类:数据查询(只读,从 erp-health 取数据)
|
||||
|
||||
@@ -131,6 +181,19 @@ pub struct ToolResult {
|
||||
| `recommend_services` | 根据症状/需求推荐科室或服务 | 新增,基于规则 + 知识库 |
|
||||
| `check_alert_rules` | 检查是否触发告警阈值 | `local_rules_engine` + `ai_risk_threshold` |
|
||||
|
||||
### 3.6 权限码声明
|
||||
|
||||
现有权限码 `ai.chat.send` 保留用于发送消息。新增以下权限码:
|
||||
|
||||
| 权限码 | 说明 | 适用端点 |
|
||||
|--------|------|----------|
|
||||
| `ai.chat.session.list` | 查看会话列表 | `GET /ai/chat/sessions` |
|
||||
| `ai.chat.session.manage` | 创建/关闭会话 | `POST/DELETE /ai/chat/sessions` |
|
||||
| `ai.chat.session.history` | 查看会话消息历史 | `GET /ai/chat/sessions/{id}/messages` |
|
||||
| `ai.chat.send` | 发送消息(触发 Agent) | `POST /ai/chat/sessions/{id}/messages`(已存在) |
|
||||
|
||||
行动类 Tool(`create_appointment`、`transfer_to_human`)不单独声明权限,由 Agent 内部根据用户角色判断。
|
||||
|
||||
#### 第四类:行动(写入操作,需更高权限)
|
||||
|
||||
| Tool 名称 | 功能 | 对接现有能力 |
|
||||
@@ -259,6 +322,8 @@ cache_service → 分析类 Tool 内部复用
|
||||
|
||||
#### ai_chat_sessions — AI 会话表
|
||||
|
||||
> 与现有 `copilot_chat_logs` 表的关系:`copilot_chat_logs` 记录 Copilot 内部对话(AI 分析引擎之间的通信),服务于风险洞察和自动分析。`ai_chat_sessions` / `ai_chat_messages` 记录用户与 AI 客服的面向用户对话。两者并存,数据不迁移。
|
||||
|
||||
```sql
|
||||
CREATE TABLE ai_chat_sessions (
|
||||
id UUID PRIMARY KEY,
|
||||
@@ -315,7 +380,91 @@ CREATE TABLE ai_tool_call_logs (
|
||||
);
|
||||
```
|
||||
|
||||
### 5.3 API 设计
|
||||
#### ai_user_profiles — 用户长期画像(长期记忆)
|
||||
|
||||
```sql
|
||||
CREATE TABLE ai_user_profiles (
|
||||
id UUID PRIMARY KEY,
|
||||
tenant_id UUID NOT NULL,
|
||||
user_id UUID NOT NULL,
|
||||
preferences JSONB, -- 用户偏好(如:偏好简洁回复、关心血压问题)
|
||||
health_interests TEXT[], -- 健康关注点(如:高血压、糖尿病)
|
||||
frequent_topics TEXT[], -- 常见咨询主题
|
||||
personality_summary TEXT, -- AI 生成的用户画像摘要
|
||||
last_updated_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
UNIQUE(tenant_id, user_id)
|
||||
);
|
||||
```
|
||||
|
||||
每次会话结束时,Agent 自动更新用户画像摘要。新会话开始时加载注入 System Prompt。
|
||||
|
||||
### 5.3 PII 脱敏规范
|
||||
|
||||
Tool 返回数据在传给 LLM 前必须脱敏。具体规则:
|
||||
|
||||
| 字段类型 | 脱敏方式 | 示例 |
|
||||
|----------|----------|------|
|
||||
| 患者姓名 | 保留姓氏 + 称呼 | "张爷爷"(Agent 用称呼,不用真名) |
|
||||
| 身份证号 | 不传给 LLM | Tool 层过滤,不出现在 ToolResult |
|
||||
| 手机号 | 不传给 LLM | 同上 |
|
||||
| 具体住址 | 不传给 LLM | 同上 |
|
||||
| 出生日期 | 转为年龄 | "68 岁" |
|
||||
| 医疗数据 | 正常传递 | 血压值、化验指标等不脱敏 |
|
||||
|
||||
脱敏在 `AgentTool::execute()` 实现中统一处理,每个 Tool 的返回值必须经过 `sanitize_for_llm()` 函数。
|
||||
|
||||
### 5.3 Provider Function Calling 适配
|
||||
|
||||
现有 `AiProvider` trait 的 `GenerateRequest` 不支持 tools/functions 参数,需要扩展:
|
||||
|
||||
```rust
|
||||
// 扩展 GenerateRequest
|
||||
pub struct GenerateRequest {
|
||||
pub system_prompt: String,
|
||||
pub messages: Vec<ChatMessage>, // 从单条 user_prompt 改为多轮消息
|
||||
pub model: Option<String>,
|
||||
pub temperature: Option<f32>,
|
||||
pub max_tokens: Option<u32>,
|
||||
pub tools: Option<Vec<ToolDefinition>>, // 新增
|
||||
}
|
||||
|
||||
pub struct ChatMessage {
|
||||
pub role: MessageRole, // User / Assistant / Tool
|
||||
pub content: String,
|
||||
pub tool_calls: Option<Vec<ToolCall>>, // assistant 消息中的 tool call
|
||||
pub tool_call_id: Option<String>, // tool 消息的关联 ID
|
||||
}
|
||||
|
||||
pub struct ToolDefinition {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub parameters: serde_json::Value, // JSON Schema
|
||||
}
|
||||
|
||||
pub struct ToolCall {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub arguments: serde_json::Value,
|
||||
}
|
||||
|
||||
// GenerateResponse 扩展
|
||||
pub struct GenerateResponse {
|
||||
pub content: Option<String>,
|
||||
pub tool_calls: Option<Vec<ToolCall>>, // LLM 返回的 tool call
|
||||
pub usage: Option<TokenUsage>,
|
||||
}
|
||||
```
|
||||
|
||||
**各 Provider 适配工作量**:
|
||||
- **Claude**:Anthropic API 使用 `tool_use`/`tool_result` 内容块,需重构消息构建和响应解析(1 天)
|
||||
- **OpenAI**:使用 `function` 或 `tool` 类型消息,相对标准(0.5 天)
|
||||
- **Ollama**:Function Calling 支持取决于模型,若不支持则降级为纯文本 Prompt 模式(0.5 天)
|
||||
|
||||
### 5.4 API 设计
|
||||
|
||||
```
|
||||
POST /api/v1/ai/chat/sessions — 创建会话
|
||||
@@ -353,20 +502,24 @@ GET /api/v1/ai/chat/sessions/{id}/messages — 消息历史
|
||||
|
||||
## 7. 分阶段实施计划
|
||||
|
||||
### Phase 0:基础设施(3-4 天)
|
||||
### Phase 0:基础设施(5-6 天)
|
||||
|
||||
> 目标:Agent 核心循环跑通,能用一个 Tool 完成完整对话
|
||||
|
||||
| 任务 | 工作量 |
|
||||
|------|--------|
|
||||
| `AgentTool` trait + `ToolRegistry` + `ToolContext` | 0.5 天 |
|
||||
| `AgentOrchestrator` ReAct 循环(含 Function Calling 消息格式) | 1 天 |
|
||||
| 数据库迁移:`ai_chat_sessions` + `ai_chat_messages` + `ai_tool_call_logs` | 0.5 天 |
|
||||
| `GenerateRequest` + `GenerateResponse` 扩展(支持 tools/functions) | 1 天 |
|
||||
| Claude Provider Function Calling 适配(消息构建 + 响应解析) | 1 天 |
|
||||
| OpenAI + Ollama Provider 适配 | 1 天 |
|
||||
| `AgentTool` trait + `ToolRegistry` + `ToolContext` + `DisplayHint` | 0.5 天 |
|
||||
| `AgentOrchestrator` ReAct 循环 | 0.5 天 |
|
||||
| erp-core `HealthDataQuery` trait 定义 + erp-health 实现 | 1 天 |
|
||||
| 数据库迁移:`ai_chat_sessions` + `ai_chat_messages` + `ai_tool_call_logs` + `ai_user_profiles` | 0.5 天 |
|
||||
| 实现 1 个 Tool:`query_patient_vitals`(验证端到端链路) | 0.5 天 |
|
||||
| 改造 `chat_handler`:接入 Orchestrator,替换原有简单逻辑 | 0.5 天 |
|
||||
| 单元测试 + 集成测试 | 0.5 天 |
|
||||
|
||||
**交付标准**:Postman 调用 `/ai/chat`,Agent 能查到患者体征数据并自然回复。
|
||||
**交付标准**:Postman 调用 `/api/v1/ai/chat/sessions/{id}/messages`,Agent 能查到患者体征数据并自然回复。
|
||||
|
||||
### Phase 1:Tool 扩展 + 策略 Prompt(5-7 天)
|
||||
|
||||
@@ -383,7 +536,7 @@ GET /api/v1/ai/chat/sessions/{id}/messages — 消息历史
|
||||
|
||||
**交付标准**:模拟 5 种典型场景(安抚/科普/推荐/预警/引导到院),Agent 均能自主选择正确策略和 Tool。
|
||||
|
||||
### Phase 2:前端升级 + 流式输出(5-7 天)
|
||||
### Phase 2:前端升级 + 流式输出(7-9 天)
|
||||
|
||||
> 目标:小程序 + Web 都有完整 AI 客服体验
|
||||
|
||||
@@ -391,10 +544,12 @@ GET /api/v1/ai/chat/sessions/{id}/messages — 消息历史
|
||||
|------|--------|
|
||||
| 后端:会话 CRUD API(创建/列表/历史消息) | 1 天 |
|
||||
| 后端:Agent 最终回复走 SSE 流式输出 | 1 天 |
|
||||
| 小程序:SSE 兼容层(Taro 原生不支持 SSE,需用 `requestTask` 或轮询适配) | 1 天 |
|
||||
| 小程序:会话列表页 + 消息历史页 + 富消息渲染 | 2 天 |
|
||||
| Web:同上,复用 API 模块 | 1.5 天 |
|
||||
| Web:AI 客服页面从零构建(会话列表 + 聊天界面 + 富消息) | 2 天 |
|
||||
| 数据卡片渲染(体征趋势小图表) | 1 天 |
|
||||
| 前端迁移:本地 Storage → DB 持久化 | 0.5 天 |
|
||||
| 前端迁移:本地 Storage → DB 持久化 + 旧数据迁移脚本 | 0.5 天 |
|
||||
| 端到端测试 | 0.5 天 |
|
||||
|
||||
**交付标准**:小程序打开 AI 客服,能自然对话,能看到数据卡片,能看到流式输出。
|
||||
|
||||
@@ -415,18 +570,36 @@ GET /api/v1/ai/chat/sessions/{id}/messages — 消息历史
|
||||
### 总工期
|
||||
|
||||
```
|
||||
Phase 0 ████████ (3-4天)
|
||||
Phase 0 ████████████████ (5-6天)
|
||||
Phase 1 ████████████████ (5-7天)
|
||||
Phase 2 ████████████████ (5-7天)
|
||||
Phase 2 ████████████████████ (7-9天)
|
||||
Phase 3 ██████████ (3-5天)
|
||||
────────────────────────
|
||||
合计 16-23 天
|
||||
合计 20-27 天
|
||||
```
|
||||
|
||||
每个 Phase 结束后都有可演示的交付物。
|
||||
|
||||
---
|
||||
|
||||
## 9. 故障处理与降级
|
||||
|
||||
| 故障场景 | 用户看到什么 | 处理方式 |
|
||||
|----------|-------------|----------|
|
||||
| 所有 Provider 不可用 | "小华暂时无法回复,请稍后再试" | 返回固定降级消息,记录到 usage_service |
|
||||
| Agent 循环超时(60s) | 已生成的部分回复 + "回复被中断,请重新提问" | SSE 断流 + 超时日志 |
|
||||
| 单个 Tool 执行超时(10s) | Agent 跳过该 Tool 继续推理 | ToolResult 返回错误摘要,Agent 可选择其他路径 |
|
||||
| Ollama 不支持 Function Calling | 自动降级为纯文本 Prompt 模式 | Provider 层检测能力,无 Function Calling 时将 Tool 描述注入 System Prompt |
|
||||
| LLM 返回无效 Tool Call | "抱歉,我刚才思考有误,请再说一次" | Orchestrator 捕获解析错误,返回重试提示 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 风险与缓解
|
||||
|
||||
每个 Phase 结束后都有可演示的交付物。
|
||||
|
||||
---
|
||||
|
||||
## 8. 风险与缓解
|
||||
|
||||
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||
|
||||
Reference in New Issue
Block a user