跳转到内容

函数工具

函数工具为模型提供了一种机制,使其能够执行操作和检索额外信息,以帮助生成响应。

当您希望模型能够采取某些行动并使用其结果时,当将代理可能需要的所有上下文放入指令中不切实际或不可能时,或者当您希望通过将生成响应所需的部分逻辑推迟到另一个(不一定是 AI 驱动的)工具来使代理的行为更具确定性或可靠性时,它们非常有用。

如果您希望模型能够调用一个函数作为其最终操作,而结果不会被发送回模型,您可以使用输出函数

有多种方法可以向代理注册工具:

对于更高级的用例,工具集功能让您可以管理工具的集合(由您构建或由 MCP 服务器或其他第三方提供),并通过 Agenttoolsets 关键字参数一次性将它们注册到代理。在内部,所有的 toolstoolsets 都被收集到一个组合工具集中,并提供给模型使用。

函数工具 vs. RAG

函数工具基本上是 RAG(检索增强生成)中的“R”——它们通过让模型请求额外信息来增强模型的能力。

Pydantic AI 工具和 RAG 之间的主要语义区别是,RAG 是向量搜索的同义词,而 Pydantic AI 工具则更通用。(注意:我们将来可能会增加对向量搜索功能的支持,特别是一个用于生成嵌入的 API。请参阅 #58

函数工具 vs. 结构化输出

顾名思义,函数工具使用模型的“工具”或“函数”API 来让模型知道可以调用什么。当使用默认的工具输出模式时,工具或函数也用于定义结构化输出的模式,因此一个模型可能可以访问许多工具,其中一些调用函数工具,而另一些则结束运行并产生最终输出。

通过装饰器注册

@agent.tool 被认为是默认的装饰器,因为在大多数情况下,工具需要访问代理上下文

以下是一个同时使用两者的示例:

dice_game.py
import random

from pydantic_ai import Agent, RunContext

agent = Agent(
    'google-gla:gemini-1.5-flash',  # (1)!
    deps_type=str,  # (2)!
    system_prompt=(
        "You're a dice game, you should roll the die and see if the number "
        "you get back matches the user's guess. If so, tell them they're a winner. "
        "Use the player's name in the response."
    ),
)


@agent.tool_plain  # (3)!
def roll_dice() -> str:
    """Roll a six-sided die and return the result."""
    return str(random.randint(1, 6))


@agent.tool  # (4)!
def get_player_name(ctx: RunContext[str]) -> str:
    """Get the player's name."""
    return ctx.deps


dice_result = agent.run_sync('My guess is 4', deps='Anne')  # (5)!
print(dice_result.output)
#> Congratulations Anne, you guessed correctly! You're a winner!
  1. 这是一个非常简单的任务,所以我们可以使用快速且廉价的 Gemini flash 模型。
  2. 我们将用户的名字作为依赖项传递,为了简单起见,我们只使用名字字符串作为依赖项。
  3. 这个工具不需要任何上下文,它只是返回一个随机数。在这种情况下,您或许可以使用动态指令。
  4. 这个工具需要玩家的名字,所以它使用 RunContext 来访问依赖项,在这种情况下就是玩家的名字。
  5. 运行代理,将玩家的名字作为依赖项传递。

(这个例子是完整的,可以“按原样”运行)

让我们打印那场游戏的消息,看看发生了什么:

dice_game_messages.py
from dice_game import dice_result

print(dice_result.all_messages())
"""
[
    ModelRequest(
        parts=[
            SystemPromptPart(
                content="You're a dice game, you should roll the die and see if the number you get back matches the user's guess. If so, tell them they're a winner. Use the player's name in the response.",
                timestamp=datetime.datetime(...),
            ),
            UserPromptPart(
                content='My guess is 4',
                timestamp=datetime.datetime(...),
            ),
        ]
    ),
    ModelResponse(
        parts=[
            ToolCallPart(
                tool_name='roll_dice', args={}, tool_call_id='pyd_ai_tool_call_id'
            )
        ],
        usage=RequestUsage(input_tokens=90, output_tokens=2),
        model_name='gemini-1.5-flash',
        timestamp=datetime.datetime(...),
    ),
    ModelRequest(
        parts=[
            ToolReturnPart(
                tool_name='roll_dice',
                content='4',
                tool_call_id='pyd_ai_tool_call_id',
                timestamp=datetime.datetime(...),
            )
        ]
    ),
    ModelResponse(
        parts=[
            ToolCallPart(
                tool_name='get_player_name', args={}, tool_call_id='pyd_ai_tool_call_id'
            )
        ],
        usage=RequestUsage(input_tokens=91, output_tokens=4),
        model_name='gemini-1.5-flash',
        timestamp=datetime.datetime(...),
    ),
    ModelRequest(
        parts=[
            ToolReturnPart(
                tool_name='get_player_name',
                content='Anne',
                tool_call_id='pyd_ai_tool_call_id',
                timestamp=datetime.datetime(...),
            )
        ]
    ),
    ModelResponse(
        parts=[
            TextPart(
                content="Congratulations Anne, you guessed correctly! You're a winner!"
            )
        ],
        usage=RequestUsage(input_tokens=92, output_tokens=12),
        model_name='gemini-1.5-flash',
        timestamp=datetime.datetime(...),
    ),
]
"""

我们可以用一个图表来表示这一点:

sequenceDiagram
    participant Agent
    participant LLM

    Note over Agent: Send prompts
    Agent ->> LLM: System: "You're a dice game..."<br>User: "My guess is 4"
    activate LLM
    Note over LLM: LLM decides to use<br>a tool

    LLM ->> Agent: Call tool<br>roll_dice()
    deactivate LLM
    activate Agent
    Note over Agent: Rolls a six-sided die

    Agent -->> LLM: ToolReturn<br>"4"
    deactivate Agent
    activate LLM
    Note over LLM: LLM decides to use<br>another tool

    LLM ->> Agent: Call tool<br>get_player_name()
    deactivate LLM
    activate Agent
    Note over Agent: Retrieves player name
    Agent -->> LLM: ToolReturn<br>"Anne"
    deactivate Agent
    activate LLM
    Note over LLM: LLM constructs final response

    LLM ->> Agent: ModelResponse<br>"Congratulations Anne, ..."
    deactivate LLM
    Note over Agent: Game session complete

通过 Agent 参数注册

除了使用装饰器,我们还可以通过 Agent 构造函数tools 参数来注册工具。当您想重用工具时,这非常有用,并且还可以对工具进行更精细的控制。

dice_game_tool_kwarg.py
import random

from pydantic_ai import Agent, RunContext, Tool

system_prompt = """\
You're a dice game, you should roll the die and see if the number
you get back matches the user's guess. If so, tell them they're a winner.
Use the player's name in the response.
"""


def roll_dice() -> str:
    """Roll a six-sided die and return the result."""
    return str(random.randint(1, 6))


def get_player_name(ctx: RunContext[str]) -> str:
    """Get the player's name."""
    return ctx.deps


agent_a = Agent(
    'google-gla:gemini-1.5-flash',
    deps_type=str,
    tools=[roll_dice, get_player_name],  # (1)!
    system_prompt=system_prompt,
)
agent_b = Agent(
    'google-gla:gemini-1.5-flash',
    deps_type=str,
    tools=[  # (2)!
        Tool(roll_dice, takes_ctx=False),
        Tool(get_player_name, takes_ctx=True),
    ],
    system_prompt=system_prompt,
)

dice_result = {}
dice_result['a'] = agent_a.run_sync('My guess is 6', deps='Yashar')
dice_result['b'] = agent_b.run_sync('My guess is 4', deps='Anne')
print(dice_result['a'].output)
#> Tough luck, Yashar, you rolled a 4. Better luck next time.
print(dice_result['b'].output)
#> Congratulations Anne, you guessed correctly! You're a winner!
  1. 通过 Agent 构造函数注册工具的最简单方法是传递一个函数列表,通过检查函数签名来确定工具是否接受 RunContext
  2. agent_aagent_b 是相同的 —— 但我们可以使用 Tool 来重用工具定义,并对工具的定义方式进行更精细的控制,例如设置它们的名称或描述,或使用自定义的 prepare 方法。

(这个例子是完整的,可以“按原样”运行)

工具输出

工具可以返回 Pydantic 能够序列化为 JSON 的任何内容。有关包括多模态内容和元数据在内的高级输出选项,请参阅高级工具功能

工具模式 (Schema)

函数参数从函数签名中提取,除了 RunContext 之外的所有参数都用于构建该工具调用的模式。

更妙的是,Pydantic AI 从函数中提取文档字符串,并(感谢 griffe)从文档字符串中提取参数描述并将其添加到模式中。

Griffe 支持googlenumpysphinx 风格的文档字符串中提取参数描述。Pydantic AI 会根据文档字符串推断要使用的格式,但您可以使用 docstring_format 显式设置它。您还可以通过设置 require_parameter_descriptions=True 来强制要求参数描述。如果缺少参数描述,这将引发一个 UserError

为了演示工具的模式,我们在这里使用 FunctionModel 来打印模型会收到的模式:

tool_schema.py
from pydantic_ai import Agent
from pydantic_ai.messages import ModelMessage, ModelResponse, TextPart
from pydantic_ai.models.function import AgentInfo, FunctionModel

agent = Agent()


@agent.tool_plain(docstring_format='google', require_parameter_descriptions=True)
def foobar(a: int, b: str, c: dict[str, list[float]]) -> str:
    """Get me foobar.

    Args:
        a: apple pie
        b: banana cake
        c: carrot smoothie
    """
    return f'{a} {b} {c}'


def print_schema(messages: list[ModelMessage], info: AgentInfo) -> ModelResponse:
    tool = info.function_tools[0]
    print(tool.description)
    #> Get me foobar.
    print(tool.parameters_json_schema)
    """
    {
        'additionalProperties': False,
        'properties': {
            'a': {'description': 'apple pie', 'type': 'integer'},
            'b': {'description': 'banana cake', 'type': 'string'},
            'c': {
                'additionalProperties': {'items': {'type': 'number'}, 'type': 'array'},
                'description': 'carrot smoothie',
                'type': 'object',
            },
        },
        'required': ['a', 'b', 'c'],
        'type': 'object',
    }
    """
    return ModelResponse(parts=[TextPart('foobar')])


agent.run_sync('hello', model=FunctionModel(print_schema))

(这个例子是完整的,可以“按原样”运行)

如果一个工具只有一个参数,并且该参数可以在 JSON schema 中表示为一个对象(例如 dataclass、TypedDict、pydantic 模型),那么该工具的模式将被简化为仅该对象。

下面是一个示例,我们使用 TestModel.last_model_request_parameters 来检查将传递给模型的工具模式。

single_parameter_tool.py
from pydantic import BaseModel

from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel

agent = Agent()


class Foobar(BaseModel):
    """This is a Foobar"""

    x: int
    y: str
    z: float = 3.14


@agent.tool_plain
def foobar(f: Foobar) -> str:
    return str(f)


test_model = TestModel()
result = agent.run_sync('hello', model=test_model)
print(result.output)
#> {"foobar":"x=0 y='a' z=3.14"}
print(test_model.last_model_request_parameters.function_tools)
"""
[
    ToolDefinition(
        name='foobar',
        parameters_json_schema={
            'properties': {
                'x': {'type': 'integer'},
                'y': {'type': 'string'},
                'z': {'default': 3.14, 'type': 'number'},
            },
            'required': ['x', 'y'],
            'title': 'Foobar',
            'type': 'object',
        },
        description='This is a Foobar',
    )
]
"""

(这个例子是完整的,可以“按原样”运行)

另请参阅

有关更多工具功能和集成,请参阅: