跳转至

Function schema

FuncSchema dataclass

捕获 Python 函数的 schema,为将其作为工具发送到 LLM 做准备。

Source code in src/agents/function_schema.py
@dataclass
class FuncSchema:
    """
    捕获 Python 函数的 schema,为将其作为工具发送到 LLM 做准备。
    """

    name: str
    """函数的名称。"""
    description: str | None
    """函数的描述。"""
    params_pydantic_model: type[BaseModel]
    """表示函数参数的 Pydantic 模型。"""
    params_json_schema: dict[str, Any]
    """从 Pydantic 模型派生出的函数参数的 JSON schema。"""
    signature: inspect.Signature
    """函数的签名。"""
    takes_context: bool = False
    """函数是否接收 RunContextWrapper 参数(必须是第一个参数)。"""
    strict_json_schema: bool = True
    """JSON schema 是否为严格模式。我们**强烈**建议将其设置为 True,
    因为这会提高正确 JSON 输入的概率。"""

    def to_call_args(self, data: BaseModel) -> tuple[list[Any], dict[str, Any]]:
        """
        将 Pydantic 模型中验证过的数据转换为 (args, kwargs),以便调用原始函数。
        """
        positional_args: list[Any] = []
        keyword_args: dict[str, Any] = {}
        seen_var_positional = False

        # 使用 enumerate(),这样如果第一个参数是 context 可以跳过。
        for idx, (name, param) in enumerate(self.signature.parameters.items()):
            # 如果函数接收 RunContextWrapper 且这是第一个参数,则跳过。
            if self.takes_context and idx == 0:
                continue

            value = getattr(data, name, None)
            if param.kind == param.VAR_POSITIONAL:
                # 例如 *args:扩展位置参数,并标记已见到 *args
                positional_args.extend(value or [])
                seen_var_positional = True
            elif param.kind == param.VAR_KEYWORD:
                # 例如 **kwargs 处理
                keyword_args.update(value or {})
            elif param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
                # 在 *args 之前,加入位置参数;在 *args 之后,加入关键字参数。
                if not seen_var_positional:
                    positional_args.append(value)
                else:
                    keyword_args[name] = value
            else:
                # 对于 KEYWORD_ONLY 参数,总是使用关键字参数。
                keyword_args[name] = value
        return positional_args, keyword_args

name instance-attribute

name: str

函数的名称。

description instance-attribute

description: str | None

函数的描述。

params_pydantic_model instance-attribute

params_pydantic_model: type[BaseModel]

表示函数参数的 Pydantic 模型。

params_json_schema instance-attribute

params_json_schema: dict[str, Any]

从 Pydantic 模型派生出的函数参数的 JSON schema。

signature instance-attribute

signature: Signature

函数的签名。

takes_context class-attribute instance-attribute

takes_context: bool = False

函数是否接收 RunContextWrapper 参数(必须是第一个参数)。

strict_json_schema class-attribute instance-attribute

strict_json_schema: bool = True

JSON schema 是否为严格模式。我们强烈建议将其设置为 True, 因为这会提高正确 JSON 输入的概率。

to_call_args

to_call_args(
    data: BaseModel,
) -> tuple[list[Any], dict[str, Any]]

将 Pydantic 模型中验证过的数据转换为 (args, kwargs),以便调用原始函数。

Source code in src/agents/function_schema.py
def to_call_args(self, data: BaseModel) -> tuple[list[Any], dict[str, Any]]:
    """
    将 Pydantic 模型中验证过的数据转换为 (args, kwargs),以便调用原始函数。
    """
    positional_args: list[Any] = []
    keyword_args: dict[str, Any] = {}
    seen_var_positional = False

    # 使用 enumerate(),这样如果第一个参数是 context 可以跳过。
    for idx, (name, param) in enumerate(self.signature.parameters.items()):
        # 如果函数接收 RunContextWrapper 且这是第一个参数,则跳过。
        if self.takes_context and idx == 0:
            continue

        value = getattr(data, name, None)
        if param.kind == param.VAR_POSITIONAL:
            # 例如 *args:扩展位置参数,并标记已见到 *args
            positional_args.extend(value or [])
            seen_var_positional = True
        elif param.kind == param.VAR_KEYWORD:
            # 例如 **kwargs 处理
            keyword_args.update(value or {})
        elif param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
            # 在 *args 之前,加入位置参数;在 *args 之后,加入关键字参数。
            if not seen_var_positional:
                positional_args.append(value)
            else:
                keyword_args[name] = value
        else:
            # 对于 KEYWORD_ONLY 参数,总是使用关键字参数。
            keyword_args[name] = value
    return positional_args, keyword_args

