🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度
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、跑测试、提交 git,MiniMax M3 负责推理怎么改。整条链路跑完,我要看的是三样东西:aider 命令、pytest 前后输出、最终 diff。
先说结论:9 个失败用例最后修掉 8 个,剩下 1 个是测试本身写错了断言,Aider 没有硬改源码去迁就它,而是把测试里的期望值修正了。最终 diff 一共 47 行,涉及 4 个文件,没有出现大段重写。这个结果对「保持 diff 最小」这个目标来说是可以接受的。
2. 环境准备:从 TaoToken 拿 Key,把 Aider 指到统一 Base URL
2.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 配置 Aider
Aider 的配置可以走环境变量,也可以走命令行参数。我习惯用命令行参数,因为这样每次跑的时候配置是显式的,不会因为 shell 里残留的旧变量串味:
export TAOTOKEN_API_KEY="YOUR_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,末尾不要加/v1,Aider 会自己拼路径。--model前面的openai/前缀是告诉 Aider 走 OpenAI 兼容协议,后面的模型 ID 以广场为准。--no-auto-commits是我个人的习惯,我想先看 diff 再决定要不要提交,避免 Agent 改错了还自动 commit 进去。
如果你更习惯用配置文件,可以在项目根目录放一个.aider.conf.yml:
openai-api-base: https://taotoken.net/api openai-api-key: YOUR_API_KEY model: openai/MiniMax-M3 auto-commits: false注意.aider.conf.yml里不要提交到 git,Key 泄露了要立刻去控制台吊销重建。
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 --stat:
datekit/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(days=days), r.end + timedelta(days=days))--- 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), locale="zh") == "zh" + assert format_date(date(2024, 3, 15)) == "2024-03-15"4.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 可能不完全一样。我第一次直接把显示名填进--model,Aider 报了一个比较含糊的错误。后来回广场复制了实际 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 以广场为准。
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度