[控场AI]
· 5 分钟阅读· 2,958 字

本地LLM不是不行,是你的上下文有Bug

本地LLM不是不行,是你的上下文有Bug

4B本地模型反复读取文件暴露Agent框架丢弃工具结果的缺陷,修复后同模型完成最短路径。

作者在纯CPU环境用Ollama运行qwen3.5:4b作为编码Agent,发现模型反复读取同一文件并声称“看不到内容”。深入排查后确认,问题出在自研Agent框架:工具结果(文件内容、命令输出)仅在上下文中保留一轮,随后被静默丢弃,而去重逻辑仍提示模型“使用已读内容”。修复工具结果持久化并剥离GREP模式外层引号后,同一4B模型从六轮失败变为五轮完成。控制实验显示,9B模型在旧框架下用重复读取自我修复,修复后三步零重复完成。核心结论是模型表现等于模型能力乘以框架约束,弱模型反而是更好的框架测试器。

一款小型本地模型在编码任务中反复读取同一文件,并声称“读过了但看不到内容”。开发者一度认定是模型太弱。深入排查后发现,问题出在他自己写的Agent框架:工具结果只存活一轮就被丢弃。一个4B参数的模型,反而用最诚实的方式暴露了这个缺陷。

故障现场:反复读取与“看不见”的内容

作者在纯CPU环境(Ryzen 5 4500U、32GB内存、Ollama)下,将本地LLM当作IDE内的编码Agent进行基准测试。任务T3要求模型修复pytest失败:找到原因、修复、确认全部通过。他交给qwen3.5:4b——一个快速的小模型。

在旧版框架下,模型的表现堪称灾难:先读test_calc.py,再读calc.py,然后再次读取test_calc.py。框架的去重逻辑返回“文件未变化,请使用你已读取的内容”。模型却回复:“我读了,但内容在这里不可见。”随后它尝试GREP,零命中,再次读取,六轮耗尽,任务失败。

作者的第一反应是:4B模型不够聪明,无法遵循指令。

本地LLM Agent框架故障示意

Ollama 是一个开源的本地大语言模型运行工具,支持在消费级硬件(包括纯CPU环境)上加载和推理多种开源模型(如Llama、Qwen等)。它通过简洁的API和命令行接口,让开发者无需GPU即可在本地运行模型,常用于隐私敏感或资源受限的场景。本文作者正是在Ollama上运行qwen3.5:4b等模型进行Agent基准测试。Ollama的轻量特性使其成为本地Agent开发的常见底座,但也意味着模型推理速度受CPU性能制约,上下文窗口管理需更加谨慎。

pytest 是Python生态中最流行的测试框架之一,用于编写和运行单元测试、功能测试。它支持自动发现测试文件、丰富的断言重写和插件扩展。在Agent编码任务中,pytest失败常被用作修复任务的起点:Agent需要读取测试代码和被测代码,定位失败原因,修改源码,再重新运行测试确认通过。本文的T3任务正是要求模型完成这一完整闭环。

根因追踪:工具结果只保留一轮

追踪Agent循环的代码后,缺陷浮出水面。历史累积逻辑存在严重不对称:用户消息被保留,助手回复被保留,但工具结果——文件内容、命令输出——在执行后仅存在于上下文一轮,然后被静默丢弃。

这意味着:第一步读取文件,第二步还能看到内容;从第三步开始,内容已从上下文中消失。而框架的去重抑制机制仍在告知模型“未变化,用你已有的”,指向的却是模型根本不再拥有的数据。

模型说“我看不到内容”并非胡言乱语,而是一份准确的状态报告。它知道自己的历史命令中包含READ_FILE,但载荷已经蒸发。这个4B模型实际上在正确地调试框架的上下文组装逻辑。