FuncDocumentation dataclass

包含从 Python 函数 docstring 中提取的元数据。

Source code in src/agents/function_schema.py
@dataclass
class FuncDocumentation:
    """包含从 Python 函数 docstring 中提取的元数据。"""

    name: str
    """函数的名称,通过 `__name__` 获取。"""
    description: str | None
    """从 docstring 派生的函数描述。"""
    param_descriptions: dict[str, str] | None
    """从 docstring 派生的函数参数描述。"""

name instance-attribute

name: str

函数的名称,通过 __name__ 获取。

description instance-attribute

description: str | None

从 docstring 派生的函数描述。

param_descriptions instance-attribute

param_descriptions: dict[str, str] | None

从 docstring 派生的函数参数描述。

generate_func_documentation

generate_func_documentation(
    func: Callable[..., Any],
    style: DocstringStyle | None = None,
) -> FuncDocumentation

从函数 docstring 中提取元数据,为将其作为工具发送到 LLM 做准备。

Parameters:

Name Type Description Default
func Callable[..., Any]

要提取文档的函数。

required
style DocstringStyle | None

用于解析 docstring 的风格。如果未提供,将尝试自动检测风格。

None

Returns:

Type Description
FuncDocumentation

包含函数名称、描述和参数描述的 FuncDocumentation 对象。

Source code in src/agents/function_schema.py
def generate_func_documentation(
    func: Callable[..., Any], style: DocstringStyle | None = None
) -> FuncDocumentation:
    """
    从函数 docstring 中提取元数据,为将其作为工具发送到 LLM 做准备。

    Args:
        func: 要提取文档的函数。
        style: 用于解析 docstring 的风格。如果未提供,将尝试自动检测风格。

    Returns:
        包含函数名称、描述和参数描述的 FuncDocumentation 对象。
    """
    name = func.__name__
    doc = inspect.getdoc(func)
    if not doc:
        return FuncDocumentation(name=name, description=None, param_descriptions=None)

    with _suppress_griffe_logging():
        docstring = Docstring(doc, lineno=1, parser=style or _detect_docstring_style(doc))
        parsed = docstring.parse()

    description: str | None = next(
        (section.value for section in parsed if section.kind == DocstringSectionKind.text), None
    )

    param_descriptions: dict[str, str] = {
        param.name: param.description
        for section in parsed
        if section.kind == DocstringSectionKind.parameters
        for param in section.value
    }

    return FuncDocumentation(
        name=func.__name__,
        description=description,
        param_descriptions=param_descriptions or None,
    )

function_schema

function_schema(
    func: Callable[..., Any],
    docstring_style: DocstringStyle | None = None,
    name_override: str | None = None,
    description_override: str | None = None,
    use_docstring_info: bool = True,
    strict_json_schema: bool = True,
) -> FuncSchema

给定一个 Python 函数,从中提取 FuncSchema,捕获名称、描述、参数描述及其他元数据。

Parameters:

Name Type Description Default
func Callable[..., Any]

要提取 schema 的函数。

required
docstring_style DocstringStyle | None

用于解析 docstring 的风格。如果未提供,将尝试自动检测风格。

None
name_override str | None

如果提供,则使用该名称替代函数的 __name__

None
description_override str | None

如果提供,则使用该描述替代从 docstring 派生的描述。

None
use_docstring_info bool

如果为 True,则使用 docstring 生成描述和参数描述。

True
strict_json_schema bool

JSON schema 是否为严格模式。如果为 True,将确保 schema 符合 OpenAI API 期望的“strict”标准。我们强烈 建议将其设置为 True,因为这会提高 LLM 提供正确 JSON 输入的概率。

True

Returns:

Type Description
FuncSchema

包含函数名称、描述、参数描述及其他元数据的 FuncSchema 对象。

