Repository: pydantic/pydantic-ai
File: pydantic_ai_slim/pydantic_ai/_function_schema.py (Python)

Code before:
    config_wrapper = ConfigWrapper(config)
    gen_schema = _generate_schema.GenerateSchema(config_wrapper)
    errors: list[str] = []

    try:
        sig = signature(function)
    except ValueError as e:
        errors.append(str(e))
        sig = signature(lambda: None)
    original_func = function.func if isinstance(function, partial) else function

    type_hints = get_type_hints(original_func)

    var_kwargs_schema: core_schema.CoreSchema | None = None
    fields: dict[str, core_schema.TypedDictField] = {}
    positional_fields: list[str] = []
    var_positional_field: str | None = None
    decorators = _decorators.DecoratorInfos()

    description, field_descriptions = doc_descriptions(original_func, sig, docstring_format=docstring_format)

    if require_parameter_descriptions:
        if takes_ctx:
            parameters_without_ctx = set(
                name for name in sig.parameters if not _is_call_ctx(sig.parameters[name].annotation)
            )
            missing_params = parameters_without_ctx - set(field_descriptions)
        else:
            missing_params = set(sig.parameters) - set(field_descriptions)

        if missing_params:
            errors.append(f'Missing parameter descriptions for {", ".join(missing_params)}')

    for index, (name, p) in enumerate(sig.parameters.items()):
        if index == 0 and takes_ctx is None:
            takes_ctx = p.annotation is not sig.empty and _is_call_ctx(type_hints[name])

        if p.annotation is sig.empty:
            if takes_ctx and index == 0:
                # should be the `context` argument, skip
                continue
            # TODO warn?
            annotation = Any
        else:
            annotation = type_hints[name]

            if index == 0 and takes_ctx:
                if not _is_call_ctx(annotation):
                    errors.append('First parameter of tools that take context must be annotated with RunContext[...]')
                continue
            elif not takes_ctx and _is_call_ctx(annotation):

Review comment (suggestion):
Instead of using `_takes_ctx()`, do the logic here. This avoids computing the type hints twice.

Code after:
    config_wrapper = ConfigWrapper(config)
    gen_schema = _generate_schema.GenerateSchema(config_wrapper)
    errors: list[str] = []

    try:
        sig = signature(function)
    except ValueError as e:
        errors.append(str(e))
        sig = signature(lambda: None)
    original_func = function.func if isinstance(function, partial) else function
    function = cast(Callable[..., Any], function)  # cope with pyright changing the type from the isinstance() check.

    type_hints = get_type_hints(original_func, include_extras=True)

    var_kwargs_schema: core_schema.CoreSchema | None = None
    fields: dict[str, core_schema.TypedDictField] = {}
    positional_fields: list[str] = []
    var_positional_field: str | None = None
    decorators = _decorators.DecoratorInfos()

    description, field_descriptions = doc_descriptions(original_func, sig, docstring_format=docstring_format)
    missing_param_descriptions: set[str] = set()

    for index, (name, p) in enumerate(sig.parameters.items()):
        if index == 0 and takes_ctx is None:
            takes_ctx = p.annotation is not sig.empty and _is_call_ctx(type_hints[name])

        if p.annotation is sig.empty:
            if takes_ctx and index == 0:
                # should be the `context` argument, skip
                continue
            # TODO warn?
            annotation = Any
        else:
            annotation = type_hints[name]

            if index == 0 and takes_ctx:
                if not _is_call_ctx(annotation):
                    errors.append('First parameter of tools that take context must be annotated with RunContext[...]')
                continue
            elif not takes_ctx and _is_call_ctx(annotation):
                errors.append('RunContext annotations can only be used with tools that take context')
                continue
            elif index != 0 and _is_call_ctx(annotation):
                errors.append('RunContext annotations can only be used as the first argument')
                continue

        field_name = p.name

        if require_parameter_descriptions and field_name not in field_descriptions:
            missing_param_descriptions.add(field_name)