这个函数其实是在判断:

“某个类型注解是不是 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]

得到:

list
list

得到:

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

str
get_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]) 和原始类型 (RunContextlist) 统一成同一种表示,方便后续比较。