# 工业 AI 交互画布方案草案

版本：v0.2  
目标：把第一阶段产品交互、系统边界和技术路线收敛清楚。本文档默认采用推荐方案，不再把每个决策点作为待选项展开。

## 1. 背景与目标

这个项目不是一个“AI 聊天框”，而是一个面向工业生产现场的 **AI 交互画布**。AI 的回复不应该主要表现为长文字，而应该表现为漂亮、清晰、可交互的组件。

核心目标有三类：

1. **自然语言控制工业能力**  
   用户先通过文字输入，后续再接入语音，要求 AI 调用工业软件、上位机软件、PLC/HMI 或其他工具能力，例如调参、开机、开灯、开泵、读取状态、执行生产动作。

2. **自然语言查询设备知识**  
   用户询问设备说明书、调机经验、故障处理、生产注意事项时，AI 不直接堆大段文字，而是生成像教学卡片一样的组件：视频、图片、步骤、重点、展开详情、专业说明、引用来源都分层显示。

3. **引导式调机流程**  
   AI 能带技师一步步完成调机。流程里既有 AI 自动调用工具完成的步骤，也有需要人工操作并反馈结果的步骤。第一阶段反馈来自鼠标点击和文字输入，后续再接入语音反馈。

当前项目已经有一个很好的雏形：左侧聊天，右侧画布，根据 tool invocation 渲染不同组件。后续把它从“根据工具名写死渲染”升级为 **结构化 Artifact 协议 + React 组件注册表 + 状态机**。

## 2. 产品定位

### 2.1 不做什么

- 不把这个系统做成完整工业软件 GUI 的替代品。
- 不把 PLC Web HMI 的所有操作搬进 AI 界面。
- 不让 LLM 随意生成前端代码并直接运行。
- 第一阶段不实现权限体系、动作分级、ASR 和完整安全审计。

### 2.2 要做什么

- 做工业软件和 HMI 之外的 **AI 辅助交互层**。
- 对关键动作提供清晰的可视化反馈、确认、进度、结果、失败原因和恢复建议。
- 对知识查询提供“先简洁、后展开”的教学式体验。
- 对调机任务提供流程引导、状态保存、点击/文字统一操作，语音操作作为后续扩展。

## 3. 两类工业场景的交互边界

### 3.1 上位机 GUI 场景

设备本身已有上位机软件 GUI。AI 页面只做补充，不重复展示所有 GUI。

适合展示：

- AI 即将执行的动作。
- 参数变更前后对比。
- 当前动作的执行前提示。
- 动作执行进度和结果。
- 异常时的解释和下一步建议。
- 调机步骤的引导卡片。

不适合展示：

- 完整工艺界面。
- 所有实时数据点。
- 原软件已有的复杂操作面板。

### 3.2 PLC + Web HMI 场景

PLC 是主控，另有 Web HMI 做实操显示。AI 画布重点是交互效果和辅助理解。

适合展示：

- “水泵已启动”“探照灯打开中”“产线准备中”等动画状态。
- 当前 HMI 页面之外的解释性信息。
- AI 对当前动作的总结。
- 面向技师的流程提示、确认卡片、注意事项。

不适合展示：

- 替代 HMI 的核心控制界面。
- 密集的实时仪表盘。
- 需要毫秒级闭环控制的操作。

## 4. 总体架构建议

如果只想快速看宏观结构和下一步优化方向，优先看这份 HTML 总览：[`architecture_overview.html`](./architecture_overview.html)。

第一阶段采用下面的分层：

```mermaid
flowchart LR
  U["技师 / 操作员"] --> V["文字输入 / 鼠标点击"]
  V --> R["意图路由器"]
  R --> A["AI 编排层"]
  A --> T["工业工具层"]
  A --> K["知识库 / 说明书 / 视频图片"]
  A --> S["Artifact 状态管理"]
  S --> C["前端组件画布"]
  C --> E["Action Event"]
  E --> R
  T --> S
  K --> S
```

关键点：

- **AI 编排层**：负责理解用户需求、选择工具、组织知识、决定展示什么组件。
- **工业工具层**：由你后续实现，负责真正调用工业软件、PLC、HMI、设备接口。
- **Artifact 状态管理**：保存当前组件、流程步骤、可点击动作、文本别名、状态快照。
- **前端组件画布**：只渲染受控组件，不运行 LLM 生成的任意代码。
- **意图路由器**：判断一句输入是在操作当前组件，还是在问新问题，还是要中断流程。

后续语音接入时，ASR 只负责把语音变成文本；BERT NLU 可以放在意图路由器前后，用来提升当前组件操作、新问题、流程控制等意图分类的稳定性。第一阶段不实现 ASR 和 BERT NLU，只保证路由接口可以承接它们。

### 4.1 宏观分层架构图

后续接入语音识别、Python NLU、工业工具和可视化配置后，建议整体分成 7 层：

