Skip to main content
Use Cases
  • Connecting models to live data (databases, calendars, internal APIs) without prompt hacks.
  • Agents that take actions on behalf of users: create tickets, send emails, run queries.
  • Multi-step workflows where the model decides which tools to invoke and in what order.
  • Replacing brittle regex parsing with structured function calls for data extraction.

Quick Start

Enable AI models to call external functions with structured parameters.
curl -X POST https://api.orq.ai/v3/router/responses \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4",
    "input": "What is the weather in NYC?",
    "tools": [{
      "type": "function",
      "name": "get_weather",
      "description": "Get current weather for a location",
      "parameters": {
        "type": "object",
        "properties": {
          "location": { "type": "string", "description": "City and state" },
          "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
        },
        "required": ["location"]
      }
    }],
    "tool_choice": "auto"
  }'
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.ORQ_API_KEY,
  baseURL: "https://api.orq.ai/v3/router",
});

async function getWeather(location: string, unit = "celsius") {
  // Your implementation here - fetch from a weather API, etc.
  return { location, temperature: 22, unit, conditions: "sunny" };
}

const tools = [
  {
    type: "function" as const,
    name: "get_weather",
    description: "Get current weather for a location",
    parameters: {
      type: "object",
      properties: {
        location: { type: "string", description: "City and state" },
        unit: { type: "string", enum: ["celsius", "fahrenheit"] },
      },
      required: ["location"],
    },
  },
];

const response = await client.responses.create({
  model: "openai/gpt-5.4",
  input: "What's the weather in NYC?",
  tools,
  tool_choice: "auto",
});

const toolCall = response.output.find((item) => item.type === "function_call");
if (toolCall && toolCall.type === "function_call") {
  const args = JSON.parse(toolCall.arguments);
  const result = await getWeather(args.location, args.unit);
  const callId = toolCall.call_id;

  // Pattern: previous_response_id - the router maintains conversation state server-side
  const finalResponse = await client.responses.create({
    model: "openai/gpt-5.4",
    previous_response_id: response.id,
    input: [{
      type: "function_call_output",
      call_id: callId,
      output: JSON.stringify(result),
    }],
  });

  console.log(finalResponse.output_text);
}
from openai import OpenAI
import json
import os

client = OpenAI(
    api_key=os.environ.get("ORQ_API_KEY"),
    base_url="https://api.orq.ai/v3/router",
)

def get_weather(location: str, unit: str = "celsius") -> dict:
    # Your implementation here - fetch from a weather API, etc.
    return {"location": location, "temperature": 22, "unit": unit, "conditions": "sunny"}

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get current weather for a location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {"type": "string", "description": "City and state"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
            },
            "required": ["location"],
        },
    }
]

response = client.responses.create(
    model="openai/gpt-5.4",
    input="What's the weather in NYC?",
    tools=tools,
    tool_choice="auto",
)

tool_call = next((item for item in response.output if item.type == "function_call"), None)
if tool_call:
    args = json.loads(tool_call.arguments)
    result = get_weather(args["location"], args.get("unit", "celsius"))

    # Pattern: previous_response_id - the router maintains conversation state server-side
    final_response = client.responses.create(
        model="openai/gpt-5.4",
        previous_response_id=response.id,
        input=[{
            "type": "function_call_output",
            "call_id": tool_call.call_id,
            "output": json.dumps(result),
        }],
    )

    print(final_response.output_text)
curl -X POST https://api.orq.ai/v3/router/chat/completions \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4",
    "messages": [{"role": "user", "content": "What is the weather in NYC?"}],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get current weather for a location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": { "type": "string", "description": "City and state" },
            "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
          },
          "required": ["location"]
        }
      }
    }],
    "tool_choice": "auto"
  }'
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.ORQ_API_KEY,
  baseURL: "https://api.orq.ai/v3/router",
});

const tools = [
  {
    type: "function" as const,
    function: {
      name: "get_weather",
      description: "Get current weather for a location",
      parameters: {
        type: "object",
        properties: {
          location: { type: "string", description: "City and state" },
          unit: { type: "string", enum: ["celsius", "fahrenheit"] },
        },
        required: ["location"],
      },
    },
  },
];

