给它状态和选项,拿回概率分布
AgentJev 不写文字。你把当前状态和已经写好的问题交给它,一次前向传播返回每个选项的概率,生成的 token 数是 0。把它放在 Agent 循环里做门控、路由和打分,把写作留给大模型。
评分头
控制台回放的是 v1 权重在 RTX 5060 上的真实请求与真实输出,已加载校准温度。为了看清过程,动画把阶段放慢了;底栏是这次请求的实测耗时。
适合做什么
Agent 的大部分步骤不是写作问题:测试过了没有,下一步调哪个工具,这条命令能不能跑。常见做法是让一个 27B–70B 的模型写一段话,再从话里解析出布尔值。AgentJev 直接给出你提供的每个选项的概率,没有 JSON 需要修,也没有一句自信的错话需要识别。
门控
测试全部通过了吗?
这条命令安全吗?
返回 P(true)。你按自己的代价选门槛。
路由
下一步该做什么?
这张工单归哪个队列?
2–255 个候选,返回完整分布、最优项和领先幅度 margin。
打分
这次改动风险几级?
多急?
2–10 个有序等级,返回期望分 Σ i·Pᵢ 和最可能的等级。
五分钟跑起来
需要 Python 3.10+ 和一块 CUDA 显卡(本次优化后权重常驻约 1.4 GB 显存);CPU 也能跑,但慢很多。左边是步骤,右边的终端按顺序回放这几步在本机的真实输出。
克隆代码,建虚拟环境
git clone https://github.com/malevrigns/agent-jev.git cd agent-jev python -m venv .venv source .venv/bin/activate
git clone https://github.com/malevrigns/agent-jev.git cd agent-jev python -m venv .venv .venv\Scripts\Activate.ps1
装 PyTorch 和依赖
先用 PyTorch 官方选择器 装好 CUDA 版 torch(2.5+;RTX 50 系列选 CUDA 12.8 或更新的构建),再装其余依赖。
bash / PowerShellpip install -r requirements.txt pip install huggingface_hub safetensors
拉取固定版本的 v1 权重
只下载修订
7d43399的 7 个文件(权重、校准温度、配置、分词器),逐个核对大小和 SHA-256,确认 343 个张量都是 BF16,全部放进models/agentjev-v1。共约 1.2 GB,不再额外下载 1.5 GB 的 Qwen3 底座模型。bash / PowerShellpython fetch_v1_weights.py
别直接从 Hugging Face 默认分支下载。默认分支现在是另一个 FP32 的代码补全检查点,体积翻倍,也不是基准表里那组权重。启动服务
bash / PowerShellpython -m jev_service.server --checkpoint models/agentjev-v1/model.safetensors --model-path models/agentjev-v1 --temperatures models/agentjev-v1/temperatures.json --port 8149
只监听 127.0.0.1,要从别的机器访问请用 SSH 端口转发。报告就绪前会先跑一次预热请求;在 Windows 上,服务还会让自己不受后台节能限速影响。浏览器打开
http://127.0.0.1:8149/是自带的工作台。确认它活着,发第一个请求
bashcurl http://127.0.0.1:8149/health
看到
"status": "ready"和"encoder": "auto"就可以发请求了。
| 启动参数 | 默认值 | 说明 |
|---|---|---|
--checkpoint | — | models/agentjev-v1/model.safetensors;旧版快速开始生成的 agentjev_v1.pt 也能用 |
--model-path | — | 带 config.json 和分词器的本地目录,即 models/agentjev-v1。只读配置和分词器,不读底座权重 |
--temperatures | 不校准 | 在独立校准集上拟合的每类问题温度,建议总是带上 |
--port | 8149 | 与客户端和钩子一致 |
--device | cuda:0 | 没有显卡时传 cpu |
--max-tokens | 2048 | 单条候选路径的 token 上限,超长直接报错,不截断 |
--encoder | auto | auto:同一状态的所有问题共享一次状态编码(新)。question:只在同一问题内共享(旧行为)。shared/path:调试用 |
三种问题
一个请求是一段 state 加若干道题。state 可以是文字,也可以是对象或数组,对象会按键排序后紧凑地序列化成 JSON。题目的 id 只用来对应返回结果,模型看不到它;意思必须写进题目文字和选项描述里。
BOOLEAN是非题
{
"id": "done",
"type": "boolean",
"question": "Are all tests passing?",
"criteria": {
"true": "The suite is green.",
"false": "At least one test failed."
}
}criteria 可省略,此时两个候选就是 TRUE 和 FALSE。写清楚真假各指什么,效果通常更稳。
返回 probability(即 P(true))、value(P(true) ≥ 0.5)和两个选项的 distribution。
CHOICE选择题
{
"id": "next",
"type": "choice",
"question": "What is the useful next action?",
"options": {
"debug_failure": "Read the failing assertion.",
"submit_patch": "Open a pull request now."
}
}options 可以是对象(键作为返回的 value)或数组(返回下标字符串)。2–255 个,描述不能重复。
返回 value、description、top_probability、margin(第一名减第二名)和完整分布。选项顺序不影响结果。
SCORE等级题
{
"id": "risk",
"type": "score",
"question": "How risky is this change?",
"levels": [
"Isolated change, no external behavior.",
"A public signature changed.",
"Authentication may be bypassed."
]
}levels 是 2–10 个按从低到高排列的描述,顺序就是分值。
返回 level(最可能的等级)、score = Σ i·Pᵢ(0 到 n−1 的连续期望分)和 legend。
构造请求
在左边改状态和题目,右边实时生成请求体和各语言的调用代码,并按服务端的规则检查。“模型实际读到的文字”展示一条候选路径被拼成什么样。
读懂返回
每道题的答案都带完整分布。下面是一个 Boolean 门控在六段测试输出上的真实 P(true)。拖动门槛或点“扫一遍”,看哪些会被放行。门槛放多高取决于放错一次的代价:自动合并代码要严,提醒人工复核可以松。
问题:“Have all tests passed?”,带 criteria。状态是为本页编写的示例,概率是 v1 权重的真实输出(已校准)。
| 字段 | 出现在 | 怎么用 |
|---|---|---|
distribution | 全部 | 键是你传的选项 id(或下标),值相加为 1。永远先看它 |
probability / value | Boolean | P(true) 和按 0.5 切出的布尔值。生产里用你自己的门槛,不要只看 value |
top_probability / margin | Choice | margin 小说明模型拿不准,适合转交大模型或人工 |
score / level | Score | score 连续,适合排序和设阈值;level 是最可能的那一档 |
usage | 每次请求 | generated_tokens 恒为 0;backbone_input_tokens 是骨干实际算的 token;shared_prefix_states 是共享了状态编码的状态数;wall_ms 是服务端耗时 |
Python 客户端
agentjev_client.py 只用标准库。三个高阶方法各问一道题,evaluate(state, questions) 一次问多道。
evaluate(state, questions) 一次发;多个状态用 evaluate_many([{"state": …, "questions": […]}, …])。服务端拒绝请求时会抛出 AgentJevError,status 和 message 就是服务端给的原因。批量与性能 本次优化 · PR #15
每个候选都会被拼成一条独立的路径:[状态][问题][候选]。以前,一个状态下有多少条候选路径,状态就要被骨干重算多少遍;只有当同一问题有 ≥8 个候选且前缀够长时才复用。现在,同一状态下的所有问题和候选合并成一棵 token 前缀树,一次前向算完。每个 token 只关注自己的祖先,位置编码取它在原路径中的深度,所以每个候选读到的仍然恰好是自己那条路径。
每格是一个案例(1 段状态 5 道题,一次请求),亮起的时刻就是它在本机实际完成的时刻。
查看数据表
README 结果表里的 79.25% 来自训练时的 FP32 权重;发布的 BF16 权重经服务端是 79.35%(1,587 / 2,000),优化前后相同。逐题对比里变了的两道题,在优化前就是平局或近似平局(第一名与第二名相差 0.0031 和 0.0000),一道因此改对、一道改错。
- 把同一状态的所有问题放进一个请求,才能共享状态编码。分开请求,每次都会把状态重算一遍。
- 一次请求最多 32 个状态、128 道题、1,024 条候选路径,请求体 1 B–1 MB。多个状态放在
{"requests": [{"id", "state", "questions"}, …]}里。 - 某个状态的前缀树太大(单次前向超过 8,192 个 token 或 64 MB 掩码)时,这个状态自动退回按问题共享的旧路径,结果不变。
写好问题
AgentJev 只看得到文字。题目和选项写得越像一个清楚的判断,分布就越可信。
"options": {"a": "A", "b": "B"},意思藏在 id 里"retry": "Rerun the suite without changing the code."criteria,说清楚 true 和 false 各指什么接入 Claude Code
agentjev_hook.py 是一个 PreToolUse 命令钩子。Claude Code 执行 Bash、Write、Edit 前,它把工具调用发给本地服务,问一道 Boolean(安全吗)和一道四级 Score(风险多高),再给出三种判决之一:风险第 3 级并且判为不安全时,拦下这一次调用,理由交给 Claude,Claude 可以换一步继续;风险第 2 级及以上,或 P(安全) 低于 0.5 时,请你确认;其余情况不干预,按你自己的权限设置走。它从不回答 allow,所以不会绕过你的权限设置。服务没有响应时钩子直接退出,这次调用照常走权限流程。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Write|Edit",
"hooks": [
{ "type": "command", "command": "python /path/to/agent-jev/agentjev_hook.py" }
]
}
]
}
}真实输出,问题与 agentjev_hook.py 完全相同。默认连 127.0.0.1:8149;AGENTJEV_URL 改地址,AGENTJEV_ASK_LEVEL、AGENTJEV_ASK_BELOW 调整询问门槛,AGENTJEV_VERBOSE=1 让不干预的调用也显示判定。
排错
启动时报找不到分词器(local_files_only)
--model-path 传的是仓库名而本机没有缓存。传 fetch_v1_weights.py 下载好的本地目录 models/agentjev-v1,里面有 config.json 和完整的分词器文件。
400:question 'x' needs N tokens; limit 2048
状态 + 题目 + 某个候选拼起来超过 2,048 token。服务不会截断任何内容,需要你缩短状态(只保留相关的日志或 diff 片段)或缩短选项描述。
400:candidate descriptions must be distinct / choice requires 2..255 candidates
同一道题的选项描述必须互不相同;Choice 要 2–255 个选项,Score 要 2–10 个等级。用上面的构造器检查一遍。
400:batch exceeds 128 questions or 1024 candidate paths
一次请求的题目总数或候选路径总数超限。例如 32 个五题案例是 160 道题,要拆成两次;同一状态的题目尽量留在同一个请求里。
CUDA 不可用,或 RTX 50 系列报 no kernel image
装了 CPU 版 torch,或 CUDA 构建太旧。按官方选择器重装;RTX 50 系列需要 CUDA 12.8 及以上的构建。临时可以 --device cpu。
请求比文档里慢一倍(Windows)
Windows 11 会给不在前台的进程做节能限速。服务现在启动时会让自己退出限速,就绪行里会写 "power_throttling_disabled": true。本机交替实测同一个请求:不退出时约 75 ms,退出后约 31 ms。用旧版代码的话,把服务所在的终端放到前台,或者更新代码。第一个请求的 CUDA 初始化(约 1 秒)也已经挪进启动时的预热。
客户端连不上
先 curl http://127.0.0.1:8149/health 确认服务在。服务、客户端和钩子默认都是 8149;旧版服务默认 18765,显式传 --port 8149 即可。客户端和钩子访问本机服务时不走系统代理,不用另配 NO_PROXY。
想要升级前的行为做对比
启动时加 --encoder question。两种模式在测试集上的概率差异见上面的质量表。