In this tutorial, we explore the full capabilities of Z.AI’s GLM-5 model and build a complete understanding of how to use it for real-world, agentic applications. We start from the fundamentals by setting up the environment using the Z.AI SDK and its OpenAI-compatible interface, and then progressively move on to advanced features such as streaming responses, thinking mode for deeper reasoning, and multi-turn conversations. As we continue, we integrate function calling, structured outputs, and eventually construct a fully functional multi-tool agent powered by GLM-5. Also, we understand each capability in isolation, and also how Z.AI’s ecosystem enables us to build scalable, production-ready AI systems. Copy CodeCopiedUse a different Browser !pip install -q zai-sdk openai rich import os import json import time from datetime import datetime from typing import Optional import getpass API_KEY = os.environ.get(“ZAI_API_KEY”) if not API_KEY: API_KEY = getpass.getpass(” Enter your Z.AI API key (hidden input): “).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 configured (ends with …{API_KEY[-4:]})”) from zai import ZaiClient client = ZaiClient(api_key=API_KEY) print(” ZaiClient initialized — ready to use GLM-5!”) print(“n” + “=” * 70) print(” SECTION 2: Basic Chat Completion”) print(“=” * 70) response = client.chat.completions.create( model=”glm-5″, messages=[ {“role”: “system”, “content”: “You are a concise, expert software architect.”}, {“role”: “user”, “content”: “Explain the Mixture-of-Experts architecture in 3 sentences.”}, ], max_tokens=256, temperature=0.7, ) print(“n GLM-5 Response:”) print(response.choices[0].message.content) print(f”n Usage: {response.usage.prompt_tokens} prompt + {response.usage.completion_tokens} completion tokens”) print(“n” + “=” * 70) print(” SECTION 3: Streaming Responses”) print(“=” * 70) print(“n GLM-5 (streaming): “, end=””, flush=True) 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 = “” for chunk in stream: delta = chunk.choices[0].delta if delta.content: print(delta.content, end=””, flush=True) full_response += delta.content print(f”nn Streamed {len(full_response)} characters”) We begin by installing the Z.AI and OpenAI SDKs, then securely capture our API key through hidden terminal input using getpass. We initialize the ZaiClient and fire off our first basic chat completion to GLM-5, asking it to explain the Mixture-of-Experts architecture. We then explore streaming responses, watching tokens arrive in real time as GLM-5 generates a Python one-liner for prime checking. Copy CodeCopiedUse a different Browser print(“n” + “=” * 70) print(” SECTION 4: Thinking Mode (Chain-of-Thought)”) print(“=” * 70) print(“GLM-5 can expose its internal reasoning before giving a final answer.”) print(“This is especially powerful for math, logic, and complex coding tasks.n”) print(“─── Thinking Mode + Streaming ───n”) stream = client.chat.completions.create( model=”glm-5″, messages=[ { “role”: “user”, “content”: ( “A farmer has 17 sheep. All but 9 run away. ” “How many sheep does the farmer have left? ” “Think carefully before answering.” ), }, ], thinking={“type”: “enabled”}, stream=True, max_tokens=2048, temperature=0.6, ) reasoning_text = “” answer_text = “” for chunk in stream: delta = chunk.choices[0].delta if hasattr(delta, “reasoning_content”) and delta.reasoning_content: if not reasoning_text: print(” Reasoning:”) print(delta.reasoning_content, end=””, flush=True) reasoning_text += delta.reasoning_content if delta.content: if not answer_text and reasoning_text: print(“nn Final Answer:”) print(delta.content, end=””, flush=True) answer_text += delta.content print(f”nn Reasoning: {len(reasoning_text)} chars | Answer: {len(answer_text)} chars”) print(“n” + “=” * 70) print(” SECTION 5: Multi-Turn Conversation”) print(“=” * 70) messages = [ {“role”: “system”, “content”: “You are a senior Python developer. Be concise.”}, {“role”: “user”, “content”: “What’s the difference between a list and a tuple in Python?”}, ] r1 = client.chat.completions.create(model=”glm-5″, messages=messages, max_tokens=512, temperature=0.7) assistant_reply_1 = r1.choices[0].message.content messages.append({“role”: “assistant”, “content”: assistant_reply_1}) print(f”n User: {messages[1][‘content’]}”) print(f” GLM-5: {assistant_reply_1[:200]}…”) messages.append({“role”: “user”, “content”: “When should I use a NamedTuple instead?”}) r2 = client.chat.completions.create(model=”glm-5″, messages=messages, max_tokens=512, temperature=0.7) assistant_reply_2 = r2.choices[0].message.content print(f”n User: {messages[-1][‘content’]}”) print(f” GLM-5: {assistant_reply_2[:200]}…”) messages.append({“role”: “assistant”, “content”: assistant_reply_2}) messages.append({“role”: “user”, “content”: “Show me a practical example with type hints.”}) r3 = client.chat.completions.create(model=”glm-5″, messages=messages, max_tokens=1024, temperature=0.7) assistant_reply_3 = r3.choices[0].message.content print(f”n User: {messages[-1][‘content’]}”) print(f” GLM-5: {assistant_reply_3[:300]}…”) print(f”n Conversation: {len(messages)+1} messages, {r3.usage.total_tokens} total tokens in last call”) We activate GLM-5’s thinking mode to observe its internal chain-of-thought reasoning streamed live through the reasoning_content field before the final answer appears. We then build a multi-turn conversation where we ask about Python lists vs tuples, follow up on NamedTuples, and request a practical example with type hints, all while GLM-5 maintains full context across turns. We track how the conversation grows in message count and token usage with each successive exchange. Copy CodeCopiedUse a different Browser print(“n” + “=” * 70) print(” SECTION 6: Function Calling (Tool Use)”) print(“=” * 70) print(“GLM-5 can decide WHEN and HOW to call external functions you define.n”) tools = [ { “type”: “function”, “function”: { “parameters”: { “type”: “object”, “properties”: { “city”: { “type”: “string”, “description”: “City name, e.g. ‘San Francisco’, ‘Tokyo'”, }, “unit”: { “type”: “string”, “enum”: [“celsius”, “fahrenheit”], “description”: “Temperature unit (default: celsius)”, }, }, “required”: [“city”], }, }, }, { “type”: “function”, “function”: { “name”: “calculate”, “description”: “Evaluate a mathematical expression safely”, “parameters”: { “type”: “object”, “properties”: { “expression”: { “type”: “string”, “description”: “Math expression, e.g. ‘2**10 + 3*7′”, } }, “required”: [“expression”], }, }, }, ] def get_weather(city: str, unit: str = “celsius”) -> dict: weather_db = { “san francisco”: {“temp”: 18, “condition”: “Foggy”, “humidity”: 78}, “tokyo”: {“temp”: 28, “condition”: “Sunny”, “humidity”: 55}, “london”: {“temp”: 14, “condition”: “Rainy”, “humidity”: 85}, “new york”: {“temp”: 22, “condition”: “Partly Cloudy”, “humidity”: 60}, } data = weather_db.get(city.lower(), {“temp”: 20, “condition”: “Clear”, “humidity”: 50}) if unit == “fahrenheit”: data[“temp”] = round(data[“temp”] * 9 / 5 + 32) return {“city”: city, “unit”: unit or “celsius”, **data} def calculate(expression: str) -> dict: allowed = set(“0123456789+-*/.()% “) if not all(c in allowed for c in expression): return {“error”: “Invalid characters in expression”} try: result = eval(expression) return {“expression”: expression, “result”: result} except Exception as e: return {“error”: str(e)} TOOL_REGISTRY = {“get_weather”: get_weather, “calculate”: calculate} def run_tool_call(user_message: str): print(f”n User: {user_message}”) messages = [{“role”: “user”, “content”: user_message}] response = client.chat.completions.create( model=”glm-5″, messages=messages, tools=tools, tool_choice=”auto”, max_tokens=1024, ) assistant_msg = response.choices[0].message messages.append(assistant_msg.model_dump()) if assistant_msg.tool_calls: for tc in assistant_msg.tool_calls: fn_name = tc.function.name fn_args = json.loads(tc.function.arguments) print(f” Tool call: {fn_name}({fn_args})”) result = TOOL_REGISTRY[fn_name](**fn_args) print(f” Result: {result}”) messages.append({ “role”: “tool”, “content”: json.dumps(result,