Building Production-Grade Agentic Systems with Z.AI GLM-5: A Complete Developer Guide

Contents show
Building Production-Grade Agentic Systems with Z.AI GLM-5: A Complete Developer Guide
Building Production-Grade Agentic Systems with Z.AI GLM-5: A Complete Developer Guide

Building Production-Grade Agentic Systems with Z.AI GLM-5: A Complete Developer Guide

If you have spent any time in the AI space recently, you have probably heard a lot of buzz about โ€œagentic systems.โ€ Agents this, agents that. But what does it actually mean to build one that works in the real world, not just inside a tutorial notebook?

That is exactly what this guide is about.

We are going to walk through Z.AI's GLM-5 model from the ground up. By the end, you will understand how to set up the SDK, stream live responses, use thinking mode, build multi-turn conversations, call external tools, and wire it all together into a working multi-tool agent. No fluff, no hand-waving. Just real code and clear explanations.

Whether you are 17, 22, or 42, this is written so you can actually follow along.


What Is Z.AI and Why Should You Care About GLM-5?

Z.AI is a platform built around one of the more capable language model families available right now: the GLM series. GLM-5 is their most recent flagship model, and it brings a few things to the table that make it stand out for developers building serious AI applications.

First, GLM-5 supports an OpenAI-compatible API. That means if you have ever worked with OpenAI's Python SDK, you will feel at home here with almost zero learning curve. The same request structure, the same response format, just pointing at Z.AI's endpoint instead.

Second, GLM-5 has a dedicated โ€œthinking modeโ€ that lets the model reason step-by-step before it gives you an answer. Think of it like asking someone to show their work. The model does not just spit out an answer; it walks through its logic first. For anything involving multi-step decisions, this is genuinely useful.

Third, the model handles function calling natively. You define tools, you tell the model what they do, and the model figures out when to call them. You do not have to write complex routing logic yourself.

Put those three things together, and you have a solid foundation for building agents that can actually think, act, and adapt.


Setting Up Your Environment

Before you write a single line of agent code, you need to get your environment right. This part is straightforward.

Installing the Required Packages

You will need three packages: the Z.AI SDK, the OpenAI library (for the compatible interface), and Rich (a Python library that makes terminal output much nicer to read).

pip install zai-sdk openai rich

That is it. Three packages, one command.

Authenticating with Your API Key

Z.AI uses API key authentication, same as most LLM platforms. You grab a free key from their dashboard at z.ai/manage-apikey/apikey-list, then load it into your environment at runtime.

Here is a clean way to handle this in Python:

import os
import getpass

api_key = os.environ.get("ZAI_API_KEY")

if not api_key:
    api_key = getpass.getpass("Enter your Z.AI API key: ").strip()

if not api_key:
    raise ValueError("No API key provided. Get one free at: https://z.ai/manage-apikey/apikey-list")

os.environ["ZAI_API_KEY"] = api_key
print(f"API key loaded (ends with ...{api_key[-4:]})")

Using getpass is smart here because it hides your key from the terminal. No accidental copy-pastes into screenshots.

Initializing the Client

Once you have your key, initializing the client is one line:

from zai import ZaiClient

client = ZaiClient(api_key=api_key)
print("ZaiClient ready.")

You now have a client object that can talk to GLM-5. Everything else builds on this.


Your First Chat Completion

The simplest thing you can do with GLM-5 is a basic chat completion. You send a message, the model replies.

How the Message Format Works

Every request to GLM-5 uses a list of messages. Each message has a role and content. The three roles are:

  • system โ€“ Sets the context for how the model should behave
  • user โ€“ Represents what the human is asking
  • assistant โ€“ Represents previous model responses (used in multi-turn conversations)

Here is a basic example:

response = client.chat.completions.create(
    model="glm-5",
    messages=[
        {
            "role": "system",
            "content": "You are a concise software architect. Keep answers under 3 sentences."
        },
        {
            "role": "user",
            "content": "Explain the Mixture-of-Experts architecture."
        }
    ],
    max_tokens=256,
    temperature=0.7
)

print(response.choices[0].message.content)
print(f"Tokens used: {response.usage.prompt_tokens} prompt + {response.usage.completion_tokens} completion")