```mermaid
flowchart TB
  subgraph L1["交互输入层"]
    Mic["麦克风 / 语音流"]
    TextInput["文字输入"]
    Click["组件点击"]
  end

  subgraph L2["感知与语义层"]
    ASR["ASR 服务\n语音转文本"]
    Normalize["文本归一化\n单位/数字/同义词"]
    NLU["Python NLU 服务\nDomain / Intent / Slot"]
  end

  subgraph L3["会话与路由层（TS）"]
    Session["Session Context\n当前设备/当前流程/当前 Artifact"]
    Router["Input Router\n当前组件优先 + NLU + LLM 兜底"]
  end

  subgraph L4["编排层（TS）"]
    Orchestrator["AI / Tool Orchestrator\n选择工具、组织结果"]
    UIInspect["inspect_ui_state\n让 LLM 读取画布摘要"]
  end

  subgraph L5["能力与配置层"]
    ToolRegistry["Tool Registry\n工具目录 / 输入输出 Schema"]
    VizRegistry["Visualization Registry\n工具到组件的绑定配置"]
    Knowledge["Knowledge Registry\n说明书 / SOP / 视频 / 图片"]
  end

  subgraph L6["执行层"]
    ToolGateway["Industrial Tool Gateway\n上位机 / PLC / HMI / Mock 工具"]
    KnowledgeSearch["知识检索服务"]
  end

  subgraph L7["展示层"]
    ArtifactStore["Artifact Store\n状态快照 / 流程实例"]
    Canvas["React Artifact Canvas\n参数卡 / 动画卡 / 知识卡 / 流程卡"]
  end

  Mic --> ASR --> Normalize
  TextInput --> Normalize
  Click --> Router
  Normalize --> Router
  Router --> NLU
  NLU --> Router
  Session --> Router
  Router --> Orchestrator
  Orchestrator --> ToolRegistry
  Orchestrator --> VizRegistry
  Orchestrator --> Knowledge
  Orchestrator --> ToolGateway
  Orchestrator --> KnowledgeSearch
  UIInspect --> ArtifactStore
  ToolGateway --> ArtifactStore
  KnowledgeSearch --> ArtifactStore
  ArtifactStore --> Canvas
  Canvas --> Click
```

各层职责：

- **交互输入层**：接收语音、文字、点击，不判断业务含义。
- **感知与语义层**：ASR 产出文本，Python NLU 产出结构化意图。
- **会话与路由层**：决定一句话是在操作当前组件，还是开启新任务。
- **编排层**：根据 NLU/LLM 结果选择工具，生成 artifact。
- **能力与配置层**：维护工具目录、工具输入输出结构、可视化组件绑定。
- **执行层**：真正调用工业软件、PLC、HMI、知识库。
- **展示层**：渲染组件、保存流程状态、接收用户反馈。

## 5. Artifact 组件协议

### 5.1 推荐思路

LLM 不直接返回 React/HTML，而是返回结构化组件数据。前端根据 `type` 从组件注册表中选择固定组件渲染。

示例：

```ts
type InteractionArtifact = {
  artifactId: string;
  type:
    | "parameter_change"
    | "device_action"
    | "production_start"
    | "knowledge_lesson"
    | "guided_procedure"
    | "media_gallery"
    | "alarm_explanation";
  title: string;
  status: "draft" | "waiting_confirmation" | "running" | "success" | "failed" | "paused";
  props: Record<string, unknown>;
  actions: ArtifactAction[];
  llmContext: ArtifactLLMContext;
  snapshotPolicy: "ephemeral" | "session" | "persistent";
};

type ArtifactAction = {
  actionId: string;
  label: string;
  kind: "primary" | "secondary" | "danger" | "input" | "toggle" | "choice";
  textAliases: string[];
  voiceAliases?: string[];
  requiresConfirmation?: boolean;
  disabled?: boolean;
};
```

这样做的好处：

- UI 风格稳定，可控。
- 工业执行边界更清晰。
- 组件可以持续迭代，不依赖每次 LLM 生成。
- 点击、文字指令和后续语音可以共用同一个 `actionId`。
- LLM 可以只看到必要摘要，不必看到复杂 UI 细节。

### 5.2 已确定的组件渲染路线

第一阶段采用 **React 组件注册表 + JSON Artifact 协议**。

具体做法：

- 控制、调参、流程类交互全部使用固定 React 组件。
- LLM 或后端编排层只产出结构化 JSON，不直接产出 JSX/HTML。
- 前端根据 `artifact.type` 从组件注册表选择组件。
- 知识内容中的长说明可以使用受限 Markdown，但外层仍由固定教学组件承载。
- 暂不做动态远程组件，也不允许 LLM 生成任意 UI 代码。

## 6. 工业控制类交互设计

### 6.1 参数设置组件

典型输入：

- “把主轴转速调到 1800。”
- “把水泵压力上限改成 0.8MPa。”
- “温度补偿加 2 度。”

组件应该展示：

- 参数名称。
- 当前值。
- 目标值。
- 单位。
- 影响范围。
- 是否需要确认。
- 执行结果。
- 可恢复/回滚信息。

建议组件：`ParameterChangeCard`

状态流：

```mermaid
stateDiagram-v2
  [*] --> Preview: 解析参数变更
  Preview --> WaitingConfirm: 展示旧值/新值
  WaitingConfirm --> Running: 用户确认
  WaitingConfirm --> Cancelled: 用户取消
  Running --> Success: 工具执行成功
  Running --> Failed: 工具执行失败
  Failed --> WaitingConfirm: 修改后重试
```

交互细节：

- 第一阶段统一采用“预览 -> 用户确认 -> 工具执行 -> 展示结果”的闭环。
- 确认可以来自按钮点击，也可以来自文字输入，例如“确认修改”“执行”。
- 后续接入语音时，语音确认也转换为同一个 `actionId`，不单独做一套逻辑。

### 6.2 具象设备控制组件

典型输入：

- “打开探照灯。”
- “启动水泵。”
- “关闭除尘风机。”
- “开始生产。”

建议组件：

- `DeviceActionCard`：打开灯、泵、阀、风机等单设备动作。
- `ProductionStartCard`：开机生产、启动工艺、产线准备。
- `MachineStatusAnimation`：动作动画，例如灯光亮起、水流动、泵叶旋转、设备进入运行态。

