☰
从零构建命令行AI编程助手:免费模型接入与安全实践
2026/10/19 6:33:17 网站建设 项目流程

最近两周,我一直在折腾一个叫 free-claude-code 的终端项目。这个名字是我自己起的代号,代码里没有任何与具体商业产品绑定的部分,它想解决的事情其实很朴实:能不能不绑卡、不开订阅,只靠免费的模型推理额度,在命令行里做出一个能读项目、能改文件、能跑测试的编程助手。今天这篇就完整记录一下我从零到一实现它的过程,包括整体架构、核心协议、接入低成本模型的方式,以及那些做的时候踩得很痛的坑。如果你也想在日常脚本和小项目里用上“会动手改代码”的 AI 助手,这篇应该能帮你省不少时间。

我把这个项目分成两个阶段:先让它“能跑”,再让它“敢干活”。第一阶段只做一件事,把模型接到命令行里,让它能回答关于代码库的问题;第二阶段才加入文件修改、命令执行这些危险操作,并且每一步都让用户确认。第二个阶段才是这个项目真正有意思的地方,也是踩坑最密集的地方。下面我会按照我的实现顺序来讲,而不是按照教科书里的软件工程分层来讲,因为这样更贴近你实际动手时会遇到的情况。

1. 先说清楚 free-claude-code 到底想解决什么问题

1.1 大模型编程助手的几道门槛

现在开发圈子里,“让 AI 自己改代码”已经不算新鲜事了。但如果你真的想在自己电脑上用,很快会发现几道门槛。

第一道是订阅门槛。成熟的商业 AI 编程助手,通常是按月订阅、按用量付费,有的还要绑定支付方式才能开通免费试用。很多人的需求只是周末写写脚本、给开源小项目补个测试,为这个额外付一笔钱,确实不划算。

第二道是环境门槛。不少助手是做成 IDE 插件的,必须打开图形界面才能用。可我们这些经常在服务器、远程终端、CI 环境里工作的人,更需要的是一条命令行工具:在终端里敲一句话,它就能读目录、改文件、执行命令,整个过程不需要鼠标。

第三道是自由度门槛。商业产品通常锁定了模型、锁定了交互方式,你没法把本地跑的小模型接进去,也没法在它每次改文件前强行插入自己的检查逻辑。我理想中的工具应该是“模型可换、操作可控、日志可查”,而不是一个黑盒。

free-claude-code 就是冲着这三道门槛去的。它不追求像商业产品那样开箱即用,而是把核心能力拆成一块块可替换的零件,让每个使用者都能按自己的模型资源和安全偏好组装。

1.2 我想要的“免费平替”长什么样

动手之前,我先给这个工具列了一个能力清单:

  • 能在终端启动一个交互式会话,用户用自然语言描述任务;
  • 能递归扫描项目目录,生成文件树和代码摘要;
  • 能根据用户指令,决定执行哪些工具操作,比如读取文件、修改文件、执行命令;
  • 能在修改文件前展示 diff,等用户确认后再落盘;
  • 能接入多种模型端点,本地模型、在线免费推理额度都能用;
  • 所有日志都能输出到文件,方便排查“它到底为什么改成这样”。

这个清单不算激进。我没有要求它自己规划复杂任务、自己定位 bug、自动跑完一整套测试,那些可以做,但优先级不高。第一个版本只要能稳定地完成“读代码-改代码-跑命令-反馈结果”这个闭环,就已经能解决很多实际问题了。

很多类似的工具失败,不是因为模型不够聪明,而是因为工程细节太粗糙:上下文塞满导致模型忽略关键文件、危险命令没有确认直接执行、流式输出解析失败导致一连串重试。我的思路很明确,先把这些工程细节做扎实,模型能力反而可以往后放。

1.3 它能做什么,不能做什么

用了一段时间之后,我对 free-claude-code 的定位越来越清晰。

它擅长的事情有三类:跨文件搜索和定位问题、做局部代码修改并补测试、解释一段陌生代码的逻辑。比如你丢给它一个报错日志,它能顺着日志找到对应文件,再定位到可疑的函数,然后提出修改建议,这时你只要输入“改”,它就自己完成修改并展示 diff。

