项目实践笔记 9:打卡/日报Agent项目Bug 修改与稳定性收口(v1.0)
在打卡 / 日报 Agent v1.0 的测试过程中,而是从测试视角出发,选取了几类具有代表性的 Bug 进行修改与收口。通过将问题分为代码执行层、模型决策层与输出兜底层,逐层定位不稳定来源,并以最小改动方式保证系统在真实自然语言输入下可运行、可解释、可回归。引入了最小化的输入控制逻辑,对相对时间与意图进行前置解析,减少模型对关键参数的自由猜测,从而显著提升 Agent 行为的一致性与可控性。这一
前言:
关键词:AI 测试 / Agent / Function Calling / 输入控制 / 回归测试 定位:不是开发教程,是测试视角的工程实践复盘
先定测试视角:
-
本次并未追求“修完所有问题”
-
目标是: 通过修改少量具有代表性的 Bug,验证 Agent 系统的稳定性边界
-
选取的 Bug 必须满足:
-
可复现
-
有明确机制原因
-
修复后可迁移、可回归
-
这是一次测试视角的“收口”,而不是功能堆叠。
一、Agent 系统典型风险
这次不是“修一堆 bug”,而是修 3 类典型风险:
-
工具参数/Schema 不匹配(LLM 给的参数不稳定 → Python 直接炸)
-
时间/相对日期不一致(LLM 会猜 today → 时间线乱)
-
查询结果与回复兜底(查询没数据/模型沉默 → 用户看到空)
这三类比“修完所有细节 bug”更有迁移价值。
二、整体思路:分层定位,逐层收口
思路:把问题分成三层,每次只收一层。
Layer A:代码执行层(不让程序炸)
目标:不管 LLM 给什么参数,程序都能跑到底,不抛 TypeError。
做法核心:
-
Schema 里把可选字段从 required 里移除(如
occurred_date、date) -
函数签名改成可选参数(
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 变成可回归、可迁移的最小工程实现。
更多推荐


所有评论(0)