2027 校招做 AI 测试项目,怎样避免只做一个聊天 Demo?做一条可回放的测试链路:让 Agent 处理一个会调用工具的真实业务请求,把当时的工具返回录下来,再用同一份事实回归新模型、新提示词或新代码。
招聘官看项目时,最怕看到两类内容:一个“接入大模型的智能客服”截图,和一堆“我会 Prompt、RAG、Agent”的名词。它们都难以证明你真的做过测试。
相反,一条小而完整、能复现失败的链路更有说服力。下面这个“校园报修助手”项目,两个周末就能完成,却足以展示 AI 测试开发最核心的能力:把一次会变的模型行为,固定成可重复的工程证据。
一、项目不要从“能聊天”开始,要从“会不会误执行”开始
场景很简单:学生说“宿舍空调不制冷,帮我报修”。Agent 可以调用两个工具:
asset.lookup(room_id) # 查设备是否在保修、是否已有工单
repair_ticket.create(...) # 创建报修单,会产生真实业务状态
项目的危险点不在模型能否识别“空调不制冷”,而在它会不会:
已有未关闭工单时又创建一张;
查不到房间信息时凭空说“已提交”;
工具超时后重试两次,造成重复工单;
把“保修已过期”解释成“已经安排维修”。
这四个问题任何一个都比“回答措辞不够自然”更像企业里会发生的测试问题。你的项目目标可以写成:
对每次报修请求,系统要么创建一张可追溯工单,要么明确转人工/提示信息不足;绝不重复创建,也不伪造成功。
二、作品集的关键文件,不是页面,而是一份“录制事实”
建议把仓库组织得足够小:
campus-agent-qa/
├── agent.py # 业务 Agent,输出 decision/reason_code/reply
├── tools.py # 真实工具协议
├── replay.py # 录制与回放工具夹具
├── cases/
│ ├── duplicate_ticket.jsonl
│ └── asset_not_found.jsonl
├── tests/
│ └── test_repair_agent.py
└── README.md # 场景、风险、如何运行
JSONL 里保存的不是用户隐私数据,而是脱敏后的测试事实:输入、工具调用顺序、返回值与期望处置。这样你就能在模型升级后,让 Agent 面对同一个世界再跑一遍。
{
"case_id": "duplicate_ticket_must_not_create_again",
"input": "A-302 空调不制冷,帮我报修",
"tool_fixtures": {
"asset.lookup": {
"asset_id": "AC-302-01",
"open_ticket_id": "R-20260902-17"
}
},
"expected": {
"decision": "inform_existing_ticket",
"forbidden_calls": ["repair_ticket.create"],
"must_include": ["R-20260902-17"]
}
}
这份记录的意义是:你不再依赖“今天模型恰好回答得不错”。面试官把模型从 A 换到 B,或者把工具返回改成超时,你都有一个明确的回归起点。
三、核心代码:用回放夹具阻止“模型表现碰巧很好”
下面是一个最小回放工具。它把真实工具替换成按名称返回固定数据的测试桩,并把调用历史保存下来供断言使用。
from dataclasses import dataclass, field
from typing import Any
@dataclass
class ReplayTools:
fixtures: dict[str, Any]
calls: list[tuple[str, dict[str, Any]]] = field(default_factory=list)
def call(self, name: str, **kwargs: Any) -> Any:
self.calls.append((name, kwargs))
value = self.fixtures.get(name)
if isinstance(value, Exception):
raise value
if value is None:
raise AssertionError(f"unexpected tool call: {name}")
return value
def test_existing_ticket_is_not_created_twice(repair_agent, case_loader):
case = case_loader("duplicate_ticket_must_not_create_again.jsonl")
tools = ReplayTools(case["tool_fixtures"])
result = repair_agent.run(case["input"], tools=tools)
assert result.decision == "inform_existing_ticket"
assert "R-20260902-17" in result.reply
assert all(name != "repair_ticket.create" for name, _ in tools.calls)
这里至少体现了三个校招面试常问的能力:
mock 与 fixture:外部工具不稳定时,如何让测试可重复;
副作用断言:不只检查回复文本,还检查有没有真的创建工单;
回归资产:一次发现的 bug,如何留下来防止下次模型变更时重现。
如果想再进一层,可以把工具超时、房间不存在、重复请求做成 8—12 条用例,并在 GitHub Actions 中跑 pytest。不需要巨大的系统,只要每条用例都能说明“业务事实是什么、预期行为是什么、我怎么证明它”。
四、在校招面试里,5 分钟这样讲项目
不要从“我用了什么模型”讲起。可以按下面的路线:
业务风险:报修 Agent 最大风险是重复创建和虚假成功;
测试设计:把已有工单、工具超时、资产不存在做成固定夹具;
代码实现:回放工具记录调用,用断言验证决策、工单副作用和最终文案;
回归价值:模型和提示词变化后重跑 JSONL,用例失败就阻断合并;
下一步:补策略版本、人工转接与结果报告。
简历上也别只写“基于大模型开发报修机器人”。可以写得更具体:
设计校园报修 Agent 的可回放测试链路:以 JSONL 固化工具夹具与失败场景,使用 pytest 验证决策、工具副作用和用户可见结果;覆盖重复工单、资产缺失与工具超时等 12 类风险场景。
它不保证你一定拿 offer,但能让面试官看到:你不是在演示模型,而是在测试一个能影响真实业务的系统。