它不擅长的事情也很多:大范围重构、需要多人评审的架构决策、超过模型上下文窗口的长文件处理、以及任何需要领域知识而提示词里又没有写清楚的任务。这些不是工具缺陷,而是当前模型能力边界。

我的建议是:把 free-claude-code 当成一个“手脚麻利的实习生”,而不是“可以托付整个项目的资深工程师”。你给它足够上下文,它就能干活;你不给它上下文,它就瞎猜。

2. 整体设计:五层架构与一条指令的完整旅程

2.1 模块划分:每一层只干一件事

free-claude-code 是一个 Python 项目,目录结构谈不上多高级,但模块边界是清晰的。我把整个系统分成五层,从外到内依次是输入层、上下文层、决策层、工具层和确认层。

输入层负责和用户打交道。它接收命令行参数,也支持交互式 REPL。用户在终端里输入“帮我在 src/utils.py 里加一个 json 安全加载函数,并写单元测试”,这句话首先到达输入层,被解析成一条待处理的用户指令。

上下文层负责“喂料”。它扫描项目目录,生成文件树,读取关键文件内容,并把这些内容整理成模型能理解的结构化文本。这一层决定了模型“看到”什么,直接影响回答质量。我做的时候在上下文层花的时间最多,因为文件太多、token 预算太紧,怎么取舍是个反复试错的过程。

决策层是“大脑”。它把用户指令、上下文、工具描述拼装成一个请求,发给模型,然后解析模型返回的内容。如果模型返回的是普通文本,直接展示给用户;如果返回的是工具调用请求,就进入下一层。

工具层是“手脚”。它定义了一组可执行操作,比如 read_file、edit_file、run_command。每一层之间通过 JSON 传递数据,模型不知道工具的实现细节,工具也不知道模型来自哪家厂商。

确认层则是“刹车”。所有涉及文件写入和命令执行的操作,都会在这里暂停,向用户展示将要执行的命令或 diff,等用户确认后才真正执行。

分层的最大好处是调试容易。哪个环节出问题,直接看哪个模块的日志就行,不用在一坨代码里翻来翻去。

2.2 为什么选 Python,而不是 Go 或 Node

很多人看到“命令行工具”就会想到 Go 或 Node,我的选择是 Python,理由比较实际。

第一,这个工具的核心负载不是高并发,而是大量文件 IO 和文本处理。Python 的 pathlib 处理路径、difflib 生成 diff、re 做文本匹配都非常顺手,开发效率明显高于 Go。

第二,Python 的标准库已经覆盖了大部分需求,启动一个简单的 HTTP 请求、解析 JSON、处理文件系统并不需要引入太多依赖。对一个小工具来说,依赖越少,维护成本越低。

第三,后续如果要接入本地模型,Python 生态里的推理框架和模型封装工具最成熟。我不需要为了一个终端工具去维护两套语言生态。

有人关心性能问题。实测下来,一次工具调用的瓶颈几乎都在模型生成时间上,Python 本身多花的那几十毫秒根本不值一提。当然,如果以后要做成高度并发的服务端,那再考虑用 Go 重写也不迟。

2.3 一条指令从终端到文件的完整流程

我拿一个具体例子展示整个数据的流动过程。假设用户输入:

帮我在 src/utils.py 里加一个 json 安全加载函数,并写单元测试

这条指令会经过下面这些步骤:

阶段做的事关键产物
1. 输入解析REPL 读入文本,做基本清洗用户指令字符串
2. 上下文构建扫描文件树,读 src/utils.py 头部文件树 + 文件内容片段
3. 请求组装拼系统提示词、工具描述、历史对话发给模型的 payload
4. 模型推理模型返回工具调用片段含 tool_calls 的文本
5. 协议解析提取 JSON,校验工具名和参数标准化的工具调用对象
6. 用户确认展示将要执行的改动,等待确认用户同意标记
7. 工具执行修改文件,跑测试命令stdout/stderr 输出
8. 结果回流把执行结果发给模型,让模型总结最终回复给用户

这个流程里最容易出问题的是第 4 到第 5 步。模型返回的往往不是干净的 JSON,而是夹杂着“好的,我来调用工具”这类自然语言,这就需要解析器足够鲁棒。我在后面会专门讲这块的踩坑经历。