async function getWeather(location: string, unit = "celsius") {
  // Your implementation here - fetch from a weather API, etc.
  return { location, temperature: 22, unit, conditions: "sunny" };
}

const response = await client.chat.completions.create({
  model: "openai/gpt-5.4",
  messages: [{ role: "user", content: "What's the weather in NYC?" }],
  tools,
  tool_choice: "auto",
});

if (response.choices[0].message.tool_calls) {
  const toolCall = response.choices[0].message.tool_calls[0];
  const args = JSON.parse(toolCall.function.arguments);
  const result = await getWeather(args.location, args.unit);

  const finalResponse = await client.chat.completions.create({
    model: "openai/gpt-5.4",
    messages: [
      { role: "user", content: "What's the weather in NYC?" },
      response.choices[0].message,
      {
        role: "tool",
        tool_call_id: toolCall.id,
        content: JSON.stringify(result),
      },
    ],
  });

  console.log(finalResponse.choices[0].message.content);
}
from openai import OpenAI
import json
import os

client = OpenAI(
    api_key=os.environ.get("ORQ_API_KEY"),
    base_url="https://api.orq.ai/v3/router",
)

def get_weather(location: str, unit: str = "celsius") -> dict:
    # Your implementation here - fetch from a weather API, etc.
    return {"location": location, "temperature": 22, "unit": unit, "conditions": "sunny"}

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {"type": "string", "description": "City and state"},
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                },
                "required": ["location"],
            },
        },
    }
]

messages = [{"role": "user", "content": "What's the weather in NYC?"}]

response = client.chat.completions.create(
    model="openai/gpt-5.4",
    messages=messages,
    tools=tools,
    tool_choice="auto",
)

if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]
    args = json.loads(tool_call.function.arguments)
    result = get_weather(args["location"], args.get("unit", "celsius"))

    messages.append(response.choices[0].message)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": json.dumps(result),
    })

    final_response = client.chat.completions.create(
        model="openai/gpt-5.4",
        messages=messages,
    )

    print(final_response.choices[0].message.content)

Configuration

Tool Definition

Responses API (/v3/router/responses): flat shape:
ParameterTypeRequiredDescription
type"function"YesTool type
namestringYesFunction identifier
descriptionstringYesWhat the function does
parametersobjectYesJSON Schema for parameters
Chat Completions (/v3/router/chat/completions): nested function wrapper:
ParameterTypeRequiredDescription
type"function"YesTool type
function.namestringYesFunction identifier
function.descriptionstringYesWhat the function does
function.parametersobjectYesJSON Schema for parameters

Tool Choice Options

ValueBehavior
"auto"Model decides when to use tools
"none"Disable tool usage
"required"Force tool usage
{type: "function", function: {name: "tool_name"}}Force specific tool

Tool Message Format

This format applies to the Chat Completions endpoint (/v3/router/chat/completions). On the Responses API, tool results use type: "function_call_output", call_id, and output instead.
When providing tool results back to the model, use the tool role:
ParameterTypeRequiredDescription
role"tool"YesMessage role for tool results
tool_call_idstring | nullYesID of the tool call being responded to
contentstringYesJSON-stringified result of the tool execution
The tool_call_id can be null in certain scenarios, such as when tool results are being provided without a corresponding tool call from the model, or when working with providers that don’t require tool call IDs.

Code examples

curl -X POST https://api.orq.ai/v3/router/responses \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4",
    "input": "What is the weather like in San Francisco?",
    "tools": [
      {
        "type": "function",
        "name": "get_weather",
        "description": "Get the current weather for a location",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {
              "type": "string",
              "description": "The city and state, e.g. San Francisco, CA"
            },
            "unit": {
              "type": "string",
              "enum": ["celsius", "fahrenheit"],
              "description": "The temperature unit"
            }
          },
          "required": ["location"]
        }
      }
    ],
    "tool_choice": "auto"
  }'
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.ORQ_API_KEY,
  baseURL: "https://api.orq.ai/v3/router",
});

const tools = [
  {
    type: "function" as const,
    name: "get_weather",
    description: "Get the current weather for a location",
    parameters: {
      type: "object",
      properties: {
        location: {
          type: "string",
          description: "The city and state, e.g. San Francisco, CA",
        },
        unit: {
          type: "string",
          enum: ["celsius", "fahrenheit"],
          description: "The temperature unit",
        },
      },
      required: ["location"],
    },
  },
];

const response = await client.responses.create({
  model: "openai/gpt-5.4",
  input: "What's the weather like in San Francisco?",
  tools,
  tool_choice: "auto",
});

const toolCall = response.output.find((item) => item.type === "function_call");
if (toolCall && toolCall.type === "function_call") {
  const args = JSON.parse(toolCall.arguments);

  const weatherResult = {
    temperature: 72,
    unit: "fahrenheit",
    description: "Sunny with light clouds",
  };

  // Pattern: spread response.output - the full conversation history is sent client-side
  const finalResponse = await client.responses.create({
    model: "openai/gpt-5.4",
    input: [
      ...response.output,
      {
        type: "function_call_output",
        call_id: toolCall.call_id,
        output: JSON.stringify(weatherResult),
      },
    ],
  });

  console.log(finalResponse.output_text);
}
from openai import OpenAI
import json
import os

client = OpenAI(
    api_key=os.environ.get("ORQ_API_KEY"),
    base_url="https://api.orq.ai/v3/router",
)

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get the current weather for a location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "The city and state, e.g. San Francisco, CA",
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "The temperature unit",
                },
            },
            "required": ["location"],
        },
    }
]

response = client.responses.create(
    model="openai/gpt-5.4",
    input="What's the weather like in San Francisco?",
    tools=tools,
    tool_choice="auto",
)

tool_call = next((item for item in response.output if item.type == "function_call"), None)
if tool_call:
    arguments = json.loads(tool_call.arguments)

    weather_result = {
        "temperature": 72,
        "unit": "fahrenheit",
        "description": "Sunny with light clouds",
    }

    final_response = client.responses.create(
        model="openai/gpt-5.4",
        input=[
            *response.output,
            {
                "type": "function_call_output",
                "call_id": tool_call.call_id,
                "output": json.dumps(weather_result),
            },
        ],
    )

    print(final_response.output_text)
curl -X POST https://api.orq.ai/v3/router/chat/completions \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4",
    "messages": [
      {
        "role": "user",
        "content": "What is the weather like in San Francisco?"
      }
    ],
    "tools": [
      {
        "type": "function",
        "function": {
          "name": "get_weather",
          "description": "Get the current weather for a location",
          "parameters": {
            "type": "object",
            "properties": {
              "location": {
                "type": "string",
                "description": "The city and state, e.g. San Francisco, CA"
              },
              "unit": {
                "type": "string",
                "enum": ["celsius", "fahrenheit"],
                "description": "The temperature unit"
              }
            },
            "required": ["location"]
          }
        }
      }
    ],
    "tool_choice": "auto"
  }'
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.ORQ_API_KEY,
  baseURL: "https://api.orq.ai/v3/router",
});

const tools = [
  {
    type: "function" as const,
    function: {
      name: "get_weather",
      description: "Get the current weather for a location",
      parameters: {
        type: "object",
        properties: {
          location: {
            type: "string",
            description: "The city and state, e.g. San Francisco, CA",
          },
          unit: {
            type: "string",
            enum: ["celsius", "fahrenheit"],
            description: "The temperature unit",
          },
        },
        required: ["location"],
      },
    },
  },
];

const messages = [
  { role: "user" as const, content: "What's the weather like in San Francisco?" },
];

const response = await client.chat.completions.create({
  model: "openai/gpt-5.4",
  messages,
  tools,
  tool_choice: "auto",
});

if (response.choices[0].message.tool_calls) {
  const toolCall = response.choices[0].message.tool_calls[0];
  const args = JSON.parse(toolCall.function.arguments);

  const weatherResult = {
    temperature: 72,
    unit: "fahrenheit",
    description: "Sunny with light clouds",
  };

  messages.push(response.choices[0].message);
  messages.push({
    role: "tool" as const,
    tool_call_id: toolCall.id,
    content: JSON.stringify(weatherResult),
  });

  const finalResponse = await client.chat.completions.create({
    model: "openai/gpt-5.4",
    messages,
  });

  console.log(finalResponse.choices[0].message.content);
}
from openai import OpenAI
import json
import os

client = OpenAI(
    api_key=os.environ.get("ORQ_API_KEY"),
    base_url="https://api.orq.ai/v3/router",
)

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g. San Francisco, CA",
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "The temperature unit",
                    },
                },
                "required": ["location"],
            },
        },
    }
]

messages = [{"role": "user", "content": "What's the weather like in San Francisco?"}]

response = client.chat.completions.create(
    model="openai/gpt-5.4",
    messages=messages,
    tools=tools,
    tool_choice="auto",
)

if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0]
    arguments = json.loads(tool_call.function.arguments)

    weather_result = {
        "temperature": 72,
        "unit": "fahrenheit",
        "description": "Sunny with light clouds",
    }

    messages.append(response.choices[0].message)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,
        "content": json.dumps(weather_result),
    })

    final_response = client.chat.completions.create(
        model="openai/gpt-5.4",
        messages=messages,
    )

    print(final_response.choices[0].message.content)

Function Execution Patterns

Basic Tool Handler

A registry that maps tool names to handler functions, replacing per-call switch statements with a single dispatch path.
class ToolHandler {
  constructor() {
    this.tools = new Map();
  }

  register(name, func, schema) {
    this.tools.set(name, { func, schema });
  }

  async execute(toolCall) {
    const tool = this.tools.get(toolCall.function.name);
    if (!tool) throw new Error(`Unknown tool: ${toolCall.function.name}`);

    const args = JSON.parse(toolCall.function.arguments);
    const result = await tool.func(args);

    return {
      role: "tool",
      tool_call_id: toolCall.id,
      content: JSON.stringify(result),
    };
  }
}

// Stubs - replace with your actual implementations
const weatherSchema = { type: "object", properties: { location: { type: "string" } }, required: ["location"] };
const searchSchema = { type: "object", properties: { query: { type: "string" } }, required: ["query"] };
const getWeatherAPI = async ({ location }) => ({ temperature: 72, condition: "sunny", location });
const searchWebAPI = async ({ query }) => ({ results: [`Result for: ${query}`] });
const response = { choices: [{ message: { tool_calls: [] } }] };

const handler = new ToolHandler();
handler.register("get_weather", getWeatherAPI, weatherSchema);
handler.register("search_web", searchWebAPI, searchSchema);

// Execute tool calls
const toolResults = await Promise.all(
  response.choices[0].message.tool_calls.map((call) => handler.execute(call)),
);
import json
import asyncio

async def get_weather_api(args: dict) -> dict: return {"temperature": 72, "condition": "sunny", "location": args["location"]}  # replace with your weather API
async def search_web_api(args: dict) -> dict: return {"results": [f"Result for: {args['query']}"]}  # replace with your search API

class ToolHandler:
    def __init__(self):
        self._tools = {}

    def register(self, name: str, func):
        self._tools[name] = func

    async def execute(self, tool_call) -> dict:
        func = self._tools.get(tool_call.function.name)
        if not func:
            raise ValueError(f"Unknown tool: {tool_call.function.name}")
        args = json.loads(tool_call.function.arguments)
        result = await func(args)
        return {"role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result)}

handler = ToolHandler()
handler.register("get_weather", get_weather_api)
handler.register("search_web", search_web_api)

async def execute_all_tools(message):
    return await asyncio.gather(*[handler.execute(call) for call in message.tool_calls])

Parallel Tool Execution

When the model returns multiple tool calls in one turn, execute them concurrently to reduce latency.
async function getWeatherAsync(args: Record<string, unknown>) { return { temperature: 72, condition: "sunny" }; } // replace with your weather API
async function searchProductsAsync(args: Record<string, unknown>) { return { products: [] as unknown[] }; } // replace with your product search
async function checkInventoryAsync(args: Record<string, unknown>) { return { inStock: true }; } // replace with your inventory API

// Replace with your actual response and messages array
const response = { choices: [{ message: { role: "assistant" as const, content: null as string | null, tool_calls: [] as Array<{ function: { name: string; arguments: string }; id: string }> } }] }; // e.g., client.chat.completions.create(...)
let messages: Array<{ role: string; tool_call_id?: string; content: string | null }> = [];

const dispatch: Record<string, (args: Record<string, unknown>) => Promise<unknown>> = {
  get_weather: getWeatherAsync,
  search_products: searchProductsAsync,
  check_inventory: checkInventoryAsync,
};

async function executeToolsParallel(
  toolCalls: Array<{ function: { name: string; arguments: string }; id: string }>,
) {
  return Promise.all(
    toolCalls.map(async (call) => {
      const args = JSON.parse(call.function.arguments) as Record<string, unknown>;
      const fn = dispatch[call.function.name] ?? (() => Promise.resolve({ error: `Unknown: ${call.function.name}` }));
      const result = await fn(args);
      return { role: "tool" as const, tool_call_id: call.id, content: JSON.stringify(result) };
    }),
  );
}

if (response.choices[0].message.tool_calls) {
  messages.push(response.choices[0].message);
  const toolResults = await executeToolsParallel(response.choices[0].message.tool_calls);
  messages = [...messages, ...toolResults];
}
import asyncio
import json

async def get_weather_async(args): return {"temperature": 72, "condition": "sunny"}  # replace with your weather API
async def search_products_async(args): return {"products": []}  # replace with your product search
async def check_inventory_async(args): return {"in_stock": True}  # replace with your inventory API

# Replace these with your actual API call and conversation history
response = None  # e.g. client.chat.completions.create(model=..., messages=messages, tools=[...])
messages: list = []

async def execute_tools_parallel(tool_calls):
    async def execute_single_tool(tool_call):
        function_name = tool_call.function.name
        arguments = json.loads(tool_call.function.arguments)

        # Route to appropriate function
        if function_name == "get_weather":
            result = await get_weather_async(arguments)
        elif function_name == "search_products":
            result = await search_products_async(arguments)
        elif function_name == "check_inventory":
            result = await check_inventory_async(arguments)
        else:
            result = {"error": f"Unknown function: {function_name}"}

        return {
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result)
        }

    # Execute all tools concurrently
    results = await asyncio.gather(
        *[execute_single_tool(call) for call in tool_calls]
    )

    return results

# Usage
if response.choices[0].message.tool_calls:
    messages.append(response.choices[0].message)
    tool_results = await execute_tools_parallel(
        response.choices[0].message.tool_calls
    )

    # Add to conversation
    messages.extend(tool_results)

Advanced Use Cases

Database Integration

Expose SQL access as a tool so the model can query data directly. Always sanitize queries before execution.
const sanitizeSql = (q: string) => q; // replace with your SQL sanitizer
const getDbConnection = () => null as unknown as { execute: (q: string) => Promise<{ fetchAll: () => unknown[] }> }; // replace with your DB client

const db = getDbConnection();

const tools = [
  {
    type: "function" as const,
    function: {
      name: "query_database",
      description: "Query the customer database",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "SQL query to execute" },
          limit: { type: "integer", description: "Maximum number of results" },
        },
        required: ["query"],
      },
    },
  },
];

async function queryDatabase(args: { query: string; limit?: number }) {
  const query = sanitizeSql(args.query);
  const limit = args.limit ?? 10;
  const results = await db.execute(`${query} LIMIT ${limit}`); // limit is integer-typed in the schema, safe to interpolate directly
  return { results: results.fetchAll() };
}
sanitize_sql = lambda q: q  # replace with your SQL sanitizer

def get_db_connection():
    pass  # replace with your database client, e.g. from myapp.db import get_db_connection

db = get_db_connection()

tools = [
    {
        "type": "function",
        "function": {
            "name": "query_database",
            "description": "Query the customer database",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "SQL query to execute"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "Maximum number of results"
                    }
                },
                "required": ["query"]
            }
        }
    }
]

async def query_database(args):
    query = sanitize_sql(args["query"])
    limit = args.get("limit", 10)

    results = await db.execute(f"{query} LIMIT {limit}")
    return {"results": results.fetchall()}

API Integration

Expose external service actions (email, calendar, notifications) as tools the model can invoke during a conversation.
const apiTools = [
  {
    type: "function",
    function: {
      name: "send_email",
      description: "Send an email to a recipient",
      parameters: {
        type: "object",
        properties: {
          to: { type: "string", description: "Email address" },
          subject: { type: "string", description: "Email subject" },
          body: { type: "string", description: "Email content" },
        },
        required: ["to", "subject", "body"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_calendar_event",
      description: "Create a calendar event",
      parameters: {
        type: "object",
        properties: {
          title: { type: "string" },
          start_time: { type: "string", format: "date-time" },
          duration: { type: "integer", description: "Duration in minutes" },
          attendees: { type: "array", items: { type: "string" } },
        },
        required: ["title", "start_time"],
      },
    },
  },
];

const emailAPI = { send: async (args: Record<string, unknown>) => ({ messageId: "msg_001" }) }; // replace with your email client
const calendarAPI = { createEvent: async (args: Record<string, unknown>) => ({ eventId: "evt_001" }) }; // replace with your calendar client

const executeApiTool = async (toolCall) => {
  const { name } = toolCall.function;
  const args = JSON.parse(toolCall.function.arguments);

  switch (name) {
    case "send_email":
      return await emailAPI.send(args);
    case "create_calendar_event":
      return await calendarAPI.createEvent(args);
    default:
      throw new Error(`Unknown API tool: ${name}`);
  }
};
import json

# Replace with your actual API clients
class EmailAPI:
    async def send(self, args: dict) -> dict: return {"message_id": "msg_001"}

class CalendarAPI:
    async def create_event(self, args: dict) -> dict: return {"event_id": "evt_001"}

email_api = EmailAPI()
calendar_api = CalendarAPI()

api_tools = [
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "Send an email to a recipient",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string", "description": "Email address"},
                    "subject": {"type": "string", "description": "Email subject"},
                    "body": {"type": "string", "description": "Email content"},
                },
                "required": ["to", "subject", "body"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "create_calendar_event",
            "description": "Create a calendar event",
            "parameters": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "start_time": {"type": "string", "format": "date-time"},
                    "duration": {"type": "integer", "description": "Duration in minutes"},
                    "attendees": {"type": "array", "items": {"type": "string"}},
                },
                "required": ["title", "start_time"],
            },
        },
    },
]

async def execute_api_tool(tool_call) -> dict:
    args = json.loads(tool_call.function.arguments)
    match tool_call.function.name:
        case "send_email":
            return await email_api.send(args)
        case "create_calendar_event":
            return await calendar_api.create_event(args)
        case _:
            raise ValueError(f"Unknown API tool: {tool_call.function.name}")

Multi-Steps Workflows

