Function Calling with Chinese AI Models: Complete Tutorial

July 20, 2026 · 20 min read · Model Pricing

Function calling (also called tool use) is what transforms a chatbot into an agent. The model decides when to call your code, what arguments to pass, and how to interpret the results — all through a standardized API format.

The good news: Chinese AI models support function calling through the exact same OpenAI-compatible interface you already know. No new SDK, no new format. Just change the base URL and model name.

In this tutorial, you'll build a working agent with three tools (weather lookup, database query, web search), test it against DeepSeek V4 Pro, GLM-5, and Kimi K3, and learn the differences between their function calling implementations.

Table of Contents

  1. What Is Function Calling?
  2. Which Chinese Models Support It?
  3. Basic Example: Weather Agent
  4. Multi-Tool Agent with Auto-Execute
  5. Model Comparison: DeepSeek vs GLM vs Kimi
  6. Parallel Tool Calls
  7. Production Patterns
  8. Common Pitfalls
  9. Complete Agent Framework

What Is Function Calling?

Function calling works in a loop:

1. User sends a message
2. Model decides: reply directly OR call a tool
3. If tool call: your code executes the function, returns result
4. Model reads the result, decides: call another tool OR reply to user
5. Repeat 3-4 until model generates a final answer

The model outputs structured JSON specifying which function to call and with what parameters. Your code parses this, runs the function, and feeds the result back. The model never "runs" your code directly — it's a controlled delegation pattern.

Which Chinese Models Support It?

ModelFunction CallingParallel CallsInput $/1MBest For
DeepSeek V4 Pro✅ Full✅ Yes$0.48General-purpose agents
DeepSeek V4 Flash✅ Full✅ Yes$0.14High-volume, simple tools
GLM-5✅ Full⚠️ Limited$0.15Chinese-language agents
GLM-5-turbo✅ Full⚠️ Limited$0.15Fast tool calls
Kimi K3✅ Full✅ Yes$1.09Long-context tool use
ERNIE 5.0⚠️ Varies by version❌ No$2.47Not recommended — use DeepSeek instead

Recommendation: Use DeepSeek V4 Pro as your default for function calling. It has the best balance of quality, speed, and parallel call support. Use DeepSeek V4 Flash for high-volume simple tool calls (cost: $0.14/M).

Basic Example: Weather Agent

Let's start with the simplest possible function calling setup:

import os
import json
import openai

client = openai.OpenAI(
    api_key=os.environ["AIWAVE_API_KEY"],
    base_url="https://aiwave.live/v1",  # OpenAI-compatible
)

# Define your tool
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name, e.g. 'Tokyo' or 'New York'",
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature unit",
                    },
                },
                "required": ["city"],
            },
        },
    }
]

# Simulated weather function (replace with real API)
def get_weather(city: str, unit: str = "celsius") -> dict:
    """Your actual implementation here."""
    # In production: call a weather API
    temps = {"Tokyo": 28, "New York": 32, "London": 18, "Singapore": 30}
    temp = temps.get(city, 22)
    if unit == "fahrenheit":
        temp = temp * 9 / 5 + 32
    return {"city": city, "temperature": temp, "unit": unit, "condition": "sunny"}

# Make the call
response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
    tools=tools,
)

# Check if model wants to call a function
message = response.choices[0].message
if message.tool_calls:
    for tool_call in message.tool_calls:
        func_name = tool_call.function.name
        func_args = json.loads(tool_call.function.arguments)
        
        # Execute the function
        if func_name == "get_weather":
            result = get_weather(**func_args)
            print(f"Weather data: {result}")
        
        # Feed result back to the model
        response2 = client.chat.completions.create(
            model="deepseek-v4-pro",
            messages=[
                {"role": "user", "content": "What's the weather in Tokyo?"},
                message,  # The tool call message
                {
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": json.dumps(result),
                },
            ],
            tools=tools,
        )
        print(f"Final answer: {response2.choices[0].message.content}")
else:
    print(f"Direct reply: {message.content}")

Key point: The base_url is the only thing that changes from standard OpenAI code. The function calling format, message structure, and tool response format are identical. This is why AIWave calls itself "OpenAI-compatible" — it really is.

Multi-Tool Agent with Auto-Execute

Now let's build a proper agent that automatically executes tool calls in a loop:

def execute_tools(tool_calls: list) -> list[dict]:
    """Execute all tool calls and return results."""
    results = []
    for tc in tool_calls:
        func_name = tc.function.name
        func_args = json.loads(tc.function.arguments)
        
        # Dispatch to registered functions
        if func_name == "get_weather":
            result = get_weather(**func_args)
        elif func_name == "query_database":
            result = query_database(**func_args)
        elif func_name == "web_search":
            result = web_search(**func_args)
        else:
            result = {"error": f"Unknown function: {func_name}"}
        
        results.append({
            "role": "tool",
            "tool_call_id": tc.id,
            "content": json.dumps(result),
        })
    return results

def run_agent(query: str, model: str = "deepseek-v4-pro", max_turns: int = 10) -> str:
    """Run an agent loop until it produces a final answer."""
    messages = [{"role": "user", "content": query}]
    
    for turn in range(max_turns):
        response = client.chat.completions.create(
            model=model,
            messages=messages,
            tools=all_tools,
        )
        
        message = response.choices[0].message
        messages.append(message)  # Add assistant message to history
        
        if not message.tool_calls:
            # Model gave a final answer (no more tool calls)
            return message.content
        
        # Execute tools and add results
        tool_results = execute_tools(message.tool_calls)
        messages.extend(tool_results)
        
        print(f"Turn {turn + 1}: Called {len(message.tool_calls)} tool(s)")
    
    return "Agent reached maximum turns without producing an answer."

Model Comparison: DeepSeek vs GLM vs Kimi

We tested all three models with identical prompts. Here's how they differ in function calling behavior:

Test: "Search for the latest Python 3.13 release date, then check the weather in the city where it was announced."

ModelCorrect ToolsTool ArgumentsMulti-step ReasoningLatency
DeepSeek V4 Pro✅ 2 calls in sequence✅ Correct args✅ Connected results3.2s
GLM-5✅ 2 calls✅ Correct args⚠️ Slightly generic2.8s
Kimi K3✅ 2 calls (parallel attempted)✅ Correct args✅ Connected results4.1s

Key Differences

Parallel Tool Calls

DeepSeek V4 Pro and Kimi K3 support parallel tool calls — calling multiple independent tools simultaneously:

response = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=[{"role": "user", "content": 
        "Compare the weather in Tokyo, London, and New York"}],
    tools=tools,
)

message = response.choices[0].message
# DeepSeek V4 Pro returns multiple tool_calls in one response:
# tool_calls[0]: get_weather(city="Tokyo")
# tool_calls[1]: get_weather(city="London") 
# tool_calls[2]: get_weather(city="New York")

# Execute all in parallel (they're independent)
import concurrent.futures
def execute_single(tc):
    args = json.loads(tc.function.arguments)
    return {"role": "tool", "tool_call_id": tc.id, 
            "content": json.dumps(get_weather(**args))}

with concurrent.futures.ThreadPoolExecutor() as pool:
    results = list(pool.map(execute_single, message.tool_calls))

messages.extend(results)
response2 = client.chat.completions.create(
    model="deepseek-v4-pro",
    messages=messages,
    tools=tools,
)
print(response2.choices[0].message.content)

Parallel calls save 50-70% latency for multi-tool queries. Instead of 3 sequential API calls (3 × 1s = 3s), you get 1 call with 3 tools + parallel execution (~1.5s total).

Production Patterns

1. Tool Result Validation

Always validate tool results before feeding them back to the model:

def safe_execute(tool_call) -> dict:
    """Execute tool call with error handling and validation."""
    try:
        func_name = tool_call.function.name
        func_args = json.loads(tool_call.function.arguments)
        
        # Validate arguments
        if func_name == "get_weather":
            if not func_args.get("city"):
                return {"role": "tool", "tool_call_id": tool_call.id,
                        "content": json.dumps({"error": "City is required"})}
        
        result = TOOL_REGISTRY[func_name](**func_args)
        
        # Truncate large results to prevent context overflow
        result_str = json.dumps(result)
        if len(result_str) > 4000:
            result_str = result_str[:4000] + "... (truncated)"
        
        return {"role": "tool", "tool_call_id": tool_call.id, "content": result_str}
    
    except json.JSONDecodeError:
        return {"role": "tool", "tool_call_id": tool_call.id,
                "content": json.dumps({"error": "Invalid JSON arguments"})}
    except Exception as e:
        return {"role": "tool", "tool_call_id": tool_call.id,
                "content": json.dumps({"error": str(e)})}

2. Tool Registry Pattern

from functools import wraps

TOOL_REGISTRY = {}

def register_tool(name: str, description: str, parameters: dict):
    """Decorator to register a function as a tool."""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        TOOL_REGISTRY[name] = {
            "function": wrapper,
            "definition": {
                "type": "function",
                "function": {
                    "name": name,
                    "description": description,
                    "parameters": parameters,
                },
            },
        }
        return wrapper
    return decorator

# Usage
@register_tool(
    name="get_weather",
    description="Get weather for a city",
    parameters={
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "City name"},
            "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
        },
        "required": ["city"],
    },
)
def get_weather(city: str, unit: str = "celsius") -> dict:
    temps = {"Tokyo": 28, "New York": 32, "London": 18}
    return {"city": city, "temp": temps.get(city, 22), "unit": unit}