另一个设计要点是:工具执行之后的结果不是直接丢给用户看,而是先回流给模型,让模型基于真实的执行结果给出总结。这样用户体验会好很多。比如工具返回的是“测试成功,3 passed”,模型会把它翻译成“单元测试全部通过,新增的 json 安全加载函数正常工作”。

2.4 配置文件与项目布局

free-claude-code 的配置目录放在用户主目录下的一个隐藏文件夹里,里面会有三个文件:主配置文件 config.toml、密钥文件 secrets.env、日志文件 runtime.log。

主配置文件存模型端点地址、模型名称、温度、token 上限等参数;密钥文件存 API Key;日志文件记录所有请求和工具调用。把密钥和配置分开是必须的,因为配置经常要分享给别人,而密钥绝对不能出机器。

这是我实际用的一个配置框架:

[model] endpoint = "https://your-endpoint.example.com/v1/chat/completions" model_name = "your-free-model" temperature = 0.2 max_tokens = 4096 [context] file_tree_depth = 3 preview_lines = 50 max_history_rounds = 4 [tool] shell_prefix_allowlist = ["ls", "grep", "cat", "pwd", "python"] max_diff_lines = 50 require_confirm = true

第一次启动时,如果发现配置文件不存在,工具会生成一个默认配置并提示用户修改。这一步虽然简单,但非常提升好感度,省得用户去查文档找格式。

3. 核心实现:让模型学会“用工具”而不是空谈

3.1 文件扫描、上下文组装与 token 预算

上下文是决定模型表现的第一要素。给少了,模型找不到关键文件;给多了,token 超限,请求直接失败。free-claude-code 的做法是三级读取。

第一级是文件树。启动时会递归扫描项目目录,生成一棵文件树。注意,不是简单的文件名列表,而是带目录层级、文件大小、最后修改时间的结构化信息。文件树本身很省 token,但能给模型提供全局视图。

第二级是代码预览。默认只读取每个代码文件的前 50 行,并截断到一定字符数。50 行这个数字是我试出来的:太少了看不出函数定义,太多了 token 很快爆掉。如果某个文件很重要,用户可以用 /open 命令把某个文件全文放进上下文。

第三级是用户指定。在处理任务的时候,工具层读取到的文件内容也会动态追加到上下文里。比如模型决定读取 src/utils.py 的某个函数,这一步产生的新内容会在下一轮请求中带上。

token 预算怎么算?我不会精确计算,因为各家模型的 tokenizer 不一样。我用的估算规则是:中文按每 1.2 个字符一个 token,英文按每 3.5 个字符一个 token,其他字符按每 2 个字符一个 token。提前在发送前估算,如果超过模型上下文窗口的 70%,就启动压缩策略:文件预览截断得更短,历史对话只保留最近两轮,最不相关的文件直接丢弃。

这里有一个很容易犯的错误:上下文的优先级搞反了。很多人为了保留完整的对话历史,把文件内容砍到很少。我的经验是,历史对话的价值远低于文件内容。模型就算忘了你五分钟前说过的一句话,只要它现在能看到正确的文件内容,结果也不会太差。

3.2 工具 Schema 与 JSON 协议

模型本身是不会执行任何操作的。它只能输出“想调用哪个函数、参数是什么”,由工具层去执行。所以我们需要一套“模型-工具”之间的通信协议。

free-claude-code 使用的是最通用的 JSON 协议。一个工具调用长这样:

{ "name": "edit_file", "arguments": { "path": "src/utils.py", "old_string": "def load_json(data):", "new_string": "def load_json_safe(data):", "mode": "replace" } }

为了让模型学会这个协议,要在系统提示词里给出每个工具的 JSON Schema 说明,外加一到两个示例。这里的关键是示例质量要高。比如 edit_file 工具,我用的是“old_string 必须精确匹配文件中的原文”这种强约束,示例里还特意演示了如果目标文本出现多次时应该带上额外上下文。

目前只定义了六个工具,够用但不过多:

  • read_file:读取指定文件内容,可按行范围截取;
  • edit_file:在文件中做局部替换;
  • create_file:新建文件,写入初始内容;
  • run_command:在项目根目录执行 shell 命令;
  • grep_search:在文件内容里做正则搜索;
  • list_files:列出某个目录下的文件。

