2026年6月将被铭记为人们意识到闭源模型可以被收回的时刻。随着Anthropic最新旗舰模型Claude Fable 5被下架的记忆犹新,不难理解为何拥有自己的AI技术栈并能在本地运行模型比以往任何时候都更加重要——尤其是当你正在AI之上构建自己的业务时。
有鉴于此,我们想分享如何在智能体框架中使用Gemma和Qwen等本地模型来执行分类任务[^1]。这种方法不同于使用BERT这类模型进行分类。像Pi这样的智能体框架中的本地模型可以与结构化输出配合使用,来分配标签。我们选择这种方法,是因为我们手头已有本地模型和框架,并且相信随着本地模型能力的提升,类似的配置将会越来越普及。[^2]
我们的起点是OpenClaw仓库中的开源贡献。OpenClaw每天都会收到数百个issue和PR,需要对其进行分类、确定优先级并分派给维护者。我,Onur,正在努力让本地模型与OpenClaw良好配合。作为这个特定垂直领域的维护者,我需要快速响应任何P0级问题。
使用GPT-5、Opus或Sonnet这类SOTA闭源模型,这是一项相当简单的任务。但我恰好拥有128 GB统一内存,即一块NVIDIA GB10。所以我接受了这个挑战:
我能否构建一个实时通知系统,只过滤并通知我负责的那些issue……使用本地开源权重模型?
如果我设置我的OpenClaw主智能体(运行在每月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 进行分类呢?是否只需向 Chat Completions 端点发送一个带有工具 JSON Schema 的简单请求,并将主题作为枚举值?
差不多是这个思路。但现在是 2026 年,不是 2023 年,我们有 AI 智能体了。我们可以做得更好!
在本地模型的选择上,我们测试了 gemma-4-26b-a4b 和 qwen3.6-35b-a3b。通过性能优化,两者在本地每秒都能生成数百个 token。
我们使用一个智能体框架来驱动分类运行。为此,我们将 pi 打包成一个框架,它可以调用本地模型端点。
默认情况下,智能体在第一个提示词中会收到 PR 标题、正文以及 PR diff 的截取片段。然后,它可以选择使用 bash 工具对 OpenClaw 仓库执行只读操作(如果需要查看代码库),或者使用 final_json 工具提交最终的分类结果。
在这种高吞吐量的场景下,你肯定不希望给本地模型完全的 bash 访问权限,因为一个被提示词注入的 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 正在对标题为“修复 Kimi 工具调用重写停止原因处理”的 openclaw/openclaw#84621 进行分类。思考块显示,该模型最初考虑的是 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 都会生成一个提示词,然后连同其他参数一起,像下面这样传递给 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 上的通知之间,是什么在协调一切呢?
围绕这一过程的编排非常简单;只有分类步骤涉及大语言模型:
- 我们使用 openclaw/gitcrawl 作为仓库的本地镜像。每当有新的 PR 或 issue 时,每个条目都会被标准化为相同的格式,并写入 localpager 自己的 SQLite 数据库。如果该条目是新的,localpager 就会为其创建一个分类任务。
- 然后,一个工作进程从该队列中领取任务。它会构建一个 GitHub 上下文对象,其中包含 issue 或 PR 的标题、正文、标签、作者、状态,以及可选的评论、变更文件和选定的差异摘要。这意味着本地模型大多数时候无需浏览 GitHub 或自行打开 URL。所有相关的上下文都会被直接提供给模型。
- 上下文对象会被渲染成提示词,并按照上一节所述的方式传递给 localpager-agent。该智能体可以进行思考并使用 reposhell,但最终必须按照定义的架构输出分类结果。
- 输出结果会存储回 localpager 的 SQLite 数据库中,并根据用户配置的通知策略(即:针对这些主题通知我,但其他主题不通知)转发到 Discord。
下图展示了 localpager 的整体架构:
该架构是半智能体式的。标签标注由智能体完成,而发送通知则由确定性规则处理。这样做的目的是通过移除任务中最简单部分的推理需求,来加快通知管道的速度。本地推理是免费的,但每个任务都存在资源争用成本:GPU 带宽应保留给绝对需要推理的任务。这同时也降低了通知出错的概率。
本地模型能否对 PR 进行分类筛选?
坦率地说,这个系统的早期本地版本噪声很大。第一个测试的模型——gemma-4-e4b-it——对于让端到端本地管道跑通很有用,但它也倾向于给 PR 或 issue 打上过多不相关的标签。误报的标签会让 Discord 信息流变得嘈杂,并且无法将我的注意力集中在正确的问题上。这促使我们转向测试更大的本地模型,包括在下面这个包含 330 行的评估集上测试了 gemma-4-26b-a4b 和 qwen3.6-35b-a3b。
在早期的提示词工作中,我们还通过 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 进行提示词优化,就能在该评估集上获得有用结果。使用相同的路由提示词,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 |
| 每个 worker 的输出 token/秒 | 25 | 50 | 13 |
| 聚合输出 token/秒 | 402.6 | 145.3 | 13 |
| 并发数 | 16 | 4 | 1 |
| 总参数量 | 26B | 35B | 284B |
| 激活参数量 | 4B | 3B | 13B |
这里的吞吐量和处理时间数据并非这些模型在该硬件上的最终最大性能数值。它们只是我们在当时使用现有优化手段所采用的设置。例如,在另一次单独测试中,gemma-4-26b-a4b 还支持并发数 32,并达到了超过 700 的聚合输出 token/秒。
对于 Gemma 基准测试,我们使用 vLLM 部署了 gemma-4-26b-a4b,并应用了为此设置找到的可用优化。其中很大一部分是 NVFP4 量化:在 GB10 类 Blackwell 硬件上,这不仅是一个更小的模型文件,更是一种硬件友好的格式,与 Q4_K_M 这类便携式 GGUF 量化相比,它能更直接地利用 NVIDIA/vLLM 执行路径。实际上,这意味着更少的内存流量和更大的批处理空间。我们还启用了前缀缓存、FP8 KV 缓存、CUTLASS MoE 后端以及纯语言模型模式。完整的 330 行运行在并发数为 16 的情况下,大约在 7.5 分钟内完成。
使用 OpenClaw 跟踪和验证实时性能
我们之前提到过,与其为每个新的 issue 或 PR 运行一个本地模型的任务,不如每隔 n 小时(例如每 2 小时)使用一个 SOTA 云端模型(例如在 OpenClaw 中运行的 GPT-5.5)运行一个批处理任务,以达到同样的目的。[^5]
在这种情况下,我们需要一个 ChatGPT Pro 订阅计划。由于该模型是 SOTA 模型,尽管将 2 小时内的 issue/PR 合并批处理,我们仍可预期其表现相当不错。
因为我们想了解本地分类器与 GPT-5.5 相比表现如何,所以我们同时运行两者,并每 2 小时让 GPT-5.5 作为判断假阳性和假阴性的裁判。
为了安全起见,我们在沙箱中运行 OpenClaw 任务,仅允许其访问我们报告结果所用的公共仓库。在我们的案例中,我们让 OpenClaw 任务更新一个机器可读的文件,然后一个简单的脚本读取 Codex 分配的标签并计算假阳性/假阴性状态。示例输出如下:
假阴性
- Issue #88499 openai-responses provider: 404 on previous_response_id when store=false (default)
- 库存区域:OpenAI 兼容/代理;通知主题:agent_runtime, api_surface, sessions;通知:无
假阳性
- PR #88275 fix(models-config): allow self-hosted providers without apiKey in models.json (#88267)
- 通知兴趣:i0;主题:self_hosted_inference, local_model_providers, config;通知:已发送
- PR #88266 refactor: extract model catalog core package
- 通知兴趣:i1;主题:config, api_surface, local_model_providers;通知:已发送
- PR #88247 feat: add hosted model providers
- 通知兴趣:i0;主题:本地模型提供商、模型服务、文档、API 接口;通知状态:已发送
关于如何分类、编辑机器可读文件、使用脚本获取误报和漏报的说明,包含在一个智能体技能中,该技能被引用在每两小时运行一次的 OpenClaw 定时任务里。随后,OpenClaw 智能体会摄取任何新的 Issue 或 PR,将其添加到带有相应标签的 JSON 文件中,运行脚本,并在同一个 Discord 频道中报告结果。通过这种方式,我们可以每隔几小时观察一次本地模型的性能,并收到遗漏情况的通知。
结论
我们认为,Issue/PR 分类任务是一类更广泛任务(我们称之为“高通量分类”)中的一个具体案例。本文探讨了使用本地模型在单一领域(即开源贡献)中实时过滤信息的想法。像 gemma-4-26b-a4b 和 qwen3.6-35b-a3b 这类中等规模的本地模型,无需任何微调就能以较高准确率进行一次性分类,这使它们成为快速原型设计的首选,之后再转向更具成本效益的传统分类器模型。
然而,同样的方法也可以应用于其他领域:
- 新闻业的新闻分类
- 在 X 或 Reddit 等社交媒体和论坛中筛选感兴趣的帖子
- 客户支持工单分类
- 内容审核申诉分类
- 销售过程中筛选潜在客户
- 研究过程中在 arXiv 上筛选特定主题
这个列表还可以继续扩展,但我们认为这个思路应该已经清晰了。
除了分类之外,我们还探索了如何利用智能体框架以安全的方式运行快速本地模型来执行分类。这种方法的一个恰当命名是“智能体式分类”:模型并非一开始就被喂入全部信息,而是可以在返回结构化数据之前搜索更多上下文。虽然我们不能完全称其为一种新颖的方法,但我们希望这篇博文能为特定的“Pi + 受限 shell + final_json”方案提供一个良好的参考。
[^1]: 针对本文中的用例,我们发现,以一种能够正确理解和标注产品表面的方式来拆解 PR/Issue 是一个难题。[^2]: 尽管在我们的测试中没有出现这种情况——但模型完全有可能合理地推断出下一步是收集信息,并使用外部分类器。智能体方法与传统方法并非互斥。[^3]: 此处可查看完整的话题列表及其他配置。[^4]: 我们使用了来自 antirez/deepseek-v4-gguf 的 DeepSeek-V4-Flash-IQ2XXS-w2Q2K-AProjQ8-SExpQ8-OutQ8-chat-v2.gguf 模型。[^5]: 虽然我们意识到使用大语言模型作为评判者会抵消“免费”这一优势,但我们的具体实现出于研究目的而采用了此方法。在实践中,可以在试用期内同步使用一个更大、更昂贵的模型进行校准,之后系统将完全过渡到较小的模型。在最近的运行中,此审计循环每 2 小时检查一次,总共消耗约 40k 个 GPT-5.5 token,其中大部分是缓存的上下文,按 API 定价每次运行成本约为 2-3 美分,若每天运行 12 次,则每月成本约为 9 美元。这是对所有新项目进行的一次性批量审计,而非每个项目单独调用评判者;如果按项目单独进行,成本可能会高出数倍。
开源协作