news 2026/10/27 14:55:20

复刻Jev决策模型:4B参数本地部署,给AI编程Agent装上方向盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
复刻Jev决策模型:4B参数本地部署,给AI编程Agent装上方向盘

如果你最近也在折腾AI编程助理,大概率已经听过Jev这个名字。它在Codex和Claude Code的小圈子里传得很快,但很多人容易搞混——Jev不是用来写代码的,而是用来做"决策"的。当Agent面对一堆文件、工具和报错信息时,Jev负责判断"下一步该干什么",真正动手写代码的还是主力模型。这种分工很有意思,等于给Agent装了个独立的方向盘。

我花了两周时间复刻了一个对标版本,命名为NeoHorse-Jev-4B,参数量只有4B,量化后普通消费级显卡就能本地跑,也能通过OpenAI兼容接口接入Codex、Claude Code、OpenCode这些主流的Agent工具。这篇文章把完整的实现思路、部署步骤和实测数据都放出来,顺便把我在这个过程中踩过的坑一并交代清楚。

1. Jev是什么,以及为什么编程Agent需要一只"方向盘"

1.1 我在Codex与Claude Code里遇到的真实痛点

先说一个我自己的经历。有段时间我频繁用Codex CLI改一个中型Python项目,改到第三四个任务的时候明显感觉到它"变笨了"——明明上个任务刚看过某个配置文件,下个任务它还是会重新cat一遍;明明测试失败日志里已经写了缺某个依赖,它非要先grep一遍源码再从长计议。更难受的是费用,每轮决策都要把全部历史上下文发给云端大模型,几十轮下来账单蹭蹭往上涨。

后来我拆开看Agent的运行循环才明白问题出在哪:主流大模型把"思考"和"行动"耦合在一起了。它既要看懂仓库结构,又要理解报错信息,还要决定调用哪个工具、传什么参数,同时还要保证代码生成质量。这些任务挤在同一个上下文窗口里,互相干扰。尤其是当上下文被工具输出、文件内容、历史命令填满之后,模型在"决策"这件事上的表现会明显退化,而我们又必须把这些历史全部发给它,费用和延迟都很难控制。

1.2 "决策模型"和"生成模型"到底哪里不一样

Jev这一类模型解决的就是上面这个问题。它的核心职责非常窄:在当前状态下,给出下一个动作。输入是"任务目标+最近的观察结果+可用的工具列表",输出是一个结构化的动作描述——比如read_file(path="src/config.py")、run_command(cmd="pytest tests/test_api.py")、或者finish(answer="...")。

这和主流生成模型有本质区别:

  • 生成模型要解决的问题是"给定上下文,续写出高质量代码/文本",输出空间巨大,需要很强的世界知识和语言能力。
  • 决策模型要解决的问题是"从有限的动作集合中选出最合适的一个",本质上是判别式任务。它不需要写出几百行代码,只需要把状态映射到动作上,信息熵比生成任务低得多。

打个比方:主力模型是执行任务的"手",决策模型是握方向的"方向盘"。方向盘不需要会写代码,但必须清楚什么时候该左转、什么时候该直行。

理解了这一点,4B参数量就说得通了。决策任务的知识密度要求没有生成任务那么高,真正关键的是两点:一是能准确理解当前状态的语义,二是能严格按固定格式输出动作,不跑偏、不发挥。

Jev原版之所以很快在小圈子里火起来,我总结有三个原因:

  1. 省钱:决策输出很短,而且可以用小模型跑,每轮消耗的tokens比主力模型少一个数量级。
  2. 快:决策请求的响应速度直接影响Agent的体感,小模型本地跑可以做到几十毫秒首token。
  3. 可控:决策格式固定后,框架层可以精准解析,不容易出现"模型嘴里说要做A,实际输出却是B"的错位。

但Jev原版有个比较麻烦的地方,就是它的使用方式偏向云端API,对于想本地化、想离线跑、想自定义训练数据的团队来说不够自由。这就是我做NeoHorse-Jev-4B的初衷:用开源基座微调一个同样定位的轻量决策模型,把整个技术栈完全握在自己手里。

2. NeoHorse-Jev-4B的搭建思路:数据、基座与输出协议

