09 实践课-基础智能体测试与 AI 协同调试

基础智能体测试与 AI 协同调试

关联:索引

  1. 场景提问:同一句话“玻璃怎么分拣”,为什么有时能查到规则、有时触发不了工具?怎么证明问题来自“意图识别”还是“工具执行”?

1. 功能测试(端到端最小闭环)

目标:验证“输入→意图→路由→工具/模型→输出”是否符合预期。

用例模板(建议复制到测试记录/错例清单)

最小功能用例(分拣场景示例)

实操案例(端到端功能测试:输出必须可复验)

目标:让学生把“看起来对”变成“证据链可复验”。

  1. 选择 1 条查询规则用例(如 F-01),运行智能体一次,记录以下证据:

示例输出片段(格式示意,便于学生对照):

用户输入:查一下 glass 的分拣规则
解析:intent=QUERY_RULE item_type=glass | parse_trace_id=9f21a3c1
最终输出:【规则查询结果】R-GLASS-01 | 玻璃制品:轻放、防碰撞;单独箱;贴易碎标;优先人工复核。 | action=use_fragile_bin | trace_id=2b8c7d19 | parse_trace_id=9f21a3c1

基于上节课项目的最小复现(直接跑一次并粘贴输出):

终端(在项目根目录执行):

cd ".\04_sorting_agent_practice"
python -c "from app_with_intent import build_llm, build_agent, run_with_router; llm=build_llm(); agent=build_agent(llm); print(run_with_router(agent,llm,'查一下 glass 的分拣规则'))"

2. 意图识别准确率测试(用标注数据评估)

目标:用标注数据统计意图识别是否正确,并定位“最常错的那一类”。

最小标注数据格式(示例,JSON Lines)

文件:data.jsonl(建议放在 04_sorting_agent_practice/ 下,便于命令行直接读取)

{"text":"查一下 glass 的分拣规则","intent":"QUERY_RULE","item_type":"glass"}
{"text":"提交反馈 t001 FAIL 玻璃破损","intent":"SUBMIT_FEEDBACK","task_id":"t001","ok":false,"message":"玻璃破损"}
{"text":"为什么要做意图识别?","intent":"GENERAL"}

说明(知识点校验):

实操案例(用标注数据计算准确率与混淆矩阵:最小脚本示例)

使用方式:把标注数据保存为 data.jsonl,每行一个 JSON;把你的 detect_intent(text, llm) 或等价函数接进来即可。

文件建议:04_sorting_agent_practice/eval_intent_accuracy.py

import json
from collections import Counter, defaultdict

LABELS = ["QUERY_RULE", "SUBMIT_FEEDBACK", "GENERAL"]

def load_jsonl(path: str):
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if line:
                yield json.loads(line)

def evaluate(data_path: str, predict_intent):
    total = 0
    correct = 0
    per_label_total = Counter()
    per_label_correct = Counter()
    matrix = defaultdict(Counter)

    for row in load_jsonl(data_path):
        text = row["text"]
        gold = row["intent"]
        pred = predict_intent(text)

        total += 1
        per_label_total[gold] += 1
        matrix[gold][pred] += 1
        if pred == gold:
            correct += 1
            per_label_correct[gold] += 1

    overall_acc = correct / total if total else 0.0
    key_acc = {
        k: (per_label_correct[k] / per_label_total[k] if per_label_total[k] else 0.0)
        for k in ["QUERY_RULE", "SUBMIT_FEEDBACK"]
    }

    print("overall_acc:", round(overall_acc, 4), f"({correct}/{total})")
    print("key_acc:", {k: round(v, 4) for k, v in key_acc.items()})
    print("confusion_matrix (gold -> pred counts):")
    for gold in LABELS:
        row = {pred: matrix[gold][pred] for pred in LABELS}
        print(gold, row)

if __name__ == "__main__":
    def predict_intent_stub(text: str) -> str:
        t = (text or "").strip()
        if "提交反馈" in t or "FEEDBACK" in t.upper():
            return "SUBMIT_FEEDBACK"
        if "规则" in t or "怎么分拣" in t or "如何分拣" in t:
            return "QUERY_RULE"
        return "GENERAL"

    evaluate("data.jsonl", predict_intent_stub)

可选加分(更贴近工程评估):

对接你自己的意图函数(示例):

如果你的 detect_intent 返回的是对象(如 IntentResult(intent=..., ...)),可以做一个薄封装,保证 predict_intent(text) 返回三类标签字符串即可。

文件:04_sorting_agent_practice/eval_intent_accuracy.py(放在同一个脚本中即可)

from intent import detect_intent

def predict_intent_from_result(text: str, llm) -> str:
    r = detect_intent(text, llm)
    return r.intent

方式 B(保留 predict_intent_stub 做对照,同时新增真实预测函数):

文件:04_sorting_agent_practice/eval_intent_accuracy.py(把脚本末尾的 __main__ 部分替换为下面这一段)

from app_with_intent import build_llm

if __name__ == "__main__":
    def predict_intent_stub(text: str) -> str:
        t = (text or "").strip()
        if "提交反馈" in t or "FEEDBACK" in t.upper():
            return "SUBMIT_FEEDBACK"
        if "规则" in t or "怎么分拣" in t or "如何分拣" in t:
            return "QUERY_RULE"
        return "GENERAL"

    llm = build_llm()

    def predict_intent_real(text: str) -> str:
        return predict_intent_from_result(text, llm)

    print("== baseline (stub) ==")
    evaluate("data.jsonl", predict_intent_stub)

    print("== real (detect_intent) ==")
    evaluate("data.jsonl", predict_intent_real)

与上节课项目对齐的提醒:

3. 工具调用测试(契约与异常路径)

目标:验证工具能被触发、入参合规、返回格式稳定,并覆盖错误分支。

最小工具测试用例(示例)

实操案例(工具契约测试:覆盖正常 + 异常分支)

目标:让学生能区分“工具没触发”和“工具执行报错”,并能用输出字段定位。

用例:

基于上节课项目的工具直测(不经过 agent,定位更快):

终端(在项目根目录执行):

cd ".\04_sorting_agent_practice"
python -c "from tools_sorting import query_sorting_rule; print(query_sorting_rule.invoke({'item_type':'glass'}))"
python -c "from tools_sorting import query_sorting_rule; print(query_sorting_rule.invoke({'item_type':''}))"
python -c "from tools_sorting import query_sorting_rule; print(query_sorting_rule.invoke({'item_type':'unknown'}))"
python -c "from tools_sorting import submit_sorting_feedback; print(submit_sorting_feedback.invoke({'task_id':'t001','ok':False,'message':'玻璃破损'}))"
python -c "from tools_sorting import submit_sorting_feedback; print(submit_sorting_feedback.invoke({'task_id':'','ok':True,'message':'正常'}))"

实操案例(回归测试:批量跑用例并断言“证据字段”)

目标:修复后必须“一次性验证多条用例”,避免只修好了一个例子却引入新问题。

文件建议:04_sorting_agent_practice/regression_test.py

import re
from dataclasses import dataclass
from typing import List

@dataclass
class Case:
    case_id: str
    text: str
    must_contain: List[str]

TRACE_RE = re.compile(r"\btrace_id=([0-9a-fA-F]{6,})\b")

def has_evidence(text: str) -> bool:
    if not text:
        return False
    if "R-" in text:
        return True
    if "RECEIPT:" in text:
        return True
    if TRACE_RE.search(text):
        return True
    return False

def run_regression(cases: List[Case], invoke_text) -> None:
    failed = []
    for c in cases:
        final_text = invoke_text(c.text) or ""
        ok_contains = all(x in final_text for x in c.must_contain)
        ok_evidence = has_evidence(final_text)
        if not (ok_contains and ok_evidence):
            failed.append((c.case_id, final_text[:160]))
    if failed:
        raise RuntimeError(f"regression_failed: {failed}")

