--- doc_id: DDS-202606-004 feature_id: FEAT-202606-006-agent-semantic-interaction type: detailed-design title: Agent 语义交互与命令治理详细设计 status: reviewing owner: 医梦研发团队 created_at: 2026-06-29 updated_at: 2026-07-01 reviewers: - 架构负责人 - 后端负责人 - 终端负责人 related_docs: - PRD-202606-002 - ODS-202606-002 - ADR-202606-001 - API-202606-004 related_modules: - emoon-openplatform - emoon-ai-agent - emoon-ai-card - emoon-ai-mcp - emoon-system tags: - 详细设计 - TaskState - SemanticResponse - Command - Presentation --- # Agent 语义交互与命令治理详细设计 ## 实现范围 本设计描述后续实现所需的目标模块、领域对象、数据模型、状态机、接口、异常、安全和测试边界。 本轮只产出设计,不实施代码或数据库变更。 首个纵向样板仅覆盖挂号 Agent: ```text 约束提取 → 号源查询 → 替代推荐 → 无状态展示 → 候选选择 → 实时复核 → 临时锁号 → 最终挂号确认 ``` ## 设计原则 1. 业务事实与表现形式分离。 2. 只读规划与写命令分离。 3. 模型建议与服务端状态分离。 4. 展示快照与执行凭证分离。 5. 明确交互与二次确认按风险分级。 6. 不通过 Prompt 替代权限、状态、幂等和审计。 ## 领域模型 ### TaskState ```json { "taskId": "task_001", "taskType": "REGISTRATION", "status": "ACTIVE", "currentStage": "CANDIDATE_SELECTION", "constraints": { "departmentId": "respiratory", "departmentName": "呼吸科", "doctorTitle": "主任医师", "visitDate": "2026-06-30", "timeRange": null, "campusId": null }, "missingFields": ["candidateId"], "activeResultRef": "result_001", "activeCommandId": null, "version": 5 } ``` 约束: - 只有服务端业务服务能修改确定性字段。 - FastGPT 只通过白名单只读工具参数表达查询条件,服务端校验后才更新查询上下文。 - 命令成功后由 Command Runtime 写入已锁号、已挂号等事实。 - 同一会话最多一个 `ACTIVE` 前台任务;其他任务为 `SUSPENDED`。 ### SemanticResponse ```json { "turnId": "turn_001", "answer": "明天呼吸科有 3 位主任医师可预约。", "task": { "taskId": "task_001", "taskType": "REGISTRATION", "status": "ACTIVE", "currentStage": "CANDIDATE_SELECTION", "version": 5 }, "resultRefs": [ { "resultRef": "result_001", "version": 1, "resultType": "APPOINTMENT_SLOT_CANDIDATES", "expiresAt": "2026-06-30T09:35:00+08:00" } ], "availableActions": [ { "actionType": "SELECT_CANDIDATE", "riskLevel": "L1", "confirmationRequired": false } ] } ``` `SemanticResponse` 是薄传输 DTO,不是持久化领域对象,也不是要求 FastGPT 一次性输出的模型 JSON。 仅 `turnId` 和 `answer` 必填;`task`、`resultRefs`、`availableActions` 按本轮结果可选。 风险和确认要求只存在于具体 Action/Command,Presentation 使用独立 SSE 事件。 ### ResultSnapshot ResultSnapshot 是只读 MCP 结果的最小化、短期、版本化引用。 必须包含: - `result_ref`、`result_version`、`result_type`; - `tenant_id`、`project_id`、`conversation_id`、`task_id`; - 可选 `patient_binding_id`; - 最小化 `result_json`; - `source_tool_call_id`、`trace_id`; - `expires_at`、`created_at`。 禁止包含: - 完整 HIS 原始响应; - 非当前任务所需的患者资料; - 可直接执行写操作的密钥或令牌; - 无明确用途的长期医疗数据副本。 ### Presentation Presentation 是 ResultSnapshot 的无状态投影: ```json { "presentationId": "prs_001", "viewModelType": "AppointmentSlotCandidates", "schemaVersion": "1.0", "resultRef": "result_001", "resultVersion": 1, "payload": { "groups": [] }, "interactions": [ { "type": "SELECT_CANDIDATE", "candidateId": "slot_001" } ] } ``` 通用布局原语包括列表、分组、标签、摘要、按钮和确认区。 领域 ViewModel 包括 `DoctorCandidate`、`AppointmentSlot`、`RegistrationConfirmation`。 模板和策略不得包含: - 工具名称或调用参数; - `nextCard`; - 命令执行顺序; - TaskState 状态迁移; - 风险等级降级规则。 ### Command ```json { "commandId": "cmd_001", "commandType": "REGISTER_APPOINTMENT", "riskLevel": "L2", "status": "AWAITING_CONFIRMATION", "taskId": "task_001", "taskVersion": 7, "summary": { "department": "呼吸科", "doctor": "张明 主任医师", "visitTime": "2026-06-30 09:00", "feeYuan": "25.00" }, "expiresAt": "2026-06-30T09:40:00+08:00" } ``` 命令参数由后端根据 TaskState、ResultSnapshot 和实时 MCP 结果准备。 终端不能提交或覆盖 `commandType`、HIS 参数、风险等级和金额。 ## 数据模型 ### ai_result_snapshot | 字段 | 类型建议 | 约束 | |---|---|---| | `id` | bigint | 主键 | | `result_ref` | varchar(96) | 全局唯一 | | `result_version` | int | 同一引用递增 | | `result_type` | varchar(64) | 领域结果类型 | | `tenant_id` | varchar(64) | 必填、索引 | | `project_id` | varchar(64) | 必填 | | `conversation_id` | varchar(96) | 必填、索引 | | `task_id` | varchar(96) | 必填、索引 | | `patient_binding_id` | varchar(96) | 可空,不保存证件号 | | `result_json` | json/text | 最小化结果 | | `source_tool_call_id` | varchar(96) | 必填 | | `trace_id` | varchar(96) | 必填、索引 | | `expires_at` | datetime | 必填、索引 | | `created_at` | datetime | 必填 | 唯一键:`(result_ref, result_version)`。 ### ai_command_instance | 字段 | 类型建议 | 约束 | |---|---|---| | `id` | bigint | 主键 | | `command_id` | varchar(96) | 全局唯一 | | `command_type` | varchar(64) | 后端注册类型 | | `risk_level` | varchar(8) | L1/L2/L3 | | `status` | varchar(32) | 状态机字段 | | `tenant_id/project_id` | varchar(64) | 权限边界 | | `conversation_id/task_id/turn_id` | varchar(96) | 链路关联 | | `task_version` | int | 防止旧任务确认 | | `patient_binding_id` | varchar(96) | 确认主体 | | `command_payload` | json/text | 后端生成,敏感字段最小化/加密 | | `summary_json` | json/text | 面向确认的脱敏摘要 | | `idempotency_key` | varchar(128) | 唯一或业务唯一 | | `source_result_ref/version` | varchar/int | 可空 | | `expires_at` | datetime | 必填 | | `confirmed_at/executed_at` | datetime | 可空 | | `trace_id` | varchar(96) | 必填、索引 | | `created_at/updated_at` | datetime | 必填 | ### ai_command_audit_log | 字段 | 类型建议 | 说明 | |---|---|---| | `audit_id` | varchar(96) | 唯一 | | `command_id` | varchar(96) | 命令关联 | | `event_type` | varchar(32) | PREPARED/CONFIRMED/REJECTED/EXECUTING/SUCCEEDED/FAILED/UNKNOWN | | `actor_type/actor_id` | varchar(32/96) | 患者、医护、系统 | | `request_digest` | varchar(128) | 请求摘要,不保存密钥 | | `result_digest` | varchar(128) | 结果摘要 | | `tool_call_id` | varchar(96) | 可空 | | `trace_id` | varchar(96) | 必填 | | `occurred_at` | datetime | 必填 | ### Presentation MVP 不新增数据表 挂号纵向样板不创建 `ai_presentation_template` 或 `ai_presentation_policy`: - `AppointmentSlotCandidatesV1`、`RegistrationConfirmationV1` Schema 随代码版本管理; - 转换器只接受明确的领域 QueryResult,不接受任意 JSON; - Scene Profile 只配置终端是否支持卡片、最大展示条数和布局偏好; - ViewModel 主版本不兼容时新增类型或版本,不原地改变旧字段语义; - 不支持模板内动作脚本、工具名、表达式和 TaskState 迁移。 只有出现三个以上可复用领域 ViewModel、且医院确有无代码换肤需求时,才另立需求评估模板表。 ## 状态机 ### TaskState ```mermaid stateDiagram-v2 [*] --> ACTIVE ACTIVE --> SUSPENDED: 切换业务任务 SUSPENDED --> ACTIVE: 恢复且上下文仍有效 ACTIVE --> COMPLETED: 业务目标完成 ACTIVE --> CANCELLED: 用户取消 ACTIVE --> EXPIRED: 任务超时 SUSPENDED --> EXPIRED: 上下文或资源过期 ``` ### Command ```mermaid stateDiagram-v2 [*] --> PREPARED PREPARED --> AWAITING_CONFIRMATION: L2/L3 PREPARED --> EXECUTING: L1 明确交互授权 AWAITING_CONFIRMATION --> EXECUTING: 确认通过 AWAITING_CONFIRMATION --> REJECTED: 用户拒绝 PREPARED --> EXPIRED: 到期 AWAITING_CONFIRMATION --> EXPIRED: 到期 EXECUTING --> SUCCEEDED EXECUTING --> FAILED EXECUTING --> UNKNOWN: 下游状态不明 UNKNOWN --> SUCCEEDED: 幂等状态查询确认 UNKNOWN --> FAILED: 幂等状态查询确认 ``` 禁止从 `UNKNOWN` 直接重新执行。 ## 服务和对象职责 | 组件 | 职责 | 备注 | |---|---|---| | `FastGptAgentEngine` | 文本流、只读工具调用和引擎元数据适配 | 首期唯一正式引擎 | | `AgentApplicationService` | 校验查询参数、更新查询上下文、保存结果引用、组装响应、解析 interaction | 保持薄应用层,不做通用 Workflow | | `ResultSnapshotService` | 最小化、版本化、过期与访问控制 | 可作为 AgentApplicationService 内部协作者 | | `PresentationPolicy` | 根据 ResultType 和终端能力选择代码内 ViewModel | 先用简单策略,不建设独立规则引擎 | | `CommandRuntime` | 固定命令策略、准备、确认、拒绝、执行、幂等和审计 | 命令类型与风险使用代码/配置白名单 | | `McpToolService` | 工具注册、权限过滤、ToolCatalog、调用和审计 | FastGPT 只获得只读集合 | ## FastGPT 交互 ### ToolCatalog 生成与下发 医梦后端是工具目录权威来源: ```json { "name": "query_appointment_slots", "description": "按科室、日期、职称等条件查询可预约号源", "riskLevel": "READ_ONLY", "timeoutMs": 5000, "dataClassification": "INTERNAL", "inputSchema": { "type": "object", "required": ["departmentId", "visitDate"], "properties": { "departmentId": {"type": "string"}, "visitDate": {"type": "string", "format": "date"}, "doctorTitle": {"type": "string"}, "timeRange": {"type": "string"} }, "additionalProperties": false } } ``` 生成步骤: 1. 从 MCP Tool Registry 读取工具定义; 2. 只保留 `READ_ONLY`; 3. 按 tenant/project/agent/scene/终端权限过滤; 4. 校验名称、说明、inputSchema、超时和数据分级; 5. 由 FastGPT Adapter 注册或注入目标 App/Workflow; 6. 发布时保存 ToolCatalog 版本和摘要,运行时记录实际版本。 FastGPT 具体采用 MCP ToolSet、HTTP ToolSet 或 Workflow 工具节点,由实施前能力验证确定。 该差异只存在于 Adapter,不改变医梦工具权限模型。 ### 查询参数与 TaskState 取消单独的 `propose_task_constraints` 协议。FastGPT 调用只读工具时提交的参数即候选查询条件: 1. AgentApplicationService 对参数做 JSON Schema、词典、日期和授权校验; 2. 校验成功后写入 TaskState 的查询上下文并执行工具; 3. 校验失败不向用户暴露内部“proposal 被拒绝”,最多修复一次; 4. 无法修复时返回针对缺失或冲突字段的自然语言追问; 5. 查询参数不能写入“已锁号、已挂号、已支付”等确定性状态。 FastGPT 接收: - 最小任务摘要; - 当前允许的只读工具; - 必要的会话文本; - `traceId/turnId`; - 不包含完整 ResultSnapshot 或命令内部参数。 FastGPT 可产生: - 自然语言增量; - 只读工具调用。 用户的锁号或挂号意图由 AgentApplicationService 结合当前 TaskState 和明确交互识别; FastGPT 不输出可执行命令参数。 后端必须拒绝: - FastGPT 发起写工具; - FastGPT 直接设置 TaskState 确定性状态; - FastGPT 自定义风险等级或跳过确认; - FastGPT 返回的最终 cardData、commandToken 或价格作为可信事实。 ## 挂号纵向样板 ### 精确查询 1. 提取日期、科室、职称。 2. 服务端校验并更新 TaskState 约束。 3. FastGPT 调用 ToolCatalog 中的 `query_appointment_slots` 只读工具。 4. 保存 `APPOINTMENT_SLOT_CANDIDATES` ResultSnapshot。 5. 文本说明精确结果;Presentation 可异步追加。 ### 替代查询 1. 精确结果为空。 2. 服务端查询策略返回允许扩展维度。 3. FastGPT 或后端依次调用只读工具。 4. 每个候选标注 `relaxedConstraint`。 5. 患者明确选择后才更新 TaskState。 ### 选择和锁号 1. AgentApplicationService 将自然语言或点击选择解析为 candidateId。 2. 校验 ResultSnapshot 未过期且版本匹配。 3. 重新查询该号源实时状态、费用和患者资格。 4. L1 选择行为视为授权,创建并执行 `LOCK_APPOINTMENT_SLOT`。 5. 成功后将 lockId 和有效期写入 TaskState。 ### 最终挂号 1. 后端准备 `REGISTER_APPOINTMENT` L2 命令。 2. SSE 返回 `command_prepared` 脱敏摘要。 3. 用户确认时携带 commandId、commandVersion 和幂等键。 4. Command Runtime 复核身份、有效期、taskVersion 和 lockId。 5. MCP 写工具执行挂号。 6. 成功后 TaskState 进入 COMPLETED。 ## 接口细节 外部接口以 `API-202606-004` 和 OpenAPI v2 为准。 SSE 事件: ```text message_delta semantic_result presentation task_updated command_prepared command_completed error completed ``` `presentation` 不保证出现,也不阻塞 `completed` 前的业务结果。 命令状态可通过独立查询接口恢复。 ## 异常处理 | 场景 | 处理 | |---|---| | FastGPT 文本流失败 | 返回明确错误或已有文本;不产生未验证状态 | | Tool Call Schema 失败 | 拒绝该调用,可有限重试;文本可降级 | | 查询超时 | 有限重试,说明无法获取实时数据,提供人工/传统入口 | | ResultSnapshot 过期 | 返回 `RESULT_EXPIRED`,重新查询 | | 选择版本冲突 | 返回 `RESULT_VERSION_CONFLICT` | | 锁号失败 | 返回最新候选,不自动改选 | | L2 命令过期 | 返回 `COMMAND_EXPIRED`,重新准备 | | 写命令超时 | 进入 UNKNOWN,按幂等键查询下游状态 | | 展示生成失败 | 记录指标,继续文本/语音 | | 任务切换 | 暂停旧任务,不续锁;提示剩余有效期 | ## 安全和合规 - 复用 Bearer/HMAC 鉴权,并校验 projectId、partnerId、deviceId。 - 所有 ResultSnapshot 和 Command 均做租户、项目、患者、会话绑定。 - 确认主体必须与准备命令时的患者绑定一致。 - FastGPT API Key、MCP 凭证和 HIS 密钥不得进入终端、日志或模型上下文。 - 患者资料按最小必要字段传递,日志只保存摘要和标识。 - Presentation 只展示当前终端权限允许的数据。 - L3 命令要求医护或人工确认主体,患者端不能单独完成。 ## 配置和部署 平台级配置: - 命令类型及固定风险等级; - 最低确认要求; - ResultSnapshot 最大有效期; - 写命令 UNKNOWN 处理策略; - FastGPT 引擎能力门禁。 租户/医院可配置: - 替代查询日期范围、职称和院区范围; - 返回候选数量; - 锁号有效期(不超过平台/HIS 上限); - 终端是否支持展示、最大条数和布局偏好。 ## 测试策略 - 单元:状态机、工具参数校验、版本校验、风险策略、简单展示策略。 - 集成:FastGPT Adapter、只读 MCP、Command Runtime、HIS Mock 幂等。 - 契约:OpenAPI v2、SSE 事件顺序、终端 interaction。 - 安全:越权 ResultSnapshot、伪造命令、重复确认、过期确认。 - 端到端:挂号完整路径、无结果替代、展示失败、写状态未知。 - 架构:FastGPT 不可依赖写工具;Command 不依赖模型;Presentation 不推进 TaskState。 ## 待评审项 - FastGPT 目标部署版本的 Tool/Function Call 事件格式需要在实施前实测并记录。 - 性能阈值需要在测试环境建立基线后填写到测试结果,不在设计阶段虚构。