工具不是越多越好。每多一个工具,模型可能就会选错一次。我砍掉过一些“看起来很酷但实际用不到”的工具,比如“自动提交版本管理记录”,因为它经常在用户还不确认改动时就执行了版本管理操作,非常危险。

3.3 安全确认机制:模型可以犯错,但工具必须挡住

模型一定会犯错,所以安全机制要设计在工具层,而不是寄希望于模型不犯错。

free-claude-code 把工具分成三类。第一类是只读操作,比如 read_file、grep_search、list_files,这类操作不会改变系统状态,直接执行。第二类是写操作,比如 edit_file、create_file,会展示 diff 并要求确认。第三类是命令执行,run_command 默认只能执行一个前缀白名单里的命令,白名单之外一律拒绝。

白名单是我手动配置的:

ls, find, grep, cat, pwd, python, python3, pytest, git status

只有这些命令可以直接执行。想运行其他命令,用户必须单独确认。这看起来增加了交互成本,实际上非常必要。有一次模型建议运行一条清理临时文件的命令,我还没来得及看就点了确认,那条命令差点把当前目录下所有缓存删光。从那之后,我再也没有让它自由执行任意 shell 命令。

写文件的确认界面要展示 diff,而不是简单地问“是否允许修改”。diff 是第一手的风险信息,用户看 diff 比看路径可靠得多。另外,我还加了一个限制:单次 edit_file 替换掉的内容不能超过 50 行,否则工具直接报错让模型重新设计改动。这个限制有效防止了模型“一改就改整个文件”的粗暴行为。

沙箱方面我没有一开始就上容器隔离,因为重量太重。折中方案是限制工作根目录:所有相对路径都限制在项目根目录内,命令执行前检查参数里是否带..或绝对路径逃逸。如果将来要多用户远程使用,再把工具层整个放进容器也不迟。

3.4 一段核心代码长什么样

决策层是最核心也是最容易写乱的部分。我贴一段经过精简的代码,说明模型返回解析和工具调度的主干逻辑:

import json import re from typing import Any # 工具注册表:所有可执行操作都放在这里 TOOL_REGISTRY = {} def register_tool(name: str): def decorator(func): TOOL_REGISTRY[name] = func return func return decorator def extract_tool_calls(raw_text: str) -> list[dict[str, Any]]: # 模型输出可能夹杂自然语言,需要先尝试直接解析,失败后再提取 try: data = json.loads(raw_text) if isinstance(data, dict) and "tool_calls" in data: return data["tool_calls"] if isinstance(data, list): return data return [] except json.JSONDecodeError: pass # 退一步:找到最后一个看起来像 JSON 对象的片段 candidates = re.findall(r"\{[^{}]*\"name\"[^{}]*\}", raw_text) result = [] for cand in candidates: try: obj = json.loads(cand) if "name" in obj and "arguments" in obj: result.append(obj) except json.JSONDecodeError: continue return result def dispatch_tool(call: dict[str, Any]) -> str: name = call.get("name", "") arguments = call.get("arguments", {}) if name not in TOOL_REGISTRY: return f"unknown_tool: {name}" return TOOL_REGISTRY[name](**arguments)

这段代码的思路很简单:先尝试严格解析,失败就正则提取。真正放在项目里的版本还会加上日志输出、耗时统计、参数校验和确认拦截,但主干就是这两层解析。

写到这里我要强调一个体会:正则解析 JSON 是不得已的兜底方案,如果模型正常返回,尽量用完整解析;只有真正拿不到结构化内容时才用正则。你还要设置一个最大重试次数,比如三次解析失败就告诉用户“模型输出无法解析,请重试或换模型”,而不是无限循环。

4. 接入低成本模型:免费额度怎么用才够稳

4.1 只对接一种 HTTP 接口格式

free-claude-code 的接入层没有绑定任何厂商 SDK,而是直接用一个 HTTP 客户端请求模型推理服务的接口。现在市面上大多数兼容接口都遵循同一个风格:向某个/v1/chat/completions地址发送 POST,请求体里带messages和tools,响应体里带content和tool_calls。

我只需要把这种风格接口的公共子集实现一次,就能接上绝大多数模型服务。这意味着本地启动的开源模型、在线社区的免费推理额度,只要能返回这种格式,都能接入。

