mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 10:17:00 +00:00
feat(xai): support base_url alias and XAI_API_BASE env var (#35790)
Add `base_url` alias and `XAI_API_BASE` env variable support to
`ChatXAI.xai_api_base`, aligning the xAI integration with the pattern
used across other partner packages (OpenAI, Groq, Fireworks, etc.).
Previously the base URL was a plain string field with no alias or
env-var lookup, making it inconsistent with the rest of the ecosystem
and harder to configure in deployment environments.
## Changes
- Add `alias="base_url"` and `default_factory=from_env("XAI_API_BASE",
default="https://api.x.ai/v1/")` to `ChatXAI.xai_api_base`, matching the
convention in `langchain_openai`, `langchain_groq`, and
`langchain_fireworks`
This commit is contained in:
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any, Literal, TypeAlias, cast
|
|||||||
|
|
||||||
import openai
|
import openai
|
||||||
from langchain_core.messages import AIMessageChunk
|
from langchain_core.messages import AIMessageChunk
|
||||||
from langchain_core.utils import secret_from_env
|
from langchain_core.utils import from_env, secret_from_env
|
||||||
from langchain_openai.chat_models.base import BaseChatOpenAI
|
from langchain_openai.chat_models.base import BaseChatOpenAI
|
||||||
from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator
|
from pydantic import BaseModel, ConfigDict, Field, SecretStr, model_validator
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
@@ -397,6 +397,7 @@ class ChatXAI(BaseChatOpenAI): # type: ignore[override]
|
|||||||
|
|
||||||
model_name: str = Field(default="grok-4", alias="model")
|
model_name: str = Field(default="grok-4", alias="model")
|
||||||
"""Model name to use."""
|
"""Model name to use."""
|
||||||
|
|
||||||
xai_api_key: SecretStr | None = Field(
|
xai_api_key: SecretStr | None = Field(
|
||||||
alias="api_key",
|
alias="api_key",
|
||||||
default_factory=secret_from_env("XAI_API_KEY", default=None),
|
default_factory=secret_from_env("XAI_API_KEY", default=None),
|
||||||
@@ -405,8 +406,16 @@ class ChatXAI(BaseChatOpenAI): # type: ignore[override]
|
|||||||
|
|
||||||
Automatically read from env variable `XAI_API_KEY` if not provided.
|
Automatically read from env variable `XAI_API_KEY` if not provided.
|
||||||
"""
|
"""
|
||||||
xai_api_base: str = Field(default="https://api.x.ai/v1/")
|
|
||||||
"""Base URL path for API requests."""
|
xai_api_base: str = Field(
|
||||||
|
alias="base_url",
|
||||||
|
default_factory=from_env("XAI_API_BASE", default="https://api.x.ai/v1/"),
|
||||||
|
)
|
||||||
|
"""Base URL path for API requests.
|
||||||
|
|
||||||
|
Automatically read from env variable `XAI_API_BASE` if not provided.
|
||||||
|
"""
|
||||||
|
|
||||||
search_parameters: dict[str, Any] | None = None
|
search_parameters: dict[str, Any] | None = None
|
||||||
"""**Deprecated.** Use web search tools instead:
|
"""**Deprecated.** Use web search tools instead:
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ def test_reasoning(output_version: Literal["", "v1"]) -> None:
|
|||||||
"""Test reasoning features.
|
"""Test reasoning features.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
`grok-4` does not return `reasoning_content`, but may optionally return
|
`grok-4` does not return `reasoning_content`, but may optionally return
|
||||||
encrypted reasoning content if `use_encrypted_content` is set to `True`.
|
encrypted reasoning content if `use_encrypted_content` is set to `True`.
|
||||||
"""
|
"""
|
||||||
@@ -25,12 +26,14 @@ def test_reasoning(output_version: Literal["", "v1"]) -> None:
|
|||||||
chat_model = ChatXAI(
|
chat_model = ChatXAI(
|
||||||
model="grok-3-mini",
|
model="grok-3-mini",
|
||||||
reasoning_effort="low",
|
reasoning_effort="low",
|
||||||
|
temperature=0,
|
||||||
output_version=output_version,
|
output_version=output_version,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
chat_model = ChatXAI(
|
chat_model = ChatXAI(
|
||||||
model="grok-3-mini",
|
model="grok-3-mini",
|
||||||
reasoning_effort="low",
|
reasoning_effort="low",
|
||||||
|
temperature=0,
|
||||||
)
|
)
|
||||||
input_message = "What is 3^3?"
|
input_message = "What is 3^3?"
|
||||||
response = chat_model.invoke(input_message)
|
response = chat_model.invoke(input_message)
|
||||||
@@ -95,7 +98,7 @@ def test_reasoning(output_version: Literal["", "v1"]) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_web_search() -> None:
|
def test_web_search() -> None:
|
||||||
llm = ChatXAI(model=MODEL_NAME).bind_tools([{"type": "web_search"}])
|
llm = ChatXAI(model=MODEL_NAME, temperature=0).bind_tools([{"type": "web_search"}])
|
||||||
|
|
||||||
# Test invoke
|
# Test invoke
|
||||||
response = llm.invoke("Look up the current time in Boston, MA.")
|
response = llm.invoke("Look up the current time in Boston, MA.")
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class TestXAIStandard(ChatModelIntegrationTests):
|
|||||||
def chat_model_params(self) -> dict:
|
def chat_model_params(self) -> dict:
|
||||||
return {
|
return {
|
||||||
"model": MODEL_NAME,
|
"model": MODEL_NAME,
|
||||||
|
"temperature": 0,
|
||||||
"rate_limiter": rate_limiter,
|
"rate_limiter": rate_limiter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from langchain_openai.chat_models.base import (
|
|||||||
_convert_dict_to_message,
|
_convert_dict_to_message,
|
||||||
_convert_message_to_dict,
|
_convert_message_to_dict,
|
||||||
)
|
)
|
||||||
|
from pydantic import SecretStr
|
||||||
|
|
||||||
from langchain_xai import ChatXAI
|
from langchain_xai import ChatXAI
|
||||||
|
|
||||||
@@ -65,6 +66,27 @@ def test_chat_xai_extra_kwargs() -> None:
|
|||||||
ChatXAI(model=MODEL_NAME, foo=3, model_kwargs={"foo": 2}) # type: ignore[call-arg]
|
ChatXAI(model=MODEL_NAME, foo=3, model_kwargs={"foo": 2}) # type: ignore[call-arg]
|
||||||
|
|
||||||
|
|
||||||
|
def test_chat_xai_base_url_alias() -> None:
|
||||||
|
llm = ChatXAI(
|
||||||
|
model=MODEL_NAME,
|
||||||
|
api_key=SecretStr("test-api-key"),
|
||||||
|
base_url="http://example.test/v1",
|
||||||
|
)
|
||||||
|
assert llm.xai_api_base == "http://example.test/v1"
|
||||||
|
assert llm.model_kwargs == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_chat_xai_api_base_from_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setenv("XAI_API_BASE", "http://env.example.test/v1")
|
||||||
|
|
||||||
|
llm = ChatXAI(
|
||||||
|
model=MODEL_NAME,
|
||||||
|
api_key=SecretStr("test-api-key"),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert llm.xai_api_base == "http://env.example.test/v1"
|
||||||
|
|
||||||
|
|
||||||
def test_function_dict_to_message_function_message() -> None:
|
def test_function_dict_to_message_function_message() -> None:
|
||||||
content = json.dumps({"result": "Example #1"})
|
content = json.dumps({"result": "Example #1"})
|
||||||
name = "test_function"
|
name = "test_function"
|
||||||
|
|||||||
12
libs/partners/xai/uv.lock
generated
12
libs/partners/xai/uv.lock
generated
@@ -655,7 +655,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain-core"
|
name = "langchain-core"
|
||||||
version = "1.2.13"
|
version = "1.2.18"
|
||||||
source = { editable = "../../core" }
|
source = { editable = "../../core" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "jsonpatch" },
|
{ name = "jsonpatch" },
|
||||||
@@ -715,7 +715,7 @@ typing = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain-openai"
|
name = "langchain-openai"
|
||||||
version = "1.1.10"
|
version = "1.1.11"
|
||||||
source = { editable = "../openai" }
|
source = { editable = "../openai" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "langchain-core" },
|
{ name = "langchain-core" },
|
||||||
@@ -726,7 +726,7 @@ dependencies = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "langchain-core", editable = "../../core" },
|
{ name = "langchain-core", editable = "../../core" },
|
||||||
{ name = "openai", specifier = ">=2.20.0,<3.0.0" },
|
{ name = "openai", specifier = ">=2.26.0,<3.0.0" },
|
||||||
{ name = "tiktoken", specifier = ">=0.7.0,<1.0.0" },
|
{ name = "tiktoken", specifier = ">=0.7.0,<1.0.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1228,7 +1228,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openai"
|
name = "openai"
|
||||||
version = "2.21.0"
|
version = "2.26.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
@@ -1240,9 +1240,9 @@ dependencies = [
|
|||||||
{ name = "tqdm" },
|
{ name = "tqdm" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/92/e5/3d197a0947a166649f566706d7a4c8f7fe38f1fa7b24c9bcffe4c7591d44/openai-2.21.0.tar.gz", hash = "sha256:81b48ce4b8bbb2cc3af02047ceb19561f7b1dc0d4e52d1de7f02abfd15aa59b7", size = 644374, upload-time = "2026-02-14T00:12:01.577Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d7/91/2a06c4e9597c338cac1e5e5a8dd6f29e1836fc229c4c523529dca387fda8/openai-2.26.0.tar.gz", hash = "sha256:b41f37c140ae0034a6e92b0c509376d907f3a66109935fba2c1b471a7c05a8fb", size = 666702, upload-time = "2026-03-05T23:17:35.874Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/56/0a89092a453bb2c676d66abee44f863e742b2110d4dbb1dbcca3f7e5fc33/openai-2.21.0-py3-none-any.whl", hash = "sha256:0bc1c775e5b1536c294eded39ee08f8407656537ccc71b1004104fe1602e267c", size = 1103065, upload-time = "2026-02-14T00:11:59.603Z" },
|
{ url = "https://files.pythonhosted.org/packages/c6/2e/3f73e8ca53718952222cacd0cf7eecc9db439d020f0c1fe7ae717e4e199a/openai-2.26.0-py3-none-any.whl", hash = "sha256:6151bf8f83802f036117f06cc8a57b3a4da60da9926826cc96747888b57f394f", size = 1136409, upload-time = "2026-03-05T23:17:34.072Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user