做 Agent 一段时间的人,十有八九会碰到同一个问题:Agent 不是不会做事,而是太会做“错事”。模型接到任务以后,常常在工具调用这一步自作主张,该调 A 接口的时候偏调 B 接口,该停下确认信息的时候偏要硬着头皮往下走。如果你也在给 Agent 加判断器、做工具路由、纠结 Laya 和 Jev 怎么选、怎么部署,那这篇文章就是写给你看的。
这篇文章是我在一个真实 Agent 项目里给系统加“判断器”的完整记录,核心是解决一句话:在模型和执行之间,加一层独立的判断逻辑,让每一执行动作都过一道闸门。这里会讲清楚判断器是什么、Laya 和 Jev 两条路线的区别、部署方式、选型思路,还有落地时踩过的坑和排查方法。适合正在做 Agent 落地、架构选型或者已经被“模型乱调工具”折磨过的工程师参考,哪怕你是刚入门想了解 Agent 内部设计,也能从里面拿到可复用的经验。
1. “判断器”到底是什么:Agent 架构里被忽略的那道闸门
1.1 没有判断器的 Agent 会犯什么错
很多时候我们把 Agent 想得太简单:模型收到用户的问题,输出一个函数调用,然后系统去执行,完事。实际上模型输出的函数调用只是“意图”,真正执行之后的副作用是不可逆的。我见过一个订单处理 Agent,用户只是问了一句“这笔订单能退吗”,模型直接调用了退款接口,数据库里的订单状态当场被改掉。幸好是测试环境,如果是生产环境,这种单点失误就会造成一笔真实的资损。
问题出在哪?出在模型默认“被调用就应该被处理”。大模型的概率生成本质决定了它不可能 100% 准确,在复杂上下文里,它会高估自己对上下文的理解,低估动作的副作用。这就像一辆车只有油门没有刹车,方向还经常跑偏。
那为什么不靠 Agent 的主模型自己判断?因为判断和执行这两件事的职责诉求不一样。执行模型追求的是“顺滑地往下接话”,判断逻辑追求的却是“该停就要停”。把这两个能力混在同一个模型调用里,等于让裁判和运动员是同一个人。一旦这个运动员状态不好,连吹哨的资格都没了。
1.2 判断器的两个核心判断:工具选择与风险闸门
判断器在 Agent 里干的事,主要就是两件:
一是工具选择的判断。模型给出一个候选工具之后,判断器要基于“用户目标 + 当前上下文 + 候选工具的说明”来决定执行这个工具是否合理。比如用户问“我账号里还剩多少钱”,候选工具里面有“查余额”和“提现到银行卡”,判断器要能识别出后者的风险,然后否决这个候选。
二是风险闸门的判断。这一步更进一步,不是看工具选得对不对,而是看当前这个动作是否具备执行条件。比如上下文里缺少关键参数、用户有情绪、动作有不可逆的副作用(支付、删除、修改状态),判断器都要拦截下来,要么拒绝,要么转为询问用户确认。我习惯把这两类判断拆成两个独立函数,而不是塞到一个大 prompt 里,这样后续调阈值和加规则的时候才不会被互相影响。
1.3 判断器在工作流中的位置
判断器不是独立于 Agent 流程之外的模块,它插在“工具召回”和“工具执行”之间。正常流程是:用户输入 → 意图识别 → 工具召回 → 模型给出候选动作 → 判断器干涉 → 工具执行 → 结果反馈给模型 → 循环。
判断器的输入有三个:用户目标、候选工具信息、当前对话上下文。输出有三个:放行、替换候选(比如从 A 工具换成 B 工具)、要求暂停确认。
这里有个细节值得提:判断器不要直接挡在主模型和上下文之间,否则每次推理都多一层延迟。更合理的做法是让主模型先自由发挥,判断器只对最终要执行的那个动作做一次校验。这样判断器也不会变成 Agent 的性能瓶颈。
2. Laya 和 Jev 两种方案的核心差异
2.1 Laya:本地部署、低延迟、私有化的“稳重型”
Laya 在我这个项目里是指一类可以本地部署的推理模型,典型的是 7B 到 13B 参数量级别,量化之后可以跑在消费级显卡上,甚至边缘设备也能带动。它的特点在于“本地”:数据和请求不出内网,延迟极低,单次判断调用实测下来大概 30 到 80 毫秒,比云端调用低了整整一个数量级。
本地部署的代价是能力上限。Laya 这类模型的推理能力和云端大参数量模型相比还是有差距,遇到复杂语义、隐含意图、模糊表达时,判断准确率会明显下降。我们在内部评测里用 500 条真实 Agent 日志测过,Laya 在“明确工具调用”场景下准确率能达到 95% 以上,但在“需要拒绝用户请求”这种软性判断上,只有 76% 的准确率。
所以 Laya 适合的角色是“快判断 + 预筛选”。负担那些高频、低风险、规则清晰的判断,比如上下文里有没有关键参数、候选工具是否存在、目标是否匹配。这类判断模式固定,本地模型完全可以覆盖,而且响应速度快,用户体验好。
2.2 Jev:云端 API、强推理、灵活的“指挥型”
Jev 则是指偏向云端大模型的推理服务,参数量大、上下文窗口长、推理能力强。它不像 Laya 那样需要自己维护硬件和服务,一条 API 就能接入,适合处理那些“需要真正理解语义”的判断。
Jev 的强项在于把模糊的事情想明白。比如用户说“帮我看着办”,模型需要理解这句话背后的执行边界;“这个操作会不会影响其他订单”,模型需要跨模块理解业务逻辑。这种判断如果交给本地小模型,结果大概率是瞎猜,交给 Jev 则能给出比较可靠的结果。
代价也很明显。第一是网络时延,单次判断调用通常要 800 毫秒到 2 秒,如果 Agent 每执行一步都要等 Jev 判断,用户会明显感觉对话变卡。第二是成本,判断器每次调用都在消耗 token,判断动作比普通对话更频繁,一个月下来 API 账单是肉眼可见的增长。第三是数据出域,企业内部敏感信息走云端 API,本身就是很多团队的心理坎和合规坎。
2.3 选型参考表与关键指标对比
| 对比维度 | Laya(本地部署) | Jev(云端 API) |
|---|---|---|
| 单次判断时延 | 30-80ms | 800ms-2s |
| 单次判断成本 | 硬件折旧 + 电费,几乎可忽略 | 按 token 计费,随调用量线性增长 |
| 数据私密性 | 数据不出内网,天然可控 | 请求发送到云端,需要评估数据合规 |
| 推理能力 | 中等,适合规则明确的判断 | 强,适合复杂语义和边界模糊的判断 |
| 维护成本 | 需要运维模型服务、监控硬件 | 只要维护 API 调用层,无需关注底层 |
| 典型适用场景 | 高频调用、隐私敏感、离线环境 | 低频但复杂、需要全局理解、跨模块关联 |
我一般给团队的建议是:先画一条分层线。凡是判断规则能明确写出来的,或者频率极高、要求低延迟的,走 Laya;凡是模棱两可、上下文缠绕、业务影响范围大的,走 Jev。最理想的状态是两者共存,做路由。
3. 两种方案的部署实操
3.1 Laya 的本地部署全流程
Laya 这类本地模型的部署有一套比较成熟的路径,我总结下来就是四步:环境准备、模型下载、启动服务、验证调优。
第一步是环境准备。我用的是 Ubuntu + NVIDIA 显卡的机器,显存 16G 以上会比较从容。CUDA 版本 12.1 左右,Python 3.10 以上,推理框架我推荐 vLLM,吞吐量高,适合服务化部署。如果设备资源紧张,也可以考虑用 Ollama 或 llama.cpp,这两个更轻量,部署门槛低,但并发能力和吞吐不如 vLLM。
第二步是模型下载。从 ModelScope 或者 Hugging Face 拉取量化后的模型权重,优先选 Q4_K_M 或者 INT8 量化版本。量化后 7B 模型大约占 4 到 5G 存储,13B 模型大约占 8 到 10G。下载时注意核对模型卡里的 requirement,有些模型对分词器版本有硬性要求。
第三步是启动服务。用 vLLM 启动示例如下:
python -m vllm.entrypoints.openai.api_server \ --model /data/models/laya-7b-q4 \ --served-model-name laya \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --host 0.0.0.0 \ --port 8001参数解释一下:tensor-parallel-size 是显卡并行数,单卡就填 1;gpu-memory-utilization 表示允许框架使用 85% 的显存,留一部分给临时请求和加载层;max-model-len 是最大上下文长度,我用 8192,判断器场景基本不需要更长。
第四步是验证。启动完成后,用 curl 发一个测试请求:
curl -X POST http://127.0.0.1:8001/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"laya","messages":[{"role":"user","content":"这是一个判断测试"}],"max_tokens":128}'返回正常就说明服务已经通了。这时候回头调整超时时间、并发线程数和 batch 大小。我的经验是 vLLM 默认配置适合文本生成,但判断器场景下请求量高、单次输出短,可以把 max-num-seqs 调大一些,用小 batch 换高并发,延迟反而更稳。
3.2 Jev 的接入方式与密钥管理
Jev 的接入比本地部署简单很多,本质上就是一次 HTTP 接口对接。但简单不代表可以马虎,尤其是密钥管理这一环。
我见过太多团队把 API key 直接写死在代码里,然后提交到仓库。这个风险不是“可能泄露”,而是“一定会在某个时间点泄露”。正确做法是把密钥放到环境变量或者独立的配置服务里:
export JEV_API_KEY="your-key-here"代码里通过 os.getenv 读取,不要在代码库里出现任何明文密钥。调用方面,一个最小示例:
import os import requests def jev_judge(user_goal, tool_info, context): api_key = os.getenv("JEV_API_KEY") resp = requests.post( "https://api.jev.example/v1/judge", headers={"Authorization": f"Bearer {api_key}"}, json={ "user_goal": user_goal, "tool_info": tool_info, "context": context, "max_tokens": 256 }, timeout=3.0 ) resp.raise_for_status() return resp.json()["decision"]注意我把 timeout 设成了 3 秒。判断器是 Agent 链路里的同步阻塞点,如果没有超时控制,一次 Jev 抖动会让整个 Agent 卡住几十秒。超时之后怎么处理,下面在降级策略里细说。
3.3 部署中常见的坑与优化
在部署过程中有几个容易被忽略的点,我先列出来:
第一是版本兼容。vLLM 和 CUDA 的版本匹配经常让人头疼,建议先看 vLLM 官方的 requirements 再装 CUDA,顺序反了自己会浪费一晚上。如果你用的是 Ollama,那就简单很多,Ollama 会把适配问题封装掉。
第二是显存不足的表现不是报错,而是性能雪崩。模型服务启动成功不代表它能正常工作。如果显存刚好卡在临界值,推理速度会突然慢几十倍。所以启动时不要贪 max-model-len,7B 模型塞到 8192 就差不多了,硬拉到 16K 会明显挤压 KV Cache 空间。
第三是请求并发测试。判断器场景的访问模式和普通聊天不一样,它是高频短请求,很多来自同一个 Agent 内部循环。建议部署后用 Jmeter 或 Locust 压测一下,看看服务在 20 并发下的 P99 延迟。如果 P99 超过 200ms,基本可以考虑换更小的量化模型或者升配置。
Jev 那边同样有优化空间。重点做三件事:配置好历史消息裁剪,只把最近几轮对话发给 Jev,而不是全部上下文;为判断器单独设计 prompt 模板,压缩输入 token;开启响应缓存,相同的判断请求直接返回历史结果,省掉重复调用。
4. 判断器在真实项目中的架构取舍
4.1 双轨方案:混合调度
我在项目里最终采用的方案是双轨混合调度:默认先走 Laya,快速判断;当 Laya 的置信度不够高时,再把请求升级给 Jev。这样既不牺牲高频场景的延迟,也能保证复杂判断的准确率。
调度器核心逻辑其实不复杂,我写了一个简单的 Python 伪代码来演示路由思路:
class JudgeRouter: def __init__(self): self.laya = LayaClient() self.jev = JevClient() self.low_confidence_threshold = 0.82 self.cache = {} # 键为 (goal, tool, context) 的哈希,值为判断结果 def judge(self, user_goal, tool_info, context): cache_key = hash(user_goal + tool_info + context[:200]) if cache_key in self.cache: return self.cache[cache_key] local_result = self.laya.judge_single(user_goal, tool_info, context) if local_result.confidence >= self.low_confidence_threshold: self.cache[cache_key] = local_result.decision return local_result.decision remote_result = self.jev.judge(user_goal, tool_info, context) self.cache[cache_key] = remote_result.decision return remote_result.decision代码里两个点比较关键:一是缓存。判断器最常见的场景就是重复判断同一类请求,缓存做得好,Jev 的调用量能降一半以上。二是置信度阈值。0.82 是我经过样本数据调出来的初始值,不同业务需要重新调,阈值太低会频繁走云端,太高又会放过误判。
4.2 缓存、超时与降级策略
超时和降级是判断器模块里必须设计好的三件事,缺一个都可能出生产事故。
超时策略:给所有判断请求都设置超时时间。Laya 本地服务超时设为 2 秒,Jev 超时设为 3 秒。超时不等于失败,而是触发降级路径。
降级策略我设计了三个层级:第一层,Laya 超时时降级到 Jev;第二层,Jev 超时时降级到规则兜底(用一个本地布尔规则引擎做简单判断);第三层,连规则引擎都停了,直接放行但记录审计日志。这里“放行”不是不负责任,而是判断器兜底时的保底原则:宁可放行一个可能错误的动作,也不能让 Agent 陷入无限阻塞。实际操作时,放行的动作会打上标记,后续系统会补偿校验。
缓存策略我做了两层。内存缓存放高频短时的判断结果,过期时间 5 分钟;Redis 缓存放跨服务共享的判断结果,过期时间 30 分钟。缓存键需要用规范化格式,不然同一个问题的不同表述会生成不同键,缓存命中率上不去。
这套混合方案实测下来的数据算是比较理想的:因为大部分简单判断被 Laya 拦截,整体平均判断时延维持在 120ms 左右;Jev 的调用量只占总量的 12% 到 18%;综合判断准确率从纯 Laya 方案的 88% 提升到了 96%,代价是每 1000 次判断多了不到 10 次云端调用,成本几乎可以忽略。
4.3 什么时候不需要上双轨
有一点必须强调,不是所有场景都适合上双轨。如果你的 Agent 只有三五个工具,判断规则完全可以硬编码到逻辑里,那你连 Laya 都不需要,一个 if-else 就能解决问题。双轨方案的价值在于工具数量多、动作频次高、判断维度复杂、上下文边界模糊。
我见过一些团队一上来就堆两个模型、混合路由、多级缓存,结果一个月过去还在调调度器,业务一点没跑起来。这属于过度工程。正确的顺序是:先写规则引擎,确认瓶颈到底在哪,再考虑加 Laya;Laya 不够了,再加 Jev。每一层都要有真实的性能数据支撑你上下一层的必要性。
5. 常见问题与排查实录
5.1 判断不准的三种典型场景
在推进判断器的过程中,我总结了三个最容易判断出错的地方,供大家参考。
第一种是上下文截断导致的误判。判断器收到的是压缩后的上下文,如果压缩策略丢失了关键信息,比如用户已经在前面明确说明了某个条件,截断后判断器看不到,就会把合理动作误判为越权。排查方法是把判断器实际收到的上下文打印出来,人工看一眼有没有丢掉关键字段。
第二种是工具描述过于简单导致的误判。有些工具的说明就一句话“获取订单信息”,判断器无法判断它到底会修改什么。解决方法是把工具描述写成结构化字段,特别标注“副作用:无/有(涉及资金变动)”“所需权限:只读/读写”。描述写得越具体,判断器的准确率越高。
第三种是阈值设置不合理导致的策略漂移。置信度阈值定得太低,很多低质量判断也混进来;定得太高,Jev 调用量飙升。这需要持续监控判断结果的分布曲线,每周调整一次阈值。
5.2 Laya 本地部署的常见问题速查
| 现象 | 排查方向 | 解决办法 |
|---|---|---|
| 服务启动后推理极慢 | 显存不足或 KV Cache 挤压 | 降低 max-model-len,关掉其他占用显存的进程 |
| 并发一高就 OOM | 进程内存限制未配置 | 在 systemd 或容器里设置内存上限,并调整 max-num-seqs |
| 返回结果和预期明显不符 | 量化版本太激进 | 换 Q8 量化或原始 fp16 版本,优先保准度 |
| 请求排队时间过长 | batch 策略未调优 | 增加 max-num-seqs,确认 gpu-memory-utilization 没有过低 |
| 模型加载时间很长 | 冷启动 | 提前做一次预加载,保持服务常驻,不要在请求时才拉起容器 |
5.3 Jev 接入的高频问题与避坑技巧
Jev 接入本身不难,难的是把问题隔离清楚。我碰到最多的是三种情况。
第一种是“偶发性超时”。这类问题要先区分是网络问题还是服务端压力问题。做法是在调用层统计连接建立耗时和响应首字节耗时,如果连接耗时正常但首字节耗时长,大概率是服务端负载问题,需要做本地重试或者切到备用节点。如果连接建立本身就慢,那就要检查网络链路。
第二种是“判断结果不一致”。同一个问题,上午返回放行,下午返回拒绝,前后判断标准漂移。排查方向是看自己的 prompt 模板里有没有带上批次号、是否隐式依赖了模型随机性,以及上下文是否被截断成不同长度。我在模板里强制加了 JSON 输出格式,把所有判断依据字段都显式列出,问题就明显缓解了。
第三种是“token 消耗比预期高很多”。原因通常是每轮判断都把完整历史消息重新发了一遍。解决方法是只发最近两轮消息加必要的业务摘要,判断结果输出用更紧凑的 JSON 格式,禁止模型输出解释性长文本。这样单次判断的 token 消耗能省至少 40%。
5.4 判断器与主模型的关系处理
还有一个经常被忽略但实际影响很大的问题:判断器判断完了,主模型不听判断器的怎么办。
我最初的做法是把判断器的输出硬塞回 prompt,让主模型重新考虑。结果经常出现主模型被“说服”之后又自己变卦的情况。后来换了思路,不再用自然语言说服,而是把判断器输出变成系统级的硬性约束:
SYSTEM_OVERRIDE = ( "系统级判断层已决定:本次不允许调用以下工具。" "如果你尝试调用,本次请求将直接终止。" "你可以做的是:向用户说明原因,并请求用户确认或补充信息。" "你不需要质疑这个决定。" )这个方案的核心在于把判断器的定位从“建议”变成了“系统约束”。主模型可以认输,但不能忽略约束。从实际效果看,硬约束方案比软引导方案在“主模型服从判断”这一项上的成功率提升了大概 20 个百分点。
最后分享一个小技巧。如果你也在做判断器,可以先从工具选择判断做起,不要贪心把风险闸门、意图校验、上下文校验一次性全上。先把最核心的一个判断做扎实,跑一周日志,看准确率和误伤率,再逐步扩展。判断器这个模块最怕的不是功能少,而是链路长、规则互相打架。等跑顺之后,可以把积累的判断规则沉淀成一份结构化数据,之后微调 Laya 模型用得上,判断准确率还会有明显提升。我一直觉得,判断器不会取代 Agent 的主模型,它是给 Agent 装配的一层刹车,而这层刹车,永远不能省。