展示内容：

- 设备图标和动画。
- 当前状态：关闭、准备中、启动中、运行、失败。
- 执行动作。
- 工具调用进度。
- 执行前提示。
- 执行后的状态确认。

控制类动作不一定都需要长文字。界面可以用动画、状态徽标、颜色和短句表达结果。

### 6.3 第一阶段控制执行边界

第一阶段不实现权限体系、动作风险分级和高风险确认策略。控制类能力先按“可演示、可模拟、可替换真实工具”的方式设计。

执行边界：

- Mock 工具阶段：所有控制动作都可以完整演示 UI、状态流和失败处理。
- 接真实工业工具后：工具层由你负责控制实际可执行范围，本项目只负责把意图、参数、确认和结果组织成清晰交互。
- 所有控制类组件都保留“预览/确认/执行中/成功/失败”的状态结构，方便未来加权限、动作分级或审计。
- `ProductionStartCard` 先作为交互样板，不在第一阶段实现复杂生产安全策略。

## 7. 知识查询类交互设计

### 7.1 展示原则

知识问答不应该每次都显示大段文字。推荐采用“儿童教学式”的结构，但视觉和语言仍保持工业专业感：

1. 先给一句结论。
2. 展示核心图、视频或步骤。
3. 给 3 到 5 个关键点。
4. 专业细节折叠起来。
5. 引用说明书或知识库来源。
6. 用户需要时再展开。

建议组件：`KnowledgeLessonArtifact`

模块：

- 顶部结论。
- 设备/部件示意图。
- 视频卡片。
- 图片步骤卡。
- “为什么这样做”解释区。
- “专业详情”折叠区。
- “继续调机”按钮。
- “查看原文/来源”按钮。

### 7.2 知识内容生成流程

```mermaid
flowchart TD
  Q["用户问题"] --> R["检索说明书/知识库"]
  R --> P["提取关键段落、图片、视频"]
  P --> G["AI 组织教学结构"]
  G --> A["生成 Knowledge Artifact"]
  A --> UI["画布分层展示"]
```

建议每次知识回答都保留来源：

- 文档名称。
- 页码或章节。
- 图片/视频 ID。
- 知识库条目版本。
- 生成时间。

### 7.3 已确定的知识展示路线

第一阶段采用 **固定教学外壳 + block 内容**。

具体做法：

- 外层固定为 `KnowledgeLessonArtifact`。
- 内部使用 block 列表表达结论、步骤、图片、视频、注意事项、专业详情、引用来源。
- 大段文字默认折叠，不直接铺满主界面。
- block 协议先保持简单，后续可以自然演进成更完整的卡片编排器。

示例 block：

```ts
type KnowledgeBlock =
  | { type: "summary"; text: string }
  | { type: "key_points"; items: string[] }
  | { type: "steps"; steps: Array<{ title: string; body: string; media?: MediaRef[] }> }
  | { type: "media"; media: MediaRef[] }
  | { type: "details"; title: string; markdown: string; defaultOpen?: boolean }
  | { type: "citations"; citations: KnowledgeCitation[] };
```

## 8. 引导式调机流程

### 8.1 流程步骤类型

调机流程建议抽象成 `GuidedProcedure`：

```ts
type ProcedureStep =
  | {
      type: "auto_tool";
      title: string;
      toolName: string;
      args: Record<string, unknown>;
    }
  | {
      type: "manual_action";
      title: string;
      instruction: string;
      expectedFeedback: string[];
      media?: MediaRef[];
    }
  | {
      type: "measurement_input";
      title: string;
      field: string;
      unit: string;
      validRange?: [number, number];
    }
  | {
      type: "decision_branch";
      title: string;
      choices: string[];
    }
  | {
      type: "checklist_gate";
      title: string;
      checks: string[];
    };
```

### 8.2 自动步骤与人工步骤

自动步骤：

- AI 调用工具读取状态。
- AI 设置参数。
- AI 触发设备动作。
- AI 查询报警或日志。

人工步骤：

- 技师更换夹具。
- 技师观察指示灯。
- 技师测量间隙。
- 技师确认工件位置。
- 技师上传或选择现场照片。

每一步都应该有明确状态：

- 未开始。
- 正在执行。
- 等待人工反馈。
- 已完成。
- 失败。
- 已跳过。
- 已暂停。

### 8.3 鼠标和文字输入统一操作

所有按钮都应该映射为 `actionId`。鼠标点击和文字命令最终都生成同一种事件；后续接入语音后，语音识别结果也走同一个事件协议：

```ts
type ArtifactActionEvent = {
  artifactId: string;
  actionId: string;
  source: "click" | "text" | "voice" | "system";
  value?: unknown;
  transcript?: string;
  confidence?: number;
  createdAt: string;
};
```

示例：

- 点击“已完成”按钮：`actionId = "step.complete"`
- 文字输入“这一步完成了”：`actionId = "step.complete"`
- 文字输入“压力是 0.72”：`actionId = "measurement.submit"`，`value = 0.72`

这样前端、文字输入、未来语音和后端流程引擎不会各做一套逻辑。

## 9. 输入路由策略：新问题还是操作当前组件

这是本项目最关键的体验问题之一。

### 9.1 推荐核心原则

当存在正在进行的调机流程或控制确认时，系统进入 **前台任务模式**。这时用户输入优先解释为对当前组件的操作，而不是立刻开启新话题。

可以把当前组件理解为持有一个“前台交互权”：

