feat(anthropic): support 1.0 sdk (#39938)

This commit is contained in:
ccurme authored and GitHub committed 2026-08-27 09:21:00 -04:00
1 parent b0fb33f919
commit fd2fbd702b
11 files changed
+440 -35

No files matched your search

+2
View File
@@ -23,6 +23,8 @@ MIN_VERSION_LIBS = [
"langchain-text-splitters",
"numpy",
"SQLAlchemy",
"anthropic",
"openai",
]
# some libs only get checked on release because of simultaneous changes in
+6
View File
@@ -11,10 +11,16 @@ TEST_FILE ?= tests/unit_tests/
PYTEST_EXTRA ?=
integration_test integration_tests: TEST_FILE=tests/integration_tests/
SDK_SMOKE_TEST = tests/integration_tests/test_chat_models.py::test_stream
test tests:
uv run --group test pytest -vvv $(PYTEST_EXTRA) --disable-socket --allow-unix-socket $(TEST_FILE)
# Smoke each supported anthropic major first: 0.x is backed by `httpx`, 1.x by
# `httpx2`, and uv.lock only ever resolves one of them.
integration_test integration_tests:
uv run --with 'anthropic>=0.120.0,<1.0.0' --group test --group test_integration pytest -v --tb=short $(SDK_SMOKE_TEST)
uv run --with 'anthropic>=1.0.0,<2.0.0' --group test --group test_integration pytest -v --tb=short $(SDK_SMOKE_TEST)
uv run --group test --group test_integration pytest -v --tb=short -n auto --benchmark-disable --timeout 30 --retries 3 --retry-delay 1 $(TEST_FILE)
test_watch:
@@ -0,0 +1,124 @@
"""Adapt to the installed `anthropic` SDK major version.
`langchain-anthropic` supports `anthropic` 0.x and 1.x. Two differences between
them reach this package, and each is resolved here by reading the answer off the
installed SDK rather than off its version string:
- **Sampling parameters.** `anthropic>=1` dropped `temperature`, `top_p` and
`top_k` from its message-creation methods, since current models do not use
them. The API still honors them for older models, so they are relocated to
`extra_body`, which the SDK merges into the request JSON as-is. The wire
payload is identical on both majors.
- **Raw responses.** `.with_raw_response` on the async client returns
`AsyncAPIResponse`, whose `parse()` is a coroutine; `anthropic<1` returned
`LegacyAPIResponse` with a synchronous `parse()`.
`anthropic>=1` also moved its HTTP layer from `httpx` to
[`httpx2`](https://github.com/pydantic/httpx2), but that does not surface here:
this package hands the SDK only primitives (a `base_url` string, a float
`timeout`, a `proxy` URL string) and subclasses `anthropic.DefaultHttpxClient`,
which already points at the right library, so no `httpx` type crosses the
boundary. Only the unit tests inject `httpx` objects, and they resolve the
matching library themselves in `tests/unit_tests/_httpx_compat.py`.
"""
from __future__ import annotations
import inspect
import logging
import re
from collections.abc import Mapping
from functools import lru_cache
from typing import Any
import anthropic
logger = logging.getLogger(__name__)
# Sampling parameters `anthropic>=1` no longer accepts as named arguments.
_SAMPLING_PARAMS = ("temperature", "top_p", "top_k")
def _sdk_major_version() -> int | None:
"""Major version of the installed `anthropic` SDK, or `None` if unreadable.
Tolerates tag-derived strings such as `v1.0.0`, which build tooling
sometimes leaves unstripped, by reading the first integer run.
"""
match = re.match(r"\D*(\d+)", str(getattr(anthropic, "__version__", "")))
return int(match.group(1)) if match else None
@lru_cache(maxsize=1)
def _unsupported_sampling_params() -> frozenset[str]:
"""Sampling parameters the installed SDK's message methods no longer accept.
Derived from the signature of `Messages.create`, which `beta.messages` and
the async resources track. No client is instantiated, so this is safe to
call during payload construction without resolved credentials.
"""
try:
from anthropic.resources.messages import Messages
accepted = inspect.signature(Messages.create).parameters
except (ImportError, AttributeError, TypeError, ValueError):
# The SDK moved the resource or the signature is unreadable. Guess from
# the major version rather than failing: a wrong guess costs a TypeError
# on the first request, an exception here breaks every import.
major = _sdk_major_version()
logger.warning(
"Could not introspect the anthropic SDK's `Messages.create` "
"signature; falling back to its major version (%r) to decide "
"whether `temperature`/`top_p`/`top_k` are still accepted.",
major,
)
if major is not None and major >= 1:
return frozenset(_SAMPLING_PARAMS)
return frozenset()
return frozenset(p for p in _SAMPLING_PARAMS if p not in accepted)
def _route_unsupported_sampling_params(payload: dict[str, Any]) -> dict[str, Any]:
"""Move sampling params the installed SDK rejects into `extra_body`.
A no-op on `anthropic<1`, where the message methods still accept them as
named arguments. Values the caller already set in `extra_body` win, since
that is the SDK's documented escape hatch and a deliberate choice.
Args:
payload: Request payload, modified in place.
Returns:
The same payload.
"""
unsupported = _unsupported_sampling_params()
if not unsupported:
return payload
extra_body = payload.get("extra_body")
if extra_body is not None and not isinstance(extra_body, Mapping):
# A value the SDK would reject anyway. Leave the payload untouched
# rather than relocating params into something that cannot hold them.
return payload
relocated = {k: payload.pop(k) for k in unsupported if k in payload}
if not relocated:
return payload
payload["extra_body"] = {**relocated, **(extra_body or {})}
return payload
async def _aparse(raw_response: Any) -> Any:
"""Parse a raw SDK response from the async client.
`anthropic>=1` returns `AsyncAPIResponse`, whose `parse()` is a coroutine;
`anthropic<1` returned `LegacyAPIResponse`, whose `parse()` was
synchronous.
"""
parsed = raw_response.parse()
if inspect.isawaitable(parsed):
return await parsed
return parsed
@@ -84,6 +84,10 @@ from langchain_anthropic._client_utils import (
_get_default_httpx_client,
)
from langchain_anthropic._compat import _convert_from_v1_to_anthropic
from langchain_anthropic._sdk_compat import (
_aparse,
_route_unsupported_sampling_params,
)
from langchain_anthropic.data._profiles import _PROFILES
from langchain_anthropic.output_parsers import extract_tool_calls
@@ -1736,7 +1740,9 @@ class ChatAnthropic(BaseChatModel):
else:
payload["betas"] = [required_beta]
return {k: v for k, v in payload.items() if v is not None}
return _route_unsupported_sampling_params(
{k: v for k, v in payload.items() if v is not None}
)
def _create(self, payload: dict) -> Any:
try:
@@ -1826,7 +1832,7 @@ class ChatAnthropic(BaseChatModel):
base_generation_info: dict[str, Any] = {}
if self._uses_gateway:
_add_gateway_metadata(base_generation_info, raw_response)
stream = raw_response.parse()
stream = await _aparse(raw_response)
coerce_content_to_string = (
not _tools_in_params(payload)
and not _documents_in_params(payload)
@@ -2185,7 +2191,7 @@ class ChatAnthropic(BaseChatModel):
if self._uses_gateway:
_add_gateway_metadata(generation_info, raw_response)
return self._format_output(
raw_response.parse(), generation_info=generation_info, **kwargs
await _aparse(raw_response), generation_info=generation_info, **kwargs
)
def _get_llm_for_structured_output_when_thinking_is_enabled(
@@ -21,6 +21,8 @@ from langchain_core.utils.utils import _build_model_kwargs, from_env, secret_fro
from pydantic import ConfigDict, Field, SecretStr, model_validator
from typing_extensions import Self
from langchain_anthropic._sdk_compat import _route_unsupported_sampling_params
class _AnthropicCommon(BaseLanguageModel):
client: Any = None
@@ -276,6 +278,7 @@ class AnthropicLLM(LLM, _AnthropicCommon):
# Remove parameters not supported by Messages API
params = {k: v for k, v in params.items() if k != "max_tokens_to_sample"}
params = _route_unsupported_sampling_params(params)
response = self.client.messages.create(
messages=self._format_messages(prompt),
@@ -312,6 +315,7 @@ class AnthropicLLM(LLM, _AnthropicCommon):
# Remove parameters not supported by Messages API
params = {k: v for k, v in params.items() if k != "max_tokens_to_sample"}
params = _route_unsupported_sampling_params(params)
response = await self.async_client.messages.create(
messages=self._format_messages(prompt),
@@ -352,6 +356,7 @@ class AnthropicLLM(LLM, _AnthropicCommon):
# Remove parameters not supported by Messages API
params = {k: v for k, v in params.items() if k != "max_tokens_to_sample"}
params = _route_unsupported_sampling_params(params)
with self.client.messages.stream(
messages=self._format_messages(prompt),
@@ -397,6 +402,7 @@ class AnthropicLLM(LLM, _AnthropicCommon):
# Remove parameters not supported by Messages API
params = {k: v for k, v in params.items() if k != "max_tokens_to_sample"}
params = _route_unsupported_sampling_params(params)
async with self.async_client.messages.stream(
messages=self._format_messages(prompt),
+2 -2
View File
@@ -23,7 +23,7 @@ classifiers = [
version = "1.6.1"
requires-python = ">=3.10.0,<4.0.0"
dependencies = [
"anthropic>=0.120.0,<1.0.0",
"anthropic>=0.120.0,<2.0.0",
"langchain-core>=1.6.0,<2.0.0",
"pydantic>=2.7.4,<3.0.0",
]
@@ -52,7 +52,7 @@ test = [
"pytest-timeout>=2.3.1,<3.0.0",
"pytest-socket>=0.7.0,<1.0.0",
"pytest-xdist>=3.8.0,<4.0.0",
"vcrpy>=8.0.0,<9.0.0",
"vcrpy>=8.2.0,<9.0.0",
"langgraph-prebuilt>=0.7.0a2", # set explicitly until we have a stable version
"langchain-tests>=1.1.9,<2.0.0",
"langchain>=1.0.0,<2.0.0",
@@ -140,7 +140,7 @@ async def test_astream() -> None:
"model": MODEL_NAME,
"max_tokens": 1024,
"messages": [{"role": "user", "content": "hi"}],
"temperature": 0.0,
"extra_body": {"temperature": 0.0},
}
stream = await async_client.messages.create(**params, stream=True)
async for event in stream:
@@ -0,0 +1,47 @@
"""Resolve the `httpx` library the installed `anthropic` SDK is built on.
Test-only. `langchain_anthropic` hands the SDK nothing but primitives — a
`base_url` string, a float `timeout`, a `proxy` URL string — and gets its client
class by subclassing `anthropic.DefaultHttpxClient`, which already points at the
right library. So no `httpx` type crosses that boundary in library code and
nothing there needs to know which library is in play.
These tests do cross it: they inject mock transports, and the SDK accepts a
built client only from the library it is itself built on (`httpx` on
`anthropic<1`, `httpx2` on `anthropic>=1`), rejecting the other with a
`TypeError`. `anthropic.DefaultHttpxClient` subclasses that library's client
class, so its MRO names the one to use.
"""
from __future__ import annotations
import importlib
import anthropic
_HTTPX_MODULES = ("httpx2", "httpx")
def _resolve_httpx_module() -> str:
"""Name of the HTTP library `anthropic.DefaultHttpxClient` derives from.
Raises:
RuntimeError: If the SDK exposes no recognizable base class. Guessing
from the version string would be wrong as often as it was right;
failing at import makes the mismatch obvious here rather than in a
confusing downstream `TypeError`.
"""
for base in anthropic.DefaultHttpxClient.__mro__:
root = base.__module__.split(".", 1)[0]
if root in _HTTPX_MODULES:
return root
msg = (
f"Cannot tell which httpx library anthropic "
f"{getattr(anthropic, '__version__', 'unknown')} is built on: "
f"DefaultHttpxClient derives from none of {_HTTPX_MODULES}."
)
raise RuntimeError(msg)
httpx = importlib.import_module(_resolve_httpx_module())
@@ -12,7 +12,6 @@ from typing import Any, Literal, cast
from unittest.mock import MagicMock, patch
import anthropic
import httpx
import pytest
from anthropic.types import Message, TextBlock, Usage
from blockbuster import blockbuster_ctx
@@ -45,6 +44,7 @@ from pydantic import BaseModel, Field, RootModel, SecretStr, ValidationError
from pytest import CaptureFixture, MonkeyPatch
from langchain_anthropic import ChatAnthropic
from langchain_anthropic._sdk_compat import _unsupported_sampling_params
from langchain_anthropic._version import __version__
from langchain_anthropic.chat_models import (
_TOOL_CALL_ID_PATTERN,
@@ -58,6 +58,7 @@ from langchain_anthropic.chat_models import (
_thinking_in_params,
convert_to_anthropic_tool,
)
from tests.unit_tests._httpx_compat import httpx
os.environ["ANTHROPIC_API_KEY"] = "foo"
@@ -118,8 +119,10 @@ _STREAM_EVENTS: list[dict[str, Any]] = [
def _gateway_handler(
expected_beta: str | None,
) -> Callable[[httpx.Request], httpx.Response]:
def handler(request: httpx.Request) -> httpx.Response:
) -> Callable[[Any], Any]:
# Annotated `Any`: the concrete request/response classes come from `httpx`
# or `httpx2` depending on the installed anthropic SDK.
def handler(request: Any) -> Any:
assert request.headers.get("anthropic-beta") == expected_beta
headers = {"x-langsmith-gateway-metadata": json.dumps(_GATEWAY_METADATA)}
if json.loads(request.content).get("stream"):
@@ -2507,6 +2510,24 @@ def test_mcp_tracing() -> None:
assert payload["mcp_servers"][0]["authorization_token"] == "PLACEHOLDER" # noqa: S105
def test_sampling_params_reach_the_request_payload() -> None:
"""`temperature`/`top_p`/`top_k` land wherever the installed SDK accepts them.
`anthropic>=1` dropped them as named arguments, so they are relocated to
`extra_body`; the resulting wire payload is the same on both majors.
"""
llm = ChatAnthropic(model=MODEL_NAME, temperature=0, top_p=0.9, top_k=5)
payload = llm._get_request_payload([HumanMessage("foo")])
sampling = {"temperature": 0, "top_p": 0.9, "top_k": 5}
if _unsupported_sampling_params():
assert payload["extra_body"] == sampling
assert not sampling.keys() & payload.keys()
else:
assert {k: payload[k] for k in sampling} == sampling
assert "extra_body" not in payload
def test_cache_control_kwarg() -> None:
llm = ChatAnthropic(model=MODEL_NAME)
@@ -0,0 +1,151 @@
"""Tests for adapting to the installed `anthropic` SDK major version."""
from __future__ import annotations
import inspect
from types import MappingProxyType
from typing import Any
from unittest.mock import patch
import anthropic
import pytest
from langchain_anthropic import _sdk_compat
from langchain_anthropic._sdk_compat import (
_aparse,
_route_unsupported_sampling_params,
_sdk_major_version,
_unsupported_sampling_params,
)
@pytest.mark.parametrize(
("version", "expected"),
[
("1.0.0", 1),
("v1.0.0", 1), # tag-derived strings build tooling leaves unstripped
("0.125.0", 0),
("2.0.0rc1", 2),
("not-a-version", None),
],
)
def test_sdk_major_version_parsing(version: str, expected: int | None) -> None:
with patch.object(anthropic, "__version__", version):
assert _sdk_major_version() == expected
def test_unsupported_sampling_params_matches_the_sdk_signature() -> None:
"""The dropped set is exactly what `Messages.create` no longer accepts."""
from anthropic.resources.messages import Messages
accepted = inspect.signature(Messages.create).parameters
assert _unsupported_sampling_params() == frozenset(
p for p in ("temperature", "top_p", "top_k") if p not in accepted
)
def test_sampling_params_end_up_where_the_sdk_accepts_them() -> None:
"""Sampling params stay top-level on 0.x and move to `extra_body` on 1.x."""
payload = _route_unsupported_sampling_params(
{"model": "claude-sonnet-4-5", "temperature": 0.5, "top_p": 0.9, "top_k": 5}
)
if _unsupported_sampling_params():
assert payload == {
"model": "claude-sonnet-4-5",
"extra_body": {"temperature": 0.5, "top_p": 0.9, "top_k": 5},
}
else:
assert payload == {
"model": "claude-sonnet-4-5",
"temperature": 0.5,
"top_p": 0.9,
"top_k": 5,
}
def test_routing_is_a_no_op_without_sampling_params() -> None:
"""No `extra_body` key is invented for payloads that set no sampling params."""
assert _route_unsupported_sampling_params({"model": "claude-sonnet-4-5"}) == {
"model": "claude-sonnet-4-5"
}
def test_explicit_extra_body_wins_over_relocated_params() -> None:
"""`extra_body` is the SDK's documented escape hatch; don't overwrite it."""
with patch.object(
_sdk_compat, "_unsupported_sampling_params", lambda: frozenset({"temperature"})
):
payload = _route_unsupported_sampling_params(
{"temperature": 0.5, "extra_body": {"temperature": 0.1, "foo": "bar"}}
)
assert payload == {"extra_body": {"temperature": 0.1, "foo": "bar"}}
def test_non_dict_mapping_extra_body_still_receives_relocated_params() -> None:
"""`extra_body` accepts any Mapping, not just `dict`."""
with patch.object(
_sdk_compat, "_unsupported_sampling_params", lambda: frozenset({"temperature"})
):
payload = _route_unsupported_sampling_params(
{"temperature": 0.5, "extra_body": MappingProxyType({"foo": "bar"})}
)
assert payload == {"extra_body": {"temperature": 0.5, "foo": "bar"}}
def test_non_dict_extra_body_leaves_payload_untouched() -> None:
"""A malformed `extra_body` is the SDK's to reject, not ours to merge into.
Crucially the sampling params must survive: relocating them into a value
that cannot hold them would drop them silently.
"""
with patch.object(
_sdk_compat, "_unsupported_sampling_params", lambda: frozenset({"temperature"})
):
payload = _route_unsupported_sampling_params(
{"temperature": 0.5, "extra_body": "not-a-dict"}
)
assert payload == {"temperature": 0.5, "extra_body": "not-a-dict"}
def test_unsupported_sampling_params_falls_back_when_introspection_fails(
caplog: pytest.LogCaptureFixture,
) -> None:
"""An unreadable signature guesses from the major version instead of raising."""
_unsupported_sampling_params.cache_clear()
try:
with (
patch.object(inspect, "signature", side_effect=ValueError("no signature")),
patch.object(anthropic, "__version__", "1.0.0"),
):
assert _unsupported_sampling_params() == frozenset(
{"temperature", "top_p", "top_k"}
)
finally:
_unsupported_sampling_params.cache_clear()
assert "Could not introspect" in caplog.text
class _SyncRawResponse:
def parse(self) -> str:
return "sync"
class _AsyncRawResponse:
async def parse(self) -> str:
return "async"
@pytest.mark.parametrize(
("raw_response", "expected"),
[(_SyncRawResponse(), "sync"), (_AsyncRawResponse(), "async")],
)
async def test_aparse_handles_both_raw_response_classes(
raw_response: Any, expected: str
) -> None:
"""`anthropic<1` parsed synchronously; `anthropic>=1` returns a coroutine."""
assert await _aparse(raw_response) == expected
+68 -26
View File
@@ -6,9 +6,11 @@ resolution-markers = [
"python_full_version >= '3.13' and python_full_version < '3.15' and platform_python_implementation == 'PyPy'",
"python_full_version >= '3.15' and platform_python_implementation != 'PyPy'",
"python_full_version >= '3.13' and python_full_version < '3.15' and platform_python_implementation != 'PyPy'",
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy'",
"python_full_version == '3.12.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'",
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten')",
"python_full_version < '3.11' and platform_python_implementation != 'PyPy'",
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy'",
"python_full_version == '3.12.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten'",
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten')",
"python_full_version < '3.11' and platform_python_implementation == 'PyPy'",
]
@@ -29,21 +31,20 @@ wheels = [
[[package]]
name = "anthropic"
version = "0.120.0"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "distro" },
{ name = "docstring-parser" },
{ name = "httpx" },
{ name = "httpx2" },
{ name = "jiter" },
{ name = "pydantic" },
{ name = "sniffio" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/92/5c/3331da4fc009d448008a50c78d86cc929e8c937cd1442245ce3f80561c4e/anthropic-0.120.0.tar.gz", hash = "sha256:6ba6007dc9b00365b20f6101a6618f5196ac1ceef81512e4b5cc0e7436d4975d", size = 1008042, upload-time = "2026-07-24T16:32:52.384Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c6/40/8249e272c9bba8b4b5802f90717fcdb55996da243a712dca449f2d81c95b/anthropic-1.1.0.tar.gz", hash = "sha256:03d180143ca61177772a35350c78e6ff975b636fd226d221b122b1595f284f3e", size = 1132609, upload-time = "2026-08-26T17:14:53.335Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1b/8a/8522bdf809e1f95f0d9c936540987a3f6afba01d2921a2bf488dedf836a8/anthropic-0.120.0-py3-none-any.whl", hash = "sha256:591bd531563ec7b63a1e138f5c11f14cb94edda99623b349c2ce2ece8e08b8a5", size = 1022602, upload-time = "2026-07-24T16:32:50.506Z" },
{ url = "https://files.pythonhosted.org/packages/93/9a/37a5d1913434913ed89bef960693870a1dd9478e76fd12e7b9f2c066270a/anthropic-1.1.0-py3-none-any.whl", hash = "sha256:babf7b217a6f289059a25ba4871c167411c747bbf233a4f1fb8ececc8a22f875", size = 1289646, upload-time = "2026-08-26T17:14:51.773Z" },
]
[[package]]
@@ -320,15 +321,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
]
[[package]]
name = "distro"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
]
[[package]]
name = "docstring-parser"
version = "0.17.0"
@@ -399,6 +391,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpcore2"
version = "2.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "h11", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" },
{ name = "truststore", marker = "python_full_version != '3.12.*' or sys_platform != 'emscripten'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/74/d370e55600d9bcfa0d9794b0166126d49291a3d2b20c268fc98c453a4948/httpcore2-2.12.0-py3-none-any.whl", hash = "sha256:7e04258ce01013d7d615e5b910a3b27fac937d7a95038227e79652b4ba3b4ceb", size = 83074, upload-time = "2026-08-18T13:22:05.854Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
@@ -415,12 +420,38 @@ wheels = [
]
[[package]]
name = "idna"
version = "3.15"
name = "httpx2"
version = "2.12.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
dependencies = [
{ name = "anyio", marker = "sys_platform != 'emscripten'" },
{ name = "httpcore2", marker = "sys_platform != 'emscripten'" },
{ name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" },
{ name = "idna" },
{ name = "truststore", marker = "sys_platform != 'emscripten'" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7f/f8/579a8b51e42e38ee32647df9f08aa25643ae788e275cc625b199829c4671/httpx2-2.12.0.tar.gz", hash = "sha256:7631fe9887a8a2275f4a2540e053aa670fcc50742864a9ae7c66e609fdcf12cf", size = 100040, upload-time = "2026-08-18T13:22:09.086Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
{ url = "https://files.pythonhosted.org/packages/c8/95/411ba65569158e862368917aaf56597f3e5fa3b91b0502919638465a08f3/httpx2-2.12.0-py3-none-any.whl", hash = "sha256:cc8b6eecb8661c146b8f89a60e97456ee086e91a784ed31ac450c3a9e613dd36", size = 95427, upload-time = "2026-08-18T13:22:06.834Z" },
]
[[package]]
name = "httpx2-jsfetch"
version = "1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" },
]
[[package]]
name = "idna"
version = "3.19"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
]
[[package]]
@@ -552,7 +583,7 @@ wheels = [
[[package]]
name = "langchain"
version = "1.3.16"
version = "1.3.17"
source = { editable = "../../langchain_v1" }
dependencies = [
{ name = "langchain-core" },
@@ -657,7 +688,7 @@ typing = [
[package.metadata]
requires-dist = [
{ name = "anthropic", specifier = ">=0.120.0,<1.0.0" },
{ name = "anthropic", specifier = ">=0.120.0,<2.0.0" },
{ name = "langchain-core", editable = "../../core" },
{ name = "pydantic", specifier = ">=2.7.4,<3.0.0" },
]
@@ -680,7 +711,7 @@ test = [
{ name = "pytest-watcher", specifier = ">=0.3.4,<1.0.0" },
{ name = "pytest-xdist", specifier = ">=3.8.0,<4.0.0" },
{ name = "syrupy", specifier = ">=5.0.0,<6.0.0" },
{ name = "vcrpy", specifier = ">=8.0.0,<9.0.0" },
{ name = "vcrpy", specifier = ">=8.2.0,<9.0.0" },
]
test-integration = [{ name = "requests", specifier = ">=2.32.3,<3.0.0" }]
typing = [
@@ -738,7 +769,7 @@ test = [
{ name = "pytest-benchmark" },
{ name = "pytest-codspeed" },
{ name = "pytest-mock", specifier = ">=3.10.0,<4.0.0" },
{ name = "pytest-socket", specifier = ">=0.7.0,<1.0.0" },
{ name = "pytest-socket", specifier = ">=0.7.0,<0.8.0" },
{ name = "pytest-watcher", specifier = ">=0.3.4,<1.0.0" },
{ name = "pytest-xdist", specifier = ">=3.6.1,<4.0.0" },
{ name = "responses", specifier = ">=0.25.0,<1.0.0" },
@@ -1137,8 +1168,10 @@ resolution-markers = [
"python_full_version >= '3.13' and python_full_version < '3.15' and platform_python_implementation == 'PyPy'",
"python_full_version >= '3.15' and platform_python_implementation != 'PyPy'",
"python_full_version >= '3.13' and python_full_version < '3.15' and platform_python_implementation != 'PyPy'",
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy'",
"python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy'",
"python_full_version == '3.12.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten'",
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation != 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation != 'PyPy' and sys_platform == 'emscripten')",
"python_full_version == '3.12.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten'",
"(python_full_version >= '3.11' and python_full_version < '3.13' and platform_python_implementation == 'PyPy' and sys_platform != 'emscripten') or (python_full_version == '3.11.*' and platform_python_implementation == 'PyPy' and sys_platform == 'emscripten')",
]
sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" }
wheels = [
@@ -1928,6 +1961,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
]
[[package]]
name = "truststore"
version = "0.10.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
]
[[package]]
name = "types-requests"
version = "2.31.0.6"