Skip to content

Expose tool calling primitives #2189

@tibbe

Description

@tibbe

Description

While working on human-in-the-loop (HITL) tool calling (see #1817), I've been implementing my own agent on top of the lower-level pydantic-ai modules (and thus not using the agent module). This means dealing with tool calling more directly.

In a HITL scenario you often want to send the human a representation of what actions the agent would take. A "diff" of a sort. So given a tool function of type:

ToolParams = ParamSpec('ToolParams', default=...)
"""Retrieval function param spec."""

ToolFuncContext = Callable[Concatenate[RunContext[AgentDepsT], ToolParams], Any]
"""A tool function that takes `RunContext` as the first argument.

We want another function with the same arguments but a different return type:

class Diff:
    """A human-readable diff showing what the tool would do."""

ToolFuncDiffContext = Callable[Concatenate[RunContext[AgentDepsT], ToolParams], Diff]

So when the agent wants to call a tool that needs a HITL, we take the arguments we would have passed to the actual tool and instead pass them to this diff function, producing a diff. We then send the diff to the human and, if they approve it, call the actual tool.

To do this well (i.e. without duplicating much of what pydantic-ai does) we need access to a function that, given a tool call (e.g. ToolCallPart) returned by the model, transform it into Python function parameters, so we can use them to call functions other than the tool itself.

In general, I see pydantic-ai as providing (at least) 3 core piece of functionality for tools:

  • Given a Python function, create a schema that can be given to the model to describe the available tool call.
  • Given a tool call from the model, convert the model response into function arguments that can be passed to the function.
  • Given the function's return value, convert it into a format that can be given back to the model.

All these pieces already exist in pydantic-ai, but they aren't (completely) exposed. I think exposing them would make pydantic-ai more usable as a library, which we can build our own agents upon (if agent doesn't work for our use case).

I think this is in fact a strength of the whole pydantic ecosystem: to be able to use pieces to build your own things without having to use a all-or-nothing framework.

References

No response

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions