From 329b07f6940c7c93b29f8fd245e02d44d3f58cfb Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Fri, 20 Mar 2026 18:07:23 +0200 Subject: [PATCH 1/8] custom callback for header injection --- .../src/uipath_langchain_client/__init__.py | 2 + .../src/uipath_langchain_client/callbacks.py | 67 +++++++++++++++++++ src/uipath/llm_client/httpx_client.py | 7 ++ src/uipath/llm_client/utils/headers.py | 23 +++++++ 4 files changed, 99 insertions(+) create mode 100644 packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/__init__.py b/packages/uipath_langchain_client/src/uipath_langchain_client/__init__.py index 95f9ea3..14fe5ed 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/__init__.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/__init__.py @@ -33,6 +33,7 @@ """ from uipath_langchain_client.__version__ import __version__ +from uipath_langchain_client.callbacks import UiPathDynamicHeadersCallback from uipath_langchain_client.clients import UiPathChat, UiPathEmbeddings from uipath_langchain_client.factory import get_chat_model, get_embedding_model from uipath_langchain_client.settings import ( @@ -47,6 +48,7 @@ "get_embedding_model", "UiPathChat", "UiPathEmbeddings", + "UiPathDynamicHeadersCallback", "get_default_client_settings", "LLMGatewaySettings", "PlatformSettings", diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py b/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py new file mode 100644 index 0000000..a3825cb --- /dev/null +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py @@ -0,0 +1,67 @@ +"""LangChain callbacks for dynamic per-request header injection.""" + +from abc import abstractmethod +from typing import Any +from uuid import UUID + +from langchain_core.callbacks import BaseCallbackHandler +from langchain_core.messages import BaseMessage + +from uipath.llm_client.utils.headers import ( + clear_dynamic_request_headers, + set_dynamic_request_headers, +) + + +class UiPathDynamicHeadersCallback(BaseCallbackHandler): + """Base callback for injecting dynamic headers into each LLM gateway request. + + Extend this class and implement ``get_headers()`` to return the headers to + inject. The headers are stored in a ContextVar before each LLM call and read + by the httpx client's ``send()`` method, so they flow transparently through + the call stack regardless of which vendor SDK is in use. + + Example (OTEL trace propagation):: + + from opentelemetry import trace, propagate + + class OtelHeadersCallback(UiPathDynamicHeadersCallback): + def get_headers(self) -> dict[str, str]: + carrier: dict[str, str] = {} + propagate.inject(carrier) + return carrier + + chat = get_chat_model(model_name="gpt-4o", client_settings=settings) + response = chat.invoke("Hello!", config={"callbacks": [OtelHeadersCallback()]}) + """ + + @abstractmethod + def get_headers(self) -> dict[str, str]: + """Return headers to inject into the next LLM gateway request.""" + ... + + def on_chat_model_start( + self, + serialized: dict[str, Any], + messages: list[list[BaseMessage]], + *, + run_id: UUID, + **kwargs: Any, + ) -> None: + set_dynamic_request_headers(self.get_headers()) + + def on_llm_start( + self, + serialized: dict[str, Any], + prompts: list[str], + *, + run_id: UUID, + **kwargs: Any, + ) -> None: + set_dynamic_request_headers(self.get_headers()) + + def on_llm_end(self, response: Any, *, run_id: UUID, **kwargs: Any) -> None: + clear_dynamic_request_headers() + + def on_llm_error(self, error: BaseException, *, run_id: UUID, **kwargs: Any) -> None: + clear_dynamic_request_headers() diff --git a/src/uipath/llm_client/httpx_client.py b/src/uipath/llm_client/httpx_client.py index 97c3456..5aa1c26 100644 --- a/src/uipath/llm_client/httpx_client.py +++ b/src/uipath/llm_client/httpx_client.py @@ -41,6 +41,7 @@ from uipath.llm_client.utils.headers import ( build_routing_headers, extract_matching_headers, + get_dynamic_request_headers, set_captured_response_headers, ) from uipath.llm_client.utils.logging import LoggingConfig @@ -174,6 +175,9 @@ def send(self, request: Request, *, stream: bool = False, **kwargs: Any) -> Resp if self._freeze_base_url: request.url = URL(str(self.base_url).rstrip("/")) request.headers[self._streaming_header] = str(stream).lower() + dynamic_headers = get_dynamic_request_headers() + if dynamic_headers: + request.headers.update(dynamic_headers) response = super().send(request, stream=stream, **kwargs) if self._captured_headers: captured = extract_matching_headers(response.headers, self._captured_headers) @@ -304,6 +308,9 @@ async def send(self, request: Request, *, stream: bool = False, **kwargs: Any) - if self._freeze_base_url: request.url = URL(str(self.base_url).rstrip("/")) request.headers[self._streaming_header] = str(stream).lower() + dynamic_headers = get_dynamic_request_headers() + if dynamic_headers: + request.headers.update(dynamic_headers) response = await super().send(request, stream=stream, **kwargs) if self._captured_headers: captured = extract_matching_headers(response.headers, self._captured_headers) diff --git a/src/uipath/llm_client/utils/headers.py b/src/uipath/llm_client/utils/headers.py index 3f48f86..4cdcc0e 100644 --- a/src/uipath/llm_client/utils/headers.py +++ b/src/uipath/llm_client/utils/headers.py @@ -24,6 +24,29 @@ def set_captured_response_headers(headers: dict[str, str]) -> contextvars.Token[ return _CAPTURED_RESPONSE_HEADERS.set(headers) +_DYNAMIC_REQUEST_HEADERS: contextvars.ContextVar[dict[str, str]] = contextvars.ContextVar( + "_dynamic_request_headers", default={} +) + + +def get_dynamic_request_headers() -> dict[str, str]: + """Get dynamic headers to be injected into the next outgoing request. + + Returns an empty dict if no dynamic headers have been set in this context. + """ + return dict(_DYNAMIC_REQUEST_HEADERS.get()) + + +def set_dynamic_request_headers(headers: dict[str, str]) -> contextvars.Token[dict[str, str]]: + """Set headers to be injected into the next outgoing request.""" + return _DYNAMIC_REQUEST_HEADERS.set(headers) + + +def clear_dynamic_request_headers() -> None: + """Clear dynamic request headers for the current context.""" + _DYNAMIC_REQUEST_HEADERS.set({}) + + def extract_matching_headers( response_headers: Headers, prefixes: Sequence[str], From a72a3971fa68d1540b3574a0a7a2363dd5e5f94d Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Fri, 20 Mar 2026 19:04:20 +0200 Subject: [PATCH 2/8] fix --- .../src/uipath_langchain_client/callbacks.py | 5 ++--- src/uipath/llm_client/utils/headers.py | 14 ++++---------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py b/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py index a3825cb..c4be557 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py @@ -8,7 +8,6 @@ from langchain_core.messages import BaseMessage from uipath.llm_client.utils.headers import ( - clear_dynamic_request_headers, set_dynamic_request_headers, ) @@ -61,7 +60,7 @@ def on_llm_start( set_dynamic_request_headers(self.get_headers()) def on_llm_end(self, response: Any, *, run_id: UUID, **kwargs: Any) -> None: - clear_dynamic_request_headers() + set_dynamic_request_headers({}) def on_llm_error(self, error: BaseException, *, run_id: UUID, **kwargs: Any) -> None: - clear_dynamic_request_headers() + set_dynamic_request_headers({}) diff --git a/src/uipath/llm_client/utils/headers.py b/src/uipath/llm_client/utils/headers.py index 4cdcc0e..6a5bea2 100644 --- a/src/uipath/llm_client/utils/headers.py +++ b/src/uipath/llm_client/utils/headers.py @@ -9,6 +9,10 @@ "_captured_response_headers", default={} ) +_DYNAMIC_REQUEST_HEADERS: contextvars.ContextVar[dict[str, str]] = contextvars.ContextVar( + "_dynamic_request_headers", default={} +) + def get_captured_response_headers() -> dict[str, str]: """Get response headers captured from the most recent request in this context. @@ -24,11 +28,6 @@ def set_captured_response_headers(headers: dict[str, str]) -> contextvars.Token[ return _CAPTURED_RESPONSE_HEADERS.set(headers) -_DYNAMIC_REQUEST_HEADERS: contextvars.ContextVar[dict[str, str]] = contextvars.ContextVar( - "_dynamic_request_headers", default={} -) - - def get_dynamic_request_headers() -> dict[str, str]: """Get dynamic headers to be injected into the next outgoing request. @@ -42,11 +41,6 @@ def set_dynamic_request_headers(headers: dict[str, str]) -> contextvars.Token[di return _DYNAMIC_REQUEST_HEADERS.set(headers) -def clear_dynamic_request_headers() -> None: - """Clear dynamic request headers for the current context.""" - _DYNAMIC_REQUEST_HEADERS.set({}) - - def extract_matching_headers( response_headers: Headers, prefixes: Sequence[str], From b35a16926dd03d6ff8b7d818980ed501a52de1d9 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Sat, 21 Mar 2026 21:39:08 +0200 Subject: [PATCH 3/8] toml bump --- pyproject.toml | 11 +- uv.lock | 344 +++++++++++++++++++++++++++++-------------------- 2 files changed, 208 insertions(+), 147 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 33fe25d..260d54e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ dependencies = [ "tenacity>=9.1.4", "pydantic>=2.12.5", "pydantic-settings>=2.13.1", - "uipath-platform>=0.0.25", + "uipath-platform>=0.1.2", ] authors = [ @@ -19,13 +19,13 @@ authors = [ [project.optional-dependencies] openai = [ - "openai>=2.28.0", + "openai>=2.29.0", ] google = [ - "google-genai>=1.67.0", + "google-genai>=1.68.0", ] anthropic = [ - "anthropic>=0.85.0", + "anthropic>=0.86.0", ] all = [ "uipath-llm-client[openai,google,anthropic]", @@ -38,9 +38,10 @@ dev = [ "pytest-asyncio>=1.3.0", "pytest-recording>=0.13.4", "pyright>=1.1.408", - "ruff>=0.15.6", + "ruff>=0.15.7", "uipath-llm-client[all]", "uipath_langchain_client[all]", + "openinference-instrumentation-langchain>=0.1.61", ] [tool.uv.workspace] diff --git a/uv.lock b/uv.lock index 0eccd7e..c9e38c8 100644 --- a/uv.lock +++ b/uv.lock @@ -148,7 +148,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.85.0" +version = "0.86.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -160,9 +160,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/08/c620a0eb8625539a8ea9f5a6e06f13d131be0bc8b5b714c235d4b25dd1b5/anthropic-0.85.0.tar.gz", hash = "sha256:d45b2f38a1efb1a5d15515a426b272179a0d18783efa2bb4c3925fa773eb50b9", size = 542034, upload-time = "2026-03-16T17:00:44.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7a/8b390dc47945d3169875d342847431e5f7d5fa716b2e37494d57cfc1db10/anthropic-0.86.0.tar.gz", hash = "sha256:60023a7e879aa4fbb1fed99d487fe407b2ebf6569603e5047cfe304cebdaa0e5", size = 583820, upload-time = "2026-03-18T18:43:08.017Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/5a/9d85b85686d5cdd79f5488c8667e668d7920d06a0a1a1beb454a5b77b2db/anthropic-0.85.0-py3-none-any.whl", hash = "sha256:b4f54d632877ed7b7b29c6d9ba7299d5e21c4c92ae8de38947e9d862bff74adf", size = 458237, upload-time = "2026-03-16T17:00:45.877Z" }, + { url = "https://files.pythonhosted.org/packages/63/5f/67db29c6e5d16c8c9c4652d3efb934d89cb750cad201539141781d8eae14/anthropic-0.86.0-py3-none-any.whl", hash = "sha256:9d2bbd339446acce98858c5627d33056efe01f70435b22b63546fe7edae0cd57", size = 469400, upload-time = "2026-03-18T18:43:06.526Z" }, ] [package.optional-dependencies] @@ -189,11 +189,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] @@ -224,15 +224,15 @@ wheels = [ [[package]] name = "azure-core" -version = "1.38.3" +version = "1.39.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/29/9641b73248745774a52c7ce7f965ed1febbdea787ec21caad3ae6891d18a/azure_core-1.38.3.tar.gz", hash = "sha256:a7931fd445cb4af8802c6f39c6a326bbd1e34b115846550a8245fa656ead6f8e", size = 367267, upload-time = "2026-03-12T20:28:21.122Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/83/bbde3faa84ddcb8eb0eca4b3ffb3221252281db4ce351300fe248c5c70b1/azure_core-1.39.0.tar.gz", hash = "sha256:8a90a562998dd44ce84597590fff6249701b98c0e8797c95fcdd695b54c35d74", size = 367531, upload-time = "2026-03-19T01:31:29.461Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3d/ac86083efa45a439d0bbfb7947615227813d368b9e1e93d23fd30de6fec0/azure_core-1.38.3-py3-none-any.whl", hash = "sha256:bf59d29765bf4748ab9edf25f98a30b7ea9797f43e367c06d846a30b29c1f845", size = 218231, upload-time = "2026-03-12T20:28:22.462Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d6/8ebcd05b01a580f086ac9a97fb9fac65c09a4b012161cc97c21a336e880b/azure_core-1.39.0-py3-none-any.whl", hash = "sha256:4ac7b70fab5438c3f68770649a78daf97833caa83827f91df9c14e0e0ea7d34f", size = 218318, upload-time = "2026-03-19T01:31:31.25Z" }, ] [[package]] @@ -296,30 +296,30 @@ wheels = [ [[package]] name = "boto3" -version = "1.42.69" +version = "1.42.73" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/f3/26d800e4efe85e7d59c63ac11d02ab2fafed371bede567af7258eb7e4c1c/boto3-1.42.69.tar.gz", hash = "sha256:e59846f4ff467b23bae4751948298db554dbdda0d72b09028d2cacbeff27e1ad", size = 112777, upload-time = "2026-03-16T20:35:30.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/8b/d00575be514744ca4839e7d85bf4a8a3c7b6b4574433291e58d14c68ae09/boto3-1.42.73.tar.gz", hash = "sha256:d37b58d6cd452ca808dd6823ae19ca65b6244096c5125ef9052988b337298bae", size = 112775, upload-time = "2026-03-20T19:39:52.814Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/39/54ad87436c637de9f7bf83ba2a28cf3b15409cbb849401837fcc37fbd794/boto3-1.42.69-py3-none-any.whl", hash = "sha256:6823a4b59aa578c7d98124280a9b6d83cea04bdb02525cbaa79370e5b6f7f631", size = 140556, upload-time = "2026-03-16T20:35:28.754Z" }, + { url = "https://files.pythonhosted.org/packages/aa/05/1fcf03d90abaa3d0b42a6bfd10231dd709493ecbacf794aa2eea5eae6841/boto3-1.42.73-py3-none-any.whl", hash = "sha256:1f81b79b873f130eeab14bb556417a7c66d38f3396b7f2fe3b958b3f9094f455", size = 140556, upload-time = "2026-03-20T19:39:50.298Z" }, ] [[package]] name = "botocore" -version = "1.42.69" +version = "1.42.73" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/d1/81a6e39c7d5419ba34bad8a1ac2c5360c26f21af698a481a8397d79134d1/botocore-1.42.69.tar.gz", hash = "sha256:0934f2d90403c5c8c2cba83e754a39d77edcad5885d04a79363edff3e814f55e", size = 14997632, upload-time = "2026-03-16T20:35:18.533Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/23/0c88ca116ef63b1ae77c901cd5d2095d22a8dbde9e80df74545db4a061b4/botocore-1.42.73.tar.gz", hash = "sha256:575858641e4949aaf2af1ced145b8524529edf006d075877af6b82ff96ad854c", size = 15008008, upload-time = "2026-03-20T19:39:40.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/13/779f3427e17f9989fd0fa6651817c5f13b63e574f3541e460b8238883290/botocore-1.42.69-py3-none-any.whl", hash = "sha256:ef0e3d860a5d7bffc0ccb4911781c4c27d538557ed9a616ba1926c762d72e5f6", size = 14670334, upload-time = "2026-03-16T20:35:14.543Z" }, + { url = "https://files.pythonhosted.org/packages/8e/65/971f3d55015f4d133a6ff3ad74cd39f4b8dd8f53f7775a3c2ad378ea5145/botocore-1.42.73-py3-none-any.whl", hash = "sha256:7b62e2a12f7a1b08eb7360eecd23bb16fe3b7ab7f5617cf91b25476c6f86a0fe", size = 14681861, upload-time = "2026-03-20T19:39:35.341Z" }, ] [[package]] @@ -801,7 +801,7 @@ requests = [ [[package]] name = "google-cloud-aiplatform" -version = "1.141.0" +version = "1.142.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docstring-parser" }, @@ -817,9 +817,9 @@ dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/dc/1209c7aab43bd7233cf631165a3b1b4284d22fc7fe7387c66228d07868ab/google_cloud_aiplatform-1.141.0.tar.gz", hash = "sha256:e3b1cdb28865dd862aac9c685dfc5ac076488705aba0a5354016efadcddd59c6", size = 10152688, upload-time = "2026-03-10T22:20:08.692Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/0d/3063a0512d60cf18854a279e00ccb796429545464345ef821cf77cb93d05/google_cloud_aiplatform-1.142.0.tar.gz", hash = "sha256:87b49e002703dc14885093e9b264587db84222bef5f70f5a442d03f41beecdd1", size = 10207993, upload-time = "2026-03-20T22:49:13.797Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/fc/428af69a69ff2e477e7f5e12d227b31fe5790f1a8234aacd54297f49c836/google_cloud_aiplatform-1.141.0-py2.py3-none-any.whl", hash = "sha256:6bd25b4d514c40b8181ca703e1b313ad6d0454ab8006fc9907fb3e9f672f31d1", size = 8358409, upload-time = "2026-03-10T22:20:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/59/8b/f29646d3fa940f0e38cfcc12137f4851856b50d7486a3c05103ebc78d82d/google_cloud_aiplatform-1.142.0-py2.py3-none-any.whl", hash = "sha256:17c91db9b613cbbafb2c36335b123686aeb2b4b8448be5134b565ae07165a39a", size = 8388991, upload-time = "2026-03-20T22:49:10.334Z" }, ] [[package]] @@ -872,7 +872,7 @@ wheels = [ [[package]] name = "google-cloud-storage" -version = "3.9.0" +version = "3.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-api-core" }, @@ -882,9 +882,9 @@ dependencies = [ { name = "google-resumable-media" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/b1/4f0798e88285b50dfc60ed3a7de071def538b358db2da468c2e0deecbb40/google_cloud_storage-3.9.0.tar.gz", hash = "sha256:f2d8ca7db2f652be757e92573b2196e10fbc09649b5c016f8b422ad593c641cc", size = 17298544, upload-time = "2026-02-02T13:36:34.119Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/e3/747759eebc72e420c25903d6bc231d0ceb110b66ac7e6ee3f350417152cd/google_cloud_storage-3.10.0.tar.gz", hash = "sha256:1aeebf097c27d718d84077059a28d7e87f136f3700212215f1ceeae1d1c5d504", size = 17309829, upload-time = "2026-03-18T15:54:11.875Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/0b/816a6ae3c9fd096937d2e5f9670558908811d57d59ddf69dd4b83b326fd1/google_cloud_storage-3.9.0-py3-none-any.whl", hash = "sha256:2dce75a9e8b3387078cbbdad44757d410ecdb916101f8ba308abf202b6968066", size = 321324, upload-time = "2026-02-02T13:36:32.271Z" }, + { url = "https://files.pythonhosted.org/packages/29/e2/d58442f4daee5babd9255cf492a1f3d114357164072f8339a22a3ad460a2/google_cloud_storage-3.10.0-py3-none-any.whl", hash = "sha256:0072e7783b201e45af78fd9779894cdb6bec2bf922ee932f3fcc16f8bce9b9a3", size = 324382, upload-time = "2026-03-18T15:54:10.091Z" }, ] [[package]] @@ -935,7 +935,7 @@ wheels = [ [[package]] name = "google-genai" -version = "1.67.0" +version = "1.68.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -949,9 +949,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/07/59a498f81f2c7b0649eacda2ea470b7fd8bd7149f20caba22962081bdd51/google_genai-1.67.0.tar.gz", hash = "sha256:897195a6a9742deb6de240b99227189ada8b2d901d61bdfba836c3092021eab6", size = 506972, upload-time = "2026-03-12T20:39:16.241Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/2c/f059982dbcb658cc535c81bbcbe7e2c040d675f4b563b03cdb01018a4bc3/google_genai-1.68.0.tar.gz", hash = "sha256:ac30c0b8bc630f9372993a97e4a11dae0e36f2e10d7c55eacdca95a9fa14ca96", size = 511285, upload-time = "2026-03-18T01:03:18.243Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/c2/562aa1f086e53529ffbeb5b43d5d8bc42c1b968102b5e2163fad005ce298/google_genai-1.67.0-py3-none-any.whl", hash = "sha256:58b0484ff2d4335fa53c724b489e9f807fcca8115d9cdbd8fdf341121fbd6d2d", size = 733542, upload-time = "2026-03-12T20:39:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/84/de/7d3ee9c94b74c3578ea4f88d45e8de9405902f857932334d81e89bce3dfa/google_genai-1.68.0-py3-none-any.whl", hash = "sha256:a1bc9919c0e2ea2907d1e319b65471d3d6d58c54822039a249fe1323e4178d15", size = 750912, upload-time = "2026-03-18T01:03:15.983Z" }, ] [[package]] @@ -1270,44 +1270,44 @@ wheels = [ [[package]] name = "jsonpointer" -version = "3.0.0" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/bf/9ecc036fbc15cf4153ea6ed4dbeed31ef043f762cccc9d44a534be8319b0/jsonpointer-3.1.0.tar.gz", hash = "sha256:f9b39abd59ba8c1de8a4ff16141605d2a8dacc4dd6cf399672cf237bfe47c211", size = 9000, upload-time = "2026-03-20T21:47:09.982Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/cebb241a435cbf4626b5ea096d8385c04416d7ca3082a15299b746e248fa/jsonpointer-3.1.0-py3-none-any.whl", hash = "sha256:f82aa0f745001f169b96473348370b43c3f581446889c41c807bab1db11c8e7b", size = 7651, upload-time = "2026-03-20T21:47:08.792Z" }, ] [[package]] name = "langchain" -version = "1.2.12" +version = "1.2.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d8/1d/1af2fc0ac084d4781778b7846b1aed62e05006bf2d73fdf84ac3a8f5225c/langchain-1.2.12.tar.gz", hash = "sha256:ed705b5b293799f7e3e394387f398a1b71707542758283206c8c21415759d991", size = 566444, upload-time = "2026-03-11T22:21:00.712Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e5/56fdeedaa0ef1be3c53721d382d9e21c63930179567361610ea6102c04ea/langchain-1.2.13.tar.gz", hash = "sha256:d566ef67c8287e7f2e2df3c99bf3953a6beefd2a75a97fe56ecce905e21f3ef4", size = 573819, upload-time = "2026-03-19T17:16:07.641Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/51/09bb1cfb0b57ae9440ca56cc576e4dc792f83d030eef7637d2c516dcb0a0/langchain-1.2.12-py3-none-any.whl", hash = "sha256:60eff184b8f92c2610f5a4c9a97ad339a891adb01901e83e4df8e6c9c69cf852", size = 112373, upload-time = "2026-03-11T22:20:59.508Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1d/a509af07535d8f4621d77f3ba5ec846ee6d52c59d2239e1385ec3b29bf92/langchain-1.2.13-py3-none-any.whl", hash = "sha256:37d4526ac4b0cdd3d7706a6366124c30dc0771bf5340865b37cdc99d5e5ad9b1", size = 112488, upload-time = "2026-03-19T17:16:06.134Z" }, ] [[package]] name = "langchain-anthropic" -version = "1.3.5" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anthropic" }, { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/ef/a096793dd880423254ddeaa92909342f9b04a11ef9000c0d121d45605800/langchain_anthropic-1.3.5.tar.gz", hash = "sha256:0745f181b8696b03f7b66a95ed97f43cc90b1b086850ebbf17804f605e640f6e", size = 674070, upload-time = "2026-03-14T03:12:33.418Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/c7/259d4d805c6ac90c8695714fc15498a4557bb515eb24f692fd611966e383/langchain_anthropic-1.4.0.tar.gz", hash = "sha256:bbf64e99f9149a34ba67813e9582b2160a0968de9e9f54f7ba8d1658f253c2e5", size = 674360, upload-time = "2026-03-17T18:42:20.751Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/22/d967f55651f6a1d9157ecefd335f6c6232f4826b637a7e89c92495288524/langchain_anthropic-1.3.5-py3-none-any.whl", hash = "sha256:1666f83ddef74d9fa844ff3d1f9d23dd12004b0799d1ca1ff329cbaf3944d3c6", size = 48250, upload-time = "2026-03-14T03:12:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c0/77f99373276d4f06c38a887ef6023f101cfc7ba3b2bf9af37064cdbadde5/langchain_anthropic-1.4.0-py3-none-any.whl", hash = "sha256:c84f55722336935f7574d5771598e674f3959fdca0b51de14c9788dbf52761be", size = 48463, upload-time = "2026-03-17T18:42:19.742Z" }, ] [[package]] name = "langchain-aws" -version = "1.4.0" +version = "1.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, @@ -1315,9 +1315,9 @@ dependencies = [ { name = "numpy" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/1d/9bc0e523b8d13dcaf301cdd85ae05ce0687fdbeff2000b50346410b260d0/langchain_aws-1.4.0.tar.gz", hash = "sha256:370dcba824d68af96372a1c979c94f8d7062d268615b5395d80d1562a05fca37", size = 435753, upload-time = "2026-03-09T20:02:54.472Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/1a/6b7824b4d88ae89a90ef50995529522ce2c3bea416d0c7868bdec82f33ea/langchain_aws-1.4.1.tar.gz", hash = "sha256:9b91efc224b4d9a44120a4dea65f57097c75adcdba906dcea13357ffa93b0559", size = 489886, upload-time = "2026-03-20T04:55:11.285Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/5d/8a52c067137c26db4c0ff6c0847d75cc5f34c42d21dfb031f27fa705fd52/langchain_aws-1.4.0-py3-none-any.whl", hash = "sha256:68e07276cd85bb45dc4415e417e04fbe1c76c3cf029ddd6d96e2fa981dd29cc1", size = 174854, upload-time = "2026-03-09T20:02:53.381Z" }, + { url = "https://files.pythonhosted.org/packages/67/7d/63a2c27b907db716f5d3076b6e0df1f9c4cbddf0d4f82cd5bd39e656baa5/langchain_aws-1.4.1-py3-none-any.whl", hash = "sha256:42faf88d646966155da006be61d8beb8e35decd4d610cf86728ef17a19be0a23", size = 196845, upload-time = "2026-03-20T04:55:09.997Z" }, ] [package.optional-dependencies] @@ -1328,7 +1328,7 @@ anthropic = [ [[package]] name = "langchain-azure-ai" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1342,14 +1342,14 @@ dependencies = [ { name = "numpy" }, { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/e0/6533cf1938340902a777b8fa0d079d83c9afe556d233988577b27b193f6f/langchain_azure_ai-1.1.0.tar.gz", hash = "sha256:d69701096011e4af52500dd0c35da1c5778311a8acfda0ced5898079aa18345d", size = 125060, upload-time = "2026-03-09T22:41:58.085Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ad/192606ae9f331fe6807dbde2902c86b8b240ff94765f3279505ea956e313/langchain_azure_ai-1.1.1.tar.gz", hash = "sha256:251724388fd75286c33ef9a3be92752bb60be6c53c215ed6329cb3bc0bddabe8", size = 127431, upload-time = "2026-03-18T12:45:21.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/a1/dd40472f35dda814fd80fddb96fdbd368c6fb91c8c4be4b5875597e14310/langchain_azure_ai-1.1.0-py3-none-any.whl", hash = "sha256:fb64773c46955db6235c2ede6664573cb7b37bb5db5cd0f9628c905f9abd59d5", size = 152149, upload-time = "2026-03-09T22:41:56.292Z" }, + { url = "https://files.pythonhosted.org/packages/59/d7/8e3be45d274e79226c845c939b9f367c35925f712aa948110f81ae807662/langchain_azure_ai-1.1.1-py3-none-any.whl", hash = "sha256:fae4d4b12acae27f7b10b85481bc5c53f7cbffa31d12b0a8982df3f31bbafb6c", size = 155017, upload-time = "2026-03-18T12:45:20.298Z" }, ] [[package]] name = "langchain-core" -version = "1.2.19" +version = "1.2.20" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1361,9 +1361,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/da/075720d37ebc668f48743bd540b047b2b08b8ba22b46d8f61166c5ad1d1c/langchain_core-1.2.19.tar.gz", hash = "sha256:87fa82c3eb4cc3d7a65f574cb447b5df09ec2131c8c2a0a02d4737ad02685438", size = 836647, upload-time = "2026-03-13T13:44:54.8Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/41/6552a419fe549a79601e5a698d1d5ee2ca7fe93bb87fd624a16a8c1bdee3/langchain_core-1.2.20.tar.gz", hash = "sha256:c7ac8b976039b5832abb989fef058b88c270594ba331efc79e835df046e7dc44", size = 838330, upload-time = "2026-03-18T17:34:45.522Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/cb/8704b2a22c0987627ed29464d23a45fb15e10a28fb482f4d84c3bddcbf27/langchain_core-1.2.19-py3-none-any.whl", hash = "sha256:6e74cb0fb443a8046ee298c05c99b67abe54cc57fcbc6d1cd3b0f2485ee47574", size = 503456, upload-time = "2026-03-13T13:44:53.241Z" }, + { url = "https://files.pythonhosted.org/packages/d9/06/08c88ddd4d6766de4e6c43111ae8f3025df383d2a4379cb938fc571b49d4/langchain_core-1.2.20-py3-none-any.whl", hash = "sha256:b65ff678f3c3dc1f1b4d03a3af5ee3b8d51f9be5181d74eb53c6c11cd9dd5e68", size = 504215, upload-time = "2026-03-18T17:34:44.087Z" }, ] [[package]] @@ -1458,7 +1458,7 @@ wheels = [ [[package]] name = "langgraph" -version = "1.1.2" +version = "1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -1468,9 +1468,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/a8/8494057db9149eb850258e5d4ae961a8dbda9a283e56e1b957393d9df0cd/langgraph-1.1.2.tar.gz", hash = "sha256:c4385ce349823a590891b3f6b1c46b54f51d0134164056866e95034985f047c9", size = 544288, upload-time = "2026-03-12T17:11:17.99Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/b2/e7db624e8b0ee063ecfbf7acc09467c0836a05914a78e819dfb3744a0fac/langgraph-1.1.3.tar.gz", hash = "sha256:ee496c297a9c93b38d8560be15cbb918110f49077d83abd14976cb13ac3b3370", size = 545120, upload-time = "2026-03-18T23:42:58.24Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/38/3117cd90325635893a76132cdae74f5b1f53c93c33b3dc6124521cec9825/langgraph-1.1.2-py3-none-any.whl", hash = "sha256:5fd43c839ec2b5af564e9ae2d2d4f22ce0a006a0b58e800cc4e8de4dd9cbb643", size = 167543, upload-time = "2026-03-12T17:11:16.965Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f7/221cc479e95e03e260496616e5ce6fb50c1ea01472e3a5bc481a9b8a2f83/langgraph-1.1.3-py3-none-any.whl", hash = "sha256:57cd6964ebab41cbd211f222293a2352404e55f8b2312cecde05e8753739b546", size = 168149, upload-time = "2026-03-18T23:42:56.967Z" }, ] [[package]] @@ -1501,20 +1501,20 @@ wheels = [ [[package]] name = "langgraph-sdk" -version = "0.3.11" +version = "0.3.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, { name = "orjson" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/cd/a019f1b1e97c519f2425593f9bccd3ac463a18fb5d2111cff59ce1ef62fe/langgraph_sdk-0.3.11.tar.gz", hash = "sha256:3640134835d89d2c7c8bb7de73bd10673d4b282db3ff0e2fdaf1cee9e50cb1eb", size = 190387, upload-time = "2026-03-11T00:46:45.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/a1/012f0e0f5c9fd26f92bdc9d244756ad673c428230156ef668e6ec7c18cee/langgraph_sdk-0.3.12.tar.gz", hash = "sha256:c9c9ec22b3c0fcd352e2b8f32a815164f69446b8648ca22606329f4ff4c59a71", size = 194932, upload-time = "2026-03-18T22:15:54.592Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/c8/b8d15d4b9a320a3f57a851030a371066b91dbd1420f097d3d0338da9adc9/langgraph_sdk-0.3.11-py3-none-any.whl", hash = "sha256:18905fd6248ade98b0995d859a98672d57c811fbfffc0d63d1c107a512351b26", size = 94887, upload-time = "2026-03-11T00:46:43.855Z" }, + { url = "https://files.pythonhosted.org/packages/17/4d/4f796e86b03878ab20d9b30aaed1ad459eda71a5c5b67f7cfe712f3548f2/langgraph_sdk-0.3.12-py3-none-any.whl", hash = "sha256:44323804965d6ec2a07127b3cf08a0428ea6deaeb172c2d478d5cd25540e3327", size = 95834, upload-time = "2026-03-18T22:15:53.545Z" }, ] [[package]] name = "langsmith" -version = "0.7.18" +version = "0.7.22" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -1527,9 +1527,9 @@ dependencies = [ { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/a8/31e9e7b42089cf194a0d873cbfc1ecec6d6dd0cc693808f1cb64494cbd0c/langsmith-0.7.18.tar.gz", hash = "sha256:d7e6e1f9c9300ee83b9f201c9254b4a32799218de102a5b1d2b217e00be2dfa2", size = 1134635, upload-time = "2026-03-16T18:54:19.131Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/2a/2d5e6c67396fd228670af278c4da7bd6db2b8d11deaf6f108490b6d3f561/langsmith-0.7.22.tar.gz", hash = "sha256:35bfe795d648b069958280760564632fd28ebc9921c04f3e209c0db6a6c7dc04", size = 1134923, upload-time = "2026-03-19T22:45:23.492Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/58/244a14e29c7feccf06ed3929c9ab65a747a9ee94d5ac43d40862053b2f54/langsmith-0.7.18-py3-none-any.whl", hash = "sha256:3253c171fe2f6506056a42f9077983a34749b7a1629e41d8fb8e2005d8960886", size = 359268, upload-time = "2026-03-16T18:54:17.397Z" }, + { url = "https://files.pythonhosted.org/packages/1a/94/1f5d72655ab6534129540843776c40eff757387b88e798d8b3bf7e313fd4/langsmith-0.7.22-py3-none-any.whl", hash = "sha256:6e9d5148314d74e86748cb9d3898632cad0320c9323d95f70f969e5bc078eee4", size = 359927, upload-time = "2026-03-19T22:45:21.603Z" }, ] [[package]] @@ -1845,7 +1845,7 @@ wheels = [ [[package]] name = "openai" -version = "2.28.0" +version = "2.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1857,9 +1857,50 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/87/eb0abb4ef88ddb95b3c13149384c4c288f584f3be17d6a4f63f8c3e3c226/openai-2.28.0.tar.gz", hash = "sha256:bb7fdff384d2a787fa82e8822d1dd3c02e8cf901d60f1df523b7da03cbb6d48d", size = 670334, upload-time = "2026-03-13T19:56:27.306Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/15/203d537e58986b5673e7f232453a2a2f110f22757b15921cbdeea392e520/openai-2.29.0.tar.gz", hash = "sha256:32d09eb2f661b38d3edd7d7e1a2943d1633f572596febe64c0cd370c86d52bec", size = 671128, upload-time = "2026-03-17T17:53:49.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/b1/35b6f9c8cf9318e3dbb7146cc82dab4cf61182a8d5406fc9b50864362895/openai-2.29.0-py3-none-any.whl", hash = "sha256:b7c5de513c3286d17c5e29b92c4c98ceaf0d775244ac8159aeb1bddf840eb42a", size = 1141533, upload-time = "2026-03-17T17:53:47.348Z" }, +] + +[[package]] +name = "openinference-instrumentation" +version = "0.1.46" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/8d/9b76b43e8b2ee2ccf1fe15b21c924095f9c0e4839919bcd4951b1c99c2ab/openinference_instrumentation-0.1.46.tar.gz", hash = "sha256:0b520002a1c682c525dcab49005c209bfd71611e8e4e4933b49779d5e899e6db", size = 23937, upload-time = "2026-03-04T10:13:48.883Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/d1/f6668492152a4180492044313e2dc427fbc237904f6bb1629abd030e3469/openinference_instrumentation-0.1.46-py3-none-any.whl", hash = "sha256:f7b63ccd5f93ce82e4e40035c9faa6b021984cbe06ad791f4cf033551533bc48", size = 30124, upload-time = "2026-03-04T10:13:47.613Z" }, +] + +[[package]] +name = "openinference-instrumentation-langchain" +version = "0.1.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openinference-instrumentation" }, + { name = "openinference-semantic-conventions" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/60/6c298fd6d6778fed0bf7cddf9c97c4391f1cdfc15fe44ddeb732bd09a695/openinference_instrumentation_langchain-0.1.61.tar.gz", hash = "sha256:210686a6cc42f8b16da1c450316025a11f6cf16f70b1a2dea7945dc16a98aa87", size = 75140, upload-time = "2026-02-26T17:51:55.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/df122348638885526e53140e9c6b0d844af7312682b3bde9587eebc28b47/openai-2.28.0-py3-none-any.whl", hash = "sha256:79aa5c45dba7fef84085701c235cf13ba88485e1ef4f8dfcedc44fc2a698fc1d", size = 1141218, upload-time = "2026-03-13T19:56:25.46Z" }, + { url = "https://files.pythonhosted.org/packages/85/9e/5c7fa64fe28de0b5a59df2fe3007a022e9cd263e7e3ed942a18f05e14c4b/openinference_instrumentation_langchain-0.1.61-py3-none-any.whl", hash = "sha256:0f80198cc5937c1a8e19f15143253d59b094f36b2b18308570b4c4ddeb506020", size = 24393, upload-time = "2026-02-26T17:51:54.181Z" }, +] + +[[package]] +name = "openinference-semantic-conventions" +version = "0.1.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/32/c79bf8bd3ea5a00e492449b31ca600bbc2a8e88a301e42c872af925a156c/openinference_semantic_conventions-0.1.28.tar.gz", hash = "sha256:6388465174e8ab3f27ebc6a9e9bb2e1b804d30caefb57234e16db874da1c6a7b", size = 12893, upload-time = "2026-03-11T04:45:46.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/40/34b570462c3ce250277254bb0cca655eb39b64c0dffe63cd7751f103f8d6/openinference_semantic_conventions-0.1.28-py3-none-any.whl", hash = "sha256:a2fed5bb167aa56c1c7448cdb7a8d775f989339ba1f8b04a7b45d4f8388cccfb", size = 10522, upload-time = "2026-03-11T04:45:45.423Z" }, ] [[package]] @@ -2251,17 +2292,17 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.5" +version = "6.33.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] [[package]] @@ -2840,27 +2881,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, - { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, - { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, - { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, - { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, - { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, - { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, - { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, - { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, - { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, - { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, - { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, - { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, +version = "0.15.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, + { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] [[package]] @@ -3077,15 +3118,15 @@ vertexai = [ [package.metadata] requires-dist = [ - { name = "anthropic", extras = ["bedrock", "vertex"], marker = "extra == 'all'", specifier = ">=0.85.0" }, - { name = "anthropic", extras = ["bedrock", "vertex"], marker = "extra == 'anthropic'", specifier = ">=0.85.0" }, - { name = "langchain", specifier = ">=1.2.12" }, - { name = "langchain-anthropic", marker = "extra == 'all'", specifier = ">=1.3.5" }, - { name = "langchain-anthropic", marker = "extra == 'anthropic'", specifier = ">=1.3.5" }, - { name = "langchain-aws", extras = ["anthropic"], marker = "extra == 'all'", specifier = ">=1.4.0" }, - { name = "langchain-aws", extras = ["anthropic"], marker = "extra == 'aws'", specifier = ">=1.4.0" }, - { name = "langchain-azure-ai", marker = "extra == 'all'", specifier = ">=1.1.0" }, - { name = "langchain-azure-ai", marker = "extra == 'azure'", specifier = ">=1.1.0" }, + { name = "anthropic", extras = ["bedrock", "vertex"], marker = "extra == 'all'", specifier = ">=0.86.0" }, + { name = "anthropic", extras = ["bedrock", "vertex"], marker = "extra == 'anthropic'", specifier = ">=0.86.0" }, + { name = "langchain", specifier = ">=1.2.13" }, + { name = "langchain-anthropic", marker = "extra == 'all'", specifier = ">=1.4.0" }, + { name = "langchain-anthropic", marker = "extra == 'anthropic'", specifier = ">=1.4.0" }, + { name = "langchain-aws", extras = ["anthropic"], marker = "extra == 'all'", specifier = ">=1.4.1" }, + { name = "langchain-aws", extras = ["anthropic"], marker = "extra == 'aws'", specifier = ">=1.4.1" }, + { name = "langchain-azure-ai", marker = "extra == 'all'", specifier = ">=1.1.1" }, + { name = "langchain-azure-ai", marker = "extra == 'azure'", specifier = ">=1.1.1" }, { name = "langchain-fireworks", marker = "extra == 'all'", specifier = ">=1.1.0" }, { name = "langchain-fireworks", marker = "extra == 'fireworks'", specifier = ">=1.1.0" }, { name = "langchain-google-genai", marker = "extra == 'all'", specifier = ">=4.2.1" }, @@ -3128,6 +3169,7 @@ openai = [ [package.dev-dependencies] dev = [ { name = "langchain-tests" }, + { name = "openinference-instrumentation-langchain" }, { name = "pyright" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -3139,35 +3181,36 @@ dev = [ [package.metadata] requires-dist = [ - { name = "anthropic", marker = "extra == 'all'", specifier = ">=0.85.0" }, - { name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=0.85.0" }, - { name = "google-genai", marker = "extra == 'all'", specifier = ">=1.67.0" }, - { name = "google-genai", marker = "extra == 'google'", specifier = ">=1.67.0" }, + { name = "anthropic", marker = "extra == 'all'", specifier = ">=0.86.0" }, + { name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=0.86.0" }, + { name = "google-genai", marker = "extra == 'all'", specifier = ">=1.68.0" }, + { name = "google-genai", marker = "extra == 'google'", specifier = ">=1.68.0" }, { name = "httpx", specifier = ">=0.28.1" }, - { name = "openai", marker = "extra == 'all'", specifier = ">=2.28.0" }, - { name = "openai", marker = "extra == 'openai'", specifier = ">=2.28.0" }, + { name = "openai", marker = "extra == 'all'", specifier = ">=2.29.0" }, + { name = "openai", marker = "extra == 'openai'", specifier = ">=2.29.0" }, { name = "pydantic", specifier = ">=2.12.5" }, { name = "pydantic-settings", specifier = ">=2.13.1" }, { name = "tenacity", specifier = ">=9.1.4" }, - { name = "uipath-platform", specifier = ">=0.0.25" }, + { name = "uipath-platform", specifier = ">=0.1.2" }, ] provides-extras = ["all", "anthropic", "google", "openai"] [package.metadata.requires-dev] dev = [ { name = "langchain-tests", specifier = ">=1.1.5" }, + { name = "openinference-instrumentation-langchain", specifier = ">=0.1.61" }, { name = "pyright", specifier = ">=1.1.408" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-asyncio", specifier = ">=1.3.0" }, { name = "pytest-recording", specifier = ">=0.13.4" }, - { name = "ruff", specifier = ">=0.15.6" }, + { name = "ruff", specifier = ">=0.15.7" }, { name = "uipath-langchain-client", extras = ["all"], editable = "packages/uipath_langchain_client" }, { name = "uipath-llm-client", extras = ["all"], editable = "." }, ] [[package]] name = "uipath-platform" -version = "0.0.25" +version = "0.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -3177,9 +3220,9 @@ dependencies = [ { name = "truststore" }, { name = "uipath-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/21/20315118cf1536f2486bc64cf9261eca8e917271c837dbe442a86c53d800/uipath_platform-0.0.25.tar.gz", hash = "sha256:fb83090dc2b451e96dec8d077498438700300e2340e161c729298fa15e583abd", size = 275146, upload-time = "2026-03-17T10:49:30.493Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/66/af0d99e0855b1486e528b79310ae5f6be4c3c5baf00b335d4a686867397e/uipath_platform-0.1.2.tar.gz", hash = "sha256:fb7d2f83f2bc9f8fa98f64d1ccf882ec37b4e8fcdbb13d11b59759d6e7d90fcf", size = 285079, upload-time = "2026-03-20T10:14:44.623Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/6c/009d6895de2e9c7909ed078714796b48fc8ba43df639624e8e2e3251b25a/uipath_platform-0.0.25-py3-none-any.whl", hash = "sha256:8edf33d38b568d3df6356f22074a1521d5d7acbc79df57decbc725aa2dedf24e", size = 166685, upload-time = "2026-03-17T10:49:29.094Z" }, + { url = "https://files.pythonhosted.org/packages/57/81/ca90aa2619a6cf540505a4f348e6557d34f7d63f6984d91d5c0ee80d4dc6/uipath_platform-0.1.2-py3-none-any.whl", hash = "sha256:b161b27d8779a0f3fe222094d43c16531fe0e2bb07a30b9ed67af25c005ee22d", size = 175966, upload-time = "2026-03-20T10:14:43.3Z" }, ] [[package]] @@ -3244,44 +3287,61 @@ wheels = [ [[package]] name = "websockets" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, - { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, - { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, - { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, - { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, - { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, - { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, - { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, - { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, - { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, - { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, - { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, - { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, - { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, - { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, - { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, - { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] [[package]] From 376e97d6862e3fbfb44b681d5e9fb490dd82c0b7 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Sat, 21 Mar 2026 21:52:27 +0200 Subject: [PATCH 4/8] toml update --- packages/uipath_langchain_client/pyproject.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/uipath_langchain_client/pyproject.toml b/packages/uipath_langchain_client/pyproject.toml index e210f0d..2a7968b 100644 --- a/packages/uipath_langchain_client/pyproject.toml +++ b/packages/uipath_langchain_client/pyproject.toml @@ -5,7 +5,7 @@ dynamic = ["version"] readme = "README.md" requires-python = ">=3.11" dependencies = [ - "langchain>=1.2.12", + "langchain>=1.2.13", "uipath-llm-client>=1.5.5", ] @@ -17,17 +17,17 @@ google = [ "langchain-google-genai>=4.2.1", ] anthropic = [ - "langchain-anthropic>=1.3.5", - "anthropic[bedrock,vertex]>=0.85.0", + "langchain-anthropic>=1.4.0", + "anthropic[bedrock,vertex]>=0.86.0", ] aws = [ - "langchain-aws[anthropic]>=1.4.0", + "langchain-aws[anthropic]>=1.4.1", ] vertexai = [ "langchain-google-vertexai>=3.2.2", ] azure = [ - "langchain-azure-ai>=1.1.0", + "langchain-azure-ai>=1.1.1", ] fireworks = [ "langchain-fireworks>=1.1.0", From b2fdda7ccecccd4d03cd356af33847499311607f Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Sat, 21 Mar 2026 23:03:17 +0200 Subject: [PATCH 5/8] fixes --- .../uipath_langchain_client/base_client.py | 76 +++- .../src/uipath_langchain_client/callbacks.py | 24 +- .../clients/normalized/chat_models.py | 118 ++--- tests/langchain/test_dynamic_headers.py | 429 ++++++++++++++++++ 4 files changed, 548 insertions(+), 99 deletions(-) create mode 100644 tests/langchain/test_dynamic_headers.py diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py index 03652ad..08669ae 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py @@ -30,6 +30,10 @@ from typing import Any, Literal from httpx import URL, Response +from langchain_core.callbacks import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) from langchain_core.embeddings import Embeddings from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.messages import BaseMessage @@ -322,6 +326,11 @@ class UiPathBaseChatModel(UiPathBaseLLMClient, BaseChatModel): from the ContextVar (populated by the httpx client's send()) and inject them into the AIMessage's response_metadata under the 'uipath_llmgateway_headers' key. + Dynamic request headers are injected via UiPathDynamicHeadersCallback: set + ``run_inline = True`` (already the default) so LangChain calls + ``on_chat_model_start`` in the same coroutine as ``_agenerate``, ensuring the + ContextVar is visible when ``httpx.send()`` fires. + Passthrough clients that delegate to vendor SDKs should inherit from this class so that headers are captured transparently. """ @@ -329,41 +338,68 @@ class UiPathBaseChatModel(UiPathBaseLLMClient, BaseChatModel): def _generate( self, messages: list[BaseMessage], - *args: Any, + stop: list[str] | None = None, + run_manager: CallbackManagerForLLMRun | None = None, **kwargs: Any, ) -> ChatResult: set_captured_response_headers({}) try: - result = super()._generate(messages, *args, **kwargs) + result = self._uipath_generate(messages, stop=stop, run_manager=run_manager, **kwargs) self._inject_gateway_headers(result.generations) return result finally: set_captured_response_headers({}) + def _uipath_generate( + self, + messages: list[BaseMessage], + stop: list[str] | None = None, + run_manager: CallbackManagerForLLMRun | None = None, + **kwargs: Any, + ) -> ChatResult: + """Override in subclasses to provide the core (non-wrapped) generate logic.""" + return super()._generate(messages, stop=stop, run_manager=run_manager, **kwargs) + async def _agenerate( self, messages: list[BaseMessage], - *args: Any, + stop: list[str] | None = None, + run_manager: AsyncCallbackManagerForLLMRun | None = None, **kwargs: Any, ) -> ChatResult: set_captured_response_headers({}) try: - result = await super()._agenerate(messages, *args, **kwargs) + result = await self._uipath_agenerate( + messages, stop=stop, run_manager=run_manager, **kwargs + ) self._inject_gateway_headers(result.generations) return result finally: set_captured_response_headers({}) + async def _uipath_agenerate( + self, + messages: list[BaseMessage], + stop: list[str] | None = None, + run_manager: AsyncCallbackManagerForLLMRun | None = None, + **kwargs: Any, + ) -> ChatResult: + """Override in subclasses to provide the core (non-wrapped) async generate logic.""" + return await super()._agenerate(messages, stop=stop, run_manager=run_manager, **kwargs) + def _stream( self, messages: list[BaseMessage], - *args: Any, + stop: list[str] | None = None, + run_manager: CallbackManagerForLLMRun | None = None, **kwargs: Any, ) -> Iterator[ChatGenerationChunk]: set_captured_response_headers({}) try: first = True - for chunk in super()._stream(messages, *args, **kwargs): + for chunk in self._uipath_stream( + messages, stop=stop, run_manager=run_manager, **kwargs + ): if first: self._inject_gateway_headers([chunk]) first = False @@ -371,16 +407,29 @@ def _stream( finally: set_captured_response_headers({}) + def _uipath_stream( + self, + messages: list[BaseMessage], + stop: list[str] | None = None, + run_manager: CallbackManagerForLLMRun | None = None, + **kwargs: Any, + ) -> Iterator[ChatGenerationChunk]: + """Override in subclasses to provide the core (non-wrapped) stream logic.""" + yield from super()._stream(messages, stop=stop, run_manager=run_manager, **kwargs) + async def _astream( self, messages: list[BaseMessage], - *args: Any, + stop: list[str] | None = None, + run_manager: AsyncCallbackManagerForLLMRun | None = None, **kwargs: Any, ) -> AsyncIterator[ChatGenerationChunk]: set_captured_response_headers({}) try: first = True - async for chunk in super()._astream(messages, *args, **kwargs): + async for chunk in self._uipath_astream( + messages, stop=stop, run_manager=run_manager, **kwargs + ): if first: self._inject_gateway_headers([chunk]) first = False @@ -388,6 +437,17 @@ async def _astream( finally: set_captured_response_headers({}) + async def _uipath_astream( + self, + messages: list[BaseMessage], + stop: list[str] | None = None, + run_manager: AsyncCallbackManagerForLLMRun | None = None, + **kwargs: Any, + ) -> AsyncIterator[ChatGenerationChunk]: + """Override in subclasses to provide the core (non-wrapped) async stream logic.""" + async for chunk in super()._astream(messages, stop=stop, run_manager=run_manager, **kwargs): + yield chunk + def _inject_gateway_headers(self, generations: Sequence[ChatGeneration]) -> None: """Inject captured gateway headers into each generation's response_metadata.""" if not self.captured_headers: diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py b/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py index c4be557..af99ac1 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/callbacks.py @@ -2,23 +2,19 @@ from abc import abstractmethod from typing import Any -from uuid import UUID from langchain_core.callbacks import BaseCallbackHandler -from langchain_core.messages import BaseMessage -from uipath.llm_client.utils.headers import ( - set_dynamic_request_headers, -) +from uipath.llm_client.utils.headers import set_dynamic_request_headers class UiPathDynamicHeadersCallback(BaseCallbackHandler): """Base callback for injecting dynamic headers into each LLM gateway request. Extend this class and implement ``get_headers()`` to return the headers to - inject. The headers are stored in a ContextVar before each LLM call and read - by the httpx client's ``send()`` method, so they flow transparently through - the call stack regardless of which vendor SDK is in use. + inject. ``run_inline = True`` ensures ``on_chat_model_start`` is called + directly in the caller's coroutine (not via ``asyncio.gather``), so the + ContextVar mutation is visible when ``httpx.send()`` fires. Example (OTEL trace propagation):: @@ -34,6 +30,8 @@ def get_headers(self) -> dict[str, str]: response = chat.invoke("Hello!", config={"callbacks": [OtelHeadersCallback()]}) """ + run_inline: bool = True # dispatch in the caller's coroutine, not via asyncio.gather + @abstractmethod def get_headers(self) -> dict[str, str]: """Return headers to inject into the next LLM gateway request.""" @@ -42,9 +40,7 @@ def get_headers(self) -> dict[str, str]: def on_chat_model_start( self, serialized: dict[str, Any], - messages: list[list[BaseMessage]], - *, - run_id: UUID, + messages: list[list[Any]], **kwargs: Any, ) -> None: set_dynamic_request_headers(self.get_headers()) @@ -53,14 +49,12 @@ def on_llm_start( self, serialized: dict[str, Any], prompts: list[str], - *, - run_id: UUID, **kwargs: Any, ) -> None: set_dynamic_request_headers(self.get_headers()) - def on_llm_end(self, response: Any, *, run_id: UUID, **kwargs: Any) -> None: + def on_llm_end(self, response: Any, **kwargs: Any) -> None: set_dynamic_request_headers({}) - def on_llm_error(self, error: BaseException, *, run_id: UUID, **kwargs: Any) -> None: + def on_llm_error(self, error: BaseException, **kwargs: Any) -> None: set_dynamic_request_headers({}) diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py index a15ffb6..582181c 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/clients/normalized/chat_models.py @@ -56,10 +56,6 @@ ) from pydantic import Field -from uipath.llm_client.utils.headers import ( - extract_matching_headers, - set_captured_response_headers, -) from uipath_langchain_client.base_client import UiPathBaseChatModel from uipath_langchain_client.settings import ApiType, RoutingMode, UiPathAPIConfig @@ -311,39 +307,27 @@ def _postprocess_response(self, response: dict[str, Any]) -> ChatResult: llm_output=llm_output, ) - def _generate( + def _uipath_generate( self, messages: list[BaseMessage], - *args: Any, + stop: list[str] | None = None, run_manager: CallbackManagerForLLMRun | None = None, **kwargs: Any, ) -> ChatResult: - request_body = self._preprocess_request(messages, **kwargs) + request_body = self._preprocess_request(messages, stop=stop, **kwargs) response = self.uipath_request(request_body=request_body, raise_status_error=True) - result = self._postprocess_response(response.json()) - if self.captured_headers: - captured = extract_matching_headers(response.headers, self.captured_headers) - if captured: - for gen in result.generations: - gen.message.response_metadata["uipath_llmgateway_headers"] = captured - return result - - async def _agenerate( + return self._postprocess_response(response.json()) + + async def _uipath_agenerate( self, messages: list[BaseMessage], - *args: Any, + stop: list[str] | None = None, run_manager: AsyncCallbackManagerForLLMRun | None = None, **kwargs: Any, ) -> ChatResult: - request_body = self._preprocess_request(messages, **kwargs) + request_body = self._preprocess_request(messages, stop=stop, **kwargs) response = await self.uipath_arequest(request_body=request_body, raise_status_error=True) - result = self._postprocess_response(response.json()) - if self.captured_headers: - captured = extract_matching_headers(response.headers, self.captured_headers) - if captured: - for gen in result.generations: - gen.message.response_metadata["uipath_llmgateway_headers"] = captured - return result + return self._postprocess_response(response.json()) def _generate_chunk( self, original_message: str, json_data: dict[str, Any] @@ -402,64 +386,46 @@ def _generate_chunk( ), ) - def _stream( + def _uipath_stream( self, messages: list[BaseMessage], - *args: Any, + stop: list[str] | None = None, run_manager: CallbackManagerForLLMRun | None = None, **kwargs: Any, ) -> Iterator[ChatGenerationChunk]: - request_body = self._preprocess_request(messages, **kwargs) - set_captured_response_headers({}) - try: - first = True - for chunk in self.uipath_stream( - request_body=request_body, stream_type="lines", raise_status_error=True - ): - chunk = str(chunk) - if chunk.startswith("data:"): - chunk = chunk.split("data:")[1].strip() - try: - json_data = json.loads(chunk) - except json.JSONDecodeError: - continue - if "id" in json_data and not json_data["id"]: - continue - gen_chunk = self._generate_chunk(chunk, json_data) - if first: - self._inject_gateway_headers([gen_chunk]) - first = False - yield gen_chunk - finally: - set_captured_response_headers({}) - - async def _astream( + request_body = self._preprocess_request(messages, stop=stop, **kwargs) + for chunk in self.uipath_stream( + request_body=request_body, stream_type="lines", raise_status_error=True + ): + chunk = str(chunk) + if chunk.startswith("data:"): + chunk = chunk.split("data:")[1].strip() + try: + json_data = json.loads(chunk) + except json.JSONDecodeError: + continue + if "id" in json_data and not json_data["id"]: + continue + yield self._generate_chunk(chunk, json_data) + + async def _uipath_astream( self, messages: list[BaseMessage], - *args: Any, + stop: list[str] | None = None, run_manager: AsyncCallbackManagerForLLMRun | None = None, **kwargs: Any, ) -> AsyncIterator[ChatGenerationChunk]: - request_body = self._preprocess_request(messages, **kwargs) - set_captured_response_headers({}) - try: - first = True - async for chunk in self.uipath_astream( - request_body=request_body, stream_type="lines", raise_status_error=True - ): - chunk = str(chunk) - if chunk.startswith("data:"): - chunk = chunk.split("data:")[1].strip() - try: - json_data = json.loads(chunk) - except json.JSONDecodeError: - continue - if "id" in json_data and not json_data["id"]: - continue - gen_chunk = self._generate_chunk(chunk, json_data) - if first: - self._inject_gateway_headers([gen_chunk]) - first = False - yield gen_chunk - finally: - set_captured_response_headers({}) + request_body = self._preprocess_request(messages, stop=stop, **kwargs) + async for chunk in self.uipath_astream( + request_body=request_body, stream_type="lines", raise_status_error=True + ): + chunk = str(chunk) + if chunk.startswith("data:"): + chunk = chunk.split("data:")[1].strip() + try: + json_data = json.loads(chunk) + except json.JSONDecodeError: + continue + if "id" in json_data and not json_data["id"]: + continue + yield self._generate_chunk(chunk, json_data) diff --git a/tests/langchain/test_dynamic_headers.py b/tests/langchain/test_dynamic_headers.py new file mode 100644 index 0000000..af5c74a --- /dev/null +++ b/tests/langchain/test_dynamic_headers.py @@ -0,0 +1,429 @@ +"""Tests for dynamic request header injection via UiPathDynamicHeadersCallback. + +Tests that headers set via callbacks are injected into outgoing LLM gateway +requests through the ContextVar mechanism in httpx_client.send(). + +Design of OtelHeadersCallback: + openinference-instrumentation-langchain intentionally does NOT attach its spans + to the Python context (to avoid leaked contexts on errors). This means + get_current_span() returns the *caller's* outer span — which is exactly the + right behaviour for distributed tracing: the application creates a parent span, + calls LangChain, and the callback propagates that parent span's context to the + LLM gateway so the gateway request becomes part of the same trace. +""" + +import os +from collections.abc import Iterator +from contextlib import contextmanager +from unittest.mock import patch + +import httpx +import pytest +from openinference.instrumentation.langchain import LangChainInstrumentor +from opentelemetry import propagate, trace +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import SimpleSpanProcessor +from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter +from opentelemetry.trace import Span, Tracer +from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator +from uipath_langchain_client import UiPathDynamicHeadersCallback +from uipath_langchain_client.clients.normalized.chat_models import UiPathChat + +from uipath.llm_client.httpx_client import UiPathHttpxAsyncClient, UiPathHttpxClient +from uipath.llm_client.settings import LLMGatewaySettings +from uipath.llm_client.settings.utils import SingletonMeta +from uipath.llm_client.utils.headers import ( + get_dynamic_request_headers, + set_dynamic_request_headers, +) + + +@contextmanager +def active_span(tracer: Tracer, name: str) -> Iterator[Span]: + """Start a named span and yield the current span via get_current_span().""" + with tracer.start_as_current_span(name): + yield trace.get_current_span() + + +# ============================================================================ +# OtelHeadersCallback — reads the active span via get_current_span() +# ============================================================================ + + +class OtelHeadersCallback(UiPathDynamicHeadersCallback): + """Injects the active OTEL span's trace and span IDs into each LLM gateway request. + + Calls get_current_span() to read the span that is active in the caller's + context (e.g. an outer application span). openinference-instrumentation-langchain + creates its own child spans but deliberately does not attach them to the + Python context, so get_current_span() always reflects the caller's outer span. + + This propagates the application's trace context to the LLM gateway, making + the gateway request part of the same distributed trace. + """ + + def get_headers(self) -> dict[str, str]: + span = trace.get_current_span() + ctx = span.get_span_context() + if not ctx.is_valid: + return {} + return { + "x-trace-id": format(ctx.trace_id, "032x"), + "x-span-id": format(ctx.span_id, "016x"), + } + + +# ============================================================================ +# Fixtures & helpers +# ============================================================================ + +LLMGW_ENV = { + "LLMGW_URL": "https://cloud.uipath.com", + "LLMGW_SEMANTIC_ORG_ID": "test-org-id", + "LLMGW_SEMANTIC_TENANT_ID": "test-tenant-id", + "LLMGW_REQUESTING_PRODUCT": "test-product", + "LLMGW_REQUESTING_FEATURE": "test-feature", + "LLMGW_ACCESS_TOKEN": "test-access-token", +} + +_CHAT_RESPONSE = ( + b'{"id":"x","object":"chat.completion","created":1,"model":"gpt-4o",' + b'"choices":[{"index":0,"message":{"role":"assistant","content":"hi"},' + b'"finish_reason":"stop"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}' +) + + +class RequestCapturingTransport(httpx.BaseTransport): + """Sync transport that records the last request's headers.""" + + def __init__(self): + self.last_request_headers: dict[str, str] = {} + + def handle_request(self, request: httpx.Request) -> httpx.Response: + self.last_request_headers = dict(request.headers) + return httpx.Response( + 200, content=_CHAT_RESPONSE, headers={"content-type": "application/json"} + ) + + +class AsyncRequestCapturingTransport(httpx.AsyncBaseTransport): + """Async transport that records the last request's headers.""" + + def __init__(self): + self._sync = RequestCapturingTransport() + + @property + def last_request_headers(self) -> dict[str, str]: + return self._sync.last_request_headers + + async def handle_async_request(self, request: httpx.Request) -> httpx.Response: + return self._sync.handle_request(request) + + +@pytest.fixture(scope="module") +def otel_exporter(): + """Module-scoped OTEL setup with LangChain instrumentation. + + TracerProvider + InMemorySpanExporter + LangChainInstrumentor are created + once for the whole module. Individual tests clear the exporter via the + `tracer` fixture before each run. + """ + exporter = InMemorySpanExporter() + provider = TracerProvider() + provider.add_span_processor(SimpleSpanProcessor(exporter)) + trace.set_tracer_provider(provider) + propagate.set_global_textmap(TraceContextTextMapPropagator()) + LangChainInstrumentor().instrument() + yield exporter + LangChainInstrumentor().uninstrument() + + +@pytest.fixture +def tracer(otel_exporter: InMemorySpanExporter) -> Tracer: + """Per-test tracer; clears the exporter so spans from previous tests don't leak.""" + otel_exporter.clear() + return trace.get_tracer("uipath-test") + + +@pytest.fixture(autouse=True) +def clear_singletons(): + SingletonMeta._instances.clear() + yield + SingletonMeta._instances.clear() + + +@pytest.fixture(autouse=True) +def reset_dynamic_headers(): + set_dynamic_request_headers({}) + yield + set_dynamic_request_headers({}) + + +@pytest.fixture +def llmgw_settings(): + with patch.dict(os.environ, LLMGW_ENV, clear=True): + return LLMGatewaySettings() + + +def _make_chat_with_sync_transport( + settings: LLMGatewaySettings, + transport: RequestCapturingTransport, +) -> UiPathChat: + chat = UiPathChat(model="gpt-4o", settings=settings) + sync_client = UiPathHttpxClient( + base_url="https://cloud.uipath.com/test-org-id/test-tenant-id/llmgateway_/api/chat/completions", + model_name="gpt-4o", + transport=transport, + ) + object.__setattr__(chat, "uipath_sync_client", sync_client) + return chat + + +def _make_chat_with_async_transport( + settings: LLMGatewaySettings, + transport: AsyncRequestCapturingTransport, +) -> UiPathChat: + chat = UiPathChat(model="gpt-4o", settings=settings) + async_client = UiPathHttpxAsyncClient( + base_url="https://cloud.uipath.com/test-org-id/test-tenant-id/llmgateway_/api/chat/completions", + model_name="gpt-4o", + transport=transport, + ) + object.__setattr__(chat, "uipath_async_client", async_client) + return chat + + +# ============================================================================ +# Test ContextVar helpers +# ============================================================================ + + +class TestDynamicRequestHeadersContextVar: + def test_default_is_empty(self): + assert get_dynamic_request_headers() == {} + + def test_set_and_get(self): + set_dynamic_request_headers({"X-Custom": "value"}) + assert get_dynamic_request_headers() == {"X-Custom": "value"} + + def test_set_empty_clears(self): + set_dynamic_request_headers({"X-Custom": "value"}) + set_dynamic_request_headers({}) + assert get_dynamic_request_headers() == {} + + def test_get_returns_copy(self): + set_dynamic_request_headers({"X-Custom": "value"}) + result = get_dynamic_request_headers() + result["X-New"] = "other" + assert "X-New" not in get_dynamic_request_headers() + + +# ============================================================================ +# Test callback lifecycle +# ============================================================================ + + +class TestCallbackLifecycle: + def test_run_inline_is_true(self): + """run_inline ensures on_chat_model_start runs in the caller's coroutine.""" + assert OtelHeadersCallback().run_inline is True + + def test_on_chat_model_start_sets_headers(self, tracer): + """on_chat_model_start injects the active span's headers into the ContextVar.""" + cb = OtelHeadersCallback() + with active_span(tracer, "llm-call"): + cb.on_chat_model_start({}, [[]]) + assert "x-trace-id" in get_dynamic_request_headers() + + def test_on_chat_model_start_no_span_sets_empty(self): + """When there is no active span, on_chat_model_start clears the ContextVar.""" + set_dynamic_request_headers({"x-stale": "value"}) + OtelHeadersCallback().on_chat_model_start({}, [[]]) + assert get_dynamic_request_headers() == {} + + +# ============================================================================ +# Test OtelHeadersCallback.get_headers() +# ============================================================================ + + +class TestOtelHeadersCallback: + def test_no_active_span_returns_empty(self): + assert OtelHeadersCallback().get_headers() == {} + + def test_active_span_injects_trace_and_span_ids(self, tracer): + cb = OtelHeadersCallback() + with active_span(tracer, "test-span") as span: + headers = cb.get_headers() + ctx = span.get_span_context() + assert headers["x-trace-id"] == format(ctx.trace_id, "032x") + assert headers["x-span-id"] == format(ctx.span_id, "016x") + + def test_trace_id_is_32_hex_chars(self, tracer): + cb = OtelHeadersCallback() + with active_span(tracer, "test-span"): + headers = cb.get_headers() + assert len(headers["x-trace-id"]) == 32 + assert all(c in "0123456789abcdef" for c in headers["x-trace-id"]) + + def test_span_id_is_16_hex_chars(self, tracer): + cb = OtelHeadersCallback() + with active_span(tracer, "test-span"): + headers = cb.get_headers() + assert len(headers["x-span-id"]) == 16 + assert all(c in "0123456789abcdef" for c in headers["x-span-id"]) + + def test_different_spans_produce_different_span_ids(self, tracer): + cb = OtelHeadersCallback() + with active_span(tracer, "span-a"): + headers_a = cb.get_headers() + with active_span(tracer, "span-b"): + headers_b = cb.get_headers() + assert headers_a["x-span-id"] != headers_b["x-span-id"] + + +# ============================================================================ +# Test httpx client injects dynamic headers in send() +# ============================================================================ + + +class TestHttpxClientDynamicHeaderInjection: + def test_sync_client_injects_headers(self): + transport = RequestCapturingTransport() + client = UiPathHttpxClient(base_url="https://example.com", transport=transport) + set_dynamic_request_headers({"x-custom-trace": "trace-123"}) + client.get("/") + assert transport.last_request_headers.get("x-custom-trace") == "trace-123" + client.close() + + def test_sync_client_no_injection_when_empty(self): + transport = RequestCapturingTransport() + client = UiPathHttpxClient(base_url="https://example.com", transport=transport) + client.get("/") + assert "x-custom-trace" not in transport.last_request_headers + client.close() + + @pytest.mark.asyncio + async def test_async_client_injects_headers(self): + transport = AsyncRequestCapturingTransport() + client = UiPathHttpxAsyncClient(base_url="https://example.com", transport=transport) + set_dynamic_request_headers({"x-custom-trace": "trace-456"}) + await client.get("/") + assert transport.last_request_headers.get("x-custom-trace") == "trace-456" + await client.aclose() + + def test_dynamic_headers_can_override_defaults(self): + transport = RequestCapturingTransport() + client = UiPathHttpxClient(base_url="https://example.com", transport=transport) + set_dynamic_request_headers({"X-UiPath-LLMGateway-TimeoutSeconds": "60"}) + client.get("/") + assert transport.last_request_headers.get("x-uipath-llmgateway-timeoutseconds") == "60" + client.close() + + +# ============================================================================ +# End-to-end: openinference instruments LangChain, callback propagates outer span +# +# openinference does NOT attach its spans to the Python context (intentional +# design to avoid leaked contexts). get_current_span() therefore returns the +# caller's outer span — perfect for propagating application trace context to +# the LLM gateway. +# ============================================================================ + + +class TestOpenInferenceEndToEnd: + def test_outer_span_context_injected_into_request( + self, otel_exporter, llmgw_settings, tracer + ): + """Outer span is the current span; its context is injected into the LLM request. + openinference creates child spans (same trace_id) but doesn't override the + current span context, so get_current_span() returns the outer span.""" + transport = RequestCapturingTransport() + chat = _make_chat_with_sync_transport(llmgw_settings, transport) + cb = OtelHeadersCallback() + + with active_span(tracer, "my-app-operation") as outer: + chat.invoke("Hello!", config={"callbacks": [cb]}) + + # openinference created child spans + spans = otel_exporter.get_finished_spans() + assert len(spans) > 0 + + outer_trace_id = format(outer.get_span_context().trace_id, "032x") + outer_span_id = format(outer.get_span_context().span_id, "016x") + + # The outer span's context was injected into the HTTP request + assert transport.last_request_headers.get("x-trace-id") == outer_trace_id + assert transport.last_request_headers.get("x-span-id") == outer_span_id + + # All openinference spans belong to the same trace + for span in spans: + assert format(span.context.trace_id, "032x") == outer_trace_id + + @pytest.mark.asyncio + async def test_async_outer_span_context_injected( + self, otel_exporter, llmgw_settings, tracer + ): + """Async path: outer span context propagates through ainvoke to the request.""" + transport = AsyncRequestCapturingTransport() + chat = _make_chat_with_async_transport(llmgw_settings, transport) + cb = OtelHeadersCallback() + + with active_span(tracer, "async-app-operation") as outer: + await chat.ainvoke("Hello!", config={"callbacks": [cb]}) + + outer_trace_id = format(outer.get_span_context().trace_id, "032x") + outer_span_id = format(outer.get_span_context().span_id, "016x") + + assert transport.last_request_headers.get("x-trace-id") == outer_trace_id + assert transport.last_request_headers.get("x-span-id") == outer_span_id + + def test_no_outer_span_no_headers_but_spans_still_created( + self, otel_exporter, llmgw_settings + ): + """Without an outer span, get_current_span() is invalid → no headers injected. + openinference still creates its own root spans for observability.""" + transport = RequestCapturingTransport() + chat = _make_chat_with_sync_transport(llmgw_settings, transport) + cb = OtelHeadersCallback() + + chat.invoke("Hello!", config={"callbacks": [cb]}) + + # openinference still exports spans + spans = otel_exporter.get_finished_spans() + assert len(spans) > 0 + + # But no trace headers were injected (no outer span to propagate) + assert "x-trace-id" not in transport.last_request_headers + assert "x-span-id" not in transport.last_request_headers + + def test_headers_cleared_after_invoke(self, otel_exporter, llmgw_settings, tracer): + """ContextVar is reset to empty after the call completes.""" + transport = RequestCapturingTransport() + chat = _make_chat_with_sync_transport(llmgw_settings, transport) + cb = OtelHeadersCallback() + + with active_span(tracer, "my-operation"): + chat.invoke("Hello!", config={"callbacks": [cb]}) + + assert get_dynamic_request_headers() == {} + + def test_sequential_calls_propagate_different_outer_spans( + self, otel_exporter, llmgw_settings, tracer + ): + """Each call with a different outer span gets different span IDs in headers.""" + transport = RequestCapturingTransport() + chat = _make_chat_with_sync_transport(llmgw_settings, transport) + cb = OtelHeadersCallback() + + with active_span(tracer, "first-operation") as first: + chat.invoke("First", config={"callbacks": [cb]}) + first_span_id = transport.last_request_headers.get("x-span-id") + + with active_span(tracer, "second-operation") as second: + chat.invoke("Second", config={"callbacks": [cb]}) + second_span_id = transport.last_request_headers.get("x-span-id") + + assert first_span_id == format(first.get_span_context().span_id, "016x") + assert second_span_id == format(second.get_span_context().span_id, "016x") + assert first_span_id != second_span_id From 2da58a58ccd47ec42baf16e90a70dcf5fab50419 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Sun, 22 Mar 2026 10:07:21 +0200 Subject: [PATCH 6/8] version update --- CHANGELOG.md | 6 ++++++ packages/uipath_langchain_client/CHANGELOG.md | 7 +++++++ packages/uipath_langchain_client/pyproject.toml | 2 +- .../src/uipath_langchain_client/__version__.py | 2 +- src/uipath/llm_client/__version__.py | 2 +- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23f5f29..affa8e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `uipath_llm_client` (core package) will be documented in this file. +## [1.5.6] - 2026-03-21 + +### Feature +- Added `_DYNAMIC_REQUEST_HEADERS` ContextVar and helper functions (`get_dynamic_request_headers`, `set_dynamic_request_headers`) to `utils/headers.py` +- Inject dynamic request headers in httpx `send()` for both sync and async clients + ## [1.5.5] - 2026-03-19 ### Fix diff --git a/packages/uipath_langchain_client/CHANGELOG.md b/packages/uipath_langchain_client/CHANGELOG.md index e49b878..cd66913 100644 --- a/packages/uipath_langchain_client/CHANGELOG.md +++ b/packages/uipath_langchain_client/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to `uipath_langchain_client` will be documented in this file. +## [1.5.6] - 2026-03-21 + +### Feature +- Added `UiPathDynamicHeadersCallback`: extend and implement `get_headers()` to inject custom headers into each LLM gateway request +- Uses `run_inline = True` so `on_chat_model_start`/`on_llm_start` run in the caller's coroutine, ensuring ContextVar mutations propagate to `httpx.send()` +- Cleanup via `on_llm_end`/`on_llm_error` + ## [1.5.5] - 2026-03-19 ### Fix headers diff --git a/packages/uipath_langchain_client/pyproject.toml b/packages/uipath_langchain_client/pyproject.toml index 2a7968b..3e55e08 100644 --- a/packages/uipath_langchain_client/pyproject.toml +++ b/packages/uipath_langchain_client/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" requires-python = ">=3.11" dependencies = [ "langchain>=1.2.13", - "uipath-llm-client>=1.5.5", + "uipath-llm-client>=1.5.6", ] [project.optional-dependencies] diff --git a/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py b/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py index 672d913..35428ab 100644 --- a/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py +++ b/packages/uipath_langchain_client/src/uipath_langchain_client/__version__.py @@ -1,3 +1,3 @@ __title__ = "UiPath LangChain Client" __description__ = "A Python client for interacting with UiPath's LLM services via LangChain." -__version__ = "1.5.5" +__version__ = "1.5.6" diff --git a/src/uipath/llm_client/__version__.py b/src/uipath/llm_client/__version__.py index 15350c0..418bec6 100644 --- a/src/uipath/llm_client/__version__.py +++ b/src/uipath/llm_client/__version__.py @@ -1,3 +1,3 @@ __title__ = "UiPath LLM Client" __description__ = "A Python client for interacting with UiPath's LLM services." -__version__ = "1.5.5" +__version__ = "1.5.6" From a242eb3ad29f37cd4f561fda8e5c75c5c14e6479 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Sun, 22 Mar 2026 10:11:19 +0200 Subject: [PATCH 7/8] workflow update --- .github/workflows/cd-langchain.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cd-langchain.yml b/.github/workflows/cd-langchain.yml index 6bd5199..ca88e60 100644 --- a/.github/workflows/cd-langchain.yml +++ b/.github/workflows/cd-langchain.yml @@ -34,7 +34,8 @@ jobs: exit 0 fi - echo "Core package version also changed — waiting for 'cd' workflow to succeed..." + CORE_VERSION=$(grep '^__version__' src/uipath/llm_client/__version__.py | sed -n 's/.*"\([^"]*\)".*/\1/p') + echo "Core package version also changed ($CORE_VERSION) — waiting for 'cd' workflow to succeed..." # Poll the cd workflow for up to 15 minutes for i in $(seq 1 30); do @@ -43,9 +44,17 @@ jobs: case "$STATUS" in completed:success) - echo "cd workflow succeeded — waiting 120s for PyPI indexing..." - sleep 120 - exit 0 + echo "cd workflow succeeded — polling PyPI until uipath-llm-client==$CORE_VERSION is available..." + for j in $(seq 1 30); do + if uv pip index versions uipath-llm-client 2>/dev/null | grep -q "$CORE_VERSION"; then + echo "uipath-llm-client==$CORE_VERSION is available on PyPI." + exit 0 + fi + echo " Not yet available, retrying in 30s ($j/30)..." + sleep 30 + done + echo "::error::uipath-llm-client==$CORE_VERSION never appeared on PyPI (15 min)" + exit 1 ;; completed:*) echo "::error::cd workflow finished with: $STATUS" From 4660f3b43e0b14b873e32cdbc8f29cf1084749e4 Mon Sep 17 00:00:00 2001 From: Cosmin Maria Date: Sun, 22 Mar 2026 10:14:05 +0200 Subject: [PATCH 8/8] fixes --- tests/langchain/test_dynamic_headers.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/langchain/test_dynamic_headers.py b/tests/langchain/test_dynamic_headers.py index af5c74a..4472844 100644 --- a/tests/langchain/test_dynamic_headers.py +++ b/tests/langchain/test_dynamic_headers.py @@ -332,9 +332,7 @@ def test_dynamic_headers_can_override_defaults(self): class TestOpenInferenceEndToEnd: - def test_outer_span_context_injected_into_request( - self, otel_exporter, llmgw_settings, tracer - ): + def test_outer_span_context_injected_into_request(self, otel_exporter, llmgw_settings, tracer): """Outer span is the current span; its context is injected into the LLM request. openinference creates child spans (same trace_id) but doesn't override the current span context, so get_current_span() returns the outer span.""" @@ -361,9 +359,7 @@ def test_outer_span_context_injected_into_request( assert format(span.context.trace_id, "032x") == outer_trace_id @pytest.mark.asyncio - async def test_async_outer_span_context_injected( - self, otel_exporter, llmgw_settings, tracer - ): + async def test_async_outer_span_context_injected(self, otel_exporter, llmgw_settings, tracer): """Async path: outer span context propagates through ainvoke to the request.""" transport = AsyncRequestCapturingTransport() chat = _make_chat_with_async_transport(llmgw_settings, transport) @@ -378,9 +374,7 @@ async def test_async_outer_span_context_injected( assert transport.last_request_headers.get("x-trace-id") == outer_trace_id assert transport.last_request_headers.get("x-span-id") == outer_span_id - def test_no_outer_span_no_headers_but_spans_still_created( - self, otel_exporter, llmgw_settings - ): + def test_no_outer_span_no_headers_but_spans_still_created(self, otel_exporter, llmgw_settings): """Without an outer span, get_current_span() is invalid → no headers injected. openinference still creates its own root spans for observability.""" transport = RequestCapturingTransport()