跳转至

02:结构化输出

Chatbot 的自然语言回答很好读,但不好接代码。旅游计划如果要给前端渲染,最好是稳定的结构。

from __future__ import annotations

from pathlib import Path
from typing import Any

from agent_patterns_lab.runtime import Message, MockLLM, SchemaValidationError, Tracer, structured_complete


def main() -> None:
    tracer = Tracer()

    model = MockLLM(
        [
            '{"city":"Hangzhou","items":["West Lake","Tea Museum"]}',
            '{"city":"Hangzhou","morning":"West Lake","afternoon":"China National Tea Museum","evening":"Hefang Street","packing":["umbrella","light jacket","comfortable shoes"]}',
        ]
    )

    messages = [
        Message(
            role="system",
            content=(
                "Return ONLY JSON with keys: city, morning, afternoon, evening, packing. "
                "packing must be a list of strings."
            ),
        ),
        Message(role="user", content="Create a one-day Hangzhou itinerary."),
    ]

    def parse_itinerary(value: Any) -> dict[str, Any]:
        if not isinstance(value, dict):
            raise SchemaValidationError("expected a JSON object")
        required = ["city", "morning", "afternoon", "evening", "packing"]
        for key in required:
            if key not in value:
                raise SchemaValidationError(f'missing key "{key}"')
        if not isinstance(value["packing"], list) or not all(isinstance(x, str) for x in value["packing"]):
            raise SchemaValidationError('"packing" must be a list of strings')
        return value

    itinerary = structured_complete(
        model,
        messages,
        parser=parse_itinerary,
        schema_hint='{"city":"...","morning":"...","afternoon":"...","evening":"...","packing":["..."]}',
        tracer=tracer,
    )

    print(itinerary)
    trace_path = tracer.export_jsonl(Path(".traces") / "10_structured_output.jsonl")
    print(f"[trace] {trace_path}")


if __name__ == "__main__":
    main()

运行:

uv run python examples/10_structured_output.py

新问题

我们希望模型输出:

{
  "city": "Hangzhou",
  "morning": "...",
  "afternoon": "...",
  "evening": "...",
  "packing": ["..."]
}

但模型可能少字段、字段类型不对,或者在 JSON 外面多说几句。

新模式:Structured Output

structured_complete(...) 做三件事:

  1. 从模型输出里提取 JSON。
  2. parse_itinerary(...) 校验字段。
  3. 如果失败,把错误反馈给模型,让它重试修复。

这一步之后,旅游助手不只是“会说”,而是能给代码一个稳定对象。

它还是不够

结构化输出只能保证格式,不保证事实。

如果模型说“明天晴天”,它可能只是猜的。旅游规划需要天气、路线、开放时间这些外部信息。

下一步:让代码提供工具。看 03:工具调用