给Agent做判断器这事,我一开始其实是拒绝的。市面上不少Agent跑起来像开盲盒:模型自己决定调哪个工具,经常在错误分支上越走越远,日志翻半天也找不到是哪一步出了问题。后来我在链路里塞了一个独立的“判断器”,让它在每个关键节点上决定“该不该做、下一步做什么、做到什么程度”,整个系统的稳定性肉眼可见地变好了。圈子里聊得比较多的两个方向,一个叫Laya,一个叫Jev。Laya是那种轻量、快速、专门做第一道闸门的判断器,Jev则是偏重推理、适合做深度审查和复盘的判断器。两者到底怎么选、怎么部署、怎么接入现有Agent框架,这篇就把我实测过的方案和踩过的坑一次性说清楚。想给Agent加控制层的开发朋友,可以参考这份完整的落地记录。
1. 判断器到底是什么:先搞清楚我们要给Agent补什么能力
1.1 Agent为什么需要一个“判断器”
很多Agent项目跑着跑着就失控,根源在于大模型本身的概率性输出。你让模型决定下一步调哪个工具,它大概率会按上下文猜一个,但“大概率”不等于“一定正确”。指令遵循再好的模型,在模糊表达、多轮对话、长上下文压缩之后,都可能做出错误选择。
举一个我实际遇到的场景。一个客户支持Agent,用户问“我的退款到哪了”,模型直接调用的是“创建工单”而不是“查询退款状态”。从语言模型角度看,两句都跟退款相关,选错情有可原;但从业务角度看,这就是一次事故,用户被导到一条完全错误的处理链路里。判断器要解决的,正是这类“模型自由发挥导致的确定性缺失”问题。
判断器不是简单的if-else,也不是把Prompt写得更长。它是在Agent与工具调用之间插入的一个独立决策层,职责可以细分成三类:
- 意图门控:判断用户输入是否满足当前节点的执行条件,不满足就拦下来。
- 工具路由:从候选工具集合里选出最合适的一个,而不是让模型自己瞎猜。
- 风险审查:在执行高成本操作(发邮件、删数据、转账)之前,再做一轮确认。
判断器可以是规则、小模型、大模型或它们的组合。Laya和Jev就是两条不同路线:一个负责快,一个负责深。
1.2 Laya与Jev的两条技术路线
Laya在设计上追求低延迟和高吞吐。它通常是一个参数量较小的模型,或者是一套规则加小模型的混合体,部署在CPU上就能跑得很舒服。核心能力是快速分类、打分、过滤和路由。比如判断用户输入属于“查询”还是“投诉”,从五个候选工具里选一个,这些都适合Laya干。
Jev则追求推理深度。它更适合做复杂规划、任务分解、错误审查、安全审计这类“需要把事想明白”的工作。Jev的参数量更大,需要GPU或较强的推理引擎支持,响应时间也明显更长。你不能让Jev处理每一个请求,否则Agent的延迟会高到没法用。
可以用一个生活化的类比来记:Laya像机场安检口,负责快速分流,包里有瓶水还是把刀,一两秒内给出结果;Jev像航线调度中心,负责规划全局路径,遇到恶劣天气怎么改航线、哪架飞机先放行,需要的是深度推理。Agent链路里,安检和调度都需要,只是分工不同。
两者的差别我用表格梳理一下:
| 对比项 | Laya | Jev |
|---|---|---|
| 参数量级 | 0.5B到3B左右 | 7B到14B甚至更大 |
| 典型硬件 | CPU即可流畅运行 | 需要GPU或高性能推理服务 |
| 单次响应时间 | 几十毫秒到几百毫秒 | 秒级甚至更长 |
| 上下文长度 | 短,够用即可 | 长,支持多轮与复盘 |
| 核心能力 | 分类、过滤、路由、打分 | 规划、审查、分解、审计 |
| 典型场景 | 每个请求都可以过一遍 | 低置信度升级、事后复盘 |
| 部署成本 | 低,内存占用小 | 高,需要显存与算力规划 |
1.3 从链路位置看选型:谁该做前端,谁该做后端
选型不是“哪个更强”,而是“哪一层需要哪种能力”。我建议在Agent链路的前端放Laya,在后端放Jev。
前端判断做在每次工具调用之前。Laya以极低延迟完成意图识别和工具路由,把90%以上的正常请求处理掉。这个过程不能重,一重整个Agent的响应就垮了。
后端判断做在关键节点或整轮任务结束之后。Jev负责复盘:刚才的执行路径有没有问题?是否出现了重复调用?用户真正想要的是不是已经被满足?这属于低频高价值的判断,慢一点可以接受。
更进一步,可以让两者串联。Laya先给出一个置信度,当置信度低于某个阈值时,把请求升级给Jev做深度判断。这种“快慢结合”的模式在实践中非常稳。我后面的部署方案也是按这个思路搭的。
2. 部署前的路线规划:决定后面会不会返工的三个问题
2.1 模型从哪来:API还是本地权重
部署判断器之前,先要决定用在线API还是本地权重。这个选择会直接影响后续的架构设计,改起来代价很大,最好一开始就想清楚。
只做验证和原型,直接调API最省事。注册、拿密钥、按文档调一下,半天就能跑通。但进入生产环境后,问题会冒出来:单次调用的延迟和费用不可控,敏感数据出网有合规风险,服务商一抖动整个Agent就跟着抖。
本地部署则刚好相反。前期要花时间拉模型、配推理引擎、做压测,但部署完就相对自由。并发自己控,数据不出内网,模型可以按需量化、裁剪、微调。费用主要是硬件成本,按调用量增长基本是边际递减的。
我给的决策参考是这样:开发阶段用API快速迭代,生产阶段切到本地权重;如果业务本身有强数据隐私要求,直接本地起步。模型权重可以从主流开源模型托管平台下载,注意看license是否允许商用,以及部署文档对硬件的要求。
2.2 跑在哪:云端GPU、纯CPU还是边缘盒子
判断器的硬件选型,取决于“跑什么模型”和“跑在哪一层”。我的经验是分三档:
- 云端GPU:适合Jev这类重推理判断器。7B到14B的量化模型在单张消费级或入门级专业卡上就能跑出可用的速度,配合vLLM这类推理引擎,并发能力也够。
- 纯CPU:适合Laya这类轻量判断器。1B到3B的量化模型用CPU推理,单次响应可以控制在几百毫秒内,部署简单,不用抢GPU资源。
- 边缘盒子:适合有视觉能力或本地优先需求的判断器。比如巡检Agent需要先判断画面里有没有异常,再决定要不要调用大模型做深度分析,这时候判断器就不适合放云端。
边缘侧我实际用过两种情况,可以给你一个方向参考。在RK3588上部署YOLOv8这类目标检测模型,先导出成RKNN格式并做量化,单帧推理在几十毫秒级别,完全能在摄像头端做实时判断。在Jetson Orin系列设备上跑轻量级语言模型,Orin Nano 8GB可以运行量化后的7B模型,但生成速度有限,适合低频判断;要做更复杂的深度推理,Orin NX或更高配置会更稳。
2.3 并发怎么扛:先想清楚流量模型
很多Agent项目部署完才发现并发扛不住,本质是没想清楚流量模型。Agent的并发和普通Web接口的并发完全不是一回事,一个用户请求可能触发多次工具调用,每次工具调用又可能触发一次判断器调用。这意味着用户并发是10,判断器服务实际承受的请求可能达到几十甚至上百。
所以判断器必须拆成独立服务,不要和Agent主进程混布。同时在接入层做削峰,常见做法是用Redis或消息队列把判断请求先接住,再由worker从容地消费。推理引擎的并发也要单独配置,Ollama里对应的是num_parallel参数,vLLM里对应的是max_num_seqs参数。
容量估算可以先用一个简单公式:并发数 = QPS × 平均响应时间。如果一个判断器接口QPS是20,平均响应时间0.5秒,那么至少需要10个并发位置,再留50%缓冲,就是15。但这只是初步估算,真正上线前一定要压测,因为token生成类服务的实际吞吐受显存、上下文长度、量化方式影响很大。
3. 实操:把Laya和Jev部署成独立判断器服务
3.1 技术栈选型与目录结构
我选用的方案是FastAPI加Ollama加Redis加Docker Compose。FastAPI负责对外提供HTTP接口,自带请求校验和自动文档,开发效率很高;Ollama作为本地推理引擎,支持拉取开源模型并提供兼容接口;Redis用来做任务队列削峰;Docker Compose负责一键拉起整套环境。
如果只是内部用一个极简API,Flask也完全够用。我自己在一些内部工具里就用过Flask,包少、逻辑简单,但一旦要接并发、做参数校验,FastAPI能省不少事。这里不纠结框架选型,核心是把判断器服务和推理引擎分开部署。
目录结构我大致是这样:
agent-judger/ ├── app/ │ ├── main.py # FastAPI入口 │ ├── schemas.py # 输入输出Schema │ ├── judger.py # 判断器调用逻辑 │ └── queue.py # Redis队列客户端 ├── docker-compose.yml ├── Dockerfile └── .env3.2 Docker Compose与关键配置
判断器服务与Ollama用Docker Compose一起编排,核心配置如下:
services: laya-api: build: . ports: - "8000:8000" environment: - LAYA_MODEL=qwen-laya-1.5b:q4_k_m - JEV_MODEL=qwen-je-7b:q4_k_m - OLLAMA_HOST=http://ollama:11434 - REDIS_URL=redis://redis:6379/0 depends_on: - ollama - redis ollama: image: ollama/ollama volumes: - ollama_data:/root/.ollama deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] redis: image: redis:7-alpine volumes: ollama_data:没有GPU的机器,把ollama服务里的deploy字段直接删掉,纯CPU跑Laya是没有问题的。首次启动后需要先拉模型,在容器里执行两条命令:
docker compose exec ollama ollama pull qwen-laya-1.5b:q4_k_m docker compose exec ollama ollama pull qwen-je-7b:q4_k_m这里有个细节,模型名称里的量化标记很重要。q4_k_m是4-bit量化,显存占用和推理速度比较均衡,是我目前用得最多的配置。如果你显存充足或者只跑CPU,可能连量化标记都不想用,但要记得推理速度会明显下降。
3.3 关键参数与调优逻辑
判断器服务里最关键的参数是并发数、上下文长度和重试策略。我直接说经验值,再解释为什么。
- num_parallel:单卡环境建议设1到2。设大了看起来吞吐高,实际到一定阈值直接OOM,得不偿失。Ollama默认是4,我之前没改这个参数,压测到并发20时进程直接崩了。
- num_ctx:Laya设2048到4096,Jev设8192到16384。判断器不需要像对话模型那样塞超长上下文,上下文越长推理越慢,显存占用也越高。
- timeout:API层设30秒,内部调用重试最多2次,采用指数退避。Jev偶尔会出现单次推理超过20秒的情况,超时设太短会导致正常请求被误判为失败。
- 队列:Redis队列长度设一个上限,比如5000,达到上限直接返回503,让上游Agent走降级策略。
这些参数不是拍脑袋定的,每个都需要压测验证。我一般是先按经验值设好,再逐步增加并发,观察延迟和内存的变化,找到拐点后回调20%作为生产配置。
3.4 接口约定:判断器的输入输出长什么样
判断器对外接口的设计直接决定Agent好不好接。输入我定义为“场景加候选工具”,输出定义为“决策加理由”,全部结构化。
请求体示例:
{ "scene": "customer_service", "user_input": "我的退款到哪了?", "candidate_tools": ["create_ticket", "query_refund", "transfer_human"], "context": "..." }响应体示例:
{ "decision": "query_refund", "confidence": 0.92, "reasons": ["用户明确询问退款状态"], "next_action": "call_query_refund_api" }这里有个非常重要的原则:输出必须强约束。模型返回任何非JSON的内容,都应该在代码层直接拦截,而不是侥幸解析。我见过太多Agent项目因为“偶尔解析成功”而忽略校验,最后在极端case上翻车。
用Pydantic定义响应模型,所有解析失败都会被捕获为judgment_failed,然后让Agent走默认策略。这个设计确保判断器永远不会把错误信息传递给下游工具。
4. 接入Agent框架与调优:让判断器真正落地
4.1 判断器在Agent框架里的位置
判断器不是一个独立运行的模型,它必须嵌入Agent的主循环才有价值。结合现在主流的Agent编排思路,我建议在两个位置插入判断逻辑:工具调用前和整轮任务结束后。
工具调用前的判断由Laya负责,它决定“这个请求该不该调工具,该调哪个工具”。整轮任务结束后的复盘由Jev负责,它审查“刚才的执行过程有没有问题,结果是否满足用户需求”。两者的调用频率完全不同,配合方式可以用下面这段伪代码理解:
def run_agent_loop(user_request, max_steps=5): for step in range(max_steps): decision = laya_judge(user_request, candidate_tools) if decision.decision == "need_human": transfer_to_human() return if decision.confidence < 0.6: decision = jev_review(user_request, history, decision) if decision.decision == "none": return result = call_tool(decision.decision) if jev_review(history + result).is_final: return result这段代码增加了至少一次模型调用,所以判断器必须保持轻。这也是我一直强调Laya要轻量化的原因,即使它只是做一个简单的二分类,只要延迟高,整个Agent就快不起来。
4.2 降低延迟:缓存、阈值、提示词优化
判断器接入后最大的痛点是延迟暴增。我试过几个有效手段,按收益从高到低排:
- 结果缓存:相同输入和候选工具组合,短时间内直接用上次的决策。我使用短时TTL缓存,缓存时间设5分钟,命中率相当可观。
- 低置信度升级:给Laya设一个阈值,只有置信度低于阈值才升级给Jev。这个策略把Jev的调用量直接降了一个数量级。
- 提示词里的白名单:不把全部工具名塞进去,而是每次只给最相关的3到5个候选,显著缩短生成长度。
- 服务预热:模型加载后第一轮推理往往很慢,服务启动时会主动发一条空请求做预热,避免上线后的首次调用超时。
延迟优化没有银弹,但组合起来效果明显。我之前把Laya服务的P95延迟从1.8秒降到了300毫秒,主要靠的就是白名单和缓存这两个手段。
4.3 日志、回退与安全审查
判断器会出错,所以必须有完善的日志、回退和审计机制。
日志要记录决策内容、置信度、原因和脱敏后的用户输入。出现误判时,这些日志是排查的唯一线索。回退策略要按业务风险分层:低风险操作可以放行,高风险操作宁可放弃也不能乱调。比如发送邮件、删除数据这类操作,判断器超时或失败时我倾向于直接转人工,而不是让Agent自己决定。
Jev另一个很有价值的用途是Agent记忆的安全审查。Agent在长期运行中会产生记忆,这些记忆如果被污染,会持续影响后续决策。让Jev定期审查Agent记忆,判断哪些记忆值得保留、哪些可能误导后续行为,能有效减少长期运行中的漂移问题。这个思路和Agent安全领域的实践方向是一致的。
5. 常见问题与排查实录
5.1 部署和接入阶段最常踩的坑
我把项目过程中遇到的典型问题整理成了速查表,对着排查效率很高。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 服务启动慢或失败 | 模型未下载完整 | 检查模型目录,重新执行pull命令 |
| 并发一上来就崩溃 | num_parallel设过大,显存溢出 | 调小并发,观察内存变化 |
| 输出JSON频繁解析失败 | 模型指令遵循弱,提示词约束不够 | 用强约束模板加few-shot,代码层强校验 |
| Jev频繁触发重试 | 超时设太短 | 把timeout调到30秒,改用指数退避 |
| 正常请求被误拦 | 判断器阈值过高或样本偏差 | 降低阈值,补充正向样例 |
| CPU推理速度慢 | 模型没有量化或上下文太长 | 换量化版本,调小num_ctx |
5.2 一次真实排查过程:并发从10调到50后的连锁问题
第一次压测时,我把并发从10调到50,结果Laya服务的平均延迟从80毫秒涨到2秒,整个链路像被掐住了一样。最开始我以为是Ollama并发参数不够,把num_parallel从1调到4,结果情况更糟,直接OOM。
停掉服务后排查,发现瓶颈不在模型推理本身,而是Ollama单实例的请求队列积压。请求一个接一个排队,排队时间远大于推理时间。后来把推理引擎从Ollama换成vLLM,并发能力明显改善,延迟也恢复到了可接受范围。
这个案例说明一个道理:判断器服务必须独立部署、独立压测,并且扩容要分阶段。不能指望一个默认配置扛住所有流量,也不能一上来就把并发拉满。
5.3 给新手的排查顺序建议
判断器出问题时,最忌讳的是到处猜。我建议固定一个排查顺序:
- 先看日志,判断是超时、OOM还是输出格式错误。
- 单独发一个测试请求,看判断器单次调用是否正常。
- 用压测工具直接压判断器服务,排除Agent框架的干扰。
- 再走全链路测试,确认Agent编排逻辑是否影响了判断器。
- 逐步调整并发和阈值,每次只改一个变量。
这套流程看起来简单,但能省下大量排查时间。判断器的核心价值是确定性和可控性,如果在排查阶段就一团乱麻,那这个判断器本身就失去意义了。
我个人在实际项目里的最终配置是:Laya用1.5B的量化模型,跑在CPU上,负责前端路由;Jev用7B的量化模型,跑在GPU上,负责深度复盘;中间用Redis队列隔开,避免流量尖峰互相影响。调优大约一个月后,整个Agent系统在50并发下能稳定运行。最后想提醒一句:不要追求单个判断器模型“看起来更聪明”,判断器最怕的是不可解释和不稳定。宁可让它笨一点,也要保证每次返回都符合约定。这个架子搭好之后,后面换模型、换硬件都只是微调,不会再伤筋动骨。