- 当前组件有可执行动作。
- 当前流程有等待反馈的步骤。
- 用户没有明确输入“暂停当前流程”。

在这种情况下，输入路由优先级如下：

1. 全局流程指令：取消、暂停、继续、返回。
2. 当前组件按钮或输入项：确认、取消、下一步、已完成、数值反馈。
3. 当前流程相关问题：例如“这一步为什么要这样做？”
4. 临时查询问题：例如“顺便查一下这个报警码。”
5. 新任务或新流程。
6. 闲聊。

### 9.2 已确定的输入路由路线

第一阶段采用 **规则优先 + LLM 兜底**。

具体做法：

- 当前组件按钮、确认、取消、下一步、已完成等短指令，用规则和 `textAliases` 直接匹配。
- 数值输入用规则解析，例如“压力 0.72”“测量值是 3.1”。
- 当前流程相关的解释性问题、新问题、复杂表达交给 LLM。
- 后续接入 BERT NLU 时，把 BERT 输出作为意图路由器的一个信号源，不改变 Artifact Action Event 协议。

### 9.3 防止闲聊打断调机流程

推荐策略：

- 调机流程进行中时，不允许普通新问题直接覆盖当前画布。
- 新问题默认以“侧边临时回答”或“小浮层”展示，不替换主流程。
- 如果新问题会打开新的大型组件，系统先问：“当前调机流程正在第 3 步，要暂停并打开新内容吗？”
- 用户可以输入“暂停这个”“先问个问题”“继续刚才”。
- 每个流程步骤都保存快照，随时可以恢复。

### 9.4 组件状态快照

建议保存：

- 当前 artifact。
- 当前流程 ID。
- 当前步骤 ID。
- 已完成步骤。
- 已调用工具及结果。
- 人工反馈记录。
- 当前可用 action 列表。
- 当前画布摘要。
- 最近 N 条用户输入 transcript。

状态快照用途：

- 防止误打断。
- 支持恢复流程。
- 支持 LLM 理解当前界面。
- 支持恢复和复盘。

## 10. LLM 如何理解当前组件

你提出的判断很对：工具调用后直接显示组件，而不是把完整组件都给 LLM 看，是合理的。但如果用户围绕界面内容提问，LLM 必须知道当前界面上有什么。

### 10.1 推荐方案：组件提供 LLM 可读摘要

每个组件除了 `props`，还提供一个 `llmContext`：

```ts
type ArtifactLLMContext = {
  visibleSummary: string;
  currentState: string;
  activeStep?: string;
  visibleFields: Array<{
    label: string;
    value: string;
  }>;
  availableActions: Array<{
    actionId: string;
    label: string;
    textAliases: string[];
    voiceAliases?: string[];
  }>;
  lastEvents: string[];
};
```

LLM 不需要看完整 DOM 或截图，只需要看这个结构化摘要。

### 10.2 已确定的 LLM 查看组件路线

第一阶段采用 **极简上下文 + `inspect_ui_state` 工具**：

- 每轮 prompt 默认带：当前是否有前台流程、当前步骤、是否等待用户反馈。
- 当用户输入“这个按钮是什么意思”“为什么让我做这一步”“刚才显示的目标值是多少”时，LLM 调用 `inspect_ui_state` 获取详细组件状态。

### 10.3 示例工具

```ts
inspect_ui_state({
  detailLevel: "summary" | "actions" | "full",
  artifactId?: string
})
```

返回：

```json
{
  "artifactId": "artifact_123",
  "type": "guided_procedure",
  "title": "主轴换型调机流程",
  "status": "waiting_confirmation",
  "currentStep": "第 3 步：确认夹具锁紧",
  "visibleSummary": "画布正在等待技师确认夹具已经锁紧。",
  "availableActions": [
    { "actionId": "step.complete", "label": "已完成" },
    { "actionId": "step.need_help", "label": "需要帮助" },
    { "actionId": "procedure.pause", "label": "暂停流程" }
  ]
}
```

## 11. 工具调用与组件显示的关系

### 11.1 推荐拆分

一次工业工具调用应该拆成三类结果：

```ts
type ToolResponse = {
  machineResult: unknown;
  llmSummary: string;
  artifact?: InteractionArtifact;
  executionRecord: {
    toolName: string;
    args: unknown;
    resultCode: string;
    createdAt: string;
  };
};
```

- `machineResult`：给系统内部使用。
- `llmSummary`：给 LLM 用，让它知道发生了什么。
- `artifact`：给前端画布渲染。
- `executionRecord`：保存最小执行记录，方便前端恢复状态和调试。

这样可以避免把过多 UI 细节塞给 LLM，同时又能让 LLM 知道关键事实。

### 11.2 已确定的工具结果展示路线

第一阶段采用 **工具只执行，编排层生成 artifact**。

具体做法：

- 工业工具层只返回执行结果，不关心 UI。
- AI 编排层或后端 Artifact Builder 根据工具结果生成 `InteractionArtifact`。
- 前端只消费 artifact，不直接理解工业工具内部数据结构。
- 这样后续替换 Mock 工具为真实工业工具时，UI 协议保持稳定。

### 11.3 第二版：工具可视化组件分类与维护

第二版需要增加一个“工具和可视化组件维护中心”，让工具能力和画布组件之间不再完全写死在代码里。这样新增工业工具时，可以配置它应该用哪种可视化方式呈现。

工具可视化先分为三类：

