From openrouter-pack
Implements tool calling for OpenRouter models using OpenAI Python client. Builds AI agents, structured outputs, multi-turn LLM tool loops across Claude, GPT, Gemini.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin openrouter-packThis skill is limited to using the following tools:
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 interfa...
Implements Claude tool use (function calling) workflows with Anthropic Messages API for AI agents, agent loops, and external system interactions.
Integrates TypeScript apps with 300+ AI models via OpenRouter SDK using callModel for text generation, tools, streaming, and multi-turn conversations. Useful for AI agents.
Integrates TypeScript apps with OpenRouter's 300+ AI models via SDK packages for callModel agents, tools, streaming, OAuth, and API key management.
Share bugs, ideas, or general feedback.
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.
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"}
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)
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)})`);
}
}
# 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 jane@acme.co, 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": "jane@acme.co", "company": "Acme Corp"}
| 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 |
| 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 |
/api/v1/models before sending toolstool_choice: "required" when you must get a tool call (e.g., extraction pipelines)max_tokens to prevent expensive completion when model decides not to use tools