openrouter-function-calling
Standardizes tool and function calling workflows across multiple models via OpenRouter.
Install
mkdir -p .claude/skills/openrouter-function-calling && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/1954" && unzip -o skill.zip -d .claude/skills/openrouter-function-calling && rm skill.zipInstalls to .claude/skills/openrouter-function-calling
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Implement function/tool calling with OpenRouter models. Use when buildingKey capabilities
- →Define tools using JSON Schema for OpenRouter models.
- →Send tool definitions with chat completion requests.
- →Parse `tool_calls` from model responses to extract function names and arguments.
- →Implement a multi-turn tool loop for agent workflows.
- →Use structured output (JSON Mode) for structured data without function execution.
- →Handle errors related to tool calling, such as malformed JSON arguments.
How it works
The skill enables function calling with OpenRouter models by defining tools as JSON Schema, sending them with requests, and processing the model's `tool_calls` to execute functions and return results.
Inputs & outputs
When to use openrouter-function-calling
- →Build agent workflows with tools
- →Implement structured output with LLMs
- →Dispatch tool calls from OpenRouter models
- →Integrate function calling in Python applications
About this skill
OpenRouter Function Calling
Overview
OpenRouter supports OpenAI-compatible tool/function calling across multiple providers. Define tools as JSON Schema, send them with your request, and the model returns structured tool_calls instead of free text. This works with GPT-4o, Claude 3.5, Gemini, and other tool-capable models via the same API. The key difference from direct provider APIs: OpenRouter normalizes the tool calling interface, so the same code works across providers.
Prerequisites
- An OpenRouter API key (
sk-or-v1-...) exported asOPENROUTER_API_KEY— see theopenrouter-install-authskill for setup - Python 3.8+ or Node.js 18+ with the OpenAI SDK (
pip install openai/npm install openai) - A tool-capable model — check the Model Compatibility table below or query
/api/v1/models(e.g.,openai/gpt-4o,anthropic/claude-3.5-sonnet) - Real function implementations to dispatch tool calls to (the
execute_tool()dispatcher below stubsget_weatherandsearch_database)
Instructions
- Pick a model from the Model Compatibility table that supports the features you need (tool calling, JSON mode, parallel tools).
- Define your tools as JSON Schema per Basic Tool Calling and send them with
tool_choice="auto"(or"required"to force a call, or a specific function name). - Read
response.choices[0].message.tool_calls— each entry carriesfunction.nameand JSON-encodedfunction.argumentsto parse withjson.loads(). - For agents, wire the Multi-Turn Tool Loop: append the assistant message, execute each tool via
execute_tool(), appendrole: "tool"results keyed bytool_call_id, and loop until the model returns plain text (bounded bymax_rounds). - Use the TypeScript Tool Calling section for the identical flow in Node — same schema, same
tool_callsshape. - When you only need structured data (no function execution), skip tools and use Structured Output (JSON Mode) with
response_format={"type": "json_object"}. - Handle failures per the Error Handling table: force
tool_choice: "required"for extraction pipelines and validate arguments server-side before executing.
Basic Tool Calling
import os, json
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"},
)
# Define tools with JSON Schema
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "search_database",
"description": "Search the product database",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "integer", "default": 10},
},
"required": ["query"],
},
},
},
]
response = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet", # Also works with openai/gpt-4o, etc.
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
tools=tools,
tool_choice="auto", # "auto" | "required" | "none" | {"type":"function","function":{"name":"..."}}
max_tokens=1024,
)
message = response.choices[0].message
if message.tool_calls:
for tc in message.tool_calls:
print(f"Function: {tc.function.name}")
print(f"Args: {json.loads(tc.function.arguments)}")
# → Function: get_weather
# → Args: {"location": "Tokyo", "unit": "celsius"}
Multi-Turn Tool Loop
def tool_loop(user_prompt: str, tools: list, model: str = "openai/gpt-4o", max_rounds: int = 5):
"""Execute tool calls in a loop until the model returns a text response."""
messages = [{"role": "user", "content": user_prompt}]
for _ in range(max_rounds):
response = client.chat.completions.create(
model=model, messages=messages, tools=tools, max_tokens=1024,
)
msg = response.choices[0].message
messages.append(msg) # Add assistant message (with tool_calls)
if not msg.tool_calls:
return msg.content # Final text response
# Execute each tool call and feed results back
for tc in msg.tool_calls:
result = execute_tool(tc.function.name, json.loads(tc.function.arguments))
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result),
})
return "Max tool rounds exceeded"
def execute_tool(name: str, args: dict) -> dict:
"""Dispatch to actual function implementations."""
TOOLS = {
"get_weather": lambda **kw: {"temp": 22, "condition": "sunny", "location": kw["location"]},
"search_database": lambda **kw: {"results": [f"Product matching '{kw['query']}'"], "count": 1},
}
fn = TOOLS.get(name)
if not fn:
return {"error": f"Unknown tool: {name}"}
try:
return fn(**args)
except Exception as e:
return {"error": str(e)}
# Usage
result = tool_loop("What's the weather in Tokyo and find me umbrella products?", tools)
print(result)
TypeScript Tool Calling
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY,
defaultHeaders: { "HTTP-Referer": "https://my-app.com", "X-Title": "my-app" },
});
const tools: OpenAI.ChatCompletionTool[] = [
{
type: "function",
function: {
name: "calculate",
description: "Evaluate a math expression",
parameters: {
type: "object",
properties: { expression: { type: "string" } },
required: ["expression"],
},
},
},
];
const response = await client.chat.completions.create({
model: "openai/gpt-4o",
messages: [{ role: "user", content: "What is 42 * 17 + 3?" }],
tools,
tool_choice: "auto",
max_tokens: 512,
});
const toolCalls = response.choices[0].message.tool_calls;
if (toolCalls) {
for (const tc of toolCalls) {
const args = JSON.parse(tc.function.arguments);
console.log(`${tc.function.name}(${JSON.stringify(args)})`);
}
}
Structured Output (JSON Mode)
# Force JSON output without tool calling (simpler for extraction tasks)
response = client.chat.completions.create(
model="openai/gpt-4o",
messages=[
{"role": "system", "content": "Extract data as JSON with fields: name, email, company"},
{"role": "user", "content": "Contact Jane Smith at [email protected], she works at Acme Corp"},
],
response_format={"type": "json_object"},
max_tokens=200,
)
data = json.loads(response.choices[0].message.content)
# → {"name": "Jane Smith", "email": "[email protected]", "company": "Acme Corp"}
Model Compatibility
| Model | Tool Calling | JSON Mode | Parallel Tools |
|---|---|---|---|
openai/gpt-4o | Yes | Yes | Yes |
openai/gpt-4o-mini | Yes | Yes | Yes |
anthropic/claude-3.5-sonnet | Yes | Via system prompt | Sequential |
google/gemini-2.0-flash-001 | Yes | Yes | Yes |
meta-llama/llama-3.1-70b-instruct | Yes (varies) | Via prompt | No |
Output
The tool-calling flows produce:
message.tool_callsentries — each with afunction.nameand JSON-encodedfunction.arguments(e.g.,get_weatherwith{"location": "Tokyo", "unit": "celsius"}) plus atool_call_idfor pairing results- The final assistant text once the Multi-Turn Tool Loop resolves — or the
"Max tool rounds exceeded"sentinel if it hitsmax_rounds - From JSON Mode: a parseable JSON object matching your system-prompt schema (e.g.,
{"name": "Jane Smith", "email": "[email protected]", "company": "Acme Corp"})
Examples
Asking a weather question with the get_weather tool registered:
message = response.choices[0].message
for tc in message.tool_calls:
print(tc.function.name, json.loads(tc.function.arguments))
# get_weather {'location': 'Tokyo', 'unit': 'celsius'}
Feed that result back as a role: "tool" message and the next completion returns prose ("It's currently 22°C and sunny in Tokyo..."). More worked examples: references/examples.md.
Error Handling
| Error | Cause | Fix |
|---|---|---|
tool_calls is null | Model chose not to call tools | Use tool_choice: "required" to force tool use |
| JSON parse error on arguments | Model generated malformed JSON | Wrap in try/catch; retry or use more capable model |
| 400 invalid tool schema | Unsupported JSON Schema types | Stick to basic types (string, number, boolean, object, array) |
| Tool called with wrong args | Schema description unclear | Improve parameter descriptions; add examples in description |
Enterprise Considerations
- Not all models support tool calling -- check model capabilities via
/api/v1/modelsbefore sending tools - Use
tool_choice: "required"when you must get a tool call (e.g., extraction pipelines) - Validate tool arguments server-side before executing -- models can hallucinate argument values
- Set
max_tokensto prevent expensive completion when model decides not to use tools - Use fallback chain with tool-capable models only (see openrouter-fallback-config)
- Log tool call names and arguments for audit trails (redact sensitive args)
References
- Examples | Errors
- Tool/Function Calling | [API Reference](https://openrouter.ai/do
Content truncated.
When not to use it
- →When the model chosen does not support tool calling.
- →When `max_tokens` is not set, potentially leading to expensive completions.
- →When tool arguments are not validated server-side before execution.
Prerequisites
Limitations
- →Not all models support tool calling.
- →Models can hallucinate argument values, requiring server-side validation.
- →Setting `max_tokens` is necessary to prevent expensive completions when tools are not used.
How it compares
This skill normalizes the tool calling interface across various LLM providers via OpenRouter, allowing the same code to work with different models, unlike direct provider APIs which may have varying implementations.
Compared to similar skills
openrouter-function-calling side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| openrouter-function-calling (this skill) | 5 | 27d | Review | Intermediate |
| mcp-builder | 136 | 3mo | Review | Advanced |
| copilot-sdk | 7 | 4mo | Review | Intermediate |
| openai-knowledge | 5 | 4mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by jeremylongshore
View all by jeremylongshore →You might also like
mcp-builder
anthropics
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
copilot-sdk
github
Build agentic applications with GitHub Copilot SDK. Use when embedding AI agents in apps, creating custom tools, implementing streaming responses, managing sessions, connecting to MCP servers, or creating custom agents. Triggers on Copilot SDK, GitHub SDK, agentic app, embed Copilot, programmable agent, MCP server, custom agent.
openai-knowledge
openai
Use when working with the OpenAI API (Responses API) or OpenAI platform features (tools, streaming, Realtime API, auth, models, rate limits, MCP) and you need authoritative, up-to-date documentation (schemas, examples, limits, edge cases). Prefer the OpenAI Developer Documentation MCP server tools when available; otherwise guide the user to enable `openaiDeveloperDocs`.
agentic-development
alinaqi
Build AI agents with Pydantic AI (Python) and Claude SDK (Node.js)
windsurf-mcp-integration
jeremylongshore
Manage integrate MCP servers with Windsurf for extended capabilities. Activate when users mention "mcp integration", "model context protocol", "external tools", "mcp server", or "cascade tools". Handles MCP server configuration and integration. Use when working with windsurf mcp integration functionality. Trigger with phrases like "windsurf mcp integration", "windsurf integration", "windsurf".
openrouter-openai-compat
jeremylongshore
Configure OpenRouter as an OpenAI API drop-in replacement. Use when migrating from OpenAI or using OpenAI-compatible libraries. Trigger with phrases like 'openrouter openai', 'openrouter drop-in', 'openrouter compatibility', 'migrate to openrouter'.