2.1 基座选择:为什么是Qwen2.5-4B

我在选基座时主要考虑三个方向,候选包括Qwen2.5-4B-Instruct、Llama-3.2-3B和Gemma-2-9B(当时9B还没出这么小的版本,且量化到4bit后体积和速度也不太理想)。最后选了Qwen2.5-4B-Instruct,原因比较实际:

  • 4B这个体量在int4量化后大约3GB显存,4090、3090都能跑,甚至MacBook的M系列也能通过llama.cpp扛住。
  • Qwen2.5系列在代码和工具调用数据上训练得比较充分,对"函数调用"这类结构化输出本身就有一定底子。
  • Chat模板成熟,开源生态好,无论是transformers直接推理还是转GGUF都有现成工具链。

这里有个经验可以分享:做决策模型不要选参数量太小的基座,比如1B-2B。我一开始试过Qwen2.5-1.5B,训练完发现它在简单任务上的决策还行,但一旦遇到需要跨文件推断的情况(比如"报错在A文件,但根因在B文件的配置"),就开始乱来了。决策任务虽然信息熵低,但还是需要基本的推理能力和上下文理解能力,4B是一个比较稳的甜点位置。

2.2 训练数据的三个来源

微调模型最核心的其实是数据,模型结构反而是成熟方案。我构造了三部分训练数据:

第一部分:公开Benchmark执行轨迹的决策抽取。把SWE-bench、终端助手类任务中真实的"模型选动作"日志拿出来,从完整轨迹里抽出(状态描述, 下一个动作)对。这一步很关键,因为公开轨迹里包含了真实仓库的结构和报错信息,状态描述足够自然。

第二部分:自建模拟环境的操作日志。我搭了50个虚拟小项目,包含Python后端、前端脚手架、配置文件、测试用例等,然后在里面跑预置任务(修bug、加功能、查配置),记录下所有操作序列。这样做的目的是补齐公开数据里覆盖不够的"低频但实用"动作,比如git回滚、依赖版本冲突处理、搜索特定报错关键字。

第三部分:用更大模型改写重写。从一些公开的Agent操作记录里把敏感信息脱敏掉,提炼成"当前状态+上一步结果"的摘要,再让大模型帮忙把决策对改写成统一格式。这里有个技巧:不要只保留成功轨迹,也要保留失败轨迹,并标注"这个动作导致了错误,应该换一种做法"。模型只有见过失败案例,才能在真实运行时避开类似的坑。

训练流程上,我做了两阶段:

  • 阶段一(SFT):全参微调,大约2个epoch,学习率5e-5,批量大小32。重点让模型学会"看状态输出动作"的基本映射。
  • 阶段二(DPO):用偏好数据做对齐。偏好数据怎么构造?同一个状态下,把"更好的下一个动作"和"较差的下一个动作"配对。比如一个场景下既可以用grep快速定位,也可以选择cat整个文件慢慢看,前者就是优选动作。DPO阶段只跑了大约1000步,学习率降到1e-5。

2.3 输出协议:真正的核心设计

模型本身只是载体,输出协议才是决策模型能不能实际落地的关键。我最终的输出格式定成下面这样:

{ "thought": "测试失败在test_login.py:25,可能是session初始化顺序问题,先看conftest配置", "action": "read_file", "action_input": { "path": "tests/conftest.py", "start_line": 1, "end_line": 60 } }

动作类型固定枚举,当前版本支持:read_file、run_command、search_files、write_file、finish、ask_user。把动作类型做成枚举有几个好处:

  1. 下游解析器不需要理解自然语言,直接读action字段做分发。
  2. 可以提前做参数校验,比如read_file必须传路径,run_command必须传命令字符串,校验失败直接让Agent重新决策,而不是盲目执行。
  3. 后续扩展动作类型只需要改枚举和微调数据,不需要动模型结构。

这里有个教训:我第一版让模型输出纯文本,靠正则去猜动作,结果模型的表述千奇百怪,解析时天天出bug。后来改成JSON结构+枚举类型,解析稳定率从86%直接跳到99%以上。决策模型和生成模型不一样,它不追求语言的丰富性,只追求动作的确定性。

3. 本地部署全流程:从Ollama快验到vLLM生产级API

