Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 41 additions & 32 deletions documentation/docs/concepts/function-calling.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,38 +85,21 @@ async def smart_home_state() -> AsyncIterable[str]:

## Configuring the Processor

To enable function calling, you must:

1. Let the model know which tools are available by providing the tool list in
the model config.

2. **Disable** any built-in automatic function calling in the model (e.g., for
`GenaiModel`, set `automatic_function_calling=...disable=True`).

3. Wrap the model processor with `FunctionCalling`, providing the same list of
functions for execution.
To enable function calling, wrap your model with `FunctionCalling` and provide
the list of functions:

```python
from genai_processors.core import function_calling, genai_model
from google.genai import types as genai_types

tools = [get_weather, set_alarm]

# 1 & 2: Configure model and disable its internal AFC.
model = genai_model.GenaiModel(
model_name="gemini-2.0-flash",
generate_content_config=genai_types.GenerateContentConfig(
tools=tools,
automatic_function_calling=genai_types.AutomaticFunctionCallingConfig(
disable=True
),
),
)
model = genai_model.GenaiModel(model_name="gemini-2.0-flash")

# 3. Wrap with FunctionCalling.
agent = function_calling.FunctionCalling(model=model, fns=tools)
agent = function_calling.FunctionCalling(model=model, fns=[get_weather, set_alarm])
```

Tool declarations are **automatically registered** on the model via
`register_tools()`. There is no need to pass `tools=` to the model's config or to
manually disable automatic function calling — `FunctionCalling` handles both.

## Async Tools and Real-Time Interaction

`FunctionCalling` is designed to work with both turn-based and real-time
Expand Down Expand Up @@ -203,13 +186,11 @@ additional tools you can add to your model's tool list:
```python
from genai_processors.core import function_calling

# We explicitly add the list_fc and cancel_fc functions from function calling
# to let the model cancel async function calls. If tools contains only synced
# functions, list_fc and cancel_fc can be omitted.
tools = [my_async_tool, function_calling.list_fc, function_calling.cancel_fc]
model = genai_model.GenaiModel(..., tools=tools, ...)

agent = function_calling.FunctionCalling(model=model, fns=tools, is_bidi_model=True)
agent = function_calling.FunctionCalling(
model=model,
fns=[my_async_tool, function_calling.list_fc, function_calling.cancel_fc],
is_bidi_model=True,
)
```

## Using MCP (Model Context Protocol)
Expand All @@ -235,6 +216,34 @@ When used with a real-time model (setting `is_bidi_model=True`), all MCP
functions will be run in the background. This lets you build real-time agents
with MCP capabilities without adapting your MCP implementation.

## Nesting FunctionCalling Processors

`FunctionCalling` processors can be nested to build hierarchical agent
architectures. When a model produces a function call whose name is **not** in
the inner processor's `fns` list, the call is passed through unmodified to the
output stream rather than raising an error. An outer `FunctionCalling` processor
can then intercept and execute it.

This enables delegation patterns—for example, a supervisor agent that owns
high-level tools (like `send_email`) while a subordinate agent handles
domain-specific tools (like `get_weather`):

```python
from genai_processors.core import function_calling, genai_model

model = genai_model.GenaiModel(model_name="gemini-2.5-flash")

# Inner agent handles weather queries.
inner_agent = function_calling.FunctionCalling(model, fns=[get_weather])

# Outer agent handles email; unknown calls from inner are passed through to it.
outer_agent = function_calling.FunctionCalling(inner_agent, fns=[send_email])
```

Because tool declarations are automatically registered on the model by
`FunctionCalling`, you **do not** need to duplicate tool definitions across
nested processors—each processor only registers its own tools.

## Tutorial

For a step-by-step guide on function calling, see the
Expand Down
14 changes: 2 additions & 12 deletions documentation/docs/development/built-in-processors.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,24 +278,14 @@ results back to the model.

```python
from genai_processors.core import function_calling
from google.genai import types as genai_types

def get_weather(city: str) -> str:
# ... implementation ...
return f"Weather in {city} is sunny."

tools = [get_weather]
model_with_tools = genai_model.GenaiModel(
...,
generate_content_config=genai_types.GenerateContentConfig(
tools=tools,
automatic_function_calling=genai_types.AutomaticFunctionCallingConfig(
disable=True
),
),
)
model_with_tools = genai_model.GenaiModel(...)

agent = function_calling.FunctionCalling(model=model_with_tools, fns=tools)
agent = function_calling.FunctionCalling(model=model_with_tools, fns=[get_weather])
```

*See: [Function Calling Concept](../concepts/function-calling.md) and
Expand Down
2 changes: 0 additions & 2 deletions examples/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,6 @@ async def run_chat() -> None:

model = models.turn_based_model(
system_instruction=SYSTEM_INSTRUCTIONS,
disable_automatic_function_calling=True,
tools=tools,
)
model = function_calling.FunctionCalling(
model=realtime.LiveModelProcessor(model),
Expand Down
8 changes: 0 additions & 8 deletions examples/live_illustrator/illustrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,6 @@ def create_live_illustrator(
model_name=MODEL_LISTEN,
generate_content_config=genai_types.GenerateContentConfig(
system_instruction=SYSTEM_INSTRUCTION,
# We will be handling tool calls on the client side.
automatic_function_calling=genai_types.AutomaticFunctionCallingConfig(
disable=True
),
tools=[
image_gen.create_image_from_description,
image_gen.create_concept_art,
],
),
)
end_of_turns_scheduler = ScheduleEndOfTurns(period_sec=image_period_sec)
Expand Down
11 changes: 4 additions & 7 deletions examples/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def turn_based_model(
]
| None
) = None,
disable_automatic_function_calling: bool = False,
) -> processor.Processor:
"""Returns a turn-based model based on command line flags.

Expand All @@ -87,9 +86,10 @@ def turn_based_model(

Args:
system_instruction: The system instruction to use for the model.
tools: The tools to use for the model, or google search tool if None.
disable_automatic_function_calling: Whether to disable automatic function
calling.
tools: Server-side tools to use for the model (e.g. google_search).
Client-side function tools should be registered via
``FunctionCalling(fns=...)`` instead — they will be auto-declared on the
model. Defaults to Google Search if None.

Returns:
A turn-based LLM model.
Expand Down Expand Up @@ -124,9 +124,6 @@ def turn_based_model(
tools=tools
if tools is not None
else [genai_types.Tool(google_search=genai_types.GoogleSearch())],
automatic_function_calling=genai_types.AutomaticFunctionCallingConfig(
disable=disable_automatic_function_calling
),
)

model_instance = genai_model.GenaiModel(
Expand Down
8 changes: 0 additions & 8 deletions examples/widgets/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,6 @@ def create_dr_widget(
model_name=MODEL,
generate_content_config=genai_types.GenerateContentConfig(
system_instruction=SYSTEM_INSTRUCTION,
# We will be handling tool calls on the client side.
automatic_function_calling=genai_types.AutomaticFunctionCallingConfig(
disable=True
),
tools=[
image_gen.create_image_from_description,
plot_gen.create_plot_from_description,
],
),
)
fc_processor = function_calling.FunctionCalling(
Expand Down
Loading
Loading