Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/models/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Pydantic AI is model-agnostic and has built-in support for multiple model provid
* [Cohere](cohere.md)
* [Bedrock](bedrock.md)
* [Hugging Face](huggingface.md)
* [Zhipu AI](zhipu.md)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please move this doc to a section in models/openai.md, and move the link to the "OpenAI-compatible Providers" list below


## OpenAI-compatible Providers

Expand Down
202 changes: 202 additions & 0 deletions docs/models/zhipu.md
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please move a simplified version of this doc to a section in models/openai.md, along the same lines of the existing sections for other OpenAI-compatible providers. There's no need to document all the supported models (as the lists will get out of date) or things like streaming (as it works the same as with any other model, so we already have extensive docs).

Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Zhipu AI

## Install

To use Zhipu AI models, you need to either install `pydantic-ai`, or install `pydantic-ai-slim` with the `openai` optional group (since Zhipu AI provides an OpenAI-compatible API):

```bash
pip/uv-add "pydantic-ai-slim[openai]"
```

## Configuration

To use [Zhipu AI](https://bigmodel.cn/) (智谱AI) through their API, you need to:

1. Visit [bigmodel.cn](https://bigmodel.cn) and create an account
2. Go to [API Keys management](https://bigmodel.cn/usercenter/proj-mgmt/apikeys)
3. Create a new API key

## Environment variable

Once you have the API key, you can set it as an environment variable:

```bash
export ZHIPU_API_KEY='your-api-key'
```

You can then use Zhipu AI models by name:

```python
from pydantic_ai import Agent

agent = Agent('zhipu:glm-4.5')
...
```

Or initialise the model directly:

```python
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel

model = OpenAIChatModel('glm-4.5', provider='zhipu')
agent = Agent(model)
...
```

## `provider` argument

You can provide a custom `ZhipuProvider` via the `provider` argument:

```python
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.zhipu import ZhipuProvider

model = OpenAIChatModel(
'glm-4.5', provider=ZhipuProvider(api_key='your-api-key')
)
agent = Agent(model)
...
```

You can also customize the `ZhipuProvider` with a custom `httpx.AsyncClient`:

```python
from httpx import AsyncClient

from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.zhipu import ZhipuProvider

custom_http_client = AsyncClient(timeout=30)
model = OpenAIChatModel(
'glm-4.5',
provider=ZhipuProvider(api_key='your-api-key', http_client=custom_http_client),
)
agent = Agent(model)
...
```

## Available Models

Zhipu AI offers several models through their OpenAI-compatible API:

### GLM-4 Series

- **`glm-4.6`**: Latest flagship model with 205K context window
- **`glm-4.5`**: High-performance model with 131K context window
- **`glm-4.5-air`**: Balanced performance and cost with 131K context
- **`glm-4.5-flash`**: Fast response model with 131K context

### Vision Models

- **`glm-4v-plus`**: Advanced vision understanding model
- **`glm-4v`**: Vision model for image analysis
- **`glm-4.5v`**: Vision-enabled variant with 64K context

### Specialized Models

- **`codegeex-4`**: Code generation and understanding model

## Features

### Function Calling

Zhipu AI models support function calling (tool use):

```python
from pydantic_ai import Agent, RunContext

agent = Agent(
'zhipu:glm-4.5',
system_prompt='You are a helpful assistant with access to tools.',
)

@agent.tool
async def get_weather(ctx: RunContext[None], location: str) -> str:
"""Get the weather for a location."""
return f"The weather in {location} is sunny, 25°C"

result = await agent.run('What is the weather in Beijing?')
print(result.output)
```

### Streaming

Zhipu AI supports streaming responses:

```python
from pydantic_ai import Agent

agent = Agent('zhipu:glm-4.5')

async with agent.run_stream('Tell me a story') as response:
async for message in response.stream_text():
print(message, end='', flush=True)
```

### Vision Understanding

Use vision models to analyze images:

```python
from pydantic_ai import Agent

agent = Agent('zhipu:glm-4v-plus')

result = await agent.run(
'What is in this image?',
message_history=[
{
'role': 'user',
'content': [
{'type': 'text', 'text': 'Describe this image:'},
{'type': 'image_url', 'image_url': {'url': 'https://example.com/image.jpg'}}
]
}
]
)
print(result.output)
```

## Important Notes

### Temperature Range

Unlike OpenAI, Zhipu AI requires temperature to be in the range `(0, 1)` (exclusive). Setting `temperature=0` is not supported and will cause an error.

```python
# This will work
agent = Agent('zhipu:glm-4.5', model_settings={'temperature': 0.1})

# This will NOT work with Zhipu AIs
# agent = Agent('zhipu:glm-4.5', model_settings={'temperature': 0})
```

### Strict Mode

Zhipu AI does not support OpenAI's strict mode for tool definitions. The framework automatically handles this by setting `openai_supports_strict_tool_definition=False` in the model profile.

## Advanced Features

### Thinking Mode

GLM-4.5 and GLM-4.5-Air support a "thinking" mode for complex reasoning tasks. This can be enabled using the `extra_body` parameter:

```python
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.zhipu import ZhipuProvider

model = OpenAIChatModel('glm-4.5', provider=ZhipuProvider(api_key='your-api-key'))

# Note: Thinking mode requires using the OpenAI client directly
# or passing extra_body through model_settings
```

## API Reference

For more details, see:

- [ZhipuProvider API Reference][pydantic_ai.providers.zhipu.ZhipuProvider]
- [Zhipu AI Official Documentation](https://docs.bigmodel.cn/)
33 changes: 33 additions & 0 deletions examples/zhipu_example.py
Copy link
Collaborator

Choose a reason for hiding this comment

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

Example file not necessary, this can just be an example in the docs

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Example of using Zhipu AI with Pydantic AI.

This example demonstrates how to use Zhipu AI models with the Pydantic AI framework.

To run this example, you need to:
1. Install pydantic-ai with openai support: pip install "pydantic-ai-slim[openai]"
2. Set your Zhipu API key: export ZHIPU_API_KEY='your-api-key'
3. Run the script: python examples/zhipu_example.py
"""

from __future__ import annotations as _annotations

import asyncio

from pydantic_ai import Agent


async def main():
"""Run a simple example with Zhipu AI."""
# Create an agent using Zhipu AI's GLM-4.5 model
model_spec = 'zhipu:glm-4.5'
agent = Agent(model_spec, system_prompt='You are a helpful assistant.')

# Run a simple query
result = await agent.run('What is the capital of China?')
print(f'Response: {result.output}')
# Access the configured model name directly from the agent's model to avoid relying on
# message internals (keeps static typing happy if message schema varies by provider).
print(f'Model used: {model_spec}')


if __name__ == '__main__':
asyncio.run(main())
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ nav:
- models/groq.md
- models/mistral.md
- models/huggingface.md
- models/zhipu.md
- Tools & Toolsets:
- tools.md
- tools-advanced.md
Expand Down
9 changes: 9 additions & 0 deletions pydantic_ai_slim/pydantic_ai/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@
'openai:o3-pro-2025-06-10',
'openai:computer-use-preview',
'openai:computer-use-preview-2025-03-11',
'zhipu:glm-4.6',
'zhipu:glm-4.5',
'zhipu:glm-4.5v',
'zhipu:glm-4.5-air',
'zhipu:glm-4.5-flash',
'zhipu:glm-4v',
'zhipu:glm-4v-plus',
'zhipu:codegeex-4',
'test',
],
)
Expand Down Expand Up @@ -691,6 +699,7 @@ def infer_model(model: Model | KnownModelName | str) -> Model: # noqa: C901
'together',
'vercel',
'litellm',
'zhipu',
):
from .openai import OpenAIChatModel

Expand Down
13 changes: 13 additions & 0 deletions pydantic_ai_slim/pydantic_ai/models/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ def __init__(
'together',
'vercel',
'litellm',
'zhipu',
]
| Provider[AsyncOpenAI] = 'openai',
profile: ModelProfileSpec | None = None,
Expand Down Expand Up @@ -312,6 +313,7 @@ def __init__(
'together',
'vercel',
'litellm',
'zhipu',
]
| Provider[AsyncOpenAI] = 'openai',
profile: ModelProfileSpec | None = None,
Expand Down Expand Up @@ -339,6 +341,7 @@ def __init__(
'together',
'vercel',
'litellm',
'zhipu',
]
| Provider[AsyncOpenAI] = 'openai',
profile: ModelProfileSpec | None = None,
Expand Down Expand Up @@ -523,6 +526,16 @@ def _process_response(self, response: chat.ChatCompletion | str) -> ModelRespons
if not isinstance(response, chat.ChatCompletion):
raise UnexpectedModelBehavior('Invalid response from OpenAI chat completions endpoint, expected JSON data')

# Some OpenAI-compatible providers (currently only Zhipu/Z.ai, see issue #2723) omit the `object` field even
# though the OpenAI schema includes it. We only patch it for that provider to avoid changing validation
# error counts in tests that purposefully feed invalid OpenAI responses (which expect 4 errors, including
# a missing `object`).
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't know exactly what this is referring to, but I'd rather unconditionally set response.object = 'chat.completion' here if response.object is None, and update the tests to fake invalidity with a different field. So please simplify the check and the commend, similar to the Ollama workaround we have below

if self._provider.name == 'zhipu' and not getattr(response, 'object', None): # pragma: no branch
try: # defensive, in case attribute is read-only in future SDK versions
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not necessary, it's a Pydantic BaseModel so fields should always be writable. If that stops being the case, we can fix it when we bump the SDK.

response.object = 'chat.completion'
except Exception: # pragma: no cover
pass

if response.created:
timestamp = number_to_datetime(response.created)
else:
Expand Down
41 changes: 41 additions & 0 deletions pydantic_ai_slim/pydantic_ai/profiles/zhipu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations as _annotations

from .openai import OpenAIModelProfile


def zhipu_model_profile(model_name: str) -> OpenAIModelProfile:
"""Get the model profile for a Zhipu AI model.
Zhipu AI provides OpenAI-compatible API, so we use OpenAIModelProfile.
Args:
model_name: The Zhipu model name (e.g., 'glm-4.6', 'glm-4.5', 'glm-4.5-air', 'glm-4.5v').
Returns:
Model profile with Zhipu-specific configurations.
"""
# Vision models — docs show vision variants with a trailing `v` like `glm-4.5v`
# Ref: https://docs.bigmodel.cn/cn/guide/develop/openai/introduction
is_vision_model = model_name.startswith(('glm-4.5v', 'glm-4v')) or (
'v' in model_name and ('glm-4.5v' in model_name or 'glm-4v' in model_name)
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This check is very redundant: 'v' in model_name and model_name.startswith('glm-4.5v') are both subsets of 'glm-4.5v' in model_name. Should we just check for the v?


# Zhipu AI models support JSON schema and object output
# All GLM-4 series models support function calling
supports_tools = model_name.startswith(('glm-4', 'codegeex-4'))
Copy link
Collaborator

Choose a reason for hiding this comment

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

The variable name is misleading then right?


# Zhipu AI doesn't support temperature=0 (must be in range (0, 1))
# This is a known difference from OpenAI
openai_unsupported_model_settings = ()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is already the default so no need to include it like this right?


return OpenAIModelProfile(
supports_json_schema_output=supports_tools,
supports_json_object_output=supports_tools,
supports_image_output=is_vision_model,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do the vision models actually support image output, or just input?

openai_supports_strict_tool_definition=False, # Zhipu doesn't support strict mode
openai_supports_tool_choice_required=True,
openai_unsupported_model_settings=openai_unsupported_model_settings,
openai_system_prompt_role=None, # Use default 'system' role
openai_chat_supports_web_search=False,
openai_supports_encrypted_reasoning_content=False,
)
4 changes: 4 additions & 0 deletions pydantic_ai_slim/pydantic_ai/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ def infer_provider_class(provider: str) -> type[Provider[Any]]: # noqa: C901
from .litellm import LiteLLMProvider

return LiteLLMProvider
elif provider == 'zhipu':
from .zhipu import ZhipuProvider

return ZhipuProvider
else: # pragma: no cover
raise ValueError(f'Unknown provider: {provider}')

Expand Down
Loading
Loading