请求体的核心结构是这样的:

{ "model": "your-free-model", "messages": [ { "role": "system", "content": "你是 free-claude-code 的决策模块,必须通过工具操作完成用户任务。" }, { "role": "user", "content": "帮我在 src/utils.py 里加一个 json 安全加载函数,并写单元测试。" } ], "tools": [ { "type": "function", "function": { "name": "edit_file", "description": "修改指定文件中的局部内容", "parameters": { "type": "object", "properties": { "path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"} }, "required": ["path", "old_string", "new_string"] } } } ], "temperature": 0.2, "max_tokens": 4096 }

这里有个很容易踩的坑:不同模型对tools参数的格式支持程度不一样。有的模型只认 OpenAI 风格,有的只认自己的格式。所以接入层做了一次适配:在发送前根据配置里的api_style字段决定要不要对 tools 做格式转换。这个问题在接多个端点时几乎必然出现,建议你在配置里预留一个api_style参数,后面会省很多事。

4.2 token 预算的硬约束

免费额度通常都卡得很紧,你不可能像用付费接口那样随意挥霍。我在接入层做了三道防线。

第一道是估算防线。发送之前先估算请求体的 token 数,如果超限就直接报错,不让请求发出。宁可让用户减少文件范围,也不要把一个注定失败的请求发出去。

第二道是截断防线。如果只是略微超限,启动自动压缩流程,把最不重要的上下文丢掉。压缩的优先级我前面讲过,文件树永远是最高优先级,历史对话是第一个被压缩的。

第三道是执行后的回流控制。工具执行结果可能很长,比如测试输出 500 行,我默认只把前 100 行和最后 50 行返回给模型,中间部分做摘要。别小看这个操作,模型不需要知道 500 行测试日志的每一行,它只需要知道哪个测试失败了、失败原因是什么。

三道防线加在一起,免费额度能跑的轮数会显著提升。我做了一次真实测试,一个包含 30 个文件的项目,一次完整重构任务约需要 6 轮工具调用,总 token 消耗大概在 5 万左右,免费的推理额度通常足够支持。

如果你们用到的免费额度非常小,还有一个更极端的思路:把整个项目的文件树和代码摘要提前离线生成好,存成缓存文件,每次会话启动时直接读缓存,而不是重新扫描。这样能省掉最开始的几轮扫描请求。

4.3 重试、轮询与稳定性

免费推理服务的稳定性通常不如付费服务,429 限流和偶发超时是家常便饭。我一开始的策略很简单:失败了就重试一次,结果第一次线上使用就连续失败,体验非常糟糕。

后来我给接入层做了三层稳定性机制。

第一层是线性退避重试。第一次失败等 1 秒重试,第二次等 3 秒,第三次等 5 秒,最多重试三次。不要用指数退避,否则用户会等很久。

第二层是多端点轮询。在配置里可以同时配多个端点,如果第一个端点连续失败两次,就自动切到下一个。这个机制挽救了无数个“免费额度暂时耗尽”的尴尬时刻。

第三层是请求内容缓存。相同的历史对话和用户指令,在 5 分钟内重复提交的话,直接返回上一次的结果。因为模型的温度很低,相同输入的结果差异很小,而这个缓存能省下不少额度。

还有一个细节:温度参数一定要调低。很多人做聊天应用喜欢把温度调到 0.8 甚至更高,但在代码工具场景里,温度太高会导致模型输出不稳定,同一个函数名一会叫这个一会叫那个。我实测 0.2 是比较合适的值,稳定性和创造性之间的平衡点就在那附近。

4.4 环境变量与密钥管理

密钥管理看着是个小事,但其实是最容易翻车的地方。我见过不止一个开发者把 API Key 直接写死在配置文件里,然后整个文件夹分享出去,几分钟之后 key 就被别人刷爆了。

free-claude-code 的规则很简单:任何密钥都只能放在环境变量或者 .env 文件里,主配置文件只保留非敏感参数。代码里读取密钥用的是下面这种方式:

import os from pathlib import Path # 支持直接读环境变量,也支持从 .env 文件加载 def load_secret(key: str) -> str: value = os.getenv(key, "") if value: return value env_file = Path.home() / ".free-claude-code" / "secrets.env" if env_file.exists(): for line in env_file.read_text().splitlines(): if line.startswith(f"{key}="): return line.split("=", 1)[1].strip() return "" API_KEY = load_secret("FCC_API_KEY") if not API_KEY: raise SystemExit("缺少 FCC_API_KEY,请检查 .env 文件")

还有一点要提醒:如果发起请求时误用了错误的 Key,许多接口返回的不是 401,而是 200 加一个错误信息,藏在响应体的某个字段里。这种“软失败”最难排查,建议在接入层统一检查响应体里的错误标记,不要只看 HTTP 状态码。

5. 从原型到顺手:踩坑记录与实测数据

5.1 流式响应中的 JSON 解析问题

开发过程中我遇到的最大坑是流式输出的解析。

很多推理接口默认是流式返回,也就是说响应不是一个完整的 JSON,而是一段一段文字。这本来不是问题,但模型在生成工具调用时,经常先输出一段自然语言,再输出工具调用,而且这两者之间可能穿插着换行、多余的空格,甚至半个 JSON 片段。

我第一次拿到这种响应时,直接调用json.loads,结果自然是解析失败。后来我在解析前加了一个预处理函数:先把所有空白字符压缩成空格,再尝试完整解析,失败后就去寻找可能的 JSON 对象片段。这个方案让解析成功率从大约 80% 提升到了 98% 以上。

剩下的 2% 怎么办?我选择用重试来解决。三次解析失败就自动重发上一轮请求,同时要求模型只输出工具调用。实测下来,这个策略基本不会让用户感知到卡顿。

另外,不要迷信“响应里的 content 和 tool_calls 一定是二选一”。有些模型会在 content 里说“我将调用工具”,然后在 tool_calls 里给出具体参数。解析器必须同时处理这两个字段,否则会丢掉模型的部分意图。

5.2 终端编码与路径兼容

终端编码问题看起来很小,但第一次遇到还是很抓狂的。模型生成的中文字符串,在某些系统的默认终端里会变成乱码,尤其是路径中包含中文的时候,read_file 工具会直接报“文件不存在”。

排查下来,问题出在命令行环境的默认编码不一致。解决方法是两招配合:第一,所有文件读写都显式指定 UTF-8 编码;第二,工具执行 shell 命令之前,把环境变量里的编码相关字段统一设置为 UTF-8。

import os import sys if sys.platform.startswith("win"): os.environ.setdefault("PYTHONIOENCODING", "utf-8") # 写文件时统一 UTF-8 with open(path, "w", encoding="utf-8", newline="") as f: f.write(new_content)

还有一个路径兼容问题是斜杠方向。模型在生成参数时,有时会用正斜杠,有时会用反斜杠。工具层在执行路径拼接之前,要把所有路径统一转成标准形式,否则在部分系统上会拼接出双斜杠的奇怪路径。

5.3 模型“乱改代码”的兜底策略

做代码修改类工具,最怕的不是模型写得慢,而是模型自作主张。我遇到过最夸张的一次,模型在执行“给某个函数补类型注解”时,把整个文件的缩进从四个空格改成了两个空格,然后又调整了一堆无关函数的换行位置,diff 看起来像重写了整个文件。

从那之后,我给 edit_file 工具加了三个硬性约束:

第一个是最大替换行数限制。old_string 和 new_string 各自的行数都不能超过 50 行,超过就报错。你别嫌这个限制太死,它逼着模型把大改动拆成多个小步骤,而小步骤的 diff 更容易被用户审查。

第二个是强匹配要求。old_string 必须精确匹配文件内容,不允许模糊匹配。如果模型给的旧文本和文件里实际内容差一个空格,工具直接失败,让模型重新生成。

第三个是 diff 展示门槛。每次 edit_file 执行前,确认层都会展示一个至少三行的 diff 视图,第一行是文件路径,第二行是修改前,第三行是修改后。如果用户看到 diff 里有意外的大段删除,可以直接输入 n 拒绝,模型会收到用户的拒绝反馈,然后重新调整方案。

我还在工具层加了一个更强的拦截器:delete_file、clear_file 这类一次性清空文件的操作,直接不在工具列表里提供。想清空文件,用户可以在 shell 里自己做,但模型没有这个权限。这不是对模型的信任问题,而是对风险的底层隔离。

