这个函数其实是在判断:
“某个类型注解是不是
RunContext(或者RunContext[T])?”
因为框架里 RunContext 不是给 LLM 看的参数,而是运行器自动注入的参数,所以要把它从 Tool Schema 里排除掉。
例如:
def search(
ctx: RunContext,
query: str,
):
...最终 Schema 里只应该出现:
{
"query": {
"type": "string"
}
}而不应该出现 ctx。
整体代码
def _is_context_annotation(annotation: Any) -> bool:
from .run_context import RunContext
if get_origin(annotation) is Annotated:
annotation = get_args(annotation)[0]
origin = get_origin(annotation) or annotation
return origin is RunContext我们一步一步拆开。
第一部分
什么是 get_origin()
这是 Python typing 模块提供的工具。
作用:
get_origin(T)返回:
泛型最外层的原始类型(origin type)
例如:
from typing import List, Dict, Optional
get_origin(List[int])结果:
list例1
annotation = list[str]
get_origin(annotation)返回:
list例2
annotation = dict[str, int]
get_origin(annotation)返回:
dict例3
annotation = Optional[str]实际上:
Optional[str]
==
Union[str, None]所以:
get_origin(Optional[str])返回:
typing.Union例4
annotation = RunContext[str]如果:
class RunContext(Generic[T]):
...那么:
get_origin(RunContext[str])返回:
RunContext第二部分
什么是 get_args()
作用:
get_args(T)返回泛型参数。
例如:
list[str]get_args(list[str])返回:
(str,)RunContext
RunContext[User]get_args(...)返回:
(User,)Union
Optional[str]get_args(Optional[str])返回:
(str, NoneType)第三部分
为什么先处理 Annotated
考虑:
Annotated[
RunContext[User],
"current user"
]这种写法完全合法。
此时:
get_origin(annotation)返回:
typing.Annotated而不是:
RunContext所以如果直接判断:
get_origin(annotation) is RunContext会失败。
因此先拆:
if get_origin(annotation) is Annotated:
annotation = get_args(annotation)[0]例如:
Annotated[
RunContext[User],
"current user"
]得到:
RunContext[User]因为:
get_args(...)返回:
(
RunContext[User],
"current user"
)第一个元素永远是真实类型。
所以:
annotation = get_args(annotation)[0]之后:
annotation变成:
RunContext[User]第四部分
最关键的一句
origin = get_origin(annotation) or annotation很多人第一次看这里会疑惑。
实际上是同时处理:
RunContext和
RunContext[T]两种情况。
情况1
用户写:
ctx: RunContext此时:
get_origin(RunContext)返回:
None因为它不是泛型实例。
所以:
origin = None or RunContext结果:
RunContext情况2
用户写:
ctx: RunContext[User]此时:
get_origin(...)返回:
RunContext于是:
origin = RunContext or RunContext[User]结果:
RunContext因此:
origin最终统一变成:
RunContext无论原来是:
RunContext还是:
RunContext[User]这是一种非常常见的 typing 判断技巧:
origin = get_origin(tp) or tp在很多框架里都能看到。
例如:
list[int]得到:
listlist得到:
list统一后就可以:
if origin is list:
...而不用分别处理:
list
list[int]第五部分
最终判断
return origin is RunContext现在:
情况1
RunContext变成:
origin = RunContext结果:
True情况2
RunContext[User]变成:
origin = RunContext结果:
True情况3
Annotated[
RunContext[User],
"current user"
]先解包:
RunContext[User]再:
origin = RunContext结果:
True情况4
strget_origin(str)返回:
None于是:
origin = str结果:
False用图理解
annotation
│
▼
Annotated[RunContext[User], ...]
│
▼
get_args(...)[0]
│
▼
RunContext[User]
│
▼
get_origin(...)
│
▼
RunContext
│
▼
origin is RunContext ?
│
┌──────────┴──────────┐
▼ ▼
True False所以这段代码的本质其实就是:
def _is_context_annotation(annotation):
# 去掉 Annotated 包装
if is_annotated(annotation):
annotation = underlying_type(annotation)
# RunContext[T] -> RunContext
annotation = get_origin(annotation) or annotation
return annotation is RunContext其中最经典、最值得记住的一行就是:
origin = get_origin(tp) or tp它的作用可以概括成一句话:
把泛型实例 (
RunContext[User]、list[str]) 和原始类型 (RunContext、list) 统一成同一种表示,方便后续比较。