The temperature parameter controls how creative or conservative the model is. A lower value like 0.2 makes responses more predictable and factual. A higher value like 0.9 makes them more varied and creative. For structured tasks like code generation, lower is usually better.

Reading the Response Object

The response comes back as an object, not a plain string. You access the actual text through response.choices[0].message.content. The usage field tells you how many tokens were consumed, which matters when you are working with rate limits or building cost-aware applications.


Streaming: Getting Responses in Real Time

If you have ever used ChatGPT, you know that responses appear word-by-word as they are generated. That is streaming, and you can do the same thing with GLM-5.

Why Streaming Matters

Without streaming, your code sends a request and then sits waiting until the entire response is ready before printing anything. For short responses, that is fine. For longer ones, users are staring at a blank screen for several seconds. That feels bad.

Streaming sends chunks of the response as they are generated. Your UI or terminal can display them immediately, so the experience feels much more alive.

Implementing Streaming

The change from non-streaming to streaming is minimal. You add stream=True to your request and then iterate over the response:

stream = client.chat.completions.create(
    model="glm-5",
    messages=[
        {
            "role": "user",
            "content": "Write a Python one-liner that checks if a number is prime."
        }
    ],
    stream=True,
    max_tokens=512,
    temperature=0.6
)

full_response = ""
print("GLM-5: ", end="", flush=True)

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        text = chunk.choices[0].delta.content
        print(text, end="", flush=True)
        full_response += text

print()  # newline at the end
print(f"\nTotal characters received: {len(full_response)}")

The flush=True argument tells Python to print immediately rather than buffering output. Without it, you would not see the streaming effect.


Thinking Mode: When You Need the Model to Reason

This is where GLM-5 starts to get interesting.

What Thinking Mode Actually Does

Most language model calls give you one thing: the final answer. Thinking mode gives you two things: the model's reasoning process, followed by the final answer. The model works through the problem step-by-step in a separate โ€œthinkingโ€ block before committing to a response.

This is useful for:

  • Complex debugging scenarios where you want to see how the model analyzed the problem
  • Multi-step math or logic questions
  • Decision-making tasks where the reasoning matters as much as the conclusion
  • Building systems where you need to log or inspect the model's thought process

Enabling Thinking Mode

You activate it by passing an extra_body parameter with thinking set to true:

response = client.chat.completions.create(
    model="glm-5",
    messages=[
        {
            "role": "user",
            "content": "Design a rate limiting system for a public API. Walk through your reasoning."
        }
    ],
    max_tokens=1024,
    temperature=0.6,
    extra_body={"thinking": True}
)

# The response contains both thinking and final answer
for choice in response.choices:
    msg = choice.message
    if hasattr(msg, "reasoning_content") and msg.reasoning_content:
        print("=== Thinking Process ===")
        print(msg.reasoning_content)
        print()
    print("=== Final Answer ===")
    print(msg.content)

When to Use It and When to Skip It

Thinking mode uses more tokens and takes slightly longer to respond. You would not use it for simple lookups or straightforward questions. Save it for tasks where the reasoning itself adds value, or where you need the model to catch edge cases it might otherwise miss.

A well-designed agent might use thinking mode for the planning stage and regular mode for execution steps. That balance keeps things fast without sacrificing quality on the decisions that matter.


Multi-Turn Conversations: Memory Through Context

By default, language models have no memory. Every API call is completely independent. So how do chatbots remember what you said two messages ago?

The answer is: they do not. You do. You pass the full conversation history with every request.

Building a Conversation Loop

Here is a simple conversation manager that keeps track of the message history and appends each new exchange:

def chat_session(client, system_prompt="You are a helpful coding assistant."):
    conversation_history = [
        {"role": "system", "content": system_prompt}
    ]

    print("Chat started. Type 'exit' to quit.\n")

    while True:
        user_input = input("You: ").strip()

        if user_input.lower() == "exit":
            print("Session ended.")
            break

        if not user_input:
            continue

        # Add user message to history
        conversation_history.append({
            "role": "user",
            "content": user_input
        })

        # Send full history to the model
        response = client.chat.completions.create(
            model="glm-5",
            messages=conversation_history,
            max_tokens=512,
            temperature=0.7
        )

        assistant_reply = response.choices[0].message.content

        # Add model response to history
        conversation_history.append({
            "role": "assistant",
            "content": assistant_reply
        })

        print(f"\nGLM-5: {assistant_reply}\n")

    return conversation_history

Every time you call the API, you are sending the entire conversation: the system prompt, every user message, and every assistant reply. The model uses all of that context to give you a coherent response.

Managing Context Window Limits

Every model has a maximum context window, meaning a limit on how much text it can process in one request. For long conversations, this can become a problem.

A practical approach is to keep only the most recent N exchanges in your history, plus always retain the system prompt. Something like this:

MAX_HISTORY_TURNS = 10  # Keep last 10 user+assistant pairs

def trim_history(history, max_turns=MAX_HISTORY_TURNS):
    system_messages = [m for m in history if m["role"] == "system"]
    conversation_messages = [m for m in history if m["role"] != "system"]
    
    # Keep only the most recent messages
    if len(conversation_messages) > max_turns * 2:
        conversation_messages = conversation_messages[-(max_turns * 2):]
    
    return system_messages + conversation_messages

This keeps your costs predictable and prevents hitting context limits in long sessions.


Function Calling: Giving the Model Real Tools

This is the feature that turns a language model into an actual agent.

The Idea Behind Function Calling

Imagine you ask GLM-5 โ€œWhat is the weather in Lagos right now?โ€ The model does not know. It was trained on historical data and has no access to live weather APIs.

But what if you gave it a weather tool? You define a function called get_weather, describe what it does and what parameters it needs, and pass that description to the model. When GLM-5 sees a question that requires weather data, it does not hallucinate an answer. Instead, it says โ€œI need to call get_weather with location=Lagos.โ€ Your code then runs the actual function, gets the real data, and sends that data back to the model, which then gives a proper answer.

That whole loop is function calling, and it is how agents interact with the world.

Defining Your Tools

You define tools as a list of JSON schema objects. Each tool has a name, a description, and a parameters block that describes what arguments it accepts:

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Gets current weather data for a given city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "The name of the city, e.g., Lagos, London, Tokyo"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature unit to use"
                    }
                },
                "required": ["city"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "search_docs",
            "description": "Searches documentation for a given query and returns relevant snippets",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "The search query"
                    },
                    "max_results": {
                        "type": "integer",
                        "description": "Maximum number of results to return (default: 3)"
                    }
                },
                "required": ["query"]
            }
        }
    }
]

Writing the Actual Functions

These are just regular Python functions. The model does not execute them. It tells you what to call and with what arguments, and your code does the actual work:

import json
import random

def get_weather(city: str, unit: str = "celsius") -> dict:
    """Simulated weather data. Replace with a real API call in production."""
    temp_c = random.randint(18, 35)
    temp_f = round(temp_c * 9/5 + 32, 1)
    
    return {
        "city": city,
        "temperature": temp_c if unit == "celsius" else temp_f,
        "unit": unit,
        "condition": random.choice(["sunny", "partly cloudy", "rainy", "humid"]),
        "humidity": f"{random.randint(40, 90)}%"
    }

def search_docs(query: str, max_results: int = 3) -> dict:
    """Simulated doc search. Replace with a real search index in production."""
    mock_results = [
        {
            "title": f"Documentation: {query} - Overview",
            "content": f"This section covers the basics of {query} in the Z.AI ecosystem.",
            "relevance": 0.95
        },
        {
            "title": f"Tutorial: Working with {query}",
            "content": f"Step-by-step guide to implementing {query} in your project.",
            "relevance": 0.88
        }
    ]
    return {"query": query, "results": mock_results[:max_results]}

The Function-Calling Loop

Now you wire it together. The model might call one tool, multiple tools, or no tools depending on the question. Here is a robust handler:

# Map tool names to their Python functions
tool_registry = {
    "get_weather": get_weather,
    "search_docs": search_docs
}