3.1 五分钟快速体验:Ollama

对于想先试试水的人,我推荐直接用Ollama,步骤简单到不能再简单。先把训练好的模型转成GGUF格式(用llama.cpp的convert脚本),然后写一个Modelfile:

FROM ./neohorse-jev-4b-q4_k_m.gguf TEMPLATE """{{- if .System }} <|im_start|>system {{ .System }}<|im_end|> {{ end }} <|im_start|>user {{ .Prompt }}<|im_end|> <|im_start|>assistant """

然后执行:

ollama create neohorse-jev-4b -f Modelfile ollama run neohorse-jev-4b

Ollama本身自带OpenAI兼容接口,默认监听http://localhost:11434/v1,所以这一步做完之后,理论上任何支持OpenAI API的工具都能直接接上。我用OpenCode实测过,指定模型名称和这个地址就能跑通,不需要额外配置。

3.2 生产级部署:vLLM OpenAI兼容服务

如果要在团队里用,或者需要并发支持,我建议上vLLM。vLLM的PagedAttention对长上下文的KV Cache管理比transformers原生的实现高效很多,而且自带OpenAI兼容server,省去自己写服务层的麻烦。

pip install vllm vllm serve ./NeoHorse-Jev-4B \ --served-model-name neohorse-jev-4b \ --max-model-len 8192 \ --gpu-memory-utilization 0.85 \ --dtype bfloat16 \ --port 8000

这里我把最大上下文设成8192。4B模型本身的KV Cache占用不大,但决策模型实际使用时我们不会把全部历史塞进去,后面会专门讲上下文裁剪策略,所以8K完全够用。

如果你的显存比较紧张,还可以用AWQ量化版本。vLLM对AWQ支持得挺好,4bit量化后模型体积大约2.5GB,3090上跑起来非常轻松:

vllm serve ./NeoHorse-Jev-4B-AWQ \ --served-model-name neohorse-jev-4b \ --quantization awq \ --max-model-len 8192 \ --gpu-memory-utilization 0.75

3.3 采样参数与资源实测

决策模型和聊天模型的采样参数设定很不一样。聊天模型我们喜欢带点随机性,输出丰富一些;决策模型追求的是稳定和可复现,我建议这样做:

  • temperature = 0.1,或者干脆设成0。温度高会让模型在动作参数上产生随机波动,比如同一个read_file动作,温度高时传给path的字符串可能时而后缀多了个斜杠,时而大小写不一致,这在Agent循环里会直接导致工具调用失败或者重复劳动。
  • top_p = 0.3,进一步收窄采样空间。
  • max_tokens = 256,决策输出一般不超过300 tokens,给256足够,还能避免模型长篇大论。

我实测了三种硬件条件下的性能表现,供参考:

硬件环境量化精度首token延迟吞吐显存占用
RTX 4090 24GAWQ 4bit约45ms约320 tokens/s约5GB
RTX 3090 24GAWQ 4bit约55ms约280 tokens/s约5GB
MacBook M2 Max 64GGGUF Q4_K_M约120ms约90 tokens/s约4GB内存

对比Jev原版云端API的实测时延(约300-500ms),本地部署在速度上优势非常明显,而且每轮决策的成本几乎可以忽略不计。这是我认为决策模型走本地化最合理的核心原因。

4. 三个主流Agent工具的接入实战

4.1 接入Codex CLI

Codex CLI支持通过配置文件指定模型提供方,对OpenAI兼容接口的适配比较干净。我的做法是在~/.codex/config.toml里加一个自定义provider:

model_provider = "neo-local" [model_providers.neo-local] name = "NeoHorse Local" base_url = "http://localhost:8000/v1" env_key = "NEOHORSE_API_KEY" wire_api = "openai"

然后在model字段里指定用哪个模型做决策。这里有一个关键点:Codex CLI的决策模型和执行模型可以分开配置,我通常这样分配:

  • 全局生成/代码部分:继续用默认的云端大模型。
  • 循环中的下一步决策:本地neohorse-jev-4b。

跑起来之后的体感是:决策环节几乎不需要等待,本地几十毫秒就返回了下一步动作,整个任务的循环节奏明显加快。

4.2 接入Claude Code

