GLM-5.2 with LangChain: Direct Z.ai or ChatOpenRouter?
Independent research — not an official Z.ai publication.Identity and provider disclosure
Original route diagram based on the dated configurations and observations in this article. The sanitized result image below is generated from the archived Docker evidence.
LangChain can reach GLM-5.2 in two useful ways. A direct Z.ai profile keeps the publisher’s endpoint, model name and account boundary together. OpenRouter’s dedicated integration adds provider selection, fallback and one gateway model namespace. The second path is more flexible, but the endpoint behind the slug can change.
That distinction became operational in our July 29, 2026 test. The direct adapter returned the requested six-token marker. The gateway adapter produced a correct tool call, but a deliberately small text budget exposed a reasoning serialization edge. OpenRouter’s public catalog also contained 33 GLM-5.2 endpoints with materially different context, quantization, prices and parameter support. A working import therefore proves very little by itself.
Navigate the LangChain route test
Section titled “Navigate the LangChain route test”- Match adapter and route
- Rebuild the environment
- Configure Z.ai direct
- Configure ChatOpenRouter
- Read the live results
- Control reasoning safely
- Validate a tool call
- Constrain gateway routes
- Compare one workload cost
- Diagnose contract failures
- Require acceptance receipts
- Choose the workload path
- Review evidence and sources
Match the adapter to the commercial path
Section titled “Match the adapter to the commercial path”Start with the account product and data route, not the class name:
| Decision | Z.ai direct | OpenRouter gateway |
|---|---|---|
| Python adapter tested | langchain-openai==1.4.1 |
langchain-openrouter==0.2.7 |
| Class | ChatOpenAI |
ChatOpenRouter |
| Base URL | https://api.z.ai/api/paas/v4 |
Dedicated package default: https://openrouter.ai/api/v1 |
| Model ID | glm-5.2 |
z-ai/glm-5.2 |
| Environment variable | ZAI_API_KEY |
OPENROUTER_API_KEY |
| Provider selection | Z.ai | Policy-controlled gateway endpoint |
| Fallback | Application-owned | Optional gateway fallback |
| Best fit | First-party behavior and one processor path | Multi-provider routing and quick model switching |
The official Z.ai API introduction
documents an OpenAI-compatible interface, which is why ChatOpenAI is the
appropriate direct adapter. The current
LangChain OpenRouter integration
uses a dedicated package instead of treating the gateway as an arbitrary
OpenAI base URL.
Do not mix the identifiers. Sending z-ai/glm-5.2 to Z.ai direct can produce
a model lookup error; sending bare glm-5.2 to the gateway loses the
publisher namespace. A 401 can likewise mean that a valid key belongs to the
other account surface.
Rebuild the pinned Python environment
Section titled “Rebuild the pinned Python environment”The final test used Python 3.13.13 in a digest-pinned Docker image:
langchain-core 1.5.2langchain-openai 1.4.1langchain-openrouter 0.2.7openai 2.50.0pydantic 2.12.5Install only the route or routes the application needs:
python -m venv .venv. .venv/bin/activatepython -m pip install \ "langchain-openai==1.4.1" \ "langchain-openrouter==0.2.7"python -m pip checkThe top-level langchain package was not required for these chat integrations
and was not installed in our container. Pinning the provider packages matters
more than copying a generic pip install langchain line: adapter fields and
their underlying SDKs change independently.
PyPI listed
langchain-openrouter 0.2.7
for Python 3.10 through 3.x, with its wheel uploaded July 22. OpenRouter
published its current
ChatOpenRouter setup tutorial
on July 29. That date is a tutorial publication date, not evidence that the
Python package first launched that day.
The Docker bridge on this host could not complete its bounded DNS preflight,
so the trusted digest-pinned container used host networking with no published
port or listener. The OpenRouter wire capture used --network none. This is a
host-specific test accommodation, not a recommendation to give arbitrary
containers host networking.
Call Z.ai directly through ChatOpenAI
Section titled “Call Z.ai directly through ChatOpenAI”Keep the secret in the environment:
export ZAI_API_KEY="your-zai-api-key"test -n "$ZAI_API_KEY" && echo "ZAI_API_KEY is set"Then instantiate the adapter with the first-party base URL and bare model ID:
import osfrom langchain_openai import ChatOpenAI
llm = ChatOpenAI( model="glm-5.2", api_key=os.environ["ZAI_API_KEY"], base_url="https://api.z.ai/api/paas/v4", temperature=0, max_tokens=64, max_retries=0, timeout=90, extra_body={"thinking": {"type": "disabled"}},)
response = llm.invoke( "Return exactly LANGCHAIN_ZAI_OK and no other text.")assert response.content.strip() == "LANGCHAIN_ZAI_OK"print(response.content)Our live result was:
{ "status": "success", "model": "glm-5.2", "elapsed_ms": 6319, "content": "LANGCHAIN_ZAI_OK", "finish_reason": "stop", "usage": { "input_tokens": 18, "output_tokens": 6, "total_tokens": 24, "reasoning_tokens": 0 }}The observation proves that this exact package, endpoint, model and configuration completed one request on the test date. It is not a latency percentile or service-level claim. For a complete direct function cycle, including application execution and a second model turn, use the separate GLM-5.2 tool-calling loop.
Route GLM-5.2 through ChatOpenRouter
Section titled “Route GLM-5.2 through ChatOpenRouter”OpenRouter needs its publisher-qualified model slug and its own secret:
export OPENROUTER_API_KEY="your-openrouter-api-key"test -n "$OPENROUTER_API_KEY" && echo "OPENROUTER_API_KEY is set"This profile constrains routing to endpoints that accept the requested parameters, denies routes that collect prompts where the provider metadata supports that filter, prefers lower latency and allows fallback:
import osfrom langchain_openrouter import ChatOpenRouter
llm = ChatOpenRouter( model="z-ai/glm-5.2", api_key=os.environ["OPENROUTER_API_KEY"], temperature=0, max_tokens=1024, max_retries=0, timeout=45_000, openrouter_provider={ "allow_fallbacks": True, "require_parameters": True, "data_collection": "deny", "sort": "latency", },)
response = llm.invoke("Name the model in one short sentence.")if not response.content: raise RuntimeError( f"No final text; finish={response.response_metadata.get('finish_reason')}" )print(response.content)The 45_000 value is intentionally in milliseconds for the tested 0.2.7
path. Its source passes the field to the underlying OpenRouter SDK as
timeout_ms. Recheck the signature and source after an upgrade instead of
assuming that every LangChain adapter uses the same unit.
The provider object is a routing policy, not a provider pin. sort: "latency" can choose a different eligible backend on a later call. Use the
current OpenRouter GLM-5.2 endpoint view
and stronger provider constraints when reproducibility, region, quantization
or a one-million-token window is contractual.
Read the four live observations together
Section titled “Read the four live observations together”
Actual evidence view generated from the sanitized archive. It is not a Z.ai, OpenRouter or LangChain dashboard and contains no API key, Authorization header, cookie, response ID, tool-call ID, model reasoning or account metadata.
| Observation | Time | Finish | Measured outcome |
|---|---|---|---|
ChatOpenAI → Z.ai |
6.319 s | stop |
Exact six-token marker; 24 total tokens |
| Raw OpenRouter control | 1.627 s | stop |
Exact marker; CoreWeave selected; provider-reported cost $0.0000282 |
ChatOpenRouter text |
4.577 s | length |
Empty final text; 120 reasoning tokens inside a 128-token output cap |
ChatOpenRouter strict tool |
11.894 s | tool_calls |
Correct LookupRepo arguments; 268 total tokens |
These are four different acceptance observations, not a fair speed race. The raw gateway control disabled reasoning successfully. The dedicated adapter text control did not. The tool prompt also had more input and performed a different task. Comparing their elapsed values as a provider leaderboard would be misleading.
The raw request is a control rather than the recommended application adapter. It proves that the key, gateway model slug and selected route could produce an exact marker. The LangChain tool response proves that the dedicated package reached the gateway and parsed a real tool-call object.
Treat reasoning controls as serialized data
Section titled “Treat reasoning controls as serialized data”The first dedicated-adapter profile used:
reasoning={"enabled": False}A network-disabled loopback server captured the final JSON body. The underlying SDK serialized that input as:
{"reasoning": {}}The live model then spent 120 of 128 output tokens on reasoning, stopped for
length, and emitted no final text. This is why a completed HTTP call can
still fail an application acceptance check.
In the tested version, this alternative survived serialization:
reasoning={"effort": "none"}{"reasoning": {"effort": "none"}}We verified that wire shape without network, but deliberately did not send a
further paid call after finding it. OpenRouter’s endpoint snapshot reported
reasoning_effort support on all 33 routes on the test date; support metadata
is still a changing provider claim. A conservative production sequence is:
- leave enough
max_tokensfor reasoning plus visible output; - capture a sanitized outgoing body in a non-production test;
- run an exact-marker acceptance call;
- require non-empty content and inspect
finish_reason; - record reasoning-token usage when the adapter exposes it.
Use the GLM-5.2 reasoning-effort map for task selection. Do not treat a Python dictionary as proof that the intended wire field survived the adapter and SDK.
Validate the exported tool name and arguments
Section titled “Validate the exported tool name and arguments”Pydantic makes a compact strict schema:
from pydantic import BaseModel, Field
class LookupRepo(BaseModel): """Read a named path from a public repository."""
repository: str = Field(description="Owner and repository name") path: str = Field(description="Repository-relative path")
tool_llm = llm.bind_tools([LookupRepo], strict=True)answer = tool_llm.invoke( "Call LookupRepo exactly once for repository zai-org/GLM-5 " "and path README.md. Do not answer in prose.")
assert answer.response_metadata["finish_reason"] == "tool_calls"assert len(answer.tool_calls) == 1call = answer.tool_calls[0]assert call["name"] == "LookupRepo"assert call["args"] == { "repository": "zai-org/GLM-5", "path": "README.md",}The captured request declared additionalProperties: false, required both
fields and named the function LookupRepo. The live response matched that
schema and both requested values.
Our initial probe assertion expected lowercase lookup_repo, so its raw
exact_call flag was false. That was a test bug: the class had exported
LookupRepo, and the model returned the declared name. The archive preserves
the original false flag plus a derived valid_against_exported_schema: true
finding. Test against the schema actually sent, not a name remembered from a
different framework.
This response did not read GitHub. A complete agent must validate the arguments, authorize and execute the function, append the result as a tool message, call the model again and validate the final answer. Tool selection is one stage of the safe GLM-5.2 function loop, not proof of tool execution.
Pin gateway capabilities rather than the logo
Section titled “Pin gateway capabilities rather than the logo”The public OpenRouter endpoint API listed 33 eligible endpoints when checked:
| Catalog property | Dated count or range |
|---|---|
| Total endpoints | 33 |
| Context minimum / median / maximum | 96,890 / 1,048,576 / 1,048,576 |
| Endpoints at or above 1M context | 24 |
| Endpoints below 262,144 context | 2 |
tools supported |
33 |
tool_choice supported |
32 |
| Structured output supported | 26 |
reasoning_effort supported |
33 |
parallel_tool_calls supported |
1 |
| Quantization | 9 FP4 / 16 FP8 / 8 unspecified |
That table explains several “works locally, fails in production” cases. A request that needs tools can route widely; a request that needs parallel tool calls had only one matching endpoint in this snapshot. A one-million-token prompt is not valid merely because the top-level model page displays one million. The selected endpoint owns the actual contract.
Use require_parameters: true to exclude endpoints that do not advertise the
requested parameters. Then record the selected provider where the response
exposes it. For a strict context, quantization, region or processor contract,
pin or order providers rather than using an unconstrained balanced route.
The named Z.ai endpoint in the gateway catalog advertised FP8, 1,048,576
context, 131,072 maximum completion tokens and the same $1.40/M input,
$0.26/M cache-read and $4.40/M output list rates as the first-party
Z.ai pricing page. Other gateway
endpoints had different prices and capabilities.
Calculate one comparable token workload
Section titled “Calculate one comparable token workload”For 100,000 uncached input tokens and 10,000 output tokens:
Z.ai direct:100,000 × $1.40 / 1,000,000 = $0.140000 10,000 × $4.40 / 1,000,000 = $0.044000total $0.184000
OpenRouter catalog rate snapshot:100,000 × $0.7616 / 1,000,000 = $0.076160 10,000 × $2.3936 / 1,000,000 = $0.023936total $0.100096The apparent difference is $0.083904 before account funding fees, cache,
retries or routing changes. It is not a like-for-like checkpoint guarantee.
The catalog route can choose a different provider or quantization, whereas
the named Z.ai gateway endpoint used the first-party rate in this snapshot.
Our tiny raw OpenRouter control reported $0.0000282 for 18 input and six
output tokens. That receipt is useful for verifying billing telemetry, not
for forecasting an agent workload. Use the
GLM-5.2 cost calculator with your actual
input, output, cache and retry distribution.
Diagnose failures by contract layer
Section titled “Diagnose failures by contract layer”A 401 identifies the wrong account surface
Section titled “A 401 identifies the wrong account surface”Check the environment-variable name, key source and base URL together.
ZAI_API_KEY belongs with api.z.ai; OPENROUTER_API_KEY belongs with
openrouter.ai. Do not print the key to prove it exists. Check only that the
variable is non-empty, then run a minimal request. A valid Coding Plan key is
also not automatically general pay-as-you-go API credit.
A model lookup error often exposes namespace mismatch
Section titled “A model lookup error often exposes namespace mismatch”Use glm-5.2 on Z.ai direct and z-ai/glm-5.2 on OpenRouter. Log the model
string and base host, not credentials. If a gateway catalog no longer lists
the slug, stop instead of silently substituting a nearby model.
A context error belongs to the selected endpoint
Section titled “A context error belongs to the selected endpoint”Count input plus requested completion budget against the actual routed endpoint. In the dated catalog, endpoint context ranged from 96,890 to 1,048,576 tokens. Provider fallback can move a request to another contract. Require the needed context and leave room for tool results and the final answer.
A tool failure can be schema, route or loop
Section titled “A tool failure can be schema, route or loop”Inspect four checkpoints separately:
- the outgoing
toolsarray names the intended function; - the route advertises
tools,tool_choiceor parallel calls as required; - the response finishes with a tool call and its JSON validates;
- the application executes the authorized function and returns a tool message for a final model turn.
The tested OpenRouter route completed checkpoint three. It did not execute a repository reader.
A silent final answer may be a reasoning budget failure
Section titled “A silent final answer may be a reasoning budget failure”Require non-empty response.content, inspect finish_reason, and read
reasoning-token details when present. Our empty answer was not a network
timeout: it ended normally with length after consuming the cap. Raise the
budget or use a verified reasoning control rather than retrying the same
request blindly.
A local fixture can violate a new SDK contract
Section titled “A local fixture can violate a new SDK contract”Our first network-disabled mock response omitted system_fingerprint.
langchain-openrouter 0.2.7 raised ResponseValidationError even though many
OpenAI-compatible fixtures omit that field. Adding an explicit null value
made the fixture validate. When an upgrade breaks tests before any network
call, compare the SDK response schema before blaming the model endpoint.
Promote the integration with acceptance receipts
Section titled “Promote the integration with acceptance receipts”A production gate should record behavior, not merely config:
text = llm.invoke("Return exactly GLM52_ACCEPTED.")assert text.response_metadata.get("finish_reason") == "stop"assert text.content.strip() == "GLM52_ACCEPTED."
tool = llm.bind_tools([LookupRepo], strict=True).invoke( "Call LookupRepo for repository zai-org/GLM-5 and path README.md.")assert tool.response_metadata.get("finish_reason") == "tool_calls"assert tool.tool_calls[0]["name"] == "LookupRepo"LookupRepo.model_validate(tool.tool_calls[0]["args"])Store package versions, route, model, sanitized timing, token usage, finish reason and schema-validation result. Do not store secrets, Authorization headers, cookies, raw confidential prompts, response IDs or model reasoning. Add a budget ceiling and fail closed if the response model or selected provider violates policy.
Run this canary after adapter upgrades and before a major route change. A constructor test catches syntax errors. An exact marker catches missing final text. A strict tool test catches name and schema drift. A full tool loop catches application execution and message-order problems.
Choose a route for the workload
Section titled “Choose a route for the workload”Choose Z.ai direct when:
- first-party model behavior and documentation are the primary contract;
- one provider and processor path simplify review;
- the application owns failover and does not need gateway model switching;
- direct pricing and account support fit the deployment.
Choose ChatOpenRouter when:
- multiple eligible providers and fallback reduce integration work;
- the application benefits from one gateway namespace across models;
- provider policy filters and routing metadata are operational requirements;
- the team can test route-dependent context, quantization and parameters.
Choose neither merely because a snippet imports. Compare current privacy, rate-limit and reliability terms in the broader GLM-5.2 API provider guide. Use the OpenCode-specific configuration for an interactive coding agent; LangChain is an application framework, not a drop-in replacement for that client.
Audit ledger and source dates
Section titled “Audit ledger and source dates”The sanitized machine-readable result preserves the acceptance outcomes and endpoint counts without credentials or response identifiers. The repository archive also contains the probe, package pins, full sanitized evidence, wire fixture and image sources.
Sources checked July 29, 2026:
- OpenRouter’s ChatOpenRouter setup tutorial
- OpenRouter’s official LangChain guide
- LangChain’s official OpenRouter integration
langchain-openrouterpackage metadata- OpenRouter’s GLM-5.2 model and API view
- OpenRouter’s public GLM-5.2 endpoint catalog
- Z.ai’s GLM-5.2 model guide
- Z.ai API introduction
- Z.ai model pricing
- AI HOT discovery item for the OpenRouter tutorial
OpenRouter’s tutorial and catalog are vendor sources. The live responses, wire capture and arithmetic are original dated observations. Prices, routes, package behavior, context limits and parameter support can change; recheck the linked first-party pages and rerun the acceptance sequence before a production decision.
