我们用本地模型免费分类了OpenClaw仓库!*
We got local models to triage the OpenClaw repo for FREE!*
Hugging Face 团队(Onur、Ben Burtenshaw、Shaun Smith、Pedro Cuenca、Lysandre)展示了如何在 agent 框架中使用本地开源模型(gemma-4-26b-a4b、qwen3.6-35b-a3b)对 OpenClaw 仓库的 issue 和 PR 进行实时分类。他们构建了 localpager 系统,通过 pi 框架结合受限的 reposhell 工具实现 agentic classification,在 NVIDIA GB10(DGX Spark)硬件上运行。在 330 行评估集上,gemma-4-26b-a4b 达到 F1 0.800、每行 1.41 秒,qwen3.6-35b-a3b 达到 F1 0.824、每行 13.51 秒,均无需微调即可实现良好准确率。系统还通过 OpenClaw cron job 与 GPT-5.5 进行实时性能对比验证。
](https://huggingface.co/osolmaz)
*免费如啤酒,不包括电费,且假设你已拥有硬件
2026年6月将被铭记为人们意识到闭源模型可以被夺走的时刻。随着 Anthropic 最新旗舰模型 Claude Fable 5 被下架的记忆犹新,不难理解为什么拥有自己的 AI 栈并能在本地运行模型比以往任何时候都更重要,尤其是当你在 AI 之上构建业务时。
有鉴于此,我们想分享如何在 agent 框架中使用 Gemma 和 Qwen 等本地模型来运行分类任务[^1]。这种方法不同于使用 BERT 等模型进行分类。在像 Pi 这样的 agent 框架中,本地模型可以与结构化输出结合使用,来分配标签。我们选择这种方法是因为我们已经拥有本地模型和框架,并且坚信随着本地模型能力的提升,类似的设置将越来越受欢迎。[^2]
我们的起点是 OpenClaw 仓库中的开源贡献。OpenClaw 每天都会收到数百个 issue 和 PR,需要对其进行分类、确定优先级并分派给维护者。我,Onur,正在努力让本地模型与 OpenClaw 良好配合。作为这个特定垂直领域的维护者,我需要快速响应任何 P0 问题。
使用像 GPT-5、Opus 或 Sonnet 这样的 SOTA 闭源模型,这是一个相当直接的任务。但我恰好拥有 128 GB 的统一内存,即一块 NVIDIA GB10。所以我接受了这个挑战:
我能否构建一个实时通知系统,只过滤并通知我负责的 issue……使用本地开源权重模型?

这个小盒子,又名 DGX Spark,可以高并发运行 gemma-4-26b-a4b,每秒生成数百个 token。
如果我设置我的 OpenClaw 主 agent 运行在每月 200 美元的 ChatGPT Pro 计划上,在每个新 issue 或 PR 时触发一个任务,那会消耗我的配额。我可能会改为设置它每 2 小时或 6 小时运行一次。这会将 issue 批处理到更长的周期内,因此我们将用实时通知换取延迟处理。
如果我在我已经运行起来的硬件上使用本地模型来运行这个任务,我不仅会获得近乎即时的通知,而且还能免费做到(或者更确切地说,只需支付电费)。
对 issue 和 PR 进行分类
我们提出了一组有限的标签,代表我们需要分类的 issue 类别,然后使用本地模型将每个 issue 分类到这些类别之一,例如 local_models、self_hosted_inference、acp、agent_runtime、codex、ui_tui 等等。[^3]
但是我们如何对 pull request 进行分类呢?简单地使用带有工具 JSON schema 的 Chat Completions 端点,将主题作为 enum 发送一次请求?
差不多。但现在是 2026 年,不是 2023 年,我们有 AGENT。我们可以做得更好!
对于本地模型的选择,我们测试了 gemma-4-26b-a4b 和 qwen3.6-35b-a3b。通过性能优化,两者都可以在本地每秒生成数百个 token。
我们使用一个 agent 框架来驱动分类运行。为此,我们将 pi 捆绑为一个可以调用本地模型端点的框架。
agent 默认在第一个 prompt 中接收 PR 标题、正文和 PR diff 的截断摘要。然后,它可以选择使用 bash 工具对 OpenClaw 仓库执行只读操作(如果需要查看代码库),或者使用 final_json 工具提交最终分类结果。
你不想在这种高吞吐量设置中给予本地模型完全的 bash 访问权限,因为一个被 prompt 注入的 issue 或 PR 可能会引导模型执行与分类无关的操作。
因此,我们使用 reposhell 代替 bash:一个受限制的类 bash shell,只允许对 OpenClaw 仓库进行只读操作(ls、find、cat、grep 等)。模型认为它在使用 bash,但任何不允许的操作都会被拒绝:
reposhell bound cwd=/repo/openclaw repos=openclaw
type help for allowed commands; exit or quit to leave
reposhell /repo/openclaw> help
allowed: pwd, ls, find, rg, grep, sed -n, cat, head, tail, wc -l, git status --short, git show --name-only, git grep, git ls-files
search: rg -n -i "lm studio" or grep -R -n -i "lm studio" .
files: rg --files -g "*.ts" or git ls-files src
examples: rg -n reposhell README.md | sed is not allowed; use one simple command at a time
reposhell /repo/openclaw> head README.md
# 🦞 OpenClaw — Personal AI Assistant
<p align="center">
<picture>
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text-dark.svg">
<img src="https://raw.githubusercontent.com/openclaw/openclaw/main/docs/assets/openclaw-logo-text.svg" alt="OpenClaw" width="500">
</picture>
</p>
<p align="center">
reposhell /repo/openclaw> curl localhost
reposhell policy denied command: unsupported command "curl"
exit_code=2
reposhell /repo/openclaw>
这里有一个具体的例子说明了这一点的重要性。在一个保存的会话示例中,qwen3.6-35b-a3b 正在分类 openclaw/openclaw#84621,标题为 Fix Kimi tool-call rewriting stop reason handling。思考块显示模型最初考虑 coding_agent_integrations,因为更改的路径 extensions/kimi-coding 看起来合理。模型使用 reposhell 通过简单的只读命令(如 ls extensions、ls extensions/kimi-coding 和 cat extensions/kimi-coding/package.json)检查本地仓库。该包元数据显示该扩展实际上是 @openclaw/kimi-provider,一个 OpenClaw Kimi 提供者插件。因此模型将最终标签修正为 inference_api 和 tool_calling,并明确排除了 coding_agent_integrations。
我们之前提到过,我们捆绑了一个特定的 pi 配置,该配置只能执行只读操作并返回分类输出。我们称之为 localpager-agent,以这里的主要项目 localpager 命名。每个 PR 和 issue 都会生成一个 prompt,然后像下面这样与其他参数一起传递给 CLI:
localpager-agent \
--model "<model-id>" \
--base-url "<openai-compatible-base-url>" \
--session-dir "<session-output-dir>" \
--final-schema "<runtime-schema.json>" \
--tools bash,final_json \
--reposhell-socket "<reposhell.sock>" \
--reposhell-default-repo "<repo-id>" \
--reposhell-visible-repos "<repo-id>[,<repo-id>...]" \
-p "$(cat <rendered-prompt.md>)"
处理传入的 PR 和 issue
那么,在传入的 PR/issue 和 Discord 上的最终通知之间,是什么在编排一切呢?

这就是最终过滤后的 Discord 通知的样子:一个关于所需垂直领域的 PR 被路由给我。
围绕这个的编排非常简单;只有分类步骤涉及 LLM:
- 我们使用 openclaw/gitcrawl 作为仓库的本地镜像。每当有新的 PR 或 issue 时,每个项目都被规范化为相同的形状,并写入 localpager 自己的 SQLite 数据库。如果项目是新的,localpager 会为其创建一个分类任务。
- 然后,一个 worker 从该队列中领取任务。它构建一个包含 issue 或 PR 标题、正文、标签、作者、状态以及可选评论、更改文件和选定 diff 摘要的 GitHub 上下文对象。这意味着本地模型大多数时候不需要浏览 GitHub 或自己打开 URL。它被提供了所有相关的上下文。
- 上下文对象被渲染成一个 prompt,并按照上一节所述传递给
localpager-agent。agent 可以思考并使用 reposhell,但最终必须按照定义的 schema 输出分类结果。 - 输出被存储回 localpager SQLite 数据库,并根据用户配置的通知策略(即,通知我这些主题,但不通知其他主题)中继到 Discord。
下图显示了 localpager 的整体架构:
该架构是半 agent 式的。标签分配是 agent 式完成的,而发送通知则由确定性规则处理。这是为了通过消除任务中最直接部分的推理需求来加快通知管道。本地推理是免费的,但每个任务都有资源争用成本:GPU 带宽应保留给绝对需要推理的任务。这也减少了通知出错的可能性。
本地模型能对 PR 进行分类吗?
坦率地说:这个系统的第一个本地版本噪音很大。第一个测试的模型——gemma-4-e4b-it 对于让端到端本地管道工作起来很有用,但它也倾向于在 PR 或 issue 上放置太多不相关的标签。误报标签使 Discord 信息流变得嘈杂,并且没有将我的注意力集中在正确的 issue 上。这促使我们测试更大的本地模型,包括 gemma-4-26b-a4b 和 qwen3.6-35b-a3b,使用下面 330 行的评估集。
对于早期的 prompt 工作,我们还通过 antirez DS4 实现[^4] 使用了 DeepSeek-V4-Flash 来创建早期的数据集标签。该设置通过 CUDA 使用 DS4 服务器。我们最终放弃了将 DS4 作为标签器,因为它在不同运行中标签不一致。我们也没有考虑将其作为主要的 localpager-agent 模型,因为它太大,无法在我们的硬件上获得足够的吞吐量:DS4 服务器给我们大约每秒 14 个 token,最大并发数为 1。
为了测试模型性能,我们选择并为 330 个 GitHub issue 和 PR 生成了标签。每个项目被标记五次(3 次 GPT-5.5 和 2 次 Opus 4.8),模型需要达成一致才能被接受。这个过程涉及人工裁决、改进标签定义以及为模型突出内部产品设计选择。这给了我们一组稳定、可复现的标签,用于比较我们较小的模型。
在从这个评估集获得有用结果之前,我们不需要对 gemma-4-26b-a4b 或 qwen3.6-35b-a3b 进行 prompt 优化。使用相同的路由 prompt,Gemma 具有更高的召回率和更低的每行挂钟时间,而 Qwen 具有更高的精确率、更高的精确匹配率和更少的误报。我们还运行了 DeepSeek-V4-Flash 作为参考。它的误报最少,但模型大小和吞吐量使其无法在 NVIDIA GB10 上实时执行这些任务。由于每行可以有多个标签,误报和漏报是所有行的标签总数。下面的 Qwen 结果是重试结构化输出失败(模型在调用 final_json 之前用完了输出 token)后的结果。对于 Gemma 和 Qwen,重复运行指标报告三次运行的平均值 ± 样本标准差。DeepSeek-V4-Flash 作为参考运行一次。
| 指标 | gemma-4-26b-a4b |
qwen3.6-35b-a3b |
DeepSeek-V4-Flash |
|---|---|---|---|
| 精确率 | 0.716 ± 0.010 | 0.831 ± 0.007 | 0.938 |
| 召回率 | 0.905 ± 0.004 | 0.818 ± 0.006 | 0.714 |
| F1 | 0.800 ± 0.008 | 0.824 ± 0.002 | 0.811 |
| 精确匹配 | 0.410 ± 0.014 | 0.540 ± 0.014 | 0.509 |
| 误报 | 227.0 ± 10.5 | 105.7 ± 6.4 | 30 |
| 漏报 | 60.0 ± 2.6 | 115.3 ± 4.0 | 181 |
| 挂钟秒数 / 行 | 1.41 ± 0.04 | 13.51 ± 0.79 | 144.14 |
| 输出 tok/s / worker | 25 | 50 | 13 |
| 输出 tok/s 总计 | 402.6 | 145.3 | 13 |
| 并发数 | 16 | 4 | 1 |
| 总参数量 | 26B | 35B | 284B |
| 激活参数量 | 4B | 3B | 13B |
这里的吞吐量和挂钟时间数字并不是这些模型在此硬件上的最终最大性能数字。它们是我们当时使用的设置以及我们可用的优化。例如,在另一次探测中,gemma-4-26b-a4b 还支持并发数 32,并达到超过 700 的聚合输出 token 每秒。
跨 330 行标签集的基准比较。每个面板使用自己的垂直刻度;蓝色标记该指标的最佳值。精确率和召回率上的误差线显示 Gemma 和 Qwen 三次运行的样本标准差。
对于 Gemma 基准测试,我们使用 vLLM 提供 gemma-4-26b-a4b,并使用了我们为此设置找到的优化。其中很大一部分是 NVFP4 量化:在 GB10 级 Blackwell 硬件上,它不仅仅是更小的模型文件,而是一种硬件友好的格式,可以比 Q4_K_M 等可移植的 GGUF 量化更直接地使用 NVIDIA/vLLM 执行路径。在实践中,这意味着更少的内存流量和更多的批处理空间。我们还启用了 prefix caching、FP8 KV cache、CUTLASS MoE 后端和仅语言模型模式。完整的 330 行运行在并发数 16 下大约 7.5 分钟完成。
使用 OpenClaw 跟踪和验证实时性能
我们之前提到过,与其为每个新 issue 或 PR 使用本地模型运行一个任务,我们可以每 n 小时(例如每 2 小时)使用一个在 OpenClaw 中运行的 SOTA 云端模型(如 GPT-5.5)运行一个批处理任务,以达到相同的目的。[^5]
在这种情况下,我们需要一个 ChatGPT Pro 计划。由于模型是 SOTA 的,尽管将 2 小时的 issue/PR 批处理在一起,我们仍然可以预期它表现合理。
因为我们想看看本地分类器与 GPT-5.5 相比表现如何,我们同时运行两者,并让 GPT-5.5 每 2 小时判断误报和漏报。
为了安全起见,我们在沙箱中运行 OpenClaw 任务,只允许访问我们报告结果的公共仓库。在我们的案例中,我们让 OpenClaw 任务更新一个机器可读的文件,然后一个简单的脚本读取 Codex 分配的标签并计算误报/漏报状态。示例输出:
漏报
- Issue #88499 openai-responses provider: 404 on previous_response_id when store=false (default)
- inventory area: OpenAI-compatible/proxy; notifier topics: agent_runtime, api_surface, sessions; notification: none
误报
PR #88275 fix(models-config): allow self-hosted providers without apiKey in models.json (#88267)
- notifier interest: i0; topics: self_hosted_inference, local_model_providers, config; notification: sent
PR #88266 refactor: extract model catalog core package
- notifier interest: i1; topics: config, api_surface, local_model_providers; notification: sent
PR #88247 feat: add hosted model providers
- notifier interest: i0; topics: local_model_providers, model_serving, docs, api_surface; notification: sent
关于如何分类、编辑机器可读文件、使用脚本获取误报和漏报的说明存在于一个 agent skill 中,该 skill 在一个每 2 小时运行的 OpenClaw cron job 中被引用。然后 OpenClaw agent 会摄取任何新的 issue 或 PR,将它们以适当的标签添加到 JSON 文件中,运行脚本并在同一个 Discord 频道中报告。这样,我们可以每隔几小时观察本地模型的性能,并收到遗漏通知。
结论
我们认为 issue/PR 分类任务是更广泛任务集的一个特定案例,我们称之为“高吞吐量分类”。这篇文章探讨了使用本地模型在实时过滤信息方面的想法,仅在一个领域,即开源贡献。像 gemma-4-26b-a4b 和 qwen3.6-35b-a3b 这样的中等规模本地模型能够一次性分类,且准确率良好,无需任何微调,这使它们成为快速原型设计的良好首选,然后再转向更具成本效益的传统分类器模型。
然而,同样的方法也可以应用于其他领域:
- 新闻业的新闻分类
- 过滤社交媒体和论坛(如 X 或 Reddit)中的感兴趣帖子
- 分类客户支持工单
- 分类内容审核申诉
- 在进行销售时过滤潜在客户
- 在进行研究时过滤 arXiv 上的特定主题
这个列表可以扩展,但我们认为这个想法应该很清楚了。
除了分类之外,我们还探索了如何使用运行快速本地模型的 agent 框架以安全的方式执行分类。这种方法的一个好名称是 agentic classification:模型不是预先被喂入全部信息,而是可以在返回结构化数据之前搜索更多上下文。虽然我们不能完全称其为一种新颖的方法,但我们希望这篇博客文章能成为特定 Pi+受限 shell+final_json 配方的良好参考。
[^1]: 对于本文中的用例,我们发现以理解产品表面并正确标记的方式分解 PR/Issue 是一个难题。 [^2]: 尽管在我们的测试中我们没有——模型得出下一步收集信息、使用外部分类器的结论是相当合理的。Agent 方法和传统方法并不互斥。 [^3]: 在此处查看主题和其他配置的完整列表 here [^4]: 我们使用了来自 antirez/deepseek-v4-gguf 的 DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2.gguf。 [^5]: 虽然我们知道使用 LLM 作为评判者否定了“免费”的方面,但我们具体的实现是为了研究目的而这样做的。在实践中,可以在试用期间将更大、更昂贵的模型结合使用以进行校准,之后系统将完全过渡到较小的模型。在最近的运行中,这个审计循环每 2 小时检查大约使用 40k 个 GPT-5.5 token,主要是缓存的上下文,按 API 定价每次运行成本约 2-3 美分,或每天 12 次运行约 9 美元/月。这是对所有新项目的一次性批量审计,而不是每个项目一次评判调用;按项目进行可能会贵几倍。