| 类型 | 用途 | 示例组件 | 适用工具 |
|---|---|---|---|
| 检索信息类型 | 展示查询、检索、知识库或设备资料结果 | `KnowledgeLessonArtifact`、`AlarmExplanationCard`、`MediaGalleryCard` | 查说明书、查报警码、查调机经验、查设备状态摘要 |
| 图标动画类型 | 展示具象设备动作和执行状态 | `DeviceActionCard`、`ProductionStartCard`、`MachineStatusAnimation` | 开灯、开泵、开阀、启动风机、开机生产 |
| 用户自定义类型 | 允许用户把某个工具绑定到自定义组件配置 | `CustomToolArtifact`、自定义 schema 驱动组件 | 特定设备动作、厂内专用流程、业务定制工具 |

第二版建议增加 `ToolVisualizationConfig`：

```ts
type ToolVisualizationConfig = {
  toolName: string;
  displayName: string;
  visualizationType: "retrieval_info" | "icon_animation" | "custom";
  artifactType: InteractionArtifact["type"] | "custom_tool";
  icon?: string;
  animationPreset?: string;
  propMapping: Array<{
    from: string;
    to: string;
    transform?: "string" | "number" | "boolean" | "status" | "media_list";
  }>;
  defaultActions?: ArtifactAction[];
  customSchema?: Record<string, unknown>;
  enabled: boolean;
};
```

维护中心需要支持：

- 查看所有工具。
- 给工具选择可视化类型。
- 配置工具结果字段到 artifact props 的映射。
- 配置按钮、文本别名和后续语音别名。
- 配置图标、动画预设、状态文案。
- 对用户自定义类型提供 schema 校验，避免组件收到不可渲染的数据。

第二版不要求用户直接写 React 组件。更稳的方式是先提供“可配置组件模板”：

- 信息卡模板。
- 状态动画模板。
- 步骤流程模板。
- 参数对比模板。
- 媒体资料模板。

真正的远程自定义组件可以作为更后面的能力，因为它会带来版本、样式、执行边界和兼容性问题。

## 12. 数据与状态设计

### 12.1 核心对象

建议至少有这些对象：

- `ConversationSession`：一次对话会话。
- `ArtifactSession`：当前画布状态。
- `WorkflowInstance`：调机流程实例。
- `ToolExecution`：每次工具调用记录。
- `ActionEvent`：每次点击、文字、未来语音、系统事件。
- `KnowledgeCitation`：知识来源。
- `OperatorContext`：当前操作员、班组、设备。第一阶段不展开权限模型。

### 12.2 状态保存级别

| 级别 | 用途 | 示例 |
|---|---|---|
| 临时态 | 普通问答卡片 | 查一个概念 |
| 会话态 | 当前任务内有效 | 当前参数修改确认 |
| 持久态 | 可恢复、可复盘 | 调机流程、生产动作、工具调用 |

调机流程和生产动作建议进入持久态。第一阶段先保证能恢复流程和复盘交互，不做完整审计系统。

## 13. 输入与 NLU 接入边界

### 13.0 汽车语音助手里的 Domain / Intent / Slot / 上下文状态

汽车语音助手常用的 NLU 体系可以理解成四层语义结构：

| 概念 | 含义 | 汽车例子 | 工业设备例子 |
|---|---|---|---|
| Domain | 业务领域，先判断用户在说哪类事情 | 空调、导航、车窗、音乐 | 参数设置、设备控制、报警知识、调机流程 |
| Intent | 用户想完成的动作 | 打开空调、导航到公司、调高音量 | 设置参数、打开设备、查询报警、进入调机 |
| Slot | 动作需要的参数 | 温度 24 度、目的地公司、音量 8 | 参数名主轴转速、目标值 1800、设备探照灯 |
| Context State | 当前对话/界面/设备状态 | 正在导航、刚打开空调、当前温度 22 度 | 当前 Artifact、当前调机步骤、当前设备、等待确认 |

例如汽车里一句话：“把空调调到 24 度”：

```json
{
  "domain": "climate",
  "intent": "set_temperature",
  "slots": {
    "temperature": 24,
    "unit": "celsius"
  },
  "contextState": {
    "vehicleArea": "front"
  }
}
```

工业设备里一句话：“把主轴转速调到 1800”：

```json
{
  "domain": "machine_parameter",
  "intent": "set_parameter",
  "slots": {
    "parameter": "主轴转速",
    "value": 1800,
    "unit": "rpm"
  },
  "contextState": {
    "deviceId": "machine_01",
    "activeArtifactId": "artifact_123"
  }
}
```

这里最容易混淆的是 **Intent 和 Tool**：

- `intent` 是用户意图，例如 `set_parameter`。
- `tool` 是系统能力，例如 `write_machine_parameter`。
- 一个 intent 可能映射到多个 tool；同一个 tool 也可能被多个 intent 使用。

所以 NLU 不应该直接决定 UI，也不应该强耦合具体 React 组件。NLU 的核心输出是“用户想做什么 + 参数是什么 + 置信度多少”。TS 编排层再根据工具目录和可视化配置决定调用哪个工具、展示哪个组件。

### 13.1 第一阶段输入链路

第一阶段只做文字输入和鼠标点击，不实现 ASR，也不实现 BERT NLU。为了后续接入语音和 BERT，当前架构要提前把“用户输入文本”和“组件动作事件”分开。

```mermaid
flowchart LR
  I["文字输入"] --> N["归一化文本"]
  B["按钮点击"] --> E["ActionEvent"]
  N --> R["意图路由器"]
  R --> C["当前组件动作"]
  R --> L["LLM 新问题/复杂语义"]
  C --> E["ActionEvent"]
  L --> O["AI 编排层"]
```

