Skip to content

feat: add GLM (Z.ai) LLM provider - #10216

Open
shawonis08 wants to merge 3 commits into
pgadmin-org:masterfrom
shawonis08:feat/add-llm-provider
Open

feat: add GLM (Z.ai) LLM provider#10216
shawonis08 wants to merge 3 commits into
pgadmin-org:masterfrom
shawonis08:feat/add-llm-provider

Conversation

@shawonis08

@shawonis08 shawonis08 commented Jul 29, 2026

Copy link
Copy Markdown

Adds GLM (Z.ai) as an LLM provider for pgAdmin's AI features, alongside
Anthropic, OpenAI, Ollama and Docker Model Runner. GLM exposes an
OpenAI-compatible API, so GLMClient subclasses OpenAIClient and
overrides only where Z.ai diverges.

Configuration

  • GLM_API_URL — defaults to https://api.z.ai/api/paas/v4
  • GLM_API_KEY_FILE — defaults to ~/.zai-api-key
  • GLM_API_MODEL — no default (see below)

ALLOWED_LLM_API_URLS gains https://api.z.ai:443.
Allowlist matching is scheme+host+port, so the single api.z.ai entry also covers Coding Plan subscriptions at
https://api.z.ai/api/coding/paas/v4.

Testing

Verified against the live Z.ai API: streaming text, tool calling, usage
reporting, the no-model guard, and the reasoning-only path.

Unit tests cover URL allowlisting for all three platforms (including the
Coding Plan path), preference resolution and rejection, the model-refresh
endpoints, and LLM status reporting.

Docs updated in ai_tools.rst and preferences.rst.

Summary by CodeRabbit

  • New Features

    • Added GLM (Z.ai) as a supported AI provider.
    • Added GLM configuration for API URL, API key file, and model selection.
    • Added model discovery and refresh capabilities for GLM.
    • Added validation for GLM URLs and API key file paths.
    • GLM can now be selected as the default AI provider.
  • Documentation

    • Updated AI Reports, Preferences, and LLM configuration documentation with GLM setup and troubleshooting guidance.

Copilot AI and others added 3 commits July 29, 2026 07:36
Add first-class GLM (Z.ai) provider support to pgAdmin AI configuration and LLM runtime
The GLM provider defaulted to https://open.bigmodel.cn/api/paas/v4, which
is Zhipu's China platform, not Z.ai. A Z.ai API key sent there is rejected,
so every AI assistant request failed with a 401. ALLOWED_LLM_API_URLS
compounded this by allowlisting only open.bigmodel.cn, so overriding the
URL in preferences was rejected as well.

Default to https://api.z.ai/api/paas/v4 as documented at docs.z.ai, and
allowlist api.z.ai alongside open.bigmodel.cn so the Z.ai open platform,
Coding Plan subscriptions (https://api.z.ai/api/coding/paas/v4) and the
China platform all work. Allowlist matching is scheme+host+port, so one
api.z.ai entry covers both Z.ai paths.

Also fixed while verifying against the live API:

- Send max_tokens rather than the inherited max_completion_tokens. Z.ai
  ignores the latter outright, silently removing the output limit instead
  of failing. Overridden in GLMClient only; it is the sole subclass of
  OpenAIClient, so OpenAI and Docker keep the parameter they want.

- Never fall back to the Responses API. Z.ai exposes no /responses
  endpoint, so an unrelated error matching the inherited heuristic would
  switch the client onto a path that cannot work and mask the real error.

- Track reasoning_content when reading a Chat Completions stream. GLM-4.6+
  returns chain-of-thought there, and a response that spends its whole
  budget reasoning has no content and no tool calls, which surfaced as a
  misleading "No response content returned from API". Report it as the
  token-limit error it is. OpenAI never sends this field, so its behaviour
  is unchanged.

- Drop the hardcoded glm-4.5 default. The available models differ across
  the three platforms, and OpenAIClient's fallback meant an unconfigured
  GLM silently POSTed "model": "gpt-4o" to Z.ai and 404'd. get_llm_client()
  now raises a message naming the preference to set, and is_available()
  requires both a key and a model.

Verified against the live Z.ai API: streaming, tool calling, usage
reporting, the no-model guard and the reasoning-only path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

GLM (Z.ai) is added as an OpenAI-compatible LLM provider with configuration, client creation, model refresh endpoints, preference integration, validation tests, and updated documentation.

Changes

GLM Provider Support

Layer / File(s) Summary
Configuration and provider resolution
web/config.py, web/pgadmin/llm/utils.py, web/pgadmin/preferences/__init__.py
Adds GLM API URL, key-file, model, allowlist, resolution, default-provider, and automatic preference mappings.
GLM client implementation
web/pgadmin/llm/providers/*, web/pgadmin/llm/client.py
Adds GLMClient, availability and authentication checks, Z.ai token handling, disabled Responses API fallback, and client-factory integration.
Preferences and model management
web/pgadmin/llm/__init__.py
Adds GLM preference fields, status handling, exposed model routes, model fetching, and refresh validation.
Validation, tests, and documentation
web/pgadmin/llm/tests/*, docs/en_US/*, web/pgadmin/llm/README.md
Covers GLM URL/key validation, status and refresh endpoints, and documents GLM configuration and troubleshooting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PreferencesUI
  participant refresh_glm_models
  participant GLMModelFetcher
  PreferencesUI->>refresh_glm_models: submit API URL and key file
  refresh_glm_models->>refresh_glm_models: validate URL and key file
  refresh_glm_models->>GLMModelFetcher: fetch GLM models
  GLMModelFetcher-->>PreferencesUI: return models or error
Loading

Possibly related PRs

Suggested reviewers: dpage, asheshv

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely matches the main change: adding the GLM (Z.ai) LLM provider.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/pgadmin/llm/__init__.py`:
- Around line 253-258: The GLM API URL guidance must explain that remote custom
endpoints require administrator allowlisting. Update the preference help text in
web/pgadmin/llm/__init__.py around the GLM URL setting to mention
ALLOWED_LLM_API_URLS and the required scheme, host, and port; also document this
configuration requirement for non-Z.ai custom URLs in docs/en_US/preferences.rst
around lines 86-89.

In `@web/pgadmin/llm/README.md`:
- Around line 23-27: Update the configuration list in the LLM README to include
docker among the supported DEFAULT_LLM_PROVIDER values and document
GLM_API_MODEL as required for GLM initialization, alongside GLM_API_KEY_FILE.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 66dc7b2a-3621-4e6e-b03a-ca8b6769b4fd

📥 Commits

Reviewing files that changed from the base of the PR and between 6e029ab and c4c1bd9.

📒 Files selected for processing (13)
  • docs/en_US/ai_tools.rst
  • docs/en_US/preferences.rst
  • web/config.py
  • web/pgadmin/llm/README.md
  • web/pgadmin/llm/__init__.py
  • web/pgadmin/llm/client.py
  • web/pgadmin/llm/providers/__init__.py
  • web/pgadmin/llm/providers/glm.py
  • web/pgadmin/llm/tests/test_api_url_validation.py
  • web/pgadmin/llm/tests/test_llm_status.py
  • web/pgadmin/llm/tests/test_model_refresh_endpoints.py
  • web/pgadmin/llm/utils.py
  • web/pgadmin/preferences/__init__.py

Comment on lines +253 to +258
help_str=gettext(
'URL for the GLM API endpoint. Leave empty to use '
'the default (https://api.z.ai/api/paas/v4). Use '
'https://api.z.ai/api/coding/paas/v4 for a Coding Plan '
'subscription, or set a custom URL to use another '
'GLM-compatible API provider.'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Explain the allowlist requirement for remote custom endpoints.

The default allowlist accepts api.z.ai and loopback hosts only, so a remote custom GLM-compatible URL will be rejected unless an administrator adds its scheme, host, and port to ALLOWED_LLM_API_URLS.

  • web/pgadmin/llm/__init__.py#L253-L258: State this constraint in the preference help text.
  • docs/en_US/preferences.rst#L86-L89: Document the required administrator allowlist configuration for non-Z.ai custom URLs.
📍 Affects 2 files
  • web/pgadmin/llm/__init__.py#L253-L258 (this comment)
  • docs/en_US/preferences.rst#L86-L89
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/pgadmin/llm/__init__.py` around lines 253 - 258, The GLM API URL guidance
must explain that remote custom endpoints require administrator allowlisting.
Update the preference help text in web/pgadmin/llm/__init__.py around the GLM
URL setting to mention ALLOWED_LLM_API_URLS and the required scheme, host, and
port; also document this configuration requirement for non-Z.ai custom URLs in
docs/en_US/preferences.rst around lines 86-89.

Comment thread web/pgadmin/llm/README.md
Comment on lines +23 to 27
- `DEFAULT_LLM_PROVIDER`: Set to 'anthropic', 'openai', 'glm', or 'ollama'
- `ANTHROPIC_API_KEY_FILE`: Path to file containing Anthropic API key
- `OPENAI_API_KEY_FILE`: Path to file containing OpenAI API key
- `GLM_API_KEY_FILE`: Path to file containing GLM API key
- `OLLAMA_API_URL`: URL for Ollama server (e.g., 'http://localhost:11434')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the required GLM model and Docker provider.

Line 23 omits supported docker; Lines 24-27 omit GLM_API_MODEL, although GLM initialization fails until a model is selected. Add both to keep config-file setup usable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/pgadmin/llm/README.md` around lines 23 - 27, Update the configuration
list in the LLM README to include docker among the supported
DEFAULT_LLM_PROVIDER values and document GLM_API_MODEL as required for GLM
initialization, alongside GLM_API_KEY_FILE.

@dpage

dpage commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Thanks for this, and apologies for the slow response. The work here is careful:
the no-default-model guard is exactly the right instinct, and I appreciate that
the comments explain why each override exists rather than restating what the
code does. I've read it in full and run the tests locally, rebased onto current
master, where pycodestyle is clean and runtests.py --pkg llm gives 199
passed with no failures, so the ten new tests all do what they claim.

I'd like to take it in a different direction, though, and I think the change
gets smaller rather than larger as a result.

The two overrides aren't GLM-specific

GLMClient overrides precisely two things, and neither is really about Z.ai:

  • max_tokens instead of max_completion_tokens, because max_completion_tokens
    is an OpenAI-ism that most compatible servers either ignore or reject.
  • Never falling back to the Responses API, because /responses is an OpenAI
    endpoint that third-party servers don't implement.

Both of those are true of every OpenAI-compatible endpoint we already tell
people to use, so LM Studio, LiteLLM and vLLM users are hitting the same two
problems today and getting no benefit from a GLM subclass. Your comment on
_should_use_responses_api actually diagnoses the general case rather well:
an unrelated error whose text happens to match the inherited heuristic switches
the client onto a path that cannot work and masks the real failure. That's a
bug in OpenAIClient for anyone off api.openai.com, not a GLM quirk.

So rather than a subclass per vendor, I'd prefer we confine the OpenAI-specific
behaviour to the case where we're actually talking to OpenAI. That fixes the
whole compatible-provider cohort in one go, and it means GLM then works through
the existing OpenAI provider with a custom API URL, with no new provider
entry, no new preferences, and no new default allowlist entry. I'd rather keep
ALLOWED_LLM_API_URLS as a small curated default and have users add whichever
host they actually use to config_local.py, instead of growing it by one line
per vendor.

What that looks like

All in web/pgadmin/llm/providers/openai.py:

  1. Add a small helper that decides whether we're on the official endpoint, by
    comparing urlparse(self._base_url).hostname against api.openai.com.
    Please use host equality rather than the startswith currently at line 99,
    which also matches something like https://api.openai.com.example.net/v1.
    It fails safe today, so it's not urgent on its own, but it's worth tidying
    whilst we're in there.
  2. In _build_chat_payload (line 204), send max_completion_tokens when the
    helper says official and max_tokens otherwise. Sending only one of the two
    matters, since some servers validate strictly and reject unknown fields.
  3. In _should_use_responses_api (line 178), return False unless official,
    which is your override generalised.
  4. Reuse the helper in is_available (line 99).

For tests, the existing scenarios in test_openai_stream.py are the right home:
cases asserting which token-limit key ends up in the payload for an official
versus a custom base URL, and that a custom host never switches to the Responses
API even when the error text matches the heuristic. The GLM-specific scenarios in
test_api_url_validation.py, test_llm_status.py and
test_model_refresh_endpoints.py can all go, along with providers/glm.py, the
glm preferences and the config.py additions.

One thing worth checking before we commit to documenting GLM at all

I'm not sure a real AI Report completes against a GLM model, and I'd value your
opinion since you have a working key.

Z.ai's chat-completions reference states that thinking defaults to enabled
across the GLM-4.5, 4.6 and 5.x line, and that GLM-4.7 in particular "will think
compulsorily". Reasoning arrives as reasoning_content, which pgAdmin never
reads: both _read_openai_stream and _parse_response look only at
delta.content and message.content. Meanwhile the report pipeline caps output
at max_tokens=500 for the planning stage
(web/pgadmin/llm/reports/pipeline.py:221) and 1500 for section analysis
(line 294).

If reasoning tokens count against max_tokens, and Z.ai's docs don't say either
way, then planning comes back with empty content and finish_reason: length,
and _raise_max_tokens_error tells the user "The request is too large for model
glm-4.7. Try using a model with a larger context window", which is both the wrong
diagnosis and unactionable advice. Your PR description mentions verifying "the
reasoning-only path", so I suspect you've seen something in this area: what did
it actually do? If it is a problem, then thinking: {'type': 'disabled'} would
handle the 4.6 and 5.x models but not 4.7, and we'd need to raise those two
budgets instead, which is a separate change we should make on its own merits.

Documentation

Don't worry about the data-handling side of the docs, as I'll write a
provider-neutral section covering what gets transmitted to a cloud provider and
under whose terms, which is broader than this change and something I'd rather
word myself.

What would help from you is adding GLM (Z.ai) to the existing lists of
OpenAI-compatible providers, at docs/en_US/ai_tools.rst:78 and
docs/en_US/preferences.rst:102, alongside LiteLLM, LM Studio and EXO, together
with a sentence making clear that any such host has to be added to
ALLOWED_LLM_API_URLS by an administrator before pgAdmin will talk to it. That
last point is CodeRabbit's, and it's a fair one: at the moment a custom remote
URL is silently rejected with no hint that the fix lies in server configuration.

Whilst you're in web/pgadmin/llm/README.md, the DEFAULT_LLM_PROVIDER list
there still omits docker, and the Supported Providers list omits Docker Model
Runner. Both predate your PR but the diff already touches those lines, so it'd
be good to fold them in.

No release-notes entry is needed, incidentally: we populate those in a batch
shortly before release now, so please don't add one.

Finally, the branch is 51 commits behind master. It rebases cleanly, so a
git rebase upstream/master before the next push would help.

Happy to talk any of this through if you'd rather, and thanks again for taking
the time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants