Skip to content

Let a model call an application action

An A11 action can serve as an LLM tool. Register the action, allow the model to call it through interact_with_llm, and return its output before the model continues. This guide adds a simulated get_weather action to the basic model interaction.

Three controls stay separate throughout the example:

  • the action schema tells the model what it may request;
  • the allow-list authorizes the action for this model turn;
  • the registry supplies the handler that executes the request.
import os

import a11
from a11.sdk.interact_with_llm import (
    INTERACT_WITH_LLM_SCHEMA,
    interact_with_llm,
)
from a11.sdk.llm import Interaction, LlmHeaders, Role
from a11.sdk.llm_tools import runner

Write the tool as an action

The handler reads its location input and writes a report — here a canned string instead of a real API call:

async def get_weather(action):
    location = await action["location"].consume()
    await action["report"].finalize(f"It is 22°C and sunny in {location}.")

Its schema names the ports and describes them; the descriptions are what the model sees when deciding whether and how to call the tool:

GET_WEATHER = a11.ActionSchema(
    name="get_weather",
    description="Get the current weather for a location.",
    inputs={
        "location": a11.ActionPortSchema(
            name="location",
            type="text/plain",
            typeinfo=str,
            required=True,
            description="The city and country or region.",
        )
    },
    outputs={
        "report": a11.ActionPortSchema(
            name="report",
            type="text/plain",
            typeinfo=str,
            required=True,
            description="A short weather report.",
        )
    },
)

Register it

Put the tool in a registry so the interaction can dispatch it by name:

registry = a11.ActionRegistry()
registry.register("get_weather", GET_WEATHER, get_weather)

Let the model reach it

Three things connect the registry to the model. bind_registry makes the tool dispatchable; the ALLOWED_LLM_ACTIONS header allow-lists which registered actions the model may call; and the tool definitions are streamed in on the tools port so the provider knows the tool's shape:

interact = (
    a11.Action(INTERACT_WITH_LLM_SCHEMA)
    .bind_handler(interact_with_llm)
    .bind_registry(registry)
    .set_header(LlmHeaders.PROVIDER.value, "gemini")
    .set_header(LlmHeaders.MODEL.value, "gemini-3.5-flash")
    .set_header(LlmHeaders.API_KEY.value, os.environ["GEMINI_API_KEY"])
    .set_header(LlmHeaders.ALLOWED_LLM_ACTIONS.value, "get_weather")
    .run()
)

tool_definitions = runner.get_tool_definitions(registry, ["get_weather"])

Run the turn

Feed and read the action as shown in the plain interaction, and stream the tool definitions onto the tools port:

user_turn = Interaction(
    role=Role.USER,
    content=[a11.to_chunk({"role": "user", "content": [
        {"type": "text", "text": "What's the weather in Paris?"}]})],
)

await interact["interactions"].finalize(user_turn)
await interact["config"].finalize()
tools = interact["tools"]
for tool in tool_definitions:
    await tools.put(tool)
await tools.finalize()

When the model decides to call get_weather, interact_with_llm dispatches the action in this process, streams its report back to the model, and lets the model continue — so the final text on text_output already reflects the tool result. Read that output from text_output:

async for chunk in interact["text_output"]:
    print(chunk, end="", flush=True)
await interact.wait()

Tool-call lifecycle

The model requests a tool call, the registered action runs, and its output returns to the model before the model answers. Because the tool is an ordinary action:

  • it could stream progress on report instead of returning one string;
  • it could itself .call() a remote action (see local to remote), so a client tool can front a real weather service;
  • swapping the canned string for a live API call changes nothing about the wiring above.

The complete interactive version — multi-turn, multi-provider, with the tool registry — is examples/002-llm-interactions.

For applications that receive provider interactions directly, see running requested actions. To offer tools from an MCP server through the same registry, see MCP tools as actions.