Skip to content
Back to Blog
1 min read

Function Calling Patterns: Building Reliable AI Tools

I wrote “Function Calling Patterns: Building Reliable AI Tools” to share practical, production-minded guidance on this topic.

Function Calling Best Practices

from azure.ai.openai import AzureOpenAI
from pydantic import BaseModel, Field
from typing import Callable, Any
import json

class ToolDefinition(BaseModel):
    """Schema for tool definitions."""
    name: str
    description: str
    parameters: dict
    function: Callable

class FunctionCallingAgent:
    def __init__(self, openai_client: AzureOpenAI):
        self.openai = openai_client
        self.tools: dict[str, ToolDefinition] = {}

    def register_tool(self, name: str, description: str, parameters: dict, function: Callable):
        """Register a tool with validation."""
        self.tools[name] = ToolDefinition(
            name=name,
            description=description,
            parameters=parameters,
            function=function
        )

    def get_tool_schemas(self) -> list:
        """Get OpenAI-compatible tool schemas."""
        return [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.parameters
            }
        } for tool in self.tools.values()]

    async def execute_tool(self, name: str, arguments: dict) -> Any:
        """Execute tool with error handling."""
        if name not in self.tools:
            return {"error": f"Unknown tool: {name}"}

        tool = self.tools[name]
        try:
            # Validate arguments against schema
            self.validate_arguments(arguments, tool.parameters)
            result = await tool.function(**arguments)
            return {"success": True, "result": result}
        except Exception as e:
            return {"success": False, "error": str(e)}

    async def run(self, messages: list) -> str:
        """Run agent with automatic tool execution."""
        while True:
            response = await self.openai.chat.completions.create(
                model="gpt-4o",
                messages=messages,
                tools=self.get_tool_schemas(),
                tool_choice="auto"
            )

            message = response.choices[0].message

            if not message.tool_calls:
                return message.content

            # Execute all tool calls
            messages.append(message)
            for tool_call in message.tool_calls:
                result = await self.execute_tool(
                    tool_call.function.name,
                    json.loads(tool_call.function.arguments)
                )
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": json.dumps(result)
                })

# Example tool registration
agent.register_tool(
    name="query_database",
    description="Execute SQL query and return results",
    parameters={
        "type": "object",
        "properties": {
            "query": {"type": "string", "description": "SQL query to execute"}
        },
        "required": ["query"]
    },
    function=execute_sql
)

Well-designed function calling creates powerful AI applications that interact safely with real systems.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.