Jev 问题设计与状态管理实战

更新于 适用版本 Jev 1.13

判断模型会放大你喂给它的东西:一道边界清晰的 jev api 问题能让你拿到稳定、带 confidence 的答案用几个月;一道含糊的问题则让答案随输入的细微变化而漂移。所以在 jev tutorial 的集成实践里,问题设计是杠杆最高的一环。这篇讲怎么把判断题、选择题、打分题写稳,怎么定义模型真正能执行的边界,调用之间怎么传状态,置信度如何与状态配合,以及怎么在 n8n 这类自动化流程里做状态持久化。

TL;DR: 显式说清答案空间,用具体规则或示例定义边界,给棘手情况配 2–5 个 few-shot 示例,上下文以明确文本随问题传入。下游调用用 confidence 字段做门控。如果一个问题两次回答结果不一致,先改问题,再怀疑模型。

稳定问题的解剖

一道稳定的问题有四个部分,按这个顺序:

  1. 题型。 “Judgment question:”、“Choice question:” 或 “Scoring question (1-10):“——先报题型,答案空间才无歧义。
  2. 判定标准。 一句话说清什么算 yes、哪个选项胜出、高分意味着什么。
  3. 边界。 边缘情况算什么,写成规则或示例。
  4. 输入。 待判对象,明确分隔。

对比弱版本和强版本:

弱:"Is this review bad?"

强:"Judgment question: should this product review be hidden for
    violating the no-promotional-links policy? The policy prohibits
    links to sellers, coupon codes, and contact info. Mentions of the
    product's own brand do not count. Review: \"...\""

弱版本在邀请漂移:“bad” 没有答案空间。强版本报了题型、给了标准、点了边界、隔开了输入。它的响应——示例数据(example fixture),正式字段名以官方文档为准——长这样:

{
  "answer": "no",
  "confidence": 0.93,
  "rationale": "The review mentions the product's own brand only, which the policy explicitly excludes from violations."
}

选择题的选项枚举

选择题成也选项列表,败也选项列表。三条规则:

import json, os, requests

prompt = (
    "Choice question: which queue does this ticket belong to? "
    "Definitions: billing - charges, refunds, invoices; "
    "technical - product errors and bugs; "
    "account - login, permissions, profile. "
    "Ticket: \"...\""
)
resp = requests.post(
    "https://openrouter.ai/api/v1/chat/completions",
    headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
    json={
        # Confirm the exact model slug on the OpenRouter model page
        "model": "typesafe/jev-1.13",
        "messages": [{"role": "user", "content": prompt}],
    },
    timeout=30,
)
route = json.loads(resp.json()["choices"][0]["message"]["content"])

一句话定义把光秃秃的标签变成了边界;上线前去 OpenRouter 模型页确认确切的 model slug。解析出的 route 是示例数据(example fixture)——响应形态为示例,正式字段名以官方文档为准,形如 {"answer":"technical","confidence":0.9,"rationale":"..."}。

few-shot 示例在边界处最值钱

抽象规则覆盖分布的中间,示例覆盖边缘。针对你被坑过的场景,在问题里放 2–5 个示例:

Examples:
- "Buy now at deals.example, 50% off!" -> hide (promotional link)
- "This product broke after two weeks, very disappointed." -> keep (genuine complaint)
- "Great product, contact me at me@example.com for bulk orders." -> hide (contact info)
Now judge: Review: \"...\"

每个示例都是被具体化的边界。答案开始漂移时,为漂移的那一类补一个示例,而不是无限扩写规则。规则段落写得再长,通常也比不上一个选得准的示例——模型从具体实例推断规则的能力强过理解抽象描述。

如果你发现自己在堆超过五个示例,通常是信号:判定标准那句话还没说清楚。先退回去把标准句收紧,再考虑加示例。

多轮/多请求里的 jev state 生命周期

jev api 调用是单轮设计,状态不会在调用之间持久化——由你来搬运。理解 jev state 的生命周期,是把集成做稳的前提。

调用范围的状态随一次请求生死。当前的对话摘要、管线阶段、上一次 jev 的答案——全是调用范围的。写进问题文本,不要在别处缓存。

会话范围的状态跨越一个用户会话内的多次调用:累积的判断结果、本次对话的违规计数、用户在更早轮次声明的偏好。放在你的应用层(用 session ID 做键的内存字典对短生命周期会话足够),每次调用时把相关切片注入问题。

账户范围的状态是长期的:订阅层级、历史升级率、固定的政策豁免。属于数据库。每次管线运行取一次,嵌进状态块。

经验法则:jev api 只知道你在当前请求里告诉它的东西。 任何需要跨调用保持一致的内容,都得由你的代码管理。

def build_prompt(criterion, item, session_state, account_state):
    state_block = (
        f"Account context: {account_state['tier']} plan, "
        f"violations this month: {account_state['violation_count']}.\n"
        f"Session context: {session_state['prior_judgments_summary']}\n"
    )
    return (
        f"Judgment question: {criterion}\n"
        f"{state_block}"
        f"Input: {item}"
    )

状态块保持简短、只陈述事实。超过两三句话就压缩——冗长的状态会挤占模型处理输入的有效注意力。

什么时候状态应该随问题传入,什么时候该由服务端持有? 状态直接影响当次判断时,写进问题。状态跨越多次调用共享但很少真正改变单次答案时(比如用户的语言偏好、全局开关),放服务端、调用前注入一次就够了,过度传递状态会推高 token 成本,还可能把真正的输入内容挤走。

问题设计:粒度、数量与冲突

一个管线步骤里应该有几道 jev 问题?实践答案是:一次调用,一个判断。复合问题——“这是垃圾邮件,并且需要升级吗?“——逼迫模型在单次答案里打包两个独立决策,哪个子标准出了问题都很难追溯,迭代成本高。

一问题、一标准、一答案。 需要两个判断就发两次请求。增加的延迟,通常比省下来的排查时间值得。

问题粒度同样关键。“给这位候选人的沟通能力打分(1-10)“太粗,如果沟通能力包含书面清晰度、响应速度、压力下的表达——最好分维度各打一次,聚合逻辑留在你的代码里,可见、可调整。

冲突问题出现在两道标准有交叉但不完全一致的时候:同一篇文档既问”相关性”又问”质量”,可能产生矛盾信号,因为相关性里本来就有一部分质量的成分。把标准定义成正交的——相关性只看主题契合度;质量只看写作水准,与主题是否正确无关——冲突就消失了。

问题重复有时用作一致性校验:用略有差异的措辞问同一个事情,看两次结果是否一致。这个方法有效,但贵。更便宜的一致性检查是给 few-shot 示例集做版本管理——如果模型在你的示例集上的答案在两次部署之间发生了偏移,说明问题或模型变了,升版前先审计:

EXAMPLE_FIXTURES = [
    {"input": "Great product!", "expected_answer": "keep", "expected_confidence_min": 0.85},
    {"input": "Buy at spam.example", "expected_answer": "hide", "expected_confidence_min": 0.90},
]

def smoke_test(prompt_template):
    for ex in EXAMPLE_FIXTURES:
        result = call_jev(prompt_template.format(input=ex["input"]))
        assert result["answer"] == ex["expected_answer"]
        assert result["confidence"] >= ex["expected_confidence_min"]

每次修改问题后都跑一遍这个冒烟测试再部署,不到一秒,能拦住大多数回归。

置信度(confidence)与 state 的配合:低置信度时如何回退与再问

confidence 字段不是装饰。它是模型在告诉你它抵达了你提供的信息边界。置信度下降时,正确反应通常是补充上下文后重问,而不是接受答案继续推进。

用置信度为下游状态做门控。 在把 jev 答案嵌入下一次调用的状态块之前,先检查:

CONFIDENCE_THRESHOLD = 0.75

def safe_embed_prior_answer(prior_result, state):
    if prior_result["confidence"] >= CONFIDENCE_THRESHOLD:
        state["prior_judgment"] = (
            f"Earlier judgment: {prior_result['answer']} "
            f"({prior_result['confidence']:.2f}) because "
            f"{prior_result['rationale']}"
        )
    else:
        state["prior_judgment"] = "Earlier judgment: unclear (low confidence — omit from this call)"
    return state

把低置信度答案当确定信号传给下游,是最常见的 jev state bug 之一。下游调用把它当可靠来源使用,而实际上它不是。

补示例后重问是置信度低于阈值时的第一步动作。针对产出低置信度答案的那类输入,加一个具体的 few-shot 示例,然后重跑同一次调用:

def reask_with_example(original_prompt, low_conf_item, correct_answer, explanation):
    extra_example = (
        f"Additional example: \"{low_conf_item}\" -> {correct_answer} ({explanation})\n"
    )
    return extra_example + original_prompt

补示例后仍然低置信度,转人工审核。不要自动叠加超过一次重问——同一条输入连续两次低置信度,说明问题标准需要重写,不是再加示例就能解决的。

置信度与问题数量的相互作用: 如果你顺序发出十道问题,每道有 5% 概率低于阈值,大概有一半管线运行会至少撞上一次低置信度。在上生产前就把降级路径设计好。

多轮与多请求场景的状态传递

多轮交互和多步骤自动化管线都需要显式的状态管理,模式略有差异。

多轮(交互式): 用户在每步输入。状态包含压缩过的对话历史:

Context: User is screening a resume for a junior engineer role.
Prior turns:
  - Turn 1: scored technical skills 7/10 (confidence 0.91)
  - Turn 2: scored communication 6/10 (confidence 0.88)
Now judge: Scoring question (1-10): overall hire recommendation.
Candidate summary: "..."

注意只有前几次的分数和置信度向前传递,而不是完整的前几次 prompt。压缩而非拼接,保持状态块简短。

多请求(批量管线): 每条输入是独立的,但管线级别的上下文共享。维护一个不随输入数量增长的共享上下文字典:

pipeline_context = {
    "policy_version": "v2.3",
    "campaign_id": "camp_abc",
    "reviewer_tier": "enterprise",
}

def process_item(item, pipeline_context):
    prompt = build_prompt(
        criterion=CRITERION,
        item=item,
        shared_context=pipeline_context,
    )
    return call_jev(prompt)

如果某个管线步骤的结果会影响后续步骤——比如路由决策改变了下一次调用的判定标准——只传 answer 和一行 rationale,不要搬整个响应对象。

与 n8n/自动化流程接线时的 state 持久化与幂等

把 jev api 调用接进 n8n 或类似的自动化平台时,状态持久化和幂等性变成生产级关切。

持久化: n8n 工作流可能在执行中途失败。如果 jev 调用成功但后续写入失败,从头重跑会把这次调用计两次。用 n8n 内置的执行数据在每次成功的 jev 调用后做检查点:

{
  "item_id": "review_9182",
  "jev_answer": "hide",
  "jev_confidence": 0.93,
  "jev_called_at": "2026-09-28T08:42:00Z"
}

在继续工作流之前,先用数据库节点把这个检查点写进去。重试时检查 jev_called_at 是否已经为该条目设置——设置了就跳过 jev 调用,直接用存好的答案。

幂等性: jev 问题本身是幂等的——同样的问题加同样的输入返回同样的答案。风险在于你附带的状态。如果失败和重试之间会话状态块发生了变化(因为另一个事件更新了它),答案可能随之变化。调用时对状态块做快照,连同答案一起存下来,之后能审计是哪个状态产出了哪个结果:

checkpoint = {
    "item_id": item_id,
    "prompt_snapshot": prompt,        # 发出的完整字符串
    "answer": result["answer"],
    "confidence": result["confidence"],
    "rationale": result["rationale"],
    "called_at": datetime.utcnow().isoformat(),
}
db.upsert("jev_checkpoints", checkpoint, conflict_on="item_id")

n8n 具体模式: 用 “Set” 节点从上游节点组装状态块,再接 HTTP Request 节点调用 jev api。这让状态构建过程在工作流图里可见,比塞进 Function 节点更容易排查:

[取账户数据] → [取会话状态] → [Set: 拼状态块] → [HTTP: jev api 调用] → [Set: 写检查点] → [后续操作]

HTTP 节点失败时,n8n 从该节点重试;账户数据和会话状态已经在执行上下文里了,不需要重新拉取。

常见错误与 debug 路径

症状:上周还稳定的答案现在开始漂移。 最可能的原因是输入分布变了,不是模型变了。记录一批输入和它们对应的答案,在低置信度的案例里找新出现的模式。为每个新模式补一个 few-shot 示例。

症状:某一类输入的置信度稳定在 0.5 附近。 问题边界对这一类输入本就含糊——模型没有坏,它在告诉你标准对这类情况无法清晰地区分 yes 和 no。收紧标准句,或者拆成两道问题。

症状:jev api 调用成功,但解析出的答案缺少预期字段。 你在解析一个不完整的缓存响应或降级响应。先确认 Content-Type 是 application/json,choices[0].message.content 是合法的 JSON 字符串再解析。网络代理或限流响应有时返回非 JSON body,json.loads 会直接抛异常。

症状:上一个会话的状态块意外影响了当前结果。 会话 ID 在碰撞,或者你在跨请求复用同一个状态字典没有清空。用 session ID + 请求时间戳的组合作为每个状态字典的键。调用范围的键在每次调用开始时显式清掉。

症状:n8n 工作流重试时对同一条输入调用了两次 jev。 检查点写入在 HTTP 节点之后、某个可能失败的转换步骤之前。把检查点写入移到紧跟 HTTP 节点的下一步,在任何转换之前。

多语言评论审核的 case 展示了枚举政策边界与 few-shot 的合体用法,横跨多种语言;你的边界定义偶尔会产出 unclear 答案,完整的决策树(包括人工升级阈值和自动重问策略)见《Jev 置信度与兜底策略》。

本文适用版本 Jev 1.13。

常见问题

+ 什么让 Jev 问题不稳定?

边界含糊、选项没有枚举、标准隐而不宣。如果两个靠谱的人会给出不同答案,模型的答案也会漂移。

+ few-shot 示例要放几个?

两到五个覆盖边界情况的精选示例,通常胜过一大段抽象指令。

+ 怎么把对话历史或上下文传给 Jev?

把相关状态显式写进问题文本——前几轮对话、用户属性、当前管线阶段——作为同一条 message content 的一部分。

+ 状态应该放在问题文本里还是由服务端持有?

账户级别的长期状态(订阅层级、历史违规次数)放数据库;调用范围内的临时状态(当前对话片段、上一次 Jev 答案)随问题文本传入。

+ 低置信度的 Jev 答案传给下游会怎样?

下游调用会继承这个不确定性。在将答案嵌入下一次调用的状态块之前,先用 confidence 字段做门控——低于阈值的一律视为 unclear,补一个示例再问,或转人工审核。

+ jev api 调用结果一直不稳定,从哪里开始排查?

先检查问题文本本身是否稳定——记录发出的完整 prompt 字符串,跨调用比较。再看 confidence:如果在 0.5 附近徘徊,说明边界对模型本身就含糊,添加一个针对失败案例的 few-shot 示例后重测。

继续阅读