KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
04. 怎样把模型建议变成程序输入? — keel 龙骨
模型可以输出自然语言,但程序不能靠字符串猜测“它是不是想调用工具”。你需要把模型输出转换成结构化的内部决定。
模型可以输出自然语言,但程序不能靠字符串猜测“它是不是想调用工具”。你需要把模型输出转换成结构化的内部决定。
三个对象先分开
Decision:模型建议下一步做什么
Action:通过协议和策略检查后允许执行的动作
Result:动作执行后的结构化结果或错误
它们的关系是:
flowchart LR
M[模型文本/JSON] --> P[Decision Parser]
P --> D[Decision]
D --> V[协议与业务校验]
V --> A[Action]
A --> X[Executor]
X --> R[Result]
Decision 不是权限,Action 不是结果,Result 也不能反过来证明原来的决定正确。
一个最小决定契约
class Decision(BaseModel):
kind: Literal["final", "tool_call", "ask_user"]
content: str | None = None
tool_name: str | None = None
arguments: dict[str, Any] | None = None
结构校验可以保证 kind 是允许值、必填字段存在、参数是对象。但它不能判断:工具是否注册、用户是否有权限、事件是否属于当前租户。这些属于后续边界。
运行真实结构化输出示例
python courses/foundation/agent-harness/course/project/examples/03_structured_decision.py
这个示例会调用真实 Ollama,并把返回内容校验为 IncidentSummary。协议边界本身不依赖模型,固定响应测试位于项目的 test_harness.py。
你可以在测试中直接构造三种输入:
{"kind":"final","content":"可以。"}
{"kind":"tool_call","tool_name":"lookup_incident","arguments":{"incident_id":"INC-001"}}
{"kind":"tool_call","tool_name":"lookup_incident","arguments":[]}
第三种输入应该在协议层失败,而不是让工具函数收到错误类型后自行崩溃。
Schema 校验、业务校验、权限校验是三层
| 层 | 它回答什么 | 例子 |
|---|---|---|
| 结构校验 | 数据形状能否被程序理解 | arguments 必须是对象 |
| 业务校验 | 值是否符合领域规则 | incident_id 必须是 INC-数字 |
| 权限校验 | 当前主体能否对资源执行动作 | Alice 不能查 Bob 的租户 |
把权限写进系统提示词不是授权。把业务错误留给函数内部抛异常也会让错误边界变得模糊。
为什么内部协议要独立于供应商 SDK
Ollama、其他模型供应商和测试 fake 的字段形状可能不同。适配器应把它们统一为内部 Decision,这样 Harness 的状态、策略、事件和测试不需要跟着供应商字段变化。
本章自测
如果模型返回了已注册工具名,但参数缺少 incident_id,运行应停在哪里?答案是结构或业务校验层,还没有资格进入 Executor。
如果参数正确,但当前用户没有权限,运行应停在哪里?答案是 Policy 层,而不是再问模型一次。
下一章让这个内部决定真正进入一个只读工具,完成第一次完整往返。