### 13.2 后续语音和 BERT NLU 接入位置

后续接入时建议链路是：

```mermaid
flowchart LR
  M["麦克风"] --> A["ASR"]
  A --> T["识别文本"]
  T --> N["BERT NLU"]
  N --> R["意图路由器"]
  R --> E["ActionEvent / LLM 编排"]
```

这里的边界是：

- ASR 只负责语音转文字。
- BERT NLU 负责稳定识别短命令、当前组件动作、流程控制、数值输入等意图。
- LLM 负责复杂问答、知识组织、流程解释和 artifact 生成。
- 不管输入来自文字、ASR 还是 BERT，最终都尽量落到统一的 `ArtifactActionEvent`。

本文档后续不展开 ASR 和 BERT NLU 的具体模型方案，只保留接口位置。

### 13.3 推荐职责边界

建议不要让 Python NLU 服务成为“工具和 UI 的唯一配置中心”。更清晰的边界是：

| 模块 | 负责什么 | 不负责什么 |
|---|---|---|
| ASR 服务 | 音频转文本、置信度、分段、最终文本 | 不判断业务意图，不调工具 |
| Python NLU 服务 | domain、intent、slot、置信度、候选工具建议 | 不调用工业工具，不决定最终 UI |
| TS Input Router | 当前组件动作优先、规则匹配、调用 NLU、决定是否 LLM 兜底 | 不训练模型 |
| Tool Registry | 工具列表、输入 Schema、输出 Schema、工具描述、示例 | 不做语义识别 |
| Visualization Registry | 工具/intent 到 artifact 组件的绑定、字段映射、默认动作 | 不执行工具 |
| Tool Orchestrator | 选择工具、调用工具、生成 artifact、维护状态 | 不做 ASR 和模型训练 |

也就是说，NLU 服务可以暴露它“理解哪些 domain/intent/slot”，也可以返回候选工具名，但工具目录和可视化绑定建议由 TS 项目或单独配置中心维护。

### 13.4 ASR 到 TS 的接口协议

第一阶段可以先做非流式接口，后面再升级 WebSocket 流式识别。

```http
POST /asr/transcribe
Content-Type: multipart/form-data
```

请求字段：

| 字段 | 类型 | 说明 |
|---|---|---|
| audio | file | 音频文件或音频片段 |
| sessionId | string | 当前会话 ID |
| deviceId | string | 当前设备 ID |
| language | string | 默认 `zh-CN` |
| sampleRate | number | 采样率 |

响应：

```ts
type ASRResult = {
  requestId: string;
  sessionId: string;
  text: string;
  normalizedText?: string;
  confidence: number;
  isFinal: boolean;
  segments?: Array<{
    text: string;
    startMs: number;
    endMs: number;
    confidence: number;
  }>;
};
```

后续流式接口可以用 WebSocket：

```txt
Client -> ASR: audio_chunk
ASR -> Client/TS: partial_transcript
ASR -> Client/TS: final_transcript
```

但无论是否流式，进入 TS Input Router 的都应该是统一文本事件：

```ts
type InputTextEvent = {
  eventId: string;
  sessionId: string;
  source: "text" | "asr";
  text: string;
  normalizedText?: string;
  confidence?: number;
  createdAt: string;
};
```

### 13.5 TS 到 Python NLU 的接口协议

核心解析接口：

```http
POST /nlu/parse
Content-Type: application/json
```

请求：

```ts
type NLUParseRequest = {
  requestId: string;
  sessionId: string;
  text: string;
  normalizedText?: string;
  locale: "zh-CN";
  context: {
    deviceId?: string;
    activeArtifact?: {
      artifactId: string;
      type: string;
      status: string;
      availableActions: Array<{
        actionId: string;
        label: string;
        textAliases: string[];
      }>;
    };
    activeWorkflow?: {
      workflowId: string;
      currentStepId: string;
      currentStepTitle: string;
    };
  };
  toolCatalogVersion?: string;
  nluSchemaVersion?: string;
};
```

响应：

```ts
type NLUParseResponse = {
  requestId: string;
  modelVersion: string;
  schemaVersion: string;
  domain: string;
  intent: string;
  confidence: number;
  slots: Record<
    string,
    {
      value: string | number | boolean;
      rawText?: string;
      confidence?: number;
      unit?: string;
    }
  >;
  routeHint:
    | "artifact_action"
    | "tool_call"
    | "knowledge_query"
    | "workflow_control"
    | "smalltalk"
    | "fallback";
  candidateTools?: Array<{
    toolName: string;
    confidence: number;
    reason?: string;
  }>;
  clarification?: {
    required: boolean;
    question?: string;
    missingSlots?: string[];
  };
};
```

示例：

```json
{
  "requestId": "req_001",
  "modelVersion": "bert-nlu-2026-05",
  "schemaVersion": "nlu_schema_v1",
  "domain": "machine_parameter",
  "intent": "set_parameter",
  "confidence": 0.94,
  "slots": {
    "parameter": { "value": "主轴转速", "rawText": "主轴转速", "confidence": 0.96 },
    "value": { "value": 1800, "rawText": "1800", "confidence": 0.98, "unit": "rpm" }
  },
  "routeHint": "tool_call",
  "candidateTools": [
    { "toolName": "write_machine_parameter", "confidence": 0.91 }
  ]
}
```

### 13.6 NLU 元数据接口

NLU 服务需要暴露自己支持的 domain、intent、slot，方便 TS 做校验、版本对齐和调试。

```http
GET /nlu/metadata
```

响应：

