-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add MoonshotAI provider with Kimi-K2 model support #2211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
DouweM
merged 22 commits into
pydantic:main
from
zachmayer:feat/add-kimi-grok-o3pro-clean
Jul 24, 2025
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
dcd7d87
feat: add MoonshotAI provider with Kimi-K2 model support
zachmayer ab62dba
Merge branch 'main' into feat/add-kimi-grok-o3pro-clean
zachmayer 390b2f8
remove one change
zachmayer b9ccddc
Merge branch 'main' into feat/add-kimi-grok-o3pro-clean
zachmayer 5ac96cf
fix-test
zachmayer 3bb2886
respond to PR feedback
zachmayer 7360d6e
one more assert
zachmayer 9e6db4f
all moonshot models
zachmayer 15255cd
Merge branch 'main' into feat/add-kimi-grok-o3pro-clean
zachmayer 0853043
Merge branch 'main' into feat/add-kimi-grok-o3pro-clean
zachmayer 5e67bac
newline
zachmayer befbef1
pr feedback and test fix
zachmayer fa81e0f
undo-change
zachmayer 2aeaa80
Update pydantic_ai_slim/pydantic_ai/providers/moonshotai.py
zachmayer 2d475c5
Merge branch 'main' into feat/add-kimi-grok-o3pro-clean
zachmayer 59b606b
name
zachmayer e8239aa
adjust provider
zachmayer 449fd4c
fix test
zachmayer b54439d
Merge branch 'main' into feat/add-kimi-grok-o3pro-clean
zachmayer e017a86
MOONSHOTAI_API_KEY
zachmayer 01b6758
improve test
zachmayer 43bf6dc
Merge branch 'main' into feat/add-kimi-grok-o3pro-clean
zachmayer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
from __future__ import annotations as _annotations | ||
|
||
import os | ||
from typing import Literal, overload | ||
|
||
from httpx import AsyncClient as AsyncHTTPClient | ||
from openai import AsyncOpenAI | ||
|
||
from pydantic_ai.exceptions import UserError | ||
from pydantic_ai.models import cached_async_http_client | ||
from pydantic_ai.profiles import ModelProfile | ||
from pydantic_ai.profiles.moonshotai import moonshotai_model_profile | ||
from pydantic_ai.profiles.openai import ( | ||
OpenAIJsonSchemaTransformer, | ||
OpenAIModelProfile, | ||
) | ||
from pydantic_ai.providers import Provider | ||
|
||
MoonshotAIModelName = Literal[ | ||
'moonshot-v1-8k', | ||
'moonshot-v1-32k', | ||
'moonshot-v1-128k', | ||
'moonshot-v1-8k-vision-preview', | ||
'moonshot-v1-32k-vision-preview', | ||
'moonshot-v1-128k-vision-preview', | ||
'kimi-latest', | ||
'kimi-thinking-preview', | ||
'kimi-k2-0711-preview', | ||
] | ||
|
||
|
||
class MoonshotAIProvider(Provider[AsyncOpenAI]): | ||
"""Provider for MoonshotAI platform (Kimi models).""" | ||
|
||
@property | ||
def name(self) -> str: | ||
return 'moonshotai' | ||
|
||
@property | ||
def base_url(self) -> str: | ||
# OpenAI-compatible endpoint, see MoonshotAI docs | ||
return 'https://api.moonshot.ai/v1' | ||
|
||
@property | ||
def client(self) -> AsyncOpenAI: | ||
return self._client | ||
|
||
def model_profile(self, model_name: str) -> ModelProfile | None: | ||
profile = moonshotai_model_profile(model_name) | ||
|
||
# As the MoonshotAI API is OpenAI-compatible, let's assume we also need OpenAIJsonSchemaTransformer, | ||
# unless json_schema_transformer is set explicitly. | ||
# Also, MoonshotAI does not support strict tool definitions | ||
zachmayer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# https://platform.moonshot.ai/docs/guide/migrating-from-openai-to-kimi#about-tool_choice | ||
# "Please note that the current version of Kimi API does not support the tool_choice=required parameter." | ||
DouweM marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return OpenAIModelProfile( | ||
json_schema_transformer=OpenAIJsonSchemaTransformer, | ||
openai_supports_tool_choice_required=False, | ||
DouweM marked this conversation as resolved.
Show resolved
Hide resolved
|
||
supports_json_object_output=True, | ||
).update(profile) | ||
|
||
# --------------------------------------------------------------------- | ||
# Construction helpers | ||
# --------------------------------------------------------------------- | ||
@overload | ||
def __init__(self) -> None: ... | ||
|
||
@overload | ||
def __init__(self, *, api_key: str) -> None: ... | ||
|
||
@overload | ||
def __init__(self, *, api_key: str, http_client: AsyncHTTPClient) -> None: ... | ||
|
||
@overload | ||
def __init__(self, *, openai_client: AsyncOpenAI | None = None) -> None: ... | ||
|
||
def __init__( | ||
self, | ||
*, | ||
api_key: str | None = None, | ||
openai_client: AsyncOpenAI | None = None, | ||
http_client: AsyncHTTPClient | None = None, | ||
) -> None: | ||
api_key = api_key or os.getenv('MOONSHOTAI_API_KEY') | ||
if not api_key and openai_client is None: | ||
raise UserError( | ||
'Set the `MOONSHOTAI_API_KEY` environment variable or pass it via ' | ||
'`MoonshotAIProvider(api_key=...)` to use the MoonshotAI provider.' | ||
) | ||
|
||
if openai_client is not None: | ||
self._client = openai_client | ||
elif http_client is not None: | ||
self._client = AsyncOpenAI(base_url=self.base_url, api_key=api_key, http_client=http_client) | ||
else: | ||
http_client = cached_async_http_client(provider='moonshotai') | ||
self._client = AsyncOpenAI(base_url=self.base_url, api_key=api_key, http_client=http_client) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import re | ||
|
||
import httpx | ||
import pytest | ||
|
||
from pydantic_ai.exceptions import UserError | ||
from pydantic_ai.profiles.openai import OpenAIJsonSchemaTransformer, OpenAIModelProfile | ||
|
||
from ..conftest import TestEnv, try_import | ||
|
||
with try_import() as imports_successful: | ||
import openai | ||
|
||
from pydantic_ai.models.openai import OpenAIModel | ||
from pydantic_ai.providers.moonshotai import MoonshotAIProvider | ||
|
||
pytestmark = pytest.mark.skipif(not imports_successful(), reason='openai not installed') | ||
|
||
|
||
def test_moonshotai_provider(): | ||
"""Test basic MoonshotAI provider initialization.""" | ||
provider = MoonshotAIProvider(api_key='api-key') | ||
assert provider.name == 'moonshotai' | ||
assert provider.base_url == 'https://api.moonshot.ai/v1' | ||
assert isinstance(provider.client, openai.AsyncOpenAI) | ||
assert provider.client.api_key == 'api-key' | ||
|
||
|
||
def test_moonshotai_provider_need_api_key(env: TestEnv) -> None: | ||
"""Test that MoonshotAI provider requires an API key.""" | ||
env.remove('MOONSHOTAI_API_KEY') | ||
with pytest.raises( | ||
UserError, | ||
match=re.escape( | ||
'Set the `MOONSHOTAI_API_KEY` environment variable or pass it via `MoonshotAIProvider(api_key=...)`' | ||
' to use the MoonshotAI provider.' | ||
), | ||
): | ||
MoonshotAIProvider() | ||
|
||
|
||
def test_moonshotai_provider_pass_http_client() -> None: | ||
"""Test passing a custom HTTP client to MoonshotAI provider.""" | ||
http_client = httpx.AsyncClient() | ||
provider = MoonshotAIProvider(http_client=http_client, api_key='api-key') | ||
assert provider.client._client == http_client # type: ignore[reportPrivateUsage] | ||
|
||
|
||
def test_moonshotai_pass_openai_client() -> None: | ||
"""Test passing a custom OpenAI client to MoonshotAI provider.""" | ||
openai_client = openai.AsyncOpenAI(api_key='api-key') | ||
provider = MoonshotAIProvider(openai_client=openai_client) | ||
assert provider.client == openai_client | ||
|
||
|
||
def test_moonshotai_provider_with_cached_http_client() -> None: | ||
"""Test MoonshotAI provider using cached HTTP client (covers line 76).""" | ||
# This should use the else branch with cached_async_http_client | ||
provider = MoonshotAIProvider(api_key='api-key') | ||
assert isinstance(provider.client, openai.AsyncOpenAI) | ||
assert provider.client.api_key == 'api-key' | ||
|
||
|
||
def test_moonshotai_model_profile(): | ||
provider = MoonshotAIProvider(api_key='api-key') | ||
model = OpenAIModel('kimi-k2-0711-preview', provider=provider) | ||
assert isinstance(model.profile, OpenAIModelProfile) | ||
assert model.profile.json_schema_transformer == OpenAIJsonSchemaTransformer | ||
assert model.profile.openai_supports_tool_choice_required is False | ||
assert model.profile.supports_json_object_output is True |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.