Introduction
I recently had time to explore the latest models available via the Mistral API and was surprised to find GLM 5.2 offered through Mistral. It seems they are replacing their own large model with this open-weight alternative. Eager to test its performance, I decided to integrate it into my Pi.dev setup, particularly to see how it would work with my dynamic routing extension. As I mentioned in my previous blog post about Mistral AI, Pi.dev, and GSD as an alternative to Claude, the combination of these tools offers a flexible and powerful setup. This time, I’m extending that setup with GLM 5.2 and dynamic model routing.

Setting Up GLM 5.2 with Pi.dev
To get started, I needed to configure GLM 5.2 via the Mistral API in my Pi harness. While there are plenty of examples for other proxies, Mistral-specific configurations were missing. Here’s how I did it:
- Edit the
models.jsonfile located in the Pi home directory:~/.pi/agents/models.json - Add the following configuration for Mistral-Zai:
"mistral-zai": {
"baseUrl": "https://api.mistral.ai/v1",
"api": "openai-completions",
"apiKey": "$MISTRAL_API_KEY",
"compat": {
"supportsStore": false,
"supportsDeveloperRole": false,
"supportsReasoningEffort": false,
"maxTokensField": "max_tokens"
},
"models": [
{
"id": "zai-glm-5-2",
"name": "GLM 5.2 (via Mistral)",
"reasoning": false,
"input": ["text"],
"contextWindow": 1000000,
"maxTokens": 128000,
"cost": { "input": 1.4, "output": 4.4, "cacheRead": 0.14, "cacheWrite": 0 }
}
]
}
- Authenticate by running the following command in the Pi CLI:
login mistral-ai
Without this, you won’t be able to access the model. Once authenticated, GLM 5.2 will appear in the /models command output.
Experimenting with the Dynamic Model Router
So when testing the GLM Model with the Dynamic Model Router I found some more Issues, including duplicate implementations of the same functionality—likely a side effect of Mistral’s „cheap“ Codestral model. This led me to release not just version 1.3.1, but also 1.4.0, as I continued fixing issues and addressing strange behaviors.
Key Improvements in Versions 1.3.0–1.4.0
Building on the concepts I explored in my earlier post on smart model routing to prevent vendor lock-in, the latest updates to the dynamic model router take flexibility and control to the next level. The core challenge remains the same: How do we efficiently match and route models across providers like Mistral, OpenRouter, and Claude without getting locked into a single ecosystem? The solution in versions 1.3.0–1.4.0 is a three-stage matching pipeline that combines authoritative mappings, deterministic fallbacks, and LLM-powered semantic matching.
Intelligent Model Assignment via LLM-Powered Matching
The Core Problem:
The router manages hundreds of models across providers like Mistral, OpenRouter, Claude, and Ollama. However, the GDPval benchmark database uses normalized slugs (e.g., glm-5-2, mistral-medium-3-5, claude-opus-5). Previously, model IDs were matched using token-based matching, which failed for complex names like mistral-zai/zai-glm-5-2.
The Solution: A Three-Stage Matching Pipeline
- Model Map (Authoritative): Explicit mappings in
model-map.yaml. - Token-Set Fallback (Deterministic): Fuzzy matching via token overlap.
- LLM-Powered Matching (Semantic): A local LLM matches model IDs to GDPval slugs using batched prompts (40 models per batch) and plausibility pre-filtering.
Vendor/Family:
In the production pipeline (matchModelsWithLLMBatched → buildMatchPromptWithCandidates), the vendor/family rule is not explicitly stated in the prompt. Instead, it is algorithmically enforced in two ways:
candidateSlugs()(inslug-matcher.ts) pre-filters each model ID to its Top-K candidates, ensuring cross-family slugs are never offered to the LLM.isPlausibleMatch()rejects any LLM response where the model ID and slug do not share a common family (e.g.,mistral→claude).
An explicit vendor rule exists only in buildMatchPrompt() / matchModelsWithLLM, but these are not called in production (only used in tests) — meaning this is currently dead code. The functional result is the same, but enforcement relies on pre-filtering + safety net, not prompt instructions.
The LLM prompt includes generic matching rules for any provider:
- Version:
glm-5-2≠glm-4— different versions are distinct models. - Size/Tier: A 3B model (e.g.,
ministral-3b) must not matchmistral-medium.
As a safety net, isPlausibleMatch() validates every LLM suggestion, ensuring no fabricated or cross-family slugs are accepted. Additionally, parseMatchResponse() drops any slug not present in the actual GDPval slug set, preventing LLM-injected hallucinations.
Provider-Agnostic Local LLM Caller
The router now supports a local LLM caller that isn’t hardcoded to Ollama. It:
- Automatically detects whether Ollama or LM Studio is running.
- Selects the most capable available model (prioritizing speed—35B models taking 45+ seconds are deprioritized).
- Falls back to free OpenRouter models (e.g.,
openrouter/openai/gpt-4o-mini:free) if local calls fail. Ensure these are configured in your pi setup. Like use the login mechanism for the openrouter configuration.
Model Ranking:gemma4:12b > qwen3.5 > gemma2:2b (the latter is a last resort due to hallucination tendencies).
Personalized Exclude Rules
Users can now globally filter models and providers without modifying code:
- Providers: Exclude entire providers.
- Models: Glob patterns for model names.
- Paid Models: Keep free tiers while removing paid variants.
These rules are applied consistently across dynamic config generation and the live TUI table.
Layered Configuration
Three configuration layers are combined using deep-merge:
- Embedded defaults (
router-config.json) — Shipped with the system. - Global user (
~/.pi/agent/router-config.user.json) — Personal preferences. - Project-local (
<project>/.pi/router-config.json) — Project-specific overrides.
Merging Rules:
- Arrays are replaced (not merged).
- Nested objects are recursively merged.
This allows users to maintain central personal settings while keeping project-specific adjustments local.
Bugfixes
| Issue | Solution |
| Rate-Limit/Spend-Limit Failover | Previously, the router detected errors but only called recordSoftFailure(), causing retries of rate-limited models. Now, recordLimit() triggers a hard cooldown, API key rotation, and cost-mux penalty. |
| GLM-5-2 Support | GLM-5-2 was missing from router tables due to divergent implementations in index.ts and metrics.ts. Now, metrics.ts is the single source of truth. |
| Cross-Family Hallucinations | The LLM matcher incorrectly matched mistral-medium-2604 to claude-opus-5. The plausibility check now blocks cross-family matches, and model ranking prioritizes more capable models. |
Reliability Fixes
- HINT Cooldown Bug: Fixed issues where HINT resolution cleared cooldowns for fallback models, causing infinite retries.
- Context-Window Guard: Now correctly counts array-shaped message content (e.g.,
tool_resultblocks), preventing undercounts that led to hangs. - Runtime Overflow Detection: Recognizes provider-specific overflow errors and triggers Pi’s native compaction.
- Dead Fallback Cascade: Direct-model HINT overrides now pass a group name, enabling proper fallback cascades.
- Force-Retry Escalation: A shared
recordStreamFailurehelper ensures consistent escalation policies.
New Features
| Feature | Description |
| LLM-Assisted Model Matching | Uses a local LLM (Ollama/LM Studio) to match models to GDPval slugs when deterministic matching fails. |
| Layered Configuration | Deep-merge of defaults, global user overrides, and project-local overrides. |
| Personalized Exclude Rules | Filter providers/models via glob patterns in router-config.json. |
| Cross-Family Hallucination Guard | Rejects implausible LLM matches (e.g., mistral→claude). |
Context Overflow Handling
- Native Compaction Integration: Emits a native-style overflow error to trigger Pi’s compaction when all candidates fail due to context-window size.
- Cascade-First Ordering: Fallback cascades run before synthetic overflow signals, allowing larger-context models in lower-tier groups to be tried.
Testing & Infrastructure
- Test Isolation: Cross-process file lock for shared state (
scan-cache/dynamic-config) with stale-lock recovery. - Stale Config Fixes:
excludeandtimeoutoverrides now sync correctly from static config.
My Personal Setup
My current AI development setup includes:
- Mistral for general tasks.
- Claude via the
claude-bridgeextension. - OpenRouter for free models.
- Local models via Ollama, particularly for the model selection process.
For optimal performance on my M3 Mac, I use gemma4:e4b-mlx. It strikes a great balance between speed and capability.
Using the Dynamic Router
To use the dynamic router for the first time (or after adding new models to your Pi Harness setup):
- Run the command:
/router scan
This regenerates the dynamic routing table based on the GDP scores of your models.
- To preview your routing configuration, use:
/router
Model Router Output
| Group | Model | GDP | Latency | TPS | Cost (I/O) | Usage (1d/7d/30d) | Status |
|---|---|---|---|---|---|---|---|
| trivial | mistral-zai/zai-glm-5-2 | 1506.11 | 1000 | 100 | $0.0 | 10.1k/59.3k/59.3k | active |
nvidia/nemotron-3-ultra-550b-a55b:free | 1162.97 | 1000 | 100 | $0.0/$0.0 | 0/997/997 | active | |
openrouter/granite-code-3-nano-omni-30b-a3b-reasoning:free | 979.1 | 1000 | 100 | $0.0/$0.0 | 167/167/167 | active | |
mistral/mistral-medium-2604 | 933 | 1000 | 100 | $0.0 | 0/0/0 | active | |
mistral/mistral-medium-3.5 | 933 | 1000 | 100 | $1.5/$7.5 | 7.2k/7.2k/7.2k | active | |
| simple | mistral-zai/zai-glm-5-2 | 1506.11 | 1000 | 100 | $0.0 | 10.1k/59.3k/59.3k | active |
nvidia/nemotron-3-ultra-550b-a55b:free | 1162.97 | 1000 | 100 | $0.0/$0.0 | 0/997/997 | active | |
openrouter/granite-code-3-nano-omni-30b-a3b-reasoning:free | 979.1 | 1000 | 100 | $0.0/$0.0 | 167/167/167 | active | |
| standard | mistral-zai/zai-glm-5-2 | 1506.11 | 1000 | 100 | $0.0 | 10.1k/59.3k/59.3k | active |
| complex | openrouter/openai/gpt-4o-mini:free | 1725.18 | 1000 | 100 | $0.1/$0.6 | 0/0/0 | active |
claude-bridge/claude-sonnet-5 | 1603 | 1000 | 100 | $0.0/$0.0 | 8/58/58 | active | |
| strategic | openrouter/openai/gpt-4o-mini:free | 1725.18 | 1000 | 100 | $0.1/$0.6 | 0/0/0 | active |
| tactical | openrouter/openai/gpt-4o-mini:free | 1725.18 | 1000 | 100 | $0.1/$0.6 | 0/0/0 | active |
| operational | mistral-zai/zai-glm-5-2 | 1506.11 | 1000 | 100 | $0.0 | 10.1k/59.3k/59.3k | active |
| scout | mistral-zai/zai-glm-5-2 | 1506.11 | 1000 | 100 | $0.0 | 10.1k/59.3k/59.3k | active |
| fallback | mistral-zai/zai-glm-5-2 | 1506.11 | 1000 | 100 | $0.0 | 10.1k/59.3k/59.3k | active |
Dynamic Routing (Content-Based):
code_simple→operationalcode_complex→tacticaldesign→strategicplanning→tacticalexploration→scout
Integrating GSD with Dynamic Model Selection
I’m using GSD (Get Shit Done)—specifically the [opengsd/get-shit-done-redux@1.1.0] project—and it works exceptionally well with dynamic model selection. For example:
- Verification of progress: Mistral Medium is sufficient.
- Implementing new features: GLM 5.2 or Sonnet 5 are better suited.
Despite some controversy around GSD, I’ve found it to be a reliable and efficient tool for my workflow. That’s why I switched over to the opengsd project aka gsd-redux.
Conclusion
With these improvements, I finally have a robust AI development setup that leverages the best of GLM 5.2, Mistral, and dynamic model routing. The combination of intelligent matching, layered configuration, and reliability fixes ensures that my workflow is both flexible and efficient.
What do you think? Are there any specific aspects you’d like me to expand on?

Schreibe einen Kommentar