def run_agent(client, user_message: str, tools: list) -> str:
    messages = [
        {
            "role": "system",
            "content": "You are a helpful assistant. Use the available tools when needed to answer questions accurately."
        },
        {
            "role": "user",
            "content": user_message
        }
    ]

    while True:
        response = client.chat.completions.create(
            model="glm-5",
            messages=messages,
            tools=tools,
            tool_choice="auto",  # Let the model decide when to use tools
            max_tokens=1024,
            temperature=0.3
        )

        assistant_message = response.choices[0].message
        messages.append({"role": "assistant", "content": assistant_message.content, "tool_calls": assistant_message.tool_calls})

        # If no tool calls, we have the final answer
        if not assistant_message.tool_calls:
            return assistant_message.content

        # Process each tool call
        for tool_call in assistant_message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)

            print(f"  Calling tool: {function_name}({function_args})")

            if function_name in tool_registry:
                result = tool_registry[function_name](**function_args)
            else:
                result = {"error": f"Unknown tool: {function_name}"}

            # Send the result back to the model
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": json.dumps(result)
            })

    # The loop continues until the model gives a final text response

This pattern is the core of every real-world agent. The model decides when to use tools, you execute them, and the loop keeps going until the model has everything it needs to respond.


Structured Outputs: Getting JSON You Can Actually Use

Sometimes you do not want a paragraph of text. You want a clean, predictable JSON object that your application can parse and act on.

Why Structure Matters in Production

Imagine you are building a system that extracts information from support tickets. You need the ticket category, priority level, and a short summary. If the model returns those as a paragraph, you are stuck parsing natural language. If it returns them as JSON, you can feed them directly into your database.

Requesting JSON Output

You can request structured output two ways. The simpler approach is to ask for it in your system prompt:

response = client.chat.completions.create(
    model="glm-5",
    messages=[
        {
            "role": "system",
            "content": """You are a support ticket analyzer. 
            Always respond in valid JSON with this exact structure:
            {
                "category": "string",
                "priority": "low|medium|high|critical",
                "summary": "string (max 50 words)",
                "suggested_action": "string"
            }
            Do not include any text outside the JSON."""
        },
        {
            "role": "user",
            "content": "User reports: App crashes every time they try to upload a file larger than 10MB. Affects our paying customers."
        }
    ],
    max_tokens=512,
    temperature=0.1  # Low temperature for consistent structure
)

raw_text = response.choices[0].message.content

# Parse it safely
try:
    parsed = json.loads(raw_text)
    print(f"Category: {parsed['category']}")
    print(f"Priority: {parsed['priority']}")
    print(f"Summary: {parsed['summary']}")
    print(f"Action: {parsed['suggested_action']}")
except json.JSONDecodeError as e:
    print(f"Parse error: {e}")
    print(f"Raw response: {raw_text}")

Setting temperature to 0.1 here is intentional. Lower temperature means the model sticks closer to the structure you defined, which reduces the chance of it going off-script.


Building the Full Multi-Tool Agent

Now let us put it all together. A complete agent that combines thinking mode, multi-turn context, multiple tools, streaming output, and graceful error handling.

Designing the Agent Architecture

A well-designed agent has four parts:

  1. The planner โ€“ Figures out what needs to happen (GLM-5 in thinking mode)
  2. The executor โ€“ Runs tools and collects results (your Python code)
  3. The synthesizer โ€“ Combines results into a coherent response (GLM-5 in regular mode)
  4. The memory โ€“ Keeps track of the conversation context (your message history)

The Complete Agent Class

import json
import time
from typing import Optional