Run the model in a loop until it produces a final text response, enabling multi-step agent behavior without streaming.
import { OpenAI } from "openai";
import type { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources";

const client = new OpenAI({
  apiKey: process.env.ORQ_API_KEY,
  baseURL: "https://api.orq.ai/v3/router",
});

class WorkflowEngine {
  private tools: Record<string, { func: (args: Record<string, unknown>) => Promise<unknown>; schema: ChatCompletionTool }> = {};

  register(name: string, func: (args: Record<string, unknown>) => Promise<unknown>, schema: ChatCompletionTool) {
    this.tools[name] = { func, schema };
  }

  async executeWorkflow(initialPrompt: string, maxSteps = 10): Promise<string> {
    const conversation: ChatCompletionMessageParam[] = [{ role: "user", content: initialPrompt }];

    for (let step = 0; step < maxSteps; step++) {
      const response = await client.chat.completions.create({
        model: "openai/gpt-5.4",
        messages: conversation,
        tools: Object.values(this.tools).map((t) => t.schema),
        tool_choice: "auto",
      });

      conversation.push(response.choices[0].message);

      if (!response.choices[0].message.tool_calls) {
        return response.choices[0].message.content ?? "";
      }

      for (const call of response.choices[0].message.tool_calls) {
        conversation.push(await this.executeTool(call));
      }
    }
    return "Workflow exceeded maximum steps";
  }

  private async executeTool(toolCall: { function: { name: string; arguments: string }; id: string }): Promise<ChatCompletionMessageParam> {
    const args = JSON.parse(toolCall.function.arguments) as Record<string, unknown>;
    const tool = this.tools[toolCall.function.name];
    const result = tool ? await tool.func(args) : { error: `Unknown tool: ${toolCall.function.name}` };
    return { role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(result) };
  }
}
from openai import AsyncOpenAI
import os
import json

client = AsyncOpenAI(
    api_key=os.environ.get("ORQ_API_KEY"),
    base_url="https://api.orq.ai/v3/router",
)

class WorkflowEngine:
    def __init__(self):
        self.tools = {}
        self.conversation = []

    def register_tool(self, name, func, schema):
        self.tools[name] = {"func": func, "schema": schema}

    async def execute_workflow(self, initial_prompt, max_steps=10):
        self.conversation = [{"role": "user", "content": initial_prompt}]

        for step in range(max_steps):
            response = await client.chat.completions.create(
                model="openai/gpt-5.4",
                messages=self.conversation,
                tools=[v["schema"] for v in self.tools.values()],
                tool_choice="auto"
            )

            self.conversation.append(response.choices[0].message)

            # Check if tools need to be executed
            if response.choices[0].message.tool_calls:
                for tool_call in response.choices[0].message.tool_calls:
                    result = await self.execute_tool(tool_call)
                    self.conversation.append(result)
            else:
                # No tools called, workflow complete
                return response.choices[0].message.content

        return "Workflow exceeded maximum steps"

    async def execute_tool(self, tool_call):
        tool_name = tool_call.function.name
        arguments = json.loads(tool_call.function.arguments)

        if tool_name in self.tools:
            result = await self.tools[tool_name]["func"](arguments)
        else:
            result = {"error": f"Unknown tool: {tool_name}"}

        return {
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result)
        }

Error Handling

Return structured error objects in tool outputs so the model can report failures or retry with corrected arguments.
const validateArgs = (name: string, args: Record<string, unknown>) => ({ valid: true, errors: [] as string[] }); // replace with your validator
const executeFunction = async (name: string, args: Record<string, unknown>): Promise<unknown> => ({}); // replace with your dispatcher

const safeToolExecution = async (toolCall) => {
  try {
    const args = JSON.parse(toolCall.function.arguments);

    // Validate arguments
    const validation = validateArgs(toolCall.function.name, args);
    if (!validation.valid) {
      return {
        role: "tool",
        tool_call_id: toolCall.id,
        content: JSON.stringify({
          error: "Invalid arguments",
          details: validation.errors,
        }),
      };
    }

    // Execute with timeout
    const result = await Promise.race([
      executeFunction(toolCall.function.name, args),
      new Promise((_, reject) =>
        setTimeout(() => reject(new Error("Tool execution timeout")), 30000),
      ),
    ]);

    return {
      role: "tool",
      tool_call_id: toolCall.id,
      content: JSON.stringify(result),
    };
  } catch (error) {
    console.error(`Tool execution failed: ${error.message}`);

    return {
      role: "tool",
      tool_call_id: toolCall.id,
      content: JSON.stringify({
        error: "Tool execution failed",
        message: error.message,
      }),
    };
  }
};
import json
import asyncio

def validate_args(name: str, args: dict) -> dict: return {"valid": True, "errors": []}  # replace with your validator
async def execute_function(name: str, args: dict): return {}  # replace with your dispatcher

async def safe_tool_execution(tool_call) -> dict:
    try:
        args = json.loads(tool_call.function.arguments)

        validation = validate_args(tool_call.function.name, args)
        if not validation["valid"]:
            return {
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps({"error": "Invalid arguments", "details": validation["errors"]}),
            }

        result = await asyncio.wait_for(
            execute_function(tool_call.function.name, args),
            timeout=30,
        )
        return {"role": "tool", "tool_call_id": tool_call.id, "content": json.dumps(result)}
    except Exception as e:
        return {
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps({"error": "Tool execution failed", "message": str(e)}),
        }

Best Practices

Tool design

  • Use clear, descriptive function names.
  • Provide detailed parameter descriptions.
  • Include examples in descriptions.
  • Make tools idempotent when possible.

Schema design

{
  "type": "object",
  "properties": {
    "location": {
      "type": "string",
      "description": "City and state (e.g., 'San Francisco, CA')",
      "examples": ["New York, NY", "London, UK"]
    },
    "units": {
      "type": "string",
      "enum": ["metric", "imperial"],
      "description": "Temperature unit system",
      "default": "metric"
    }
  },
  "required": ["location"]
}

Security considerations

  • Never expose destructive operations directly.
  • Validate all inputs thoroughly.
  • Use allowlists for sensitive operations.
  • Implement proper authentication.
  • Log all tool executions.

Troubleshooting

Tool not being called
  • Check tool descriptions are clear.
  • Verify parameter schemas are correct.
  • Ensure tool_choice is set appropriately.
  • Try more explicit prompts. Invalid arguments
  • Validate JSON Schema thoroughly.
  • Add parameter examples.
  • Check required fields are marked.
  • Simplify complex parameter structures.
Execution failures
  • Implement proper error handling.
  • Add timeout protection.
  • Validate inputs before execution.
  • Return structured error messages.

Limitations

LimitationImpactWorkaround
Tool limitMax ~20 tools per requestGroup related functions
Parameter sizeLarge schemas may failSimplify parameter structure
Execution timeTools block responseUse async patterns
Error propagationFailures can break workflowImplement error recovery
Model differencesVarying tool calling qualityTest across models

Performance Optimization

Tool caching

Cache results from read-only tools to avoid redundant external calls within a session.
class CachedToolExecutor {
  constructor() {
    this.cache = new Map();
    this.cacheTTL = 300000; // 5 minutes
  }

  getCacheKey(toolCall) {
    return `${toolCall.function.name}:${toolCall.function.arguments}`;
  }

  async executeFunction(toolCall) {
    throw new Error("executeFunction must be implemented in a subclass");
  }

  async execute(toolCall) {
    const key = this.getCacheKey(toolCall);
    const cached = this.cache.get(key);

    if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
      return cached.result;
    }

    const result = await this.executeFunction(toolCall);
    this.cache.set(key, { result, timestamp: Date.now() });

    return result;
  }
}
import time
import json

class CachedToolExecutor:
    def __init__(self, cache_ttl: int = 300):
        self._cache: dict = {}
        self._cache_ttl = cache_ttl

    def _cache_key(self, tool_call) -> str:
        return f"{tool_call.function.name}:{tool_call.function.arguments}"

    async def execute_function(self, tool_call) -> dict:
        raise NotImplementedError("Implement in subclass")

    async def execute(self, tool_call) -> dict:
        key = self._cache_key(tool_call)
        entry = self._cache.get(key)
        if entry and (time.time() - entry["timestamp"]) < self._cache_ttl:
            return entry["result"]
        result = await self.execute_function(tool_call)
        self._cache[key] = {"result": result, "timestamp": time.time()}
        return result

Batch operations

Accept arrays of inputs in a single tool to reduce the number of model turns required for bulk operations.
const getWeather = (location: string) => ({ temperature: 72, condition: "sunny", location }); // replace with your weather API

const getWeatherBatch = (locations: string[]) =>
  Object.fromEntries(locations.map((loc) => [loc, getWeather(loc)]));

const getWeatherBatchTool = {
  type: "function" as const,
  name: "get_weather_batch",
  description: "Get weather for multiple locations in a single call",
  parameters: {
    type: "object",
    properties: {
      locations: { type: "array", items: { type: "string" }, description: "List of city names" },
    },
    required: ["locations"],
  },
};
# Pattern fragment: add client setup and get_weather stub before using
# Instead of multiple individual calls
def get_weather_batch(locations):
    return {loc: get_weather(loc) for loc in locations}

# Tool that accepts multiple inputs
get_weather_batch_tool = {
    "type": "function",
    "name": "get_weather_batch",
    "description": "Get weather for multiple locations in a single call",
    "parameters": {
        "type": "object",
        "properties": {
            "locations": {
                "type": "array",
                "items": {"type": "string"}
            }
        },
        "required": ["locations"]
    }
}