Processing Tools

Enhancing tool reliability with schema, input, and output transformations

Schema Processing and Preprocessing

There may be scenarios where you want to control how the LLM calls a tool and how Composio executes it. You may want to remove some parameters from the schema, inject some values into the tool call or process the response from the tool call.

Let’s take GMAIL_SEND_EMAIL as an example. Below is the schema.

JSON
1{
2 "type": "function",
3 "function": {
4 "name": "GMAIL_SEND_EMAIL",
5 "description": "Send An Email Using Gmail's Api.",
6 "parameters": {
7 "properties": {
8 "user_id": {
9 "default": "me",
10 "description": "The user's email address or 'me' for the authenticated user. Please provide a value of type string.",
11 "title": "User Id",
12 "type": "string"
13 },
14 "recipient_email": {
15 "description": "Email address of the recipient. Please provide a value of type string. This parameter is required.",
16 "examples": ["john@doe.com"],
17 "title": "Recipient Email",
18 "type": "string"
19 },
20 "cc": {
21 "default": [],
22 "description": "Email addresses of the recipients to be added as a carbon copy (CC).",
23 "examples": [
24 ["john@doe.com", "jane@doe.com"]
25 ],
26 "items": {
27 "type": "string"
28 },
29 "title": "Cc",
30 "type": "array"
31 },
32 "bcc": {
33 "default": [],
34 "description": "Email addresses of the recipients to be added as a blind carbon copy (BCC).",
35 "examples": [
36 ["john@doe.com", "jane@doe.com"]
37 ],
38 "items": {
39 "type": "string"
40 },
41 "title": "Bcc",
42 "type": "array"
43 },
44 "subject": {
45 "default": null,
46 "description": "Subject of the email. Please provide a value of type string.",
47 "examples": ["Job Application"],
48 "title": "Subject",
49 "type": "string"
50 },
51 "body": {
52 "description": "Body content of the email. Can be plain text or HTML. Please provide a value of type string. This parameter is required.",
53 "examples": ["<h1>Hello</h1><p>This is an HTML email.</p>"],
54 "title": "Body",
55 "type": "string"
56 },
57 "is_html": {
58 "default": false,
59 "description": "Set to True if the body content is HTML. Please provide a value of type boolean.",
60 "title": "Is Html",
61 "type": "boolean"
62 },
63 "attachment": {
64 "anyOf": [{
65 "file_uploadable": true,
66 "properties": {
67 "name": {
68 "title": "Name",
69 "type": "string"
70 },
71 "mimetype": {
72 "title": "Mimetype",
73 "type": "string"
74 },
75 "s3key": {
76 "title": "S3Key",
77 "type": "string"
78 }
79 },
80 "required": ["name", "mimetype", "s3key"],
81 "title": "FileUploadable",
82 "type": "object"
83 }, {
84 "type": "null"
85 }],
86 "default": null,
87 "description": "Path of File to be attached with the mail, If attachment to be sent.",
88 "type": "object"
89 }
90 },
91 "title": "SendEmailRequest",
92 "type": "object",
93 "required": ["recipient_email", "body"]
94 }
95 }
96}
Python
1from dotenv import load_dotenv
2from openai import OpenAI
3from composio_openai import ComposioToolSet, App, Action
4
5load_dotenv()
6
7client = OpenAI()
8toolset = ComposioToolSet()
9
10send_email_tool = toolset.get_tools(
11 [Action.GMAIL_SEND_EMAIL], check_connected_accounts=False
12)
13response = client.chat.completions.create(
14 model="gpt-4o",
15 messages=[
16 {"role": "system", "content": "You are a head of AGI at OpenAI."},
17 {
18 "role": "user",
19 "content": "Send an email to Sam Altman in one sentence that AGI is coming 5 years earlier.",
20 },
21 ],
22 tools=send_email_tool,
23)

This emits a tool call to GMAIL_SEND_EMAIL from the LLM. Here are the arguments for it.

GMAIL_SEND_EMAIL
1{
2 "user_id": "me",
3 "recipient_email": "sam.altman@openai.com",
4 "subject": "Urgent Update on AGI Timeline",
5 "body": "AGI is projected to arrive 5 years earlier than anticipated."
6}

When building a system that already infers parameters, the LLM doesn’t need to generate these parameters - you can inject them directly.

This is where tool processing comes into play.

Schema processing

Handler to modify the tool schema, description, parameters before sending to an LLM.

Preprocessing

Handler to modify or inject parameters before executing a tool call.

Developers looking to improve agent reliability should analyze both the schema and outputs of tools, then use these processors to make interactions more deterministic where needed.

Schema processing

Say your system already infers the recipient_email parameter and you don’t want to send anattachment. You wouldn’t want the LLM to generate it in the tool call. You can simply modify the schema processor to do so:

1def gmail_schema_processor(schema: dict) -> dict:
2 del schema["recipient_email"]
3 del schema["attachment"]
4
5processed_send_email_tool = toolset.get_tools(
6 actions=[Action.GMAIL_SEND_EMAIL],
7 processors={
8 "schema": {Action.GMAIL_SEND_EMAIL: gmail_schema_processor},
9 },
10)

This removes the recipient_email and attachment parameters from the schema, preventing the LLM from generating them.

Preprocessing

But you still need to pass recipient_email to the tool execution so that handle_tool_calls can successfully send the email.

