Jev 接入编码代理:Cursor 与 Claude Code 配置

更新于 适用版本 Jev 1.13

编码代理擅长产出改动,却不太擅长给改动”划硬线”。“这能接受吗?“恰好是聊天代理最容易和稀泥的问题类型——它给出流畅的散文,对不对另说,没有可校验性,也没有 confidence 信号可以升级。Jev 的三种原语返回带 confidence 的结构化 JSON,正好可以作为外部裁判嵌进代理工作流:代理写代码,Jev 做有界判断。

这篇把整套接法讲清楚:什么是技能包、文件怎么排布、一个完整可运行的批量分类示例、让代理少犯错的写法,以及怎么排查代理声称看不见的技能。

TL;DR: 把 Jev 调用包进一个脚本,把它打包成 .claude/skills/ 或 ~/.claude/skills/ 下的技能目录,给技能写一个 description——前 57 个字符必须是自包含的触发句。代理加载技能,匹配到触发条件时运行脚本,读取结构化答案并分支。密钥放环境变量,每批最多 10 条,加上 confidence 门控再行动。上线前到 OpenRouter 模型页确认确切的 model slug。

为什么要给代理配一个判断模型

问代理”这次改动是否违反我们的日志规范”,它会给出一段自信的散文,对不对另说。两个随之而来的问题:答案无法被机器校验,也没有 confidence 信号可以升级。Jev 调用一次解决两个——答案以带 confidence 的结构化 JSON 返回,代理的工作流可以确定性地分支。核心思路:代理负责生成,Jev 负责判断。

还有一个不太明显的好处。让代理把判断嵌在自己的推理里,推理和生成就纠缠在一起了——你无法区分是规范检查驱动了代码,还是代码驱动了规范检查。把判断外包给一个独立的、有日志的 Jev 调用,两者就干净地分离了。每个决策都有可独立审查的调用记录,和代理后来做了什么无关。

技能包是什么——以及它不是什么

技能是代理运行时在会话启动时加载为命名能力的一个目录。最低配置是一个带 frontmatter 头的 SKILL.md;可选添加 scripts/ 子目录放可执行辅助脚本,以及 references/ 子目录放分主题的参考文档供代理按需读取。

三个”不是”:

实践判断:如果能把能力描述为”带这些参数运行这个脚本、解析 JSON 结果”,技能就是合适的打包方式。如果需要运行时注册一个在生成过程中可以带结构化参数调用的新工具函数,那就用 MCP。

目录结构与文件契约

.claude/
  skills/
    jev-judge/
      SKILL.md          ← 必须:指令 + frontmatter
      scripts/
        judge.py        ← 代理调用的可执行脚本
      references/
        field-names.md  ← 代理按需读取的参考文档

同样的结构放在 ~/.claude/skills/ 下是用户级技能,随你跨项目使用;放在项目根目录的 .claude/skills/ 下是项目级技能,仅对当前仓库生效。

SKILL.md frontmatter 字段:

字段是否必须约束
name是小写,允许连字符,不允许空格,须与目录名一致
description是前 57 个字符必须是自包含的触发句,见下文

description 的约束是”技能不触发”类 bug 最常见的根源。代理运行时在加载时按 description 建立技能索引,调用时将其与代理意图匹配。如果前 57 个字符是通用总结(“这个技能帮助你以各种方式调用 Jev API……”),匹配器没有具体特征可以锚定。如果是触发句(“判断一个 diff 或文本是否符合规范,使用 Jev API”),匹配就干净了。

两个对比示例:

# 太通用——触发不稳定
description: "这个技能把 Jev AI 判断 API 集成到你的工作流中。"

# 前 57 字符是自包含触发句
description: "用 Jev API 判断 diff 或文本是否符合规范规则。调用 jev-judge/scripts/judge.py,返回带 confidence 的 JSON 答案。"

加载与重载: 运行时在会话启动时读取一次技能注册表。新增技能或修改 SKILL.md 需要重启会话才生效。已加载技能内部的脚本修改立即生效——脚本在每次调用时从磁盘读取,不缓存。

权限与沙箱: scripts/ 目录里的脚本在与其他代理调用 shell 命令相同的沙箱中运行。在 Claude Code 里,用户需要批准首次执行;同一会话内后续对同一脚本的调用不再提示。代理有权访问的环境变量对脚本同样可用。把密钥写进环境变量,不要写进技能目录里的任何文件。

