大家好,我是小木头。前面三期我们聊了 Jev 是什么,并用两个公开数据集做了基本的评估操作,尝试看看它的决策准不准。又对比了一款非常流行的开源产品叫做 Laya,有朋友呢也将其比作 Jev 的开源平替。这一期我想聊聊自己在一个小项目里的 Jev 应用实战,这个项目是我自己用的一个 Pi 小插件,叫 pi-jev-router。

Pi 是一款非常流行的开源智能体产品,有时我们也将它称为 Pi Agent 或者 Pi Coding Agent。那么在 Pi 官方文档里将一个特性叫做扩展,也就是咱们日常经常会提到的插件。社区里呢一般也叫它插件,那官方文档将其称为 extension。在后面介绍中,当我们提到 extension、插件或扩展,其实呢都是同一个东西。

它想解决的是一个我在日常智能体使用中的痛点:聊着聊着有时呢可能就跑偏了。不知道大家有没有这个困扰呢?比如我正在一个会话里认真排查一个结账服务的内存问题,这个服务总是因为内存超过上限被系统强制重启,也就是画面上会看到的类似这个 Kubernetes OOM 的情况。

排查到一半我突然想起我家里该换扫地机器人了,就顺手问了一句:“这个插件的作用就是防跑偏。”每条消息进入会话之前先让 Jev 判断一下,它跟当前在做的事情有没有关系。没关系就提醒一句问我要不要换个地方聊。插件本身不是重点,这期我想分享的是做它的过程中关于 Jev 的一些体会:怎么选场景、给它看什么、问它什么、概率数据怎么用,还有实测时可能遇到的一些坑。

关于刚才我提到的跑偏这个问题,那么跑偏为什么值得用来应用 Jev?或者 Jev 为什么能够在这里发挥作用呢?模型当然是能够回答那个无关的问题,但是这条消息从此就成为了这个会话历史的一部分。后面每一轮都会带上它,上下文看起来呢就似乎不那么干净了,不那么聚焦于整个会话中本在讨论的那个话题。

这个判断我期望要在每条消息之前就做一次,而且呢它得足够快、足够便宜。还要最重要的呢就是给出程序能够直接使用的结果,而不是一段话。这类判断和 Jev 的设计呢比较契合:答案的范围是固定的,一次前向计算就能给出每个选项的概率。我这边实测平均不到 0.3 秒。

Pi Agent 给插件开放了一个 input 事件,它发生在按下 Enter 之后、消息进入 Agent 之前。插件挂在这个位置就能够在上下文被污染之前就触发操作。Jev 再便宜也不是每条消息都值得问对吧?比如像斜杠小、像斜杠命令 shell 命令、单独的文件路径、15 个字符以内的一些短指令。

图片、Agent 运行中排队的消息,还有会话里的第一条输入,都在本地直接运行或直接放行就好。那会话闲置超过 12 个小时,或者上下文用掉 85%,由代码直接提醒也不需要问 Jev。那剩下真正需要判断的才发给 Jev。

第二件事是关于 state,也就是给 Jev 看些什么。我没有把整个会话发过去,当然了,这也完全没有必要。只发一个压缩后的视图:比如会话标题、最近的三条用户消息、上一条回复的末尾,以及这条新的输入。尝试让它了解最近对话正在做些什么,他们在讨论些什么。

每个字段都有一个长度上限,一次调用大约 800 个输入 token。至于判断有没有跑偏,显然需要知道的是当前在做什么,而不是全部的细节。state 越小速度越快、成本越低,也越不容易带进无关的噪音。第三件事情呢是问题:Jev 的问题都有固定的答案空间,这里最主要的是四个选项。

做四选一:第一个继续;第二个顺手问一个一句无关的;第三个是基于当前内容另做一版,这就类似于咱们 fork 还是说开始一项无关的新任务。这个呢就非常类似于咱们开启一个新的会话。另一个关键的是一个是非题:这条输入还在当前的主题上吗?Jev 只给概率,要不要提醒。

咱们是写在代码里的策略可以画成一张三行的决策表:无关的概率不到 0.6,那么就放行让当前的会话、当前模型继续处理。Jev 认为还在当前主题上,放行。剩下的呢才做提醒:提醒用户当前情况是该分岔还是该开启一个新的会话,基于当前内容另做个版本。

话题呢并没有变所以并不算跑偏,这个呢插件可以不管。这是我用下来感受最深的一点:让模型给出概率然后由代码来处理做最终的决定。阈值、优先级、例外情况都能够写成普通的代码,也可以测试也方便随时的进行调整。

当 Jev 判断当前用户的输入为一个无关的消息时,或者说更具体的说与当前这个聊天或讨论的话题无关时,插件会尝试弹出一个选择框。选项包含了像留在这里继续在当前会话进行对话,还是说从这里 fork 分叉出去,或者开启一个新的会话。

或者说留在这里并且呢本会话不再询问,也就是说不再引入 Jev 进行决策。选了 fork 或者新会话消息会被暂存,编辑器里出现斜杠命令 /route-go。要再按一次 Enter 才会真正的创建会话,或者是来到一个新的会话进行后续的问答。这个操作失败时原消息呢放回编辑器并不会丢失。Jev 负责判断,最终的决定权始终还是在用户的手里。