Claude Code的架构和Codex不太一样,它默认走Anthropic接口,自定义模型接入需要转换层。我试过两种方案:

第一种是网关转换方案。用LiteLLM这类工具架一个代理,把OpenAI格式的请求转成Anthropic格式。这样Claude Code的请求会先打到网关,再转发到本地的vLLM服务。但这种方案有个麻烦:Claude Code内置的tool_use机制和OpenAI function calling的消息格式差异较大,网关转换时经常丢字段,尤其是tool_use_id的来回对应,一旦丢了对不上,Agent就不知道上一个工具结果属于哪次调用。

第二种是我最终稳定使用的MCP工具方案。Claude Code支持MCP(Model Context Protocol),我写了一个非常简单的MCP server,暴露一个名叫decision的工具,本地Claude Code在循环中遇到需要决策的点时调用它:

# decision_mcp_server.py import json from mcp.server.fastmcp import FastMCP mcp = FastMCP("neohorse-decision") @mcp.tool() def decision(task: str, state: str, tools: str) -> str: """ 将当前任务摘要和可用工具列表发给NeoHorse-Jev-4B, 返回格式化的下一步动作JSON。 """ prompt = build_decision_prompt(task, state, tools) resp = requests.post( "http://localhost:8000/v1/chat/completions", json={ "model": "neohorse-jev-4b", "messages": [{"role": "user", "content": prompt}], "temperature": 0.1, "max_tokens": 256, } ) return resp.json()["choices"][0]["message"]["content"] mcp.run()

然后在Claude Code的配置里启用这个MCP server。实测下来,Claude Code在长任务里的"下一步做什么"都由本地决策模型返回,稳定性和速度都ok。不过说实话,Claude Code本身自带的工具调用很出色,这种接入方式主要用于统一多Agent场景下的决策逻辑,或者当主模型API不够稳定时的容灾方案。

4.3 接入OpenCode

OpenCode对OpenAI兼容provider的支持是我见过最顺滑的,没有之一。配置文件里加一段:

{ "$schema": "https://opencode.ai/config.json", "provider": { "neohorse": { "npm": "@ai-sdk/openai-compatible", "name": "NeoHorse", "options": { "baseURL": "http://localhost:8000/v1", "apiKey": "local" }, "models": { "neohorse-jev-4b": { "name": "NeoHorse-Jev-4B" } } } }, "model": "neohorse/neohorse-jev-4b" }

OpenCode的架构是把模型提供方和Agent逻辑解耦的,所以切换模型非常轻量。但要注意:OpenCode默认会把整个Agent循环都交给这个模型,这时如果你只配置了NeoHorse-Jev-4B,它不但要决策,还要写代码,很快就会露馅。正确做法是让OpenCode使用主模型跑完整循环,而在工具调用过程中,把"选择下一步动作"这一环用NeoHorse的决策输出作为参考注入。我是在自定义工具层里处理的:拦截到OpenCode的"下一步动作选择"钩子,调用本地决策模型获取推荐动作,再交还给主模型确认。

5. 实测对比与行为差异:NeoHorse-Jev-4B vs Jev

5.1 评测任务设计

为了说清楚"我这个复刻版到底行不行",我设计了一组对比评测。评测集合从真实项目里抽了100个典型Agent任务,覆盖五类场景:

  • 文件检索类(20个):根据模糊描述定位到具体文件和代码行。
  • 多步重构类(20个):需要跨文件移动、重命名、改引用。
  • 依赖安装报错类(20个):根据报错信息决定是重装、升级还是改约束。
  • 测试修复类(20个):针对失败的测试决定查看源码还是改配置。
  • git操作类(20个):提交、回滚、分支切换等操作决策。

每个任务记录以下指标:

  • 决策动作准确率:模型选出的下一个动作是否与人工标注的"合理动作"一致。
  • 平均每轮决策输出tokens:决策模型的输出长度,直接影响成本。
  • 首token延迟:从发送请求到收到第一个token的时间。
  • 决策轮数:完成一个任务平均需要多少轮决策,反映决策质量对任务收敛速度的影响。

5.2 数据对比

