OpenHands 实战:TaoToken 跑通仓库级 Python 类型标注修复
2026/9/20 22:27:13 网站建设 项目流程

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度

1. 让 OpenHands 自己修 mypy 报错,这件事比想象中省心

OpenHands 是一个开源的软件工程 Agent,能读写仓库文件、跑命令、看报错、再改代码,适合把「仓库级」的重复劳动交给它。这次我拿一个带 mypy 报错的 Python CLI 小仓库做实验,让它自动补类型标注、把 CI 里的类型检查跑绿。模型选 Kimi K2.7 Code,接入走 TaoToken,base_url 填https://taotoken.net/api。适合谁?手上有中小型 Python 仓库、CI 卡在 mypy、又不想一条条手改标注的人。整条链路是:拿 Key → 配 OpenHands → 给 Agent 下任务 → 看它改文件跑命令 → 验证 mypy 输出。下面把可复现的配置、命令和前后输出都摊开。

2. 准备仓库与复现 mypy 报错

先造一个最小可复现的 CLI 仓库。用 click 写两个子命令,故意留类型问题:函数没标注、返回值类型对不上、可选参数没处理 None。

mkdir -p demo-cli/demo_cli && cd demo-cli python -m venv .venv && source .venv/bin/activate pip install click mypy

目录结构:

demo-cli/ ├── demo_cli/ │ ├── __init__.py │ └── main.py ├── pyproject.toml └── .github/workflows/ci.yml

demo_cli/main.py故意写成有问题的版本:

import click def add(a, b): return a + b def greet(name=None): return "Hello, " + name @click.group() def cli(): pass @cli.command() @click.option("--a", type=int, required=True) @click.option("--b", type=int, required=True) def add_cmd(a, b): click.echo(add(a, b)) @cli.command() @click.option("--name", default=None) def hello(name): click.echo(greet(name)) if __name__ == "__main__": cli()

pyproject.toml里加上 mypy 配置,方便 CI 直接调用:

[tool.mypy] python_version = "3.11" strict = true warn_unused_ignores = true

跑一次 mypy,拿到「修复前」的基线输出:

mypy demo_cli

典型报错会是这样(版本不同行号略有差异):

demo_cli/main.py:4: error: Function is missing a type annotation [no-untyped-def] demo_cli/main.py:8: error: Function is missing a type annotation [no-untyped-def] demo_cli/main.py:9: error: Unsupported operand types for + ("str" and "None") [operator] demo_cli/main.py:13: error: Function is missing a type annotation [no-untyped-def] demo_cli/main.py:21: error: Function is missing a type annotation [no-untyped-def] demo_cli/main.py:26: error: Function is missing a type annotation [no-untyped-def] Found 6 errors in 1 file (checked 1 source file)

CI 文件里加一步mypy demo_cli,这样 Agent 改完能直接看 CI 是否通过:

name: ci on: [push, pull_request] jobs: typecheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" - run: pip install click mypy - run: mypy demo_cli

到这一步,仓库和报错都齐了,接下来把 OpenHands 接上模型。

3. 拿 Key 并配置 OpenHands 的 LLM

先去 TaoToken 官网拿 Key:打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_generate&utm_content= ,注册后在控制台创建 API Key。控制台入口在 https://taotoken.net/console?utm_source=taotoken_aicg_blog_generate&utm_campaign=generate ,Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_generate&utm_campaign=generate 。拿到的 Key 形如sk-...,先存到环境变量,别写进仓库:

export TAOTOKEN_API_KEY="sk-你的key"

OpenHands 的 LLM 配置写在config.toml。关键点是把base_url指向https://taotoken.net/api,模型名填 Kimi K2.7 Code 对应的标识。下面是我实测能跑通的配置:

[core] workspace_base = "./workspace" max_iterations = 30 [llm] model = "kimi-k2.7-code" api_key = "sk-你的key" base_url = "https://taotoken.net/api" temperature = 0.2 max_output_tokens = 8192

如果你不想把 Key 明文写进文件,OpenHands 支持从环境变量读,把api_key那行换成引用即可,具体字段名以你本地 OpenHands 版本的文档为准。配置好后先做一次连通性检查,避免后面 Agent 跑到一半才发现鉴权失败:

curl -s https://taotoken.net/api/models \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" | head -c 400