Agent循环中的上下文组装 是指框架在每一轮推理前,将系统提示、用户消息、助手历史回复以及工具调用结果按顺序拼接成完整提示词的过程。大模型本身是无状态的,它只能看到当前提示词中的内容,无法访问此前的会话记忆。因此,框架必须负责持久化所有关键信息——尤其是工具返回的文件内容、命令输出等——并在后续轮次中重新注入。如果框架丢弃了某些消息,模型在后续轮次中就会“失忆”。本文的缺陷正是工具结果仅在执行后的一轮内存在,随后被静默移除,导致模型无法引用已读取的文件内容。

意外发现:过去对9B的评估也要重写

这个bug改变了对早期测试结果的解读。作者的9B模型(qwen3.5:9b)曾被评价为“冗余地重复读取文件”,7步中有2步浪费在旧框架上。

但真相是:那不是记忆薄弱,而是合理的自我修复——重新获取已经消失的内容。更强的模型在绕开作者的bug,而较弱的模型只是诚实地报告了它。这一对比揭示了一个常被忽视的事实:模型表现是模型能力与框架约束的乘积。

修复方案与连带发现的第二个bug

修复包含两项。第一,将工具结果持久化到运行历史中——每一步的工具输出在整个运行期间保留在上下文中,“未变化”的提示终于名副其实。第二,剥离GREP模式的外层引号——模型反复输出GREP "member.*discount",字面引号进入正则表达式,导致零匹配;七轮重复引用,零命中。解析器现在会剥离包围引号,零匹配的GREP回复也会附带提示,建议简化模式或直接READ文件。

修复随Teaspoon IDE v1.5.1发布,采用FSL-1.1-MIT许可证。

GREP 是Unix/Linux系统中用于文本搜索的命令行工具,支持正则表达式匹配。在编码Agent中,GREP常被封装为工具,让模型通过模式匹配快速定位代码中的特定字符串或结构。正则表达式中的特殊字符(如.、*、")具有特定含义,若模型输出的模式被外层引号包裹且未剥离,引号会作为字面字符进入正则,导致匹配失败。本文中模型反复输出GREP "member.*discount",解析器未剥离引号,使正则实际匹配包含引号的字面串,因而零命中。修复解析器剥离包围引号后,GREP才能按预期工作。

控制实验:同一模型从“绝望”到最短路径

作者用相同模型、相同任务、相同提示词,仅改变框架修复状态,进行了控制实验。

条件结果
4b · 旧框架六轮耗尽,重复读取循环,失败
4b · 修复后五轮完成(pytest→读取→修复→复验,正确)
9b · 旧框架七步,含两次重复读取(自我修复)
9b · 修复后三步,零重复读取,最短路径
e4b · 修复后四轮,零重复读取,干净完成

同一个4B模型,仅靠修复周围代码,就从“无药可救”变为“干净的最短路径完成”。

三个值得记住的教训

“模型弱”与“框架弱”在检查之前不可分割。 每一篇“这个模型没用”的文章,评的可能都是框架的约束。如果你在构建Agent工具,先怀疑上下文里有什么,再怀疑模型。

去重与上下文节省必须针对模型实际可见的内容来设计。 “它之前读过”只在那个内容物理存在于提示词中时才成立。

弱模型是最好的框架测试器。 强模型用自我修复掩盖缺陷;4B模型没有余量,忠实地可视化了作者的漏洞。测试自己的Agent循环时,跑一个边缘模型。

数据与环境

所有原始日志、任务定义和评估记录均已公开,包括完整测量数据与分析(labo.mond.jp/articles/localllm-harness-bug.en.html)、fixtures与prompts及运行产物(github.com/cuculhart/ai-editor-lab),以及Teaspoon IDE框架(github.com/cuculhart/teaspoon-ide)。

环境刻意保持简陋:老旧六核笔记本CPU,无GPU。如果框架更差,每个模型看起来都会是坏的。在检查你的模型之前,先检查你的上下文。

#本地LLM#Agent框架#上下文管理#工具结果持久化#LLM编码Agent#模型评估#Teaspoon IDE
分享:

相关推荐