Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 人工智能 / Artificial Intelligence

智能体工程 / Agent Engineering

1. Agent 工程体系全景 / Agent Engineering System Overview

2. Function Calling - 让 LLM 具备行动能力 / Function Calling for Giving LLMs the Ability to Act

3. Agent 框架演进 - 从裸 SDK 到 LangGraph / The Evolution of Agent Frameworks from Raw SDKs to LangGraph

4. RAG 基础 - 让 Agent 拥有"知识" / Retrieval-Augmented Generation Fundamentals for Agent Knowledge

5. 记忆管理 - Agent 的大脑 / Memory Management as the Brain of an Agent

6. Agent 工作流 - 从单步到复杂的执行编排 / Agent Workflows from Single Steps to Complex Orchestration

7. 多 Agent 系统 - 多个 Agent 协作 / Multi-Agent Systems and Agent Collaboration

8. RAG 进阶 - 企业级知识库实战 / Advanced RAG for Enterprise Knowledge Bases

9. 真实 Agent 应用场景 / Real-World AI Agent Applications

10. Structured Output - 让 LLM 输出可控的结构化数据 / Structured Output for Controllable, Machine-Readable LLM Responses

11. Tools Design Best Practices - AI Agent 工具设计最佳实践 / Tools Design Best Practices for AI Agents

12. Agent 架构模式 - 从单 Agent 到多 Agent 的工程范式 / Agent Architecture Patterns

13. Agent Modes — 编程 Agent 的交互模式设计 / Designing Interaction Modes for Coding Agents

14. Agent Workflow 编排:从循环到持久化执行的演进

15. Context Engineering - 从 Prompt 设计到上下文编排 / Context Engineering: From Prompt Design to Context Orchestration

16. Agent 缓存工程:从 KV Cache、Prompt Cache 到语义缓存 / Agent Caching Engineering

17. Harness Engineering, Skills, and Loop Engineering — 从信任模型到验证系统 / From Trusting Models to Verifying Systems

18. MCP 协议 - AI 工具的"USB 接口" / Model Context Protocol for AI Tool Integration

19. Agent 评估与测试 — 如何衡量一个"不可预测"的系统 / Agent Evaluation and Testing — How to Measure an "Unpredictable" System

20. 安全沙箱 - Agent 的安全边界 / Secure Sandboxes as Agent Safety Boundaries

21. 权限与门卫 - Agent 的安全控制中枢 / Permissions and Policy Gates for Agent Control

22. API Key 管理与安全 - Agent 的密钥生命周期的管理 / API Key Lifecycle Management and Security for Agents

23. 提示词注入防护 - Agent 的防御前沿 / Prompt Injection Defense for AI Agents

24. 可观测性与调试 - Agent 运行的透明度保障 / Observability and Debugging for Transparent Agent Operations

25. 模型路由 - 让正确的模型做正确的事 / Model Routing for Matching Models to Tasks

26. OpenClaw 设计深度分析 - 为什么它让人觉得"活"了 / OpenClaw Design Analysis and the Illusion of Liveliness

27. Claude Code 泄露源码深度分析 - 512,000 行代码揭示的生产级 Agent 架构 / Claude Code Source Analysis and Production Agent Architecture

28. LobeChat 设计深度分析 - 全栈 Agent Chat 应用工程实践 / LobeChat Design Analysis and Full-Stack Agent Chat Engineering

29. 编程 Agent 全面对比:从 Claude Code 到 Pi 的设计哲学 / Coding Agents Comparison: Design Philosophies from Claude Code to Pi

30. 领域 Agent 的确定性工具编译与延迟执行——从自然语言规格到单次 CAE 提交

31. Agent 工程学习指南 / An AI Agent Engineering Learning Guide

本页目录

领域 Agent 的确定性工具编译与延迟执行——从自然语言规格到单次 CAE 提交 ​

📅 创建时间:2026-07-29
🏷️ 标签:#DomainAgent #DeterministicCompilation #DeferredExecution #HumanInTheLoop #Schema #Controller #CAEAgent
📚 前置知识:[[01-function-calling]] [[05-agent-workflow]] [[09-structured-output]] [[10-tools-design]] [[13-workflow-orchestration]] [[15-harness-loop-skills]]


📋 本章目标 ​

  • 理解为什么普通的“LLM 调工具 → 立即执行”循环不适合 CAE、CAD、数据库迁移和工业控制等高副作用领域
  • 掌握一种新的领域 Agent 模式:Human-Gated Deterministic Tool Compilation
  • 区分设计规格、Tool Call、领域中间表示、生成脚本和真实执行结果
  • 理解 JSON Schema 能约束什么、不能约束什么,以及为什么 Controller 仍然不可替代
  • 掌握 Controller 如何冻结目标、控制状态、路由工具、收集多轮 Tool Call、验证顺序并决定唯一执行时机
  • 理解如何把 LLM 输出的 JSON 参数确定性编译为 SAM Python,而不是让 LLM自由编写生产脚本
  • 理解 SSE Tool Call 分片重组、Provider 兼容、执行凭据和结果回读
  • 区分“单次执行”“精确一次”“幂等”和“事务原子性”,避免给系统不存在的安全保证
  • 将 SAM Agent 的经验抽象为可复用的领域 Agent 架构

第0部分:先回答核心问题——为什么普通 Function Calling 不够? ​

0.1 标准 Function Calling 默认“调用后立即执行” ​

常见 Agent 循环如下:

python
while True:
    response = llm.chat(messages, tools=tools)

    if not response.tool_calls:
        return response.text

    for call in response.tool_calls:
        result = execute_tool(call.name, call.arguments)
        messages.append(tool_result(call.id, result))
1
2
3
4
5
6
7
8
9

它隐含了一个重要语义:

text
LLM 输出 Tool Call
        ↓
宿主程序立即执行
        ↓
执行结果成为下一轮 Observation
1
2
3
4
5

这种模式非常适合搜索、读取文件、查询数据库和获取天气,因为工具结果本身就是下一步推理需要的信息。一次调用失败,Agent 可以根据错误修正参数后继续。

但对于 CAE 建模,这种立即执行会产生三个问题。

第一,很多操作本来属于同一个工程意图:

text
复制全部单元
→ 合并重合节点
→ 删除重复单元
→ 清理自由节点
→ 只读审计
1
2
3
4
5

如果每一步收到 Tool Call 就立即修改 SAM,系统会在完整方案尚未形成时开始改变模型。

第二,SAM Python 具有真实副作用。脚本执行到第三步时报错,前两步可能已经改变内存中的模型。普通 ReAct 的“失败后再试一次”可能把同一个副作用重复施加。

第三,LLM 并不可靠地掌握某个具体 SAM 版本的 API 签名。让模型直接生成:

python
part.Element(nodes=(n1, n2, n3, n4), elemShape=QUAD)
1

意味着它必须同时正确处理 Python 版本、模块导入、SAM 枚举、节点对象、目标 Part、调用顺序和平台差异。任何一项猜错都会让整段脚本失败。

0.2 领域 Agent 需要把“决定做什么”和“知道怎么做”分开 ​

SAM Agent 最终形成的边界是:

text
LLM:理解用户意图,形成规格,填写领域参数
Controller:决定允许哪些操作、按什么顺序、何时执行
Compiler:把领域参数转换为已验证的 SAM Python
SAM:执行脚本并返回真实证据
1
2
3
4

这里的 Tool Call 不再等同于一次立即执行的函数调用。它更接近一条领域操作提案,或者一段待编译的领域中间表示。


第1部分:模式定义——Human-Gated Deterministic Tool Compilation ​

1.1 名称与定义 ​

本章把这种模式称为:

Human-Gated Deterministic Tool Compilation
人工确认的确定性工具编译模式

它包含三个关键约束:

  1. Human-Gated:自然语言意图先变成工程规格,用户确认后才能进入有副作用阶段。
  2. Deterministic Tool Compilation:LLM 只输出结构化领域参数,本地编译器确定性生成平台脚本。
  3. Deferred Execution:Tool Call 先收集和校验,不在每次模型返回后立即执行;全部收齐后再组合提交。

完整链路是:

text
用户请求
   │
   ▼
只读观察 SAM 当前状态
   │
   ▼
Design:生成中文工程规格,不发送工具
   │
   ▼
用户确认规格
   │
   ▼
Route:确定本次允许的模块与能力
   │
   ▼
Collect:一轮或多轮收集 Tool Call 参数,不执行
   │
   ▼
Validate + Compile:本地校验并生成脚本片段
   │
   ▼
Compose:按固定阶段组合成一个 Python 脚本
   │
   ▼
Execute:SAM 只接收一次脚本执行请求
   │
   ▼
Readback + Verify:回读真实模型并验证证据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

1.2 它不是标准 ReAct ​

维度ReAct普通 Plan-and-Execute本模式
规划边执行边规划先规划先形成可确认工程规格
Tool Call 后是否立即执行是通常是否,先收集
下一轮 LLM 是否看到工具结果是可能看到参数收集阶段看不到执行结果
生产代码由谁生成工具实现或 LLM工具实现或 LLM本地领域编译器
执行次数多次多次一次组合脚本提交
适合场景探索和诊断固定业务流程高副作用、强 API 约束领域

本模式不是要取代 ReAct。它只是在“中间状态不应随意暴露给模型反复修改”的任务上,把执行权收回到 Controller。

1.3 五个必须区分的对象 ​

text
Design Spec       用户可读、可确认的工程规格
Tool Call         LLM 提交的结构化操作提案
Domain IR         经过解析和规范化的领域中间表示
Generated Script  本地编译器生成的平台代码
Execution Receipt SAM 执行和回读产生的事实证据
1
2
3
4
5

它们不能混为一谈:

  • 设计写得正确,不代表 Tool Call 参数正确。
  • Tool Call 符合 Schema,不代表业务语义正确。
  • 脚本生成成功,不代表脚本执行成功。
  • 脚本返回 PASS,也不代表目标模型确实发生了预期变化。

第2部分:总体架构——模型只占系统的一层 ​

2.1 六层架构 ​

text
┌──────────────────────────────────────────────────────────────┐
│ UI / Human Gate                                              │
│ 选择 Part/Property/Mesh、冻结 Model/Part、展示规格、确认执行 │
└──────────────────────────┬───────────────────────────────────┘
                           ▼
┌──────────────────────────────────────────────────────────────┐
│ DirectAgentController                                        │
│ 状态机、路由、轮次控制、顺序校验、收集、编译调度、验证       │
└──────────────┬───────────────────────────────┬───────────────┘
               ▼                               ▼
┌──────────────────────────┐      ┌────────────────────────────┐
│ Provider Adapter         │      │ Tool Catalog + Schemas     │
│ Prompt、HTTP、SSE 重组   │      │ 能力映射、最小工具披露     │
└──────────────┬───────────┘      └──────────────┬─────────────┘
               ▼                                  ▼
┌──────────────────────────────────────────────────────────────┐
│ LLM                                                          │
│ Design 阶段输出规格;Script 阶段输出 Tool Calls              │
└──────────────────────────┬───────────────────────────────────┘
                           ▼
┌──────────────────────────────────────────────────────────────┐
│ Domain Tool Compilers                                       │
│ JSON → normalized arguments → preflight/mutation/postflight  │
└──────────────────────────┬───────────────────────────────────┘
                           ▼
┌──────────────────────────────────────────────────────────────┐
│ SAM Gateway + Embedded Runtime                               │
│ 执行一次组合脚本、写回执、回读 Model/Part 状态               │
└──────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

2.2 每层拥有的决定权 ​

层可以决定不应该决定
UI当前模式、目标对象、是否确认SAM API 细节
LLM意图翻译、规格表达、工具参数目标绑定、真实执行时机
Schema字段、类型、枚举、基本结构跨字段业务真值
Tool Catalog可见工具、固定能力顺序用户是否确认
Controller状态转换、调用顺序、何时提交自由猜测工程参数
Compiler平台 API、规范化、预检、代码生成擅自扩展用户意图
SAM Gateway传输和执行判断设计是否合理
Readback Verifier判断证据是否完整用预期结果冒充真实结果

这张表是整个架构的核心:可靠性来自明确分权,而不是来自一段越来越长的 System Prompt。


第3部分:交互是怎么被控制的——Controller 状态机 ​

3.1 显式状态 ​

当前实现使用以下状态:

cpp
enum class AgentPhase {
    Idle,
    Observing,
    Designing,
    Review,
    GeneratingScript,
    Executing,
    Complete,
    Failed
};
1
2
3
4
5
6
7
8
9
10

状态转换如下:

text
Idle / Complete / Failed
          │ submit
          ▼
      Observing
          │ observationReady
          ▼
      Designing
          │ designReady
          ▼
        Review
          │ confirm
          ▼
   GeneratingScript
          │ calls collected + compiled
          ▼
       Executing
          │ receipt + evidence valid
          ▼
       Complete

任一阶段发生不可恢复错误 → Failed
工具参数或顺序错误        → 返回 Review,让用户重新确认或重试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

3.2 为什么状态机比 Prompt 更重要 ​

Prompt 可以告诉模型“设计阶段不要调用工具”,但真正的保证来自请求体:Design 阶段根本不把 tools 字段发给 Provider。

Prompt 可以告诉模型“没有确认不要执行”,但真正的保证来自 Controller:只有 phase == Review 且 confirm() 被调用后,才能进入 GeneratingScript。

Prompt 可以告诉模型“不要改变目标 Part”,但真正的保证来自冻结目标:Review 阶段如果 Model、Part 或对象模式改变,Controller 会让本次运行失败,要求重新观察和重新设计。

这体现了 Harness 的基本原则:

Prompt 提供行为引导;Controller 提供不可绕过的状态边界。

3.3 submit:先观察,不直接问模型 ​

submit() 主要完成:

  1. 拒绝在 Observing / Designing / GeneratingScript / Executing 中重入。
  2. 验证 Provider、SAM Gateway、History 是否可用。
  3. 冻结用户请求、Model、Part 和 Part/Property/Mesh 模式。
  4. 创建唯一 runId 和运行目录。
  5. 保存 request.txt。
  6. 进入 Observing,请求 SAM 返回只读工作区状态。

为什么不直接让 LLM 规划?因为“当前 Part 是空的还是已有结构”“有哪些 Set”“节点与单元数量是多少”不能依靠对话历史猜测。

3.4 Observation:事实必须来自平台 ​

观察回执至少承担两个作用:

  • 验证目标 Model 是否真实存在;
  • 为路由和设计提供当前 Part 摘要。

观察通过后才进入 Designing。这意味着 LLM 收到的是:

json
{
  "tool_module": "Part",
  "target": {
    "model_name": "Model-1",
    "part_name": "Part-1",
    "exists": true
  },
  "observation": {
    "summary": {
      "node_count": 6,
      "element_count": 2
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

3.5 Design:无工具的规格阶段 ​

Design 阶段只允许输出中文工程规格:

  • 用户想创建、增加、删除、变换、修复还是审计什么;
  • 使用哪些尺寸、坐标、区域和选择条件;
  • 哪些是假设,哪些是当前能力边界;
  • 需要哪些能力,按什么顺序。

请求体中不包含 Tool Schema,因此模型即使想调用工具也没有正式的 Tool Call 通道。

3.6 Review:人类确认的是语义,不是 JSON ​

用户确认的对象是可读工程规格,而不是数百行 JSON Schema。这一层解决的是:

  • 尺寸是否正确;
  • 坐标系是否正确;
  • 是追加还是替换;
  • 是全部单元还是某个区域;
  • 是否需要清理和审计。

确认并不证明后续参数一定正确,它只是冻结了允许实现的意图范围。

3.7 confirm:打开执行阶段,但仍不执行 SAM ​

确认时 Controller:

text
confirmed = true
collectedToolCalls = []
providerToolRound = 1
phase = GeneratingScript
发送第一轮 Script 请求
1
2
3
4
5

这里特别重要:confirm() 只是允许模型开始提交工具参数,并没有立即修改 SAM。


第4部分:Schema 的真实作用——限制表达空间,而不是替代控制器 ​

4.1 Tool Schema 由三部分组成 ​

json
{
  "type": "function",
  "function": {
    "name": "build_part",
    "description": "把自然语言几何映射为节点和单元……",
    "parameters": {
      "type": "object",
      "additionalProperties": false,
      "required": ["nodes", "elements"],
      "properties": {}
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

三部分分别承担不同职责:

  • name:让模型区分能力。
  • description:告诉模型何时使用、如何把自然语言映射到字段、有哪些禁止事项。
  • parameters:限制 JSON 的字段、类型、数组和枚举。

4.2 build_part:局部 ID,不暴露真实 SAM 标签 ​

简化后的 Schema 是:

json
{
  "nodes": [
    {"id": "N1", "coordinates": [0, 0, 0]},
    {"id": "N2", "coordinates": [60, 0, 0]},
    {"id": "N3", "coordinates": [60, 60, 0]},
    {"id": "N4", "coordinates": [0, 60, 0]}
  ],
  "elements": [
    {
      "id": "E1",
      "shape": "QUAD",
      "node_ids": ["N1", "N2", "N3", "N4"]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

设计中的关键点不是字段名称,而是 ID 语义:

text
N1 / E1 = 本次 Tool Call 内的局部别名
SAM label = 平台运行后真实生成的正整数标签
1
2

LLM 不需要也不应该猜测 SAM 将分配什么标签。Compiler 在执行时把局部关系解析成真实节点对象。

4.3 edit_part:nodes 是坐标解析表,不是强制创建表 ​

这是实际开发中最重要的一次 Schema 语义修正。

用户要求利用两个已有坐标增加 TRI:

text
(0,0,500)
(1000,0,500)
(500,500,500)
1
2
3

正确 Tool Call 仍然必须列出三个坐标:

json
{
  "nodes": [
    {"id": "n1", "coordinates": [0, 0, 500]},
    {"id": "n2", "coordinates": [1000, 0, 500]},
    {"id": "n3", "coordinates": [500, 500, 500]}
  ],
  "add_elements": [
    {
      "id": "e1",
      "shape": "TRI",
      "node_ids": ["n1", "n2", "n3"]
    }
  ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Compiler 的语义是:

python
for coordinate in requested_coordinates:
    if surviving_node_exists_at(coordinate):
        reuse_node()
    else:
        create_one_node()
1
2
3
4
5

如果只提供新节点 n3,却让单元引用 n1/n2/n3,Schema 可能仍然是合法 JSON,但跨字段引用不成立。这个错误必须由 Compiler 的语义校验发现。

4.4 Schema 能做什么 ​

Schema 擅长约束:

  • coordinates 必须是三个数字;
  • shape 只能是 QUAD / TRI / BEAM;
  • 标签必须是大于等于 1 的整数;
  • levels 只能是 1 到 3;
  • 不允许未知字段;
  • 必填字段不能缺失;
  • 数组是否至少包含一项。

4.5 Schema 不能证明什么 ​

仅靠 JSON Schema 很难证明:

  • QUAD 恰好引用四个不同坐标节点;
  • node_ids 都能在同一次调用的 nodes 中解析;
  • Set 引用的元素确实由同一次调用创建;
  • 删除节点时依赖单元应该如何级联删除;
  • clear_all=true 是否真的来自用户明确的“删除全部”意图;
  • Section 类型是否与被选元素类型匹配;
  • 梁方向 n1 是否与梁轴平行;
  • 两个工具的选择区域是否相互冲突;
  • Tool Call 的顺序是否与确认规格一致。

因此需要三层验证:

text
JSON Schema          结构合法性
Domain Compiler      领域语义合法性
Readback Verifier    平台执行事实合法性
1
2
3

4.6 Tool description 不是注释,而是模型操作手册 ​

当前工具描述中不仅写 API 能力,还写了:

  • “平面板”如何映射为 QUAD;
  • “梁/杆”如何映射为 BEAM;
  • 共享坐标必须复用;
  • 哪些 ID 是局部 ID,哪些是 SAM 标签;
  • 正确和错误 TRI 示例;
  • 未明确要求时不得生成 Set;
  • clear_all 只能用于显式整体替换。

这解释了为什么“只把 API 封装成 Tool”还不够。模型面对的不是 C++ 实现,而是 Tool Schema 与 description。它们共同构成模型的领域操作手册。


第5部分:工具不是一次全部发送——Tool Catalog 与渐进披露 ​

5.1 一级路由由 UI 决定 ​

用户先在界面选择:

text
Part | Property | Mesh
1

这一选择冻结为 AgentObjectMode。LLM 不需要在所有 CAE 模块中猜测当前任务属于什么。

这是一个重要设计原则:

如果可靠的界面状态已经知道路由结果,就不要再让 LLM 重做一次分类。

5.2 二级路由由确认规格决定 ​

当前能力集合是:

text
Part:     build, edit, transform, repair, audit
Property: define, assign, audit
Mesh:     refine, audit
1
2
3

Tool Catalog 把能力名映射为 Wire Tool Name:

text
build     → build_part
edit      → edit_part
transform → transform_part
repair    → repair_part
audit     → audit_part

define    → define_property
assign    → assign_property
refine    → refine_mesh
1
2
3
4
5
6
7
8
9

5.3 空 Part 与已有 Part 的路由不同 ​

Part 模块结合 Observation 判断:

text
目标不存在或节点数=0且单元数=0 → build_part
目标已有结构                    → edit/transform/repair/audit
1
2

build_part 不能和已有 Part 的操作组合。这不是 LLM 偏好,而是 Controller/Compiler 的硬规则。

5.4 每一轮只发送剩余工具 ​

假设确认规格需要:

text
transform_part → repair_part → audit_part
1

第一轮上下文包含全部确认能力,Tool Catalog 发送剩余工具。若 Provider 只返回 transform_part,Controller 不执行它,而是记录:

text
collected = [transform_part]
remaining = [repair_part, audit_part]
1
2

第二轮只暴露剩余工具。这样既减少 Schema Token,也降低模型重复调用已经收集工具的概率。

5.5 显式路由标记与语义回退 ​

早期实现要求设计最后一行必须严格包含 capability line,例如:

text
计划 Mesh 操作类别:refine, audit
1

严格格式曾导致合法规格被阻断:

text
Confirmed design must contain exactly one Mesh capability line
1

后续 Property 和 Mesh 增加了语义回退:

  • 有合法显式标记时,以标记为准;
  • 标记遗漏时,从规格中的明确操作语义推断;
  • 如果仍无法识别,才拒绝路由。

这里的经验是:

自然语言中的格式标记可以提高确定性,但不应该成为唯一安全边界。真正的边界应由工具白名单、参数校验和执行控制建立。


第6部分:多轮 Tool Call 是如何被收集而不执行的 ​

6.1 Controller 保存两组状态 ​

概念上应区分:

text
expectedTools       确认规格要求的完整工具序列
collectedToolCalls  Provider 已提交但尚未执行的调用
1
2

每轮返回后,Controller 计算:

cpp
collectedCount = collectedToolCalls.size();
remainingCount = expectedTools.size() - collectedCount;
1
2

然后逐项校验:

cpp
expectedIndex = collectedCount + responseIndex;

if (call.name != expectedTools[expectedIndex])
    return_to_review();

collectedToolCalls.append(call);
1
2
3
4
5
6

6.2 顺序由 Controller 控制 ​

Provider 不能把:

text
transform → repair → audit
1

擅自改成:

text
audit → transform → repair
1

因为后者的审计发生在修改之前,语义完全不同。Controller 对工具名称、位置和数量进行校验。

6.3 为什么允许多轮收集 ​

一些 Provider 在一次响应中不能稳定返回多个工具,或者模型只愿意返回第一个调用。因此 Controller 接受:

text
本轮返回调用数 >= 1
本轮返回调用数 <= 剩余调用数
1
2

只要顺序正确,就继续收集。如果还没有收齐,再发下一轮 Script 请求。

6.4 “collected”绝不能写成“completed” ​

当前实现已经使用 collectedToolCalls 保存真实状态,这是正确方向。但传给 Provider 的兼容字段仍名为:

json
"completed_part_tools": ["transform_part"]
1

这个命名会诱导模型说:

transform_part 已经完成,现在执行 repair_part。

事实上它尚未进入 SAM。更准确的命名应该是:

json
"collected_tool_calls": ["transform_part"]
1

同理,当前 ExecutedPartToolCall 在脚本提交前就被填充,它实际更接近:

text
CompiledToolCall / PlannedToolCall
1

状态命名不是小事。错误命名会同时污染:

  • Provider Prompt;
  • UI 文案;
  • 日志解释;
  • 结果验证;
  • 后续维护者对执行时机的理解。

6.5 收齐之前零副作用 ​

这是本模式最重要的不变量:

text
collectedToolCalls.size() < expectedTools.size()
        ⇒ 不调用 sam.executeScript()
1
2

只有全部调用收齐、JSON 解析成功、所有 Compiler 校验通过、脚本组合成功并写入历史后,Controller 才会进入 Executing。


第7部分:Provider Adapter——兼容模型差异,而不是假设 API 一致 ​

7.1 Design 与 Script 使用不同请求体 ​

Design 请求:

json
{
  "model": "...",
  "stream": true,
  "temperature": 0.1,
  "messages": ["system", "user"]
}
1
2
3
4
5
6

Script 请求才增加:

json
{
  "tools": ["当前模块、当前轮次真正需要的 Tool Schema"]
}
1
2
3

这比在一个巨大的 System Prompt 中反复声明“现在不能调用工具”可靠。

7.2 为什么没有强制 tool_choice ​

实际接入 DeepSeek thinking 模式时曾出现:

text
Thinking mode does not support this tool_choice
1

因此当前策略是不向 Provider 强行发送不兼容的 tool_choice,而把约束放到本地:

  • 只发送允许的工具;
  • Prompt 要求每个剩余工具调用一次;
  • Controller 校验工具名称、顺序和数量;
  • 没有调用或调用错误时返回 Review。

通用经验是:

text
Provider Capability ≠ Agent Invariant
1

Provider 支持某项功能时可以利用;不支持时,Agent 的关键不变量仍应由 Harness 本地执行。

7.3 SSE Tool Call 不是一次完整 JSON ​

流式响应可能把一个调用拆成:

text
chunk 1: index=0, id="call_", name="transform_", args="{\"oper"
chunk 2: index=0, id="123",  name="part",       args="ations\":["
chunk 3: index=0,                                  args="...]}"
1
2
3

Adapter 使用 index 聚合:

cpp
QMap<int, ProviderToolCall> callsByIndex;

ProviderToolCall &call = callsByIndex[index];
call.id += chunk.id;
call.name += chunk.function.name;
call.argumentsJson += chunk.function.arguments;
1
2
3
4
5
6

完成后再按 index 输出完整调用。

7.4 SSE 解析必须验证结束语义 ​

当前解析器检查:

  • 是否至少收到一个 data: 事件;
  • 每个 Tool Call 是否有非负数值 index;
  • 每个 SSE data 是否是合法 JSON;
  • 是否最终收到 [DONE]。

没有 [DONE] 时,即使前面看起来已有完整参数,也不能轻易当成完整响应,因为尾部可能被网络截断。

7.5 UI 重复文本与重复执行是两回事 ​

多轮 Provider 响应会累计到 scriptResponse,界面可能显示:

text
TOOL transform_part
TOOL transform_part
TOOL repair_part
TOOL audit_part
1
2
3
4

这可能来自累计文本重复展示,也可能来自 Provider 在下一轮复述先前调用。判断是否真正重复执行,不能看 UI 文本次数,而要看:

text
parsed tool call index
collectedToolCalls
compiled tool receipts
execution submission count
SAM receipt
1
2
3
4
5

因此可观测性至少要区分五类事件:

text
provider_stream_chunk
provider_tool_call_reconstructed
tool_call_collected
tool_call_compiled
script_execution_submitted
1
2
3
4
5

第8部分:Tool Call 是领域 IR,不是 Python 代码 ​

8.1 为什么不让 LLM 写生产 Python ​

早期自由脚本曾出现:

text
TypeError: String Expected as dictionary Type
NameError: name 'QUAD' is not defined
TypeError: keyword error on dimensionality
1
2
3

这些错误并不是用户几何意图错误,而是模型猜错了运行时边界:

  • 宿主注入值的类型;
  • SAM 枚举导入;
  • Part() 的真实关键字;
  • SAM 与 Abaqus API 的差异。

把这些责任交给 Tool Compiler 后,LLM 只需要表达:

json
{
  "shape": "QUAD",
  "node_ids": ["N1", "N2", "N3", "N4"]
}
1
2
3
4

Compiler 负责生成正确的平台代码:

python
part.Element(
    nodes=(resolved_nodes["N1"], resolved_nodes["N2"],
           resolved_nodes["N3"], resolved_nodes["N4"]),
    elemShape=QUAD,
    intersectNodes=True)
1
2
3
4
5

8.2 Domain IR 的四个特征 ​

一个好的领域 Tool Call 应当:

  1. 使用业务概念,而不是泄漏底层 API 参数。
  2. 使用局部符号或声明式选择器,而不是让模型猜运行时对象。
  3. 能被本地程序完全校验。
  4. 能确定性编译,同一规范化参数生成同一语义脚本。

8.3 编译过程 ​

text
raw argumentsJson
      │ JSON parse
      ▼
QJsonObject
      │ schema + domain validation
      ▼
normalizedArguments
      │ platform mapping
      ▼
ScriptFragment {
  preflight,
  mutation,
  postflight,
  evidence
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

8.4 规范化的意义 ​

规范化参数用于:

  • 统一枚举大小写;
  • 去重标签;
  • 固定坐标精度;
  • 补入安全默认值;
  • 删除未被用户明确要求的字段;
  • 记录真正进入编译器的参数。

例如用户没有明确要求 Set 时,Controller 会移除模型擅自生成的 element_sets。clear_all=true 还必须通过原始请求中的显式整体删除意图检查。

这说明 Controller 不只验证 JSON,它还把“用户授权范围”与“模型输出”做交叉验证。


第9部分:脚本组合——Preflight、Mutation、Postflight ​

9.1 每个工具编译为片段 ​

Tool Compiler 不立即执行,而是返回:

cpp
struct ScriptFragment {
    QString toolName;
    QStringList preflight;
    QStringList mutation;
    QStringList postflight;
    QJsonObject normalizedArguments;
    QJsonObject evidence;
};
1
2
3
4
5
6
7
8

9.2 组合器按阶段重排 ​

PartScriptProgram::compose() 的结构是:

text
固定导入与目标绑定
→ 所有工具的 preflight
→ 所有工具的 mutation
→ 所有工具的 postflight
→ viewport refresh
→ completion marker
1
2
3
4
5
6

这比简单拼接:

text
tool1 全部代码
tool2 全部代码
tool3 全部代码
1
2
3

更安全,因为能在任何修改发生前尽可能完成全局预检。

9.3 目标绑定由宿主注入 ​

生成脚本使用:

python
model_name = SAM_AGENT_TARGET['model_name']
part_name = SAM_AGENT_TARGET['part_name']
1
2

Tool Schema 不允许模型传 model_name 和 part_name。这避免:

  • 模型修改错误 Part;
  • 用户规格与执行目标漂移;
  • Tool Call 注入另一个模型名称。

9.4 Property 的额外不变量 ​

Property 组合脚本在执行前记录节点、单元数,执行后检查:

python
if len(part.nodes) != before_nodes or len(part.elements) != before_elements:
    raise RuntimeError('Property workflow changed Part geometry')
1
2

这是一条很好的模块边界:Property 模式可以修改材料、Section 和分配,但不能改变几何拓扑。

9.5 一次执行不等于数据库事务 ​

组合脚本只调用一次 sam.executeScript(),它具有以下价值:

  • 避免工具逐个往返 SAM;
  • 固定执行顺序;
  • 统一保存生成脚本;
  • 在修改前执行更多预检;
  • 只产生一次执行提交记录。

但必须明确:

text
Single Submission ≠ Atomic Transaction
1

如果 Mutation 第三步抛异常,前两步的内存修改仍可能保留。除非 SAM 提供事务、快照恢复或在副本上执行,否则不能宣称具备 rollback。


第10部分:完成的定义——不是“脚本没报错” ​

10.1 执行回执的第一层检查 ​

SAM Gateway 返回 receipt。Controller 首先检查:

text
receipt.status == PASS
1

失败时优先展示 traceback,其次展示 error。

10.2 Readback Evidence ​

执行成功后,Controller 检查目标是否仍存在,并读取:

  • 节点数;
  • 单元数;
  • QUAD/TRI/BEAM 数量;
  • Set 列表;
  • Property 摘要;
  • Audit 报告文件。

例如 edit_part 会根据执行前状态、删除数量、新增坐标数和新增元素数,计算合理范围,再与真实回读对比。

10.3 为什么使用范围而不是始终精确相等 ​

删除节点可能级联删除依赖单元;重复坐标会被复用;删除和新增可能相互影响。因此某些操作的预期结果只能表达为:

text
minimum <= actual <= maximum
1

这比简单要求 actual == before + added - deleted 更符合真实拓扑操作。

10.4 Audit Artifact 也是证据 ​

如果调用了 audit_part,回执中必须能找到 part_audit.json;如果调用了 Property Audit,则必须有 property_audit.json。

这防止出现:工具名被记录为已执行,但实际脚本没有生成报告的假完成状态。

10.5 建议使用更精确的状态词汇 ​

text
Proposed   LLM 已提出调用
Collected  Controller 已收集调用
Compiled   本地编译成功
Submitted  脚本已提交 SAM
Executed   SAM 返回执行成功
Verified   回读证据符合预期
1
2
3
4
5
6

只有 Verified 才适合向用户显示“完成”。


第11部分:错误如何分类,Controller 如何响应 ​

11.1 六类错误 ​

错误层示例推荐动作
ObservationModel 不存在停止,要求修正目标
Design无法识别能力回到需求/规格阶段
Provider ProtocolSSE 缺 [DONE]失败,可安全重试 Provider
Tool Call工具顺序错误、参数非 JSON返回 Review,不执行 SAM
Compile未知节点 ID、类型冲突返回 Review,不执行 SAM
ExecutionSAM Python traceback标记失败,提示可能部分修改
Verification执行 PASS 但回读证据缺失标记失败,不宣称完成

11.2 参数错误为什么返回 Review ​

工具参数或顺序错误发生时,SAM 尚未执行,因此可以安全地:

text
confirmed = false
phase = Review
展示明确错误
1
2
3

这是一个有界恢复点。系统不应该无限让模型在后台自动重试,因为多次不透明重试会消耗 Token,也会让用户不知道规格是否被改变。

11.3 运行错误不能盲目重试 ​

执行阶段失败后,模型可能已经部分改变。正确恢复流程应是:

text
停止自动重试
→ 重新观察 SAM 当前真实状态
→ 生成修复规格
→ 用户确认
→ 新一轮编译与执行
1
2
3
4
5

这叫人工确认的有界修复循环,而不是自主 ReAct 修复。


第12部分:真实失败案例如何推动架构演进 ​

12.1 自由 Python:平台 API 猜测失败 ​

错误包括:

text
String Expected as dictionary Type
QUAD is not defined
keyword error on dimensionality
1
2
3

教训:LLM 不应承担目标绑定、模块导入、SAM 枚举和 API 签名。

改进:把自然语言翻译为 Tool Call,由本地 Compiler 生成生产 Python。

12.2 build_part 能创建,edit_part 却无法复用已有节点 ​

早期 edit_part 把局部 ID 与 SAM 标签混淆,导致:

text
Element references an unknown added node id
1

教训:局部引用必须有完整的坐标解析表;“列出节点”不应等同于“强制创建节点”。

改进:nodes 定义为 coordinate-resolution entries,已有坐标复用,缺失坐标才创建。

12.3 模型擅自生成 Set ​

用户只要求增加 TRI,模型却附带 element_sets,并引用已有或不存在元素。

教训:Tool description 必须解释“用户不会说 Set”并不代表应该自动创建 Set;Controller 还要对照原始用户意图移除未授权字段。

12.4 多工具计划只返回第一个 Tool Call ​

系统曾要求 Provider 一次返回 transform/repair/audit,Provider 只调用 transform,Controller 报错。

教训:不能把“模型必须一次返回全部工具”当成 Provider 能力保证。

改进:允许多轮参数收集,但执行仍延迟到全部收齐以后。

12.5 tool_choice 与 Provider thinking 模式冲突 ​

教训:OpenAI 风格接口不代表所有 Provider 支持完全相同的行为参数。

改进:取消不兼容的强制 tool_choice,用最小工具披露和本地顺序校验实现同一不变量。

12.6 严格 capability line 阻断正确请求 ​

教训:把自然语言输出格式当成唯一协议,会让形式正确性压过任务正确性。

改进:显式标记优先,明确语义回退,最终仍由 Tool 白名单约束。

12.7 refine_mesh 使用字符串而非枚举 ​

运行时报错:

text
found string, expecting enum
1

教训:Schema 中的 "QUAD" 是领域 IR 字符串,不能原样当作 SAM API 枚举。

改进:Compiler 负责 "QUAD" → QUAD 的平台映射。


第13部分:事务、幂等和部分执行风险 ​

13.1 四个容易混淆的概念 ​

概念含义当前实现
单次提交Controller 只调用一次执行网关已实现
精确一次同一运行不会被重复提交部分实现,需要持久化幂等键加强
幂等重复执行产生同一最终状态不保证,部分操作天然非幂等
原子事务全部成功或全部回滚未实现

13.2 哪些操作不是幂等的 ​

text
translate_copy  重复执行会多复制一份
linear_array    重复执行会继续增加元素
refine_mesh     重复执行会再次细分
createNode      无坐标复用保护时会产生重复节点
1
2
3
4

因此网络超时后不能简单地“再执行一次”。必须先查询当前状态或使用运行 ID 确认上一次是否已提交。

13.3 推荐的事务增强路线 ​

从低成本到高成本:

  1. 执行前全局 Preflight:尽量在 Mutation 前发现错误。
  2. Execution Idempotency Key:以 runId + script hash 防止重复提交。
  3. 执行前快照:保存临时 .sam 或复制目标 Part。
  4. 影子 Part 执行:在临时 Part 上修改,验证后替换。
  5. 平台事务 API:如果 SAM 将来提供 begin/commit/rollback,映射为真正事务。

在没有快照或事务的情况下,UI 必须诚实提示:运行失败可能留下部分修改。


第14部分:大型 CAE 模型的 Context Engineering ​

14.1 全量 Observation 不可扩展 ​

一个几十万节点、几十万元素的模型如果把所有坐标和连接关系塞入 Prompt,会造成:

  • 输入 Token 爆炸;
  • Provider 请求截断;
  • 设计响应被挤压;
  • 模型注意力分散;
  • 敏感工程数据暴露范围扩大。

14.2 分层观察模型 ​

推荐四层:

text
L0 Workspace Index
   模型、Part 名称和存在性

L1 Part Summary
   节点/单元数、包围盒、类型计数、Set/Section 名称

L2 Region Index
   分区、站位、空间盒、连通分量摘要

L3 On-Demand Detail
   指定标签、Set、坐标范围内的节点与单元
1
2
3
4
5
6
7
8
9
10
11

LLM 默认只拿 L0/L1。需要局部操作时,通过只读查询能力取得 L2/L3,而不是把全模型提前注入。

14.3 Observation 也需要 Schema ​

不仅 Tool Call 需要 Schema,Observation 也应该稳定:

json
{
  "target_exists": true,
  "summary": {
    "node_count": 188630,
    "element_count": 176022,
    "bounding_box": {
      "minimum": [0, -5000, 0],
      "maximum": [32634, 5000, 8000]
    },
    "shape_counts": {
      "QUAD": 170000,
      "TRI": 6022,
      "BEAM": 0
    }
  },
  "sets": ["HULL", "DECK", "LEFT_END", "RIGHT_END"]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

稳定 Observation Schema 能提高 Prompt 缓存、日志对比和 Controller 验证的可靠性。


第15部分:从重复分支演进为通用模块协议 ​

15.1 当前问题 ​

Controller 中 Part、Property、Mesh 有大量相似逻辑:

  • 解析设计能力;
  • 生成 Tool 定义;
  • 校验调用顺序;
  • 解析参数;
  • 编译 Fragment;
  • 组合脚本;
  • 验证回执。

如果继续增加 Assembly、Load、Boundary、Step 和 Job,分支会快速膨胀。

15.2 推荐模块协议 ​

cpp
class DomainModule {
public:
    virtual QString designPrompt() const = 0;
    virtual RouteResult routeCapabilities(
        const QString &design,
        const QJsonObject &observation) const = 0;
    virtual QJsonArray toolDefinitions(
        const QStringList &remainingCapabilities) const = 0;
    virtual CompileResult compileToolCalls(
        const QList<ProviderToolCall> &calls,
        const DirectTarget &target,
        const QJsonObject &observation) const = 0;
    virtual QString verifyReceipt(
        const CompileReceipt &expected,
        const QJsonObject &actual) const = 0;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Controller 只保留通用流程:

text
observe
→ request design
→ route
→ review
→ collect
→ module.compile
→ execute
→ module.verify
1
2
3
4
5
6
7
8

15.3 哪些规则仍应留在模块内 ​

  • Part:空/已有几何路由、坐标复用、拓扑删除。
  • Property:材料与 Section 引用、壳/梁分配、几何不变性。
  • Mesh:细分合法性、Section 继承、共享中点。
  • Load:载荷类型、区域、步关联和单位。
  • Boundary:自由度、参考点、耦合和冲突检查。

通用 Controller 不应该知道 QUAD 有四个节点,也不应该知道壳 SectionAssignment 的替换规则。


第16部分:可观测性——记录“发生了什么”,而不是只存最终文本 ​

16.1 当前运行证据 ​

一次运行目录可包含:

text
request.txt
workspace.json
design.txt
design_route.json
provider_tool_response.sse
provider_tool_response.2.sse
script_response.txt
tool_calls.json
generated_script.py
result.json
part_audit.json / property_audit.json / mesh_audit.json
1
2
3
4
5
6
7
8
9
10
11

这些文件让问题可以沿链路定位:

text
用户是否表达错
→ 设计是否翻译错
→ 路由是否选错
→ Provider 是否填错参数
→ Compiler 是否生成错脚本
→ SAM 是否执行失败
→ Readback 是否证据不足
1
2
3
4
5
6
7

16.2 推荐事件模型 ​

json
{
  "run_id": "...",
  "event": "tool_call_compiled",
  "module": "Mesh",
  "tool": "refine_mesh",
  "tool_index": 0,
  "provider_round": 1,
  "script_hash": "...",
  "timestamp": "..."
}
1
2
3
4
5
6
7
8
9
10

建议事件至少包括:

  • observation_completed
  • design_received
  • design_confirmed
  • tool_call_reconstructed
  • tool_call_collected
  • tool_call_rejected
  • tool_call_compiled
  • script_composed
  • execution_submitted
  • execution_completed
  • readback_verified

16.3 UI 文案必须对应真实状态 ​

text
已收到参数   ≠ 已执行
脚本已生成   ≠ 已提交
SAM 返回成功 ≠ 已验证
1
2
3

建议界面分别显示:

text
正在收集 2/3 个操作参数
正在编译已确认操作
脚本已提交 SAM
SAM 执行成功,正在回读验证
验证完成
1
2
3
4
5

第17部分:可复用 Controller 骨架 ​

下面的伪代码展示模式本身,而不是某个框架 API:

python
class DeterministicDomainController:
    def submit(self, request, target, module):
        self.assert_not_busy()
        self.run = RunContext(
            request=request,
            frozen_target=target,
            module=module,
            phase="OBSERVING",
        )
        self.sam.observe(target)

    def on_observation(self, observation):
        self.verify_observation(observation)
        self.run.observation = observation
        self.run.phase = "DESIGNING"

        # Design 阶段不提供 tools
        self.provider.request(
            prompt=self.module.design_prompt(),
            context=self.design_context(),
            tools=None,
        )

    def on_design(self, design):
        route = self.module.route_capabilities(
            design, self.run.observation
        )
        self.assert_route_valid(route)
        self.run.design = design
        self.run.expected_tools = route.ordered_tools
        self.run.phase = "REVIEW"

    def confirm(self):
        self.assert_phase("REVIEW")
        self.run.confirmed = True
        self.run.collected_calls = []
        self.run.phase = "COLLECTING"
        self.request_remaining_calls()

    def request_remaining_calls(self):
        remaining = self.run.expected_tools[
            len(self.run.collected_calls):
        ]
        self.provider.request(
            prompt=self.module.tool_prompt(),
            context=self.script_context(remaining),
            tools=self.module.definitions(remaining),
        )

    def on_tool_calls(self, calls):
        self.validate_names_order_and_count(calls)
        self.run.collected_calls.extend(calls)

        if len(self.run.collected_calls) < len(self.run.expected_tools):
            self.request_remaining_calls()
            return

        # 到此之前没有执行任何领域修改
        compiled = self.module.compile_all(
            calls=self.run.collected_calls,
            target=self.run.frozen_target,
            observation=self.run.observation,
            original_request=self.run.request,
        )
        self.persist(compiled.normalized_calls)
        self.persist(compiled.script)

        self.run.phase = "EXECUTING"
        self.sam.execute_once(
            target=self.run.frozen_target,
            script=compiled.script,
            idempotency_key=self.run.id,
        )

    def on_execution_receipt(self, receipt):
        self.assert_execution_passed(receipt)
        self.module.verify_receipt(
            expected=self.run.compiled_evidence,
            actual=receipt,
            before=self.run.observation,
        )
        self.run.phase = "COMPLETE"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

17.1 Controller 的核心不变量 ​

实现时可以把以下规则写成断言或状态检查:

text
I1  Design 请求不得包含 tools
I2  未确认不得进入 Collecting
I3  Review 后 target/module 不得漂移
I4  只能调用本次路由允许的工具
I5  Tool Call 顺序必须与 expectedTools 一致
I6  未收齐全部调用不得执行 SAM
I7  所有调用必须先编译成功再组合
I8  同一 runId 最多提交一次执行
I9  SAM PASS 后仍必须完成 readback verification
I10  Execution 失败不得自动盲重试副作用脚本
1
2
3
4
5
6
7
8
9
10

第18部分:何时应该使用这种模式 ​

18.1 适合 ​

  • CAE/CAD 模型修改;
  • 数据库 Schema 迁移;
  • PLC/工业控制配置;
  • 云基础设施变更;
  • 财务批处理;
  • 多步骤但应统一提交的文档或配置变更;
  • 平台 API 复杂、模型容易猜错签名的领域系统。

共同特征是:

text
修改有副作用
+ 操作之间有顺序
+ 中间状态不适合交给 LLM 自由探索
+ 底层 API 应由确定性代码掌握
1
2
3
4

18.2 不适合 ​

  • 搜索和信息检索;
  • 需要根据每次工具结果动态决定下一步的诊断;
  • 工具全部只读且失败成本很低;
  • 任务路径本质未知,必须边探索边规划。

这些场景使用 ReAct 更自然。

18.3 混合模式 ​

复杂工程系统可以外层使用本模式,局部只读分析使用 ReAct:

text
只读 ReAct 探索模型
      ↓
形成修改规格
      ↓
用户确认
      ↓
确定性编译与单次提交
      ↓
只读 ReAct 分析审计结果
1
2
3
4
5
6
7
8
9

关键边界是:ReAct 可以自由探索事实,但真正修改必须重新经过确认门。


第19部分:设计检查清单 ​

19.1 Schema ​

  • [ ] 工具参数使用领域概念,而不是暴露底层 API 对象
  • [ ] additionalProperties=false
  • [ ] 枚举、数组长度、数值范围尽量明确
  • [ ] 局部 ID 与平台真实 ID/标签语义分离
  • [ ] Tool description 解释自然语言映射、边界和正确示例
  • [ ] 跨字段引用由 Compiler 校验

19.2 Controller ​

  • [ ] 状态机显式存在,而不是由 UI 文案隐式推断
  • [ ] Design 阶段不发送工具
  • [ ] 用户确认后冻结目标和模块
  • [ ] expected、collected、compiled、executed、verified 状态分离
  • [ ] 多轮调用只发送剩余工具
  • [ ] 工具名称、顺序和数量由本地代码校验
  • [ ] 收齐全部调用之前不产生副作用

19.3 Compiler ​

  • [ ] 所有平台 API 语法由本地代码生成
  • [ ] Schema 字符串枚举转换为真实平台枚举
  • [ ] 全局 Preflight 尽量先于 Mutation
  • [ ] 规范化参数和预期证据可持久化
  • [ ] 编译器不能添加用户未授权的操作

19.4 Execution 与验证 ​

  • [ ] 同一运行具有幂等提交键
  • [ ] 清楚声明是否支持 rollback
  • [ ] 运行错误不自动盲重试
  • [ ] PASS 后回读真实状态
  • [ ] Audit 文件和目标计数被当作执行证据
  • [ ] UI 不把 collected 显示为 completed

19.5 Provider ​

  • [ ] 不假设所有 Provider 支持相同 tool_choice
  • [ ] SSE 按 index 重组 Tool Call
  • [ ] 检查完整结束标志
  • [ ] 原始响应、重组调用和执行事件分开记录
  • [ ] 工具数量按模块和轮次最小化

核心总结 ​

总结1:Tool Call 可以是提案,而不是立即执行 ​

标准 Function Calling 把 Tool Call 解释为“请现在执行这个函数”。高副作用领域可以把它重新解释为:

text
请把这条结构化领域操作加入待提交计划。
1

总结2:Schema 只负责第一道边界 ​

text
Schema           保证结构
Compiler         保证领域语义与平台映射
Controller       保证授权、状态、顺序和执行时机
Readback         保证真实结果
1
2
3
4

任何一层都不能完全替代其他层。

总结3:Controller 是交互的真正导演 ​

模型并不控制完整流程。Controller 决定:

  • 什么时候观察;
  • 什么时候设计;
  • 什么时候等待确认;
  • 发送哪些工具;
  • 还缺哪些调用;
  • 是否允许编译;
  • 是否允许执行;
  • 什么时候才算完成。

总结4:确定性编译器是领域 Agent 的关键边界 ​

LLM 擅长把“60×100 的平面板”翻译成节点和连接关系,但不应该负责记忆 SAM 某版本的枚举、对象类型和函数签名。让模型输出领域 IR,让本地编译器输出平台代码,是当前系统从不稳定走向可用的关键变化。

总结5:单次提交提高可靠性,但不自动获得事务 ​

一次组合脚本能够减少中间往返和顺序漂移,但脚本仍可能部分执行。除非实现快照、影子对象或平台事务,否则必须诚实承认没有原子 rollback。

总结6:最可靠的 Agent 不是校验最多,而是边界最清楚 ​

早期系统的问题并不是“校验不够多”,而是把很多形式校验放在了错误层:强制 capability line、让模型自由写 Python、混淆局部 ID 与真实标签、把已收集说成已完成。

真正有效的约束是:

text
UI 冻结目标
Schema 限制表达
Controller 控制流程
Compiler 掌握 API
SAM 提供事实
Readback 证明结果
1
2
3
4
5
6

章节测试 ​

测试1:模式辨析 ​

为什么本章模式不能简单称为 ReAct?

测试2:Schema 边界 ​

一个 Tool Call 完全符合 JSON Schema,为什么仍然可能被 Compiler 拒绝?请给出两个例子。

测试3:状态语义 ​

collected_tool_calls、compiled_tool_calls 和 executed_tool_calls 有什么区别?为什么不能统一叫 completed tools?

测试4:多轮调用 ​

确认工具序列为 transform → repair → audit,Provider 第一轮只返回 transform。Controller 应该立即执行 transform,还是继续收集?为什么?

测试5:Provider 兼容 ​

Provider 不支持强制 tool_choice 时,如何仍然保证模型只能按顺序调用允许的工具?

测试6:SSE ​

为什么必须按 tool_call.index 重组流式调用,而不能简单把所有 argument 文本拼成一个字符串?

测试7:事务 ​

“Controller 只调用一次 executeScript”能否证明整个修改具有原子性?为什么?

测试8:完成语义 ​

SAM 返回 PASS 后为什么还需要 Readback Verification?

测试9:架构抽象 ​

如果增加 Load 模块,哪些逻辑应该复用通用 Controller,哪些逻辑应该放进 LoadModule?


参考答案 ​

测试1答案 ​

ReAct 在每次 Tool Call 后立即执行工具,并把结果作为 Observation 交给下一轮模型。本模式先生成可确认规格,再多轮收集 Tool Call,全部收齐后才统一编译和执行。参数收集期间没有执行结果返回给 LLM,因此不是标准 Thought-Action-Observation 循环。

测试2答案 ​

Schema 只能验证结构。例如 node_ids 可以是字符串数组,但不能保证每个 ID 都在同一次调用的 nodes 中存在;shape=QUAD 可以是合法枚举,但 Schema 未必保证它引用四个不同坐标。跨字段引用、拓扑约束和平台状态需要 Compiler 校验。

测试3答案 ​

  • collected:已从 Provider 收到并通过顺序检查,尚未编译和执行;
  • compiled:已解析、规范化并生成脚本片段,尚未获得 SAM 执行事实;
  • executed:SAM 已执行并返回成功;
  • verified:回读证据也符合预期。

统一称 completed 会让模型、UI 和维护者误判副作用已经发生。

测试4答案 ​

继续收集 repair 和 audit,不执行 transform。三个操作属于同一确认计划,只有全部参数收齐并共同通过编译后,才能组合为一次脚本提交。立即执行会破坏“执行前完整计划”和“零中间副作用”的不变量。

测试5答案 ​

只向 Provider 暴露当前模块和当前轮次剩余工具;在 System Prompt 中说明固定顺序;Controller 本地校验工具名称、位置、数量;错误时拒绝并返回 Review。Provider 参数是优化手段,本地 Harness 才是最终保证。

测试6答案 ​

一个 SSE 响应可能同时交错多个 Tool Call。只有相同 index 的 id、name 和 arguments 分片属于同一个调用。全局拼接会把两个调用的参数混在一起,或者破坏原有顺序。

测试7答案 ​

不能。单次调用只表示一次提交。Python 脚本内部仍包含多个顺序 Mutation,后一步报错时前一步可能已经改变 SAM。真正原子性需要事务 API、快照恢复或影子对象提交。

测试8答案 ​

PASS 只能证明运行器没有报告异常,不能证明正确 Part 被修改、数量符合预期、Section 被保留或 Audit 文件真正生成。Readback 使用平台真实状态验证目标存在性、节点单元计数、类型数量、Set、Property 摘要和报告产物。

测试9答案 ​

通用 Controller 复用观察、Design、Review、确认、多轮收集、顺序校验、编译调度、执行和回读阶段。LoadModule 负责载荷能力路由、Load Tool Schema、载荷区域和 Step 引用校验、SAM Load API 编译以及载荷专属回读证据。


相关笔记 ​

  • [[01-function-calling]] - Tool Call 的基础协议和宿主执行语义
  • [[05-agent-workflow]] - ReAct 与 Plan-and-Execute
  • [[09-structured-output]] - JSON Schema 与结构化输出
  • [[10-tools-design]] - Tool description、参数与粒度设计
  • [[12-agent-modes]] - Plan/Edit 模式和工具渐进披露
  • [[13-workflow-orchestration]] - 状态机与持久化工作流
  • [[14-context-engineering]] - Observation 和上下文预算
  • [[15-harness-loop-skills]] - 模型外部约束系统
  • [[19-权限与门卫]] - 人工确认和高副作用操作边界
  • [[22-可观测性与调试]] - Trace、日志和执行回放

下一步学习 ​

  • [ ] 将 completed_part_tools 重命名为 collected_tool_calls
  • [ ] 将 ExecutedPartToolCall 拆分为 Compiled、Submitted、Executed、Verified 状态
  • [ ] 为脚本提交增加 runId + script hash 幂等键
  • [ ] 抽象统一 DomainModule 接口,减少 Part/Property/Mesh Controller 分支
  • [ ] 为大型 CAE 模型实现 Summary → Region → Detail 分层 Observation
  • [ ] 评估影子 Part 或执行前快照,实现失败恢复

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇29. 编程 Agent 全面对比:从 Claude Code 到 Pi 的设计哲学 / Coding Agents Comparison: Design Philosophies from Claude Code to Pi
下一篇31. Agent 工程学习指南 / An AI Agent Engineering Learning Guide

持续记录,持续成长

Copyright © Tidenflow