# Get tool definitions for API call
all_tools = [v["definition"] for v in TOOL_REGISTRY.values()]

3. Token Budget Guard

MAX_AGENT_TOKENS = 50_000  # Stop agent if conversation exceeds this

def run_agent_with_budget(query: str) -> str:
    messages = [{"role": "user", "content": query}]
    
    while True:
        response = client.chat.completions.create(
            model="deepseek-v4-pro",
            messages=messages,
            tools=all_tools,
        )
        
        # Check token budget
        total_tokens = response.usage.total_tokens
        if total_tokens > MAX_AGENT_TOKENS:
            return f"Agent stopped: token budget exceeded ({total_tokens:,} tokens). Last response: {response.choices[0].message.content}"
        
        message = response.choices[0].message
        messages.append(message)
        
        if not message.tool_calls:
            return message.content
        
        messages.extend(execute_tools(message.tool_calls))

Common Pitfalls

1. Forgetting to Append Tool Messages

The model needs to see the tool_call message AND the tool response in the conversation history. If you skip either, the model loses context:

# ❌ WRONG: Only sending tool result
messages.append({"role": "tool", "content": result})

# ✅ CORRECT: Send assistant's tool_call, then tool result
messages.append(assistant_message)  # Contains tool_calls
messages.append({"role": "tool", "tool_call_id": "...", "content": result})

2. Overly Complex Tool Schemas

Keep tool parameters simple. Complex nested schemas confuse the model:

# ❌ TOO COMPLEX (model struggles)
{"properties": {"address": {"properties": {"street": ..., "city": ...}}}}

# ✅ FLAT (model nails it)  
{"properties": {"street": {"type": "string"}, "city": {"type": "string"}}}

3. Not Setting Temperature Low

For function calling, use temperature=0 or 0.1. Higher temperatures cause the model to hallucinate tool names or invent parameters:

# ❌ temperature=0.7 → model invents function names
# ✅ temperature=0.1 → reliable function calling

Complete Agent Framework

"""
Production Agent Framework with Chinese AI Models
pip install openai
"""
import os, json
import openai
from typing import Callable

client = openai.OpenAI(
    api_key=os.environ["AIWAVE_API_KEY"],
    base_url="https://aiwave.live/v1",
)

class Agent:
    def __init__(self, model: str = "deepseek-v4-pro", system_prompt: str = "You are a helpful assistant."):
        self.model = model
        self.system_prompt = system_prompt
        self.tools: list[dict] = []
        self.functions: dict[str, Callable] = {}
        self.conversation: list[dict] = []
    
    def tool(self, name: str, description: str, parameters: dict):
        """Decorator to register a tool."""
        def decorator(func):
            self.functions[name] = func
            self.tools.append({
                "type": "function",
                "function": {
                    "name": name,
                    "description": description,
                    "parameters": parameters,
                },
            })
            return func
        return decorator
    
    def run(self, query: str, max_turns: int = 10) -> str:
        """Execute agent loop."""
        self.conversation = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": query},
        ]
        
        for turn in range(max_turns):
            response = client.chat.completions.create(
                model=self.model,
                messages=self.conversation,
                tools=self.tools if self.tools else openai.NOT_GIVEN,
                temperature=0.1,
            )
            
            msg = response.choices[0].message
            self.conversation.append(msg)
            
            if not msg.tool_calls:
                return msg.content
            
            for tc in msg.tool_calls:
                try:
                    func = self.functions[tc.function.name]
                    args = json.loads(tc.function.arguments)
                    result = func(**args)
                    self.conversation.append({
                        "role": "tool",
                        "tool_call_id": tc.id,
                        "content": json.dumps(result),
                    })
                except Exception as e:
                    self.conversation.append({
                        "role": "tool",
                        "tool_call_id": tc.id,
                        "content": json.dumps({"error": str(e)}),
                    })
        
        return "Agent exceeded maximum turns."

# === Usage ===
agent = Agent(
    model="deepseek-v4-pro",
    system_prompt="You are a weather and data assistant.",
)

@agent.tool("get_weather", "Get current weather for a city", {
    "type": "object",
    "properties": {"city": {"type": "string"}},
    "required": ["city"],
})
def get_weather(city: str) -> dict:
    temps = {"Tokyo": 28, "New York": 32, "London": 18}
    return {"city": city, "temp": temps.get(city, 22), "unit": "celsius"}

answer = agent.run("What's the weather in Tokyo and how does it compare to London?")
print(answer)

Build Agents Today

Get $5 to test function calling with DeepSeek V4 Pro, GLM-5, Kimi K3, and 50+ more models. Same OpenAI SDK, 95% lower cost.

Start Building → · Compare Model Prices →

Further Reading

← All Articles