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 层,而不是再问模型一次。

下一章让这个内部决定真正进入一个只读工具,完成第一次完整往返。

下一章:让模型第一次用上只读工具

进入 keel 阅读