```ts
type NLUMetadata = {
  serviceName: string;
  modelVersion: string;
  schemaVersion: string;
  domains: Array<{
    name: string;
    description: string;
    intents: Array<{
      name: string;
      description: string;
      requiredSlots: string[];
      optionalSlots: string[];
      examples: string[];
    }>;
  }>;
  slots: Array<{
    name: string;
    type: "string" | "number" | "boolean" | "enum";
    values?: string[];
    units?: string[];
  }>;
};
```

### 13.7 工具目录接口

工具目录建议由 TS 项目或工业工具网关维护，NLU 可以读取它来训练/更新候选工具映射，但不要让 NLU 成为工具真实执行的源头。

```http
GET /tools/catalog
```

响应：

```ts
type ToolCatalog = {
  version: string;
  tools: Array<{
    toolName: string;
    displayName: string;
    domain: string;
    description: string;
    inputSchema: Record<string, unknown>;
    outputSchema: Record<string, unknown>;
    examples: string[];
    aliases: string[];
    enabled: boolean;
  }>;
};
```

示例：

```json
{
  "version": "tool_catalog_v1",
  "tools": [
    {
      "toolName": "write_machine_parameter",
      "displayName": "写入设备参数",
      "domain": "machine_parameter",
      "description": "把目标参数写入指定工业设备。",
      "inputSchema": {
        "type": "object",
        "required": ["deviceId", "parameter", "value"],
        "properties": {
          "deviceId": { "type": "string" },
          "parameter": { "type": "string" },
          "value": { "type": ["number", "string"] },
          "unit": { "type": "string" }
        }
      },
      "outputSchema": {
        "type": "object",
        "properties": {
          "previousValue": { "type": ["number", "string"] },
          "targetValue": { "type": ["number", "string"] },
          "status": { "type": "string" }
        }
      },
      "examples": ["把主轴转速调到 1800", "温度补偿加 2 度"],
      "aliases": ["调参数", "设置参数", "修改参数"],
      "enabled": true
    }
  ]
}
```

### 13.8 工具到可视化组件的绑定协议

工具调用结果不应该直接等于 UI。第二版通过 Visualization Registry 维护工具和可视化组件的绑定。

```http
GET /visualizations/config
PUT /visualizations/config/:bindingId
```

配置结构：

```ts
type ToolVisualizationBinding = {
  bindingId: string;
  target: {
    type: "tool" | "intent";
    name: string;
  };
  visualizationType: "retrieval_info" | "icon_animation" | "custom";
  artifactType:
    | "parameter_change"
    | "device_action"
    | "production_start"
    | "knowledge_lesson"
    | "guided_procedure"
    | "custom_tool";
  propMapping: Array<{
    from: string;
    to: string;
    transform?: "string" | "number" | "boolean" | "status" | "media_list";
  }>;
  defaultActions: ArtifactAction[];
  enabled: boolean;
};
```

示例：参数写入工具绑定到参数对比卡：

```json
{
  "bindingId": "viz_write_parameter",
  "target": {
    "type": "tool",
    "name": "write_machine_parameter"
  },
  "visualizationType": "custom",
  "artifactType": "parameter_change",
  "propMapping": [
    { "from": "tool.input.parameter", "to": "props.parameterName", "transform": "string" },
    { "from": "tool.output.previousValue", "to": "props.currentValue" },
    { "from": "tool.input.value", "to": "props.targetValue" },
    { "from": "tool.input.unit", "to": "props.unit" }
  ],
  "defaultActions": [
    {
      "actionId": "artifact.confirm",
      "label": "确认执行",
      "kind": "primary",
      "textAliases": ["确认", "执行", "确认执行"]
    }
  ],
  "enabled": true
}
```

### 13.9 一次完整链路

```mermaid
sequenceDiagram
  participant UI as React 画布
  participant ASR as ASR 服务
  participant Router as TS Input Router
  participant NLU as Python NLU
  participant Tools as Tool Registry
  participant Viz as Visualization Registry
  participant Exec as 工业工具网关
  participant Store as Artifact Store

  UI->>ASR: 音频或语音片段
  ASR-->>UI: transcript
  UI->>Router: InputTextEvent
  Router->>Store: inspect active artifact
  alt 命中当前组件动作
    Router->>Store: ArtifactActionEvent
    Store-->>UI: 更新当前组件
  else 需要语义识别
    Router->>NLU: /nlu/parse(text + context)
    NLU-->>Router: domain / intent / slots / candidateTools
    Router->>Tools: 查询工具 schema
    Router->>Exec: 调用工具
    Exec-->>Router: tool result
    Router->>Viz: 查询工具可视化绑定
    Router->>Store: 生成并保存 artifact
    Store-->>UI: 渲染交互组件
  end
```

## 14. 第一阶段最小记录

第一阶段先不实现权限、动作分级、高风险确认和完整审计系统。这里仅保留产品运行所需的最小记录能力，目的是支持状态恢复、问题排查和后续扩展。

最小记录内容：

- 当前会话 ID。
- 当前 artifact 快照。
- 当前流程步骤。
- 用户输入 transcript。
- 点击或文字触发的 `ActionEvent`。
- Mock/真实工具调用名称、参数、结果码、时间。
- 异常时展示明确的失败原因和恢复步骤。
- 支持暂停、取消、恢复。

后续如果要进入真实生产控制，再单独补权限、动作分级、二次确认、审计日志和合规要求。

## 15. 前端组件库初始清单

第一阶段建议做 8 个核心组件：