一个完整可运行的示例

下面把一个 Jev 批量分类器打包成技能。代理可以调用它,每次对最多 10 条内容按命名规范做分类。

SKILL.md:

---
name: jev-judge
description: "用 Jev API 判断 diff、评论或文本是否符合规范规则。把问题作为第一个参数传给 scripts/judge.py。"
---

## 何时使用这个技能

需要机器可校验的答案时调用——规范合规、严重度打分、模板匹配。不要用于开放式设计问题。

## 如何调用

    python .claude/skills/jev-judge/scripts/judge.py "Judgment question: [你的问题]"

脚本从环境变量读取 OPENROUTER_API_KEY,把结构化 JSON 写到 stdout。解析 `answer` 和 `confidence` 字段。

## Confidence 门控

如果 `confidence` 低于 0.80,不要自动行动。把 `rationale` 字段内容展示给用户并请求复核,再继续。

## 批量使用

分类多条内容时,在循环里每条调用一次脚本。每次运行保持 10 条以内,避免超时。

scripts/judge.py:

import json, os, sys, time, requests

def ask_jev(question: str, retries: int = 2) -> dict:
    url = "https://openrouter.ai/api/v1/chat/completions"
    headers = {
        "Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
        "Content-Type": "application/json",
    }
    body = {
        # 上线前到 OpenRouter 模型页确认确切的 model slug
        "model": os.environ.get("JEV_MODEL_SLUG", "typesafe/jev-1.13"),
        "messages": [{"role": "user", "content": question}],
        "usage": {"include": True},
    }
    for attempt in range(retries + 1):
        resp = requests.post(url, headers=headers, json=body, timeout=30)
        if resp.status_code == 429:
            wait = 2 ** attempt
            time.sleep(wait)
            continue
        resp.raise_for_status()
        envelope = resp.json()
        result = json.loads(envelope["choices"][0]["message"]["content"])
        # 附带 token 用量便于可观测性——字段名为示例数据(example fixture)
        usage = envelope.get("usage", {})
        result["_tokens"] = {
            "prompt": usage.get("prompt_tokens"),
            "completion": usage.get("completion_tokens"),
        }
        return result
    raise RuntimeError(f"达到速率限制,已重试 {retries} 次")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(json.dumps({"error": "请把判断问题作为第一个参数传入"}))
        sys.exit(1)
    question = sys.argv[1]
    result = ask_jev(question)
    print(json.dumps(result, indent=2, ensure_ascii=False))

代理调用示例:

python .claude/skills/jev-judge/scripts/judge.py \
  "Judgment question: does this diff add logging of user PII? Diff: \"user_id=\${user.id} written to access.log\""

期望输出——示例数据(example fixture),正式字段名以官方文档为准:

{
  "answer": "yes",
  "confidence": 0.93,
  "rationale": "The diff writes user_id to an access log. User identifiers are PII under most logging policies.",
  "_tokens": { "prompt": 87, "completion": 41 }
}

代理读取 answer 和 confidence,应用技能指令里的 0.80 阈值,要么继续执行,要么把 rationale 展示给用户复核。

让代理少犯错的写法

脚本负责 API 调用,SKILL.md 正文负责认知引导。模糊的指令产生不一致的代理行为。下面六条规则覆盖了最常出错的模式。

把输入嵌入模板,而不只是问题模板。 代理复制问题模板后经常忘记替换实际输入。把模板和替换后的示例并排写出来:

模板:  "Judgment question: does this diff add logging of user PII? Diff: \"{diff_text}\""
示例:  "Judgment question: does this diff add logging of user PII? Diff: \"user_id=42 written to access.log\""

在指令里写明 confidence 门控,不要只存在脑子里。 说清楚阈值以及低于阈值时做什么。“如果 confidence 低于 0.80,不要自动行动——展示 rationale 并请用户复核”是明确的。“根据 confidence 自行判断”不是。

明确写出环境变量名。 指令里的 OPENROUTER_API_KEY 和脚本里的 os.environ['OPENROUTER_API_KEY'] 一一对应。如果名字不一致,脚本会抛 KeyError,代理会尝试”修复”它——有时候会把附近看到的值直接硬编码进去。在两个文件里保持变量名是单一来源,从根本上防止这种情况。