class GLM5Agent:
    def __init__(self, client, tools: list, system_prompt: str = None):
        self.client = client
        self.tools = tools
        self.tool_registry = {}
        self.conversation_history = []
        self.max_iterations = 10  # Safety limit on tool call loops
        
        default_prompt = (
            "You are a capable AI assistant with access to tools. "
            "Think through what the user needs carefully, use tools when appropriate, "
            "and give clear, accurate answers."
        )
        
        self.conversation_history.append({
            "role": "system",
            "content": system_prompt or default_prompt
        })

    def register_tool(self, name: str, func):
        """Register a Python function as an executable tool."""
        self.tool_registry[name] = func
        return self

    def _execute_tool(self, tool_name: str, arguments: dict) -> str:
        """Run a registered tool and return the result as a JSON string."""
        if tool_name not in self.tool_registry:
            return json.dumps({"error": f"Tool '{tool_name}' not found in registry"})
        
        try:
            start = time.time()
            result = self.tool_registry[tool_name](**arguments)
            elapsed = round(time.time() - start, 3)
            return json.dumps({"result": result, "execution_time_seconds": elapsed})
        except Exception as e:
            return json.dumps({"error": str(e), "tool": tool_name})

    def run(self, user_message: str, use_thinking: bool = False, stream_final: bool = False) -> str:
        """Process a user message and return the agent's response."""
        
        self.conversation_history.append({
            "role": "user",
            "content": user_message
        })

        iteration = 0

        while iteration < self.max_iterations:
            iteration += 1

            # Build request parameters
            request_params = {
                "model": "glm-5",
                "messages": self.conversation_history,
                "tools": self.tools,
                "tool_choice": "auto",
                "max_tokens": 1024,
                "temperature": 0.4
            }

            if use_thinking:
                request_params["extra_body"] = {"thinking": True}

            response = self.client.chat.completions.create(**request_params)
            assistant_msg = response.choices[0].message

            # Store assistant message in history
            history_entry = {
                "role": "assistant",
                "content": assistant_msg.content or ""
            }
            if assistant_msg.tool_calls:
                history_entry["tool_calls"] = assistant_msg.tool_calls
            
            self.conversation_history.append(history_entry)

            # Print thinking process if available
            if use_thinking and hasattr(assistant_msg, "reasoning_content") and assistant_msg.reasoning_content:
                print(f"\n[Thinking]\n{assistant_msg.reasoning_content}\n")

            # If no tool calls, we have the final answer
            if not assistant_msg.tool_calls:
                final_answer = assistant_msg.content or "No response generated."
                print(f"\nAgent: {final_answer}")
                return final_answer

            # Execute tool calls
            print(f"\n[Tools requested: {len(assistant_msg.tool_calls)}]")
            
            for tool_call in assistant_msg.tool_calls:
                name = tool_call.function.name
                args = json.loads(tool_call.function.arguments)
                
                print(f"  Running: {name}({args})")
                result = self._execute_tool(name, args)
                
                self.conversation_history.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": result
                })

        return "Max iteration limit reached. Stopping agent loop."

    def reset(self):
        """Clear conversation history but keep system prompt."""
        system = self.conversation_history[0]
        self.conversation_history = [system]

Putting the Agent to Work

With the class defined, using it looks like this:

# Initialize agent with tools
agent = GLM5Agent(
    client=client,
    tools=tools,
    system_prompt="You are a smart assistant. Use weather and documentation tools to answer questions thoroughly."
)

# Register tool implementations
agent.register_tool("get_weather", get_weather)
agent.register_tool("search_docs", search_docs)

# Run it
response = agent.run(
    user_message="What is the weather like in Abuja today? Also search the docs for how to configure rate limiting.",
    use_thinking=True
)

The agent will:

  1. Think through what is being asked
  2. Call get_weather for Abuja
  3. Call search_docs for rate limiting
  4. Receive both results
  5. Combine them into one coherent answer

That is a real agent loop, not a demo.


Error Handling and Reliability in Production

A tutorial agent that crashes on unexpected input is useless in production. Here are the patterns you need to make your agent reliable.

Wrapping API Calls with Retry Logic

Network calls fail. Rate limits happen. Wrap your API calls with exponential backoff:

import time

def call_with_retry(fn, max_retries=3, base_delay=1.0):
    """Retry a function call with exponential backoff on failure."""
    for attempt in range(max_retries):
        try:
            return fn()
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            wait = base_delay * (2 ** attempt)
            print(f"Attempt {attempt + 1} failed: {e}. Retrying in {wait}s...")
            time.sleep(wait)

Validating Tool Arguments

Before executing a tool, check that the arguments the model passed are actually valid:

def safe_execute(tool_name: str, arguments: dict, schema: dict) -> dict:
    """Validate arguments against expected schema before running a tool."""
    required = schema.get("required", [])
    missing = [k for k in required if k not in arguments]
    
    if missing:
        return {"error": f"Missing required arguments: {missing}"}
    
    return tool_registry[tool_name](**arguments)

Capping Iteration Depth