指标Jev(原版)NeoHorse-Jev-4B
决策动作准确率87%83.5%
平均每轮决策输出tokens185102
首token延迟(本地int4)约320ms约45ms
决策轮数(修复类任务平均)6.26.8
上下文占用策略全部历史近3轮+摘要

整体结论:NeoHorse-Jev-4B在动作准确率上比Jev原版低了3.5个百分点,但胜在速度和成本。每轮决策输出压缩了将近一半,这意味着同样的任务,Agent每走一步只花Jev大约一半的tokens。而在多步重构这类任务里,决策轮数略高,说明偶尔会有"选错动作导致多绕一圈"的情况——这个差距后面还会单独分析。

5.3 典型的失败案例

我的评测集里有一个案例特别典型。任务是"修复tests/test_api.py第42行报错的AttributeError"。人工标注的合理第一步应该是read_file查看出错文件附近代码。但NeoHorse-Jev-4B第一次给出的动作是grep AttributeError在整个项目里搜索,这个动作本身不致命,但平白多了一轮工具调用。对比Jev原版,它会直接读文件,路径选择更精准。

还有一个案例是修改一个HTTP请求超时参数。模型连续两次选择了write_file,第一次把超时改成30秒,第二次又改回15秒——原因是Agent循环中上一步的写入结果和测试反馈还没有完全同步,模型在上下文里"忘掉"了自己刚改过这个文件。这种问题是决策模型通病,和参数量关系不大,更多是上下文组装策略的问题,后面我会讲怎么缓解。

整体来看,83.5%的准确率在真实Agent场景中是可用的,因为决策错误不会直接导致任务失败,只是会多绕几步路。配合一个"决策回退"机制(检测到同一动作重复超过两次就强制转向),整体任务完成率可以拉回到接近Jev原版的水平。

6. 从模型到工程:绕不开的坑与调优经验

6.1 数据污染与"纸上谈兵"倾向

我训练第一版模型时犯过一个典型的错误,就是训练数据里思考过程太多、动作太少。结果模型学会了"总结分析"但迟迟不调用工具,输出一大段thought之后动作还是空的,Agent就在原地打转。这其实就是数据污染——我用大模型改写数据时保留了太多笔记性质的文字,模型被带偏了。

解决办法是严格控制训练样本中thought和action的比例。我把数据清洗规则改成:thought不超过50个token,action必须具体且完整,动作类型分布要均衡。另外强制提升了数据集中动作类型的多样性,避免read_file一家独大,否则模型会形成"遇到任何情况先读文件"的惰性策略。

6.2 输出鲁棒性与schema校验

即便输出协议设计成JSON,模型在低精度量化下还是可能出错。我测试过Q3_K_M和Q2_K两种量化,发现动作类型字段偶尔会拼错,比如read_file变成read_fiel,参数里的路径偶尔少个引号。这个问题在Q4_K_M以上基本消失,但为了保险,我还是在Agent框架层加了一道schema校验:

import jsonschema decision_schema = { "type": "object", "properties": { "thought": {"type": "string", "maxLength": 100}, "action": {"enum": ["read_file", "run_command", "search_files", "write_file", "finish", "ask_user"]}, "action_input": {"type": "object"} }, "required": ["action", "action_input"] } def validate_decision(raw): try: data = json.loads(raw) jsonschema.validate(data, decision_schema) return data except Exception: return None # 触发重新决策

实测发现,加了schema校验后,即使模型偶尔输出格式错误,框架也能优雅处理(重新决策一次),而不会直接崩溃。这种"模型输出不可靠,但框架足够可靠"的思路,是决策模型上生产的关键。

6.3 多轮工具调用的记忆问题

我最开始直接把最近全部历史塞给决策模型,结果很快发现两个问题:一是上下文一长,延迟明显上升;二是模型在长上下文中容易"迷失重点",反而选错动作。我后来改成**"最近3轮+全局摘要"**的策略:

  • 保留最近三轮完整的(状态, 动作, 结果)。
  • 更早的历史压缩成几行摘要,摘要由主模型在关键节点生成,比如"已完成config.py的修改,测试test_db.py仍在失败"。

这个策略在实测中效果很好:决策准确率不但没降,反而因为上下文更聚焦提升了约2个百分点,延迟也稳住了。如果你的Agent工具允许自定义上下文组装逻辑,强烈建议不要无脑塞历史。