我们先来看看正常的工作:OAuth callback 写完了,接着跑集成测试,再更新 README。这都是同一件事两次呢都是 continue,并没有弹窗能看到的只有状态栏这一行每次判断都只有零点几秒。再看明显跑偏,也就是开头咱们介绍的那种情况:会话在排查结账服务的内存问题时新输入是比较扫地机器人。

Jev 判定这是一个与当前会话的主题无关的输入,那它就弹出提醒我选择新开会话再按一次 Enter。这个问题呢就会去到一个新的会话里面,原来的排查会话就保持不变。第三段是同主题的变体:主线在写比如一个 8 分钟的视频脚本,我想看看 60 秒竖屏版怎么安排。插件放行主力模型呢直接回答就好。

咱们可以用斜杠命令 /route-status 看一下这一次的原始答案:Jev 其实认为它更像是一个 fork,也就是做会话的分叉。还在主题上这个的概率呢是 0.85,状态栏上的 47% 是继续这个选项的概率。剩下的呢给到了 fork,而不是认为说它是一个无关的输入。

fork 并不在我定义的这个无关这个选项里所以呢代码是决定放行,这正好就是前面咱们说到的模型给出概率、代码呢做决定。要不要另开一个分支这个呢是由我自己或者是由用户来决定的。为了知道它到底判断的怎么样,我先写了一组带标注的场景一共 52 个。

我人工呢提前标好了预期值再来调用 OpenRouter 上的 Jev,每一组跑两轮。其中 22 个是改代码之前就标好的,改的时候呢不去看留着做验证。在这个插件上我之前之后呢也做了一些不同的版本,最早的版本 38 个正常输入里打断了一次。

14 个真正跑偏的里面呢漏掉了两三个,回头我看了漏掉的那些例子问题其实出在我的用法上。Jev 的判断是对的这条输入跟当前会话确实无关,但我给的选项里顺手问一句和新任务这两个呢都表示无关。于是呢概率被拆成了 0.45 和 0.47,在插件的旧版本中要求其中一个单独超过 0.6。

但这两个呢都没有过没有超过 0.6,因此在最终的判定上就放行了让当前的模型继续去处理在当前会话中继续的进行任务的执行。我觉得这个可能就是 choice 这类问题比较常见的一个坑:选项之间的语义有重叠概率呢就会被分摊,每个选项看起来呢都不够稳定。

我能想到的办法有两个:要么就是把选项设计的互不重叠,要么在代码里把这类同类选项的概率加起来。我用了第二种因为是顺手一问还是新任务只影响它这个最终会话或用户被引导去哪里,而不影响要不要提醒。改完之后在这组场景里面 14 个真正跑偏的都得到了提醒,38 个其他输入没有被打断。

从延迟上看平均延迟呢是 277 毫秒 P95 不到 0.35 秒。把 Jev 放在每条消息的必经之路上失败处理也要事先的做设计,比如没有 API key、比如 API 报错限流或者说 API 的响应超过 2.5 秒。在这些情况下呢都全部放行,出错代价只是少提醒一次并不会卡住当前的输入也不会丢失消息。

最后把这次的几点体会简单的总结一下吧:答案范围是固定的,同时呢需要响应速度快而且每次呢都要做的判断看起来呢是蛮适合交给 Jev。给它的 state 只放判断所需要的内容压缩好设好上限,问题要有固定的答案空间 choice 选项尽量呢也不要出现语义上的重叠。

模型给的概率由代码呢基于它来做一些决定看起来呢也是不错的选择。动手调整之前咱们先准备好数据集,特别是带了标注的符合自己场景需求的数据集。另外在判断失败时呢尝试放行让用户来决定后面该怎么做。

此外特别要提醒的是,在 Jev 模型的使用中我们一定要有预期:模型的判断是会有失败的概率的。那么咱们的应用场景中是否具备一定的容错性?对于这些失败是否能够接受?在当前这个场景中即使 Jev 判断失败了交给用户继续执行,或者是交付给模型直接在当前的会话场景中执行任务并不会影响到整个会话或者智能体的使用。

这也是具备了一定的容错性也使得 Jev 的使用呢具备了可能。这里呢也要说说限制:这 52 个场景是我自己写数据集呢我自己标注的并不是什么公开的基准。这组结果呢也不代表真实使用中不会出现误判。另外压缩后的会话片段或者说与 Jev 进行交互的数据在这个调用中显然数据是会发给第三方的 API。

因此在处理敏感代码之前一定要想清楚,对我来说这个案例里 Jev 最有价值的用法并不是聊天而是在 Agent 生命周期的一个节点上快速给出几个带概率的结构化的答案。再让代码来决定要不要提示用户,来决定会话数据究竟该怎么继续的延伸、继续的发展。

插件的源代码测试场景和数据呢我都会放在视频的描述中,如果你感兴趣呢也欢迎拿去试一试帮忙评估发现判断不准的例子也欢迎反馈给我。我也很想听听大家在 Jev 使用上的经验,如果你也在项目里用过 Jev 或者类似的决策模型那么欢迎在评论区分享你的实战案例吧。

那么我们也来一起互相的学习一起进步,好了我是小木头我们下期见吧