AI Agent开发新范式:用TDD小步交付构建可靠智能体技能

📅 2026/8/5 4:32:00
AI Agent开发新范式:用TDD小步交付构建可靠智能体技能
1. 项目概述从“大而全”到“小而精”的AI开发范式转变最近在折腾AI Agent开发特别是那些需要执行复杂、多步骤任务的智能体时我发现一个普遍存在的“陷阱”我们总倾向于给AI下一个宏大的指令比如“帮我写一个完整的用户注册模块包含前端表单、后端API和数据库操作”然后坐等它生成几百行代码。结果往往是生成的代码要么跑不起来要么逻辑有严重缺陷调试起来如同大海捞针时间全耗在了“猜AI心思”和“缝缝补补”上。这让我想起了软件开发领域一个古老但极其有效的实践——测试驱动开发TDD以及它的核心节奏Red - Green - Refactor。这个项目就是将TDD的“小步快跑、快速反馈”哲学系统地引入到AI Agent的技能Skill开发流程中。简单来说“别让AI一口气写完”是一种开发策略的警示而“按Red - Green小步交付”则是具体的行动指南。它要求我们把一个庞大的任务Spec分解成一系列微小、可验证的步骤然后引导AI像一名严谨的开发者一样为每一步先写一个会失败的测试Red再实现刚好能让测试通过的功能Green如此循环直至完成整个功能。这不仅仅是让代码更可靠更深层的是在训练我们与AI协作的“肌肉记忆”建立一种可预测、可控制、高质量的合作模式。无论你是刚开始接触Agent开发的初学者还是已经构建过复杂智能体的资深玩家掌握这套方法都能显著提升你的开发效率与产出质量让你从“魔法调试”走向“工程化开发”。2. 核心理念拆解为什么“小步交付”对AI开发至关重要2.1 传统“一口气写完”模式的三大痛点在深入Red-Green流程之前我们必须先认清旧模式的弊端。当你要求AI生成一个完整功能时其实是在进行一场高风险赌博。痛点一反馈周期过长调试成本指数级上升。AI一次性吐出的代码量可能非常大其中任何一个环节出错——可能是业务逻辑误解、API调用方式错误、甚至是简单的语法问题——都会导致整个模块无法运行。你需要从头开始阅读、理解AI生成的“黑盒”代码定位问题如同在迷宫中寻找出口。这个反馈周期从发出指令到看到错误结果可能长达几十分钟甚至更久极大地消耗了开发者的耐心和精力。痛点二上下文丢失与逻辑断层。AI在生成长篇代码时其“注意力”是有限的。它可能会在生成到第200行时完全忘记了第50行设定的某个关键约束条件。这会导致生成的代码前后不一致存在逻辑断层。例如前面定义了一个用户状态枚举后面却使用了完全不同的状态值进行判断。这种错误非常隐蔽单靠人工审查极难发现。痛点三难以进行增量验证与演进。软件需求是经常变化的。如果一开始就生成了一个庞大、紧耦合的代码块当需求发生细微调整时你往往需要推倒重来或者进行风险极高的“外科手术式”修改。因为你没有一套自动化的测试来保障修改不会破坏现有功能每一次改动都心惊胆战。2.2 Red-Green小步交付的核心优势相比之下Red-Green模式将上述痛点一一化解。优势一即时反馈问题被就地解决。每一步一个微小的功能点都对应一个测试。AI先写出这个测试此时运行会失败状态为Red然后立即实现功能代码让测试通过状态为Green。如果实现有误测试会立刻失败反馈周期缩短到几秒之内。问题被限制在最小的上下文中定位和修复变得极其简单。优势二强制澄清需求建立可执行的“契约”。在让AI写测试Red之前你必须非常清晰地定义这个微小步骤的输入、输出和行为边界。这个测试本身就是一份可执行的、无歧义的“需求规格说明书”Spec。AI在实现Green时目标非常明确就是让这个测试通过。这极大地减少了因需求理解偏差导致的返工。优势三自然形成安全网支持 fearless change。随着一个个Green状态的积累你就构建起了一个不断增长的自动化测试套件。这套测试成为了代码的“安全网”。当你后续需要重构代码、添加新功能或修改需求时可以自信地运行这些测试。如果测试全部通过你就有高度的信心认为现有功能未被破坏。这为代码的持续演进奠定了坚实基础。优势四优化AI的“思考”过程。让AI进行小步骤的、目标明确的编码比让它进行天马行空的长篇创作更能发挥其当前的技术优势。它更擅长在有限上下文中进行精确的模式匹配和补全而不是进行需要长期记忆和复杂规划的逻辑推理。Red-Green流程正是在引导AI做它更擅长的事。3. 实战框架搭建将TDD节奏融入Agent Skill开发理解了“为什么”接下来我们看“怎么做”。将Red-Green流程应用于Agent Skill开发需要一套清晰的框架和约定。这里我结合实践总结出一个四阶段循环模型。3.1 阶段一任务分解与微规格Micro-Spec定义这是整个流程的起点也是最考验开发者设计能力的一环。你不能直接把“构建用户系统”扔给AI。你需要像产品经理一样对其进行逐层拆解。1. 横向功能切片首先从用户价值流的角度将大功能切成独立的、可交付的薄片。例如“用户注册”可以切分为片1接收用户名、邮箱、密码的HTTP API端点输入验证。片2检查用户名和邮箱是否已存在的业务逻辑。片3密码加密存储。片4生成并返回用户唯一ID。2. 纵向测试用例定义对每一个“薄片”定义其具体的、可测试的行为。这就是我们的“微规格”Micro-Spec。一个好的Micro-Spec应该符合“Given-When-Then”格式Given给定初始状态或上下文。例如“给定一个空的用户数据库”。When当执行的操作。例如“当调用注册API传入合法的用户名‘testuser’和邮箱‘testexample.com’”。Then那么预期的结果。例如“那么应该返回成功状态码201并在响应体中包含新创建的用户ID且该用户信息已存入数据库”。实操心得在这一步我强烈建议使用纯文本或简单的Markdown列表来编写Micro-Spec并和AI共享这个上下文。你可以这样对AI说“接下来我们将实现用户注册功能的第一个切片。这是我们的Micro-Spec1. 给定一个空的用户数据库2. 当调用POST/api/users 传入{“username”: “alice”, “email”: “aliceexample.com”, “password”: “Secret123!”}3. 那么应该返回状态码201JSON响应体包含{“userId”: “某个UUID”}并且数据库中有一条对应的用户记录密码需加密。请首先为此Micro-Spec编写一个会失败的单元测试Red。”3.2 阶段二驱动AI编写失败测试Red在这个阶段你的角色是“测试驱动者”。你引导AI根据上一步的Micro-Spec生成一个具体的、可运行的单元测试。这个测试在初始状态下必须失败。关键指令模式“基于上述Micro-Spec请使用 [你选择的测试框架如Jest for JavaScript, pytest for Python] 编写一个单元测试。该测试应该验证registerUser函数的行为。目前registerUser函数尚未实现因此这个测试运行起来应该是失败的Red状态。请只输出测试代码。”AI可能生成的示例Python pytest# test_user_registration.py import pytest from your_module import registerUser, UserModel # 假设的模块和模型 def test_register_user_with_valid_data(): 测试使用有效数据注册用户应成功。 Micro-Spec: 空数据库 - 调用注册 - 返回201和userId数据入库。 # Given: 清空或模拟一个空的数据库这里用简单列表模拟 UserModel.clear_all() # 假设有这个方法 # When: 调用注册函数 user_data { username: alice, email: aliceexample.com, password: Secret123! } result registerUser(user_data) # Then: 断言结果 assert result[status_code] 201 assert userId in result[body] assert isinstance(result[body][userId], str) and len(result[body][userId]) 0 # 检查数据库这里简化 stored_user UserModel.find_by_username(alice) assert stored_user is not None assert stored_user.email aliceexample.com # 注意不应断言明文密码 assert stored_user.password ! Secret123! # 密码应被加密运行这个测试毫无疑问会失败因为registerUser函数还不存在。这就是我们想要的Red状态。注意事项你需要检查AI生成的测试是否准确反映了Micro-Spec并且是“可运行”的例如导入路径正确。有时AI会过度设计生成依赖复杂Mock的测试。在初期我们应追求最简单、最直接的测试。3.3 阶段三引导AI实现通过测试Green现在我们进入“实现者”角色。目标非常单一编写最少、最简单的代码让上一步的测试通过。关键指令模式“现在请实现registerUser函数其接口应与测试中的调用方式匹配。目标是让刚刚编写的test_register_user_with_valid_data测试通过Green状态。请只输出实现该函数所需的代码保持实现尽可能简单。”AI可能生成的示例# user_service.py import uuid from some_password_lib import encrypt_password # 假设的加密库 # 模拟一个内存数据库 _in_memory_db [] class UserModel: staticmethod def clear_all(): global _in_memory_db _in_memory_db.clear() staticmethod def find_by_username(username): for user in _in_memory_db: if user[username] username: return user return None staticmethod def create(user_data): user_record { userId: str(uuid.uuid4()), username: user_data[username], email: user_data[email], password: encrypt_password(user_data[password]) # 密码加密 } _in_memory_db.append(user_record) return user_record def registerUser(user_data): # 简单实现仅满足当前测试 new_user UserModel.create(user_data) return { status_code: 201, body: { userId: new_user[userId] } }运行测试现在应该通过了Green。这一步的诀窍在于“刚好通过”。不要提前实现邮箱格式验证、密码强度检查等。那些将是后续Micro-Spec的内容。如果AI试图提前实现你需要明确制止“请只实现让当前测试通过的最简功能其他验证逻辑我们会在后续步骤中添加。”3.4 阶段四重构与循环测试通过后我们获得了“安全网”。现在可以审视刚刚写的代码看看是否有需要改进的地方例如代码重复、命名不清晰、结构不佳。在AI的辅助下进行重构。重构后再次运行测试确保它们依然保持Green状态。然后循环整个过程回到阶段一定义下一个Micro-Spec例如“当注册时用户名已存在应返回400错误”。进入阶段二让AI为这个新Spec编写一个新的、会失败的测试现在有两个测试一个Green一个新的Red。进入阶段三让AI修改registerUser函数使所有测试包括旧的都通过全部Green。进入阶段四必要时进行重构。如此往复像搭乐高一样一步步构建出健壮、可靠的功能模块。4. 核心环节实现一个完整的Agent Skill开发实录让我们通过一个更具体的例子串联整个流程。假设我们要开发一个“天气查询Agent Skill”它调用一个外部API获取天气并格式化回复。4.1 迭代一定义核心数据获取能力Micro-Spec 1.1给定一个城市名称如“北京”当调用天气获取函数时那么它应返回一个包含city、temperature温度、condition天气状况字段的字典。RedAI生成测试# test_weather_agent.py import pytest from weather_agent import get_weather_data def test_get_weather_returns_structured_data(): 测试get_weather_data函数返回正确的数据结构 result get_weather_data(北京) assert isinstance(result, dict) assert city in result assert result[city] 北京 assert temperature in result assert isinstance(result[temperature], (int, float)) assert condition in result assert isinstance(result[condition], str)运行失败因为get_weather_data未定义。GreenAI生成实现# weather_agent.py def get_weather_data(city_name): # 硬编码数据仅用于通过测试 mock_data { 北京: {city: 北京, temperature: 22, condition: 晴}, 上海: {city: 上海, temperature: 25, condition: 多云}, } return mock_data.get(city_name, {city: city_name, temperature: 0, condition: 未知})运行测试通过。4.2 迭代二引入真实的API调用Micro-Spec 2.1函数应能实际调用一个模拟的天气API端点例如一个本地Mock服务器或一个测试用的公开API并解析其返回的JSON数据。Red新增测试# test_weather_agent.py (新增) import responses # 使用responses库来Mock HTTP请求 import pytest responses.activate def test_get_weather_calls_real_api(): 测试get_weather_data会调用正确的API并解析响应 # Given: Mock一个外部API响应 mock_api_response { location: {name: 北京}, current: {temp_c: 22, condition: {text: 晴}} } responses.add( responses.GET, https://api.weatherapi.com/v1/current.json, jsonmock_api_response, status200 ) # When result get_weather_data(北京) # Then assert result[city] 北京 assert result[temperature] 22 assert result[condition] 晴 # 验证API确实被调用了 assert len(responses.calls) 1运行失败因为当前实现返回的是硬编码数据并未调用API。Green修改实现# weather_agent.py import requests import os WEATHER_API_KEY os.getenv(WEATHER_API_KEY, test-key) # 从环境变量读取 BASE_URL https://api.weatherapi.com/v1/current.json def get_weather_data(city_name): # 先尝试调用真实API在测试中会被Mock拦截 try: params {key: WEATHER_API_KEY, q: city_name, aqi: no} response requests.get(BASE_URL, paramsparams, timeout5) response.raise_for_status() data response.json() return { city: data[location][name], temperature: data[current][temp_c], condition: data[current][condition][text] } except (requests.RequestException, KeyError): # 失败时降级为之前的模拟数据 mock_data { 北京: {city: 北京, temperature: 22, condition: 晴}, 上海: {city: 上海, temperature: 25, condition: 多云}, } return mock_data.get(city_name, {city: city_name, temperature: 0, condition: 未知})运行所有测试通过。现在我们的Skill既能在测试环境下工作通过Mock又具备了真实调用API的能力。4.3 迭代三完善错误处理与边界情况Micro-Spec 3.1当城市名称不存在或API返回错误时函数应返回一个明确的错误指示而不是一个模拟的成功数据。Red修改旧测试或新增测试# test_weather_agent.py (新增) responses.activate def test_get_weather_handles_api_error_gracefully(): 测试API调用失败时返回明确的错误结构 # Given: Mock一个API失败响应 responses.add( responses.GET, https://api.weatherapi.com/v1/current.json, json{error: {message: City not found}}, status400 ) # When result get_weather_data(InvalidCity) # Then: 我们期望返回一个包含错误信息的结构而不是降级到模拟数据 assert error in result assert result[error] City not found # 或者我们设计函数在API错误时返回None或抛出异常运行失败因为当前实现在API错误时降级到模拟数据而不是返回错误信息。Green修改实现调整设计这时我们需要和AI讨论设计决策。是返回一个包含error字段的字典还是抛出异常这取决于Skill的整体错误处理策略。假设我们决定返回统一结构。# weather_agent.py def get_weather_data(city_name): try: params {key: WEATHER_API_KEY, q: city_name, aqi: no} response requests.get(BASE_URL, paramsparams, timeout5) response.raise_for_status() data response.json() # 检查API是否返回了业务错误如城市不存在 if error in data: return {city: city_name, error: data[error].get(message, Unknown API error)} return { city: data[location][name], temperature: data[current][temp_c], condition: data[current][condition][text], error: None } except requests.Timeout: return {city: city_name, error: Request timeout} except requests.RequestException as e: return {city: city_name, error: fNetwork error: {str(e)}} except (KeyError, ValueError) as e: return {city: city_name, error: fData parsing error: {str(e)}}同时需要更新之前的测试以匹配新的返回结构例如检查result.get(“error”) is None。运行所有测试确保它们依然通过。这就是重构的一部分。通过以上三个迭代我们从一个简单的硬编码函数逐步演进为一个具备真实API调用、健壮错误处理能力的核心Skill。每一步都有测试保障每一步的变更范围都很小风险可控。5. 常见问题与避坑指南在实际操作中即使遵循Red-Green流程也会遇到一些典型问题。这里记录下我踩过的坑和总结的技巧。5.1 AI不按Spec生成测试或代码问题你给出了清晰的Micro-Spec但AI生成的测试验证了错误的东西或者实现的代码逻辑完全跑偏。排查与解决检查Spec的清晰度你的Micro-Spec是否真正做到了“无歧义”使用“Given-When-Then”格式能极大改善这一点。避免使用“应该正常工作”这类模糊描述。提供更具体的示例在指令中直接给出你期望的测试函数签名或代码片段示例。例如“请编写一个名为test_user_login_with_correct_password的pytest函数它应该创建一个用户然后用正确的密码调用login函数并断言返回的session_token不为空。”分步引导如果AI一次理解不了就拆成更小的对话。先让它“只写测试的框架包含函数定义和assert False语句”然后再让它“填充测试的具体断言逻辑”。利用上下文确保AI记住了之前通过的测试代码风格和项目结构。在对话中适时引用之前的代码片段。5.2 测试过于脆弱或依赖过多问题AI生成的测试可能依赖系统时间、随机数、未隔离的外部服务导致测试时好时坏Flaky Tests。排查与解决强调“单元测试”在指令中明确要求“编写一个独立的单元测试”。要求它使用Mock、Stub来隔离外部依赖如数据库、API、文件系统。审查测试代码养成习惯仔细阅读AI生成的测试。检查是否有time.sleep()、直接调用requests.get()、使用真实数据库连接等。一旦发现立即要求AI重写并告诉它使用哪个Mock库如unittest.mock,pytest-mock,responses。固定测试数据要求测试使用固定的、确定性的输入数据避免随机性。5.3 重构时破坏现有功能问题在Green之后进行重构不小心引入了Bug导致之前的测试失败。排查与解决小步重构一次只做一项很小的重构比如重命名一个变量、提取一个只有两三行的小函数。完成一步立即运行全部测试。利用AI进行安全重构可以明确指示AI“我想将calculateTotalPrice函数中的税费计算逻辑提取到一个名为calculateTax的新函数中。请在不改变任何现有行为的前提下进行重构并确保所有现有测试仍然通过。” AI在代码变换方面通常很可靠。测试本身就是文档如果重构后测试失败失败的测试恰恰说明了你的重构在哪里改变了系统行为。仔细阅读测试失败信息判断这个行为改变是预期的说明你也在改功能这其实不是纯粹重构还是意外的。5.4 流程显得繁琐想走捷径问题感觉为每一个微小改动都写测试太慢了不如直接让AI生成大段代码。心态调整与技巧长远效率记住前期多花的几分钟写测试会在后期调试、修改、增加功能时节省数小时甚至数天。这是一种投资。工具辅助使用好的IDE测试通常可以一键运行。将“运行测试”绑定到一个简单的快捷键上让Red-Green的反馈循环在几秒内完成。并非所有代码都需要TDD对于一次性的、简单的、不会进入核心逻辑的脚本比如数据清洗可以不用严格TDD。但对于构成Agent核心能力的Skill、工具函数、业务逻辑Red-Green流程的价值是无可替代的。AI加速测试编写你不需要手动从头编写测试。你的工作是定义清晰的Micro-Spec然后让AI去生成测试代码。这本身已经比传统手动TDD快了很多。5.5 与现有项目或框架集成困难问题项目已经存在没有测试或者使用的是不熟悉的框架如特定的Agent框架LangChain、AutoGen等。策略从外围开始不要试图一次性为整个庞大系统添加测试。选择一个独立的、新开发的Skill或工具函数开始实践Red-Green流程。学习框架的测试方式先花一点时间让AI帮你搜索或生成一个该框架下简单的测试示例。例如“请展示一个如何使用pytest测试LangChain Tool的基本例子。” 有了这个模板你就可以依葫芦画瓢。为遗留代码添加“接缝测试”对于已有的、复杂的函数可以先为其添加一个简单的“集成测试”或“端到端测试”验证其整体输入输出。这虽然不算严格的单元测试但能提供一个初始的安全网。然后当你需要修改这个函数时可以围绕要修改的部分用Red-Green流程添加更精细的测试。将Red-Green小步交付融入AI Agent开发初期会感觉有些约束但一旦习惯你会发现自己对代码的控制力、对AI输出的信心以及对项目进度的把握都达到了一个新的水平。这不再是“祈祷AI能一次生成对的代码”而是“引导AI和我一起用可验证、可重复的方式稳健地构建复杂系统”。这种转变正是从AI魔术师走向AI工程师的关键一步。