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

发布时间:2026/10/8 10:59:40
复刻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(pathsrc/config.py)、run_command(cmdpytest tests/test_api.py)、或者finish(answer...)。这和主流生成模型有本质区别生成模型要解决的问题是给定上下文续写出高质量代码/文本输出空间巨大需要很强的世界知识和语言能力。决策模型要解决的问题是从有限的动作集合中选出最合适的一个本质上是判别式任务。它不需要写出几百行代码只需要把状态映射到动作上信息熵比生成任务低得多。打个比方主力模型是执行任务的手决策模型是握方向的方向盘。方向盘不需要会写代码但必须清楚什么时候该左转、什么时候该直行。理解了这一点4B参数量就说得通了。决策任务的知识密度要求没有生成任务那么高真正关键的是两点一是能准确理解当前状态的语义二是能严格按固定格式输出动作不跑偏、不发挥。Jev原版之所以很快在小圈子里火起来我总结有三个原因省钱决策输出很短而且可以用小模型跑每轮消耗的tokens比主力模型少一个数量级。快决策请求的响应速度直接影响Agent的体感小模型本地跑可以做到几十毫秒首token。可控决策格式固定后框架层可以精准解析不容易出现模型嘴里说要做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。把动作类型做成枚举有几个好处下游解析器不需要理解自然语言直接读action字段做分发。可以提前做参数校验比如read_file必须传路径run_command必须传命令字符串校验失败直接让Agent重新决策而不是盲目执行。后续扩展动作类型只需要改枚举和微调数据不需要动模型结构。这里有个教训我第一版让模型输出纯文本靠正则去猜动作结果模型的表述千奇百怪解析时天天出bug。后来改成JSON结构枚举类型解析稳定率从86%直接跳到99%以上。决策模型和生成模型不一样它不追求语言的丰富性只追求动作的确定性。3. 本地部署全流程从Ollama快验到vLLM生产级API3.1 五分钟快速体验Ollama对于想先试试水的人我推荐直接用Ollama步骤简单到不能再简单。先把训练好的模型转成GGUF格式用llama.cpp的convert脚本然后写一个ModelfileFROM ./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-4bOllama本身自带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.5GB3090上跑起来非常轻松vllm serve ./NeoHorse-Jev-4B-AWQ \ --served-model-name neohorse-jev-4b \ --quantization awq \ --max-model-len 8192 \ --gpu-memory-utilization 0.753.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约5GBRTX 3090 24GAWQ 4bit约55ms约280 tokens/s约5GBMacBook M2 Max 64GGGUF Q4_K_M约120ms约90 tokens/s约4GB内存对比Jev原版云端API的实测时延约300-500ms本地部署在速度上优势非常明显而且每轮决策的成本几乎可以忽略不计。这是我认为决策模型走本地化最合理的核心原因。4. 三个主流Agent工具的接入实战4.1 接入Codex CLICodex CLI支持通过配置文件指定模型提供方对OpenAI兼容接口的适配比较干净。我的做法是在~/.codex/config.toml里加一个自定义providermodel_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 CodeClaude Code的架构和Codex不太一样它默认走Anthropic接口自定义模型接入需要转换层。我试过两种方案第一种是网关转换方案。用LiteLLM这类工具架一个代理把OpenAI格式的请求转成Anthropic格式。这样Claude Code的请求会先打到网关再转发到本地的vLLM服务。但这种方案有个麻烦Claude Code内置的tool_use机制和OpenAI function calling的消息格式差异较大网关转换时经常丢字段尤其是tool_use_id的来回对应一旦丢了对不上Agent就不知道上一个工具结果属于哪次调用。第二种是我最终稳定使用的MCP工具方案。Claude Code支持MCPModel 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 接入OpenCodeOpenCode对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 Jev5.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个tokenaction必须具体且完整动作类型分布要均衡。另外强制提升了数据集中动作类型的多样性避免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 生产拓扑建议最后说一个运维层面的建议。决策模型虽然小但也别把它当真正的主力模型用。我最终落地的生产拓扑是这样的主模型跑代码生成、代码补全、复杂推理。NeoHorse-Jev-4B跑每一步的下一个动作决策用本地vLLM部署。回退策略当决策模型连续两次返回非法动作或者校验失败自动让主模型直接决策并记录日志方便后续分析。这个拓扑的好处是即使决策模型偶尔犯错主模型兜底任务不会卡死而决策模型正确响应的时候整个循环的成本和速度都被优化了一截。我自己的实跑体会是把决策单独拆出来并不是要让小模型替代大模型而是让大模型把精力花在真正需要创造力的地方。就像一支团队里资深的工程师不要事事亲力亲为把查资料、跑测试、看报错这类高重复度决策交给工具链整体效率反而更高。NeoHorse-Jev-4B是一个很轻的起点后面还可以顺着这个思路微调出针对前端、数据工程甚至非代码领域的专用决策模型毕竟模型小、训练快、部署成本低迭代周期可以压得非常短。