设置明确的批量上限。 “对每条内容分类”在没有限制的情况下会变成”在一个循环里处理 200 条”。写清楚批量大小(30 秒超时情况下,10 条是安全上限),列表更长时怎么处理,批次之间是否需要暂停。

写清楚失败路径,而不只是成功路径。 脚本在不可重试的状态码时会抛异常。告诉代理 raise_for_status() 触发时怎么做:“如果脚本以非零退出码退出,记录原始输出,向用户报告错误,不要在未经用户批准的情况下自动重试。“没有这条,代理要么无限重试,要么静默吞掉错误。

批量任务使用幂等提示。 如果代理在分类一批条目时会话中断,它会从头重跑整个列表,除非指令里说明了如何检测已处理的条目。一个简单的基于文件的进度日志,或者在输出文件名里包含输入的哈希,就足够了。

排查五步:技能不触发时

代理报告找不到技能,或者触发了错误的行为。按顺序走这五层——每步只有一个检查点和一个明确的修复方式。

第一步:description 触发句
   └─ 前 57 个字符是不是具体可操作的一句话?
      修复:改写为以动作开头,而不是以产品名开头。

第二步:目录路径
   └─ 技能是否在 ~/.claude/skills/name/ 或 .claude/skills/name/?
      修复:在项目根目录运行 ls -la .claude/skills/ 确认。

第三步:文件权限
   └─ 代理运行时能读 SKILL.md 并执行 scripts/ 吗?
      修复:chmod 644 SKILL.md; chmod 755 scripts/judge.py

第四步:frontmatter 语法
   └─ YAML 是否合法?(尾部空格、缺少引号、tab vs 空格)
      修复:运行 python -c "import yaml; yaml.safe_load(open('SKILL.md'))"

第五步:会话缓存
   └─ 技能是在当前会话启动后才加入的吗?
      修复:重启会话。注册表只在启动时读取一次。

还有一类不同的失败:技能触发了,但代理传了格式不对的问题。读一下实际 Jev 响应的 rationale 字段——如果它描述的是对问题类型的误解,说明代理复制了模板但没有替换输入。在 SKILL.md 正文里加一个更明确的示例。

故障排查指南有完整的 HTTP 错误对照表,用于脚本本身出错的情况;上面的五步法专门针对”技能找不到”这类问题。

技能工作流里 Jev 的 confidence 与回落

Jev 返回的 confidence 是 0-1 的分数。在技能工作流里,这个分数驱动两个不同的决策。

何时行动,何时升级。 在技能指令里设一个阈值——规范检查通常用 0.80,触及生产状态变更的操作用 0.85。高于阈值时,代理可以直接依据 answer 行动。低于阈值时,技能应输出 rationale 并移交给用户。不要用相同措辞重试低置信度的调用:低置信意味着模型已经看过这个输入,发现它确实模糊。改进问题——更多上下文、更具体的问题类型——才是正解,不是第二次完全相同的调用。

何时完全回落。 三种情况值得跳过 Jev 调用直接走人工复核:输入不足 10 个词(上下文太少无法可靠判断)、问题混合了多种题型(判断题加打分题合并在一个 content 里)、以及调用返回 answer 为 unclear。在技能指令里把这三种情况写为短路条件,让代理不会在它们上面打转。

如果输入文本少于 10 个词:跳过技能,直接问用户。
如果 confidence < 0.80:展示 rationale,路由到人工复核,不重试。
如果 answer == "unclear":等同于 confidence < 0.80,同样路由。

有了生产数据之后,confidence 与回落指南深入讲解如何选阈值并校准。

代理适合委托给 Jev 的任务

代理任务题型问题示例
收尾前的规范检查判断题“Judgment question: does this diff add logging of user PII? Diff: ”…""
进线 issue 分诊选择题“Choice question: does this issue match template bug, feature or question? Issue: ”…""
严重度估计打分题“Scoring question (1-10): how severe is this regression for users? Description: ”…""
提交信息把关判断题“Judgment question: does this commit message describe the change accurately?”
PR 标签分配选择题“Choice question: which label fits best — perf, security, ux, or other? PR title and diff: ”…""

每一行返回——示例数据(example fixture)——形如 {"answer":"no","confidence":0.91,"rationale":"..."} 的结构化答案;正式字段名以官方文档为准。

六条反模式

以下六种模式是技能接入 Jev 最可靠的失败路径,发现得越晚代价越高。

