用 Jev 做函数调用:官方 cookbook 的选函数、校验参数与边界
先说结论
- 官方 function calling cookbook 解决的不是“让模型替你执行工具”,而是把自然语言请求映射为函数名、闭集参数值和置信度。官方原文写得很清楚:它把交易请求转成对普通类型化函数的调用,方式是“mapping function names and closed-set arguments to confidence-aware TypeSafe questions”。
- 它和让 LLM 直接输出
tool_call或 JSON 是两回事。Jev 的 Primitives 返回的是类型化答案,例如Choice返回choice、probabilities、confidence,Noul返回 0 到 1 的noul,Score返回score、legend、probabilities、confidence。因此你不用解析自由文本 JSON,也不用担心格式漂移。 - 一个可照抄的工程骨架是:把候选函数及其参数 schema 放进
state,用Choice选函数,用Noul判断“参数是否齐全”,用Score给低置信度调用打分排序。这是本文按官方 SDK 字段组合的做法,不是官方 function calling cookbook 的逐字代码。 - 官方 cookbook 展示的是决策与参数填充,不包含工具执行。执行仍在你自己代码里的普通 Python 函数中;低置信度或参数不齐时,工程上应走澄清或人工确认,而不是硬调。
- 可以和官方 Guardrails for LLMs cookbook 配合:先对输入输出做风险筛查,再决定是否执行函数。这是本文的集成建议,不是官方页面写明的组合方式。
官方 cookbook 到底在解决什么
官方 function calling cookbook 解决的是一个很具体的场景:从一组候选函数里选一个,并判断参数是否足够。页面开头的说明是:
Turns natural-language trading requests into calls to ordinary typed functions by mapping function names and closed-set arguments to confidence-aware TypeSafe questions.
这句来自官方 Function calling cookbook。它强调的是“ordinary typed functions”,也就是普通 Python 函数,而不是模型内部的工具插件。官方示例用交易助手展示:输入一句话,输出函数名、已求值的枚举参数和一个置信度。例如官方页面给出这些示例数据:
"plot rolling correlation between nvda and spy for the past month"对应rolling_correlation(symbol='NVDA', benchmark='SPY', window='1mo') confidence 0.91"compare nvda amd and msft over the past three months"对应compare_returns(symbols=['NVDA', 'AMD', 'MSFT'], window='3mo') confidence 0.94"show me apple daily with volume"对应plot_price(symbol='AAPL', resolution='1d', include_volume=True) confidence 0.75"what tickers do you have"对应list_symbols() confidence 1.00
这些置信度数字属于官方数据,不是我推算的。可以看到,模型返回的不是一段 JSON 字符串,而是一个已经选好的函数名、一组已经归一到枚举范围内的参数值,以及一个置信度。
官方接下来解释了为什么这样做。函数参数如果来自固定列表,就是 closed set。官方原文是:
An argument whose values come from a fixed list is a closed set. When it takes one value out of that list, it gets a Choice question over exactly those values, so whatever reaches the function is a value the function accepts.
也就是说,如果参数类型是 Literal["1mo", "3mo"],它就会被做成一个 Choice 问题,选项就是这两个值,因此传进函数的值一定在枚举范围内。官方还指出,Literal 只给字符串本身,不解释语义:
The Literal gives you the strings “1mo” and “3mo” . It does not say that a user typing “this quarter” means the second one. The spec says that.
这说明你还需要为每个参数写一段自然语言说明,把“this quarter”映射到 "3mo"。这一步不是让模型自由发挥,而是把你的领域知识放进 spec。
与“让 LLM 直接输出 tool_call”的差异
这是本文的判断,而不是官方 cookbook 直接写的对比。我的判断依据来自官方 Primitives 页面和 Quick start 示例。
官方 Primitives 页面写明,三个 primitive 分别返回:
Choice:What it answers 是“Which of these options?”,Returns 是choice,probabilities,confidenceScore:What it answers 是“Which level?”,Returns 是score,legend,probabilities,confidenceNoul:What it answers 是“Is this true?”,Returns 是noul(0 to 1)
来源是官方 Primitives。
官方 Quick start 示例也展示,拿到的是类型化属性,而不是需要解析的 JSON 字符串:
print(response.answers["department"].choice) # "technical"
print(response.answers["frustration"].score) # 1.0
print(response.answers["is_urgent"].noul) # 1.0
这段来自官方 Quick start。
所以我的分析是:原生 tool_call 一般是由 LLM 生成结构化输出,比如一个 JSON 对象,你再自己解析字段、处理缺字段、防格式漂移。而 Jev 的 function calling 走的是类型化判断:模型只回答一个问题,答案直接落到 choice、score、noul 这些字段上。解析工作由 SDK 完成,你拿到的是 Python 对象,不是自由文本。这也能解释为什么官方 function calling cookbook 里强调的是“confidence-aware TypeSafe questions”,而不是“parse the model output”。
可照抄的请求骨架
下面是本文按官方 SDK 字段拼装的请求骨架,可以改着用。它参考了官方 Quick start 的 state / questions / client.system_one 写法,以及官方 Primitives 里的 Choice、Noul、Score 字段。注意这不是官方 function calling cookbook 的逐字示例,而是把“选函数、校验参数、置信度排序”拆成三个问题的工程做法。
from typesafe_sdk import TypeSafeClient, Choice, Noul, Score
client = TypeSafeClient() # 读取 TYPESAFE_API_KEY,默认 jev-latest
# 把候选函数及其参数 schema 放进 state。
# 注意:不要在 state 中放未脱敏的敏感参数,先做最小化或脱敏。
state = {
"user_input": "plot rolling correlation between nvda and spy for the past month",
"candidates": [
{
"name": "rolling_correlation",
"description": "Plot rolling correlation between two symbols",
"params": {
"symbol": ["NVDA", "SPY", "AMD", "AAPL", "MSFT", "TSLA"],
"benchmark": ["SPY", "QQQ", "IWM"],
"window": ["1d", "1w", "1mo", "3mo"],
"resolution": ["1m", "5m", "15m", "1h", "1d"]
}
},
{
"name": "plot_price",
"description": "Plot price with style and indicators",
"params": {
"symbol": ["NVDA", "SPY", "AMD", "AAPL", "MSFT", "TSLA"],
"style": ["line", "candles"],
"resolution": ["1m", "5m", "15m", "1h", "1d"],
"window": ["1d", "1w", "1mo", "3mo"]
}
}
]
}
questions = {
"selected_function": Choice(
instructions="Which candidate function should handle this user request?",
criteria={
"rolling_correlation": "Plot rolling correlation between two symbols",
"plot_price": "Plot price with style and indicators",
"none": "None of the candidates fit"
}
),
"params_complete": Noul(
instructions="Are all required parameters for the selected function either present in the user request or safely inferable from context?"
),
"call_confidence": Score(
instructions="How confident are we that the selected function and filled parameters are correct?",
criteria=["Very low", "Low", "Medium", "High", "Very high"]
)
}
response = client.system_one(state=state, questions=questions)
selected = response.answers["selected_function"].choice
confidence = response.answers["selected_function"].confidence
params_ok = response.answers["params_complete"].noul
call_score = response.answers["call_confidence"].score
print(selected, confidence, params_ok, call_score)
这个骨架只负责决策。选完函数之后,你仍然需要在自己的 dispatcher 里根据 selected 调用对应的普通 Python 函数。换句话说,selected 是一个字符串或枚举值,不是已经执行完的函数调用。
坑与边界:它不执行工具,低置信度要拦截
官方 function calling cookbook 里有一句容易忽略但很重要的话:
Those calls go to ten ordinary functions in a trading assistant.
这句来自官方 Function calling cookbook。它说明这些调用最终去的是“ordinary functions”,也就是普通函数。官方页面展示的是把自然语言请求转换为函数名和参数值,并没有展示由 TypeSafe 或 Jev 直接执行这些函数。因此,我的判断是:这个 cookbook 的边界是“决策与参数填充”,不是“执行”。执行工具仍然是你自己的代码责任。
第二个坑是低置信度。官方示例中有些调用置信度很高,比如 list_symbols() confidence 1.00,有些较低,比如 plot_price(...) confidence 0.75。这些是官方数据。但官方没有给出“低于多少必须人工处理”的阈值。是否拦截、何时转澄清,这是工程选择。本文的建议是:当 params_complete 的 noul 明显偏低,或者 call_confidence 的 score 低于你设定的阈值时,不要硬调函数,而是进入澄清分支或人工确认。这不是官方要求,而是工程上的常见做法。
第三个坑是敏感参数。官方 function calling cookbook 用交易数据做例子,没有讨论脱敏。但工程上,如果你把用户输入、候选函数 schema 甚至上下文一起放进 state,可能存在泄露风险。本文建议不要把未脱敏的敏感参数塞进 state,只放完成决策所需的最小信息,并在进入模型前做脱敏。这条是工程建议,不是官方建议。
和官方 Guardrails cookbook 配合
官方 Guardrails for LLMs cookbook 解决的是另一个问题:对进入和离开 LLM 应用的每条消息做风险筛查。官方原文是:
Screen every message going into and out of an LLM app with one TypeSafe request, thresholding hazard probabilities and severity to pass, review, block, or route.
出处是官方 Guardrails for LLMs cookbook。
我的集成判断是:函数调用也可以沿用这个思路。在执行函数前,先对用户输入和候选参数做一轮 Noul 风险问题筛查;如果风险概率高或 harm severity 高,就 pass / review / block / route。通过之后,再做函数选择和参数填充。官方两个 cookbook 没有明确写要这样组合,所以这是我给出的集成建议,不是官方推荐。
自己的判断 / 清单
如果我要基于 Jev 做函数调用,我会按这个清单来:
- 只把闭集参数交给
Choice,自由文本、数字、日期尽量不放进模型判断,保留函数默认值或由代码补全。 - 把“选哪个函数”“参数齐不齐”“这次调用可不可信”拆成三个问题,分别用
Choice、Noul、Score。不要一个 prompt 让模型直接输出整个调用 JSON。 - 拿到
choice、noul、score后,在自己的代码里完成 dispatcher 映射和执行。不让模型参与执行。 - 低置信度或参数不齐时,直接进入澄清或人工队列,不硬调函数。
- 在执行前,如果需要,可以接 Guardrails cookbook 的风险筛查;执行后也可以对输出再做一次检查。
state里只放完成决策所需的最小信息,敏感参数先脱敏。
这次没核实的
- 未能核实官方 function calling cookbook 是否明确推荐把
Score用于“置信度低的调用打分排序”。页面被截断,我看到的完整部分没有给出这个用法。本文的call_confidence设计是我根据 Primitives 字段自己组合的。 - 未能核实官方是否给出低置信度的具体阈值。官方示例只提供了
0.75、0.91、0.94、1.00等数值,没有推荐阈值。 - 未能核实官方 function calling cookbook 中是否有
params_complete这种 Noul 问题的直接示例。我看到的原文更多在讲 closed sets 和 Choice,本文的 Noul 用法是自己设计的。 - 未能核实官方是否展示过 function calling 与 Guardrails 的集成路径。两个 cookbook 是独立页面,本文的配合方式是自己的集成建议。
参考来源
评论区
登录后可评论。