Always set a maximum number of tool-call iterations. Without it, a confused model could loop indefinitely:

MAX_ITERATIONS = 8

if iteration >= MAX_ITERATIONS:
    print("Agent hit iteration limit. Returning partial result.")
    return assistant_msg.content or "Could not complete task within iteration limit."

These three practices alone will save you a lot of headaches when your agent hits something it was not expecting.


Moving to Production: What Actually Changes

Running GLM-5 in a notebook and running it in production are two different things. Here is what changes.

Managing API Keys Securely

Never hardcode API keys. In production, use environment variables loaded from a secrets manager:

import os
from functools import lru_cache

@lru_cache(maxsize=1)
def get_api_key() -> str:
    key = os.getenv("ZAI_API_KEY")
    if not key:
        raise EnvironmentError("ZAI_API_KEY not set in environment")
    return key

Logging Agent Actions

In production, you need to know what your agent did and why. Log every tool call and response:

import logging
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("glm5_agent")

def log_tool_call(tool_name: str, args: dict, result: dict):
    logger.info(
        "tool_call",
        extra={
            "tool": tool_name,
            "args": args,
            "result_size": len(str(result)),
            "timestamp": datetime.utcnow().isoformat()
        }
    )

Token Cost Tracking

Track token usage per session so you can monitor costs and optimize prompts:

class TokenTracker:
    def __init__(self):
        self.total_prompt_tokens = 0
        self.total_completion_tokens = 0
    
    def track(self, usage):
        self.total_prompt_tokens += usage.prompt_tokens
        self.total_completion_tokens += usage.completion_tokens
    
    def report(self):
        total = self.total_prompt_tokens + self.total_completion_tokens
        print(f"Session tokens: {self.total_prompt_tokens} prompt + {self.total_completion_tokens} completion = {total} total")

Real-World Use Cases for GLM-5 Agents

The patterns in this guide are not just for toy demos. Here are actual use cases where this architecture makes sense.

Customer Support Automation

An agent with access to a ticket database, a knowledge base search tool, and an escalation tool can handle a large percentage of tier-1 support queries without human intervention. Thinking mode helps it reason through ambiguous customer complaints.

Code Review Assistance

An agent that can read files, run linting tools, and search documentation can give contextual code review feedback. You would define tools for file reading, command execution (sandboxed), and doc lookup.

Research and Summarization Pipelines

An agent with a web search tool and a document parser can pull information from multiple sources, synthesize them, and generate structured reports. Multi-turn context lets it refine its research based on follow-up questions.

Data Extraction at Scale

Pair GLM-5's structured output capability with a batch processing loop, and you can run thousands of extractions per hour. The model reads unstructured text, extracts fields you defined, and returns clean JSON.


What Makes This Stack Worth Your Time

A few things stand out about building on GLM-5 specifically.

The OpenAI-compatible interface means your existing knowledge transfers directly. If you have ever built anything with OpenAI's Python SDK, you are not starting from zero.

Thinking mode is genuinely useful for complex tasks. It is not a gimmick. Models that reason step-by-step before answering make fewer obvious mistakes, especially on tasks that require logic or multi-step planning.

The function calling interface is clean and well-designed. You write normal Python functions, describe them in JSON, and the model handles routing. You do not need a separate orchestration library for basic agent workflows.

And the free API tier means you can build, test, and prototype without a billing card. That matters a lot when you are learning.


A Note on What Comes Next

This guide covered the full stack: setup, streaming, thinking mode, multi-turn conversations, function calling, structured outputs, a complete agent class, error handling, and production patterns.

From here, the logical next steps are:

  • Connecting real APIs instead of mock functions (weather services, databases, REST APIs)
  • Adding persistent memory using a vector database for long-term context
  • Deploying your agent behind a web server so other people can use it
  • Experimenting with multi-agent patterns, where one agent coordinates others

Each of those builds directly on what you learned here. The foundation is solid.

Take the code in this guide, run it, break it, adapt it. That is how you actually learn this stuff. The agent you build after reading a tutorial and the one you build after spending three hours debugging tool call parsing are completely different in terms of what you understand. Go do the second kind of building.

โ€ข API Keys: https://z.ai/manage-apikey/apikey-list

More Posts:

Subscription Form