AI Agent 入门教程:05-测试与调试

暂停添加功能,为最小 Agent 定义输入、权限、预算、输出和验收规则,再用 Fake Model 离线复现成功、参数修正和预算耗尽。后续能力共享这套行为基线。

本文目录7 个章节

“能跑”还缺一把尺子

第 4 篇的 run_agent() 已经能按“搜索—补查—结束”推进,但“回答得不错”无法写进测试。两名审核者可能对同一份简报给出相反结论,开发者也无法判断一次改动提升了质量,还是碰巧换了措辞。

先停止添加功能,把模糊需求改成一组可检查的输入和输出约束。规格不规定模型的思考过程,只规定合法输入、可用工具、资源预算、输出形状和验收办法。

把一句需求拆成可检查字段

技术简报任务使用下面这份合同。

TEXT
输入
├── topic: 非空字符串
├── audience: engineer | manager
└── max_sources: 1..8

权限
├── 允许: search_local_sources, get_document
└── 禁止: 写文件、访问网络、读取未登记路径

预算
├── max_turns: 6
└── max_tool_calls: 8

输出
├── status: succeeded | insufficient_evidence | failed
├── summary: 200..800 字
└── source_ids: 至少 1 个且全部存在

insufficient_evidence 是合格结果。资料库找不到可靠证据时,系统应说明缺口,而不是编造出处。

用数据类保存任务规格

PYTHON
from dataclasses import dataclass
from typing import Literal

@dataclass(frozen=True)
class Budget:
    max_turns: int = 6
    max_tool_calls: int = 8

@dataclass(frozen=True)
class BriefTask:
    topic: str
    audience: Literal["engineer", "manager"]
    max_sources: int
    allowed_tools: tuple[str, ...]
    budget: Budget

def validate_task(task: BriefTask) -> list[str]:
    errors = []
    if not task.topic.strip():
        errors.append("topic_required")
    if not 1 <= task.max_sources <= 8:
        errors.append("max_sources_out_of_range")
    if set(task.allowed_tools) - {"search_local_sources", "get_document"}:
        errors.append("tool_not_allowed")
    return errors

无效任务在调用模型前返回,避免为确定性错误支付推理费用。规格对象同时交给运行时和测试,防止 Prompt、代码和验收器各维护一套限制。

Fake Model 让控制流测试离线运行

Fake Model 不模拟语言能力,只按脚本返回预定输出。它用来验证 Runner 是否正确分发工具、追加结果并停止。

PYTHON
from dataclasses import dataclass
from typing import Any

@dataclass(frozen=True)
class FakeOutput:
    output: list[Any]
    output_text: str = ""

class FakeResponses:
    def __init__(self, script: list[FakeOutput]):
        self._script = iter(script)
        self.calls: list[dict] = []

    def create(self, **kwargs) -> FakeOutput:
        self.calls.append(kwargs)
        return next(self._script)

class FakeClient:
    def __init__(self, script: list[FakeOutput]):
        self.responses = FakeResponses(script)

测试脚本可以精确控制三轮输出,不依赖网络、API 余额或模型采样。真实客户端与 Fake Client 都提供 responses.create,Runner 不需要知道当前使用哪一个。

行为测试检查路径,不锁死措辞

测试重点是结束状态、工具顺序和来源,不比较完整自然语言文本。

PYTHON
def test_research_path_uses_search_then_read(fake_client, tool_schemas, tools):
    result = run_agent(
        client=fake_client,
        model="fake-model",
        question="How do agent loops work?",
        tools_schema=tool_schemas,
        tools=tools,
        max_turns=6,
    )

    assert result["status"] == "succeeded"
    assert [event.get("tool") for event in result["events"] if "tool" in event] == [
        "search_local_sources",
        "get_document",
    ]

再补四类测试:空主题在模型调用前失败;未知工具返回稳定错误;工具参数错误后模型可以修正一次;连续请求工具最终耗尽预算。

从单元测试到业务验收

TEXT
┌─────────────────────────────────┐
│ 业务验收:来源存在、字数合格、结论有证据 │
├─────────────────────────────────┤
│ 行为测试:工具顺序、状态转换、预算出口    │
├─────────────────────────────────┤
│ 单元测试:检索、参数校验、错误转换        │
└─────────────────────────────────┘

底层测试数量最多,运行最快。业务验收数量较少,检查最终产物。真实模型集成测试只保留少量冒烟用例,避免每次改代码都受随机输出影响。

完成后的代码清单

前五篇结束时,仓库应包含:一次真实模型调用、一个只读工具、一个可停止循环、一份任务约束、Fake Client 和离线测试。

接下来重构工具层:新增 get_document(source_id),先写两个失败测试,再把它与 search_local_sources 一起注册到 Tool Registry。第 6 篇保持现有行为不变,只统一参数校验、权限、超时和错误;工具边界稳定后,第 7 篇再接入 RAG。

REFERENCES

参考链接

  1. 01Function Calling | OpenAI API
  2. 02Running Agents | OpenAI API
  3. 03A Practical Guide to Building Agents | OpenAI

所属系列

AI AGENT 入门教程

下一步

继续浏览相关主题

沿着同一主题继续阅读。

查看最新资讯