把整份 API 文档塞进 SKILL.md。 4000 字的文档不是技能指令,是文档转储。代理顺序读取,操作规则淹没在噪音里。把 SKILL.md 正文控制在 200-400 字的可操作指令,详细参考资料放进 references/ 目录供代理按需读取。

在脚本里硬编码 API 密钥。 api_key = "sk-or-abc123" 写进 scripts/judge.py 是等待发生的凭证泄漏。脚本在仓库里,仓库会被提交,提交有时是公开的,git 历史在你删除文件后仍然存在。用 os.environ['OPENROUTER_API_KEY'],并在 SKILL.md 里写明变量名。

没有批量上限地让代理循环处理 100 条。 代理收到 100 条 issue 的列表,在一个会话里顺序调用脚本 100 次,最后几次超时,代理从头重试,最终花了两倍 token 换来一半结果。上限设为 10 条,进度写文件,指令里告诉代理如何从上次检查点恢复。

指令里没有输出 schema。 代理解析 JSON 结果,但没有文档化的字段期望。Jev 版本间字段名变化时,代理静默产出错误答案,或抛 KeyError 后无限重试。在 SKILL.md 里把期望字段写成 example fixture,并附注正式字段名以官方文档为准。

指令里没有失败处理。 脚本在收到 500 响应时抛异常。代理捕获异常,决定”修复”脚本,把 raise_for_status() 改成 pass。明确写出预期的失败行为:“如果脚本以非零退出码退出,把原始错误输出报告给用户并停止。”

把技能当 MCP 用。 技能适合”带这些参数运行这个脚本”。它不适合”暴露一个代理在生成过程中可以带类型参数调用的函数”。如果你发现自己用复杂的 prompt 工程来模拟工具调用,那就是该换 MCP 的信号。MCP server 前面的端点配置见 API 渠道指南。

什么时候没用

给代理的每个问题只保留一种题型并嵌入 diff 或 issue。问题设计与状态管理指南讲如何把 prompt 写稳。第一次 API 调用指南完整讲解了请求/响应结构,如果你需要理解脚本在底层做了什么。

本文适用版本 Jev 1.13。

常见问题

+ Cursor 或 Claude Code 能调用 Jev 吗?

可以。最简洁的方式是磁盘上放一个封装了 OpenRouter OpenAI 兼容端点的小脚本,加上告诉代理何时运行它的指令。代理把脚本当 shell 命令执行,读取结构化 JSON,再根据 confidence 字段分支。不需要任何专用工具——一个函数、一个存放密钥的环境变量,就够了。

+ 技能文件和普通 prompt 模板有什么区别?

技能是把指令、可执行脚本和参考文档打包在一起的目录,代理把它作为一个有名字的能力加载。prompt 模板是一段你粘贴进对话的字符串——它不携带可执行代码,也没有自包含的参考资料。当代理需要调用私有 API 时,这个区别很关键:技能可以把脚本、重试逻辑和字段名参考资料一起打包成可安装单元,而不是把它们散落在文档各处。

+ 编码代理该让 Jev 干什么?

需要机器可校验答案的有界判断:这个 diff 是否违反日志规范、这个 bug 报告套哪个模板、这次回归在 1-10 量表上有多严重。开放式生成留给编码代理——架构讨论、代码编写、重构决策。Jev 回答答案空间可枚举的问题,并返回可用于分支的 confidence 分数。

+ 代理说找不到技能,怎么排查?

按顺序走五层:确认 description 字段前 57 个字符是一句自包含的触发句;确认目录位置正确(用户级 ~/.claude/skills/name/ 或项目级 .claude/skills/name/);检查文件权限(SKILL.md 和 scripts 须可读);用 YAML linter 验证 frontmatter 语法;最后重启会话,因为大多数运行时在启动时缓存技能注册表。Jev 调用返回的 rationale 字段也有诊断价值:如果它描述了错误的问题类型,说明技能触发了但代理传了格式不对的 prompt。

+ 技能工作流里 Jev 的 confidence 字段是什么意思?

confidence 是 0-1 的分数,反映模型在给定输入和问题措辞下对答案的把握程度。在技能工作流里把它当路由门:高于阈值(规范检查通常用 0.80 或 0.85)时代理可以直接依据 answer 行动;低于阈值时技能应该输出 rationale 并路由到人工复核,而不是重试。用相同措辞重试低置信度的调用几乎没用——模型已经看过这个输入了。改善措辞或补充上下文才是正解。

继续阅读