1def gmail_preprocessor(inputs: dict) -> dict:
2 inputs["recipient_email"] = "sam@openai.com"
3 return inputs
4
5processed_send_email_tool = toolset.get_tools(
6 actions=[Action.GMAIL_SEND_EMAIL],
7 processors={
8 "schema": {Action.GMAIL_SEND_EMAIL: gmail_schema_processor},
9 "pre": {Action.GMAIL_SEND_EMAIL: gmail_preprocessor},
10 },
11)
To avoid confusion between schema and preprocessing, think of schema processing as you changing the tool’s definition and preprocessing as a way to process the parameters before the tool is executed!

Putting it all together

1def gmail_preprocessor(inputs: dict) -> dict:
2 inputs["recipient_email"] = "sama@composio.dev" # Change to an email you can access to test!
3 return inputs
4
5
6def gmail_schema_processor(schema: dict) -> dict:
7 del schema["recipient_email"]
8 return schema
9
10
11processed_send_email_tool = toolset.get_tools(
12 actions=[Action.GMAIL_SEND_EMAIL],
13 processors={
14 "schema": {Action.GMAIL_SEND_EMAIL: gmail_schema_processor},
15 "pre": {Action.GMAIL_SEND_EMAIL: gmail_preprocessor},
16 },
17 check_connected_accounts=False,
18)
19
20response = client.chat.completions.create(
21 model="gpt-4o-mini",
22 messages=[
23 {"role": "system", "content": "You are head of AGI at OpenAI."},
24 {
25 "role": "user",
26 "content": "Send an email to Sam Altman in one sentence that AGI is coming 5 years earlier.",
27 },
28 ],
29 tools=processed_send_email_tool,
30)
31
32exec_response = toolset.handle_tool_calls(response)

Postprocessing

After executing a tool call, the system sends the response back to the LLM. However many APIs tend to return lots of data in their responses, making agentic flows unstable and eating up tokens.

We’ll deconstruct the working behind handle_tool_calls by using execute_action to directly call the GMAIL_FETCH_EMAILS tool.
1import tiktoken
2from composio_openai import ComposioToolset, Action
3
4def num_tokens_from_string(string: str, encoding_name: str) -> int:
5 encoding = tiktoken.get_encoding(encoding_name)
6 num_tokens = len(encoding.encode(string))
7 return num_tokens
8
9encoding = tiktoken.encoding_for_model("gpt-4o-mini")
10toolset = ComposioToolset()
11
12emails = toolset.execute_action(Action.GMAIL_FETCH_EMAILS, {})
13print(num_tokens_from_string(str(emails), encoding.name))
>>291344
This is already more than twice the context length of gpt-4o-mini (128k tokens)!

Postprocessing lets you filter what information from the tool execution reaches the LLM.

To filter only the subject and sender of the emails, create a postprocessor as follows:

1def gmail_postprocessor(result: dict) -> dict:
2 processed_result = result.copy()
3 processed_response = []
4 for email in result["data"]["messages"]:
5 processed_response.append(
6 {
7 "subject": email["subject"],
8 "sender": email["sender"],
9 }
10 )
11 processed_result["data"] = processed_response
12 return processed_result
13
14processed_emails = gmail_postprocessor(emails)
15print(num_tokens_from_string(str(processed_emails), encoding.name))

The postprocessor produces a more digestible response for the LLMs.

> 356
1[{'data': [{'subject': 'Update on AGI Development',
2 'sender': 'Greg Brockman <gdb@composio.dev>'},
3 {'subject': 'Security alert',
4 'sender': 'Google <no-reply@accounts.google.com>'},
5 {'subject': 'Hiddens Flaw in Most RAG Systems from ChromaDB, LlamaIndex, and Glean',
6 'sender': 'Jason Liu <work@jxnl.co>'},
7 {'subject': 'AGI, Inc. ❇️',
8 'sender': 'Cerebral Valley <cerebralvalley@mail.beehiiv.com>'}],
9 'error': None,
10 'successfull': True,
11 'successful': True}]

Putting it all together

1from openai import OpenAI
2from composio_openai import ComposioToolSet
3
4client = OpenAI()
5toolset = ComposioToolSet()
6
7def gmail_postprocessor(result: dict) -> dict:
8 processed_result = result.copy()
9 processed_response = []
10 for email in result["data"]["messages"]:
11 processed_response.append(
12 {
13 "subject": email["subject"],
14 "sender": email["sender"],
15 }
16 )
17 processed_result["data"] = processed_response
18 return processed_result
19
20processed_fetch_emails_tool = toolset.get_tools(
21 actions=[Action.GMAIL_FETCH_EMAILS],
22 processors={
23 "post": {Action.GMAIL_FETCH_EMAILS: gmail_postprocessor},
24 },
25)
26
27messages = [
28 {"role": "system", "content": "You are an Email Agent"},
29 {
30 "role": "user",
31 "content": "get me my emails and then answer who has sent me an email about agi?",
32 },
33]
34while True:
35 response = client.chat.completions.create(
36 model="gpt-4o-mini",
37 tools=processed_fetch_emails_tool,
38 messages=messages,
39 )
40 result = toolset.handle_tool_calls(response)
41 if response.choices[0].finish_reason != "tool_calls":
42 print(response.choices[0].message.content)
43 break
44 messages.append(
45 {
46 "role": "assistant",
47 "content": "",
48 "tool_calls": response.choices[0].message.tool_calls,
49 }
50 )
51 for tool_call in response.choices[0].message.tool_calls:
52 messages.append(
53 {
54 "role": "tool",
55 "content": str(result),
56 "tool_call_id": tool_call.id,
57 }
58 )
You have received two emails related to AGI:
1. **Email from gdb@composio.dev**
- Subject: Update on AGI Development
2. **Email from Cerebral Valley**
- Subject: AGI, Inc. - from MultiOn's Div Garg ❇️
Would you like to take any specific actions regarding these emails?
Built with