DEV Community

Etrit Neziri
Etrit Neziri

Posted on

LLM Function Calling: The Complete Guide for Building AI Tools

LLM Function Calling: The Complete Guide for Building AI Tools

Function calling (tool use) is the technology that turned LLMs from chatbots into agents. Here's the complete guide.

What Is Function Calling?

Function calling lets an LLM decide when to call external tools and format the arguments correctly. Instead of generating text about searching the web, it generates a structured call that actually searches.

How It Works

User: "What's the weather in Tokyo?"

LLM thinks: I need to call get_weather(city="Tokyo")
         ↓
Tool call: get_weather(city="Tokyo")
         ↓
Result: { "temp": 22, "condition": "Sunny" }
         ↓
LLM: "The weather in Tokyo is 22°C and sunny."
Enter fullscreen mode Exit fullscreen mode

The key insight: the LLM doesn't execute the function — it tells your code what to execute.

Basic Implementation

from openai import OpenAI

client = OpenAI()

# Define your tools
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": { "type": "string", "description": "City name" },
                    "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "run_python",
            "description": "Execute Python code and return output",
            "parameters": {
                "type": "object",
                "properties": {
                    "code": { "type": "string", "description": "Python code to execute" }
                },
                "required": ["code"]
            }
        }
    }
]

def run_agent(user_message):
    messages = [{"role": "user", "content": user_message}]

    while True:
        response = client.chat.completions.create(
            model="gpt-4",
            messages=messages,
            tools=tools
        )

        msg = response.choices[0].message
        messages.append(msg.to_dict())

        # No tool calls = agent is done
        if not msg.tool_calls:
            return msg.content

        # Execute each tool call
        for tool_call in msg.tool_calls:
            result = 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)
            })

def execute_tool(name, args):
    if name == "get_weather":
        return {"temp": 22, "condition": "Sunny"}  # Your API call here
    elif name == "run_python":
        try:
            exec_locals = {}
            exec(args["code"], {}, exec_locals)
            return {"output": str(exec_locals)}
        except Exception as e:
            return {"error": str(e)}
Enter fullscreen mode Exit fullscreen mode

The ReAct Pattern

The most effective agent pattern combines Reasoning + Acting:

  1. Observe: What's the current state?
  2. Think: What should I do next?
  3. Act: Call a tool or respond
  4. Repeat until done
def react_agent(task, max_steps=10):
    messages = [{"role": "user", "content": task}]

    for step in range(max_steps):
        # LLM reasons about next action
        response = client.chat.completions.create(
            model="gpt-4",
            messages=messages,
            tools=tools
        )

        choice = response.choices[0]

        if choice.finish_reason == "tool_calls":
            # Execute tool calls and feed results back
            for tc in choice.message.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)})
        else:
            # Agent has a final answer
            return choice.message.content

    return "Agent exceeded max steps without completing"
Enter fullscreen mode Exit fullscreen mode

Best Practices

Practice Why How
Limit tool count Reduces confusion 3-7 tools per agent
Clear descriptions LLM picks right tool One-sentence purpose + parameter docs
Validate inputs Prevent injection Pydantic/schemas on all tool args
Handle errors Agents will call wrong tools Return descriptive error messages
Set max iterations Prevent infinite loops 10-20 steps max
Log all calls Debug + audit trail Store tool name, args, result

Advanced: Multi-Agent Tool Use

For complex tasks, decompose into specialist agents:

# Router agent picks which specialist to use
tools = [
    {"type": "function", "function": {"name": "ask_research_agent", ...}},
    {"type": "function", "function": {"name": "ask_coding_agent", ...}},
    {"type": "function", "function": {"name": "ask_writing_agent", ...}},
]
Enter fullscreen mode Exit fullscreen mode

Each specialist has its own system prompt and tool set. The router just decides who to delegate to.

Key Takeaways

  1. Function calling = agents. Without tools, LLMs are just fancy chatbots
  2. ReAct pattern is the foundation of all modern agent frameworks
  3. Tool design is UX design — clear names, descriptions, and schemas
  4. Error handling is critical — agents will call tools wrong
  5. Log everything — you'll need it for debugging

Building AI agents? I write about this regularly. Follow for more, and check out my work on GitHub.

Top comments (0)