前言:

关键词:AI 测试 / Agent / Function Calling / 输入控制 / 回归测试 定位:不是开发教程,是测试视角的工程实践复盘

先定测试视角:

  • 本次并未追求“修完所有问题”

  • 目标是: 通过修改少量具有代表性的 Bug,验证 Agent 系统的稳定性边界

  • 选取的 Bug 必须满足:

    • 可复现

    • 有明确机制原因

    • 修复后可迁移、可回归

这是一次测试视角的“收口”,而不是功能堆叠。

一、Agent 系统典型风险

这次不是“修一堆 bug”,而是修 3 类典型风险:

  1. 工具参数/Schema 不匹配(LLM 给的参数不稳定 → Python 直接炸)

  2. 时间/相对日期不一致(LLM 会猜 today → 时间线乱)

  3. 查询结果与回复兜底(查询没数据/模型沉默 → 用户看到空)

这三类比“修完所有细节 bug”更有迁移价值。

二、整体思路:分层定位,逐层收口

思路:把问题分成三层,每次只收一层。

Layer A:代码执行层(不让程序炸)

目标:不管 LLM 给什么参数,程序都能跑到底,不抛 TypeError。

做法核心:

  • Schema 里把可选字段从 required 里移除(如 occurred_datedate

  • 函数签名改成可选参数date: str | None = None

  • dispatch_tool_call 兜底补默认值(缺 date → today;缺 datetime → now)

结果:

LLM 就算漏参,也不会把系统“炸停”。

Layer B:模型决策层(不让模型反复追问/乱猜)

目标:让模型“敢调用工具”,并且在“相对时间”上稳定。

典型问题:

  • 代码里已经能默认 today,但模型仍然在调用前追问日期

  • “昨天”被模型算成了 2024(因为它自己猜 today)

做法核心:

  • 在 system prompt 动态注入【当前日期】(让模型不要猜 today)

  • 对“今天/现在”明确规则:不追问,直接默认 today

  • 对“昨天/前天”你最终选择:允许自动换算(基于当前日期锚点)

结果:

“今天/昨天”不再追问、不再跑到 2024。

#部分system prompt:
【相对日期处理规则】
- 对于“昨天 / 前天”这类明确的相对日期,允许直接自动换算为具体日期并调用工具,不需要向用户追问。
- 换算规则:
  - 昨天 = 今天 - 1 天
  - 前天 = 今天 - 2 天
- 只有在相对日期不明确(如“上周”“最近几天”)时,才允许追问。

Layer C:输出兜底层(不让用户看到空白)

目标:就算查询结果为空、或者模型第二次回复为空,也必须给用户一句明确结果。

典型现象:

  • “昨天打卡了吗”系统返回空(模型 content 为空字符串)

做法核心:

  • main.py 里不要在 try 里直接 return,先拿到 text 再判断

  • 如果 text 为空:根据 last_tool_results本地兜底回复 (例如:根据查询结果,YYYY-MM-DD 没有打卡记录。

结果:

从“沉默/空白”变成“可解释、可回归”的稳定输出。

# 兜底:模型沉默时自己说话
    if len(last_tool_results) == 1:
        name, result = last_tool_results[0]
        if name == "get_clock_status":
            d = result.get("date", "该日期")
            items = result.get("items") or result.get("item") or {}
            if not items:
                return f"根据查询结果,{d} 没有打卡记录。"
            return f"根据查询结果,{d} 打卡状态:{json.dumps(items, ensure_ascii=False)}"

三、输入控制

加入 normalize_input 是企业化最小版本

做了什么:

在进入 LLM 前,用 Python 做两件确定性的事:

  • 意图识别:clock_query / fragment_record

  • 相对日期解析:今天/昨天/前天 → YYYY-MM-DD

然后把解析结果作为“事实”写进 system prompt:

  • 【当前日期】…

  • 【已解析日期】…

  • 【已识别意图】…

差别在哪里(核心一句):

从“让模型猜关键参数” → 变成“你先确定关键参数,模型只负责执行和表达”。

是项目中最常见的第一层 guardrail(确定性护栏)。

# 部分代码
def normalize_input(user_text: str):
    """
    返回一个 dict:
    {
        "intent": "clock_query" | "fragment_record" | "unknown",
        "resolved_date": "YYYY-MM-DD" | None,
        "clean_text": 原始用户输入
    }
    """

四、回归测试怎么做

保持一组 典型输入回归集:

A. 日期类

  • 今天打卡了吗(默认 today)

  • 昨天打卡了吗(today-1)

  • 前天打卡了吗(today-2,如果支持)

B. 参数缺失类

  • 记录一条事实:今天完成xx(不传 occurred_date 也能记录)

  • 查询打卡状态(不传 date 也能查)

C. 空结果/沉默兜底类

  • 查询一个肯定没记录的日期(应该输出“无记录”,不沉默)

  • 模型返回空时(兜底文案必须出现)

这些回归用例,未来你改任何 prompt/tool 都先跑一遍,就不会反复踩坑。

总结:

把 Agent 的不稳定点按“执行层-决策层-输出层”分层收口:执行层保证不崩,决策层用时间锚点+输入归一化减少模型猜测,输出层兜底避免沉默,从而把一个 demo 变成可回归、可迁移的最小工程实现。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