DeepSeek Advanced

DeepSeek Function Calling in Action: Building an AI Weather Assistant

Build an AI weather assistant with Function Calling: define tools, handle calls, return results, and generate the final response.

DeepSeekFunction CallingPythonAdvanced
By Xingxing Yang · AI technology enthusiast & founder of China AI Tutorials

What Problem Does This Tutorial Solve?

You will build a complete AI weather assistant from scratch that can:

  • Understand natural language queries from users
  • Automatically invoke weather APIs to fetch real-time data
  • Generate friendly responses based on the data

This tutorial is your first step toward understanding AI Agents. Function Calling is the cornerstone of AI application development.

What Is Function Calling?

Function Calling enables AI to use external tools. Here is the workflow:

User: "What's the weather like in Beijing today?"

AI determines: I need to call the get_weather function

AI returns function call request: {name: "get_weather", args: {city: "Beijing"}}

Your code executes the function → retrieves weather data

Return the result to the AI

AI: "Beijing is sunny today, 22°C, great for outdoor activities."

Step 1: Define the Tool

import os, json
from openai import OpenAI

client = OpenAI(
    api_key=os.environ.get("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com/v1",
)

# Define the get_weather tool
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Query the weather information for a specified city on the current day",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "City name, in either Chinese or English"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit"
                }
            },
            "required": ["city"]
        }
    }
}]

Step 2: Mock the Weather Query Function

def get_weather(city: str, unit: str = "celsius") -> dict:
    """Mock weather query (use a real API in production)"""
    weather_data = {
        "Beijing": {"temp": 22, "condition": "Sunny", "humidity": 45},
        "Shanghai": {"temp": 28, "condition": "Cloudy", "humidity": 65},
        "Tokyo": {"temp": 25, "condition": "Light Rain", "humidity": 70},
        "Hangzhou": {"temp": 30, "condition": "Sunny", "humidity": 55},
    }

    data = weather_data.get(city, {"temp": 20, "condition": "Unknown", "humidity": 50})
    if unit == "fahrenheit":
        data["temp"] = data["temp"] * 9 / 5 + 32

    data["city"] = city
    data["unit"] = unit
    return data

Step 3: Build the Complete Assistant

def chat_with_weather_assistant(user_query: str) -> str:
    messages = [{"role": "user", "content": user_query}]

    # First call: AI decides whether a tool is needed
    response = client.chat.completions.create(
        model="deepseek-v4-pro",
        messages=messages,
        tools=tools,
        tool_choice="auto",
        extra_body={"thinking_mode": "thinking"},
    )

    msg = response.choices[0].message

    # If the AI does not need a tool, return the content directly
    if not msg.tool_calls:
        return msg.content

    # Process each tool call
    for tool_call in msg.tool_calls:
        func_name = tool_call.function.name
        func_args = json.loads(tool_call.function.arguments)

        print(f"Tool invoked: {func_name}")
        print(f"Arguments: {func_args}")

        # Execute the function
        if func_name == "get_weather":
            result = get_weather(**func_args)

        # Return the execution result to the AI
        messages.append({
            "role": "tool",
            "tool_call_id": tool_call.id,
            "content": json.dumps(result, ensure_ascii=False)
        })

    # Second call: AI generates the final response based on tool results
    final_response = client.chat.completions.create(
        model="deepseek-v4-pro",
        messages=messages,
    )

    return final_response.choices[0].message.content


# Test
print(chat_with_weather_assistant("What's the weather like in Beijing today?"))
print(chat_with_weather_assistant("Which city is hotter, Shanghai or Hangzhou?"))

Full Workflow Walkthrough

Input: “Which city is hotter, Shanghai or Hangzhou?”

1st API call:
  → AI returns: need to call get_weather("Shanghai") and get_weather("Hangzhou")

Code executes get_weather → retrieves weather data for both cities

2nd API call (with weather data):
  → AI returns: "Hangzhou (30°C) is 2 degrees hotter than Shanghai (28°C).
             Shanghai has higher humidity (65% vs 55%), so it may feel similar.
             Neither city has rain today — great for going out."
Key Insight: The AI not only compared temperatures, but also factored in humidity and weather conditions when giving advice. This is the value of Function Calling — the AI gains access to real data and no longer fabricates answers.

From Mock to Real: Wiring a Live Weather API

The mock function above is fine for learning, but a real assistant needs live data. Here’s the same get_weather backed by a real HTTP call, with the network failure paths handled explicitly:

import requests

def get_weather(city: str, unit: str = "celsius") -> dict:
    """Fetch live weather. Falls back gracefully on any failure."""
    try:
        resp = requests.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": 39.9042,   # look these up per city in production
                "longitude": 116.4074,
                "current": "temperature_2m,relative_humidity_2m",
            },
            timeout=10,
        )
        resp.raise_for_status()
        data = resp.json()["current"]
        temp = data["temperature_2m"]
        if unit == "fahrenheit":
            temp = temp * 9 / 5 + 32
        return {
            "city": city,
            "temp": round(temp, 1),
            "humidity": data["relative_humidity_2m"],
            "unit": unit,
        }
    except (requests.RequestException, KeyError) as e:
        # Never let a tool crash the whole conversation — return a structured error
        return {"city": city, "error": f"weather lookup failed: {e}"}
⚠️ A tool that raises an unhandled exception kills the whole request. Always return a structured result — even for errors — so the model can tell the user "I couldn't fetch the weather" instead of your server returning a 500.

Handling Tool Execution Failures

When a tool returns an error, feed that error back to the model. The model will then explain the failure to the user in natural language instead of hallucinating an answer:

def dispatch_tool(func_name: str, func_args: dict) -> dict:
    """Route a tool call to the right function, catching everything."""
    registry = {
        "get_weather": get_weather,
        # add more tools here
    }
    fn = registry.get(func_name)
    if fn is None:
        return {"error": f"unknown tool: {func_name}"}
    try:
        return fn(**func_args)
    except TypeError as e:
        # Model passed wrong/missing arguments
        return {"error": f"invalid arguments for {func_name}: {e}"}
    except Exception as e:
        return {"error": f"{func_name} failed: {e}"}

Also guard the JSON parse — the model occasionally emits malformed arguments:

try:
    func_args = json.loads(tool_call.function.arguments)
except json.JSONDecodeError:
    func_args = {}
    # Return an error result so the model can retry with valid arguments

Parallel Tool Calls

Modern models can request several tools in a single turn. The “Which city is hotter” example already triggers two get_weather calls. The key is to append one tool message per call, matching each tool_call_id, before the second API call:

# msg.tool_calls may contain multiple entries
messages.append(msg)  # append the assistant's tool-call message itself

for tool_call in msg.tool_calls:
    func_args = json.loads(tool_call.function.arguments)
    result = dispatch_tool(tool_call.function.name, func_args)
    messages.append({
        "role": "tool",
        "tool_call_id": tool_call.id,  # MUST match the specific call
        "content": json.dumps(result, ensure_ascii=False),
    })

# Now the model has every tool result and can compose the final answer
final = client.chat.completions.create(model="deepseek-v4-pro", messages=messages)
📝 Common mistake: forgetting to append the assistant's original tool_calls message before the tool results. If the tool_call_id in your tool message doesn't match a prior call, the API rejects the request.

Advanced: Defining Multiple Tools

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Query weather information",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"}
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_news",
            "description": "Query the latest news",
            "parameters": {
                "type": "object",
                "properties": {
                    "topic": {"type": "string"},
                    "count": {"type": "integer", "default": 3}
                },
                "required": ["topic"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "Send an email",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string"},
                    "subject": {"type": "string"},
                    "body": {"type": "string"}
                },
                "required": ["to", "subject", "body"]
            }
        }
    }
]

Frequently Asked Questions (FAQ)

Q: When should I use Function Calling?

A: Use it when the AI needs access to real-time data (weather, stocks, news), needs to perform actions (send emails, create files), or needs to invoke your business logic.

Q: How detailed should tool definitions be?

A: The more detailed, the better. The description field for each parameter directly affects whether the AI can invoke the function correctly. Use natural language to describe what each parameter should be.

Q: What happens if a tool call fails or times out?

A: Always return a structured error dictionary (e.g. {"error": "..."}) rather than raising an exception. The model reads that error and explains it to the user in plain language. If you let the exception propagate, your entire request crashes and the user gets nothing. Wrap every tool in a try/except and set a timeout on any network call.

Q: Can the model call multiple tools at once?

A: Yes. msg.tool_calls is a list — the model may request several tools in one turn. Append the assistant’s tool-call message first, then one tool message per call with a matching tool_call_id. Only after every result is appended do you make the second API call to get the final answer.

Next Steps

Tutorial version note: Based on DeepSeek V4 API, verified working as of June 2026.