ComposioComposio 0.21 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
8 small wins to finish your pathNext lesson

Executing tool calls

Composio runs tool calls two ways: through a session, with its meta tools, or directly, with tools you fetch by slug for a user. Only the direct path takes modifiers.

SessionDirect
Toolssession.tools()composio.tools.get(user_id, tools=[...])
Executeprovider.handle_tool_calls(session=session, response=...)provider.handle_tool_calls(user_id=..., response=..., modifiers=[...])
The model seesMeta tools that find and run app toolsExactly the tools you listed
Examplewith a key
tools = composio.tools.get("cust_7f3a", tools=["GITHUB_CREATE_AN_ISSUE", "GMAIL_FETCH_EMAILS"])
response = client.chat.completions.create(model="gpt-4.1-mini", messages=messages, tools=tools)
results = composio.provider.handle_tool_calls(user_id="cust_7f3a", response=response)

handle_tool_calls takes either a user_id or a session, never both, and runs every tool call in the response's first choice, in order, so the first result answers the first tool call. Modifiers, your own functions that change a call's arguments or its result, are the subject of lesson 6. Passing them together with a session is refused before anything is sent:

Example
from composio import Composio, after_execute
from openai.types.chat import ChatCompletion

composio = Composio(api_key="not-a-real-key", allow_tracking=False)


@after_execute(toolkits=["gmail"])
def unchanged(tool, toolkit, response):
    return response


response = ChatCompletion(id="1", object="chat.completion", created=0, model="m", choices=[])
composio.provider.handle_tool_calls(session=object(), response=response, modifiers=[unchanged])

Results and failures

Example
from composio.core.models.tools import ToolExecutionResponse

print(list(ToolExecutionResponse.__annotations__))

Each result is a dictionary with the app's data, an error message and successful. Give the model the error so it can try something else, but stop your loop on authentication problems, such as a user who revoked access, and ask the user to reconnect.

Every call acts on a real account
A tool that sends an email sends it. Before a customer goes live, decide which tools need a person's approval, as in the Pydantic AI and AI SDK courses, and test with a test account, never the customer's.
Try it yourself
  • Print the signature of composio.tools.execute. What does it need that handle_tool_calls does not?
  • Read ToolVersionRequiredError in composio/exceptions.py: when is it raised?
  • Print inspect.signature(composio.provider.handle_tool_calls).

This is what real progress feels like.