AI Agent开发中的Red-Green循环:测试驱动开发实践指南
1. 从“一蹴而就”到“小步快跑”:为什么AI Agent开发需要Red-Green循环
如果你和我一样,刚开始接触AI Agent开发,或者尝试用大模型(LLM)来自动化一些复杂任务,大概率会掉进一个坑里:给AI一个宏大的目标,然后满怀期待地等它一口气给你一个完美的、能直接运行的解决方案。结果呢?要么是AI“一本正经地胡说八道”,生成了一堆看似合理但根本无法执行的代码或逻辑;要么是它确实生成了些东西,但一旦运行就报错,而你面对着一大坨“黑盒”输出,根本无从下手调试,最后只能推倒重来,挫败感满满。
这背后的根源,其实是我们把AI当成了一个“魔法黑箱”,期望输入需求,输出成品。但现实是,无论是基于GPT、Claude还是其他模型的Agent,其输出都具有不可预测性和“幻觉”风险。让它一次性完成复杂任务,就像让一个程序员在不写单元测试、不编译运行的情况下,直接交付一个大型系统——翻车的概率极高。
于是,一个在传统软件开发领域被验证了数十年的最佳实践,在AI时代显得尤为重要,那就是 测试驱动开发(TDD) 。而“Red -> Green”正是TDD核心循环的形象化表述。简单来说,它的流程是:
- Red(红) :先写一个肯定会失败的测试。这个测试定义了你的代码或功能“应该做什么”。此时运行测试,看到的是代表失败的红色。
- Green(绿) :编写 最少、最简单 的代码,让这个测试通过。此时运行测试,看到的是代表成功的绿色。
- Refactor(重构) :在测试保护下,优化刚刚写的代码,改善其结构,但不改变其行为(测试保持绿色)。
把这个思想平移到AI Agent Skill(技能)开发上,就变成了: 不要让AI Agent一口气写完整个技能,而是引导它按照“定义失败(Red) -> 实现通过(Green)”的小步循环来交付 。这不仅仅是方法论,更是控制AI、提升开发效率与结果质量的“金科玉律”。
2. 拆解“Red-Green”循环在Agent技能开发中的具体含义
在纯代码的TDD中,“测试”是明确的、可自动执行的代码。在AI Agent开发中,尤其是涉及自然语言处理、工具调用、决策流程的场景,“测试”的定义需要更灵活,但其核心精神不变: 先明确且可验证地定义“什么是错的”或“什么还没做对”,再让AI去修正它。
2.1 “Red”阶段:如何为AI定义清晰的“失败”信号
“Red”阶段的目标不是让AI犯错,而是为我们和AI建立一个清晰、无歧义的验收标准。这个标准必须是 可观测、可验证 的。
1. 基于规格说明(Spec)的“失败” 这是最直接的方式。你可以为AI Agent的技能编写一个详细的规格说明(Specification)。这个Spec不是模糊的需求描述,而是结构化的输入-输出定义或行为描述。
-
示例(一个“天气查询”技能) :
- 模糊需求 :“帮我写一个查天气的Agent技能。”
- 清晰的Spec(可作为Red测试) :
技能名称 :GetCityWeather 输入 :用户消息,应包含城市名(如“北京”、“New York”)。 预期输出 :一个结构化的JSON对象,包含字段:
city(字符串,城市名)、temperature(数字,摄氏度)、condition(字符串,如“晴朗”、“多云”)、humidity(数字,百分比)。 失败条件(Red) :AI的回复是自然语言(如“北京今天天气不错”),而不是JSON;或者JSON中缺少temperature字段;或者temperature不是数字。
在开发时,你首先把这个Spec交给AI,然后给它一个测试输入:“上海天气怎么样?” 如果AI回复了一段话而不是JSON,或者JSON格式不对,这就是一个明确的“Red”状态。你不需要自己去运行代码判断,肉眼就能识别这个“失败”。
2. 基于现有代码或逻辑的“失败” 当你是在迭代或修复一个现有技能时,“Red”可以是一个已知的Bug或未实现的功能。
- 示例 :你的Agent有一个计算器技能,但目前只能做加法。你定义一个“Red”测试:输入“计算 5 乘以 3”,期望输出是“15”。当前技能显然无法处理(因为它只懂加法),这就是“Red”。你把这个具体的失败案例和期望结果告诉AI,让它去实现乘法逻辑。
3. 基于工具调用结果的“失败” 很多Agent技能需要调用外部API、数据库或命令行工具。“Red”可以定义为工具调用失败或返回了意外结果。
- 示例 :你让AI Agent调用一个股票查询API。你首先告诉它API的端点、所需参数和预期的响应格式。然后你给出指令:“获取特斯拉(TSLA)的股价。” 如果AI构造的API请求URL错了(导致404错误),或者它无法解析返回的JSON数据,这就是“Red”。你可以把错误的API响应或错误日志反馈给AI,让它诊断问题。
关键心得 :定义“Red”的秘诀在于 具体和原子化 。不要定义“实现一个完整的用户注册流程”这样的大“Red”,而是拆分成“验证邮箱格式(Red:接受‘abc’为合法邮箱)”、“检查用户名是否重复(Red:未调用查重API)”等多个小“Red”。小步快跑,风险可控。
2.2 “Green”阶段:引导AI用最小改动消除“失败”
“Green”阶段的目标是让AI用最直接、最简单的方式,让刚才定义的“Red”测试通过。这里的关键是 “最小” 和 “聚焦” 。
1. 提供明确的修正指令 不要只说“它错了,你改一下”。要结合“Red”的具体表现,给出指向性的指令。
- 从“Red”到“Green”的指令示例 :
- (接上文天气查询例子)当AI回复了自然语言时,你可以说:“你刚才回复的是自然语言。请严格按照我提供的Spec,将查询结果组织成JSON格式输出,只输出JSON,不要有其他文字。”
- (接上文乘法例子)你可以说:“当前的技能缺少乘法功能。请扩展技能逻辑,当识别到输入中包含‘乘以’或‘*’时,计算两个数字的乘积并返回结果。现在请处理‘计算 5 乘以 3’这个输入。”
- (接上文API例子)你可以把错误的HTTP 404响应返回给AI,并说:“调用失败了,返回了404状态码。请检查你构造的URL是否正确。API的基准URL是
https://api.example.com/v1/,查询特斯拉股价的完整路径应该是/quote/TSLA。请修正你的请求。”
2. 接受不完美但正确的“Green” 在“Green”阶段,我们的唯一目标是让 当前这个 “Red”测试通过。AI给出的方案可能很笨拙、不优雅、甚至留下了其他隐患,但只要它通过了当前测试,我们就应该接受。
- 示例 :为了让天气技能输出JSON,AI可能会写一个非常死板的代码:
return ‘{“city”: “” + city + ““, “temperature”: 25, “condition”: “晴朗”, “humidity”: 60}’。它甚至没有去真实查询天气,只是硬编码了一个响应。但从通过“输出JSON格式”这个测试的角度看,它 已经绿了 。我们下一步的“Red”测试就可以是:“温度不能是硬编码的25度,需要调用真实的天气API获取。”
3. 利用AI的上下文理解能力 在对话式开发中(比如使用ChatGPT、Cursor等工具),你可以把整个对话历史看作上下文。当你指出“Red”并请求“Green”时,AI会基于之前的对话历史来理解当前任务所处的阶段,从而给出更相关的修正。这就要求我们在对话中保持逻辑的连贯性,清晰地标记出哪个是测试(Red),哪个是期望结果(Green)。
3. 实战演练:手把手构建一个“会议纪要生成”Agent技能
让我们通过一个完整的例子,来看看如何应用Red-Green循环来开发一个实用的Agent技能。假设我们要开发一个技能: 它能读取一段会议录音的文字转录稿,并生成一份结构化的会议纪要 。
3.1 第一步:定义最原子化的“Red”测试(解析与会者)
我们不会一开始就说:“AI,给我写一个会议纪要生成器。” 那太模糊了。我们从最小的、可验证的功能点开始。
初始Prompt(定义第一个Red) : “我们来开发一个MeetingMinutesAgent技能。它的第一个子任务是:从会议转录文本中提取出所有 与会者姓名 。 这是第一次会议的转录文本片段: ‘大家好,我是张三。李四和王五也上线了吗?哦,看到王五了。还有赵六,你能听到吗?好的,我们开始吧。今天主要讨论Q2的产品计划…’ 请你编写这个子任务的逻辑或代码。输出要求: 只输出一个Python列表,列表里是所有出现的、像是人名的词语。例如,对于上面的输入,你期望的输出应该类似于 [‘张三’, ‘李四’, ‘王五’, ‘赵六’] 。不要输出任何其他解释文字。 ”
AI的第一次输出(可能遭遇Red) : AI可能会输出:
# 一些复杂的NLP代码,或者直接输出:
[‘张三’, ‘李四’, ‘王五’, ‘赵六’, ‘大家好’, ‘今天’, ‘Q2’]
或者更糟,它输出了一段解释文字,根本没有列表。
我们的反馈(确认Red并引导Green) : “你输出的列表里包含了‘大家好’、‘今天’、‘Q2’这些明显不是人名的词。这不符合‘像是人名的词语’这个要求。这是一个‘Red’测试失败。 请修正你的逻辑。一个简单的思路是:可以预设一个常见的中文姓氏列表,然后匹配文本中‘我是X’、‘X也上线了’、‘X,你能听到吗’这类模式。 现在,请只输出修正后的、能正确处理上面那段文本的Python代码或逻辑描述。 ”
AI的修正输出(实现Green) : AI可能会调整后输出:
import re
def extract_attendees(text):
# 一个简单的姓氏列表(实际中会更全)
common_surnames = [‘张’, ‘李’, ‘王’, ‘赵’, ‘刘’, ‘陈’]
# 匹配“我是张三”、“李四也”、“王五,”等模式
pattern = r‘(?:我是|呼叫|)\s*([{}][\u4e00-\u9fa5]{{1,2}})(?:\\s|,|。|$)’.format(‘’.join(common_surnames))
matches = re.findall(pattern, text)
# 去重
return list(set(matches))
# 测试用例
text = “大家好,我是张三。李四和王五也上线了吗?哦,看到王五了。还有赵六,你能听到吗?好的,我们开始吧。今天主要讨论Q2的产品计划…”
print(extract_attendees(text)) # 输出: [‘张三’, ‘李四’, ‘王五’, ‘赵六’]
现在,对于这个特定的测试输入,代码输出了正确的结果。 第一个Red-Green循环完成! 我们得到了一个虽然简单、但能工作的与会者提取函数。
3.2 第二步:引入新的“Red”测试,扩展能力(处理英文名)
现在,我们有了一个能处理中文名的“Green”状态。但我们知道它很脆弱。我们引入一个新的、肯定会让它失败的“Red”测试。
定义第二个Red : “好的,现在技能能处理基础中文名了。但这里有一个新的会议转录文本,它包含了英文名: ‘Hi everyone, this is Alice. Bob, are you there? And welcome Charlie from the design team. David will join us later.’ 请你用刚才的 extract_attendees 函数处理这段文本。我预测它会失败(Red),因为它无法识别英文名。请证实我的预测,并 然后修改函数,使其能同时提取常见的中文名和英文名(英文名通常由连续的英文字母组成,且出现在‘this is X‘, ‘X, are you‘, ‘welcome X‘等语境中)。输出修正后的完整函数代码。 ”
AI的响应与修正(新的Green) : AI会运行(或推理)发现确实提取不出 [‘Alice’, ‘Bob’, ‘Charlie’, ‘David’] 。然后它会修改正则表达式或逻辑,加入对英文名的识别。最终给出一个更强的、能处理中英文混合文本的函数。 第二个循环完成。
3.3 第三步:继续迭代,处理更复杂的“Red”(提取决议项)
按照同样的节奏,我们一步步增加复杂度:
- 新的Red :定义“从讨论中提取‘决议’或‘行动项’”的测试。例如,输入文本包含“我们决定将项目截止日期推迟到下周。”,期望输出
[{‘action’: ‘推迟项目截止日期’, ‘owner’: ‘团队’, ‘deadline’: ‘下周’}]。初始的函数肯定无法处理,状态为Red。 - 实现Green :引导AI修改或新增函数,使用关键词匹配(如“决定”、“同意”、“将要做”)和简单解析,让这个测试用例通过。
- 新的Red :测试更复杂的决议,如“张三负责在下周五前完成原型设计,李四需要同步更新文档。” 要求解析出多个行动项及其负责人、截止日期。再次进入Red。
- 实现Green :AI进一步优化解析逻辑,可能引入更复杂的句法分析或命名实体识别(NER)思路。
通过这样一连串的Red-Green小循环,我们像搭积木一样,从一个只能提取人名的脆弱脚本,逐步构建出一个功能越来越强大的会议纪要生成器。每一个循环都是可控的,每一次“失败”都有明确的原因和修正方向,AI始终在解决一个具体而微的问题,而不是面对一个模糊的、令人畏惧的宏大目标。
4. 在主流Agent框架中实践Red-Green开发模式
Red-Green是一种思想,它可以应用在任何AI交互场景中。下面看看如何在不同的具体工具或框架中运用它。
4.1 在ChatGPT/Cursor/Claude等对话AI中
这是最直接的应用场景,正如上面的实战演练所示。关键在于 对话的管理 :
- 为每个循环新建对话或清晰分隔 :对于复杂的技能,建议为每个主要功能点(如“提取与会者”、“提取议程”、“生成摘要”)开启新的对话线程,或者在同一个对话中用明显的标记(如“## 任务一:提取与会者 ##”)分隔。避免上下文过长导致AI遗忘最早的需求。
- 精确提供“错误”信息 :当AI输出不符合预期时,不要笼统地说“不对”。要把它的输出贴出来,明确指出哪一部分错了,为什么错(违反了哪条Spec)。例如:“你生成的JSON中,‘temperature’字段的值是‘25度’,这是一个字符串。但Spec里定义的是数字类型。请修正。”
- 善用“系统提示词”固化Spec :在对话开始时,可以用系统提示词(或第一条用户消息)来设定角色和核心规范,这相当于为整个对话定义了基础的“测试套件”。例如:“你是一个Python代码助手。我们将采用测试驱动开发(TDD)模式。我首先会给你一个失败的测试用例(Red),你需要编写最少的代码使其通过(Green)。请只输出代码,不要解释。”
4.2 在LangChain/LlamaIndex等开发框架中
这些框架用于构建可编程的AI应用。Red-Green思想可以融入其开发流程:
- 为Chain或Agent编写单元测试 :使用Python的
unittest或pytest框架,为你构建的LangChain Chain或Agent编写测试用例。每个测试用例就是一个“Red”定义(在实现前运行会失败)。# test_meeting_agent.py - 这是一个“Red”测试文件 def test_extract_attendees(): # 1. 准备输入 transcript = “大家好,我是张三...” # 2. 调用你将要实现的组件(此时会失败,因为组件不存在或功能不全) result = meeting_agent.extract_attendees(transcript) # 3. 断言期望结果 assert result == [‘张三’, ‘李四’, ‘王五’, ‘赵六’] # 这个断言目前会失败,状态为Red - 实现组件使测试变Green :然后你去实现
meeting_agent.extract_attendees函数,直到pytest命令运行这个测试通过(输出绿色对勾)。 - 利用LLM本身作为测试器 :你甚至可以构建一个“元Agent”,它的任务是评估你的主Agent的输出是否符合某个Spec。这个“元Agent”的提示词就是你的测试标准,它的输出(是否符合)就是你的Red/Green信号。
4.3 在可视化流程工具(如Node-RED)中
对于使用Node-RED这类低代码/可视化工具来编排AI流程的场景,Red-Green同样适用:
- 单个节点的测试 :为你添加的每个功能节点(比如一个调用OpenAI API的节点)设计特定的输入。如果它的输出不符合下游节点的期望格式,那么这个节点就是“Red”。你需要调整这个节点的配置(如提示词、解析函数)直到输出正确(Green)。
- 子流程的测试 :将一段连接好的节点群看作一个子流程。给定一个输入,检查最终输出。如果不对,就逐个节点检查中间结果,定位是哪个节点导致了“Red”,然后修复它。
- 利用Debug节点 :Node-RED的Debug节点是你的最佳盟友。在每个关键节点后连接一个Debug节点,查看流经的数据。数据不对(Red)?那就回溯检查。
5. 高级技巧与常见陷阱:让Red-Green循环真正高效
掌握了基本循环后,一些高级技巧和避坑指南能让你事半功倍。
5.1 技巧一:如何设计一个好的“Spec”作为Red的源头
Spec的质量直接决定了Red-Green循环的效率。一个好的Spec应该是:
- 机器可读(或至少易于判断) :尽量使用结构化数据(JSON Schema、函数签名、类型注解)来定义输入输出。例如,用JSON Schema描述Agent技能返回的数据格式,这样你可以用代码自动验证。
- 包含正面和反面例子 :不仅告诉AI“成功时应该什么样”,也要告诉它“失败时可能什么样”。例如:“有效的城市名:’北京‘, ’Shanghai‘。无效的城市名:’我家附近‘, ’123‘。”
- 分层级 :有一个总体的高级别Spec,然后每个功能模块有自己更详细的子Spec。这样便于拆分Red测试。
5.2 技巧二:当AI“听不懂”或“改偏了”怎么办?
这是最常见的挫折。AI可能误解了你的修正指令,引入了新的错误,或者干脆开始胡言乱语。
- 回退到上一步 :如果对话开始失控,最简单有效的方法是使用工具的“回到之前某条消息”功能(如果有),或者直接复制上一条成功的提示词和AI回复,开启一个新对话,从那个稳定的“Green”点重新开始。不要试图在已经混乱的上下文中纠偏,那往往更耗时。
- 更原子化地分解问题 :如果AI连续无法完成一个修正,说明你这一步可能还是太大了。试着把问题拆得更碎。比如,从“修改函数以支持中英文名”拆成“1. 修改正则表达式,使其能捕获‘this is X’模式中的英文名。2. 将捕获到的英文名加入返回列表。”
- 提供更具体的示例 :不只是说“这样不对”,而是给出一个“示范”。例如:“我希望你像这样处理:输入‘我是Alice’, 你应该提取出‘Alice’。请看这个示例代码片段…” 让AI模仿你的示例。
5.3 陷阱一:陷入“局部最优”的Green
AI为了通过当前的Red测试,可能会采取一种非常取巧、甚至扭曲逻辑的方式。比如,为了通过一个“输入‘苹果’返回‘fruit’”的测试,它可能直接写一个字典: {‘苹果’: ‘fruit’, ‘香蕉’: ‘fruit’, …} ,而不是去调用一个真正的分类模型。这虽然通过了当前测试,但完全不具备扩展性。
- 如何避免 :在下一个Red测试中,立即引入它无法用取巧方式通过的例子。比如紧接着测试“输入‘榴莲’返回什么?”。这样会迫使它转向实现更通用的逻辑。
5.4 陷阱二:过度依赖AI导致逻辑混乱
Red-Green是开发方法,不是把思考完全外包给AI。开发者必须保持清晰的架构设计主导权。
- 你的角色是系统架构师和产品经理 :你来定义Spec,你来拆分任务(定义一个个Red),你来评估AI的方案是否合理(即使它通过了Green)。AI是高效的执行者和创意助手,但不是决策者。如果AI提出的实现方案在架构上很糟糕(比如把所有逻辑写在一个巨大的函数里),你应该在Refactor阶段介入,要求它进行重构,或者你自己来重构。
6. 从Red-Green到持续演进:技能维护与迭代
当一个技能通过一系列Red-Green循环初步建成后,工作并未结束。技能的维护和新需求的加入,同样可以遵循这个模式。
场景:为会议纪要技能添加“情感分析”功能
- 定义新的Red :在现有的会议纪要JSON输出中,增加一个可选的
sentiment字段,用于描述会议的整体氛围(积极/消极/中性)。为一段新的转录文本编写测试,期望输出包含这个字段。当前技能显然没有这个字段,状态为Red。 - 实现Green :引导AI修改代码,在生成摘要时,调用一个情感分析API(或内置一个简单的基于关键词的分析),并将结果填入
sentiment字段。 - Refactor :检查新增的情感分析模块是否与原有代码耦合过紧,考虑将其抽象为一个独立的函数或类。
通过这种方式,你可以像维护传统软件一样,以可预测、低风险的方式维护和扩展你的AI Agent技能。每一个新功能或每一次Bug修复,都是一个独立的Red-Green循环。
在我自己开发多个AI Agent项目的经历中,放弃“一次性交付”的幻想,转而拥抱“小步快跑”的Red-Green循环,是效率提升最显著的一次转变。它把与AI协作中那种模糊的、令人焦虑的“猜谜游戏”,变成了一个清晰的、可管理的“问题解决流水线”。你不再祈祷AI能一次性读懂你的心思,而是通过一个又一个微小的、成功的交互,像雕刻一样,共同将想法变成现实。这不仅是开发方法的改变,更是与AI协作心智模式的根本性升级。
更多推荐

所有评论(0)