6.4 生产拓扑建议

最后说一个运维层面的建议。决策模型虽然小,但也别把它当真正的"主力模型"用。我最终落地的生产拓扑是这样的:

  1. 主模型:跑代码生成、代码补全、复杂推理。
  2. NeoHorse-Jev-4B:跑每一步的"下一个动作"决策,用本地vLLM部署。
  3. 回退策略:当决策模型连续两次返回非法动作,或者校验失败,自动让主模型直接决策,并记录日志方便后续分析。

这个拓扑的好处是:即使决策模型偶尔犯错,主模型兜底,任务不会卡死;而决策模型正确响应的时候,整个循环的成本和速度都被优化了一截。

我自己的实跑体会是:把决策单独拆出来,并不是要让小模型替代大模型,而是让大模型把精力花在真正需要创造力的地方。就像一支团队里,资深的工程师不要事事亲力亲为,把"查资料、跑测试、看报错"这类高重复度决策交给工具链,整体效率反而更高。NeoHorse-Jev-4B是一个很轻的起点,后面还可以顺着这个思路微调出针对前端、数据工程甚至非代码领域的专用决策模型,毕竟模型小、训练快、部署成本低,迭代周期可以压得非常短。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 20:26:16

RGA性能优化实战:多核并行与内存压缩的工程落地

RGA这个数据结构&#xff0c;算法层面讲再多&#xff0c;最后还是要回到一个现实问题&#xff1a;跑通demo很容易&#xff0c;扛住真实业务很难。我在前面几篇连载里把RGA&#xff08;Replicated Growable Array&#xff09;的基本操作、副本合并规则、一致性协议都过了一遍&am…

作者头像 李华
网站建设 2026/10/27 20:26:36

前端缓存完全指南:从HTTP缓存到Service Worker的实战优化

前端缓存这玩意儿&#xff0c;在圈子里一直是“人人都知道很重要&#xff0c;但真没几个人认真做”的状态。我身边不少同事&#xff0c;一聊性能优化就是上CDN、压缩图片、加懒加载&#xff0c;问起缓存策略&#xff0c;得到的回答往往是“哦&#xff0c;那不就是设置个Cache-C…

作者头像 李华
网站建设 2026/10/16 1:08:05

Chatbot联网搜索演进:从搜索框到Agent的完整技术指南

最近在开发者社区里翻Agent相关的内容&#xff0c;发现大多数人聊的是架构、记忆、编排这些上层概念&#xff0c;却很少有人单独把“联网搜索”拎出来讲。但凡是做过Chatbot的人应该都有同感&#xff1a;模型知识截止、答非所问、一本正经编新闻&#xff0c;这些问题绕來绕去最…

作者头像 李华
网站建设 2026/10/27 18:03:40

DSec弹性计算:智能体训练的语义感知沙箱架构

1. 项目概述&#xff1a;这不是“又一个沙箱”&#xff0c;而是智能体训练范式的底层重定义 DeepSeek弹性计算&#xff08;DSec&#xff09;这个名字乍看像套技术包装话术——“弹性”“沙箱”“智能体训练”&#xff0c;堆砌了当下最热的三个词。但我在实际参与过两个千卡级智…

作者头像 李华
网站建设 2026/10/16 15:55:21

Agent Skills 实战:为 AI 编程助手构建可复用技能模块

1. 从“skills”这个标题说起&#xff1a;它到底指什么第一次看到“skills”这个标题&#xff0c;很多人会以为是某个招聘网站上的技能标签&#xff0c;或者是一份简历里的能力清单。但结合热搜词里反复出现的 Claude Code、Codex、agents、plugin 这些词&#xff0c;就能判断出…

作者头像 李华
网站建设 2026/10/25 13:33:11

superpowers技能扩展框架:重构开发工作流与效率提升实战

1. 什么是 superpowers&#xff0c;以及它到底解决什么问题第一次看到“superpowers”这个词&#xff0c;是在一个技术社群的分享帖里。有人问“有没有类似《超能力》一样的东西&#xff0c;可以让我的工作流瞬间起飞”&#xff0c;底下有人回复了一个链接&#xff0c;就是这个…

作者头像 李华