Source code in src/agents/function_schema.py
def function_schema(
    func: Callable[..., Any],
    docstring_style: DocstringStyle | None = None,
    name_override: str | None = None,
    description_override: str | None = None,
    use_docstring_info: bool = True,
    strict_json_schema: bool = True,
) -> FuncSchema:
    """
    给定一个 Python 函数,从中提取 `FuncSchema`,捕获名称、描述、参数描述及其他元数据。

    Args:
        func: 要提取 schema 的函数。
        docstring_style: 用于解析 docstring 的风格。如果未提供,将尝试自动检测风格。
        name_override: 如果提供,则使用该名称替代函数的 `__name__`。
        description_override: 如果提供,则使用该描述替代从 docstring 派生的描述。
        use_docstring_info: 如果为 True,则使用 docstring 生成描述和参数描述。
        strict_json_schema: JSON schema 是否为严格模式。如果为 True,将确保
            schema 符合 OpenAI API 期望的“strict”标准。我们**强烈**
            建议将其设置为 True,因为这会提高 LLM 提供正确 JSON 输入的概率。

    Returns:
        包含函数名称、描述、参数描述及其他元数据的 `FuncSchema` 对象。
    """

    # 1. 获取 docstring 信息
    if use_docstring_info:
        doc_info = generate_func_documentation(func, docstring_style)
        param_descs = doc_info.param_descriptions or {}
    else:
        doc_info = None
        param_descs = {}

    func_name = name_override or doc_info.name if doc_info else func.__name__

    # 2. 检查函数签名并获取类型注解
    sig = inspect.signature(func)
    type_hints = get_type_hints(func)
    params = list(sig.parameters.items())
    takes_context = False
    filtered_params = []

    if params:
        first_name, first_param = params[0]
        # 优先使用已解析的类型注解(如果有)
        ann = type_hints.get(first_name, first_param.annotation)
        if ann != inspect._empty:
            origin = get_origin(ann) or ann
            if origin is RunContextWrapper:
                takes_context = True  # 标记函数接收 context
            else:
                filtered_params.append((first_name, first_param))
        else:
            filtered_params.append((first_name, first_param))

    # 对第一个参数以外的参数,如果有使用 RunContextWrapper,则抛出错误。
    for name, param in params[1:]:
        ann = type_hints.get(name, param.annotation)
        if ann != inspect._empty:
            origin = get_origin(ann) or ann
            if origin is RunContextWrapper:
                raise UserError(
                    f"RunContextWrapper param found at non-first position in function"
                    f" {func.__name__}"
                )
        filtered_params.append((name, param))

    # We will collect field definitions for create_model as a dict:
    #   field_name -> (type_annotation, default_value_or_Field(...))
    fields: dict[str, Any] = {}

    for name, param in filtered_params:
        ann = type_hints.get(name, param.annotation)
        default = param.default

        # If there's no type hint, assume `Any`
        if ann == inspect._empty:
            ann = Any

        # If a docstring param description exists, use it
        field_description = param_descs.get(name, None)

        # Handle different parameter kinds
        if param.kind == param.VAR_POSITIONAL:
            # e.g. *args: extend positional args
            if get_origin(ann) is tuple:
                # e.g. def foo(*args: tuple[int, ...]) -> treat as List[int]
                args_of_tuple = get_args(ann)
                if len(args_of_tuple) == 2 and args_of_tuple[1] is Ellipsis:
                    ann = list[args_of_tuple[0]]  # type: ignore
                else:
                    ann = list[Any]
            else:
                # If user wrote *args: int, treat as List[int]
                ann = list[ann]  # type: ignore

            # Default factory to empty list
            fields[name] = (
                ann,
                Field(default_factory=list, description=field_description),  # type: ignore
            )

        elif param.kind == param.VAR_KEYWORD:
            # **kwargs handling
            if get_origin(ann) is dict:
                # e.g. def foo(**kwargs: dict[str, int])
                dict_args = get_args(ann)
                if len(dict_args) == 2:
                    ann = dict[dict_args[0], dict_args[1]]  # type: ignore
                else:
                    ann = dict[str, Any]
            else:
                # e.g. def foo(**kwargs: int) -> Dict[str, int]
                ann = dict[str, ann]  # type: ignore

            fields[name] = (
                ann,
                Field(default_factory=dict, description=field_description),  # type: ignore
            )

        else:
            # Normal parameter
            if default == inspect._empty:
                # Required field
                fields[name] = (
                    ann,
                    Field(..., description=field_description),
                )
            else:
                # Parameter with a default value
                fields[name] = (
                    ann,
                    Field(default=default, description=field_description),
                )

    # 3. Dynamically build a Pydantic model
    dynamic_model = create_model(f"{func_name}_args", __base__=BaseModel, **fields)

    # 4. Build JSON schema from that model
    json_schema = dynamic_model.model_json_schema()
    if strict_json_schema:
        json_schema = ensure_strict_json_schema(json_schema)

    # 5. Return as a FuncSchema dataclass
    return FuncSchema(
        name=func_name,
        description=description_override or doc_info.description if doc_info else None,
        params_pydantic_model=dynamic_model,
        params_json_schema=json_schema,
        signature=sig,
        takes_context=takes_context,
        strict_json_schema=strict_json_schema,
    )