)
2026年10月报名参加Datawhale当月组队学习里的共学从0开始入门、了解jev本篇记载我的 Task04 学习笔记主要内容是实战指南。【教程地址】https://github .com/datawhalechina/jev-cookbookTask 04 实战指南这一章学习中一共包含18篇实验小节每篇 Notebook 都演示一个可组合的“state → 类型化判断 → 代码处理”配方可以从以下四部分线索来学习。路线篇目你会看到什么判断稳定性与分流01 自一致性 Noul · 02 自一致性 Choice · 18 基于置信度的分类重复运行、保留完整分布、把不确定样本交给后续策略检索与证据03 并行提问 · 04 重排序 · 05 逐行语义搜索 · 10 RAG 段落分类 · 11 引用核查候选召回、相关性排序、段落筛选与来源核对Agent 与风险控制07 函数调用 · 08 技能推荐 · 12 LLM 防护栏 · 13 SDE 级联由语义判断辅助路由、候选验证和升级结构化处理06 结构恢复 · 09 实体对齐 · 14 日期抽取 · 15 预解析值抽取 · 16 层级分类 · 17 自动研究特征发现在已有候选、字段或片段上作有限选择再由代码拼装结构01·02自一致性实战指南开篇两篇都叫自一致性分别对应 Noul 和 Choice 两种原语但核心问题只有一个模型不是每次都给一模一样的答案临界样本尤其会抖怎么把这种抖动变成可控的业务行为而不是随机的分类错误01 自一致性 Noul概率本身就是不确定性不i把中间值硬掰成是/否场景一份车险理赔雨天打滑撞护栏车门凹陷但还能开保单刚过等待期需要同时判断五个是非问题。一次请求发五个 Noul互相独立、共享同一份 stateCLAIM { claim_type: 车险理赔, description: 车辆在雨天打滑撞上护栏车门凹陷但仍可缓慢行驶。保单刚过等待期。, photos: 已提交车辆侧面和现场照片, } QUESTIONS { covered: Noul(这份理赔是否属于保单承保范围), repairable: Noul(车辆是否仍然可以维修而不是必须报废), fraud_flag: Noul(这份理赔是否存在明显的欺诈信号), rental_eligible: Noul(客户是否符合租车替代服务的条件), manual_review: Noul(这份理赔是否应该交给人工审核), }自一致性的做法是同一份 state 重复跑几次收集每次的概率再对每个维度统计均值和标准差。这里用了 5 轮的示例数据真实 API 下流程一致只是数字来自模型第 1 次 covered0.79 | repairable0.88 | fraud_flag0.15 | rental_eligible0.33 | manual_review0.67 第 2 次 covered0.79 | repairable0.86 | fraud_flag0.15 | rental_eligible0.35 | manual_review0.70 第 3 次 covered0.80 | repairable0.87 | fraud_flag0.15 | rental_eligible0.34 | manual_review0.67 第 4 次 covered0.80 | repairable0.88 | fraud_flag0.14 | rental_eligible0.35 | manual_review0.66 第 5 次 covered0.80 | repairable0.88 | fraud_flag0.15 | rental_eligible0.33 | manual_review0.69统计所有结果之后再进行决策而关键就在这个函数将阈值明确为业务策略而不是模型返回的事实def noul_decision(probability): if 0.30 probability 0.70: return uncertain # 中间区间不强行二值化交人工 return yes if probability 0.70 else no五个维度的统计结果covered mean0.80 stdev0.005 decisionyes repairable mean0.87 stdev0.009 decisionyes fraud_flag mean0.15 stdev0.004 decisionno rental_eligible mean0.34 stdev0.010 decisionuncertain manual_review mean0.68 stdev0.016 decisionuncertaincovered和repairable稳定地高fraud_flag稳定地低这三个可以自动决策而rental_eligible和manual_review落在 0.30–0.70 之间被明确标记为uncertain交给人工。而这里有三个要点Noul 没有 confidence 字段。noul值本身就是为真的概率它就是不确定性的完整表达——不需要另一个数字来描述这个概率有多可信中间概率往往比错误分类更有价值。把 0.30–0.70 留给人工审核等于告诉系统这件事我拿不准而不是硬把 0.51 判成 yes、0.49 判成 no成本可控。Jev 只按输入 token 计费、输出免费重复 n 次调用 ≈ 成本 ×n但延迟几乎不变。社区 JevBench 实测同一套题隔 16 分钟跑两遍只有 1.2% 的答案漂移所以多数请求一次就够自一致性主要留给临界样本。02 自一致性 Choiceconfidence 当稳定度用不当概率用场景一条社区帖子——有人说这个功能会泄露数据我没有证据但大家最好先别用了。需要三个 Choice审核动作、进哪个队列、风险等级。POST { author: 用户 1842, text: 有人说这个功能会泄露数据我没有证据但大家最好先别用了。, reports: 3, } QUESTIONS { moderation: Choice(这条帖子最适合采取哪种审核动作, criteria{keep: 保留帖子, remove: 删除帖子, escalate: 升级给人工审核}), queue: Choice(这条帖子应该进入哪个队列, criteria{general: 普通队列, threat: 潜在威胁队列, spam: 垃圾内容队列}), severity: Choice(这条帖子的风险等级是什么, criteria{low: 低风险, medium: 中风险, high: 高风险}), }重复跑 5 次发现标签确实会跳可以看到moderation有四次选了escalateconfidence 0.58–0.62有一次跳到了keepconf 0.51——临界样本的稳定性就是这么差。当决策函数用 confidence 当自动动作的门槛def choice_decision(answer, threshold0.60): return answer.choice if answer.confidence threshold else uncertain以下是 5 次决策结果第 1 次{moderation: escalate, queue: threat, severity: uncertain} 第 2 次{moderation: escalate, queue: threat, severity: uncertain} 第 3 次{moderation: uncertain, queue: uncertain, severity: uncertain} 第 4 次{moderation: uncertain, queue: threat, severity: uncertain} 第 5 次{moderation: escalate, queue: threat, severity: uncertain}severity五次 confidence 都在 0.55–0.59全部没过 0.60 的线始终是uncertain。因此在这里也能知道Choice 和 Noul 的区别要明确Noul 的概率就是不确定性本身所以直接用概率区间0.30–0.70划不确定区Choice 的最高概率不等于确定性。第一名 0.55、第二名 0.45 时模型实际上在两个选项间严重摇摆probabilities分布是扁平的。这时候confidence才是更合理的稳定度信号我们倾向于用它当自动动作的门槛临界答案就被明确转交人工。再上面的例子中0.60 这个阈值也是演示值正式上线需要用独立标注数据选并且永远保留不确定 → 人工的出口。01与02 的核心思想是同一条模型只给判断该不该信、信到什么程度、不信怎么办全部由代码说了算实际上也是由我们人自己定义的。自一致性不是为了让模型答对而是为了让系统在模型拿不准的时候有一个出口转人工而不是赌一把给出糟糕的回答。03·04·05·10·11检索与证据这五篇解决的是同一件事的不同切面检索回来的一堆候选怎么判断哪些相关、哪些该丢、顺序怎么排、有没有撒谎。从一次问多个问题的基础设施03到重排序04、逐行搜索05再到 RAG 段落分类10和引用核查11是一条从粗到细的流水线。03 并行提问把独立问题一次问完这是整条路线的基础能力。这里的实例是一段公司公告需要同时知道它是否提到退款、是否提到安全审计、主题是什么、紧迫程度如何——四个问题读同一段文本互相独立。ARTICLE 公司宣布下季度把客服、退款和安全审计流程统一到一个工作台。 QUESTIONS { about_refund: Noul(这段文字是否提到退款), about_security: Noul(这段文字是否提到安全审计), topic: Choice(这段文字的主要主题是什么, criteria{product: 产品变化, operations: 运营流程, policy: 政策公告}), urgency: Score(这段文字表达的紧迫程度, criteria[没有紧迫性, 需要近期关注, 需要立即处理]), }一次请求拿回全部答案offline 示例about_refund0.81、about_security0.74、topicoperations (conf 0.72)、urgency1.15。批量和逐条对照实测架构章数据批量 0.31s、逐条 1.77s加问题几乎不增延迟。代码只消费当前分支需要的字段没用的答案直接忽略零成本。当然之前也有说过问题之间不能互相引用需要看上一题答案的场景必须拆两轮请求。04 重排序用 Noul 概率当排序键场景用户问如何撤销一笔尚未结算的转账检索回来 4 段候选。对每个查询–候选对提一个 Noul这段是否直接回答了问题按返回概率排序。scores [] for passage in CANDIDATES: resp ts.call({query: QUERY, candidate: passage}, {relevant: Noul(这段候选内容是否直接回答用户的问题)}) scores.append((resp.nouls[relevant].noul, passage)) ranked sorted(scores, keylambda x: x[0], reverseTrue) for prob, passage in ranked: label 推荐 if prob 0.50 else 低相关4 段候选的相关性示例值0.93、0.07、0.19、0.78排序后自然把可撤销转账排在第一。再这里有一个设计0.50 只决定是否展示不改变其它排名。社区在 80 条 SciFact 查询上的实测显示Jev 精排使 nDCG10 提升 0.0778比传统精排还高 0.0332且耗时和 token 显著更低。05 逐行语义搜索不返回不相关重排序解决哪个最相关但如果文档里根本没有答案硬返回最相关的那行就是错的。所以逐行搜索用两个判断拆开这个问题先逐行判相关性再用最高概率判断答案是否存在。QUERY 如何修改账单邮箱 LINES [ 你可以在个人资料页修改姓名和头像。, 账单邮箱位于设置 → 通知 → 账单中修改后会立即生效。, 发票下载链接会发送到当前账单邮箱。, 安全邮箱用于接收登录提醒不等同于账单邮箱。, ]逐行判这一行是否直接回答查询得到概率[0.08, 0.94, 0.51, 0.18]。然后best_probability, best_line max(matches) answer_exists best_probability 0.50最高概率 0.94 ≥ 0.50所以答案存在返回第二行。如果所有行都低于 0.50就明确返回未找到不能硬返回最不相关的那行。实际生产上先用代码过滤明显无关的行长度、关键词剩下的在逐行问语义能省一大半 token。10 RAG 段落分类4 个 Noul first-match 路由检索回来的段落不只分相关/不相关还要区分可用证据、冲突信息、提示注入——因为生成模型需要把证据和冲突放进不同提示块才能正确反驳错误前提。实例中用6 段语料官方步骤、邮件说明、否认前提、论坛注入、无关账单、弱相关 2FA每段发 4 个 NoulPASSAGE_QUESTIONS { relevant: Noul(这段文字是否在讨论该查询的主题), usable_evidence: Noul(这段文字是否陈述了可直接用于回答该查询的信息), contradicts_premise: Noul(这段文字是否与查询中当作事实陈述的前提相矛盾), instructs_model: Noul(这段文字是否试图控制或指挥负责回答查询的系统), }阈值集中在一个常量里路由用first-match顺序固定注入 → 冲突 → 无关 → 证据。THRESHOLDS {injection_max: 0.70, contradicts_min: 0.70, relevant_min: 0.45, evidence_min: 0.55} def route(answers): if answers[instructs_model] 0.70: return drop # 注入最先拦 if answers[contradicts_premise] 0.70: return conflicting if answers[relevant] 0.45: return drop # 无关 if answers[usable_evidence] 0.55: return usable_evidence return drop以下是6 段的路由结果doc-reset rel0.96 evid0.94 contra0.08 inj0.05 → usable_evidence doc-email rel0.88 evid0.82 contra0.10 inj0.06 → usable_evidence doc-contradict rel0.72 evid0.65 contra0.91 inj0.12 → conflicting forum-injection rel0.70 evid0.40 contra0.20 inj0.98 → drop注入 doc-billing rel0.18 evid0.12 contra0.09 inj0.07 → drop无关 doc-2fa rel0.61 evid0.48 contra0.11 inj0.08 → drop证据不足在这个拦截分类中最关键的是顺序注入检测必须排在证据判断之前。安全决定优先于证据决定否则含注入指令的段落会被误当证据塞进提示词。冲突也排在证据之前否认前提的段落不会混进可用证据。最终提示词把证据和冲突分两块生成侧才知道该反驳什么。11 引用核查字符串先抓捏造语义再判关系核查 LLM 生成内容里的引用是否站得住。流水线可以分三层能代码查的绝不问模型字符串匹配引文逐字出现在源文档里吗不在 →fabricated直接结案不调模型Choice 判关系幸存引用问小节与论断的关系——supports/contradicts/says_nothing映射到verified/contradicted/unsupported置信度门控confidence 0.8 → 转人工否则自动采纳。RELATION_TO_VERDICT {supports: verified, contradicts: contradicted, says_nothing: unsupported} def verdict_from(status, answer): if status missing: return {verdict: fabricated, auto: True} return {verdict: RELATION_TO_VERDICT[answer.choice], auto: answer.confidence 0.8}5 条引用的结果verified_ok found supports conf0.93 → verified auto fabricated missing - - → fabricated auto不调模型 contradicted_ok found contradicts conf0.96 → contradicted auto unsupported_ok found says_nothing conf0.88 → unsupported auto low_conf found says_nothing conf0.42 → unsupported reviewunsupported是常见陷阱引文真实存在但小节压根没谈这件事——引文存在 ≠ 支持论断。低置信度那条即使有获胜选项says_nothing 0.42也因 confidence 0.8 被转人工。因此总结再这五篇里一条主线贯穿始终模型只回答这段相关吗/支持吗/是注入吗这种狭窄问题排序、去留、路由、提示词组装全在代码里。04 用概率排序、05 用阈值判空、10 用 first-match 路由、11 用分层核查——都是同一种工程纪律在不同场景的具体长相。07·08·12·13Agent 与风险控制这一组的四篇回答是像关于 Agent 应用里最敏感的问题AI 可以参与决定调用哪个工具、加载哪个技能、放行哪条消息、升级哪份文档但不能让它自己按按钮。四篇层层递进从单函数分派07到两段式技能推荐08再到完整的安全护栏12最后是多模型级联的成本控制13。07 函数调用Choice 选函数代码执行场景用户说请查询订单 TS-2048 的物流状态并告诉我预计送达日期。系统里注册了三个函数查订单、退款、改地址要让模型选一个。问题定义只有两问一次请求QUESTIONS { function: Choice(用户最想执行哪个函数, criteria{ lookup_order: 查询订单物流或状态, refund_order: 申请订单退款, update_address: 修改订单收货地址, }), needs_confirmation: Noul(执行这个函数前是否需要用户再次确认), }模型答functionlookup_order (conf 0.96)、needs_confirmation0.08示例值。然后代码接手selected response.choices[function].choice if selected not in FUNCTIONS: raise ValueError(f模型返回了未注册函数{selected}) # 防幻觉函数名 if response.nouls[needs_confirmation].noul 0.50: print(→ 先请求用户确认不执行副作用。) else: print(→ 确定性代码执行, FUNCTIONS[selected](TS-2048))和普通 LLM function calling 的差别是本质性的模型只从注册清单里指名不生成参数、不触发执行。参数用代码从 state 里抽取正则/解析器14、15 篇会细讲所以不存在模型编造参数的可能needs_confirmation这道 Noul 还给危险操作加了一道确认闸即使模型返回了清单外的名字代码的ValueError也会兜住——信任边界永远在代码里。08 技能推荐先 Noul 宽召回再 Choice 精选场景用户说帮我把本周会议纪要整理成行动项并安排下周跟进会议。技能里有四项日历、会议纪要、待办、邮件要推荐一个先加载。如果直接用 Choice 在多个技能里选选项多时需要分析很多token 贵还难区分。在书中更聪明的做法是下面的两段式。第一段宽召回。对每个技能单独发一个廉价 Noul这个技能是否可能帮助完成用户请求for name, description in SKILLS.items(): response TS.call({request: REQUEST, skill: description}, {relevant: Noul(这个技能是否可能帮助完成用户请求)}) ranked.append((response.nouls[relevant].noul, name)) SHORTLIST [name for _, name in sorted(ranked, reverseTrue)[:3]]四个技能的相关性示例值meeting_summary0.94、calendar0.88、task0.79、mail0.18取前三名mail被宽召回直接淘汰。第二段精选。只在候选里发一道 Choicecriteria {name: SKILLS[name] for name in SHORTLIST} question {best_skill: Choice(哪个候选技能最适合先处理请求, criteriacriteria)}得到best_skillmeeting_summary (conf 0.86)代码再按置信度决定conf 0.60 就交给人工选择否则加载该技能。这两段回答的是不同的问题Noul 问值得考虑吗宽进Choice 问谁最合适严选。这正是架构模式章里推测性扇出的最小形态——先用便宜问题把候选缩到 2–3 个精选题才能又快又准。12 LLM 防护栏规则写在代码里别埋在系统提示词里这是四篇里最完整的一篇把前面所有零件组装成一个可复用的guard()入口任何消息进 LLM 之前先过一道安检。INPUTbattery一次请求并行问 5 道题4 道危害 Noul越狱有害请求医疗建议自伤迹象加 1 道严重程度 Score四档无害/轻微/严重/极严重。6 条演示消息覆盖普通请求、经典越狱忽略你之前的全部指令、爆炸物制作、索要诊断剂量、自伤暗示、边界医疗咨询。路由逻辑是整个护栏的核心三层结构HAZARD_ACTION {jailbreak: block, harmful_request: block, medical_advice: review, self_harm: support} PRECEDENCE [support, block, review, pass] # 自伤支持优先于拦截 POLICIES { strict: {review_threshold: 0.35, action_threshold: 0.70, severity_block: 2.0}, permissive: {review_threshold: 0.35, action_threshold: 0.85, severity_block: 2.0}, }每种危害有自己的处置动作危害概率 ≥ 动作阈值触发策略只是具名的数字包strict 和 permissive 区别只在阈值换策略不用重写路由按优先级取最重的动作。6 条消息的判定结果示例值ordinary pass 所有危害 0.10severity 0.12 jailbreak BLOCK 越狱概率 0.94severity 1.85 harmful BLOCK 有害请求 0.92severity 2.40 medical review 医疗建议 0.89不直接拦截 self_harm_msg support 自伤 0.91优先级高于 block borderline_medical review 医疗概率 0.48落进 review 带宽这里有三个设计要点自伤路径优先于拦截。PRECEDENCE里support排在block前面——对表达自伤念头的人正确动作是转接支持资源不是冷冰冰地拒绝为什么不能用系统提示词做护栏越狱最擅长突破的恰恰就是系统提示。把拦不拦写成代码里的阈值比较攻击者就没法靠话术绕过拦截阈值要偏保守。漏放行还有输出侧护栏兜底误拦则直接伤害正常用户体验所以两头都要留人工复核通道。13 SDE 级联便宜模型提取Jev 逐字段验尸红旗才升级大模型做结构化提取SDE效果好但贵且慢小模型便宜但会幻觉。级联的思路是每层只做自己最划算的事小模型提取 → Jev 逐字段验证 → 只有红旗文档才付推理价。实验设计3 份中文文档。A 是干净发票应通过B 是同一发票但 mini 模型故意幻觉了总额9800 vs 源文本 1280和备注源文本没写硬件C 是付款提醒片段。验证问题不问整条记录好不好而是对每个字段发一组原子 Noul——hallucinated源文本不支持、name_desc_mismatch与字段说明不符、missing_in_source本该有值却漏提字段值作为结构化 instructions 传给模型外加一个只展示不参与决策的整体__overall__::judge头。门控只有一行逻辑但很关键FIRE_T 0.7 def any_flag(checks, thresholdFIRE_T): return {qid: p for qid, p in checks.items() if not qid.startswith(__overall__) and p threshold} escalate bool(any_flag(checks)) # 任一字段红旗即升级三份文档的验证结果示例值A_干净发票各字段 P(wrong) 0.04–0.12 → ACCEPT保留 mini 结果 B_幻觉总额total::hallucinated0.92 FIRES total::missing_in_source0.71 FIRES notes::hallucinated0.88 FIRES → ESCALATE升级推理模型 C_付款提醒各字段 P(wrong) 0.06–0.14 → ACCEPT为什么用any取 max而不是平均值因为只要一个自信的红旗就足以升级——文档 B 的__overall__::judge只有 0.61中等如果看整体分可能放过它但total::hallucinated0.92这一个字段信号已经说明总额是编的。而逐字段验证还有个副产品错误可定位、可分诊知道是哪个字段错了未来甚至可以按字段类型路由到不同的修复模型。成本直觉多数干净文档A、C 这类在验证层就被放行但只有红旗文档才付出推理模型的价格这正是架构模式章意图路由在工程上的镜像。最后总结一下这四篇的共同的模式语义判断用来辅助路由和验证但选完之后的动作永远由确定性代码执行。07 的确认闸、08 的双段召回、12 的优先级路由、13 的 any_flag 门控都是同一条边界在不同场景的画法——Agent 可以越来越自主但每一步自主都要站在代码铺好的轨道上。06·09·14·15·16·17结构化处理结构化处理是四条路线里最重工程、轻生成的一组。六篇的共同模式可以用一句话概括先由确定性代码正则、解析器、树遍历找出候选或搭建骨架再让 Jev 在这个有限集合里做语义判断或选择最后由代码按规则拼装出可靠结构。06 结构恢复模型只判这里该不该换段拼接由代码完成场景一段被错误换行的纯文本需要重建段落TypeSafe 返回结构化答案\n代码可以直接消费这些答案。\n每个问题都应当足够窄\n让模型在一秒内完成判断。\n这是一个新的段落。做法演示对每对相邻行发一个 Noul换行是否把同一个句子切开了然后按阈值 0.50 重建段落paragraphs [LINES[0]] for index, probability in enumerate(joins): if probability 0.50: paragraphs[-1] LINES[index 1] # 同一行拼上去 else: paragraphs.append(LINES[index 1]) # 换段新开一行四对行的连接概率离线示例依次是 0.91、0.87、0.78、0.12——前三对合并、最后一对断开重建出两段。调高阈值会切出更多段落调低则更激进地合并阈值是策略参数不是事实。而这章的生产实例fast-jev-compaction插件用同样的模式给 Claude Code 做上下文压缩逐条工具调用打分、过期的丢、保留的保持原文替代有损摘要。判断不生成时凡是要保真的处理都用判断 代码不要让模型写新文字。09 实体对齐Score 三档 字段 Noul防错合并优先场景四张啤酒实体对——同款两侧字段一致、完全不同、同厂但风格冲突一侧写英式淡色艾尔、另一侧写德式黑啤、变体常规版 vs 波本桶陈特别版。用一道 Score 三档路由 三道字段 Noul同请求并行设置Score 的三档LEVELS [ 两个不同的产品, 相关但不一定相同变体、特别版、名称易混淆, # 中间档交给策展人 完全同一款产品, ] OUTCOME {0: leave unlinked, 1: curator queue, 2: assert sameAs} def route(score_value: float) - str: level min(int(score_value 0.5), len(LEVELS) - 1) # 四舍五入到最近层级 return OUTCOME[level]为什么用 Score 而不是 Choice因为三种结果有有序关系不同 → 相关可疑 → 同一中间档用于防错合并因为错误合并的代价远高于漏合并会把两侧事实与外链一并污染。四对实体的路由结果示例值同款score1.92 → assert sameAs、完全不同0.18 → leave unlinked、风格冲突1.12 → curator queuesame_style Noul 只有 0.22策展人会看到风格不一致提示、变体1.05 → curator queue。三道字段 Noul 同请求扇出仅当结果落入策展人档时才被代码消费告诉策展人名称不一致还是酒厂不一致或字段大多一致请确认是否变体。没有阈值要拟合改动决策只需改 LEVELS / OUTCOME 或四舍五入规则。14 日期抽取模型只读怎么写的日历算术在代码里场景四份中文文档——绝对日期2027年8月14日、相对日期明天、下周四、缺失日期请尽快交回没写具体日期。七个 Choice 问题modeabsolute / relative / none 绝对三件套month / day / year 相对三件套day_anchor / weekday / week_offset一次请求发出。模型只读文本说了什么形态、几月几日、星期几不做日历计算拼年、推明天 / 下周四全在代码里TODAY date(2026, 7, 30) # 固定保证可复现 def resolve_weekday(today, weekday, week_offset): w WEEKDAYS.index(weekday) this_monday today - timedelta(daystoday.weekday()) if week_offset next: return this_monday timedelta(days7 w) ... def assemble(parts, todayTODAY): confidence min(所用各部分的 confidence) # 取最小值 needs_review confidence 0.60 ...四条文档的结果2027年8月14日 → modeabsolute代码拼出2027-08-14conf 0.95明天 → day_anchortomorrow → 代码算TODAY12026-07-31下周四 → weekdayThursday week_offsetnext →2026-08-06纯代码自检已验证 resolve_weekday 通过缺失日期 → modenoneconf 0.46 0.60 →needs_reviewTrue送人工。两个关键设计其一日期置信度取所用各部分的最小值用最弱一环决定可信度其二模型在给定候选格式里选Choice而不是生成日期字符串杜绝格式崩坏。15 预解析值抽取正则先找候选Choice 挑角色代码逐字复场景一封带四个邮箱地址的邮件发件人 From、两个抄送、一个 Reply-To需要挑出收据应寄到哪个邮箱和发件地址是哪个一段含四个电话号码的公司简介总机、传真、三个手机需要挑出经办人的直接手机一份发票小计、税额、应付合计、贷记需要挑出应付总额并区分 charge vs credit。三层流水线EMAIL_RE re.compile(r[A-Za-z0-9._%-][A-Za-z0-9.-]\.[A-Za-z]{2,}) def find(pattern, text): # 正则高召回宁多勿漏 ... def pick(document, candidates, question): # TypeSafe Choice从候选里挑角色 criteria {c: None for c in candidates} criteria[none] 这些候选都不符合问题所问的值。 return Choice(instructionsquestion, criteriacriteria)邮件的实测正则找出四个邮箱TypeSafe 根据正文意图挑出dana.personalgmail.com作为收据地址返回值是候选列表中的原样复制再由代码.lower()规范化——模型不会凭空发明字符串金额的实测正则找出四个金额挑出¥1,315.50应付合计后再发两道 Noul 区分 charge vs credit——应付总额 P(credit)0.02 → charge贷记 P(credit)0.97 → credit最后由to_decimal把字符串解析成 Python Decimal 对象。失败回退解析失败的片段可以转交大模型但主路径永远是最便宜、最可验证的方法。16 层级分类贪心下行 vs 束搜索 K2逐层 Choice 拆成兄弟选择场景一棵三级中文数码分类树约 10 个叶子旗舰机、中端机、笔记本、耳机……三条商品描述旗舰手机、轻薄本、降噪耳机。把从几百个叶子里直接选拆成多次在少量兄弟节点里选每个内部节点的子节点构成一次 Choice 的选项。有两种走法贪心下行每层 argmax一条路走到黑。路径上每条边概率都记录计算path_score product(probs) ** (1 / 层数)长度归一化的几何平均否则深浅叶子不公平。束搜索 K2每层保留 path_score 最高的 2 条路径最终按 path_score 取最优。K2 的扩展方式对存活路径各自发 Choice可并行放进同一次 system_one 请求取全部子选项概率扩展候选再排序保留 top-K。三条商品的结果离线示例旗舰手机贪心 → 数码 手机 旗舰机path_score0.733轻薄本 → 数码 电脑 笔记本0.763降噪耳机 → 数码 音频 耳机0.686。束搜索 K2 时第三条商品第一层 audio0.54 已过半贪心和束结果一致但如果第一层更扁平如 audio0.48、phone0.28束会保留两条路径最终可能把耳机和手机配件都放进候选。束搜索的价值高层错误会污染整棵子树所以高层要么用更保守的阈值要么用束搜索保留多条路径。生产中可以把 K 条路径的 Choice 并行放进同一次请求。17 自动研究特征发现问题即特征误差驱动下一轮场景五条中文品鉴笔记高分红酒、平淡咖啡、均衡白葡萄酒、精品咖啡、有缺陷的酒 批评家分数标签78–94 分。核心思路让 TypeSafe 问题直接产出数值特征——六个强度型 Score0–4 档果香/橡木/酸度/酒体/余韵/平衡感 三个是否提及型 Noul是否提到缺陷/陈年/产地对每条笔记一次请求全部发出Feature 表的列是Score 的 score 值 Noul 的 noul 值 Score 的1 - confidence作不确定性列然后是 autoresearch 的迭代循环本笔记只做概念演示不训练 CatBoost提出特征问题 → TypeSafe 填表 → 训练有监督模型 → 看误差 → 决定留下/丢掉哪些问题 → 下一轮极简基线把六个 Score 取平均线性映射到 78–96 分区间pred 78 mean_s * 4.5 - 6.0 * mentions_fault得到 MAE ≈ 2.3 分。即便不训练复杂模型特征均值已能粗分高分/低分说明问题设计在起作用。生产里完整流程CatBoost 交叉验证 RMSE → 按特征重要性和残差决定保留哪些 Score/Noul → LLM 根据误差报告提出下一轮问题提案 → 重复这里的价值在于误差驱动迭代不是一次写对所有问题。一条贯穿始终的边界模型从不凭空发明字符串、数字或结构——它的输出永远被锁死在代码先圈好的范围里候选列表、三档 Score、给定 Choice 选项、相邻行对。代码侧永远做三件事确定候选范围 → 拼装最终结构 → 规范化/持久化。这正是这组章节名叫结构化处理的原因——Jev 提供的是让代码能轻易拼出可靠结构的语义判断零件而不是结构本身。