5.4 一次重构任务的完整实测记录

为了测试工具在真实场景下的表现,我在一个模拟项目上跑了一次完整的重构任务。模拟项目包含 30 个 Python 文件,约 6000 行代码,任务描述是:

把项目中所有 eval() 调用替换成 ast.literal_eval(),并对相关函数补充返回类型注解。

整个执行过程是这样的:

轮次工具调用观测结果
1list_files + grep_search找到 6 处 eval() 调用,分布在 3 个文件
2read_file x3读取三个文件的完整内容
3edit_file x3替换成功,diff 正常
4run_command(pytest)测试全部通过
5无工具调用模型总结改动

关键数据如下:

  • 总轮数:6 轮工具调用;
  • 总耗时:约 2 分 20 秒,其中大部分是模型生成时间;
  • 总 token 消耗:约 5 万;
  • 人工介入:只有 3 次 edit_file 确认,平均每次确认只看 diff 不超过 30 秒。

这个结果让我比较满意。它说明了一个道理:模型可以一次生成多次工具调用,但工具层必须一一把关。只要把关这件事做好,AI 改代码就不至于失控。

如果换成本地小模型跑同样任务,耗时会长很多,可能到 10 分钟以上,但胜在完全不消耗在线额度。我的建议是:日常小任务用本地模型,效率要求高的任务用在线免费端点,二者通过配置切换即可。

5.5 后续值得继续做的方向

free-claude-code 现在的状态是可以用的,但离一个完整产品还差得远。我自己列了一个 TODO 清单,按优先级排序。

第一件是会话持久化。现在每次启动都是全新会话,模型不记得昨天改到哪了。如果加一个会话存档,把历史摘要和上下文缓存存在本地,再启动时就能无缝续上。

第二件是增量索引。当前的上下文构建是每次全量扫描,项目一大就会变慢。改成增量索引之后,只有发生变化的文件才需要重新扫描。

第三件是多步规划。现在模型是一轮工具调用一轮反馈,遇到复杂任务效率不高。可以在决策层加一个 planner,让模型先输出一个包含多个工具调用的计划,再逐项执行。

第四件是 TUI 界面。文本交流虽然够用,但选择文件、查看 diff 还是图形化一点更舒服。做一个基于文本的终端界面,用上下键导航,会比单纯输入命令流畅很多。

第五件是更细粒度的权限模型。比如按文件目录设置权限,允许模型修改 tests 目录但禁止修改核心代码目录。这个做起来不难,对多人项目很有价值。

这些方向每一个都不算复杂的算法问题,难的是把它们组合在一起还不让工具变得臃肿。我会先把会话持久化和增量索引做掉,因为这两个直接决定日常使用体验。

6. 最后聊几句我做这个项目的真实感受

如果你也在做类似的东西,我最大的建议是:先把安全确认机制做完,再考虑接模型。很多人一上来就急着接最强模型,结果发现模型“啥都会但管不住手”,改代码改得飞快也错得飞快,最后只能关掉。反过来,先把工具层和确认层做严实,模型能力反而能安心发挥。

另外一个心得是关于迭代节奏的。我一开始野心很大,想一次做出任务规划、自动测试、多模型切换,结果卡了几天没进展。后来把目标砍到“能用命令行完成一次文件修改”,两天就跑通了。从那个最小闭环开始,每加一个功能都能立刻验证,速度反而快了。

还有一点,日志一定要从第一天就写完整。free-claude-code 的日志文件记录每一轮请求的输入输出、每次工具调用的参数、每个 diff 的完整内容。排错时这些日志几乎是救命稻草。很多奇怪的 bug,光靠看终端输出是看不出名堂的,翻一下 runtime.log 就明白了。

我现在的使用习惯是:小项目直接用 free-claude-code 操作,遇到不想人工处理的重复修改就先让它出方案,我再审 diff。它已经从一个周末实验品变成了我日常脚本开发的固定搭档。接下来我打算继续把会话持久化和增量索引做完,然后拉几个不同规模的项目做横向测试,看看它在真实代码库上的表现到底如何。到时候我会把新的实测结果整理出来继续分享。

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

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

立即咨询