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.
!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.
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.
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, ensure_ascii=False), "tool_call_id": tc.id, }) final = client.chat.completions.create( model="glm-5", messages=messages, tools=tools, max_tokens=1024, ) print(f"π€ GLM-5: {final.choices[0].message.content}") else: print(f"π€ GLM-5: {assistant_msg.content}") run_tool_call("What's the weather like in Tokyo right now?") run_tool_call("What is 2^20 + 3^10 - 1024?") run_tool_call("Compare the weather in San Francisco and London, and calculate the temperature difference.") print("n" + "=" * 70) print("π SECTION 7: Structured JSON Output") print("=" * 70) print("Force GLM-5 to return well-structured JSON for downstream processing.n") response = client.chat.completions.create( model="glm-5", messages=[ { "role": "system", "content": ( "You are a data extraction assistant. " "Always respond with valid JSON only β no markdown, no explanation." ), }, { "role": "user", "content": ( "Extract structured data from this text:nn" '"Acme Corp reported Q3 2025 revenue of $4.2B, up 18% YoY. ' "Net income was $890M. The company announced 3 new products " "and plans to expand into 5 new markets by 2026. CEO Jane Smith " 'said she expects 25% growth next year."nn' "Return JSON with keys: company, quarter, revenue, revenue_growth, " "net_income, new_products, new_markets, ceo, growth_forecast" ), }, ], max_tokens=512, temperature=0.1, ) raw_output = response.choices[0].message.content print("π Raw output:") print(raw_output) try: clean = raw_output.strip() if clean.startswith("```"): clean = clean.split("n", 1)[1].rsplit("```", 1)[0] parsed = json.loads(clean) print("nβ
Parsed JSON:") print(json.dumps(parsed, indent=2)) except json.JSONDecodeError as e: print(f"nβ οΈ JSON parsing failed: {e}") print("Tip: You can add response_format={'type': 'json_object'} for stricter enforcement")
We define two tools, a weather lookup and a math calculator, then let GLM-5 autonomously decide when to invoke them based on the userβs natural language query. We run a complete tool-calling round-trip: the model selects the function, we execute it locally, feed the result back, and GLM-5 synthesizes a final human-readable answer. We then switch to structured output, prompting GLM-5 to extract financial data from raw text into clean, parseable JSON.
print("n" + "=" * 70) print("π€ SECTION 8: Multi-Tool Agentic Loop") print("=" * 70) print("Build a complete agent that can use multiple tools across turns.n") class GLM5Agent: def __init__(self, system_prompt: str, tools: list, tool_registry: dict): self.client = ZaiClient(api_key=API_KEY) self.messages = [{"role": "system", "content": system_prompt}] self.tools = tools self.registry = tool_registry self.max_iterations = 5 def chat(self, user_input: str) -> str: self.messages.append({"role": "user", "content": user_input}) for iteration in range(self.max_iterations): response = self.client.chat.completions.create( model="glm-5", messages=self.messages, tools=self.tools, tool_choice="auto", max_tokens=2048, temperature=0.6, ) msg = response.choices[0].message self.messages.append(msg.model_dump()) if not msg.tool_calls: return msg.content for tc in msg.tool_calls: fn_name = tc.function.name fn_args = json.loads(tc.function.arguments) print(f" π§ [{iteration+1}] {fn_name}({fn_args})") if fn_name in self.registry: result = self.registry[fn_name](**fn_args) else: result = {"error": f"Unknown function: {fn_name}"} self.messages.append({ "role": "tool", "content": json.dumps(result, ensure_ascii=False), "tool_call_id": tc.id, }) return "β οΈ Agent reached maximum iterations without a final answer." extended_tools = tools + [ { "type": "function", "function": { "name": "get_current_time", "description": "Get the current date and time in ISO format", "parameters": { "type": "object", "properties": {}, "required": [], }, }, }, { "type": "function", "function": { "name": "unit_converter", "description": "Convert between units (length, weight, temperature)", "parameters": { "type": "object", "properties": { "value": {"type": "number", "description": "Numeric value to convert"}, "from_unit": {"type": "string", "description": "Source unit (e.g., 'km', 'miles', 'kg', 'lbs', 'celsius', 'fahrenheit')"}, "to_unit": {"type": "string", "description": "Target unit"}, }, "required": ["value", "from_unit", "to_unit"], }, }, }, ] def get_current_time() -> dict: return {"datetime": datetime.now().isoformat(), "timezone": "UTC"} def unit_converter(value: float, from_unit: str, to_unit: str) -> dict: conversions = { ("km", "miles"): lambda v: v * 0.621371, ("miles", "km"): lambda v: v * 1.60934, ("kg", "lbs"): lambda v: v * 2.20462, ("lbs", "kg"): lambda v: v * 0.453592, ("celsius", "fahrenheit"): lambda v: v * 9 / 5 + 32, ("fahrenheit", "celsius"): lambda v: (v - 32) * 5 / 9, ("meters", "feet"): lambda v: v * 3.28084, ("feet", "meters"): lambda v: v * 0.3048, } key = (from_unit.lower(), to_unit.lower()) if key in conversions: result = round(conversions[key](value), 4) return {"value": value, "from": from_unit, "to": to_unit, "result": result} return {"error": f"Conversion {from_unit} β {to_unit} not supported"} extended_registry = { **TOOL_REGISTRY, "get_current_time": get_current_time, "unit_converter": unit_converter, } agent = GLM5Agent( system_prompt=( "You are a helpful assistant with access to weather, math, time, and " "unit conversion tools. Use them whenever they can help answer the user's " "question accurately. Always show your work." ), tools=extended_tools, tool_registry=extended_registry, ) print("π§ User: What time is it? Also, if it's 28Β°C in Tokyo, what's that in Fahrenheit?") print(" And what's 2^16?") result = agent.chat( "What time is it? Also, if it's 28Β°C in Tokyo, what's that in Fahrenheit? " "And what's 2^16?" ) print(f"nπ€ Agent: {result}") print("n" + "=" * 70) print("βοΈ SECTION 9: Thinking Mode ON vs OFF Comparison") print("=" * 70) print("See how thinking mode improves accuracy on a tricky logic problem.n") tricky_question = ( "I have 12 coins. One of them is counterfeit and weighs differently than the rest. " ) print("βββ WITHOUT Thinking Mode βββ") t0 = time.time() r_no_think = client.chat.completions.create( model="glm-5", messages=[{"role": "user", "content": tricky_question}], thinking={"type": "disabled"}, max_tokens=2048, temperature=0.6, ) t1 = time.time() print(f"β±οΈ Time: {t1-t0:.1f}s | Tokens: {r_no_think.usage.completion_tokens}") print(f"π Answer (first 300 chars): {r_no_think.choices[0].message.content[:300]}...") print("nβββ WITH Thinking Mode βββ") t0 = time.time() r_think = client.chat.completions.create( model="glm-5", messages=[{"role": "user", "content": tricky_question}], thinking={"type": "enabled"}, max_tokens=4096, temperature=0.6, ) t1 = time.time() print(f"β±οΈ Time: {t1-t0:.1f}s | Tokens: {r_think.usage.completion_tokens}") print(f"π Answer (first 300 chars): {r_think.choices[0].message.content[:300]}...")
We build a reusable GLM5Agent class that runs a full agentic loop, automatically dispatching to weather, math, time, and unit conversion tools across multiple iterations until it reaches a final answer. We test it with a complex multi-part query that requires calling three different tools in a single turn. We then run a side-by-side comparison of the same tricky 12-coin logic puzzle with thinking mode disabled versus enabled, measuring both response time and answer quality.
print("n" + "=" * 70) print("π SECTION 10: OpenAI SDK Compatibility") print("=" * 70) print("GLM-5 is fully compatible with the OpenAI Python SDK.") print("Just change the base_url β your existing OpenAI code works as-is!n") from openai import OpenAI openai_client = OpenAI( api_key=API_KEY, base_url="https://api.z.ai/api/paas/v4/", ) completion = openai_client.chat.completions.create( model="glm-5", messages=[ {"role": "system", "content": "You are a writing assistant."}, { "role": "user", "content": "Write a 4-line poem about artificial intelligence discovering nature.", }, ], max_tokens=256, temperature=0.9, ) print("π€ GLM-5 (via OpenAI SDK):") print(completion.choices[0].message.content) print("nπ Streaming (via OpenAI SDK):") stream = openai_client.chat.completions.create( model="glm-5", messages=[ { "role": "user", "content": "List 3 creative use cases for a 744B parameter MoE model. Be brief.", } ], stream=True, max_tokens=512, ) for chunk in stream: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) print() print("n" + "=" * 70) print("π Tutorial Complete!") print("=" * 70) print(""" You've learned how to use GLM-5 for: β
Basic chat completions β
Real-time streaming responses β
Thinking mode (chain-of-thought reasoning) β
Multi-turn conversations with context β
Function calling / tool use β
Structured JSON output extraction β
Building a multi-tool agentic loop β
Comparing thinking mode ON vs OFF β
Drop-in OpenAI SDK compatibility π Next steps: β’ GLM-5 Docs: https://docs.z.ai/guides/llm/glm-5 β’ Function Calling: https://docs.z.ai/guides/capabilities/function-calling β’ Structured Output: https://docs.z.ai/guides/capabilities/struct-output β’ Context Caching: https://docs.z.ai/guides/capabilities/cache β’ Web Search Tool: https://docs.z.ai/guides/tools/web-search β’ GitHub: https://github.com/zai-org/GLM-5 β’ API Keys: https://z.ai/manage-apikey/apikey-list π‘ Pro tip: GLM-5 also supports web search and context caching via the API for even more powerful applications! """)
We demonstrate that GLM-5 works as a drop-in replacement with the standard OpenAI Python SDK; we simply point base_url, and everything works identically. We test both a standard completion for creative writing and a streaming call that lists use cases for a 744B MoE model. We wrap up with a full summary of all ten capabilities covered and links to the official docs for deeper exploration.
Check outΒ theΒ Full Codes Notebook here. Β Also,Β feel free to follow us onΒ TwitterΒ and donβt forget to join ourΒ 120k+ ML SubRedditΒ and Subscribe toΒ our Newsletter. Wait! are you on telegram?Β now you can join us on telegram as well.