| 组件 | 用途 |
|---|---|
| `ParameterChangeCard` | 参数前后对比、确认、执行结果 |
| `DeviceActionCard` | 开灯、开泵、开阀、风机等具象设备动作 |
| `ProductionStartCard` | 开机生产、启动前检查、启动进度 |
| `KnowledgeLessonArtifact` | 说明书/知识库教学式回答 |
| `GuidedProcedureArtifact` | 调机流程引导 |
| `MeasurementInputCard` | 人工输入测量值，后续可支持语音填数 |
| `MediaGalleryCard` | 视频、图片、说明资料展示 |
| `AlarmExplanationCard` | 报警解释、原因、处理步骤 |

## 16. 当前项目演进建议

当前 `src/app/page.tsx` 里已经是左侧聊天 + 右侧画布。可以按这个路线演进：

1. 把 `activeTool.toolName === "getWeather"` 这种判断抽成组件注册表。
2. 定义 `InteractionArtifact` 类型。
3. API 不再只返回工具结果，而是返回 `artifact`。
4. 前端用 `artifact.type` 渲染组件。
5. 所有组件按钮都发 `ArtifactActionEvent`。
6. 加一个 `artifact store` 保存当前组件快照。
7. 后端加 `inspect_ui_state` 工具，让 LLM 可按需读取当前组件摘要。
8. 预留后续 BERT NLU/语音入口，把未来语音也转成 `ArtifactActionEvent`。

## 17. 阶段计划

### 阶段 1：交互画布原型

目标：

- 固定组件注册表。
- Mock 工业工具。
- 参数变更组件。
- 设备动作动画组件。
- 知识教学组件。

验收：

- 用户输入“把转速改到 1800”，右侧显示参数前后对比。
- 用户确认后显示执行中、成功或失败。
- 用户输入“打开探照灯”，右侧显示灯光动画和状态。
- 用户问知识问题，右侧显示教学式卡片，而不是大段文本。

### 阶段 2：流程引导

目标：

- 实现 `GuidedProcedureArtifact`。
- 支持自动步骤和人工步骤。
- 支持暂停、恢复、跳过、失败重试。
- 支持文字反馈“已完成”“数值是 0.72”。

验收：

- 一个调机流程可以从第 1 步走到最后。
- 中途问临时问题不会覆盖流程。
- 可以输入“继续刚才”恢复流程。

### 阶段 3：LLM 读取 UI 状态

目标：

- 实现 `llmContext`。
- 实现 `inspect_ui_state`。
- 用户围绕界面提问时，LLM 能知道当前组件和步骤。

验收：

- 用户问“刚才让我确认的目标值是多少”，AI 能答对。
- 用户问“这个按钮是什么意思”，AI 能解释当前按钮。
- 用户问“这一步为什么要做”，AI 能结合流程上下文回答。

### 阶段 4：工业工具接入

目标：

- 接入你实现的工业软件能力。
- 保持工具执行结果到 artifact 展示的稳定映射。
- 保留最小执行记录和异常处理。

验收：

- 工具调用结果能稳定驱动组件状态变化。
- 工具失败时组件能给出恢复建议。

## 18. 已收敛的技术路线

本阶段不再保留多个待选决策，统一采用以下路线：

| 方向 | 已采用方案 |
|---|---|
| 组件渲染 | React 组件注册表 + JSON Artifact 协议 |
| 知识展示 | 固定教学外壳 + block 内容 |
| 输入路由 | 规则优先 + LLM 兜底 |
| 流程状态 | 后端持久化 `WorkflowInstance`，原型阶段可先用会话态模拟 |
| LLM 理解界面 | 每轮给极简上下文，需要时调用 `inspect_ui_state` |
| 工具展示关系 | 工具只执行，编排层生成 artifact |
| 第一阶段输入 | 文字输入 + 鼠标点击 |
| 后续 NLU | 预留 BERT NLU 接口，但本阶段不实现 |
| 语音/ASR | 预留接口，本阶段不实现 |
| 权限/动作分级/高风险确认 | 本阶段不实现 |
| 第二版工具可视化维护 | 增加工具和可视化组件维护中心，支持检索信息、图标动画、用户自定义三类 |

## 19. 我建议的第一版 MVP 范围

第一版不要做太大，建议只做这 5 条闭环：

1. 参数修改：旧值/新值/确认/执行结果。
2. 设备控制：打开探照灯或水泵的动画状态。
3. 知识问答：说明书内容变成教学卡片。
4. 调机流程：3 到 5 步，包含一个自动步骤和一个人工反馈步骤。
5. 文字输入操作当前组件：确认、取消、下一步、已完成、数值输入。

这 5 条跑通后，再扩展到真实工业软件和 PLC/HMI。

## 20. 下一步建议

建议按下面顺序进入实现：

1. 先定义 `InteractionArtifact`、`ArtifactAction`、`ArtifactActionEvent` 类型。
2. 建立组件注册表，把当前写死的 weather/stock 渲染替换成 artifact 渲染。
3. 先实现两个控制类组件：`ParameterChangeCard` 和 `DeviceActionCard`。
4. 再实现 `KnowledgeLessonArtifact`，支持 summary、key_points、steps、media、details、citations 这些 block。
5. 再实现 `GuidedProcedureArtifact`，跑通 3 到 5 步调机流程。
6. 加入 `inspect_ui_state`，让 LLM 能按需读取当前画布摘要。
7. 最后预留 BERT NLU/ASR 接口，但不在第一阶段接入。

完成后，这个项目就会从“聊天 + 单个工具卡片 demo”变成一个真正可扩展的工业 AI 交互画布原型。
