第 17 章

第 17 章:技能推荐

第 17 章:技能推荐

本文整理自 Datawhale 开源项目 datawhalechina/jev-cookbook(CC BY-NC-SA 4.0),源文件:main/04_实战指南/08_技能推荐.ipynb。

本 notebook 把中文镜像站中的 Cookbook 改写成可以逐格运行、修改输入并观察结果的最小实验。 先用 Noul 宽召回,再用 Choice 选择主技能。

运行方式与 ../03_架构模式/01_架构模式.ipynb 一致:有有效的 TYPESAFE_API_KEY 时调用真实的 TypeSafe API;没有 Key 或返回 401 时使用内置的离线示例答案。后续代码不区分两种模式,便于先学习 控制流,再切换到真实模型观察概率和置信度。

学习提示:先顺序运行全部单元格,再回到“定义 state”或“定义问题”的单元格修改内容,重新运行后面的单元格。 API Key 只从环境变量读取,不能写进 notebook。

0. 准备

0.1 安装依赖

%pip install -q -U typesafe-sdk

0.2 创建客户端

import os
import statistics
import time
from pprint import pprint

from typesafe_sdk import (
    Choice,
    Score,
    Noul,
    TypeSafeClient,
    TypeSafeAuthenticationError,
)

API_KEY = os.environ.get("TYPESAFE_API_KEY", "")
client = TypeSafeClient(api_key=API_KEY, model="jev-latest") if API_KEY else None
print("客户端已创建:模型=jev-latest,Key=", "已配置" if API_KEY else "未配置(将使用离线示例)")

0.3 离线响应与统一调用入口

class _FakeAnswer:
    def __init__(self, type_, **values):
        self.type = type_
        for key, value in values.items():
            setattr(self, key, value)


class _FakeResponse:
    def __init__(self, answers):
        self.answers = answers
        self.nouls = {k: v for k, v in answers.items() if v.type == "noul"}
        self.choices = {k: v for k, v in answers.items() if v.type == "choice"}
        self.scores = {k: v for k, v in answers.items() if v.type == "score"}
        self.model = "jev-latest(离线示例)"
        self.usage = _FakeAnswer("usage", input_tokens=0, output_tokens=0)


class TS:
    offline = False
    _warned = False

    @classmethod
    def call(cls, state, questions, offline_answers):
        if client is None:
            cls.offline = True
            if not cls._warned:
                cls._warned = True
                print("⚠️ 未设置有效 TYPESAFE_API_KEY,以下输出使用内置离线示例。")
            return _FakeResponse(offline_answers)
        try:
            return client.system_one(state, questions)
        except TypeSafeAuthenticationError:
            cls.offline = True
            if not cls._warned:
                cls._warned = True
                print("⚠️ 未设置有效 TYPESAFE_API_KEY,以下输出使用内置离线示例。")
            return _FakeResponse(offline_answers)


def answer_line(name, answer):
    if answer.type == "noul":
        return f"{name}: noul={answer.noul:.2f}"
    if answer.type == "choice":
        return f"{name}: choice={answer.choice} confidence={answer.confidence:.2f}"
    return f"{name}: score={answer.score:.2f} confidence={answer.confidence:.2f}"


print("模式:", "离线示例" if TS.offline else "真实 API(首次调用后确定)")

0.4 连通性测试

if client is None:
    TS.offline = True
    print("⚠️ API Key 未设置,后续单元格使用离线示例。")
else:
    try:
        ping = client.system_one("你好", {"is_greeting": Noul(instructions="这段文字是在打招呼吗?")})
        print("✅ API 连通正常,后续单元格会使用真实结果。")
    except TypeSafeAuthenticationError:
        TS.offline = True
        print("⚠️ API Key 无效,后续单元格使用离线示例。")

1. 技能推荐

先对技能名册中的每一项做廉价的相关性判断,再从候选中选出最合适的技能。推荐结果仍由代码根据 置信度决定是否展示。

1.1 定义用户请求和技能名册

REQUEST = "帮我把本周会议纪要整理成行动项,并安排下周跟进会议。"
SKILLS = {
    "calendar": "创建和修改日历日程,查询忙闲和会议室",
    "meeting_summary": "整理会议纪要,提取决定、行动项和负责人",
    "task": "创建待办任务,分配成员并跟踪状态",
    "mail": "搜索、起草、回复和发送邮件",
}
print("技能名册已定义:技能数=", len(SKILLS))

1.2 先做宽召回:每个技能一个 Noul