if __name__ == "__main__":
    cases = [
        Case("R-01", "查一下 glass 的分拣规则", ["【规则查询结果】", "R-GLASS-01", "parse_trace_id="]),
        Case("R-02", "提交反馈 t001 失败,玻璃破损", ["【反馈回执】", "RECEIPT:", "task_id=t001", "parse_trace_id="]),
        Case("R-03", "为什么要先做意图识别?", ["【解释】", "no_tools=true", "parse_trace_id="]),
        Case("R-04", "", ["【需要澄清】", "parse_trace_id="]),
    ]

    from app_with_intent import build_llm, build_agent, run_with_router
    llm = build_llm()
    agent = build_agent(llm)

    def invoke_text(text: str) -> str:
        return run_with_router(agent, llm, text)

    run_regression(cases, invoke_text)

1)严格口径(更工程化):只把“执行证据”算作 evidence

2)宽松口径(更适合解释/澄清分支):把 parse_trace_id= 也算作 evidence

def has_evidence(text: str) -> bool:
    if not text:
        return False
    if "R-" in text:
        return True
    if "RECEIPT:" in text:
        return True
    if "trace_id=" in text:
        return True
    if "parse_trace_id=" in text:
        return True
    return False

不建议把 【解释】【需要澄清】 当作 evidence 的判断条件:它们是“展示文案”,容易改版;trace_id/parse_trace_id 才是更稳定的“证据字段”。

业务代码改动只选 1 类文件即可(对应根因选点修复):

1. 意图识别错误:误判/漏判/槽位字段缺失

常见现象:

实操案例(误判复现与修复:冲突输入)

  1. 失败用例输入:提交反馈 t001:按玻璃规则分拣后发现破损
  2. 期望:SUBMIT_FEEDBACK(含 task_id=t001,ok=False,message 含“破损”)
  3. 常见误判原因:规则关键词“规则”优先级过高,覆盖了“提交反馈 + task_id”这一更强信号。
  4. 修复策略示例(规则优先级调整):
  1. 回归:用同一用例 + 3 条相似表达(包含/不包含“规则”)验证不再误判

  2. 复现:用失败用例原句重跑,保留输入与 trace_id。

  3. 最小化:只跑意图识别模块,隔离 LLM 与工具干扰(先看规则匹配输出)。

  4. 定位:看命中规则/关键词/正则路径,确认是哪条规则导致误判。

  5. 修复:优先修正规则(更确定);必要时增加“澄清提问”分支(need_clarification)。

  6. 回归:重跑标注数据与关键功能用例,统计是否提升且不引入新错例。

2. 工具调用失败:不触发/入参不合规/工具异常

常见现象:

知识点校验(快速判断法):

实操案例(工具不触发:路由没选中)

  1. 输入:查一下 battery 的分拣规则
  2. 观察点:
  1. 典型根因:
  1. 修复后回归:
  1. 判定故障类型:先确认“工具是否被触发”(看消息链/日志/trace_id)。
  2. 核对路由:检查意图结果是否正确、工具选择逻辑是否覆盖该输入。
  3. 核对入参:检查槽位字段是否抽取成功、是否做了空值校验与默认值策略。
  4. 核对契约:工具输出是否含 rule_id/receipt/trace_id,格式是否被后续解析破坏。
  5. 回归:重跑工具测试用例 + 功能用例,确保输出稳定。

3. 结果反馈异常:回执缺失/状态不一致/证据链断裂

常见现象:

实操案例(回执缺失:契约与模板同时检查)

  1. 输入:提交反馈 t002 正常,已按规则处理完成
  2. 期望:输出包含 RECEIPT: task_id=t002 ... | trace_id=...,最终回复引用 receipt 或 trace_id
  3. 若最终回复没有任何 receipt/trace_id:
  1. 修复后回归:重跑 F-02 + 本用例,确保每次都有可复验字段

目标:让每个小组先拿到一个“稳定可复现”的失败(这比直接讲原理更有效)。

文件:04_sorting_agent_practice/regression_test.py

运行(PowerShell,在项目根目录执行):

cd ".\04_sorting_agent_practice"
python .\regression_test.py

出现 RuntimeError: regression_failed: [...] 是正常现象:表示有用例不满足你写的输出契约(must_contain + evidence)。

目标:让 AI 做“分析与建议”,但不允许它“直接替你改完”。

证据三件套(必须给全):

文件建议:ai_debug_prompt.txt(保存你给 AI 的输入,便于复盘)

你是智能体调试助手。请基于我提供的【失败用例】【日志】【关键代码片段】完成:
1)判断故障类型:意图识别/路由/工具入参/工具执行/结果回填;
2)给出最可能的根因(按优先级排序);
3)给出可落地的修复方案(明确修改点与代码片段);
4)给出复验步骤(至少 3 条),并写清楚预期输出中必须出现的证据字段。
输出必须结构化(分点或 JSON 均可),不要编造不存在的日志或代码。

目标:把 AI 的建议落成可运行的代码改动,并用回归脚本证明“修复有效且不引入新问题”。

允许改动的范围(只选 1 类):

人工审计检查(写进小组复盘记录,最低要求):

修复后回归(必须做):

cd ".\04_sorting_agent_practice"
python .\regression_test.py

目标:用脚本输出“改前/改后”的混淆矩阵,避免只靠感觉。

文件:04_sorting_agent_practice/data.jsonl

{"text":"查一下 glass 的分拣规则","intent":"QUERY_RULE","item_type":"glass"}
{"text":"查一下 battery 的分拣规则","intent":"QUERY_RULE","item_type":"battery"}
{"text":"glass 怎么分拣?","intent":"QUERY_RULE","item_type":"glass"}
{"text":"battery 如何分拣","intent":"QUERY_RULE","item_type":"battery"}
{"text":"RULE glass","intent":"QUERY_RULE","item_type":"glass"}
{"text":"RULE battery","intent":"QUERY_RULE","item_type":"battery"}
{"text":"怎么分拣?","intent":"QUERY_RULE"}
{"text":"查一下分拣规则","intent":"QUERY_RULE"}
{"text":"提交反馈 t001 FAIL 玻璃破损","intent":"SUBMIT_FEEDBACK","task_id":"t001","ok":false,"message":"玻璃破损"}
{"text":"提交反馈 t002 OK 已按规则处理","intent":"SUBMIT_FEEDBACK","task_id":"t002","ok":true,"message":"已按规则处理"}
{"text":"反馈 t003:按玻璃规则分拣后破损","intent":"SUBMIT_FEEDBACK","task_id":"t003","ok":false,"message":"按玻璃规则分拣后破损"}
{"text":"FEEDBACK t004 FAIL battery 鼓包","intent":"SUBMIT_FEEDBACK","task_id":"t004","ok":false,"message":"battery 鼓包"}
{"text":"怎么处理 t005 这单 battery 鼓包?","intent":"SUBMIT_FEEDBACK","task_id":"t005","ok":false,"message":"battery 鼓包"}
{"text":"提交反馈 t006 失败,查一下 glass 的分拣规则后发现破损","intent":"SUBMIT_FEEDBACK","task_id":"t006","ok":false,"message":"查一下 glass 的分拣规则后发现破损"}
{"text":"为什么要做意图识别?","intent":"GENERAL"}
{"text":"规则和反馈有什么区别?","intent":"GENERAL"}
{"text":"电池为什么要隔离?","intent":"GENERAL"}
{"text":"你好","intent":"GENERAL"}

文件:04_sorting_agent_practice/eval_intent_accuracy.py

运行(PowerShell,在项目根目录执行):

cd ".\04_sorting_agent_practice"
python .\eval_intent_accuracy.py
  1. 至少 10 条测试用例(功能 ≥ 4、意图准确率 ≥ 3、工具调用 ≥ 3)。
  2. 至少 1 个问题完成“复现→修复→回归”闭环,证据链完整(含 trace_id 或 parse_trace_id)。
  3. AI 协同调试记录 ≥ 1 个问题,且能说明“AI 建议是什么、你审计了什么、你最终改了什么”。

现场出示的证据清单(不收文件,现场查看即可):


  1. regression_test.py 新增 1 条回归用例,能覆盖你修复的边界情况。
  2. 能解释“为什么选择改业务代码/为什么选择调整 has_evidence 口径”,并说明风险与后续收紧标准的计划。