OP

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.zip

Installs 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 building
73 chars✓ has a “when” trigger
Intermediate

Key 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

You give it
User prompt, tool definitions (JSON Schema), and model choice.
You get back
Model response with `tool_calls` or structured JSON output, or a final text response after tool execution.

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 as OPENROUTER_API_KEY — see the openrouter-install-auth skill 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 stubs get_weather and search_database)

Instructions

  1. Pick a model from the Model Compatibility table that supports the features you need (tool calling, JSON mode, parallel tools).
  2. 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).
  3. Read response.choices[0].message.tool_calls — each entry carries function.name and JSON-encoded function.arguments to parse with json.loads().
  4. For agents, wire the Multi-Turn Tool Loop: append the assistant message, execute each tool via execute_tool(), append role: "tool" results keyed by tool_call_id, and loop until the model returns plain text (bounded by max_rounds).
  5. Use the TypeScript Tool Calling section for the identical flow in Node — same schema, same tool_calls shape.
  6. When you only need structured data (no function execution), skip tools and use Structured Output (JSON Mode) with response_format={"type": "json_object"}.
  7. 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

ModelTool CallingJSON ModeParallel Tools
openai/gpt-4oYesYesYes
openai/gpt-4o-miniYesYesYes
anthropic/claude-3.5-sonnetYesVia system promptSequential
google/gemini-2.0-flash-001YesYesYes
meta-llama/llama-3.1-70b-instructYes (varies)Via promptNo

Output

The tool-calling flows produce:

  • message.tool_calls entries — each with a function.name and JSON-encoded function.arguments (e.g., get_weather with {"location": "Tokyo", "unit": "celsius"}) plus a tool_call_id for pairing results
  • The final assistant text once the Multi-Turn Tool Loop resolves — or the "Max tool rounds exceeded" sentinel if it hits max_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

ErrorCauseFix
tool_calls is nullModel chose not to call toolsUse tool_choice: "required" to force tool use
JSON parse error on argumentsModel generated malformed JSONWrap in try/catch; retry or use more capable model
400 invalid tool schemaUnsupported JSON Schema typesStick to basic types (string, number, boolean, object, array)
Tool called with wrong argsSchema description unclearImprove parameter descriptions; add examples in description

Enterprise Considerations

  • Not all models support tool calling -- check model capabilities via /api/v1/models before 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_tokens to 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


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

An OpenRouter API key (`sk-or-v1-...`) exported as `OPENROUTER_API_KEY`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`Real function implementations to dispatch tool calls to

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.

SkillInstallsUpdatedSafetyDifficulty
openrouter-function-calling (this skill)527dReviewIntermediate
mcp-builder1363moReviewAdvanced
copilot-sdk74moReviewIntermediate
openai-knowledge54moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

More by jeremylongshore

View all by jeremylongshore

analyzing-logs

jeremylongshore

Analyze application logs to detect performance issues, identify error patterns, and improve stability by extracting key insights.

14123

ollama-setup

jeremylongshore

Configure auto-configure Ollama when user needs local LLM deployment, free AI alternatives, or wants to eliminate hosted API costs. Trigger phrases: "install ollama", "local AI", "free LLM", "self-hosted AI", "replace OpenAI", "no API costs". Use when appropriate context detected. Trigger with relevant phrases based on skill purpose.

1167

backtesting-trading-strategies

jeremylongshore

Backtest crypto and traditional trading strategies against historical data. Calculates performance metrics (Sharpe, Sortino, max drawdown), generates equity curves, and optimizes strategy parameters. Use when user wants to test a trading strategy, validate signals, or compare approaches. Trigger with phrases like "backtest strategy", "test trading strategy", "historical performance", "simulate trades", "optimize parameters", or "validate signals".

1071

generating-database-seed-data

jeremylongshore

Process this skill enables AI assistant to generate realistic test data and database seed scripts for development and testing environments. it uses faker libraries to create realistic data, maintains relational integrity, and allows configurable data volumes. u... Use when working with databases or data models. Trigger with phrases like 'database', 'query', or 'schema'.

1033

cursor-codebase-indexing

jeremylongshore

Execute set up and optimize Cursor codebase indexing. Triggers on "cursor index setup", "codebase indexing", "index codebase", "cursor semantic search". Use when working with cursor codebase indexing functionality. Trigger with phrases like "cursor codebase indexing", "cursor indexing", "cursor".

885

testing-mobile-apps

jeremylongshore

Execute mobile app testing on iOS and Android devices/simulators. Use when performing specialized testing. Trigger with phrases like "test mobile app", "run iOS tests", or "validate Android functionality".

810

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).

136215

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.

763

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`.

539

agentic-development

alinaqi

Build AI agents with Pydantic AI (Python) and Claude SDK (Node.js)

19

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".

14

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'.

13

Search skills

Search the agent skills registry