OFFLINE = {"calendar": .88, "meeting_summary": .94, "task": .79, "mail": .18}
ranked = []
for name, description in SKILLS.items():
    response = TS.call({"request": REQUEST, "skill": description},
                       {"relevant": Noul(instructions="这个技能是否可能帮助完成用户请求?")},
                       {"relevant": _FakeAnswer("noul", noul=OFFLINE[name])})
    probability = response.nouls["relevant"].noul
    ranked.append((probability, name))
    print(f"{probability:.2f}  {name}: {description}")
ranked.sort(reverse=True)
SHORTLIST = [name for _, name in ranked[:3]]
print("候选前三名:", SHORTLIST)

1.3 在候选中选择主技能

criteria = {name: SKILLS[name] for name in SHORTLIST}
question = {"best_skill": Choice(instructions="哪个候选技能最适合先处理请求?", criteria=criteria)}
offline_choice = SHORTLIST[1] if len(SHORTLIST) > 1 else SHORTLIST[0]
response = TS.call({"request": REQUEST, "shortlist": criteria}, question,
                   {"best_skill": _FakeAnswer("choice", choice=offline_choice, confidence=.86,
                                              probabilities={offline_choice: .86})})
answer = response.choices["best_skill"]
print(f"推荐技能:{answer.choice}(confidence={answer.confidence:.2f})")
if answer.confidence < 0.60:
    print("→ 置信度不足,交给人工选择。")
else:
    print("→ 先加载技能:", SKILLS[answer.choice])

观察:宽召回使用 Noul 判断“是否值得考虑”,候选重排使用 Choice 做相对选择;两者回答的是不同问题。

知识补充

  • 两段式 = 扇出的缩影:Noul 宽召回(问一圈"相关吗")+ Choice 精选,正是官方 Patterns 里 speculative fan-out 的最小形态。
  • 为什么先 Noul 后 Choice:Choice 的选项数决定 token 与区分难度;先用便宜的 noul 把候选缩到 2-3 个,Choice 又快又准。
  • 进阶阅读:完整扇出与置信度门控见 ../03_架构模式/01_架构模式.ipynb。

小结

这本 notebook 的边界很清楚:TypeSafe 只负责受限、可编程的判断;排序、阈值、分组、重建文本和 函数分派都由 Python 代码完成。修改输入或问题后重新运行,就能观察“模型答案 → 确定性代码”的变化。

📑 Jev Cookbook:System One 判断模型实战教程

1 第 1 章:认识 Jev:模型、上手与场景 2 第 2 章:核心概念总览 3 第 3 章:System One:判断的核心心智模型 4 第 4 章:状态:让判断连续可追溯 5 第 5 章:原语:Choice、Score 与 Noul 6 第 6 章:置信度:让概率可信 7 第 7 章:应用构建:从原语到完整系统 8 第 8 章:架构模式 9 第 9 章:实战指南总览 10 第 10 章:自一致性 · Noul 11 第 11 章:自一致性 · Choice 12 第 12 章:并行提问 13 第 13 章:重排序 14 第 14 章:逐行语义搜索 15 第 15 章:结构恢复 16 第 16 章:函数调用 17 第 17 章:技能推荐 18 第 18 章:实体对齐 19 第 19 章:RAG 段落分类 20 第 20 章:引用核查 21 第 21 章:LLM 防护栏 22 第 22 章:SDE 级联 23 第 23 章:日期抽取 24 第 24 章:预解析值抽取 25 第 25 章:层级分类 26 第 26 章:自动研究特征发现 27 第 27 章:基于置信度的分类 28 第 28 章:智能家居实验 29 第 29 章:模型评测总览 30 第 30 章:模型评测实验 31 第 31 章:Laya vs Jev 对比基准 32 第 32 章:JevBench:LLM 评测体系 33 第 33 章:智能家居应用实战 34 第 34 章:Jev-Mem 研究总览 35 第 35 章:Jev-Mem 缩放实验 36 第 36 章:Jev-Mem 完整走查 37 第 37 章:Agent 集成总览 38 第 38 章:Pi 集成实验 39 第 39 章:DSH 决策协作 40 第 40 章:本地模型总览 41 第 41 章:本地模型介绍与对比 42 第 42 章:中文数据集构建方案 43 第 43 章:微调指南 44 第 44 章:RLCD 原理与实验优化 45 第 45 章:中文数据集构建实验 46 第 46 章:中文 Head 微调实验 47 第 47 章:全量 v2 微调实验 48 第 48 章:知识库
← 返回本书大纲