Aider 实战:TaoToken 跑通 pytest 全红仓库的修复
告别海外账号与网络限制稳定直连全球优质大模型限时半价接入中。 点击领取海量免费额度1. 一个 pytest 全红的仓库交给 Aider 和 MiniMax M3手头这个仓库是我从旧项目里拆出来的一个小工具库功能不复杂但测试写得比较密。某次重构之后pytest直接变成一片红12 个用例里挂了 9 个报错集中在日期解析、边界值处理和两个 mock 断言上。这种仓库特别适合拿来测 Agent 类工具——失败原因明确、修复范围可控、diff 大小容易衡量不像那种一挂就是架构问题的项目Agent 进去只会越改越乱。这次我用 Aider 作为 Harness模型选 MiniMax M3供应商走 TaoToken。TaoToken 在这里的角色很单纯提供一把 Key 和一个兼容 Base URL让 Aider 能稳定调到 MiniMax M3我不需要为每个模型单独维护一套接入配置。Aider 负责读文件、生成 patch、跑测试、提交 gitMiniMax M3 负责推理怎么改。整条链路跑完我要看的是三样东西aider 命令、pytest 前后输出、最终 diff。先说结论9 个失败用例最后修掉 8 个剩下 1 个是测试本身写错了断言Aider 没有硬改源码去迁就它而是把测试里的期望值修正了。最终 diff 一共 47 行涉及 4 个文件没有出现大段重写。这个结果对「保持 diff 最小」这个目标来说是可以接受的。2. 环境准备从 TaoToken 拿 Key把 Aider 指到统一 Base URL2.1 安装 Aider 和确认版本Aider 的安装方式我用的是 pipx隔离环境干净一些pipx install aider-chat aider --version版本号这里不写死因为 Aider 迭代很快你装到的很可能比我新。关键是确认aider命令能跑起来并且支持--openai-api-base这类参数。Aider 对 OpenAI 兼容接口的支持是通过--openai-api-base和--openai-api-key两个参数走的MiniMax M3 在 TaoToken 上就是按这个协议暴露的。2.2 创建 Key打开 TaoToken 控制台 创建一把 API Key复制出来。这个 Key 后面会同时给 Aider 和 curl 验证用。创建的时候注意选对项目别把测试 Key 和生产 Key 混在一起后面看用量会分不清。2.3 确认模型 ID模型 ID 不要凭记忆写。打开 模型广场 找到 MiniMax M3 对应的条目把 ID 原样复制。不同通道对同一个模型的命名可能不一样写错了 Aider 会直接报 404而且报错信息不一定告诉你「模型不存在」可能只说请求失败排查起来很烦。2.4 配置 AiderAider 的配置可以走环境变量也可以走命令行参数。我习惯用命令行参数因为这样每次跑的时候配置是显式的不会因为 shell 里残留的旧变量串味export TAOTOKEN_API_KEYYOUR_API_KEY aider \ --openai-api-base https://taotoken.net/api \ --openai-api-key $TAOTOKEN_API_KEY \ --model openai/MiniMax-M3 \ --no-auto-commits几个点解释一下。--openai-api-base填https://taotoken.net/api末尾不要加/v1Aider 会自己拼路径。--model前面的openai/前缀是告诉 Aider 走 OpenAI 兼容协议后面的模型 ID 以广场为准。--no-auto-commits是我个人的习惯我想先看 diff 再决定要不要提交避免 Agent 改错了还自动 commit 进去。如果你更习惯用配置文件可以在项目根目录放一个.aider.conf.ymlopenai-api-base: https://taotoken.net/api openai-api-key: YOUR_API_KEY model: openai/MiniMax-M3 auto-commits: false注意.aider.conf.yml里不要提交到 gitKey 泄露了要立刻去控制台吊销重建。2.5 先验证通道通不通在正式让 Aider 改代码之前先用 curl 打一发确认 Key 和 Base URL 没问题curl https://taotoken.net/api/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: MiniMax-M3, messages: [{role: user, content: reply with ok}] }返回里能看到choices就说明通道是通的。这一步能省掉后面很多「到底是 Aider 配错了还是 Key 有问题」的扯皮。注意这个 curl 里的 URL 是https://taotoken.net/api加上接口路径不要在这里加 UTM 参数UTM 是给浏览器落地页用的加到 API 请求上没意义还可能被网关拒掉。3. 任务目标9 个失败用例diff 越小越好3.1 仓库结构仓库不大结构是这样datekit/ ├── datekit/ │ ├── __init__.py │ ├── parser.py │ ├── range.py │ └── format.py ├── tests/ │ ├── test_parser.py │ ├── test_range.py │ └── test_format.py └── pyproject.tomlparser.py负责把各种格式的日期字符串解析成date对象range.py负责区间计算format.py负责输出格式化。测试覆盖了正常路径、边界值和异常输入。3.2 跑一遍 pytest 看全红现场先跑一次基线pytest -q输出大概是这样我截了关键部分FAILED tests/test_parser.py::test_parse_iso_with_timezone - ValueError: unconverted data remains: 08:00 FAILED tests/test_parser.py::test_parse_slash_format - ValueError: time data 2024/03/15 does not match format %Y-%m-%d FAILED tests/test_parser.py::test_parse_empty_string - AssertionError: assert None is not None FAILED tests/test_range.py::test_range_overlap_touching - AssertionError: assert False is True FAILED tests/test_range.py::test_range_contains_boundary - AssertionError: assert False is True FAILED tests/test_range.py::test_range_negative_days - ValueError: days must be positive FAILED tests/test_format.py::test_format_compact - AssertionError: assert 2024-03-15 20240315 FAILED tests/test_format.py::test_format_with_weekday - KeyError: weekday FAILED tests/test_format.py::test_format_locale_fallback - AssertionError: assert en zh 9 failed, 3 passed in 0.42s9 个失败3 个通过。失败类型分三类解析格式不匹配、区间边界判断反了、格式化输出和预期不一致。这种分布对 Agent 比较友好因为每个失败用例的报错信息都指向了具体的函数和具体的断言不需要 Agent 去猜「哪里可能有问题」。3.3 给 Aider 的任务描述Aider 支持直接把测试文件加进上下文然后让它根据失败信息改代码。我的启动命令是aider \ --openai-api-base https://taotoken.net/api \ --openai-api-key $TAOTOKEN_API_KEY \ --model openai/MiniMax-M3 \ --no-auto-commits \ datekit/parser.py datekit/range.py datekit/format.py \ tests/test_parser.py tests/test_range.py tests/test_format.py把源码和测试都加进去是因为有些失败其实是测试写错了Agent 需要能看到测试的断言才能判断该改源码还是改测试。如果只加源码Agent 可能会为了迁就一个错误的断言去改本来正确的逻辑diff 反而更大。进入 Aider 交互界面后我给的指令是pytest 有 9 个失败用例。请逐个修复优先改源码只有当测试断言本身写错时才改测试。 保持 diff 最小不要重构无关代码不要改函数签名。 修完后跑 pytest 确认全绿。这条指令里「保持 diff 最小」和「不要重构无关代码」是关键。如果不写Agent 很容易顺手把整个文件重写一遍虽然测试能过但 diff 会膨胀到几百行review 的时候根本看不出改了什么。4. 用 TaoToken 跑通修复Aider 的每一步和 pytest 的变化4.1 第一轮解析类失败Aider 先处理test_parser.py的三个失败。MiniMax M3 读完parser.py和测试文件后给出的 patch 大致是parse_iso_with_timezone原来的实现用datetime.strptime直接解析带时区的字符串遇到08:00就报unconverted data remains。改成先用datetime.fromisoformat失败再回退到strptime。parse_slash_format原来只支持%Y-%m-%d测试里传的是2024/03/15。加了一个格式列表按顺序尝试。parse_empty_string原来空字符串返回None测试期望抛ValueError。这里 Agent 判断是源码行为不符合测试预期改了源码。第一轮 patch 应用后Aider 自动跑了 pytest输出变成FAILED tests/test_range.py::test_range_overlap_touching - AssertionError: assert False is True FAILED tests/test_range.py::test_range_contains_boundary - AssertionError: assert False is True FAILED tests/test_range.py::test_range_negative_days - ValueError: days must be positive FAILED tests/test_format.py::test_format_compact - AssertionError: assert 2024-03-15 20240315 FAILED tests/test_format.py::test_format_with_weekday - KeyError: weekday FAILED tests/test_format.py::test_format_locale_fallback - AssertionError: assert en zh 6 failed, 6 passed in 0.38s解析类三个全过通过数从 3 涨到 6。这一步 diff 大概 18 行集中在parser.py。4.2 第二轮区间边界range.py的三个失败都是边界判断问题。test_range_overlap_touching期望两个首尾相接的区间算重叠原实现用的是严格小于改成小于等于。test_range_contains_boundary期望包含端点同样的问题。test_range_negative_days期望负数天数抛异常原实现没做校验。这三个改动都很小Agent 没有动其他逻辑。第二轮后FAILED tests/test_format.py::test_format_compact - AssertionError: assert 2024-03-15 20240315 FAILED tests/test_format.py::test_format_with_weekday - KeyError: weekday FAILED tests/test_format.py::test_format_locale_fallback - AssertionError: assert en zh 3 failed, 9 passed in 0.35s4.3 第三轮格式化输出format.py的三个失败里test_format_compact和test_format_with_weekday是源码问题test_format_locale_fallback是测试断言写错了——测试期望 locale 回退到zh但源码里默认就是en而且测试没有传 locale 参数这个断言本身就不合理。Agent 在这里做了一个我比较认可的判断它改了前两个的源码第三个改了测试断言并在回复里说明了理由。如果它硬改源码去让en变成zh反而会破坏默认行为。第三轮后9 passed in 0.31s全绿。4.4 最终 diff跑git diff --statdatekit/parser.py | 14 ----- datekit/range.py | 11 ---- datekit/format.py | 16 ----- tests/test_format.py | 6 --- 4 files changed, 32 insertions(), 15 deletions(-)47 行变更4 个文件。没有新增文件没有删除函数没有改签名。这个 diff 规模对于一个 9 失败的仓库来说算是克制的。具体 diff 内容节选关键部分--- a/datekit/parser.py b/datekit/parser.py -12,9 12,13 def parse_date(value: str) - date: if not value: - return None raise ValueError(empty date string) - return datetime.strptime(value, %Y-%m-%d).date() for fmt in (%Y-%m-%d, %Y/%m/%d): try: return datetime.strptime(value, fmt).date() except ValueError: continue return datetime.fromisoformat(value).date()--- a/datekit/range.py b/datekit/range.py -8,7 8,9 def overlaps(a: DateRange, b: DateRange) - bool: - return a.start b.end and b.start a.end return a.start b.end and b.start a.end -20,6 22,8 def contains(r: DateRange, d: date) - bool: - return r.start d r.end return r.start d r.end -30,6 34,8 def shift(r: DateRange, days: int) - DateRange: if days 0: raise ValueError(days must be positive) return DateRange(r.start timedelta(daysdays), r.end timedelta(daysdays))--- a/datekit/format.py b/datekit/format.py -5,7 5,9 def format_date(d: date, style: str iso) - str: - return d.strftime(%Y-%m-%d) if style compact: return d.strftime(%Y%m%d) return d.strftime(%Y-%m-%d) -15,6 17,8 def format_with_weekday(d: date) - str: - return d.strftime(%Y-%m-%d) weekday d.strftime(%A) return f{d.strftime(%Y-%m-%d)} {weekday}--- a/tests/test_format.py b/tests/test_format.py -22,7 22,7 def test_format_locale_fallback(): - assert format_date(date(2024, 3, 15), localezh) zh assert format_date(date(2024, 3, 15)) 2024-03-154.5 Token 和耗时这一轮跑下来Aider 和 MiniMax M3 之间的交互大概 6 轮输入 token 累计在 4 万左右输出 token 不到 3 千。耗时方面从启动 Aider 到 pytest 全绿大概 3 分钟出头其中大部分时间花在模型推理上本地 pytest 每次不到 0.5 秒。这些数字是我这一次运行的结果不代表任何公榜成绩也不代表 MiniMax M3 的平均水平。不同仓库、不同失败类型、不同指令措辞都会影响结果。如果你想看模型在公开榜单上的表现那是另一回事本文不含排行分数。5. 排障Aider 接 TaoToken 时我踩过的三个坑5.1 Base URL 末尾加了 /v1第一次配的时候我顺手写了https://taotoken.net/api/v1结果 Aider 报 404。原因是 Aider 自己会拼/chat/completions如果 Base URL 已经带了/v1最终路径就变成/api/v1/chat/completions和网关期望的路径对不上。改成https://taotoken.net/api就好了。这个坑很常见因为很多其他工具的 Base URL 是带/v1的切换过来的时候容易惯性写错。5.2 模型 ID 写成了显示名模型广场上 MiniMax M3 的显示名和实际 ID 可能不完全一样。我第一次直接把显示名填进--modelAider 报了一个比较含糊的错误。后来回广场复制了实际 ID 才通。所以配置里写模型 ID 的时候一定要以广场展示的为准不要自己拼。5.3 Aider 自动 commit 把中间状态提交了默认情况下 Aider 每改一轮就自动 commit。我第一轮跑的时候没加--no-auto-commits结果 git log 里多了 6 个 commit每个都是半成品状态。虽然后面可以 squash但 review 的时候很乱。加上--no-auto-commits之后所有改动都留在工作区我确认 diff 没问题再自己 commit 一次。5.4 401 的时候先查 Key 再查配置如果 Aider 报 401先确认 Key 有没有复制完整、有没有多余空格。然后确认--openai-api-key传的是 Key 本身不是Bearer xxx这种带前缀的字符串。Aider 会自己加Bearer你再加一遍就变成Bearer Bearer xxx网关直接拒。6. 用同一把 Key 复现这次修复如果你想在自己的仓库上复现这套流程步骤不复杂。先去 TaoToken 控制台 创建一把 Key然后在模型广场确认 MiniMax M3 的 ID。Aider 的 Base URL 填https://taotoken.net/api模型填openai/加上广场上的 ID。跑之前先用 curl 验证通道再启动 Aider。指令里明确写「保持 diff 最小」和「不要重构无关代码」这两句对控制 diff 规模很有效。跑完 pytest 全绿之后先看git diff --stat如果变更行数远超失败用例数说明 Agent 可能顺手改了不该改的东西回退重跑把指令写得更具体。这次修复调用是否入账可以打开 模型对话 看用量记录。长期跑这类 Agent 任务的话Coding Plan 比按次调用更划算。Claude Code 和 CC Switch 的接入配置可以参考 接入文档三件套的填法和 Aider 这里的思路是一样的Base URL 用统一网关Key 从控制台拿模型 ID 以广场为准。 告别海外账号与网络限制稳定直连全球优质大模型限时半价接入中。 点击领取海量免费额度