
Function Calling
- 15 installs
- 213 repo stars
- Updated August 4, 2026
- yonatangross/orchestkit
Helps with ai & agent building tasks.
About
function-calling is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- function-calling
- AI & Agent Building
- AI-coding skill
Function Calling by the numbers
- 15 all-time installs (skills.sh)
- Ranked #11,187 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yonatangross/orchestkit --skill function-callingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 15 |
|---|---|
| repo stars | ★ 213 |
| Last updated | August 4, 2026 |
| Repository | yonatangross/orchestkit ↗ |
What it does
Helps with ai & agent building tasks.
Files
Function Calling
Enable LLMs to use external tools and return structured data.
Basic Tool Definition (2026 Best Practice)
# OpenAI format with strict mode (2026 recommended)
tools = [{
"type": "function",
"function": {
"name": "search_documents",
"description": "Search the document database for relevant content",
"strict": True, # ← 2026: Enables structured output validation
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
},
"limit": {
"type": "integer",
"description": "Max results to return"
}
},
"required": ["query", "limit"], # All props required when strict
"additionalProperties": False # ← 2026: Required for strict mode
}
}
}]
# Note: With strict=True:
# - All properties must be listed in "required"
# - additionalProperties must be False
# - No "default" values (provide via code instead)Tool Execution Loop
async def run_with_tools(messages: list, tools: list) -> str:
"""Execute tool calls until LLM returns final answer."""
while True:
response = await llm.chat(messages=messages, tools=tools)
# Check if LLM wants to call tools
if not response.tool_calls:
return response.content
# Execute each tool call
for tool_call in response.tool_calls:
result = await execute_tool(
tool_call.function.name,
json.loads(tool_call.function.arguments)
)
# Add tool result to conversation
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
# Continue loop (LLM will process tool results)
async def execute_tool(name: str, args: dict) -> any:
"""Route to appropriate tool implementation."""
tools = {
"search_documents": search_documents,
"get_weather": get_weather,
"calculate": calculate,
}
return await tools[name](**args)Structured Output (Guaranteed JSON)
from pydantic import BaseModel
class Analysis(BaseModel):
sentiment: str
confidence: float
key_points: list[str]
# OpenAI structured output
response = await client.beta.chat.completions.parse(
model="gpt-5.2",
messages=[{"role": "user", "content": "Analyze this text..."}],
response_format=Analysis
)
analysis = response.choices[0].message.parsed # Typed Analysis objectLangChain Tool Binding
from langchain_core.tools import tool
from pydantic import BaseModel, Field
@tool
def search_documents(query: str, limit: int = 5) -> list[dict]:
"""Search the document database.
Args:
query: Search query string
limit: Maximum results to return
"""
return db.search(query, limit=limit)
# Bind to model
llm_with_tools = llm.bind_tools([search_documents])
# Or with structured output
class SearchResult(BaseModel):
query: str = Field(description="The search query used")
results: list[str] = Field(description="Matching documents")
structured_llm = llm.with_structured_output(SearchResult)Parallel Tool Calls
# OpenAI supports parallel tool calls
response = await llm.chat(
messages=messages,
tools=tools,
parallel_tool_calls=True # Default in GPT-5 series
)
# Handle multiple calls in parallel
if response.tool_calls:
results = await asyncio.gather(*[
execute_tool(tc.function.name, json.loads(tc.function.arguments))
for tc in response.tool_calls
])⚠️ 2026 Compatibility Note:
# Structured outputs with strict=True may not work with parallel_tool_calls
# If using strict mode schemas, disable parallel calls:
response = await llm.chat(
messages=messages,
tools=tools_with_strict_true,
parallel_tool_calls=False # Required for strict mode reliability
)Key Decisions
| Decision | Recommendation |
|---|---|
| Tool count | 5-15 max (more = confusion) |
| Description length | 1-2 sentences |
| Parameter validation | Use Pydantic/Zod |
| Error handling | Return error as tool result |
| Schema mode | `strict: true` (2026 best practice) |
| Output format | Structured Outputs > JSON mode |
| Parallel calls | Disable with strict mode |
Common Mistakes
- Vague tool descriptions (LLM won't know when to use)
- No input validation (LLM sends bad params)
- Missing error handling (crashes on tool failure)
- Too many tools (LLM gets confused)
Related Skills
agent-loops- Multi-step tool use with reasoningllm-streaming- Streaming with tool callsstructured-output- Complex output schemas
Capability Details
tool-definition
Keywords: tool, function, define tool, tool schema, function schema Solves:
- Define tools with clear descriptions
- Create JSON schemas for tool parameters
- Document tool behavior for LLM
tool-execution-loop
Keywords: execution loop, tool call, agent loop, run tool Solves:
- Implement tool execution loops
- Handle multiple tool calls
- Process tool results
structured-output
Keywords: structured output, JSON output, typed response, response schema Solves:
- Get structured JSON from LLM
- Enforce output schemas
- Parse and validate responses
parallel-tool-calls
Keywords: parallel, concurrent, multiple tools, batch tools Solves:
- Execute multiple tools in parallel
- Handle concurrent tool results
- Optimize tool call latency
strict-mode-schemas
Keywords: strict mode, strict schema, additionalProperties, required fields Solves:
- Enforce strict JSON schemas
- Prevent extra fields in output
- Ensure schema compliance
Function Calling Checklist
Tool Definition
- [ ] Clear, concise description (1-2 sentences)
- [ ] All parameters documented
- [ ] Use strict mode (
strict: true) for reliability - [ ] All properties in
required(when strict) - [ ] Set
additionalProperties: false(when strict)
Schema Design
- [ ] Use specific types (not just
string) - [ ] Add enum constraints where applicable
- [ ] Provide examples in descriptions
- [ ] Limit to 5-15 tools per request
Tool Execution
- [ ] Validate input parameters (Pydantic/Zod)
- [ ] Handle errors gracefully
- [ ] Return errors as tool results (don't crash)
- [ ] Log tool calls for debugging
Execution Loop
- [ ] Check for tool calls in response
- [ ] Execute all requested tools
- [ ] Add results to conversation
- [ ] Continue until final answer
Parallel Tool Calls
- [ ] Disable parallel calls with strict mode
- [ ] Use asyncio.gather for parallel execution
- [ ] Handle partial failures
Structured Output
- [ ] Use Pydantic for type safety
- [ ] Validate output schema
- [ ] Handle parse errors
- [ ] Provide fallback behavior
Testing
- [ ] Test each tool independently
- [ ] Test tool selection (right tool for task)
- [ ] Test error handling
- [ ] Test with invalid inputs
Tool Schema Patterns
Define robust tool schemas for OpenAI and Anthropic function calling.
OpenAI Strict Mode Schema
from typing import Literal
def create_tool_schema(
name: str,
description: str,
parameters: dict,
strict: bool = True
) -> dict:
"""Create OpenAI-compatible tool schema with strict mode."""
schema = {
"type": "function",
"function": {
"name": name,
"description": description,
"strict": strict,
"parameters": {
"type": "object",
"properties": parameters,
"required": list(parameters.keys()), # All required in strict
"additionalProperties": False
}
}
}
return schema
# Example: Search tool
search_tool = create_tool_schema(
name="search_documents",
description="Search knowledge base for relevant documents",
parameters={
"query": {"type": "string", "description": "Search query"},
"limit": {"type": "integer", "description": "Max results (1-100)"},
"filters": {
"type": "object",
"properties": {
"category": {"type": "string"},
"date_from": {"type": "string", "format": "date"}
},
"required": ["category", "date_from"],
"additionalProperties": False
}
}
)Anthropic Tool Schema
def create_anthropic_tool(
name: str,
description: str,
input_schema: dict
) -> dict:
"""Create Anthropic-compatible tool definition."""
return {
"name": name,
"description": description,
"input_schema": {
"type": "object",
"properties": input_schema,
"required": list(input_schema.keys())
}
}
# Anthropic usage
tools = [create_anthropic_tool(
name="get_weather",
description="Get current weather for a location",
input_schema={
"location": {"type": "string", "description": "City name"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
}
)]Configuration
strict: true- Enforces schema compliance (OpenAI)additionalProperties: false- No extra fields allowed- All properties in
requiredarray for strict mode - Use
enumfor fixed choices
Cost Optimization
- Shorter descriptions reduce prompt tokens
- Limit tools to 5-15 per request
- Cache tool schemas (they're static)
- Disable parallel_tool_calls with strict mode
"""
Function calling template for OpenAI and Anthropic APIs.
Usage:
from templates.function_def import ToolRegistry, run_tool_loop
registry = ToolRegistry()
registry.register(search_documents)
result = await run_tool_loop(registry, "Find Python tutorials")
"""
import asyncio
import json
from collections.abc import Callable
from typing import Any
from openai import AsyncOpenAI
# --- Tool Registry ---
class ToolRegistry:
"""Registry for managing tool definitions and execution."""
def __init__(self):
self.tools: dict[str, Callable] = {}
self.schemas: list[dict] = []
def register(self, func: Callable) -> Callable:
"""Register a function as a tool."""
schema = self._extract_schema(func)
self.tools[func.__name__] = func
self.schemas.append(schema)
return func
def _extract_schema(self, func: Callable) -> dict:
"""Extract OpenAI tool schema from function."""
hints = func.__annotations__
properties = {}
for name, hint in hints.items():
if name == "return":
continue
properties[name] = {"type": self._python_to_json_type(hint)}
return {
"type": "function",
"function": {
"name": func.__name__,
"description": func.__doc__ or "",
"strict": True,
"parameters": {
"type": "object",
"properties": properties,
"required": list(properties.keys()),
"additionalProperties": False
}
}
}
def _python_to_json_type(self, hint) -> str:
type_map = {str: "string", int: "integer", float: "number", bool: "boolean"}
return type_map.get(hint, "string")
async def execute(self, name: str, args: dict) -> Any:
"""Execute a registered tool."""
if name not in self.tools:
raise ValueError(f"Unknown tool: {name}")
func = self.tools[name]
if asyncio.iscoroutinefunction(func):
return await func(**args)
return func(**args)
# --- Tool Execution Loop ---
async def run_tool_loop(
registry: ToolRegistry,
user_message: str,
model: str = "gpt-5.2",
max_iterations: int = 10
) -> str:
"""Run tool execution loop until completion."""
client = AsyncOpenAI()
messages = [{"role": "user", "content": user_message}]
for _ in range(max_iterations):
response = await client.chat.completions.create(
model=model,
messages=messages,
tools=registry.schemas,
parallel_tool_calls=False # Required for strict mode
)
message = response.choices[0].message
if not message.tool_calls:
return message.content
messages.append(message.model_dump())
for tool_call in message.tool_calls:
result = await registry.execute(
tool_call.function.name,
json.loads(tool_call.function.arguments)
)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
raise RuntimeError("Max iterations reached")
# --- Example Usage ---
if __name__ == "__main__":
registry = ToolRegistry()
@registry.register
def search_documents(query: str, limit: int) -> list:
"""Search knowledge base for documents."""
return [{"title": f"Result for {query}", "score": 0.95}]
@registry.register
def get_weather(location: str) -> dict:
"""Get weather for a location."""
return {"location": location, "temp": 22, "unit": "celsius"}
async def main():
result = await run_tool_loop(registry, "What's the weather in Paris?")
print(result)
asyncio.run(main())