返回里能看到模型列表就说明 Key 和 base_url 都对。若返回 401,多半是 Key 复制时带了空格或已失效;返回 404,检查 base_url 是否误写成带路径的地址。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_generate&utm_campaign=generate ,字段有疑问时对照一下。

4. 让 Agent 跑仓库级类型修复

OpenHands 的启动方式按你安装的版本走,命令行入口一般是openhands。把工作目录指到仓库根,让它能读写demo_cli

cd demo-cli openhands --config ../config.toml

进入交互后,把任务描述清楚。仓库级修复最怕指令含糊,我用的提示词是这样的:

仓库根目录是当前目录。任务: 1. 运行 `mypy demo_cli`,记录所有报错。 2. 为 demo_cli/main.py 中所有函数补全类型标注,保持 click 命令行为不变。 3. 修复 greet 在 name 为 None 时的类型错误,不要改变默认输出语义。 4. 每改完一轮重新运行 `mypy demo_cli`,直到 0 error。 5. 最后把 mypy 的最终输出贴出来。

Agent 会自己执行命令、读报错、改文件。它改完main.py后大致长这样:

from typing import Optional import click def add(a: int, b: int) -> int: return a + b def greet(name: Optional[str] = None) -> str: if name is None: return "Hello, stranger" return "Hello, " + name @click.group() def cli() -> None: pass @cli.command() @click.option("--a", type=int, required=True) @click.option("--b", type=int, required=True) def add_cmd(a: int, b: int) -> None: click.echo(add(a, b)) @cli.command() @click.option("--name", default=None) def hello(name: Optional[str]) -> None: click.echo(greet(name)) if __name__ == "__main__": cli()

注意greet的语义被显式定义了:name为 None 时返回Hello, stranger。这是 Agent 在「不改变默认输出语义」约束下做的合理选择,但你要在 review 时确认这符合预期。如果原逻辑其实不该有 None 分支,应该在提示词里写清楚,比如「name 为 None 时抛 ValueError」。

跑完后 mypy 输出变成:

Success: no issues found in 1 source file

CI 里的mypy demo_cli这一步也就绿了。整个过程 Agent 大概迭代了 3 到 5 轮,取决于报错数量和模型对 click 装饰器的理解。

5. 验证结果与失败分支

验证分三层。第一层,本地 mypy 零报错,上面已经看到。第二层,跑一遍 CLI 确认行为没坏:

python -m demo_cli.main add --a 2 --b 3 python -m demo_cli.main hello --name Tao python -m demo_cli.main hello

前两条分别输出5Hello, Tao,第三条输出Hello, stranger。第三层,把改动推到分支触发 CI,看 typecheck job 是否通过。

失败分支要提前想好。如果 Agent 反复改不对,常见原因是提示词没限定「不改变行为」,它会顺手重构;这时把约束写死,或者先让它只补标注、不动逻辑。如果 mypy 报的是第三方库缺 stub,Agent 可能去装types-*包,你要确认这不会污染依赖,必要时在提示词里禁止改pyproject.toml的依赖段。如果模型在长仓库里上下文吃紧,把任务拆成「先修 main.py,再修 utils.py」,一次只喂一个文件范围。还有一种情况是 Agent 改了文件但没重跑 mypy 就宣布完成,这时你手动跑一次mypy demo_cli就能戳破。

6. 成本、模型选择与边界

成本主要看 token 消耗。仓库级修复的输入包含文件内容和多轮命令输出,输出是补丁和命令,Kimi K2.7 Code 这类偏代码的模型在补丁生成上比较稳。具体单价和计费方式以 TaoToken 官网为准,别拿别处的报价套。模型选择上,纯类型标注修复对推理要求不算高,代码模型够用;如果仓库里有复杂泛型和协议,换更强的推理模型可能更省迭代轮次,但单价也更高,按仓库规模权衡。

限制也说清楚。OpenHands 能改文件、跑命令,但它不理解你的业务语义,greet的 None 分支就是例子,最终 review 必须人工过一遍。mypy strict 模式下有些报错需要引入TypeVarProtocol,Agent 未必一次写对,可能要你给个示例。CI 环境里的 Python 版本、依赖版本要和本地一致,否则本地绿了 CI 红。最后,Key 别提交进仓库,用环境变量或密钥管理,config.toml里如果写了明文 Key,记得加进.gitignore

🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询