From 3f3c261a02f32cfee97c8aff03d6d3b79b2b3176 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:24:44 -0700 Subject: [PATCH 01/10] build(deps): bump langchain-openai to 1.7.2 and lift semantic-router's openai cap in the lock --- pyproject.toml | 4 +- uv.lock | 421 +++++++++++++++++++++++++------------------------ 2 files changed, 217 insertions(+), 208 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9076760153a..039d540a43d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -291,7 +291,7 @@ ci = [ "blockbuster==1.5.26", "beautifulsoup4==4.14.3", "pylint==4.0.5", - "langchain-openai==1.1.14", + "langchain-openai==1.7.2", "langgraph>=1.2.4,<1.3.0", "langgraph-prebuilt>=1.1.0,<1.3.0", "claude-agent-sdk==0.2.165", @@ -363,6 +363,8 @@ constraint-dependencies = [ override-dependencies = [ # a2a-sdk 1.x requires packaging>=24.0; lunary 1.4.x still caps at <24.0. "packaging>=24.0", + # semantic-router (through 1.1.0) caps openai at <3.0.0 but only imports it for encoders LiteLLM never builds. + "openai>=2.20.0,<4.0.0", "cryptography>=50.0.0,<51.0", "mcp>=2.3.0,<3", ] diff --git a/uv.lock b/uv.lock index 03403828225..301225a8277 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 5 +revision = 3 requires-python = ">=3.10, <3.15" resolution-markers = [ "python_full_version >= '3.14'", @@ -10,7 +10,7 @@ resolution-markers = [ ] [options] -exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. +exclude-newer = "2026-10-18T01:19:57.352406047Z" exclude-newer-span = "P3D" [manifest] @@ -30,6 +30,7 @@ constraints = [ overrides = [ { name = "cryptography", specifier = ">=50.0.0,<51.0" }, { name = "mcp", specifier = ">=2.3.0,<3" }, + { name = "openai", specifier = ">=2.20.0,<4.0.0" }, { name = "packaging", specifier = ">=24.0" }, ] @@ -227,9 +228,9 @@ name = "aiologic" version = "0.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "sniffio" }, - { name = "typing-extensions" }, - { name = "wrapt" }, + { name = "sniffio", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "wrapt", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/a7/809482759f40079f4c4328c7318bf569ae25d457f5017aad30a1b9aafedc/aiologic-0.17.0.tar.gz", hash = "sha256:65aa058e858c94cd208badb188e7f00b54dcabb3ba85b34f794db98074d108b9", size = 251625, upload-time = "2026-06-14T12:24:35.367Z" } wheels = [ @@ -521,14 +522,14 @@ name = "aurelio-sdk" version = "0.0.19" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiofiles" }, - { name = "aiohttp" }, - { name = "colorlog" }, - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "requests-toolbelt" }, - { name = "tornado" }, + { name = "aiofiles", marker = "python_full_version < '3.14'" }, + { name = "aiohttp", marker = "python_full_version < '3.14'" }, + { name = "colorlog", marker = "python_full_version < '3.14'" }, + { name = "pydantic", marker = "python_full_version < '3.14'" }, + { name = "python-dotenv", marker = "python_full_version < '3.14'" }, + { name = "requests", marker = "python_full_version < '3.14'" }, + { name = "requests-toolbelt", marker = "python_full_version < '3.14'" }, + { name = "tornado", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/27/0e/c2e369ad173fb3d76448e46d10beb3dcc53388318933ddf8169a3f21a810/aurelio_sdk-0.0.19.tar.gz", hash = "sha256:14107e7440ff2efd0b4a08c52fb595e7680bd4bc973a0ddfb3b64157c6666b91", size = 15258, upload-time = "2025-03-24T14:37:32.203Z" } wheels = [ @@ -540,9 +541,9 @@ name = "aws-sdk-bedrock-runtime" version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "smithy-aws-core", extra = ["eventstream", "json"] }, - { name = "smithy-core" }, - { name = "smithy-http", extra = ["aiohttp"] }, + { name = "smithy-aws-core", extra = ["eventstream", "json"], marker = "python_full_version >= '3.12'" }, + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, + { name = "smithy-http", extra = ["aiohttp"], marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8e/b3/9c225cbfe9f17ea2e3d75a0fdd0b325ef79839b9c09a376bda63a7bf3bb3/aws_sdk_bedrock_runtime-0.11.0.tar.gz", hash = "sha256:f2c45d34625bf6a7b56375e29a53a16b376880bda771e4bbf7d84491622eb193", size = 173854, upload-time = "2026-08-24T21:17:16.304Z" } wheels = [ @@ -551,7 +552,7 @@ wheels = [ [package.optional-dependencies] awscrt = [ - { name = "smithy-http", extra = ["awscrt"] }, + { name = "smithy-http", extra = ["awscrt"], marker = "python_full_version >= '3.12'" }, ] [[package]] @@ -1308,7 +1309,7 @@ name = "colorlog" version = "6.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "colorama", marker = "python_full_version < '3.14' and sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } wheels = [ @@ -1332,7 +1333,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } wheels = [ @@ -1405,7 +1406,7 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } @@ -1675,8 +1676,8 @@ name = "culsans" version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiologic" }, - { name = "typing-extensions" }, + { name = "aiologic", marker = "python_full_version < '3.13'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" } wheels = [ @@ -1930,7 +1931,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -2513,11 +2514,11 @@ resolution-markers = [ "python_full_version >= '3.14'", ] dependencies = [ - { name = "google-auth" }, - { name = "googleapis-common-protos" }, - { name = "proto-plus" }, - { name = "protobuf" }, - { name = "requests" }, + { name = "google-auth", marker = "python_full_version >= '3.14'" }, + { name = "googleapis-common-protos", marker = "python_full_version >= '3.14'" }, + { name = "proto-plus", marker = "python_full_version >= '3.14'" }, + { name = "protobuf", marker = "python_full_version >= '3.14'" }, + { name = "requests", marker = "python_full_version >= '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/09/cd/63f1557235c2440fe0577acdbc32577c5c002684c58c7f4d770a92366a24/google_api_core-2.25.2.tar.gz", hash = "sha256:1c63aa6af0d0d5e37966f157a77f9396d820fba59f9e43e9415bc3dc5baff300", size = 166266, upload-time = "2025-10-03T00:07:34.778Z" } wheels = [ @@ -2526,8 +2527,8 @@ wheels = [ [package.optional-dependencies] grpc = [ - { name = "grpcio" }, - { name = "grpcio-status" }, + { name = "grpcio", marker = "python_full_version >= '3.14'" }, + { name = "grpcio-status", marker = "python_full_version >= '3.14'" }, ] [[package]] @@ -2541,11 +2542,11 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "google-auth" }, - { name = "googleapis-common-protos" }, - { name = "proto-plus" }, - { name = "protobuf" }, - { name = "requests" }, + { name = "google-auth", marker = "python_full_version < '3.14'" }, + { name = "googleapis-common-protos", marker = "python_full_version < '3.14'" }, + { name = "proto-plus", marker = "python_full_version < '3.14'" }, + { name = "protobuf", marker = "python_full_version < '3.14'" }, + { name = "requests", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/16/ce/502a57fb0ec752026d24df1280b162294b22a0afb98a326084f9a979138b/google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b", size = 177001, upload-time = "2026-04-10T00:41:28.035Z" } wheels = [ @@ -2554,8 +2555,8 @@ wheels = [ [package.optional-dependencies] grpc = [ - { name = "grpcio" }, - { name = "grpcio-status" }, + { name = "grpcio", marker = "python_full_version < '3.14'" }, + { name = "grpcio-status", marker = "python_full_version < '3.14'" }, ] [[package]] @@ -2741,12 +2742,12 @@ resolution-markers = [ "python_full_version >= '3.14'", ] dependencies = [ - { name = "google-api-core", version = "2.25.2", source = { registry = "https://pypi.org/simple" } }, - { name = "google-auth" }, - { name = "google-cloud-core" }, - { name = "google-crc32c" }, - { name = "google-resumable-media" }, - { name = "requests" }, + { name = "google-api-core", version = "2.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, + { name = "google-auth", marker = "python_full_version >= '3.14'" }, + { name = "google-cloud-core", marker = "python_full_version >= '3.14'" }, + { name = "google-crc32c", marker = "python_full_version >= '3.14'" }, + { name = "google-resumable-media", marker = "python_full_version >= '3.14'" }, + { name = "requests", marker = "python_full_version >= '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bd/ef/7cefdca67a6c8b3af0ec38612f9e78e5a9f6179dd91352772ae1a9849246/google_cloud_storage-3.4.1.tar.gz", hash = "sha256:6f041a297e23a4b485fad8c305a7a6e6831855c208bcbe74d00332a909f82268", size = 17238203, upload-time = "2025-10-08T18:43:39.665Z" } wheels = [ @@ -2764,12 +2765,12 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "google-api-core", version = "2.30.3", source = { registry = "https://pypi.org/simple" } }, - { name = "google-auth" }, - { name = "google-cloud-core" }, - { name = "google-crc32c" }, - { name = "google-resumable-media" }, - { name = "requests" }, + { name = "google-api-core", version = "2.30.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "google-auth", marker = "python_full_version < '3.14'" }, + { name = "google-cloud-core", marker = "python_full_version < '3.14'" }, + { name = "google-crc32c", marker = "python_full_version < '3.14'" }, + { name = "google-resumable-media", marker = "python_full_version < '3.14'" }, + { name = "requests", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4c/47/205eb8e9a1739b5345843e5a425775cbdc472cc38e7eda082ba5b8d02450/google_cloud_storage-3.10.1.tar.gz", hash = "sha256:97db9aa4460727982040edd2bd13ff3d5e2260b5331ad22895802da1fc2a5286", size = 17309950, upload-time = "2026-03-23T09:35:23.409Z" } wheels = [ @@ -4231,13 +4232,13 @@ name = "langchain-classic" version = "1.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "langchain-core" }, - { name = "langchain-text-splitters" }, - { name = "langsmith" }, - { name = "pydantic" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "sqlalchemy" }, + { name = "langchain-core", marker = "python_full_version >= '3.11'" }, + { name = "langchain-text-splitters", marker = "python_full_version >= '3.11'" }, + { name = "langsmith", marker = "python_full_version >= '3.11'" }, + { name = "pydantic", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "sqlalchemy", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9b/78/84b5065816f348c39fefa4316f209f0135e8410216340a953bec17d9e4e4/langchain_classic-1.0.7.tar.gz", hash = "sha256:debbec8065e69b95108d2652e8d5c44f4516e19aa8d716c02ed2211c3aee099d", size = 10554118, upload-time = "2026-05-07T15:46:56.8Z" } wheels = [ @@ -4252,18 +4253,18 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "aiohttp" }, - { name = "dataclasses-json" }, - { name = "httpx-sse" }, - { name = "langchain" }, - { name = "langchain-core" }, - { name = "langsmith" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, - { name = "pydantic-settings" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "sqlalchemy" }, - { name = "tenacity" }, + { name = "aiohttp", marker = "python_full_version < '3.11'" }, + { name = "dataclasses-json", marker = "python_full_version < '3.11'" }, + { name = "httpx-sse", marker = "python_full_version < '3.11'" }, + { name = "langchain", marker = "python_full_version < '3.11'" }, + { name = "langchain-core", marker = "python_full_version < '3.11'" }, + { name = "langsmith", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pydantic-settings", marker = "python_full_version < '3.11'" }, + { name = "pyyaml", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "sqlalchemy", marker = "python_full_version < '3.11'" }, + { name = "tenacity", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/83/49/2ff5354273809e9811392bc24bcffda545a196070666aef27bc6aacf1c21/langchain_community-0.3.31.tar.gz", hash = "sha256:250e4c1041539130f6d6ac6f9386cb018354eafccd917b01a4cff1950b80fd81", size = 33241237, upload-time = "2025-10-07T20:17:57.857Z" } wheels = [ @@ -4281,19 +4282,19 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "aiohttp" }, - { name = "dataclasses-json" }, - { name = "httpx-sse" }, - { name = "langchain-classic" }, - { name = "langchain-core" }, - { name = "langsmith" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "aiohttp", marker = "python_full_version >= '3.11'" }, + { name = "dataclasses-json", marker = "python_full_version >= '3.11'" }, + { name = "httpx-sse", marker = "python_full_version >= '3.11'" }, + { name = "langchain-classic", marker = "python_full_version >= '3.11'" }, + { name = "langchain-core", marker = "python_full_version >= '3.11'" }, + { name = "langsmith", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "pydantic-settings" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "sqlalchemy" }, - { name = "tenacity" }, + { name = "pydantic-settings", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "sqlalchemy", marker = "python_full_version >= '3.11'" }, + { name = "tenacity", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/53/97/a03585d42b9bdb6fbd935282d6e3348b10322a24e6ce12d0c99eb461d9af/langchain_community-0.4.1.tar.gz", hash = "sha256:f3b211832728ee89f169ddce8579b80a085222ddb4f4ed445a46e977d17b1e85", size = 33241144, upload-time = "2025-10-27T15:20:32.504Z" } wheels = [ @@ -4302,9 +4303,10 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.4.8" +version = "1.6.12" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "httpx" }, { name = "jsonpatch" }, { name = "langchain-protocol" }, { name = "langsmith" }, @@ -4315,23 +4317,24 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/e3/bea6d0080acf183332f24dcd74c208aee5857cf8f783c3fb0bd86027d8fb/langchain_core-1.4.8.tar.gz", hash = "sha256:5bf1f8411077c904182ad8f975943d36adcbf579c4e017b3a118b719229ebf9a", size = 957974, upload-time = "2026-06-18T19:39:23.636Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/c2/9779d64322d4e270f6d5392bc86b30f735581a87b5eefd6ad15a0828a169/langchain_core-1.6.12.tar.gz", hash = "sha256:dc6b2a965c351b4cc94df1c80220bcf3c5472e14437d2186c8290ea22f4d0219", size = 1015639, upload-time = "2026-10-16T19:28:31.055Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/d6/bdf6f0481cc57ef300d6b1eb48cf1400c0409be715d6eb3cabadd1142a09/langchain_core-1.4.8-py3-none-any.whl", hash = "sha256:d84c28b05e3ba8d4271d0827aad5b592ccdaaf986e76768c23503f0a2045e8aa", size = 557416, upload-time = "2026-06-18T19:39:21.902Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7b/49de1d4d6b9ff62e7c40a6fceb6aeecd8c190ac5f49dd7a99948c6650c16/langchain_core-1.6.12-py3-none-any.whl", hash = "sha256:39b07aed976baac14c47b7a21e3c7d4073362bccf06cf8799c585dcc9548547d", size = 573838, upload-time = "2026-10-16T19:28:29.466Z" }, ] [[package]] name = "langchain-openai" -version = "1.1.14" +version = "1.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "certifi" }, { name = "langchain-core" }, { name = "openai" }, { name = "tiktoken" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/f5/b1a56f703fb90952b07ff9fb5507123a39df1267d62a7f2bb821c5dbb628/langchain_openai-1.1.14.tar.gz", hash = "sha256:71b4262932fabe506ce79c175dbc956cc48f24d81e20b27662df493147750643", size = 1115195, upload-time = "2026-04-16T14:55:24.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/ca/b64799a9f2ee688aa6c238f5230b20d150b19cc5892bdcb6faf2b59866f1/langchain_openai-1.7.2.tar.gz", hash = "sha256:e1fcbc7a96f7c8c94679d61639a979bba88f27889c0a68dfa225b8057345328d", size = 3405949, upload-time = "2026-10-14T13:50:59.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/fa/8c33befbc0cf81b21371cc1dab4e7bf94a80b8116194f263a5021ec02529/langchain_openai-1.1.14-py3-none-any.whl", hash = "sha256:cb525d2011f9813fc15a7dcfd4bca5b87badcbcb2c113a7fbe45d1b8a1bbb69c", size = 88705, upload-time = "2026-04-16T14:55:23.159Z" }, + { url = "https://files.pythonhosted.org/packages/39/5d/8c4888a21087e4cd1c32965613a1a17cfc8e90e41ba5205c3dad302f3410/langchain_openai-1.7.2-py3-none-any.whl", hash = "sha256:9be7980edf355ca2dd595b795a5125836ccf04515ef9377228913246156e59f5", size = 146367, upload-time = "2026-10-14T13:50:58.697Z" }, ] [[package]] @@ -4351,7 +4354,7 @@ name = "langchain-text-splitters" version = "1.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "langchain-core" }, + { name = "langchain-core", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/26/9f/6c545900fefb7b00ddfa3f16b80d61338a0ec68c31c5451eeeab99082760/langchain_text_splitters-1.1.2.tar.gz", hash = "sha256:782a723db0a4746ac91e251c7c1d57fd23636e4f38ed733074e28d7a86f41627", size = 293580, upload-time = "2026-04-16T14:20:39.162Z" } wheels = [ @@ -4439,23 +4442,27 @@ wheels = [ [[package]] name = "langsmith" -version = "0.8.18" +version = "0.14.11" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "httpx" }, + { name = "anyio" }, + { name = "distro" }, + { name = "httpx2" }, { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, { name = "packaging" }, { name = "pydantic" }, { name = "requests" }, { name = "requests-toolbelt" }, + { name = "sniffio" }, + { name = "typing-extensions" }, { name = "uuid-utils" }, { name = "websockets" }, { name = "xxhash" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/d9/a6681aa9847bbbc5ec21abe20a5e233b94e5edcfe39624db607ac7e8ccb4/langsmith-0.8.18.tar.gz", hash = "sha256:32dde9c0e67e053e0fb738921fc8ced768af7b8fa83d7a0e3fd63597cf8776dd", size = 4526988, upload-time = "2026-06-19T13:12:17.123Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/c3/a9a98d7adf573fdd5ea5c92cadbc26d7c597ab36f4dc31c093eb19507808/langsmith-0.14.11.tar.gz", hash = "sha256:d9e7ffe587e12ce51573cab654d02e84feb924d3b1f582274c4b1412ebeabf4c", size = 5020659, upload-time = "2026-10-16T07:16:56.865Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/70/0e0cc80a3b064c8d6c8d697c3125ed86e39d5a7393ec6dc8b07cb1cf13c4/langsmith-0.8.18-py3-none-any.whl", hash = "sha256:3940183349993faef48e6c7d08e4822ee9cefd906b362d0e3c2d650314d2f282", size = 508108, upload-time = "2026-06-19T13:12:15.348Z" }, + { url = "https://files.pythonhosted.org/packages/e2/1d/b683b3d8777e625e021efeaaf72dc04f709be59e6a5f2d65725cebaedcfb/langsmith-0.14.11-py3-none-any.whl", hash = "sha256:9ecca7633df228ef2c44563f7187e426b8a79e7c5c2351c5dd8c9074c61512e7", size = 869018, upload-time = "2026-10-16T07:16:54.634Z" }, ] [[package]] @@ -4996,7 +5003,7 @@ ci = [ { name = "google-generativeai", specifier = "==0.8.6" }, { name = "jsonlines", specifier = "==4.0.0" }, { name = "langchain", specifier = "==1.3.9" }, - { name = "langchain-openai", specifier = "==1.1.14" }, + { name = "langchain-openai", specifier = "==1.7.2" }, { name = "langgraph", specifier = ">=1.2.4,<1.3.0" }, { name = "langgraph-prebuilt", specifier = ">=1.1.0,<1.3.0" }, { name = "logfire", specifier = "==4.6.0" }, @@ -5087,10 +5094,10 @@ name = "litellm-admin-mcp" version = "0.1.0" source = { url = "https://github.com/BerriAI/liteadmin-mcp/archive/d35ec9c19c117d4c50cc1eccf6ce1296aac25a1a.tar.gz" } dependencies = [ - { name = "httpx2" }, - { name = "jsonschema" }, - { name = "mcp" }, - { name = "uvicorn" }, + { name = "httpx2", marker = "python_full_version >= '3.12'" }, + { name = "jsonschema", marker = "python_full_version >= '3.12'" }, + { name = "mcp", marker = "python_full_version >= '3.12'" }, + { name = "uvicorn", marker = "python_full_version >= '3.12'" }, ] sdist = { hash = "sha256:86b930f6706fb2da10d53d1798ee7fd14e44fb0afdef0da122cf0e6bb88fa2c3" } @@ -5193,16 +5200,16 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "aiohttp" }, - { name = "chevron" }, - { name = "jsonpickle" }, - { name = "langchain-community", version = "0.3.31", source = { registry = "https://pypi.org/simple" } }, - { name = "packaging" }, - { name = "pydantic" }, - { name = "pyhumps" }, - { name = "requests" }, - { name = "setuptools" }, - { name = "tenacity" }, + { name = "aiohttp", marker = "python_full_version < '3.11'" }, + { name = "chevron", marker = "python_full_version < '3.11'" }, + { name = "jsonpickle", marker = "python_full_version < '3.11'" }, + { name = "langchain-community", version = "0.3.31", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pydantic", marker = "python_full_version < '3.11'" }, + { name = "pyhumps", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "setuptools", marker = "python_full_version < '3.11'" }, + { name = "tenacity", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4a/6f/9ca1acf766848aaf5f0ac4140c34c91ad0dbfad2654359699644be3352c9/lunary-1.4.36.tar.gz", hash = "sha256:53f002f385c83d9c0e6368e7999923acffbde987f53c5205c2c249c38ee2d75c", size = 20253, upload-time = "2026-02-09T20:49:30.56Z" } wheels = [ @@ -5220,16 +5227,16 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "aiohttp" }, - { name = "chevron" }, - { name = "jsonpickle" }, - { name = "langchain-community", version = "0.4.1", source = { registry = "https://pypi.org/simple" } }, - { name = "packaging" }, - { name = "pydantic" }, - { name = "pyhumps" }, - { name = "requests" }, - { name = "setuptools" }, - { name = "tenacity" }, + { name = "aiohttp", marker = "python_full_version >= '3.11'" }, + { name = "chevron", marker = "python_full_version >= '3.11'" }, + { name = "jsonpickle", marker = "python_full_version >= '3.11'" }, + { name = "langchain-community", version = "0.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pydantic", marker = "python_full_version >= '3.11'" }, + { name = "pyhumps", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "setuptools", marker = "python_full_version >= '3.11'" }, + { name = "tenacity", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/37/ef/1acbc6957585cc0110e648d787663871717ced3df27fcd3cb5e18fa418f3/lunary-1.4.37.tar.gz", hash = "sha256:1781091e9dceffcc28ebc4be7e085c9fec4102d98d7ca945ed0021e9ce03c36f", size = 20248, upload-time = "2026-02-12T08:15:02.091Z" } wheels = [ @@ -9090,10 +9097,10 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "joblib" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } }, - { name = "threadpoolctl" }, + { name = "joblib", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } wheels = [ @@ -9140,11 +9147,11 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "joblib" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "joblib", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" } }, - { name = "threadpoolctl" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } wheels = [ @@ -9194,7 +9201,7 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } wheels = [ @@ -9256,7 +9263,7 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } @@ -9341,20 +9348,20 @@ name = "semantic-router" version = "0.1.15" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aiohttp" }, - { name = "aurelio-sdk" }, - { name = "colorama" }, - { name = "colorlog" }, - { name = "litellm" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or python_full_version >= '3.14'" }, + { name = "aiohttp", marker = "python_full_version < '3.14'" }, + { name = "aurelio-sdk", marker = "python_full_version < '3.14'" }, + { name = "colorama", marker = "python_full_version < '3.14'" }, + { name = "colorlog", marker = "python_full_version < '3.14'" }, + { name = "litellm", marker = "python_full_version < '3.14'" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and python_full_version < '3.14'" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "pyyaml" }, - { name = "regex" }, - { name = "tiktoken" }, - { name = "tornado" }, - { name = "urllib3" }, + { name = "openai", marker = "python_full_version < '3.14'" }, + { name = "pydantic", marker = "python_full_version < '3.14'" }, + { name = "pyyaml", marker = "python_full_version < '3.14'" }, + { name = "regex", marker = "python_full_version < '3.14'" }, + { name = "tiktoken", marker = "python_full_version < '3.14'" }, + { name = "tornado", marker = "python_full_version < '3.14'" }, + { name = "urllib3", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/a9/1a689e916e8b280f1fd8fb335cc059be626a22fe4533baa045d32fcd6de5/semantic_router-0.1.15.tar.gz", hash = "sha256:328256ddc3c2b713101ec69561d6585aecbf1198ea3461e1486289d8c3a35288", size = 95605, upload-time = "2026-05-23T12:58:15.444Z" } wheels = [ @@ -9428,9 +9435,9 @@ name = "smithy-aws-core" version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "aws-sdk-signers" }, - { name = "smithy-core" }, - { name = "smithy-http" }, + { name = "aws-sdk-signers", marker = "python_full_version >= '3.12'" }, + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, + { name = "smithy-http", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/d3/501c0023548173416109ac42298ca33b708469dc922005770811a597949f/smithy_aws_core-0.11.0.tar.gz", hash = "sha256:29ee89976a520a87e3db557e03e115fdc21a0a60b81161e95174395a1b064da1", size = 38791, upload-time = "2026-08-24T21:16:59.631Z" } wheels = [ @@ -9439,10 +9446,10 @@ wheels = [ [package.optional-dependencies] eventstream = [ - { name = "smithy-aws-event-stream" }, + { name = "smithy-aws-event-stream", marker = "python_full_version >= '3.12'" }, ] json = [ - { name = "smithy-json" }, + { name = "smithy-json", marker = "python_full_version >= '3.12'" }, ] [[package]] @@ -9450,7 +9457,7 @@ name = "smithy-aws-event-stream" version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "smithy-core" }, + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/0e/6efb3a4ed92c0f1ada6de060ac92e7115a1e34d0ab1fb99a6056734a88ea/smithy_aws_event_stream-0.3.0.tar.gz", hash = "sha256:a0e227367a973144e205a075d0a424f95c92f26656a1018d08900da2ae547c49", size = 12818, upload-time = "2026-05-05T18:04:14.317Z" } wheels = [ @@ -9471,7 +9478,7 @@ name = "smithy-http" version = "0.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "smithy-core" }, + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/78/b5f3113d6c8f0bc1f9777a7f5ca84b892d29efac05850e14f7d4f7e645b5/smithy_http-0.5.0.tar.gz", hash = "sha256:bb4a19672f7c7eeb872a308f777eb505281a5bafb1ee3d1ea9c760c06c352510", size = 31122, upload-time = "2026-08-24T21:16:56.488Z" } wheels = [ @@ -9480,11 +9487,11 @@ wheels = [ [package.optional-dependencies] aiohttp = [ - { name = "aiohttp" }, - { name = "yarl" }, + { name = "aiohttp", marker = "python_full_version >= '3.12'" }, + { name = "yarl", marker = "python_full_version >= '3.12'" }, ] awscrt = [ - { name = "awscrt" }, + { name = "awscrt", marker = "python_full_version >= '3.12'" }, ] [[package]] @@ -9492,8 +9499,8 @@ name = "smithy-json" version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ijson" }, - { name = "smithy-core" }, + { name = "ijson", marker = "python_full_version >= '3.12'" }, + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/04164eefb3da7479f52f6535b4b39cc8384c292cb2bb74279f2acc4f4b4d/smithy_json-0.3.0.tar.gz", hash = "sha256:c81c7034587e01bc64767cbbecb05a7d65ca9070612fd94e8a03e80540290a22", size = 7956, upload-time = "2026-08-20T17:55:32.177Z" } wheels = [ @@ -9571,23 +9578,23 @@ resolution-markers = [ "python_full_version < '3.11'", ] dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" } }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, - { name = "tomli" }, + { name = "alabaster", marker = "python_full_version < '3.11'" }, + { name = "babel", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "imagesize", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } wheels = [ @@ -9602,23 +9609,23 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "roman-numerals" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, + { name = "alabaster", marker = "python_full_version == '3.11.*'" }, + { name = "babel", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "imagesize", marker = "python_full_version == '3.11.*'" }, + { name = "jinja2", marker = "python_full_version == '3.11.*'" }, + { name = "packaging", marker = "python_full_version == '3.11.*'" }, + { name = "pygments", marker = "python_full_version == '3.11.*'" }, + { name = "requests", marker = "python_full_version == '3.11.*'" }, + { name = "roman-numerals", marker = "python_full_version == '3.11.*'" }, + { name = "snowballstemmer", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } wheels = [ @@ -9635,23 +9642,23 @@ resolution-markers = [ "python_full_version == '3.12.*'", ] dependencies = [ - { name = "alabaster" }, - { name = "babel" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } }, - { name = "imagesize" }, - { name = "jinja2" }, - { name = "packaging" }, - { name = "pygments" }, - { name = "requests" }, - { name = "roman-numerals" }, - { name = "snowballstemmer" }, - { name = "sphinxcontrib-applehelp" }, - { name = "sphinxcontrib-devhelp" }, - { name = "sphinxcontrib-htmlhelp" }, - { name = "sphinxcontrib-jsmath" }, - { name = "sphinxcontrib-qthelp" }, - { name = "sphinxcontrib-serializinghtml" }, + { name = "alabaster", marker = "python_full_version >= '3.12'" }, + { name = "babel", marker = "python_full_version >= '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "imagesize", marker = "python_full_version >= '3.12'" }, + { name = "jinja2", marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } wheels = [ @@ -9799,8 +9806,8 @@ name = "standard-aifc" version = "3.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "audioop-lts" }, - { name = "standard-chunk" }, + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, + { name = "standard-chunk", marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/53/6050dc3dde1671eb3db592c13b55a8005e5040131f7509cef0215212cb84/standard_aifc-3.13.0.tar.gz", hash = "sha256:64e249c7cb4b3daf2fdba4e95721f811bde8bdfc43ad9f936589b7bb2fae2e43", size = 15240, upload-time = "2024-10-30T16:01:31.772Z" } wheels = [ @@ -9821,7 +9828,7 @@ name = "standard-sunau" version = "3.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "audioop-lts" }, + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/e3/ce8d38cb2d70e05ffeddc28bb09bad77cfef979eb0a299c9117f7ed4e6a9/standard_sunau-3.13.0.tar.gz", hash = "sha256:b319a1ac95a09a2378a8442f403c66f4fd4b36616d6df6ae82b8e536ee790908", size = 9368, upload-time = "2024-10-30T16:01:41.626Z" } wheels = [ @@ -9855,8 +9862,8 @@ name = "taskgroup" version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "exceptiongroup" }, - { name = "typing-extensions" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f0/8d/e218e0160cc1b692e6e0e5ba34e8865dbb171efeb5fc9a704544b3020605/taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d", size = 11504, upload-time = "2025-01-03T09:24:13.761Z" } wheels = [ From 356087654a8c23b90ae91bda89e3c3e69ba7f766 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:26:24 -0700 Subject: [PATCH 02/10] build(deps): lock openai 3.35.1 so the Docker image runs OpenAI SDK 3 --- uv.lock | 202 +++++++++++++++++++++++++++----------------------------- 1 file changed, 98 insertions(+), 104 deletions(-) diff --git a/uv.lock b/uv.lock index 301225a8277..5143f7168f0 100644 --- a/uv.lock +++ b/uv.lock @@ -10,7 +10,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-10-18T01:19:57.352406047Z" +exclude-newer = "2026-10-18T01:25:51.279086783Z" exclude-newer-span = "P3D" [manifest] @@ -3859,105 +3859,101 @@ wheels = [ [[package]] name = "jiter" -version = "0.14.0" +version = "0.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/c1/0cddc6eb17d4c53a99840953f95dd3accdc5cfc7a337b0e9b26476276be9/jiter-0.14.0.tar.gz", hash = "sha256:e8a39e66dac7153cf3f964a12aad515afa8d74938ec5cc0018adcdae5367c79e", size = 165725, upload-time = "2026-04-10T14:28:42.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/1f/8176d92e001f86505424b41664032ae26a882bc9ca41a32c803f373f9195/jiter-0.17.0.tar.gz", hash = "sha256:03e432f226a453851079fb84cd17c6da9991eab723e28d716f14ae3d906e0c12", size = 229037, upload-time = "2026-09-12T15:14:14.253Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/2e/a9959997739c403378d0a4a3a1c4ed80b60aeace216c4d37b303a9fc60a4/jiter-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:02f36a5c700f105ac04a6556fe664a59037a2c200db3b7e88784fac2ddf02531", size = 316927, upload-time = "2026-04-10T14:25:40.753Z" }, - { url = "https://files.pythonhosted.org/packages/27/72/b6de8a531e0adbadd839bec301165feb1fccf00e9ff55073ba2dd20f0043/jiter-0.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41eab6c09ceffb6f0fe25e214b3068146edb1eda3649ca2aee2a061029c7ba2e", size = 321181, upload-time = "2026-04-10T14:25:42.621Z" }, - { url = "https://files.pythonhosted.org/packages/db/d8/2040b9efa13c917f855c40890ae4119fe02c25b7c7677d5b4fa820a851fc/jiter-0.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cf4d4c109641f9cfaf4a7b6aebd51654e405cd00fa9ebbf87163b8b97b325aa", size = 347387, upload-time = "2026-04-10T14:25:44.212Z" }, - { url = "https://files.pythonhosted.org/packages/49/62/655c0ad5ce6a8e90f9068c175b8a236877d753e460762b3183c136db1c5b/jiter-0.14.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80c7b41a628e6be2213ad0ece763c5f88aa5ee003fa394d58acaaee1f4b8342", size = 373083, upload-time = "2026-04-10T14:25:45.55Z" }, - { url = "https://files.pythonhosted.org/packages/f1/66/549c40fa068f08710b7570869c306a051eb67a29758bd64f4114f730554c/jiter-0.14.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb3dbf7cc0d4dbe73cce307ebe7eefa7f73a7d3d854dd119ea0c243f03e40927", size = 463639, upload-time = "2026-04-10T14:25:47.452Z" }, - { url = "https://files.pythonhosted.org/packages/25/2f/97a32a05fed14ed58a18e181fdfb619e05163f3726b54ee6080ec0539c09/jiter-0.14.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7054adcdeb06b46efd17b5734f75817a44a2d06d3748e36c3a023a1bb52af9ec", size = 380735, upload-time = "2026-04-10T14:25:49.305Z" }, - { url = "https://files.pythonhosted.org/packages/2a/3b/4347e1d6c2a973d653bbb7a2d671a2d2426e54b52ba735b8ff0d0a29b75c/jiter-0.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d597cd1bf6790376f3fffc7c708766e57301d99a19314824ea0ccc9c3c70e1e2", size = 358632, upload-time = "2026-04-10T14:25:50.931Z" }, - { url = "https://files.pythonhosted.org/packages/ef/24/ca452fbf2ea33548ed30ce68a39a50442d3f7c9bf0704a7af958a930c057/jiter-0.14.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:df63a14878da754427926281626fd3ee249424a186e25a274e78176d42945264", size = 359969, upload-time = "2026-04-10T14:25:52.381Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a3/94470a0d199287caabeb4da2bb2ae5f6d17f3cf05dfc975d7cb064d58e0f/jiter-0.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ea73187627bcc5810e085df715e8a99da8bdfd96a7eb36b4b4df700ba6d4c9c", size = 397529, upload-time = "2026-04-10T14:25:53.801Z" }, - { url = "https://files.pythonhosted.org/packages/cf/71/6768edc09d7c45c39f093feb3de105fa718a3e982b5208b8a2ed6382b44b/jiter-0.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9f541eaf7bb8382367a1a23d6fc3d6aad57f8dd8c18c3c17f838bee20f217220", size = 522342, upload-time = "2026-04-10T14:25:55.396Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6b/5c2e17559a0f4e96e934479f7137df46c939e983fa05244e674815befb73/jiter-0.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:107465250de4fce00fdb47166bcd51df8e634e049541174fe3c71848e44f52ce", size = 556784, upload-time = "2026-04-10T14:25:56.927Z" }, - { url = "https://files.pythonhosted.org/packages/b1/83/c25f3556a60fc74d11199100f1b6cc0c006b815c8494dea8ca16fe398732/jiter-0.14.0-cp310-cp310-win32.whl", hash = "sha256:ffb2a08a406465bb076b7cc1df41d833106d3cf7905076cc73f0cb90078c7d10", size = 208439, upload-time = "2026-04-10T14:25:58.796Z" }, - { url = "https://files.pythonhosted.org/packages/2e/99/781a1b413f0989b7f2ea203b094b331685f1a35e52e0a45e5d000ecaab27/jiter-0.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb8b682d10cb0cce7ff4c1af7244af7022c9b01ae16d46c357bdd0df13afb25d", size = 204558, upload-time = "2026-04-10T14:26:00.208Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/198ae537fccb7080a0ed655eb56abf64a92f79489dfbf79f40fa34225bcd/jiter-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7e791e247b8044512e070bd1f3633dc08350d32776d2d6e7473309d0edf256a2", size = 316896, upload-time = "2026-04-10T14:26:01.986Z" }, - { url = "https://files.pythonhosted.org/packages/cf/34/da67cff3fce964a36d03c3e365fb0f8726ade2a6cfd4d3c70107e216ead6/jiter-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71527ce13fd5a0c4e40ad37331f8c547177dbb2dd0a93e5278b6a5eecf748804", size = 321085, upload-time = "2026-04-10T14:26:03.364Z" }, - { url = "https://files.pythonhosted.org/packages/ed/36/4c72e67180d4e71a4f5dcf7886d0840e83c49ab11788172177a77570326e/jiter-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02c4a7ab56f746014874f2c525584c0daca1dec37f66fd707ecef3b7e5c2228c", size = 347393, upload-time = "2026-04-10T14:26:05.314Z" }, - { url = "https://files.pythonhosted.org/packages/bc/db/9b39e09ceafa9878235c0fc29e3e3f9b12a4c6a98ea3085b998cadf3accc/jiter-0.14.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:376e9dafff914253bb9d46cdc5f7965607fbe7feb0a491c34e35f92b2770702e", size = 372937, upload-time = "2026-04-10T14:26:06.884Z" }, - { url = "https://files.pythonhosted.org/packages/b0/96/0dcba1d7a82c1b720774b48ef239376addbaf30df24c34742ac4a57b67b2/jiter-0.14.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23ad2a7a9da1935575c820428dd8d2490ce4d23189691ce33da1fc0a58e14e1c", size = 463646, upload-time = "2026-04-10T14:26:08.345Z" }, - { url = "https://files.pythonhosted.org/packages/f1/e3/f61b71543e746e6b8b805e7755814fc242715c16f1dba58e1cbccb8032c2/jiter-0.14.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54b3ddf5786bc7732d293bba3411ac637ecfa200a39983166d1df86a59a43c9f", size = 380225, upload-time = "2026-04-10T14:26:10.161Z" }, - { url = "https://files.pythonhosted.org/packages/ad/5e/0ddeb7096aca099114abe36c4921016e8d251e6f35f5890240b31f1f60ae/jiter-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c001d5a646c2a50dc055dd526dad5d5245969e8234d2b1131d0451e81f3a373", size = 358682, upload-time = "2026-04-10T14:26:11.574Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/fe0c46cd7fda9cad8f1ff9ad217dc61f1e4280b21052ec6dfe88c1446ef2/jiter-0.14.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:834bb5bdabca2e91592a03d373838a8d0a1b8bbde7077ae6913fd2fc51812d00", size = 359973, upload-time = "2026-04-10T14:26:13.316Z" }, - { url = "https://files.pythonhosted.org/packages/ac/21/f5317f91729b501019184771c80d60abd89907009e7bfa6c7e348c5bdd44/jiter-0.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4e9178be60e229b1b2b0710f61b9e24d1f4f8556985a83ff4c4f95920eea7314", size = 397568, upload-time = "2026-04-10T14:26:15.212Z" }, - { url = "https://files.pythonhosted.org/packages/e9/05/79d8f33fb2bf168db0df5c9cd16fe440a8ada57e929d3677b22712c2568f/jiter-0.14.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7e4ccff04ec03614e62c613e976a3a5860dc9714ce8266f44328bdc8b1cab2c", size = 522535, upload-time = "2026-04-10T14:26:16.956Z" }, - { url = "https://files.pythonhosted.org/packages/5c/00/d1e3ff3d2a465e67f08507d74bafb2dcd29eba91dc939820e39e8dea38b8/jiter-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69539d936fb5d55caf6ecd33e2e884de083ff0ea28579780d56c4403094bb8d9", size = 556709, upload-time = "2026-04-10T14:26:18.5Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/bbb2189f62ace8d95e869aa4c84c9946616f301e2d02895a6f20dcc3bba3/jiter-0.14.0-cp311-cp311-win32.whl", hash = "sha256:4927d09b3e572787cc5e0a5318601448e1ab9391bcef95677f5840c2d00eaa6d", size = 208660, upload-time = "2026-04-10T14:26:20.511Z" }, - { url = "https://files.pythonhosted.org/packages/b8/86/c500b53dcbf08575f5963e536ebd757a1f7c568272ba5d180b212c9a87fb/jiter-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:42d6ed359ac49eb922fdd565f209c57340aa06d589c84c8413e42a0f9ae1b842", size = 204659, upload-time = "2026-04-10T14:26:22.152Z" }, - { url = "https://files.pythonhosted.org/packages/75/4a/a676249049d42cb29bef82233e4fe0524d414cbe3606c7a4b311193c2f77/jiter-0.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:6dd689f5f4a5a33747b28686e051095beb214fe28cfda5e9fe58a295a788f593", size = 194772, upload-time = "2026-04-10T14:26:23.458Z" }, - { url = "https://files.pythonhosted.org/packages/5a/68/7390a418f10897da93b158f2d5a8bd0bcd73a0f9ec3bb36917085bb759ef/jiter-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb2ce3a7bc331256dfb14cefc34832366bb28a9aca81deaf43bbf2a5659e607", size = 316295, upload-time = "2026-04-10T14:26:24.887Z" }, - { url = "https://files.pythonhosted.org/packages/60/a0/5854ac00ff63551c52c6c89534ec6aba4b93474e7924d64e860b1c94165b/jiter-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5252a7ca23785cef5d02d4ece6077a1b556a410c591b379f82091c3001e14844", size = 315898, upload-time = "2026-04-10T14:26:26.601Z" }, - { url = "https://files.pythonhosted.org/packages/41/a1/4f44832650a16b18e8391f1bf1d6ca4909bc738351826bcc198bba4357f4/jiter-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c409578cbd77c338975670ada777add4efd53379667edf0aceea730cabede6fb", size = 343730, upload-time = "2026-04-10T14:26:28.326Z" }, - { url = "https://files.pythonhosted.org/packages/48/64/a329e9d469f86307203594b1707e11ae51c3348d03bfd514a5f997870012/jiter-0.14.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ede4331a1899d604463369c730dbb961ffdc5312bc7f16c41c2896415b1304a", size = 370102, upload-time = "2026-04-10T14:26:30.089Z" }, - { url = "https://files.pythonhosted.org/packages/94/c1/5e3dfc59635aa4d4c7bd20a820ac1d09b8ed851568356802cf1c08edb3cf/jiter-0.14.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92cd8b6025981a041f5310430310b55b25ca593972c16407af8837d3d7d2ca01", size = 461335, upload-time = "2026-04-10T14:26:31.911Z" }, - { url = "https://files.pythonhosted.org/packages/e3/1b/dd157009dbc058f7b00108f545ccb72a2d56461395c4fc7b9cfdccb00af4/jiter-0.14.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:351bf6eda4e3a7ceb876377840c702e9a3e4ecc4624dbfb2d6463c67ae52637d", size = 378536, upload-time = "2026-04-10T14:26:33.595Z" }, - { url = "https://files.pythonhosted.org/packages/91/78/256013667b7c10b8834f8e6e54cd3e562d4c6e34227a1596addccc05e38c/jiter-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dcfbeb93d9ecd9ca128bbf8910120367777973fa193fb9a39c31237d8df165", size = 353859, upload-time = "2026-04-10T14:26:35.098Z" }, - { url = "https://files.pythonhosted.org/packages/de/d9/137d65ade9093a409fe80955ce60b12bb753722c986467aeda47faf450ad/jiter-0.14.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ae039aaef8de3f8157ecc1fdd4d85043ac4f57538c245a0afaecb8321ec951c3", size = 357626, upload-time = "2026-04-10T14:26:36.685Z" }, - { url = "https://files.pythonhosted.org/packages/2e/48/76750835b87029342727c1a268bea8878ab988caf81ee4e7b880900eeb5a/jiter-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7d9d51eb96c82a9652933bd769fe6de66877d6eb2b2440e281f2938c51b5643e", size = 393172, upload-time = "2026-04-10T14:26:38.097Z" }, - { url = "https://files.pythonhosted.org/packages/a6/60/456c4e81d5c8045279aefe60e9e483be08793828800a4e64add8fdde7f2a/jiter-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d824ca4148b705970bf4e120924a212fdfca9859a73e42bd7889a63a4ea6bb98", size = 520300, upload-time = "2026-04-10T14:26:39.532Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9f/2020e0984c235f678dced38fe4eec3058cf528e6af36ebf969b410305941/jiter-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff3a6465b3a0f54b1a430f45c3c0ba7d61ceb45cbc3e33f9e1a7f638d690baf3", size = 553059, upload-time = "2026-04-10T14:26:40.991Z" }, - { url = "https://files.pythonhosted.org/packages/ef/32/e2d298e1a22a4bbe6062136d1c7192db7dba003a6975e51d9a9eecabc4c2/jiter-0.14.0-cp312-cp312-win32.whl", hash = "sha256:5dec7c0a3e98d2a3f8a2e67382d0d7c3ac60c69103a4b271da889b4e8bb1e129", size = 206030, upload-time = "2026-04-10T14:26:42.517Z" }, - { url = "https://files.pythonhosted.org/packages/36/ac/96369141b3d8a4a8e4590e983085efe1c436f35c0cda940dd76d942e3e40/jiter-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:fc7e37b4b8bc7e80a63ad6cfa5fc11fab27dbfea4cc4ae644b1ab3f273dc348f", size = 201603, upload-time = "2026-04-10T14:26:44.328Z" }, - { url = "https://files.pythonhosted.org/packages/01/c3/75d847f264647017d7e3052bbcc8b1e24b95fa139c320c5f5066fa7a0bdd/jiter-0.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:ee4a72f12847ef29b072aee9ad5474041ab2924106bdca9fcf5d7d965853e057", size = 191525, upload-time = "2026-04-10T14:26:46Z" }, - { url = "https://files.pythonhosted.org/packages/97/2a/09f70020898507a89279659a1afe3364d57fc1b2c89949081975d135f6f5/jiter-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:af72f204cf4d44258e5b4c1745130ac45ddab0e71a06333b01de660ab4187a94", size = 315502, upload-time = "2026-04-10T14:26:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/d6/be/080c96a45cd74f9fce5db4fd68510b88087fb37ffe2541ff73c12db92535/jiter-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b77da71f6e819be5fbcec11a453fde5b1d0267ef6ed487e2a392fd8e14e4e3a", size = 314870, upload-time = "2026-04-10T14:26:49.149Z" }, - { url = "https://files.pythonhosted.org/packages/7d/5e/2d0fee155826a968a832cc32438de5e2a193292c8721ca70d0b53e58245b/jiter-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f4ea612fe8b84b8b04e51d0e78029ecf3466348e25973f953de6e6a59aa4c1", size = 343406, upload-time = "2026-04-10T14:26:50.762Z" }, - { url = "https://files.pythonhosted.org/packages/70/af/bf9ee0d3a4f8dc0d679fc1337f874fe60cdbf841ebbb304b374e1c9aaceb/jiter-0.14.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62fe2451f8fcc0240261e6a4df18ecbcd58327857e61e625b2393ea3b468aac9", size = 369415, upload-time = "2026-04-10T14:26:52.188Z" }, - { url = "https://files.pythonhosted.org/packages/0f/83/8e8561eadba31f4d3948a5b712fb0447ec71c3560b57a855449e7b8ddc98/jiter-0.14.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6112f26f5afc75bcb475787d29da3aa92f9d09c7858f632f4be6ffe607be82e9", size = 461456, upload-time = "2026-04-10T14:26:53.611Z" }, - { url = "https://files.pythonhosted.org/packages/f6/c9/c5299e826a5fe6108d172b344033f61c69b1bb979dd8d9ddd4278a160971/jiter-0.14.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:215a6cb8fb7dc702aa35d475cc00ddc7f970e5c0b1417fb4b4ac5d82fa2a29db", size = 378488, upload-time = "2026-04-10T14:26:55.211Z" }, - { url = "https://files.pythonhosted.org/packages/5d/37/c16d9d15c0a471b8644b1abe3c82668092a707d9bedcf076f24ff2e380cd/jiter-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ab96a30fb3cb2c7e0cd33f7616c8860da5f5674438988a54ac717caccdbaa", size = 353242, upload-time = "2026-04-10T14:26:56.705Z" }, - { url = "https://files.pythonhosted.org/packages/58/ea/8050cb0dc654e728e1bfacbc0c640772f2181af5dedd13ae70145743a439/jiter-0.14.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3a99c1387b1f2928f799a9de899193484d66206a50e98233b6b088a7f0c1edb2", size = 356823, upload-time = "2026-04-10T14:26:58.281Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/cf71506d270e5f84d97326bf220e47aed9b95e9a4a060758fb07772170ab/jiter-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab18d11074485438695f8d34a1b6da61db9754248f96d51341956607a8f39985", size = 392564, upload-time = "2026-04-10T14:27:00.018Z" }, - { url = "https://files.pythonhosted.org/packages/b0/cc/8c6c74a3efb5bd671bfd14f51e8a73375464ca914b1551bc3b40e26ac2c9/jiter-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:801028dcfc26ac0895e4964cbc0fd62c73be9fd4a7d7b1aaf6e5790033a719b7", size = 520322, upload-time = "2026-04-10T14:27:01.664Z" }, - { url = "https://files.pythonhosted.org/packages/41/24/68d7b883ec959884ddf00d019b2e0e82ba81b167e1253684fa90519ce33c/jiter-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ad425b087aafb4a1c7e1e98a279200743b9aaf30c3e0ba723aec93f061bd9bc8", size = 552619, upload-time = "2026-04-10T14:27:03.316Z" }, - { url = "https://files.pythonhosted.org/packages/b6/89/b1a0985223bbf3150ff9e8f46f98fc9360c1de94f48abe271bbe1b465682/jiter-0.14.0-cp313-cp313-win32.whl", hash = "sha256:882bcb9b334318e233950b8be366fe5f92c86b66a7e449e76975dfd6d776a01f", size = 205699, upload-time = "2026-04-10T14:27:04.662Z" }, - { url = "https://files.pythonhosted.org/packages/4c/19/3f339a5a7f14a11730e67f6be34f9d5105751d547b615ef593fa122a5ded/jiter-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:9b8c571a5dba09b98bd3462b5a53f27209a5cbbe85670391692ede71974e979f", size = 201323, upload-time = "2026-04-10T14:27:06.139Z" }, - { url = "https://files.pythonhosted.org/packages/50/56/752dd89c84be0e022a8ea3720bcfa0a8431db79a962578544812ce061739/jiter-0.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:34f19dcc35cb1abe7c369b3756babf8c7f04595c0807a848df8f26ef8298ef92", size = 191099, upload-time = "2026-04-10T14:27:07.564Z" }, - { url = "https://files.pythonhosted.org/packages/91/28/292916f354f25a1fe8cf2c918d1415c699a4a659ae00be0430e1c5d9ffea/jiter-0.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e89bcd7d426a75bb4952c696b267075790d854a07aad4c9894551a82c5b574ab", size = 320880, upload-time = "2026-04-10T14:27:09.326Z" }, - { url = "https://files.pythonhosted.org/packages/ad/c7/b002a7d8b8957ac3d469bd59c18ef4b1595a5216ae0de639a287b9816023/jiter-0.14.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b25beaa0d4447ea8c7ae0c18c688905d34840d7d0b937f2f7bdd52162c98a40", size = 346563, upload-time = "2026-04-10T14:27:11.287Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3b/f8d07580d8706021d255a6356b8fab13ee4c869412995550ce6ed4ddf97d/jiter-0.14.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:651a8758dd413c51e3b7f6557cdc6921faf70b14106f45f969f091f5cda990ea", size = 357928, upload-time = "2026-04-10T14:27:12.729Z" }, - { url = "https://files.pythonhosted.org/packages/47/5b/ac1a974da29e35507230383110ffec59998b290a8732585d04e19a9eb5ba/jiter-0.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e1a7eead856a5038a8d291f1447176ab0b525c77a279a058121b5fccee257f6f", size = 203519, upload-time = "2026-04-10T14:27:14.125Z" }, - { url = "https://files.pythonhosted.org/packages/96/6d/9fc8433d667d2454271378a79747d8c76c10b51b482b454e6190e511f244/jiter-0.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e692633a12cda97e352fdcd1c4acc971b1c28707e1e33aeef782b0cbf051975", size = 190113, upload-time = "2026-04-10T14:27:16.638Z" }, - { url = "https://files.pythonhosted.org/packages/4f/1e/354ed92461b165bd581f9ef5150971a572c873ec3b68a916d5aa91da3cc2/jiter-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6f396837fc7577871ca8c12edaf239ed9ccef3bbe39904ae9b8b63ce0a48b140", size = 315277, upload-time = "2026-04-10T14:27:18.109Z" }, - { url = "https://files.pythonhosted.org/packages/a6/95/8c7c7028aa8636ac21b7a55faef3e34215e6ed0cbf5ae58258427f621aa3/jiter-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a4d50ea3d8ba4176f79754333bd35f1bbcd28e91adc13eb9b7ca91bc52a6cef9", size = 315923, upload-time = "2026-04-10T14:27:19.603Z" }, - { url = "https://files.pythonhosted.org/packages/47/40/e2a852a44c4a089f2681a16611b7ce113224a80fd8504c46d78491b47220/jiter-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce17f8a050447d1b4153bda4fb7d26e6a9e74eb4f4a41913f30934c5075bf615", size = 344943, upload-time = "2026-04-10T14:27:21.262Z" }, - { url = "https://files.pythonhosted.org/packages/fc/1f/670f92adee1e9895eac41e8a4d623b6da68c4d46249d8b556b60b63f949e/jiter-0.14.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4f1c4b125e1652aefbc2e2c1617b60a160ab789d180e3d423c41439e5f32850", size = 369725, upload-time = "2026-04-10T14:27:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/01/2f/541c9ba567d05de1c4874a0f8f8c5e3fd78e2b874266623da9a775cf46e0/jiter-0.14.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be808176a6a3a14321d18c603f2d40741858a7c4fc982f83232842689fe86dd9", size = 461210, upload-time = "2026-04-10T14:27:24.315Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a9/c31cbec09627e0d5de7aeaec7690dba03e090caa808fefd8133137cf45bc/jiter-0.14.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26679d58ba816f88c3849306dd58cb863a90a1cf352cdd4ef67e30ccf8a77994", size = 380002, upload-time = "2026-04-10T14:27:26.155Z" }, - { url = "https://files.pythonhosted.org/packages/50/02/3c05c1666c41904a2f607475a73e7a4763d1cbde2d18229c4f85b22dc253/jiter-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80381f5a19af8fa9aef743f080e34f6b25ebd89656475f8cf0470ec6157052aa", size = 354678, upload-time = "2026-04-10T14:27:27.701Z" }, - { url = "https://files.pythonhosted.org/packages/7d/97/e15b33545c2b13518f560d695f974b9891b311641bdcf178d63177e8801e/jiter-0.14.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:004df5fdb8ecbd6d99f3227df18ba1a259254c4359736a2e6f036c944e02d7c5", size = 358920, upload-time = "2026-04-10T14:27:29.256Z" }, - { url = "https://files.pythonhosted.org/packages/ad/d2/8b1461def6b96ba44530df20d07ef7a1c7da22f3f9bf1727e2d611077bf1/jiter-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff5708f7ed0fa098f2b53446c6fa74c48469118e5cd7497b4f1cd569ab06928", size = 394512, upload-time = "2026-04-10T14:27:31.344Z" }, - { url = "https://files.pythonhosted.org/packages/e3/88/837566dd6ed6e452e8d3205355afd484ce44b2533edfa4ed73a298ea893e/jiter-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:2492e5f06c36a976d25c7cc347a60e26d5470178d44cde1b9b75e60b4e519f28", size = 521120, upload-time = "2026-04-10T14:27:33.299Z" }, - { url = "https://files.pythonhosted.org/packages/89/6b/b00b45c4d1b4c031777fe161d620b755b5b02cdade1e316dcb46e4471d63/jiter-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7609cfbe3a03d37bfdbf5052012d5a879e72b83168a363deae7b3a26564d57de", size = 553668, upload-time = "2026-04-10T14:27:34.868Z" }, - { url = "https://files.pythonhosted.org/packages/ad/d8/6fe5b42011d19397433d345716eac16728ac241862a2aac9c91923c7509a/jiter-0.14.0-cp314-cp314-win32.whl", hash = "sha256:7282342d32e357543565286b6450378c3cd402eea333fc1ebe146f1fabb306fc", size = 207001, upload-time = "2026-04-10T14:27:36.455Z" }, - { url = "https://files.pythonhosted.org/packages/e5/43/5c2e08da1efad5e410f0eaaabeadd954812612c33fbbd8fd5328b489139d/jiter-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd77945f38866a448e73b0b7637366afa814d4617790ecd88a18ca74377e6c02", size = 202187, upload-time = "2026-04-10T14:27:38Z" }, - { url = "https://files.pythonhosted.org/packages/aa/1f/6e39ac0b4cdfa23e606af5b245df5f9adaa76f35e0c5096790da430ca506/jiter-0.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:f2d4c61da0821ee42e0cdf5489da60a6d074306313a377c2b35af464955a3611", size = 192257, upload-time = "2026-04-10T14:27:39.504Z" }, - { url = "https://files.pythonhosted.org/packages/05/57/7dbc0ffbbb5176a27e3518716608aa464aee2e2887dc938f0b900a120449/jiter-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bf7ff85517dd2f20a5750081d2b75083c1b269cf75afc7511bdf1f9548beb3b", size = 323441, upload-time = "2026-04-10T14:27:41.039Z" }, - { url = "https://files.pythonhosted.org/packages/83/6e/7b3314398d8983f06b557aa21b670511ec72d3b79a68ee5e4d9bff972286/jiter-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8ef8791c3e78d6c6b157c6d360fbb5c715bebb8113bc6a9303c5caff012754a", size = 348109, upload-time = "2026-04-10T14:27:42.552Z" }, - { url = "https://files.pythonhosted.org/packages/ae/4f/8dc674bcd7db6dba566de73c08c763c337058baff1dbeb34567045b27cdc/jiter-0.14.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e74663b8b10da1fe0f4e4703fd7980d24ad17174b6bb35d8498d6e3ebce2ae6a", size = 368328, upload-time = "2026-04-10T14:27:44.574Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5f/188e09a1f20906f98bbdec44ed820e19f4e8eb8aff88b9d1a5a497587ff3/jiter-0.14.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1aca29ba52913f78362ec9c2da62f22cdc4c3083313403f90c15460979b84d9b", size = 463301, upload-time = "2026-04-10T14:27:46.717Z" }, - { url = "https://files.pythonhosted.org/packages/ac/f0/19046ef965ed8f349e8554775bb12ff4352f443fbe12b95d31f575891256/jiter-0.14.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b39b7d87a952b79949af5fef44d2544e58c21a28da7f1bae3ef166455c61746", size = 378891, upload-time = "2026-04-10T14:27:48.32Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c3/da43bd8431ee175695777ee78cf0e93eacbb47393ff493f18c45231b427d/jiter-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d918a68b26e9fab068c2b5453577ef04943ab2807b9a6275df2a812599a310", size = 360749, upload-time = "2026-04-10T14:27:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/72/26/e054771be889707c6161dbdec9c23d33a9ec70945395d70f07cfea1e9a6f/jiter-0.14.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:b08997c35aee1201c1a5361466a8fb9162d03ae7bf6568df70b6c859f1e654a4", size = 358526, upload-time = "2026-04-10T14:27:51.504Z" }, - { url = "https://files.pythonhosted.org/packages/c3/0f/7bea65ea2a6d91f2bf989ff11a18136644392bf2b0497a1fa50934c30a9c/jiter-0.14.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:260bf7ca20704d58d41f669e5e9fe7fe2fa72901a6b324e79056f5d52e9c9be2", size = 393926, upload-time = "2026-04-10T14:27:53.368Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/b1ff7d70deef61ac0b7c6c2f12d2ace950cdeecb4fdc94500a0926802857/jiter-0.14.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:37826e3df29e60f30a382f9294348d0238ef127f4b5d7f5f8da78b5b9e050560", size = 521052, upload-time = "2026-04-10T14:27:55.058Z" }, - { url = "https://files.pythonhosted.org/packages/0b/7b/3b0649983cbaf15eda26a414b5b1982e910c67bd6f7b1b490f3cfc76896a/jiter-0.14.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:645be49c46f2900937ba0eaf871ad5183c96858c0af74b6becc7f4e367e36e06", size = 553716, upload-time = "2026-04-10T14:27:57.269Z" }, - { url = "https://files.pythonhosted.org/packages/97/f8/33d78c83bd93ae0c0af05293a6660f88a1977caef39a6d72a84afab94ce0/jiter-0.14.0-cp314-cp314t-win32.whl", hash = "sha256:2f7877ed45118de283786178eceaf877110abacd04fde31efff3940ae9672674", size = 207957, upload-time = "2026-04-10T14:27:59.285Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ac/2b760516c03e2227826d1f7025d89bf6bf6357a28fe75c2a2800873c50bf/jiter-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:14c0cb10337c49f5eafe8e7364daca5e29a020ea03580b8f8e6c597fed4e1588", size = 204690, upload-time = "2026-04-10T14:28:00.962Z" }, - { url = "https://files.pythonhosted.org/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" }, - { url = "https://files.pythonhosted.org/packages/32/a1/ef34ca2cab2962598591636a1804b93645821201cc0095d4a93a9a329c9d/jiter-0.14.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a25ffa2dbbdf8721855612f6dca15c108224b12d0c4024d0ac3d7902132b4211", size = 311366, upload-time = "2026-04-10T14:28:27.943Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/520576a532a6b8a6f42747afed289c8448c879a34d7802fe2c832d4fd38f/jiter-0.14.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ac9cbaa86c10996b92bd12c91659b60f939f8e28fcfa6bc11a0e90a774ce95b", size = 309873, upload-time = "2026-04-10T14:28:29.688Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7c/c16db114ea1f2f532f198aa8dc39585026af45af362c69a0492f31bc4821/jiter-0.14.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:844e73b6c56b505e9e169234ea3bdea2ea43f769f847f47ac559ba1d2361ebea", size = 344816, upload-time = "2026-04-10T14:28:31.348Z" }, - { url = "https://files.pythonhosted.org/packages/99/8f/15e7741ff19e9bcd4d753f7ff22f988fd54592f134ca13701c13ea8c20e0/jiter-0.14.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52c076f187405fc21523c746c04399c9af8ece566077ed147b2126f2bcba577", size = 351445, upload-time = "2026-04-10T14:28:33.093Z" }, - { url = "https://files.pythonhosted.org/packages/21/42/9042c3f3019de4adcb8c16591c325ec7255beea9fcd33a42a43f3b0b1000/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:fbd9e482663ca9d005d051330e4d2d8150bb208a209409c10f7e7dfdf7c49da9", size = 308810, upload-time = "2026-04-10T14:28:34.673Z" }, - { url = "https://files.pythonhosted.org/packages/60/cf/a7e19b308bd86bb04776803b1f01a5f9a287a4c55205f4708827ee487fbf/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:33a20d838b91ef376b3a56896d5b04e725c7df5bc4864cc6569cf046a8d73b6d", size = 308443, upload-time = "2026-04-10T14:28:36.658Z" }, - { url = "https://files.pythonhosted.org/packages/ca/44/e26ede3f0caeff93f222559cb0cc4ca68579f07d009d7b6010c5b586f9b1/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:432c4db5255d86a259efde91e55cb4c8d18c0521d844c9e2e7efcce3899fb016", size = 343039, upload-time = "2026-04-10T14:28:38.356Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/1f9ada30cef7b05e74bb06f52127e7a724976c225f46adb65c37b1dadfb6/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f00d94b281174144d6532a04b66a12cb866cbdc47c3af3bfe2973677f9861a", size = 349613, upload-time = "2026-04-10T14:28:40.066Z" }, + { url = "https://files.pythonhosted.org/packages/e8/fb/0b68a8666a203ab349334e502285153ac97e84a1a3e2f80de8385ed0899c/jiter-0.17.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed1a24005daac667d577402d75a2922f9775a165b146b883ff1ad3602d8be689", size = 290968, upload-time = "2026-09-12T15:11:10.44Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e9/5993079500530586655c6f2823e2926b3f5ceddd49918c5a86757eadba3a/jiter-0.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b847b18d066c46b3b7ae49d6c94a7634c5e4a8983146ee25562a092000f5e3ad", size = 324072, upload-time = "2026-09-12T15:11:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/6f5dbffaa5b0e647386e3c3376aa150987538f1b13f6da1a9582787957af/jiter-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b68d3495d95da120651a5628c7ebadee84ed001a1b76e6afc325c42482f15b5", size = 344891, upload-time = "2026-09-12T15:11:13.387Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9d/8dd5719add6c77be95b23c129682a1484c5339ffd66736d66bfa6831a2f3/jiter-0.17.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c1a5336c04a41b1f1cf9572e294aec27cc569767ff73de7bf87a91f0bea7cb9", size = 328213, upload-time = "2026-09-12T15:11:14.655Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e5/82f885863afaa79efac066efdb7b70bd242ef83d3e548453d61b3737e54a/jiter-0.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b75f85660108965a94be77911a25a253429307294d9415b3c597118977a614de", size = 343033, upload-time = "2026-09-12T15:11:16.046Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/ee3cd7db1704ec9c274ae549803ef1369c801490f7f6d48d0aa3b9ac1ae0/jiter-0.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32aaaa764604496610a3ad2d98503ae88ccb2fbe769e892ff4533e778e85f708", size = 351701, upload-time = "2026-09-12T15:11:19.188Z" }, + { url = "https://files.pythonhosted.org/packages/93/2c/af9ca503b38683a82b6732352a861515f28e2d46fe445aea57e07fc86e06/jiter-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:826871c42cebaae22f0a2b5673a4a1a75c851bb2d13b3c17764a630a6b298984", size = 348754, upload-time = "2026-09-12T15:11:20.486Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ba/692b071b194270b019b7d1790adb8658193379b951029e5ad15a14a2809d/jiter-0.17.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:00b5a98df3e3a3e8cf7b619f4ac2f8bf975bbf3d95d02c5d17b8dbfe5c8b8245", size = 329263, upload-time = "2026-09-12T15:11:21.701Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8f/35a22beb6b9eb025b7e2f88a38ca3b9c87017aac237feec2583eabe243ad/jiter-0.17.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6af5b74073bd25bae695e6d00919f6a9be7ed5a9f8836d981eb1ffe84139e6fb", size = 337572, upload-time = "2026-09-12T15:11:23.145Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0f/4341ceb3ce08199b4e8a798acc0bb0c79b3e861b6cecdb9d3b5138965c5d/jiter-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:16dd0c1baf098ae70b8f3616574eb3fedf34e26670b89e16a7e67561f737ed2d", size = 485174, upload-time = "2026-09-12T15:11:24.394Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6d/92fa138ac82db9001b68563df204c32f2203bee84301df083260039ca23b/jiter-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:545c36a0f3b2238c242cc9785439d3242a871b7bc39fe3f441bcaa07bf3aa83e", size = 522115, upload-time = "2026-09-12T15:11:25.645Z" }, + { url = "https://files.pythonhosted.org/packages/52/92/9b11557ddf898e84c253d1c42cc1bddd8ad1a754b46b01ae35a6a253526f/jiter-0.17.0-cp310-cp310-win32.whl", hash = "sha256:155be7355bdb7ca76ab0961be8982c225f964a5c073a83984183f22391cc29fc", size = 188163, upload-time = "2026-09-12T15:11:26.793Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f3/b4381cc962fed7d505913d5920e9f51f1fd5cf340a563134e8cadf3604d2/jiter-0.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:37150a9e02e869475854fa20b7d0d5e26d18d0f8bc17293999973ff27e99ae7a", size = 231679, upload-time = "2026-09-12T15:11:28.911Z" }, + { url = "https://files.pythonhosted.org/packages/a1/50/17afdaffcc8af4bf4fddf2b6c26d066553aa2221983f2affcde435fc2532/jiter-0.17.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cfafd7be8b16ceadd298db542cead37cddc211c4c49e04ad2596924df18625b1", size = 290699, upload-time = "2026-09-12T15:11:30.085Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/c185d32d5b3657ad84da26c84a9eb15f00aa1b39d6882fcc0052dba2d7c2/jiter-0.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8adca2e793288e5f1bb29279bb439d0d3cfbb50eddca7e7e6ffd42ff4f482406", size = 324246, upload-time = "2026-09-12T15:11:31.3Z" }, + { url = "https://files.pythonhosted.org/packages/24/7a/8b8903bfe91a90a8fa1ec9b45d9fda5b6287a386693b69d720a882d73f3c/jiter-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c692d567ba206c7cca38c9d1d0ccc70c9786290173c184d871ca12e9981ed7", size = 343275, upload-time = "2026-09-12T15:11:32.758Z" }, + { url = "https://files.pythonhosted.org/packages/a2/5d/6821fae2abc71a3c3a84bef8598d31fc4f27d9edfb55bd8f6c08afb8ef93/jiter-0.17.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81c83c0abe614446a283d994d2c07c4f58632dea2cdf66ba9e2921bb8ccd593e", size = 328034, upload-time = "2026-09-12T15:11:33.9Z" }, + { url = "https://files.pythonhosted.org/packages/f3/51/8e7a963b1c2dfdc01d6228b004f50a2a3d7c46f0549d7b096a5d15ef81d5/jiter-0.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:073dc68c1a700c8fc480e877864a6b6ffc887533e261f4380c08c16bf09d057a", size = 342457, upload-time = "2026-09-12T15:11:36.414Z" }, + { url = "https://files.pythonhosted.org/packages/73/27/8b2a267e3bda45d9298331cacfe3521e761f0a2b05ad10a23c6548d08358/jiter-0.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:492f37230bbf9581ab2c17bcda862c249afb9ae2e3ab2dd6db59943bc4cc3153", size = 351271, upload-time = "2026-09-12T15:11:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/4a/8f/5c74e5e142a6736833d7a991ab04d5c0738038dc44db05af0bb3cd2559e8/jiter-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5888fe5abc1ca2fa834a3e1b4c7ef0dcece286a7d7e95a609ef0934b777b9fc9", size = 346052, upload-time = "2026-09-12T15:11:39.722Z" }, + { url = "https://files.pythonhosted.org/packages/98/9c/f54920f06d1696e80b1be841d56412871c6856b1b1e3b541b1ed35346554/jiter-0.17.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:84ac78df457e1ee3f7e733bd114823302ae8c5ad5542d7e6647d92ffaa090a04", size = 328915, upload-time = "2026-09-12T15:11:41.065Z" }, + { url = "https://files.pythonhosted.org/packages/21/53/080f126863bceb055db9f1fd5431485eb493b35545a3c35e961ac18cd924/jiter-0.17.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7573e80232c5bcf80c24c038cf7e53a463f5c3b1dd1dd4109d66304f4dccc233", size = 337240, upload-time = "2026-09-12T15:11:42.356Z" }, + { url = "https://files.pythonhosted.org/packages/f0/76/3ab742823a0e0e70e143c6c90a482d9d90396ac2445e7b9483eb7245d3b5/jiter-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:11902505d401691720f5785c15b02204248526edee11b635cd6c40cd52b81599", size = 484917, upload-time = "2026-09-12T15:11:43.549Z" }, + { url = "https://files.pythonhosted.org/packages/14/e0/8ca71bc8b9cc9ed96c9da565863e00f3bb875a8fb82abcf03e975e067902/jiter-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:64846211a2debe7c071d2146d2283d2b0c1c93dc8fd5fb7794faac2ca6061b5c", size = 521868, upload-time = "2026-09-12T15:11:44.704Z" }, + { url = "https://files.pythonhosted.org/packages/c1/02/81f8719dcedb75713082a2048405376c81f6546b75af8167f3bba01a1ed0/jiter-0.17.0-cp311-cp311-win32.whl", hash = "sha256:c19b9357309b8cc6de8a48fca8e44a8c9c2feaaa2f5896d037fa505d48fcab80", size = 187844, upload-time = "2026-09-12T15:11:45.874Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8c/59693f348488f01ed12d862e99ab8da14961152d3e9c39b9b1ef363f3572/jiter-0.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:e654b6b04e39c9cb19cb8b04c6ddf1f2db07751fa14156413969fd78bad0e5cb", size = 231402, upload-time = "2026-09-12T15:11:47.083Z" }, + { url = "https://files.pythonhosted.org/packages/fe/89/fb35e286463cb9f01edc2c4e47df6e5477ee36bca5096414e9ea87985588/jiter-0.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:3ad556afc289f15d2b181b941982d01f06190863c07440185b9f354e1bd2def3", size = 187006, upload-time = "2026-09-12T15:11:48.245Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f8/07bd8c3a23f7a8a6875e6a820bbffe1483a18f18f9398a91b5495123176e/jiter-0.17.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ebf918dfd6a74adc1b9ad71f63c4ab00902fcd3b7fd39f2e24d871db8d713b91", size = 291633, upload-time = "2026-09-12T15:11:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5e/0de4c6f84ffefa6809ffc2d550b9a314365acf7e7ec9b6c7375d49047900/jiter-0.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61aed66ee042b3b49ef85fdf75714234d055d89d8496ac1c6e47f89e7a30d5e4", size = 321695, upload-time = "2026-09-12T15:11:52.727Z" }, + { url = "https://files.pythonhosted.org/packages/20/ac/befe2e82065bee37a0252081666ed2f48c1ac5f5c6c318c2de8168ba393d/jiter-0.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76eb4a5c20e86f9f848286f167024890f2862258a965d254774deb7fc1545ca1", size = 341967, upload-time = "2026-09-12T15:11:54.231Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cd/9797c1e529746750ae589da7c1a8c24373f00d88e11a989f9e5eb1959079/jiter-0.17.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bcc064f99183a9cbe7f26ed648c352031a74145cd61ed75d34632c73eb46a5a8", size = 326546, upload-time = "2026-09-12T15:11:55.41Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fd/e6914c38d6347bab4ebff2b1f0c0f191db276e7a1d5c376176757da42fe3/jiter-0.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73b64e69c4150748e020356d958af94bec33c70a0a93d665cfa8f6d580fe1a63", size = 340995, upload-time = "2026-09-12T15:11:58.211Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7d/611b3abf6f88945b5474da5cdc6d1a185e805ac9bf446bb7766dcda6ea87/jiter-0.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0bc7f684b65bcda9c20434267577db71bf9905ceddd32b60d1d93278d8c8d3a", size = 352188, upload-time = "2026-09-12T15:11:59.414Z" }, + { url = "https://files.pythonhosted.org/packages/52/f8/b6e513ecbdf3b3cebe587c2279281ecf775b729a58cf4cc7bdf898ded029/jiter-0.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c21265b251d99bbb40080d178a8953e35601d3a1564e05c4de4c0d2ca616797", size = 345025, upload-time = "2026-09-12T15:12:00.697Z" }, + { url = "https://files.pythonhosted.org/packages/28/a8/fe26d06c5a6c5a4cfe703c5154c8a140da1305671eb3681aba9422d4f393/jiter-0.17.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:f3d7f7b34114f7ddc6d72a8e882d49de636b35d9fd12b4d420d3c5729f6c9812", size = 329180, upload-time = "2026-09-12T15:12:01.831Z" }, + { url = "https://files.pythonhosted.org/packages/e1/58/e6d66a26af40a20e62486feb7e222fd50f6e7aaa4f107abd89675dcc835b/jiter-0.17.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5078ab00664307fab2019b522a93aeb191122789f085daf5fd9e362154021d4a", size = 335805, upload-time = "2026-09-12T15:12:03.056Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3e/96520aa2fef5ef831d95483a902140bfab83dcac9eaa74f7df61b5e50a1b/jiter-0.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:470e1b1e4c42f1ead2189166a299691871a2df5056c976e7fb96feafaf5f9d44", size = 484121, upload-time = "2026-09-12T15:12:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/6a/8f/5d9d92fe538bf36ff481a2278c48147e59c1cf8eb2f7be665260665febe5/jiter-0.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6aedeb7352b8f3b6af9cbd67983840165c00428e63f1b420a85885128ea31", size = 521310, upload-time = "2026-09-12T15:12:05.612Z" }, + { url = "https://files.pythonhosted.org/packages/50/06/a09f979b22e652afbc3de66c709b2ba92edcef555f7535ab937c86b4f21a/jiter-0.17.0-cp312-cp312-win32.whl", hash = "sha256:362bb47423886d45a9f705d2d9d4008c6eedd4e41eb1bab4e96fb6daa06b33fd", size = 185029, upload-time = "2026-09-12T15:12:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d9/98265a005b2473ec2be5a84e2b64c2f65382c673879f1574845cd4bcd77c/jiter-0.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:9bd3caac219df476dd0cc3fe01d2f1581ed588906feac767abd9614c1c12f8b3", size = 227381, upload-time = "2026-09-12T15:12:08.823Z" }, + { url = "https://files.pythonhosted.org/packages/a8/11/2e05bf5a56e57a543ebb8f585074adf09383e99d7b062dac92eab1f4d57f/jiter-0.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:36ee6e69027396664e59995b9a635a947a5304ee9837279584a0bb8145c8f6b8", size = 183610, upload-time = "2026-09-12T15:12:10.374Z" }, + { url = "https://files.pythonhosted.org/packages/40/eb/2c4a8075ed5ea02b56911e9375d4c8d7784572ff4af32e5a99ae0d071044/jiter-0.17.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1b18434638228c0c184281609bf3d9459026a0f1ea48fb76c205e3ef72069caa", size = 290991, upload-time = "2026-09-12T15:12:11.641Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b1/34bfa29599d420423baac6ff7cada6674fe63d5a7a2ccb3900b904678783/jiter-0.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ec89771f4272b989487a6364e519db6bbaba323e8bbf949ac89a45ea9c18b7a3", size = 321425, upload-time = "2026-09-12T15:12:13.855Z" }, + { url = "https://files.pythonhosted.org/packages/11/71/a5ac64a62a04aebd556afadab14a6b730001e16df87266ded943a100a1d9/jiter-0.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e3f052c671d5f425cca5ea5901cf11a831369fba4a55a3862cab93c323b4c3b", size = 343138, upload-time = "2026-09-12T15:12:15.046Z" }, + { url = "https://files.pythonhosted.org/packages/01/dd/f761e320ea473314cb68612bc6a435393464dbd198051399b36848b4ebf3/jiter-0.17.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:785a216bbaf8f15fc974e964ced7322cd3d774bb0e86949edd78c6bffd6ba35b", size = 325805, upload-time = "2026-09-12T15:12:16.506Z" }, + { url = "https://files.pythonhosted.org/packages/19/1a/27d8e40f0fb29bbc7a5adf30907144396a115dbe93d5d8976c054a6dfe96/jiter-0.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d85c558c9f8532bba287a990ac63767c7daf756f0d8c030219f62499b1fa228a", size = 340230, upload-time = "2026-09-12T15:12:17.682Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c0/30bcde78a28155461f965d16b7aca4ffca6d17494d905f7a0bb072e6c64e/jiter-0.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c23849235d2142ce444b2b8c6eceee9f82f4cc0bd5c9081602e4155c6197807", size = 351343, upload-time = "2026-09-12T15:12:19.337Z" }, + { url = "https://files.pythonhosted.org/packages/27/17/91420b156315ae22732f5ee1a7b5725a030aab9dc8fd7dcdacfb4aa588d3/jiter-0.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58df29268a95e910f17db7ec9178eb7f15aa8619aaca3575275c4e6b3f4fe4c5", size = 344990, upload-time = "2026-09-12T15:12:20.705Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a2/ae6d5672644cc11127970277c9aeb0fa6fae376845587f5b0a8e8828167c/jiter-0.17.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:a277f97eba7d66b1ee27eb5dab5b774ff46a10c78d89a1d3dcce04ce1357c8ca", size = 328624, upload-time = "2026-09-12T15:12:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/04/62/45cb1162f6aa586536e4a973fc339d72dc6b08cca030d70a838a307aa778/jiter-0.17.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe15ddf316f1f1f643347d3a474e74ce61880c79a11ec5dca53df20c071bd3e8", size = 334731, upload-time = "2026-09-12T15:12:25.229Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/45c1574b644da7deda0b7591c349520dcf83ce45b24d7ca19922dab1fc27/jiter-0.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:02adebb7ce6413c44d40af9ad59d1c1cd79630ccdcb6f7bdd2d461e48c03d8f9", size = 483649, upload-time = "2026-09-12T15:12:27.557Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d3/ebea1ecb5b241c519f192b30215c79a8e47f42f1621acbcd6f8830728416/jiter-0.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:55d0e0e613a3f9ad600cf436e0e2b8057d1b52bcf1d91b2d36ac53451231e6a8", size = 520758, upload-time = "2026-09-12T15:12:28.99Z" }, + { url = "https://files.pythonhosted.org/packages/64/e6/682b641ff0765ea9bdc349dbc7d223de5c8af8ec1abda0db3406992f92fe/jiter-0.17.0-cp313-cp313-win32.whl", hash = "sha256:2c45ad7c973ef33fe5114a953377b35a95240f4542c0724d9f781e47dc24bac7", size = 184334, upload-time = "2026-09-12T15:12:30.813Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d2/9a49aac2b27af4cc5015e368c0cc3588491a532f717a668ffce1f1ac57da/jiter-0.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:a3cebb1fe4a1abb00465f3f8a17e09112603e8b7c59e5c3adbcd9f7815a64acd", size = 226601, upload-time = "2026-09-12T15:12:32.096Z" }, + { url = "https://files.pythonhosted.org/packages/b4/ce/9a43e9f614608eafa78de22aedcff54cd21324467b5d442d5c9b00244145/jiter-0.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:96b8b0c6dc5d78682f54a450785e075aa929cde768304cad363cd4efba5a82ac", size = 183103, upload-time = "2026-09-12T15:12:34.396Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/23065f8e2c7a4c372c1b6f6622e4cfab4dc786cb5150052b1527e6a6a840/jiter-0.17.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:00d783a779c5664e16dbad5e3a3c3a75e128b07dd5f4765159658d9210a50ca5", size = 292210, upload-time = "2026-09-12T15:12:35.613Z" }, + { url = "https://files.pythonhosted.org/packages/ea/81/67b58647560bc82a4490d722caa8561d7a86a9f45d4fa620b7e5fe282c7a/jiter-0.17.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0619d806e260ecf0c2a64521942c94af5d547c9ec99b55ae4f51b538b5576a76", size = 321512, upload-time = "2026-09-12T15:12:36.907Z" }, + { url = "https://files.pythonhosted.org/packages/c7/07/6658359a25f55927f7f8bf0e16465dee2ccd0b2a1a5208acc0df8972e074/jiter-0.17.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc0288ce39190ee33fe6e4ec73161eed34e7e2da509b525546ca061778d62b64", size = 343897, upload-time = "2026-09-12T15:12:38.189Z" }, + { url = "https://files.pythonhosted.org/packages/46/04/5d50a9f0319cbdc37fd53c27f8c313d46afc34f1b048219ae6d8ea068da4/jiter-0.17.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a52a430d04225ffde633e6840bf2381d34c019ff98526b5929755b9052fb199", size = 326519, upload-time = "2026-09-12T15:12:39.532Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c7/d02517832b29eb8275fdd0f4ce0f17b80f58cc4c3ebecd4d9ace990d633d/jiter-0.17.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37f33d327900bf2879613b3363fd48df97b4232d0c41f54bcf2e790c2fc40a71", size = 341369, upload-time = "2026-09-12T15:12:41.486Z" }, + { url = "https://files.pythonhosted.org/packages/3b/07/499b5f5603501cdd93a73a6a176dfad9c96555a3ae58ca9f8e3acba63dc9/jiter-0.17.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6cf564d43c4388149ca58ee571d0f5ccf875e20d1fd4662fd94cc0d1ea3b10ef", size = 352160, upload-time = "2026-09-12T15:12:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/f5/75/b04013c7743269d4533ef4e746fc0ed678a143968dd7448658e3f51daad2/jiter-0.17.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:523c499235fb65add25d4bb01b1c4709ce695efdc7deb6c0a7bc515b5c44e0fb", size = 345018, upload-time = "2026-09-12T15:12:44.192Z" }, + { url = "https://files.pythonhosted.org/packages/1d/96/cbb6fd1e42a77c8412ec4643db95059b30cdfc635e387cc9193e098ce268/jiter-0.17.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:455e4ab35cb2a4a91a8404e08fd3c621bae433922e59bf1c494fe20a426b013b", size = 329244, upload-time = "2026-09-12T15:12:45.491Z" }, + { url = "https://files.pythonhosted.org/packages/15/67/d3be402f398566a379bf40ae65be5c3505b14d9e95e0802a597ddde7ddee/jiter-0.17.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6871973bfbd4408f7f1c632b30bbb5bbd9671c1bc8650af6823e24b7be13709b", size = 335693, upload-time = "2026-09-12T15:12:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8d/98e2c4130b93d64f1d67c89060b928d04102549bf05e64451c9e6024f9ca/jiter-0.17.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:77f6aac0137309b31448c1bdcda4c6c77077664a6d018ece8d94019c68a5a5b9", size = 484329, upload-time = "2026-09-12T15:12:48.361Z" }, + { url = "https://files.pythonhosted.org/packages/78/5e/8da91e49f0fbca37c3489fb4cf3ad6676d4965f00ae5468bca3a2513737a/jiter-0.17.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:93946d89fa04d5ba64dd323a8dd8d901676cb8a3c81d99ae4f6c051a9b4c3f2f", size = 521358, upload-time = "2026-09-12T15:12:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/be/21/5388684a5a38af3557cd9c2424b9827c71809cff24373c75ef9d0d3dfba9/jiter-0.17.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:70f19a2ca8429f91e82eeffb2f51cb87bc2d6e953b009b91a92d29c3a16ccb03", size = 110459, upload-time = "2026-09-12T15:12:51.747Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ad/58b3a93525d2ffca7f54d9dee441381990082bd1172fbeb8d6a3f72a4dc3/jiter-0.17.0-cp314-cp314-win32.whl", hash = "sha256:71dbd74314c5df52a1bccf7b8bca46d14e943af7a2012e73b23f49977ef194c8", size = 185043, upload-time = "2026-09-12T15:12:54.477Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4a/1aa520eb6c359b262c14ff995ca7283837208ddfb1202082ce9d73cf214d/jiter-0.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:ac3c6ee3264d6f5c44c617f90bc7e8b9e1587e7d6708c9d8f811cb65582ee312", size = 227163, upload-time = "2026-09-12T15:12:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e4/5997f648794bd9b499491d0ff480b096cc9a9c65bdba29f57568e6aa1705/jiter-0.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:6219adaf59711ba7063a52496e8ec6d3fa3e209d7827d83eee3b2abc780a1744", size = 183505, upload-time = "2026-09-12T15:12:58.196Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/84a5ec271d09f7590b6073af5ee4abb44eab4ccace453b7e2c5ce45234ca/jiter-0.17.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:59bddbe6f9ffecc68d641e1e2d619ce64cf8a9e9eeb74e5c518f74fc87abf1b0", size = 321527, upload-time = "2026-09-12T15:12:59.394Z" }, + { url = "https://files.pythonhosted.org/packages/39/71/9e1fd0045f5920b4c36be35c3f0f0dfd123668684f8ad352619d7aa44183/jiter-0.17.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cb41cd1432f1dc19a231cf70b54d42b2c9f05085155859263fce06fa4d41388", size = 340865, upload-time = "2026-09-12T15:13:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2b/14627fd2bc377f3dd09491bcace6b90e34b4d7fea2f1f3295031ff91f528/jiter-0.17.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd7790aa79c8b518e512ebcdfce9f11d8ef5f30efd43720c8a19a548b39fa489", size = 325412, upload-time = "2026-09-12T15:13:02.152Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/8d5808f7bf0f456bde79e6393587183a0cee5f83d179fe1f7f1eff2ba067/jiter-0.17.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbbfe4e3c21c8166980cddc5bee1a315df082454f007947dfb6fb73800768165", size = 340473, upload-time = "2026-09-12T15:13:03.485Z" }, + { url = "https://files.pythonhosted.org/packages/4f/da/1d8c7c6c4ae6b2423b94a81b6b907d37b28f87664e077427b531bf1b5313/jiter-0.17.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c286860abfe8b100cac1c02e225e5776eb9216edd71ba17cdb237da4af32bc9", size = 350757, upload-time = "2026-09-12T15:13:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/eb/96/c1813dcca15c5a370145a448aaea7d1f83f6f0228a5f1130e79340ee385f/jiter-0.17.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f753eb70b1474a29e635e7542ff7312e6d6b951e0b25e8a2e8c34eeb1ddcd478", size = 345203, upload-time = "2026-09-12T15:13:06.131Z" }, + { url = "https://files.pythonhosted.org/packages/d7/f7/fc61cbcf2992d169ede13648fc3fd8e2d3171a3669dde43cd4db556549ac/jiter-0.17.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:eae86b1f027031e39db2e0e9c4842221edb7b8cd474d23f87a79b3bd4b651768", size = 328322, upload-time = "2026-09-12T15:13:07.392Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/46418a3abbdffb7dc41b314200360f24f75faaeb35573e81c92de322cce9/jiter-0.17.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bf350452a43173e69e1fc74847c57a60e3d7515807287f29849baa2a85d8718", size = 336570, upload-time = "2026-09-12T15:13:08.666Z" }, + { url = "https://files.pythonhosted.org/packages/f0/28/b8a55b949be6306df8888e365a8df05441de8a7b11289f6957004302e41e/jiter-0.17.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:da139721f4b7cafdbff580a4f511ea24cb91f4909330c6b926a1ca53836c0a59", size = 482879, upload-time = "2026-09-12T15:13:10.037Z" }, + { url = "https://files.pythonhosted.org/packages/75/3b/21d0afa53ba0680962c39f3eb95ed2946f8793369ed44b0c82b490723081/jiter-0.17.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:8079849db9a1371bfd90bad088458a8fb836261879df2233cc9632464ecf64e1", size = 520406, upload-time = "2026-09-12T15:13:11.456Z" }, + { url = "https://files.pythonhosted.org/packages/ef/03/bcbaf8b6b9ea23c2c074411f8ecfbb02d820abac5d0cb8f4e280209174a2/jiter-0.17.0-cp314-cp314t-win32.whl", hash = "sha256:8f770b0c77e5fac482e1ba03ca1a7e18286bfb213d749932a00a7e4cd5de5e06", size = 184434, upload-time = "2026-09-12T15:13:13.037Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b5/5d6ce2c93ef6fe1241b37a9005547f9b6d58db1f07f39fe95807d4b98f51/jiter-0.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c4289293e5278d9314b00f15c37f2120fa51d3d68565292e715524c750e775a9", size = 227392, upload-time = "2026-09-12T15:13:14.933Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4b/1e52baf90187606e33a7b8cfa8f96f5829acd7f01870077eb01059ab76d0/jiter-0.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4dfbfe5a6e1e80a7082af559f66386405025ec278833e0c649f69cbc6e1004cc", size = 182776, upload-time = "2026-09-12T15:13:16.239Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3b/05a917204413e2e09906dfa35240c1021227aeb56c7305abea9562c598b4/jiter-0.17.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:eaba834b72d573547b9d966465b3394b749d5e14208cc70acb63aca37619ab33", size = 288143, upload-time = "2026-09-12T15:14:02.998Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e1/a1cd3c0cf8f79945939e4f8caae9990529f67d7f77f671769f73956329b9/jiter-0.17.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:51e1519d676a9f14dad9c2a411170d43b022ddb7989562df4e849b261ce127b2", size = 281847, upload-time = "2026-09-12T15:14:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/72/b4/9b797679e09f4a46c32986aeb3670bd9bc562fc0b373c7a0ee5c5dce1206/jiter-0.17.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ce4feb52493e3513335b2accdcd75605652e4632772d3c8c2f7b86954d7f39", size = 304797, upload-time = "2026-09-12T15:14:05.733Z" }, + { url = "https://files.pythonhosted.org/packages/25/4a/0d77415b27a00d970e4e710f7c1de62e96a11c4cab3ed1add0015af04626/jiter-0.17.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29f49b325e0234e4ad9ecca5b861ffbd09b95ccac9bd46fa55841b6e56eea5fe", size = 307815, upload-time = "2026-09-12T15:14:07.105Z" }, + { url = "https://files.pythonhosted.org/packages/17/31/4bb27f54333d3b9ef1e5bd3312dc0b4bbe59c68bb0885fdb40583a6b1567/jiter-0.17.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:454c4997d73cc466c71fd565d91e603b0274e48ea0c6b0b7a7aee6967e4ceb7c", size = 288415, upload-time = "2026-09-12T15:14:08.455Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/879570ecf82574eaea77c5eb10309f4b630dece5f2a556e9814a90ba3f2d/jiter-0.17.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:40d2c240f8f80b5b0f201b29f0ae129c81448c60c772227a41747b5e0026f6a2", size = 279113, upload-time = "2026-09-12T15:14:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/77/7a/1f0b8a35fbd079a4f1752c31a15dc99cf277f863747c459be0af39e900e5/jiter-0.17.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e05f5adbf68c4bd11e1610f394034d984152988e84be6f8314235ce6f2139e5", size = 303708, upload-time = "2026-09-12T15:14:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8b/d76219ebdbcf3d4209d9d21a0810db4c8d0a6f88e3ee87d30bdea4e90d30/jiter-0.17.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c0bf24c72fd0491405dce5d40194f2070e9021ce648c1a1d46234b93d848ff", size = 307147, upload-time = "2026-09-12T15:14:12.897Z" }, ] [[package]] @@ -6241,21 +6237,19 @@ wheels = [ [[package]] name = "openai" -version = "2.33.0" +version = "3.35.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, + { name = "httpx2" }, { name = "jiter" }, { name = "pydantic" }, { name = "sniffio" }, - { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f0/ee/d056c82f63c05f06baac0cffb4a90952d8274f90c49dfe244f20497b9bbd/openai-2.33.0.tar.gz", hash = "sha256:f850c435e2a4685bba3295bd54912dd26315d9c1b7733068186134d6e0599f9a", size = 693254, upload-time = "2026-04-28T14:04:42.428Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/4c/609b16f2a6e9a99f27a82ce93ff75868e7cb364fe6daff50cc0c1d620772/openai-3.35.1.tar.gz", hash = "sha256:b33e6561b90586e450def8a388e7d364755325da6884fc71348be5542e043bd0", size = 2250942, upload-time = "2026-10-18T01:06:38.964Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/32/37734d769bc8b42e4938785313cc05aade6cb0fa72479d3220a0d61a4e78/openai-2.33.0-py3-none-any.whl", hash = "sha256:03ac37d70e8c9e3a8124214e3afa785e2cbc12e627fbd98177a086ef2fd87ad5", size = 1162695, upload-time = "2026-04-28T14:04:40.482Z" }, + { url = "https://files.pythonhosted.org/packages/22/7b/6d7e70f0762d1b286051bd7cb2fd0c364c1830d0f8826738673aeec05c2b/openai-3.35.1-py3-none-any.whl", hash = "sha256:264fd0fafc889021f526b7c1e5a2df5ca3e8dea874738ecd31da666434f25967", size = 2280230, upload-time = "2026-10-18T01:06:36.16Z" }, ] [[package]] From 9dc0e71aedd3e5f0696a74769998e42d7f3851d0 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:22:23 -0700 Subject: [PATCH 03/10] test(streaming): cover SDK 3 transport errors on finished streams and file downloads A finished chat stream whose connection drops before [DONE] should end cleanly on both the httpx and httpx2 transports, a mid-stream timeout should fall back as a Timeout, and file_content(stream=True) should raise litellm errors instead of raw transport errors --- .../test_streaming_handler.py | 127 ++++++++++++++++++ tests/unit/llms/openai/test_openai.py | 81 ++++++++++- .../test_openai_file_content_streaming.py | 88 +++++++++++- 3 files changed, 293 insertions(+), 3 deletions(-) diff --git a/tests/unit/litellm_core_utils/test_streaming_handler.py b/tests/unit/litellm_core_utils/test_streaming_handler.py index 68aabc3c01e..c4a55408179 100644 --- a/tests/unit/litellm_core_utils/test_streaming_handler.py +++ b/tests/unit/litellm_core_utils/test_streaming_handler.py @@ -11,6 +11,7 @@ from typing import AsyncIterator, Callable, Final, Iterator, Literal, NoReturn, from unittest.mock import AsyncMock, MagicMock, Mock, patch import httpx +import openai import pytest from openai.types.chat import ChatCompletionChunk from pydantic import BaseModel @@ -4586,6 +4587,132 @@ async def test_transport_read_error_before_finish_reason_raises(logging_obj: Log assert fabricated_finish_reasons == [] +_SDK3_CHAT_REQUEST_URL: Final = "https://api.openai.com/v1/chat/completions" + + +def _raised_from(error: Exception, cause: Exception) -> Exception: + try: + raise error from cause + except Exception as raised: + return raised + + +def _httpx2_read_error() -> Exception: + return pytest.importorskip("httpx2").ReadError("peer closed connection") + + +def _httpx2_protocol_error() -> Exception: + return pytest.importorskip("httpx2").RemoteProtocolError("peer closed connection without sending complete body") + + +def _sdk_wrapped_read_error() -> Exception: + httpx2: Final = pytest.importorskip("httpx2") + return _raised_from( + openai.APIConnectionError(request=httpx2.Request("POST", _SDK3_CHAT_REQUEST_URL)), + httpx2.ReadError("peer closed connection"), + ) + + +def _httpx2_read_timeout() -> Exception: + return pytest.importorskip("httpx2").ReadTimeout("timed out") + + +def _sdk_wrapped_read_timeout() -> Exception: + httpx2: Final = pytest.importorskip("httpx2") + return _raised_from( + openai.APITimeoutError(request=httpx2.Request("POST", _SDK3_CHAT_REQUEST_URL)), + httpx2.ReadTimeout("timed out"), + ) + + +_SDK3_DROPPED_CONNECTIONS: Final = pytest.mark.parametrize( + "dropped_connection", + (_httpx2_read_error, _httpx2_protocol_error, _sdk_wrapped_read_error), + ids=("httpx2_read_error", "httpx2_protocol_error", "sdk_wrapped_read_error"), +) +_SYNC_AND_ASYNC: Final = pytest.mark.parametrize("sync_mode", (True, False), ids=("sync", "async")) + + +def _chunks_then_failure(chunks: Sequence[ModelResponseStream], failure: Exception) -> Iterator[ModelResponseStream]: + yield from chunks + raise failure + + +def _openai_stream_over( + chunks: Sequence[ModelResponseStream], failure: Exception, sync_mode: bool, logging_obj: Logging +) -> CustomStreamWrapper: + stream: Final = _chunks_then_failure(chunks, failure) + return CustomStreamWrapper( + completion_stream=stream if sync_mode else _as_async(stream), + model="openai/stub-model", + custom_llm_provider="openai", + logging_obj=logging_obj, + ) + + +async def _collect(wrapper: CustomStreamWrapper, sync_mode: bool) -> list[ModelResponseStream]: + if sync_mode: + return list(wrapper) + return [chunk async for chunk in wrapper] + + +@pytest.mark.asyncio +@_SYNC_AND_ASYNC +@_SDK3_DROPPED_CONNECTIONS +async def test_sdk3_dropped_connection_after_finish_reason_ends_the_stream_cleanly( + logging_obj: Logging, sync_mode: bool, dropped_connection: Callable[[], Exception] +): + wrapper: Final = _openai_stream_over( + (_reset_test_chunk(content="Hello"), _reset_test_chunk(finish_reason="stop")), + dropped_connection(), + sync_mode, + logging_obj, + ) + + chunks: Final = await _collect(wrapper, sync_mode) + + assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks if chunk.choices) == "Hello" + assert [chunk.choices[0].finish_reason for chunk in chunks if chunk.choices[0].finish_reason] == ["stop"] + + +@pytest.mark.asyncio +@_SYNC_AND_ASYNC +@_SDK3_DROPPED_CONNECTIONS +async def test_sdk3_dropped_connection_before_finish_reason_raises_mid_stream_fallback( + logging_obj: Logging, sync_mode: bool, dropped_connection: Callable[[], Exception] +): + wrapper: Final = _openai_stream_over( + (_reset_test_chunk(content="Hel"),), dropped_connection(), sync_mode, logging_obj + ) + + with pytest.raises(MidStreamFallbackError) as excinfo: + await _collect(wrapper, sync_mode) + + assert excinfo.value.is_pre_first_chunk is False + assert excinfo.value.generated_content == "Hel" + assert isinstance(excinfo.value.original_exception, litellm.APIConnectionError) + + +@pytest.mark.asyncio +@_SYNC_AND_ASYNC +@pytest.mark.parametrize( + "read_timeout", (_httpx2_read_timeout, _sdk_wrapped_read_timeout), ids=("httpx2", "sdk_wrapped_httpx2") +) +@pytest.mark.parametrize("finished", (False, True), ids=("before_finish_reason", "after_finish_reason")) +async def test_sdk3_read_timeout_mid_stream_falls_back_as_a_timeout( + logging_obj: Logging, sync_mode: bool, read_timeout: Callable[[], Exception], finished: bool +): + chunks: Final = (_reset_test_chunk(content="Hel"), _reset_test_chunk(finish_reason="stop")) + wrapper: Final = _openai_stream_over(chunks if finished else chunks[:1], read_timeout(), sync_mode, logging_obj) + + with pytest.raises(MidStreamFallbackError) as excinfo: + await _collect(wrapper, sync_mode) + + assert excinfo.value.is_pre_first_chunk is False + assert isinstance(excinfo.value.original_exception, litellm.Timeout) + assert excinfo.value.original_exception.status_code == 408 + + def test_openai_custom_tool_call_stream_deltas_survive_conversion(logging_obj: Logging): """ Regression test: OpenAI chat completions custom tool calls stream as diff --git a/tests/unit/llms/openai/test_openai.py b/tests/unit/llms/openai/test_openai.py index 11d7c7d6d3c..2fab1286e51 100644 --- a/tests/unit/llms/openai/test_openai.py +++ b/tests/unit/llms/openai/test_openai.py @@ -2,7 +2,7 @@ import asyncio import importlib import json import os -from collections.abc import Mapping +from collections.abc import AsyncIterator, Iterator, Mapping from types import ModuleType from typing import Final, Literal, Protocol from unittest.mock import MagicMock, Mock, patch @@ -32,6 +32,7 @@ from litellm.llms.openai.openai import ( from litellm.types.utils import ( ImageResponse, ModelResponse, + ModelResponseStream, PromptTokensDetails, ) from litellm.litellm_core_utils.logging_worker import GLOBAL_LOGGING_WORKER @@ -441,6 +442,84 @@ async def test_acompletion_streams_text_deltas_over_injected_transport( assert last_finish == "stop" +def _sse_event(delta: Mapping[str, str], finish: str | None) -> bytes: + body: Final = { + "id": "chatcmpl-dropped", + "object": "chat.completion.chunk", + "created": 0, + "model": "gpt-5.6", + "choices": [{"index": 0, "delta": delta, "finish_reason": finish}], + } + return f"data: {json.dumps(body)}\n\n".encode() + + +_FINISHED_EVENTS_WITHOUT_DONE: Final = ( + _sse_event({"role": "assistant", "content": "Hel"}, None), + _sse_event({"content": "lo"}, "stop"), +) + + +def _finished_then_dropped_transport(http_backend: ModuleType) -> object: + class FinishedThenDropped(http_backend.SyncByteStream, http_backend.AsyncByteStream): + def __iter__(self) -> Iterator[bytes]: + yield from _FINISHED_EVENTS_WITHOUT_DONE + raise http_backend.ReadError("peer closed the connection before [DONE]") + + async def __aiter__(self) -> AsyncIterator[bytes]: + for event in _FINISHED_EVENTS_WITHOUT_DONE: + yield event + raise http_backend.ReadError("peer closed the connection before [DONE]") + + return http_backend.MockTransport( + lambda _request: http_backend.Response( + 200, headers={"content-type": "text/event-stream"}, stream=FinishedThenDropped() + ) + ) + + +def _streamed_text_and_finish_reasons(chunks: list[ModelResponseStream]) -> tuple[str, list[str]]: + text: Final = "".join(part.choices[0].delta.content or "" for part in chunks if part.choices) + finish_reasons: Final = [part.choices[0].finish_reason for part in chunks if part.choices and part.choices[0].finish_reason] + return text, finish_reasons + + +@pytest.mark.asyncio +async def test_acompletion_ends_a_finished_stream_cleanly_when_the_connection_drops_before_done( + http_backend: ModuleType, +): + async with http_backend.AsyncClient(transport=_finished_then_dropped_transport(http_backend)) as http_client: + stream: Final = await litellm.acompletion( + model="openai/gpt-5.6", + client=AsyncOpenAI(api_key="transport-only", http_client=http_client), + messages=[{"role": "user", "content": "dropped-stream-request"}], + stream=True, + num_retries=0, + max_retries=0, + ) + + async def drain() -> list[ModelResponseStream]: + return [part async for part in stream] + + chunks: Final = await asyncio.wait_for(drain(), timeout=10) + + assert _streamed_text_and_finish_reasons(chunks) == ("Hello", ["stop"]) + + +def test_completion_ends_a_finished_stream_cleanly_when_the_connection_drops_before_done(http_backend: ModuleType): + with http_backend.Client(transport=_finished_then_dropped_transport(http_backend)) as http_client: + stream: Final = litellm.completion( + model="openai/gpt-5.6", + client=OpenAI(api_key="transport-only", http_client=http_client), + messages=[{"role": "user", "content": "dropped-stream-request"}], + stream=True, + num_retries=0, + max_retries=0, + ) + chunks: Final = list(stream) + + assert _streamed_text_and_finish_reasons(chunks) == ("Hello", ["stop"]) + + @pytest.mark.asyncio async def test_acompletion_streams_tool_call_arguments_over_injected_transport(http_backend: ModuleType): outbound: Final = asyncio.Queue() diff --git a/tests/unit/llms/openai/test_openai_file_content_streaming.py b/tests/unit/llms/openai/test_openai_file_content_streaming.py index 466918267f6..1455fd45e67 100644 --- a/tests/unit/llms/openai/test_openai_file_content_streaming.py +++ b/tests/unit/llms/openai/test_openai_file_content_streaming.py @@ -1,6 +1,13 @@ -import pytest -from typing import AsyncIterator, Iterator, cast +import importlib +from types import ModuleType +import pytest +from typing import AsyncIterator, Final, Iterator, cast + +from openai import AsyncOpenAI, OpenAI +from openai._types import Response as SDKResponse + +import litellm from litellm.files import main as files_main from litellm.files.streaming import FileContentStreamingResponse from litellm.files.types import FileContentStreamingResult @@ -323,3 +330,80 @@ def test_file_content_streaming_passes_exception_to_context_manager_exit(): assert response_cm.exc_info[0] is RuntimeError assert response_cm.exc_info[1] is exc_info.value assert response_cm.exc_info[2] is not None + + +@pytest.fixture(params=("httpx", "sdk")) +def http_backend(request: pytest.FixtureRequest) -> ModuleType: + return importlib.import_module("httpx" if request.param == "httpx" else SDKResponse.__module__) + + +_TRANSPORT_FAILURES: Final = pytest.mark.parametrize( + ("failure_name", "public_error"), + ( + ("ReadTimeout", litellm.Timeout), + ("ReadError", litellm.APIConnectionError), + ("RemoteProtocolError", litellm.APIConnectionError), + ), +) + + +def _body_then_failure_transport(http_backend: ModuleType, failure: Exception) -> object: + class BodyThenFailure(http_backend.SyncByteStream, http_backend.AsyncByteStream): + def __iter__(self) -> Iterator[bytes]: + yield b"hello" + raise failure + + async def __aiter__(self) -> AsyncIterator[bytes]: + yield b"hello" + raise failure + + return http_backend.MockTransport( + lambda _request: http_backend.Response( + 200, headers={"content-type": "application/octet-stream"}, stream=BodyThenFailure() + ) + ) + + +@_TRANSPORT_FAILURES +def test_file_content_stream_raises_litellm_errors_when_the_transport_fails_mid_body( + http_backend: ModuleType, failure_name: str, public_error: type[Exception] +): + failure: Final = getattr(http_backend, failure_name)("transport failed mid-body") + + with http_backend.Client(transport=_body_then_failure_transport(http_backend, failure)) as http_client: + result: Final = litellm.file_content( + file_id="file-abc123", + custom_llm_provider="openai", + client=OpenAI(api_key="transport-only", http_client=http_client), + stream=True, + chunk_size=len(b"hello"), + ) + stream: Final = cast(Iterator[bytes], result.stream_iterator) + assert next(stream) == b"hello" + with pytest.raises(public_error) as excinfo: + next(stream) + + assert excinfo.value.__cause__ is failure + + +@pytest.mark.asyncio +@_TRANSPORT_FAILURES +async def test_afile_content_stream_raises_litellm_errors_when_the_transport_fails_mid_body( + http_backend: ModuleType, failure_name: str, public_error: type[Exception] +): + failure: Final = getattr(http_backend, failure_name)("transport failed mid-body") + + async with http_backend.AsyncClient(transport=_body_then_failure_transport(http_backend, failure)) as http_client: + result: Final = await litellm.afile_content( + file_id="file-abc123", + custom_llm_provider="openai", + client=AsyncOpenAI(api_key="transport-only", http_client=http_client), + stream=True, + chunk_size=len(b"hello"), + ) + stream: Final = cast(AsyncIterator[bytes], result.stream_iterator) + assert await stream.__anext__() == b"hello" + with pytest.raises(public_error) as excinfo: + await stream.__anext__() + + assert excinfo.value.__cause__ is failure From 53312cb5f2d6876d0345d34b59e321cadb828706 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:44:47 -0700 Subject: [PATCH 04/10] fix(streaming): end finished streams cleanly on SDK 3 transport errors and map file download errors openai SDK 3 reads over httpx2 and, from 3.25, wraps transport errors in APIConnectionError or APITimeoutError. The stream wrapper only knew httpx, so a connection drop after finish_reason raised MidStreamFallbackError and a read timeout fell back as a 500. Transport errors are now classified across httpx, httpx2 and the SDK's explicit cause chain, a drop after finish_reason ends the stream on both the sync and async paths, and file_content(stream=True) raises litellm.Timeout or litellm.APIConnectionError instead of raw transport errors --- litellm/files/streaming.py | 33 ++++++- .../exception_mapping_utils.py | 3 +- .../litellm_core_utils/streaming_handler.py | 62 +++++++------ litellm/llms/custom_httpx/transport_errors.py | 54 ++++++++++++ .../custom_httpx/test_transport_errors.py | 86 +++++++++++++++++++ 5 files changed, 205 insertions(+), 33 deletions(-) create mode 100644 litellm/llms/custom_httpx/transport_errors.py create mode 100644 tests/unit/llms/custom_httpx/test_transport_errors.py diff --git a/litellm/files/streaming.py b/litellm/files/streaming.py index 460050872f4..6956d3baf32 100644 --- a/litellm/files/streaming.py +++ b/litellm/files/streaming.py @@ -5,7 +5,9 @@ from typing import TYPE_CHECKING, Any, Final, Optional, cast import anyio +from litellm.exceptions import APIConnectionError, Timeout from litellm.files.types import FileContentProvider +from litellm.llms.custom_httpx.transport_errors import TransportErrorKind, chained_transport_error_kind if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import ( @@ -59,8 +61,11 @@ class FileContentStreamingResponse: self._log_success_sync() raise except Exception as e: - self._log_failure_sync(e) - raise + public_error: Final = self._public_error(e) + self._log_failure_sync(public_error) + if public_error is e: + raise + raise public_error from e def __aiter__(self) -> "FileContentStreamingResponse": if not hasattr(self.stream_iterator, "__anext__"): @@ -77,8 +82,28 @@ class FileContentStreamingResponse: await self._log_success_async() raise except Exception as e: - await self._log_failure_async(e) - raise + public_error: Final = self._public_error(e) + await self._log_failure_async(public_error) + if public_error is e: + raise + raise public_error from e + + def _public_error(self, error: Exception) -> Exception: + match chained_transport_error_kind(error): + case TransportErrorKind.TIMEOUT: + return Timeout( + message=f"timed out reading file content: {error}", + model=self.model, + llm_provider=self.custom_llm_provider, + ) + case TransportErrorKind.DROPPED_CONNECTION: + return APIConnectionError( + message=f"connection dropped while reading file content: {error}", + llm_provider=self.custom_llm_provider, + model=self.model, + ) + case None: + return error async def aclose(self) -> None: if self._close_completed: diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index 1f49cc2c4bb..e7f2f5dc359 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -18,6 +18,7 @@ from litellm.litellm_core_utils.bug_report import ( ) from litellm.litellm_core_utils.core_helpers import as_str_mapping from litellm.litellm_core_utils.secret_redaction import redact_string as redact_secret_string +from litellm.llms.custom_httpx.transport_errors import TransportErrorKind, transport_error_classes from litellm.types.utils import LlmProviders from ..exceptions import ( @@ -2551,7 +2552,7 @@ def exception_type( ################################################################################ if ( - isinstance(original_exception, httpx.TimeoutException) + isinstance(original_exception, transport_error_classes(TransportErrorKind.TIMEOUT)) or "Request Timeout Error" in error_str or "Request timed out" in error_str or "Timed out generating response" in error_str diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index 6ec426d6da5..045943fdd88 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -35,6 +35,7 @@ from litellm.litellm_core_utils.model_response_utils import ( ) from litellm.litellm_core_utils.redact_messages import LiteLLMLoggingObject from litellm.litellm_core_utils.thread_pool_executor import executor +from litellm.llms.custom_httpx.transport_errors import TransportErrorKind, chained_transport_error_kind from litellm.llms.ollama.common_utils import OllamaResponseIdNormalizer from litellm.types.llms.base import LiteLLMBaseModel from litellm.types.llms.openai import OpenAIChatCompletionChunk @@ -54,7 +55,7 @@ from litellm.types.utils import ( ) from litellm.types.utils import GenericStreamingChunk as GChunk -from ..exceptions import OpenAIError, Timeout +from ..exceptions import OpenAIError from .core_helpers import map_finish_reason, process_response_headers from .exception_mapping_utils import exception_type, with_deployment_api_base from .llm_response_utils.get_api_base import get_api_base @@ -122,12 +123,6 @@ def _delta_from_upstream(upstream_delta: BaseModel | None) -> Delta | None: return None -def _is_transport_timeout(error: BaseException) -> bool: - return isinstance(error, httpx.TimeoutException) or ( - isinstance(error, Timeout) and isinstance(error.__cause__, httpx.TimeoutException) - ) - - def _skips_stream_fallback(status_code: int | None) -> bool: return status_code is not None and 400 <= status_code < 500 and status_code != 429 @@ -2000,7 +1995,7 @@ class CustomStreamWrapper: ): chunk = self.completion_stream else: - chunk = next(self.completion_stream) + chunk = self._next_sync_chunk() if chunk is not None and chunk != b"": print_verbose( f"PROCESSED CHUNK PRE CHUNK CREATOR: {chunk.decode('utf-8', errors='replace') if isinstance(chunk, bytes) else chunk}; custom_llm_provider: {self.custom_llm_provider}" @@ -2162,6 +2157,17 @@ class CustomStreamWrapper: self.close() self._handle_stream_fallback_error(e) + def _next_sync_chunk(self) -> object: + try: + return next(self.completion_stream) + except Exception as e: + if ( + self.received_finish_reason is None + or chained_transport_error_kind(e) is not TransportErrorKind.DROPPED_CONNECTION + ): + raise + raise StopIteration from e + def fetch_sync_stream(self): if self.completion_stream is None and self.make_call is not None: # Call make_call to get the completion stream @@ -2282,26 +2288,26 @@ class CustomStreamWrapper: return processed_chunk except (StopAsyncIteration, StopIteration): return await self._finalize_completed_stream(cache_hit=cache_hit) - except httpx.TimeoutException as e: # if httpx read timeout error occues - traceback_exception = traceback.format_exc() - ## ADD DEBUG INFORMATION - E.G. LITELLM REQUEST TIMEOUT - traceback_exception += f"\nLiteLLM Default Request Timeout - {litellm.request_timeout}" - if self.logging_obj is not None: - self._record_partial_usage_for_failure() - ## LOGGING - self._failure_logging = asyncio.create_task( - self.logging_obj.dispatch_failure_handlers(e, traceback_exception, prefer_async_handlers=True) - ) - await self.aclose() - self._handle_stream_fallback_error(e) - except (httpx.ReadError, httpx.RemoteProtocolError) as e: - if self.received_finish_reason is None: - await self.aclose() - self._log_stream_failure_and_raise(e) - return await self._finalize_completed_stream(cache_hit=cache_hit) except Exception as e: - await self.aclose() - self._log_stream_failure_and_raise(e) + match chained_transport_error_kind(e): + case TransportErrorKind.DROPPED_CONNECTION if self.received_finish_reason is not None: + return await self._finalize_completed_stream(cache_hit=cache_hit) + case TransportErrorKind.TIMEOUT: + traceback_exception: Final = ( + f"{traceback.format_exc()}\nLiteLLM Default Request Timeout - {litellm.request_timeout}" + ) + if self.logging_obj is not None: + self._record_partial_usage_for_failure() + self._failure_logging = asyncio.create_task( + self.logging_obj.dispatch_failure_handlers( + e, traceback_exception, prefer_async_handlers=True + ) + ) + await self.aclose() + self._handle_stream_fallback_error(e) + case _: + await self.aclose() + self._log_stream_failure_and_raise(e) async def _finalize_completed_stream(self, cache_hit: bool) -> "ModelResponseStream": if self.sent_last_chunk is True: @@ -2556,7 +2562,7 @@ class CustomStreamWrapper: # Raise non-retriable client errors directly (skip fallback). # Exception: 429 (rate-limit) IS retriable/transient — allow it # through so the Router can switch to a different model group. - if not _is_transport_timeout(e) and ( + if chained_transport_error_kind(e) is not TransportErrorKind.TIMEOUT and ( _skips_stream_fallback(mapped_status_code) or _skips_stream_fallback(original_status_code) ): raise mapped_exception diff --git a/litellm/llms/custom_httpx/transport_errors.py b/litellm/llms/custom_httpx/transport_errors.py new file mode 100644 index 00000000000..f3a2b04a4ea --- /dev/null +++ b/litellm/llms/custom_httpx/transport_errors.py @@ -0,0 +1,54 @@ +import sys +from collections.abc import Iterator +from enum import Enum +from typing import Final, TypeAlias + +import httpx + + +class TransportErrorKind(Enum): + TIMEOUT = "timeout" + DROPPED_CONNECTION = "dropped_connection" + + +_ErrorTable: TypeAlias = tuple[tuple[type[BaseException], TransportErrorKind], ...] + +_HTTPX_ERRORS: Final[_ErrorTable] = ( + (httpx.TimeoutException, TransportErrorKind.TIMEOUT), + (httpx.ReadError, TransportErrorKind.DROPPED_CONNECTION), + (httpx.RemoteProtocolError, TransportErrorKind.DROPPED_CONNECTION), +) +_MAX_CAUSE_DEPTH: Final = 16 + + +def _transport_errors() -> _ErrorTable: + if "httpx2" not in sys.modules: + return _HTTPX_ERRORS + import httpx2 + + return ( + *_HTTPX_ERRORS, + (httpx2.TimeoutException, TransportErrorKind.TIMEOUT), + (httpx2.ReadError, TransportErrorKind.DROPPED_CONNECTION), + (httpx2.RemoteProtocolError, TransportErrorKind.DROPPED_CONNECTION), + ) + + +def transport_error_kind(error: object) -> TransportErrorKind | None: + return next((kind for error_class, kind in _transport_errors() if isinstance(error, error_class)), None) + + +def transport_error_classes(kind: TransportErrorKind) -> tuple[type[BaseException], ...]: + return tuple(error_class for error_class, error_kind in _transport_errors() if error_kind is kind) + + +def _explicit_causes(error: BaseException | None, depth: int) -> Iterator[BaseException]: + if error is None or depth == 0: + return + yield error + yield from _explicit_causes(error.__cause__, depth - 1) + + +def chained_transport_error_kind(error: BaseException) -> TransportErrorKind | None: + kinds: Final = (transport_error_kind(cause) for cause in _explicit_causes(error, _MAX_CAUSE_DEPTH)) + return next((kind for kind in kinds if kind is not None), None) diff --git a/tests/unit/llms/custom_httpx/test_transport_errors.py b/tests/unit/llms/custom_httpx/test_transport_errors.py new file mode 100644 index 00000000000..92aad0318c8 --- /dev/null +++ b/tests/unit/llms/custom_httpx/test_transport_errors.py @@ -0,0 +1,86 @@ +from typing import Final + +import httpx +import openai +import pytest + +from litellm.llms.custom_httpx.transport_errors import ( + TransportErrorKind, + chained_transport_error_kind, + transport_error_classes, + transport_error_kind, +) + +REQUEST: Final = httpx.Request("POST", "https://api.openai.com/v1/chat/completions") + + +def _raised_from(error: Exception, cause: Exception) -> Exception: + try: + raise error from cause + except Exception as raised: + return raised + + +def _raised_while_handling(error: Exception, handled: Exception) -> Exception: + try: + try: + raise handled + except Exception: + raise error + except Exception as raised: + return raised + + +@pytest.mark.parametrize("module_name", ("httpx", "httpx2")) +@pytest.mark.parametrize( + ("class_name", "expected"), + ( + ("ReadTimeout", TransportErrorKind.TIMEOUT), + ("ConnectTimeout", TransportErrorKind.TIMEOUT), + ("ReadError", TransportErrorKind.DROPPED_CONNECTION), + ("RemoteProtocolError", TransportErrorKind.DROPPED_CONNECTION), + ("ConnectError", None), + ("WriteError", None), + ), +) +def test_transport_error_kind_matches_httpx_and_httpx2_alike( + module_name: str, class_name: str, expected: TransportErrorKind | None +): + module: Final = pytest.importorskip(module_name) + error: Final = getattr(module, class_name)("transport failure") + + assert transport_error_kind(error) is expected + assert chained_transport_error_kind(error) is expected + assert all(isinstance(error, transport_error_classes(kind)) is (kind is expected) for kind in TransportErrorKind) + + +def test_transport_error_kind_ignores_values_that_are_not_transport_errors(): + assert transport_error_kind(ValueError("decoder failure")) is None + assert transport_error_kind({"error": "not an exception"}) is None + + +def test_chained_kind_reads_the_transport_error_an_sdk_error_was_raised_from(): + httpx2: Final = pytest.importorskip("httpx2") + dropped: Final = _raised_from(openai.APIConnectionError(request=REQUEST), httpx2.ReadError("peer closed")) + timed_out: Final = _raised_from(openai.APITimeoutError(request=REQUEST), httpx2.ReadTimeout("timed out")) + twice_wrapped: Final = _raised_from(ValueError("outer"), timed_out) + + assert transport_error_kind(dropped) is None + assert chained_transport_error_kind(dropped) is TransportErrorKind.DROPPED_CONNECTION + assert chained_transport_error_kind(timed_out) is TransportErrorKind.TIMEOUT + assert chained_transport_error_kind(twice_wrapped) is TransportErrorKind.TIMEOUT + assert chained_transport_error_kind(openai.APIConnectionError(request=REQUEST)) is None + + +def test_chained_kind_ignores_an_error_raised_while_handling_a_transport_error(): + contextual: Final = _raised_while_handling(ValueError("bug in a handler"), httpx.ReadError("connection reset")) + + assert contextual.__context__ is not None + assert chained_transport_error_kind(contextual) is None + + +def test_chained_kind_stops_on_a_cause_cycle(): + cyclic: Final = ValueError("caused by itself") + cyclic.__cause__ = cyclic + + assert chained_transport_error_kind(cyclic) is None From 2d862de42d866368191bacc980e66157f81ee742 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:22:23 -0700 Subject: [PATCH 05/10] test(streaming): cover SDK 3 transport errors on finished streams and file downloads A finished chat stream whose connection drops before [DONE] should end cleanly on both the httpx and httpx2 transports, a mid-stream timeout should fall back as a Timeout, and file_content(stream=True) should raise litellm errors instead of raw transport errors --- .../test_streaming_handler.py | 127 ++++++++++++++++++ tests/unit/llms/openai/test_openai.py | 81 ++++++++++- .../test_openai_file_content_streaming.py | 88 +++++++++++- 3 files changed, 293 insertions(+), 3 deletions(-) diff --git a/tests/unit/litellm_core_utils/test_streaming_handler.py b/tests/unit/litellm_core_utils/test_streaming_handler.py index 68aabc3c01e..c4a55408179 100644 --- a/tests/unit/litellm_core_utils/test_streaming_handler.py +++ b/tests/unit/litellm_core_utils/test_streaming_handler.py @@ -11,6 +11,7 @@ from typing import AsyncIterator, Callable, Final, Iterator, Literal, NoReturn, from unittest.mock import AsyncMock, MagicMock, Mock, patch import httpx +import openai import pytest from openai.types.chat import ChatCompletionChunk from pydantic import BaseModel @@ -4586,6 +4587,132 @@ async def test_transport_read_error_before_finish_reason_raises(logging_obj: Log assert fabricated_finish_reasons == [] +_SDK3_CHAT_REQUEST_URL: Final = "https://api.openai.com/v1/chat/completions" + + +def _raised_from(error: Exception, cause: Exception) -> Exception: + try: + raise error from cause + except Exception as raised: + return raised + + +def _httpx2_read_error() -> Exception: + return pytest.importorskip("httpx2").ReadError("peer closed connection") + + +def _httpx2_protocol_error() -> Exception: + return pytest.importorskip("httpx2").RemoteProtocolError("peer closed connection without sending complete body") + + +def _sdk_wrapped_read_error() -> Exception: + httpx2: Final = pytest.importorskip("httpx2") + return _raised_from( + openai.APIConnectionError(request=httpx2.Request("POST", _SDK3_CHAT_REQUEST_URL)), + httpx2.ReadError("peer closed connection"), + ) + + +def _httpx2_read_timeout() -> Exception: + return pytest.importorskip("httpx2").ReadTimeout("timed out") + + +def _sdk_wrapped_read_timeout() -> Exception: + httpx2: Final = pytest.importorskip("httpx2") + return _raised_from( + openai.APITimeoutError(request=httpx2.Request("POST", _SDK3_CHAT_REQUEST_URL)), + httpx2.ReadTimeout("timed out"), + ) + + +_SDK3_DROPPED_CONNECTIONS: Final = pytest.mark.parametrize( + "dropped_connection", + (_httpx2_read_error, _httpx2_protocol_error, _sdk_wrapped_read_error), + ids=("httpx2_read_error", "httpx2_protocol_error", "sdk_wrapped_read_error"), +) +_SYNC_AND_ASYNC: Final = pytest.mark.parametrize("sync_mode", (True, False), ids=("sync", "async")) + + +def _chunks_then_failure(chunks: Sequence[ModelResponseStream], failure: Exception) -> Iterator[ModelResponseStream]: + yield from chunks + raise failure + + +def _openai_stream_over( + chunks: Sequence[ModelResponseStream], failure: Exception, sync_mode: bool, logging_obj: Logging +) -> CustomStreamWrapper: + stream: Final = _chunks_then_failure(chunks, failure) + return CustomStreamWrapper( + completion_stream=stream if sync_mode else _as_async(stream), + model="openai/stub-model", + custom_llm_provider="openai", + logging_obj=logging_obj, + ) + + +async def _collect(wrapper: CustomStreamWrapper, sync_mode: bool) -> list[ModelResponseStream]: + if sync_mode: + return list(wrapper) + return [chunk async for chunk in wrapper] + + +@pytest.mark.asyncio +@_SYNC_AND_ASYNC +@_SDK3_DROPPED_CONNECTIONS +async def test_sdk3_dropped_connection_after_finish_reason_ends_the_stream_cleanly( + logging_obj: Logging, sync_mode: bool, dropped_connection: Callable[[], Exception] +): + wrapper: Final = _openai_stream_over( + (_reset_test_chunk(content="Hello"), _reset_test_chunk(finish_reason="stop")), + dropped_connection(), + sync_mode, + logging_obj, + ) + + chunks: Final = await _collect(wrapper, sync_mode) + + assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks if chunk.choices) == "Hello" + assert [chunk.choices[0].finish_reason for chunk in chunks if chunk.choices[0].finish_reason] == ["stop"] + + +@pytest.mark.asyncio +@_SYNC_AND_ASYNC +@_SDK3_DROPPED_CONNECTIONS +async def test_sdk3_dropped_connection_before_finish_reason_raises_mid_stream_fallback( + logging_obj: Logging, sync_mode: bool, dropped_connection: Callable[[], Exception] +): + wrapper: Final = _openai_stream_over( + (_reset_test_chunk(content="Hel"),), dropped_connection(), sync_mode, logging_obj + ) + + with pytest.raises(MidStreamFallbackError) as excinfo: + await _collect(wrapper, sync_mode) + + assert excinfo.value.is_pre_first_chunk is False + assert excinfo.value.generated_content == "Hel" + assert isinstance(excinfo.value.original_exception, litellm.APIConnectionError) + + +@pytest.mark.asyncio +@_SYNC_AND_ASYNC +@pytest.mark.parametrize( + "read_timeout", (_httpx2_read_timeout, _sdk_wrapped_read_timeout), ids=("httpx2", "sdk_wrapped_httpx2") +) +@pytest.mark.parametrize("finished", (False, True), ids=("before_finish_reason", "after_finish_reason")) +async def test_sdk3_read_timeout_mid_stream_falls_back_as_a_timeout( + logging_obj: Logging, sync_mode: bool, read_timeout: Callable[[], Exception], finished: bool +): + chunks: Final = (_reset_test_chunk(content="Hel"), _reset_test_chunk(finish_reason="stop")) + wrapper: Final = _openai_stream_over(chunks if finished else chunks[:1], read_timeout(), sync_mode, logging_obj) + + with pytest.raises(MidStreamFallbackError) as excinfo: + await _collect(wrapper, sync_mode) + + assert excinfo.value.is_pre_first_chunk is False + assert isinstance(excinfo.value.original_exception, litellm.Timeout) + assert excinfo.value.original_exception.status_code == 408 + + def test_openai_custom_tool_call_stream_deltas_survive_conversion(logging_obj: Logging): """ Regression test: OpenAI chat completions custom tool calls stream as diff --git a/tests/unit/llms/openai/test_openai.py b/tests/unit/llms/openai/test_openai.py index 11d7c7d6d3c..2fab1286e51 100644 --- a/tests/unit/llms/openai/test_openai.py +++ b/tests/unit/llms/openai/test_openai.py @@ -2,7 +2,7 @@ import asyncio import importlib import json import os -from collections.abc import Mapping +from collections.abc import AsyncIterator, Iterator, Mapping from types import ModuleType from typing import Final, Literal, Protocol from unittest.mock import MagicMock, Mock, patch @@ -32,6 +32,7 @@ from litellm.llms.openai.openai import ( from litellm.types.utils import ( ImageResponse, ModelResponse, + ModelResponseStream, PromptTokensDetails, ) from litellm.litellm_core_utils.logging_worker import GLOBAL_LOGGING_WORKER @@ -441,6 +442,84 @@ async def test_acompletion_streams_text_deltas_over_injected_transport( assert last_finish == "stop" +def _sse_event(delta: Mapping[str, str], finish: str | None) -> bytes: + body: Final = { + "id": "chatcmpl-dropped", + "object": "chat.completion.chunk", + "created": 0, + "model": "gpt-5.6", + "choices": [{"index": 0, "delta": delta, "finish_reason": finish}], + } + return f"data: {json.dumps(body)}\n\n".encode() + + +_FINISHED_EVENTS_WITHOUT_DONE: Final = ( + _sse_event({"role": "assistant", "content": "Hel"}, None), + _sse_event({"content": "lo"}, "stop"), +) + + +def _finished_then_dropped_transport(http_backend: ModuleType) -> object: + class FinishedThenDropped(http_backend.SyncByteStream, http_backend.AsyncByteStream): + def __iter__(self) -> Iterator[bytes]: + yield from _FINISHED_EVENTS_WITHOUT_DONE + raise http_backend.ReadError("peer closed the connection before [DONE]") + + async def __aiter__(self) -> AsyncIterator[bytes]: + for event in _FINISHED_EVENTS_WITHOUT_DONE: + yield event + raise http_backend.ReadError("peer closed the connection before [DONE]") + + return http_backend.MockTransport( + lambda _request: http_backend.Response( + 200, headers={"content-type": "text/event-stream"}, stream=FinishedThenDropped() + ) + ) + + +def _streamed_text_and_finish_reasons(chunks: list[ModelResponseStream]) -> tuple[str, list[str]]: + text: Final = "".join(part.choices[0].delta.content or "" for part in chunks if part.choices) + finish_reasons: Final = [part.choices[0].finish_reason for part in chunks if part.choices and part.choices[0].finish_reason] + return text, finish_reasons + + +@pytest.mark.asyncio +async def test_acompletion_ends_a_finished_stream_cleanly_when_the_connection_drops_before_done( + http_backend: ModuleType, +): + async with http_backend.AsyncClient(transport=_finished_then_dropped_transport(http_backend)) as http_client: + stream: Final = await litellm.acompletion( + model="openai/gpt-5.6", + client=AsyncOpenAI(api_key="transport-only", http_client=http_client), + messages=[{"role": "user", "content": "dropped-stream-request"}], + stream=True, + num_retries=0, + max_retries=0, + ) + + async def drain() -> list[ModelResponseStream]: + return [part async for part in stream] + + chunks: Final = await asyncio.wait_for(drain(), timeout=10) + + assert _streamed_text_and_finish_reasons(chunks) == ("Hello", ["stop"]) + + +def test_completion_ends_a_finished_stream_cleanly_when_the_connection_drops_before_done(http_backend: ModuleType): + with http_backend.Client(transport=_finished_then_dropped_transport(http_backend)) as http_client: + stream: Final = litellm.completion( + model="openai/gpt-5.6", + client=OpenAI(api_key="transport-only", http_client=http_client), + messages=[{"role": "user", "content": "dropped-stream-request"}], + stream=True, + num_retries=0, + max_retries=0, + ) + chunks: Final = list(stream) + + assert _streamed_text_and_finish_reasons(chunks) == ("Hello", ["stop"]) + + @pytest.mark.asyncio async def test_acompletion_streams_tool_call_arguments_over_injected_transport(http_backend: ModuleType): outbound: Final = asyncio.Queue() diff --git a/tests/unit/llms/openai/test_openai_file_content_streaming.py b/tests/unit/llms/openai/test_openai_file_content_streaming.py index 466918267f6..1455fd45e67 100644 --- a/tests/unit/llms/openai/test_openai_file_content_streaming.py +++ b/tests/unit/llms/openai/test_openai_file_content_streaming.py @@ -1,6 +1,13 @@ -import pytest -from typing import AsyncIterator, Iterator, cast +import importlib +from types import ModuleType +import pytest +from typing import AsyncIterator, Final, Iterator, cast + +from openai import AsyncOpenAI, OpenAI +from openai._types import Response as SDKResponse + +import litellm from litellm.files import main as files_main from litellm.files.streaming import FileContentStreamingResponse from litellm.files.types import FileContentStreamingResult @@ -323,3 +330,80 @@ def test_file_content_streaming_passes_exception_to_context_manager_exit(): assert response_cm.exc_info[0] is RuntimeError assert response_cm.exc_info[1] is exc_info.value assert response_cm.exc_info[2] is not None + + +@pytest.fixture(params=("httpx", "sdk")) +def http_backend(request: pytest.FixtureRequest) -> ModuleType: + return importlib.import_module("httpx" if request.param == "httpx" else SDKResponse.__module__) + + +_TRANSPORT_FAILURES: Final = pytest.mark.parametrize( + ("failure_name", "public_error"), + ( + ("ReadTimeout", litellm.Timeout), + ("ReadError", litellm.APIConnectionError), + ("RemoteProtocolError", litellm.APIConnectionError), + ), +) + + +def _body_then_failure_transport(http_backend: ModuleType, failure: Exception) -> object: + class BodyThenFailure(http_backend.SyncByteStream, http_backend.AsyncByteStream): + def __iter__(self) -> Iterator[bytes]: + yield b"hello" + raise failure + + async def __aiter__(self) -> AsyncIterator[bytes]: + yield b"hello" + raise failure + + return http_backend.MockTransport( + lambda _request: http_backend.Response( + 200, headers={"content-type": "application/octet-stream"}, stream=BodyThenFailure() + ) + ) + + +@_TRANSPORT_FAILURES +def test_file_content_stream_raises_litellm_errors_when_the_transport_fails_mid_body( + http_backend: ModuleType, failure_name: str, public_error: type[Exception] +): + failure: Final = getattr(http_backend, failure_name)("transport failed mid-body") + + with http_backend.Client(transport=_body_then_failure_transport(http_backend, failure)) as http_client: + result: Final = litellm.file_content( + file_id="file-abc123", + custom_llm_provider="openai", + client=OpenAI(api_key="transport-only", http_client=http_client), + stream=True, + chunk_size=len(b"hello"), + ) + stream: Final = cast(Iterator[bytes], result.stream_iterator) + assert next(stream) == b"hello" + with pytest.raises(public_error) as excinfo: + next(stream) + + assert excinfo.value.__cause__ is failure + + +@pytest.mark.asyncio +@_TRANSPORT_FAILURES +async def test_afile_content_stream_raises_litellm_errors_when_the_transport_fails_mid_body( + http_backend: ModuleType, failure_name: str, public_error: type[Exception] +): + failure: Final = getattr(http_backend, failure_name)("transport failed mid-body") + + async with http_backend.AsyncClient(transport=_body_then_failure_transport(http_backend, failure)) as http_client: + result: Final = await litellm.afile_content( + file_id="file-abc123", + custom_llm_provider="openai", + client=AsyncOpenAI(api_key="transport-only", http_client=http_client), + stream=True, + chunk_size=len(b"hello"), + ) + stream: Final = cast(AsyncIterator[bytes], result.stream_iterator) + assert await stream.__anext__() == b"hello" + with pytest.raises(public_error) as excinfo: + await stream.__anext__() + + assert excinfo.value.__cause__ is failure From 7cbead868843f6ae6950cb7447106773723869a4 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 20 Oct 2026 19:46:03 -0700 Subject: [PATCH 06/10] ci(openai-compat): install the matrix's exact openai version past the lock's openai override --- .github/workflows/test-unit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index c593acbe8fb..aae3a607168 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -1340,7 +1340,7 @@ jobs: if: steps.changes.outputs.decision != 'skip' run: | .github/scripts/uv_sync_with_retries.sh --frozen --group dev --group proxy-dev --extra proxy --no-install-workspace - uv pip install "openai==${OPENAI_VERSION:?}" --exclude-newer-package openai=2026-10-07T00:00:00Z + uv pip install "openai==${OPENAI_VERSION:?}" --override <(echo "openai==${OPENAI_VERSION:?}") --exclude-newer-package openai=2026-10-07T00:00:00Z uv run --no-sync python -c 'import os, openai; assert openai.__version__ == os.environ["OPENAI_VERSION"], openai.__version__' - name: Run OpenAI SDK compatibility tests From 6517ef7d6421adfcec5d1eedb466785718f182ec Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 21 Oct 2026 00:07:57 -0700 Subject: [PATCH 07/10] fix(openai): handle SDK 3 error shapes, audio deltas, Azure deployment URLs, and httpx2 token exchange errors OpenAI SDK 3 sends HTTP through httpx2, rejects an empty api_key, and ships new Responses error codes and types. This maps the new error codes and the SDK 3 missing-credentials message, keeps the raw upstream error code, copies audio deltas without unset fields, strips operation paths from full Azure deployment URLs, closes the websocket passthrough cleanly on httpx2 token exchange errors, and replaces the recursive cause walk with a bounded loop. Tests route the SDK 3 workload identity token exchange through respx and build router clients SDK 3 accepts. The lazy OpenAPI snapshot and dashboard API types are regenerated --- .../exception_mapping_utils.py | 9 +- .../litellm_core_utils/streaming_handler.py | 7 +- litellm/llms/azure/common_utils.py | 18 +- litellm/llms/custom_httpx/transport_errors.py | 17 +- litellm/proxy/_lazy_openapi_snapshot.json | 1151 ++++++++++++++++- .../common_utils/responses_stream_errors.py | 2 +- .../llm_passthrough_endpoints.py | 3 +- litellm/responses/streaming_iterator.py | 3 + tests/unit/conftest.py | 28 + .../test_exception_mapping_utils.py | 6 +- .../chat/test_azure_ai_transformation.py | 5 + .../openai/test_openai_workload_identity.py | 17 +- .../openai/test_moderations.py | 2 + .../test_llm_pass_through_endpoints.py | 2 +- .../test_openai_ws_passthrough_routes.py | 2 +- tests/unit/realtime_api/test_main.py | 4 +- .../test_streaming_iterator_error_events.py | 3 + tests/unit/test_router/test_router.py | 2 +- .../test_router_provider_endpoints.py | 9 +- ui/litellm-dashboard/src/lib/http/schema.d.ts | 929 +++++++++++-- 20 files changed, 2062 insertions(+), 157 deletions(-) diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index e7f2f5dc359..10db9c3b438 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -271,6 +271,10 @@ class _ProviderHTTPException(Protocol): _ERROR_BODY_ADAPTER: Final = TypeAdapter(dict[str, object]) +_OPENAI_CLIENT_MISSING_CREDENTIALS_MESSAGES: Final = ( + "The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variable", + "Missing credentials. Please pass an `api_key`", +) def _openai_cyber_policy_body(original_exception: _ProviderHTTPException) -> Mapping[str, object] | None: @@ -439,10 +443,7 @@ def _map_openai_exception( litellm_debug_info=extra_information, body=getattr(original_exception, "body", None), ) - elif ( - "The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variable" - in error_str - ): + elif any(marker in error_str for marker in _OPENAI_CLIENT_MISSING_CREDENTIALS_MESSAGES): raise AuthenticationError( message=f"AuthenticationError: {exception_provider} - {message}", llm_provider=custom_llm_provider, diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index 045943fdd88..edb997173b7 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -1056,7 +1056,12 @@ class CustomStreamWrapper: """ Copy a specific attribute from source delta to target delta. """ - setattr(target_delta, attribute_name, getattr(source_delta, attribute_name)) + value: Final = stored_attribute(source_delta, attribute_name) + setattr( + target_delta, + attribute_name, + value.model_dump(exclude_unset=True) if isinstance(value, BaseModel) else value, + ) def _has_any_special_delta_attributes(self, delta: object) -> bool: """ diff --git a/litellm/llms/azure/common_utils.py b/litellm/llms/azure/common_utils.py index 52e3cdf380e..2d864fa7a08 100644 --- a/litellm/llms/azure/common_utils.py +++ b/litellm/llms/azure/common_utils.py @@ -8,6 +8,7 @@ from contextlib import closing from functools import lru_cache from types import MappingProxyType from typing import Final, Literal, NamedTuple, cast +from urllib.parse import urlsplit, urlunsplit import httpx from openai import AsyncAzureOpenAI, AsyncOpenAI, AzureOpenAI, OpenAI @@ -326,13 +327,26 @@ def get_azure_ad_token_from_oidc( return azure_ad_token_access_token +_DEPLOYMENT_OPERATION_PATH: Final = re.compile( + r"(?P.*/openai/deployments/[^/]+)" + r"/(?:chat/completions|completions|embeddings|audio/(?:transcriptions|translations|speech)|images/(?:generations|edits))/?" +) + + +def _azure_deployment_base_url(deployment_url: str) -> str: + parts: Final = urlsplit(deployment_url) + operation_path: Final = _DEPLOYMENT_OPERATION_PATH.fullmatch(parts.path) + deployment_path: Final = parts.path if operation_path is None else operation_path["deployment"] + return urlunsplit(parts._replace(path=deployment_path, query="", fragment="")) + + def select_azure_base_url_or_endpoint(azure_client_params: dict): azure_endpoint: Final = azure_client_params.get("azure_endpoint", None) - if azure_endpoint is not None: + if isinstance(azure_endpoint, str): # see : https://github.com/openai/openai-python/blob/3d61ed42aba652b547029095a7eb269ad4e1e957/src/openai/lib/azure.py#L192 if "/openai/deployments" in azure_endpoint: # this is base_url, not an azure_endpoint - azure_client_params["base_url"] = azure_endpoint + azure_client_params["base_url"] = _azure_deployment_base_url(azure_endpoint) azure_client_params.pop("azure_endpoint") return azure_client_params diff --git a/litellm/llms/custom_httpx/transport_errors.py b/litellm/llms/custom_httpx/transport_errors.py index f3a2b04a4ea..20c4c0aadc4 100644 --- a/litellm/llms/custom_httpx/transport_errors.py +++ b/litellm/llms/custom_httpx/transport_errors.py @@ -1,6 +1,9 @@ import sys from collections.abc import Iterator from enum import Enum +from functools import partial +from itertools import accumulate, takewhile +from operator import is_not from typing import Final, TypeAlias import httpx @@ -42,13 +45,15 @@ def transport_error_classes(kind: TransportErrorKind) -> tuple[type[BaseExceptio return tuple(error_class for error_class, error_kind in _transport_errors() if error_kind is kind) -def _explicit_causes(error: BaseException | None, depth: int) -> Iterator[BaseException]: - if error is None or depth == 0: - return - yield error - yield from _explicit_causes(error.__cause__, depth - 1) +def _next_cause(error: BaseException | None, _depth: int) -> BaseException | None: + return None if error is None else error.__cause__ + + +def _explicit_causes(error: BaseException) -> Iterator[BaseException | None]: + causes: Final = accumulate(range(1, _MAX_CAUSE_DEPTH), _next_cause, initial=error) + return takewhile(partial(is_not, None), causes) def chained_transport_error_kind(error: BaseException) -> TransportErrorKind | None: - kinds: Final = (transport_error_kind(cause) for cause in _explicit_causes(error, _MAX_CAUSE_DEPTH)) + kinds: Final = (transport_error_kind(cause) for cause in _explicit_causes(error)) return next((kind for kind in kinds if kind is not None), None) diff --git a/litellm/proxy/_lazy_openapi_snapshot.json b/litellm/proxy/_lazy_openapi_snapshot.json index 545b317055a..2334286dbde 100644 --- a/litellm/proxy/_lazy_openapi_snapshot.json +++ b/litellm/proxy/_lazy_openapi_snapshot.json @@ -22556,8 +22556,15 @@ "title": "Queries" }, "query": { - "title": "Query", - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Query" }, "sources": { "anyOf": [ @@ -22580,7 +22587,6 @@ } }, "required": [ - "query", "type" ], "title": "ActionSearch", @@ -22644,6 +22650,98 @@ "title": "ActionWait", "type": "object" }, + "AdditionalTools": { + "additionalProperties": true, + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "role": { + "enum": [ + "unknown", + "user", + "assistant", + "system", + "critic", + "discriminator", + "developer", + "tool" + ], + "title": "Role", + "type": "string" + }, + "tools": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/FunctionTool" + }, + { + "$ref": "#/components/schemas/FileSearchTool" + }, + { + "$ref": "#/components/schemas/ComputerTool" + }, + { + "$ref": "#/components/schemas/ComputerUsePreviewTool" + }, + { + "$ref": "#/components/schemas/WebSearchTool" + }, + { + "$ref": "#/components/schemas/Mcp" + }, + { + "$ref": "#/components/schemas/CodeInterpreter" + }, + { + "$ref": "#/components/schemas/ProgrammaticToolCalling" + }, + { + "$ref": "#/components/schemas/ImageGeneration" + }, + { + "$ref": "#/components/schemas/LocalShell" + }, + { + "$ref": "#/components/schemas/FunctionShellTool" + }, + { + "$ref": "#/components/schemas/CustomTool" + }, + { + "$ref": "#/components/schemas/NamespaceTool" + }, + { + "$ref": "#/components/schemas/ToolSearchTool" + }, + { + "$ref": "#/components/schemas/WebSearchPreviewTool" + }, + { + "$ref": "#/components/schemas/ApplyPatchTool" + } + ] + }, + "title": "Tools", + "type": "array" + }, + "type": { + "const": "additional_tools", + "title": "Type", + "type": "string" + } + }, + "required": [ + "id", + "role", + "tools", + "type" + ], + "title": "AdditionalTools", + "type": "object" + }, "AnnotationContainerFileCitation": { "additionalProperties": true, "description": "A citation for a container file used to generate a model response.", @@ -22782,6 +22880,24 @@ "additionalProperties": true, "description": "Allows the assistant to create, delete, or update files using unified diffs.", "properties": { + "allowed_callers": { + "anyOf": [ + { + "items": { + "enum": [ + "direct", + "programmatic" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Allowed Callers" + }, "type": { "const": "apply_patch", "title": "Type", @@ -22899,6 +23015,41 @@ "title": "CachedTokensDetails", "type": "object" }, + "CallerDirect": { + "additionalProperties": true, + "properties": { + "type": { + "const": "direct", + "title": "Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "CallerDirect", + "type": "object" + }, + "CallerProgram": { + "additionalProperties": true, + "properties": { + "caller_id": { + "title": "Caller Id", + "type": "string" + }, + "type": { + "const": "program", + "title": "Type", + "type": "string" + } + }, + "required": [ + "caller_id", + "type" + ], + "title": "CallerProgram", + "type": "object" + }, "Click": { "additionalProperties": true, "description": "A click action.", @@ -22955,6 +23106,24 @@ "additionalProperties": true, "description": "A tool that runs Python code to help generate a response to a prompt.", "properties": { + "allowed_callers": { + "anyOf": [ + { + "items": { + "enum": [ + "direct", + "programmatic" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Allowed Callers" + }, "container": { "anyOf": [ { @@ -23132,7 +23301,7 @@ }, "ComputerTool": { "additionalProperties": true, - "description": "A tool that controls a virtual computer.\n\nLearn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use).", + "description": "A tool that controls a virtual computer.\n\nLearn more about the [computer tool](https://developers.openai.com/api/docs/guides/tools-computer-use).", "properties": { "type": { "const": "computer", @@ -23148,7 +23317,7 @@ }, "ComputerUsePreviewTool": { "additionalProperties": true, - "description": "A tool that controls a virtual computer.\n\nLearn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use).", + "description": "A tool that controls a virtual computer.\n\nLearn more about the [computer tool](https://developers.openai.com/api/docs/guides/tools-computer-use).", "properties": { "display_height": { "title": "Display Height", @@ -23384,8 +23553,37 @@ }, "CustomTool": { "additionalProperties": true, - "description": "A custom tool that processes input using a specified format.\n\nLearn more about [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools)", + "description": "A custom tool that processes input using a specified format.\n\nLearn more about [custom tools](https://developers.openai.com/api/docs/guides/function-calling#custom-tools)", "properties": { + "allowed_callers": { + "anyOf": [ + { + "items": { + "enum": [ + "direct", + "programmatic" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Allowed Callers" + }, + "async": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Async" + }, "defer_loading": { "anyOf": [ { @@ -23665,7 +23863,7 @@ }, "FileSearchTool": { "additionalProperties": true, - "description": "A tool that searches for relevant content from uploaded files.\n\nLearn more about the [file search tool](https://platform.openai.com/docs/guides/tools-file-search).", + "description": "A tool that searches for relevant content from uploaded files.\n\nLearn more about the [file search tool](https://developers.openai.com/api/docs/guides/tools-file-search).", "properties": { "filters": { "anyOf": [ @@ -23748,6 +23946,24 @@ "additionalProperties": true, "description": "A tool that allows the model to execute shell commands.", "properties": { + "allowed_callers": { + "anyOf": [ + { + "items": { + "enum": [ + "direct", + "programmatic" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Allowed Callers" + }, "environment": { "anyOf": [ { @@ -23779,8 +23995,37 @@ }, "FunctionTool": { "additionalProperties": true, - "description": "Defines a function in your own code the model can choose to call.\n\nLearn more about [function calling](https://platform.openai.com/docs/guides/function-calling).", + "description": "Defines a function in your own code the model can choose to call.\n\nLearn more about [function calling](https://developers.openai.com/api/docs/guides/function-calling).", "properties": { + "allowed_callers": { + "anyOf": [ + { + "items": { + "enum": [ + "direct", + "programmatic" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Allowed Callers" + }, + "async": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Async" + }, "defer_loading": { "anyOf": [ { @@ -23807,6 +24052,18 @@ "title": "Name", "type": "string" }, + "output_schema": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Output Schema" + }, "parameters": { "anyOf": [ { @@ -23996,6 +24253,31 @@ "title": "Grammar", "type": "object" }, + "HTTPError": { + "additionalProperties": true, + "properties": { + "code": { + "title": "Code", + "type": "integer" + }, + "message": { + "title": "Message", + "type": "string" + }, + "type": { + "const": "http_error", + "title": "Type", + "type": "string" + } + }, + "required": [ + "code", + "message", + "type" + ], + "title": "HTTPError", + "type": "object" + }, "HTTPValidationError": { "properties": { "detail": { @@ -24079,7 +24361,14 @@ "enum": [ "gpt-image-1", "gpt-image-1-mini", - "gpt-image-1.5" + "gpt-image-2", + "gpt-image-2-2026-04-21", + "gpt-image-2.5-sunburst", + "gpt-image-2.5-sunburst-2026-09-08", + "gpt-image-2.5-flare", + "gpt-image-2.5-flare-2026-09-08", + "gpt-image-1.5", + "chatgpt-image-latest" ], "type": "string" }, @@ -24149,6 +24438,8 @@ "low", "medium", "high", + "xhigh", + "max", "auto" ], "type": "string" @@ -24161,6 +24452,9 @@ }, "size": { "anyOf": [ + { + "type": "string" + }, { "enum": [ "1024x1024", @@ -24192,10 +24486,87 @@ "additionalProperties": true, "description": "An image generation request made by the model.", "properties": { + "action": { + "anyOf": [ + { + "enum": [ + "generate", + "edit", + "auto" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Action" + }, + "background": { + "anyOf": [ + { + "enum": [ + "transparent", + "opaque", + "auto" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Background" + }, + "error": { + "anyOf": [ + { + "$ref": "#/components/schemas/ImageGenerationCallError" + }, + { + "type": "null" + } + ] + }, "id": { "title": "Id", "type": "string" }, + "output_format": { + "anyOf": [ + { + "enum": [ + "png", + "webp", + "jpeg" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Output Format" + }, + "quality": { + "anyOf": [ + { + "enum": [ + "low", + "medium", + "high", + "xhigh", + "max", + "auto" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Quality" + }, "result": { "anyOf": [ { @@ -24207,6 +24578,36 @@ ], "title": "Result" }, + "revised_prompt": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Revised Prompt" + }, + "size": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "1024x1024", + "1024x1536", + "1536x1024" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Size" + }, "status": { "enum": [ "in_progress", @@ -24231,6 +24632,86 @@ "title": "ImageGenerationCall", "type": "object" }, + "ImageGenerationCallError": { + "additionalProperties": true, + "description": "Details of a moderation failure, when available.", + "properties": { + "code": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Code" + }, + "message": { + "title": "Message", + "type": "string" + }, + "moderation_details": { + "anyOf": [ + { + "$ref": "#/components/schemas/ImageGenerationCallErrorModerationDetails" + }, + { + "type": "null" + } + ] + }, + "param": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Param" + }, + "type": { + "title": "Type", + "type": "string" + } + }, + "required": [ + "message", + "type" + ], + "title": "ImageGenerationCallError", + "type": "object" + }, + "ImageGenerationCallErrorModerationDetails": { + "additionalProperties": true, + "description": "Public details for a `moderation_blocked` error, when available.", + "properties": { + "categories": { + "items": { + "type": "string" + }, + "title": "Categories", + "type": "array" + }, + "moderation_stage": { + "enum": [ + "input", + "output", + "unknown" + ], + "title": "Moderation Stage", + "type": "string" + } + }, + "required": [ + "categories", + "moderation_stage" + ], + "title": "ImageGenerationCallErrorModerationDetails", + "type": "object" + }, "ImageGenerationInputImageMask": { "additionalProperties": true, "description": "Optional mask for inpainting.\n\nContains `image_url`\n(string, optional) and `file_id` (string, optional).", @@ -24270,7 +24751,9 @@ { "enum": [ "max_output_tokens", - "content_filter" + "max_messages", + "content_filter", + "steered" ], "type": "string" }, @@ -24712,8 +25195,26 @@ }, "Mcp": { "additionalProperties": true, - "description": "Give the model access to additional tools via remote Model Context Protocol\n(MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp).", + "description": "Give the model access to additional tools via remote Model Context Protocol\n(MCP) servers. [Learn more about MCP](https://developers.openai.com/api/docs/guides/tools-connectors-mcp).", "properties": { + "allowed_callers": { + "anyOf": [ + { + "items": { + "enum": [ + "direct", + "programmatic" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Allowed Callers" + }, "allowed_tools": { "anyOf": [ { @@ -24832,6 +25333,17 @@ ], "title": "Server Url" }, + "tunnel_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tunnel Id" + }, "type": { "const": "mcp", "title": "Type", @@ -24978,7 +25490,13 @@ "error": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/McpProtocolError" + }, + { + "$ref": "#/components/schemas/McpToolExecutionError" + }, + { + "$ref": "#/components/schemas/HTTPError" }, { "type": "null" @@ -25127,6 +25645,31 @@ "title": "McpListToolsTool", "type": "object" }, + "McpProtocolError": { + "additionalProperties": true, + "properties": { + "code": { + "title": "Code", + "type": "integer" + }, + "message": { + "title": "Message", + "type": "string" + }, + "type": { + "const": "mcp_protocol_error", + "title": "Type", + "type": "string" + } + }, + "required": [ + "code", + "message", + "type" + ], + "title": "McpProtocolError", + "type": "object" + }, "McpRequireApprovalMcpToolApprovalFilter": { "additionalProperties": true, "description": "Specify which of the MCP server's tools require approval.\n\nCan be\n`always`, `never`, or a filter object associated with tools\nthat require approval.", @@ -25221,6 +25764,25 @@ "title": "McpRequireApprovalMcpToolApprovalFilterNever", "type": "object" }, + "McpToolExecutionError": { + "additionalProperties": true, + "properties": { + "content": { + "title": "Content" + }, + "type": { + "const": "mcp_tool_execution_error", + "title": "Type", + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "title": "McpToolExecutionError", + "type": "object" + }, "Move": { "additionalProperties": true, "description": "A mouse move action.", @@ -25908,6 +26470,111 @@ "title": "PendingSafetyCheck", "type": "object" }, + "Program": { + "additionalProperties": true, + "properties": { + "call_id": { + "title": "Call Id", + "type": "string" + }, + "code": { + "title": "Code", + "type": "string" + }, + "fingerprint": { + "title": "Fingerprint", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "type": { + "const": "program", + "title": "Type", + "type": "string" + } + }, + "required": [ + "id", + "call_id", + "code", + "fingerprint", + "type" + ], + "title": "Program", + "type": "object" + }, + "ProgramOutput": { + "additionalProperties": true, + "properties": { + "call_id": { + "title": "Call Id", + "type": "string" + }, + "id": { + "title": "Id", + "type": "string" + }, + "result": { + "title": "Result", + "type": "string" + }, + "status": { + "enum": [ + "completed", + "incomplete" + ], + "title": "Status", + "type": "string" + }, + "type": { + "const": "program_output", + "title": "Type", + "type": "string" + } + }, + "required": [ + "id", + "call_id", + "result", + "status", + "type" + ], + "title": "ProgramOutput", + "type": "object" + }, + "ProgrammaticToolCalling": { + "additionalProperties": true, + "properties": { + "type": { + "const": "programmatic_tool_calling", + "title": "Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ProgrammaticToolCalling", + "type": "object" + }, + "PromptCacheBreakpoint": { + "additionalProperties": true, + "description": "Marks the exact end of a reusable prompt prefix.\n\nThe breakpoint inherits its TTL from the request's `prompt_cache_options.ttl`; the boundary is not rounded to a token block.", + "properties": { + "mode": { + "const": "explicit", + "title": "Mode", + "type": "string" + } + }, + "required": [ + "mode" + ], + "title": "PromptCacheBreakpoint", + "type": "object" + }, "RankingOptions": { "additionalProperties": true, "description": "Ranking options for search.", @@ -26029,6 +26696,34 @@ "title": "RealtimeTranscriptionSessionResponse", "type": "object" }, + "Reasoning": { + "additionalProperties": true, + "description": "The reasoning configuration applied by this update.", + "properties": { + "effort": { + "anyOf": [ + { + "enum": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Effort" + } + }, + "title": "Reasoning", + "type": "object" + }, "ResponseAPIUsage": { "additionalProperties": true, "properties": { @@ -26092,6 +26787,20 @@ "title": "Call Id", "type": "string" }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" + }, "created_by": { "anyOf": [ { @@ -26153,6 +26862,20 @@ "title": "Call Id", "type": "string" }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" + }, "created_by": { "anyOf": [ { @@ -26274,7 +26997,7 @@ }, "ResponseCompactionItem": { "additionalProperties": true, - "description": "A compaction item generated by the [`v1/responses/compact` API](https://platform.openai.com/docs/api-reference/responses/compact).", + "description": "A compaction item generated by the [`v1/responses/compact` API](https://developers.openai.com/api/reference/resources/responses/methods/compact).", "properties": { "created_by": { "anyOf": [ @@ -26311,7 +27034,7 @@ }, "ResponseComputerToolCall": { "additionalProperties": true, - "description": "A tool call to a computer use tool.\n\nSee the\n[computer use guide](https://platform.openai.com/docs/guides/tools-computer-use) for more information.", + "description": "A tool call to a computer use tool.\n\nSee the\n[computer use guide](https://developers.openai.com/api/docs/guides/tools-computer-use) for more information.", "properties": { "action": { "anyOf": [ @@ -26533,6 +27256,37 @@ "title": "ResponseComputerToolCallOutputScreenshot", "type": "object" }, + "ResponseConfigurationUpdateItem": { + "additionalProperties": true, + "description": "A configuration update that applies to subsequent responses until it is\nreplaced by another configuration update.", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "reasoning": { + "anyOf": [ + { + "$ref": "#/components/schemas/Reasoning" + }, + { + "type": "null" + } + ] + }, + "type": { + "const": "configuration_update", + "title": "Type", + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "title": "ResponseConfigurationUpdateItem", + "type": "object" + }, "ResponseContainerReference": { "additionalProperties": true, "description": "Represents a container created with /v1/containers.", @@ -26558,10 +27312,35 @@ "additionalProperties": true, "description": "A call to a custom tool created by the model.", "properties": { + "async": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Async" + }, "call_id": { "title": "Call Id", "type": "string" }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" + }, "id": { "anyOf": [ { @@ -26611,10 +27390,35 @@ "additionalProperties": true, "description": "A call to a custom tool created by the model.", "properties": { + "async": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Async" + }, "call_id": { "title": "Call Id", "type": "string" }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" + }, "created_by": { "anyOf": [ { @@ -26683,6 +27487,20 @@ "title": "Call Id", "type": "string" }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" + }, "created_by": { "anyOf": [ { @@ -26749,7 +27567,7 @@ }, "ResponseFileSearchToolCall": { "additionalProperties": true, - "description": "The results of a file search tool call.\n\nSee the\n[file search guide](https://platform.openai.com/docs/guides/tools-file-search) for more information.", + "description": "The results of a file search tool call.\n\nSee the\n[file search guide](https://developers.openai.com/api/docs/guides/tools-file-search) for more information.", "properties": { "id": { "title": "Id", @@ -26836,7 +27654,7 @@ }, "ResponseFormatTextJSONSchemaConfigParam": { "additionalProperties": true, - "description": "JSON Schema response format.\n\nUsed to generate structured JSON responses.\nLearn more about [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs).", + "description": "JSON Schema response format.\n\nUsed to generate structured JSON responses.\nLearn more about [Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs).", "properties": { "description": { "title": "Description", @@ -26887,6 +27705,20 @@ "title": "Call Id", "type": "string" }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" + }, "created_by": { "anyOf": [ { @@ -26949,6 +27781,20 @@ "title": "Call Id", "type": "string" }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" + }, "created_by": { "anyOf": [ { @@ -27009,16 +27855,41 @@ }, "ResponseFunctionToolCall": { "additionalProperties": true, - "description": "A tool call to run a function.\n\nSee the\n[function calling guide](https://platform.openai.com/docs/guides/function-calling) for more information.", + "description": "A tool call to run a function.\n\nSee the\n[function calling guide](https://developers.openai.com/api/docs/guides/function-calling) for more information.", "properties": { "arguments": { "title": "Arguments", "type": "string" }, + "async": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Async" + }, "call_id": { "title": "Call Id", "type": "string" }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" + }, "id": { "anyOf": [ { @@ -27078,16 +27949,41 @@ }, "ResponseFunctionToolCallItem": { "additionalProperties": true, - "description": "A tool call to run a function.\n\nSee the\n[function calling guide](https://platform.openai.com/docs/guides/function-calling) for more information.", + "description": "A tool call to run a function.\n\nSee the\n[function calling guide](https://developers.openai.com/api/docs/guides/function-calling) for more information.", "properties": { "arguments": { "title": "Arguments", "type": "string" }, + "async": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Async" + }, "call_id": { "title": "Call Id", "type": "string" }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" + }, "created_by": { "anyOf": [ { @@ -27148,8 +28044,29 @@ "additionalProperties": true, "properties": { "call_id": { - "title": "Call Id", - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Call Id" + }, + "caller": { + "anyOf": [ + { + "$ref": "#/components/schemas/CallerDirect" + }, + { + "$ref": "#/components/schemas/CallerProgram" + }, + { + "type": "null" + } + ], + "title": "Caller" }, "created_by": { "anyOf": [ @@ -27166,6 +28083,28 @@ "title": "Id", "type": "string" }, + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "namespace": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Namespace" + }, "output": { "anyOf": [ { @@ -27207,7 +28146,6 @@ }, "required": [ "id", - "call_id", "output", "status", "type" @@ -27217,7 +28155,7 @@ }, "ResponseFunctionWebSearch": { "additionalProperties": true, - "description": "The results of a web search tool call.\n\nSee the\n[web search guide](https://platform.openai.com/docs/guides/tools-web-search) for more information.", + "description": "The results of a web search tool call.\n\nSee the\n[web search guide](https://developers.openai.com/api/docs/guides/tools-web-search) for more information.", "properties": { "action": { "anyOf": [ @@ -27242,7 +28180,8 @@ "in_progress", "searching", "completed", - "failed" + "failed", + "incomplete" ], "title": "Status", "type": "string" @@ -27270,8 +28209,9 @@ "anyOf": [ { "enum": [ - "high", - "low" + "auto", + "low", + "high" ], "type": "string" }, @@ -27325,6 +28265,16 @@ ], "title": "Filename" }, + "prompt_cache_breakpoint": { + "anyOf": [ + { + "$ref": "#/components/schemas/PromptCacheBreakpoint" + }, + { + "type": "null" + } + ] + }, "type": { "const": "input_file", "title": "Type", @@ -27339,7 +28289,7 @@ }, "ResponseInputImage": { "additionalProperties": true, - "description": "An image input to the model.\n\nLearn about [image inputs](https://platform.openai.com/docs/guides/vision).", + "description": "An image input to the model.\n\nLearn about [image inputs](https://developers.openai.com/api/docs/guides/images-vision).", "properties": { "detail": { "enum": [ @@ -27373,6 +28323,16 @@ ], "title": "Image Url" }, + "prompt_cache_breakpoint": { + "anyOf": [ + { + "$ref": "#/components/schemas/PromptCacheBreakpoint" + }, + { + "type": "null" + } + ] + }, "type": { "const": "input_image", "title": "Type", @@ -27454,6 +28414,16 @@ "additionalProperties": true, "description": "A text input to the model.", "properties": { + "prompt_cache_breakpoint": { + "anyOf": [ + { + "$ref": "#/components/schemas/PromptCacheBreakpoint" + }, + { + "type": "null" + } + ] + }, "text": { "title": "Text", "type": "string" @@ -27508,9 +28478,21 @@ { "$ref": "#/components/schemas/ResponseToolSearchOutputItem" }, + { + "$ref": "#/components/schemas/AdditionalTools" + }, + { + "$ref": "#/components/schemas/ResponseConfigurationUpdateItem" + }, { "$ref": "#/components/schemas/ResponseReasoningItem" }, + { + "$ref": "#/components/schemas/Program" + }, + { + "$ref": "#/components/schemas/ProgramOutput" + }, { "$ref": "#/components/schemas/ResponseCompactionItem" }, @@ -27751,7 +28733,7 @@ }, "ResponseReasoningItem": { "additionalProperties": true, - "description": "A description of the chain of thought used by a reasoning model while generating\na response. Be sure to include these items in your `input` to the Responses API\nfor subsequent turns of a conversation if you are manually\n[managing context](https://platform.openai.com/docs/guides/conversation-state).", + "description": "A description of the chain of thought used by a reasoning model while generating\na response. Be sure to include these items in your `input` to the Responses API\nfor subsequent turns of a conversation if you are manually\n[managing context](https://developers.openai.com/api/docs/guides/conversation-state).", "properties": { "content": { "anyOf": [ @@ -27821,7 +28803,7 @@ }, "ResponseTextConfigParam": { "additionalProperties": true, - "description": "Configuration options for a text response from the model.\n\nCan be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](https://platform.openai.com/docs/guides/text)\n- [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs)", + "description": "Configuration options for a text response from the model.\n\nCan be plain\ntext or structured JSON data. Learn more:\n- [Text inputs and outputs](https://developers.openai.com/api/docs/guides/text)\n- [Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs)", "properties": { "format": { "anyOf": [ @@ -27992,6 +28974,9 @@ { "$ref": "#/components/schemas/CodeInterpreter" }, + { + "$ref": "#/components/schemas/ProgrammaticToolCalling" + }, { "$ref": "#/components/schemas/ImageGeneration" }, @@ -28155,12 +29140,21 @@ { "$ref": "#/components/schemas/ResponseReasoningItem" }, + { + "$ref": "#/components/schemas/Program" + }, + { + "$ref": "#/components/schemas/ProgramOutput" + }, { "$ref": "#/components/schemas/ResponseToolSearchCall" }, { "$ref": "#/components/schemas/ResponseToolSearchOutputItem" }, + { + "$ref": "#/components/schemas/AdditionalTools" + }, { "$ref": "#/components/schemas/ResponseCompactionItem" }, @@ -28355,6 +29349,9 @@ { "$ref": "#/components/schemas/ToolChoiceCustomParam" }, + { + "$ref": "#/components/schemas/ToolChoiceSpecificProgrammaticToolCallingParam" + }, { "$ref": "#/components/schemas/ToolChoiceApplyPatchParam" }, @@ -28393,6 +29390,9 @@ { "$ref": "#/components/schemas/CodeInterpreter" }, + { + "$ref": "#/components/schemas/ProgrammaticToolCalling" + }, { "$ref": "#/components/schemas/ImageGeneration" }, @@ -28845,9 +29845,24 @@ "title": "ToolChoiceShellParam", "type": "object" }, + "ToolChoiceSpecificProgrammaticToolCallingParam": { + "additionalProperties": true, + "properties": { + "type": { + "const": "programmatic_tool_calling", + "title": "Type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "ToolChoiceSpecificProgrammaticToolCallingParam", + "type": "object" + }, "ToolChoiceTypesParam": { "additionalProperties": true, - "description": "Indicates that the model should use a built-in tool to generate a response.\n[Learn more about built-in tools](https://platform.openai.com/docs/guides/tools).", + "description": "Indicates that the model should use a built-in tool to generate a response.\n[Learn more about built-in tools](https://developers.openai.com/api/docs/guides/tools).", "properties": { "type": { "enum": [ @@ -28873,6 +29888,35 @@ "ToolFunction": { "additionalProperties": true, "properties": { + "allowed_callers": { + "anyOf": [ + { + "items": { + "enum": [ + "direct", + "programmatic" + ], + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Allowed Callers" + }, + "async": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Async" + }, "defer_loading": { "anyOf": [ { @@ -28899,6 +29943,18 @@ "title": "Name", "type": "string" }, + "output_schema": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Output Schema" + }, "parameters": { "anyOf": [ {}, @@ -29062,7 +30118,7 @@ }, "WebSearchPreviewTool": { "additionalProperties": true, - "description": "This tool searches the web for relevant results to use in a response.\n\nLearn more about the [web search tool](https://platform.openai.com/docs/guides/tools-web-search).", + "description": "This tool searches the web for relevant results to use in a response.\n\nLearn more about the [web search tool](https://developers.openai.com/api/docs/guides/tools-web-search).", "properties": { "search_content_types": { "anyOf": [ @@ -29125,8 +30181,19 @@ }, "WebSearchTool": { "additionalProperties": true, - "description": "Search the Internet for sources related to the prompt.\n\nLearn more about the\n[web search tool](https://platform.openai.com/docs/guides/tools-web-search).", + "description": "Search the Internet for sources related to the prompt.\n\nLearn more about the\n[web search tool](https://developers.openai.com/api/docs/guides/tools-web-search).", "properties": { + "external_web_access": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "External Web Access" + }, "filters": { "anyOf": [ { @@ -29180,7 +30247,7 @@ }, "openai__types__responses__web_search_preview_tool__UserLocation": { "additionalProperties": true, - "description": "The user's location.", + "description": "The approximate location of the user.\n\nIf omitted or null, defaults to the United States. To avoid this fallback, pass `{\"type\": \"approximate\"}` without location fields. To localize results, provide the relevant location fields.", "properties": { "city": { "anyOf": [ @@ -29240,7 +30307,7 @@ }, "openai__types__responses__web_search_tool__UserLocation": { "additionalProperties": true, - "description": "The approximate location of the user.", + "description": "The approximate location of the user.\n\nIf omitted or null, defaults to the\nUnited States. To avoid this fallback, pass `{\"type\": \"approximate\"}` without\nlocation fields. To localize results, provide the relevant location fields.", "properties": { "city": { "anyOf": [ @@ -47554,6 +48621,9 @@ "input_audio": { "$ref": "#/components/schemas/InputAudio" }, + "prompt_cache_breakpoint": { + "$ref": "#/components/schemas/PromptCacheBreakpoint" + }, "type": { "const": "input_audio", "title": "Type", @@ -48939,6 +50009,21 @@ "title": "PolicyValidationResponse", "type": "object" }, + "PromptCacheBreakpoint": { + "description": "Marks the exact end of a reusable prompt prefix.\n\nThe breakpoint inherits its TTL from the request's `prompt_cache_options.ttl`; the boundary is not rounded to a token block.", + "properties": { + "mode": { + "const": "explicit", + "title": "Mode", + "type": "string" + } + }, + "required": [ + "mode" + ], + "title": "PromptCacheBreakpoint", + "type": "object" + }, "SuggestTemplatesRequest": { "properties": { "attack_examples": { diff --git a/litellm/proxy/common_utils/responses_stream_errors.py b/litellm/proxy/common_utils/responses_stream_errors.py index 3409b4b6657..2b57618ea5f 100644 --- a/litellm/proxy/common_utils/responses_stream_errors.py +++ b/litellm/proxy/common_utils/responses_stream_errors.py @@ -72,7 +72,7 @@ def _failure_details(original: Exception) -> _FailureDetails: upstream: Final = _FailureDetails.model_validate(body) return _FailureDetails( message=upstream.message or mapped.message, - code=upstream.code if upstream.code is not None else mapped.code, + code=upstream.code if "code" in body and body["code"] is not None else mapped.code, type=upstream.type or mapped.type, status_code=mapped.status_code, ) diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index 16fbd1de376..7bc404c6eca 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -22,6 +22,7 @@ from types import MappingProxyType from typing import TYPE_CHECKING, Annotated, Final, Literal, Protocol, cast import httpx +import httpx2 import openai from fastapi import APIRouter, Depends, HTTPException, Request, Response, WebSocket from fastapi.responses import StreamingResponse @@ -3398,7 +3399,7 @@ async def openai_websocket_proxy_route( base_target_url: Final = os.getenv("OPENAI_API_BASE") or "https://api.openai.com/" try: openai_api_key: Final = await _openai_passthrough_credential(base_target_url) - except (openai.OpenAIError, httpx.HTTPError, LiteLLMOpenAIError): + except (openai.OpenAIError, httpx.HTTPError, httpx2.HTTPError, LiteLLMOpenAIError): verbose_proxy_logger.exception("OpenAI workload identity token exchange failed for websocket passthrough") await websocket.close(code=1011, reason=_OPENAI_WS_TOKEN_EXCHANGE_FAILED_REASON) return diff --git a/litellm/responses/streaming_iterator.py b/litellm/responses/streaming_iterator.py index c5dc45566a9..dd6a554cefa 100644 --- a/litellm/responses/streaming_iterator.py +++ b/litellm/responses/streaming_iterator.py @@ -226,6 +226,9 @@ _ERROR_CODE_HTTP_STATUS: Final[Mapping[str, int]] = MappingProxyType( "insufficient_quota": 429, "vector_store_timeout": 504, "invalid_prompt": 400, + "data_residency_mismatch": 400, + "bio_policy": 400, + "misalignment_policy_violation": 403, "invalid_image": 400, "invalid_image_format": 400, "invalid_base64_image": 400, diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index c96e3e2c031..d9ec2cece9e 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -2,8 +2,10 @@ import asyncio import base64 import importlib import os +import sys from collections.abc import Callable, Coroutine, Iterator from dataclasses import dataclass, field +from functools import partial from pathlib import Path from types import MappingProxyType from typing import Final, Literal, TypeAlias @@ -427,6 +429,32 @@ def fake_provider_credentials(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv(name, value) +@pytest.fixture +def openai_token_exchange_via_httpx(monkeypatch: pytest.MonkeyPatch) -> None: + workload_module: Final = sys.modules.get("openai.auth._workload") + native_client_factory: Final = getattr(workload_module, "DefaultHttpx2Client", None) + if native_client_factory is None: + return + import httpx2 + + def send_through_httpx(request: httpx2.Request) -> httpx2.Response: + with httpx.Client(trust_env=False) as client: + try: + response: Final = client.send( + httpx.Request(request.method, str(request.url), headers=dict(request.headers), content=request.content) + ) + except httpx.TransportError as error: + native_error: Final = getattr(httpx2, type(error).__name__, httpx2.TransportError) + raise native_error(str(error), request=request) from error + return httpx2.Response(response.status_code, headers=dict(response.headers), content=response.content) + + monkeypatch.setattr( + workload_module, + "DefaultHttpx2Client", + partial(native_client_factory, transport=httpx2.MockTransport(send_through_httpx), trust_env=False), + ) + + @pytest.fixture async def drained_logging_worker() -> None: await asyncio.wait_for(GLOBAL_LOGGING_WORKER.clear_queue(), timeout=10) diff --git a/tests/unit/litellm_core_utils/test_exception_mapping_utils.py b/tests/unit/litellm_core_utils/test_exception_mapping_utils.py index 5ee14ff530d..365e319e34b 100644 --- a/tests/unit/litellm_core_utils/test_exception_mapping_utils.py +++ b/tests/unit/litellm_core_utils/test_exception_mapping_utils.py @@ -2235,10 +2235,8 @@ def test_completion_perplexity_exception_on_openai_client(monkeypatch): model="perplexity/mistral-7b-instruct", messages=[{"role": "user", "content": "hello"}], ) - assert ( - "The api_key client option must be set either by passing api_key to the client or by setting the PERPLEXITY_API_KEY environment variable" - in str(exc_info.value) - ) + assert "PERPLEXITY_API_KEY" in str(exc_info.value) + assert "OPENAI_API_KEY" not in str(exc_info.value) @pytest.mark.asyncio diff --git a/tests/unit/llms/azure_ai/chat/test_azure_ai_transformation.py b/tests/unit/llms/azure_ai/chat/test_azure_ai_transformation.py index 5186541a62b..1a6d206dbc8 100644 --- a/tests/unit/llms/azure_ai/chat/test_azure_ai_transformation.py +++ b/tests/unit/llms/azure_ai/chat/test_azure_ai_transformation.py @@ -160,6 +160,11 @@ async def test_azure_ai_acompletion_posts_the_conversation_to_the_models_endpoin "/openai/deployments/gpt-4.1-mini/chat/completions", id="full-deployment-url", ), + pytest.param( + "https://example-resource.services.ai.azure.com/openai/deployments/gpt-4.1-mini/chat/completions", + "/openai/deployments/gpt-4.1-mini/chat/completions", + id="full-deployment-url-without-query", + ), ], ) def test_azure_ai_completion_routes_flexible_api_base( diff --git a/tests/unit/llms/openai/test_openai_workload_identity.py b/tests/unit/llms/openai/test_openai_workload_identity.py index 1a1a4ca7047..c85f132e9fd 100644 --- a/tests/unit/llms/openai/test_openai_workload_identity.py +++ b/tests/unit/llms/openai/test_openai_workload_identity.py @@ -56,22 +56,7 @@ CHAT_COMPLETION_BODY: Final = { } -@pytest.fixture(autouse=True) -def mock_sdk_token_exchange_transport(monkeypatch: pytest.MonkeyPatch) -> None: - native_client_factory: Final = getattr(sys.modules.get("openai.auth._workload"), "DefaultHttpx2Client", None) - if native_client_factory is not None: - import httpx2 - - def handle_exchange(request: httpx2.Request) -> httpx2.Response: - response: Final = respx.mock.handler( - httpx.Request(request.method, str(request.url), headers=dict(request.headers), content=request.content) - ) - return httpx2.Response(response.status_code, headers=dict(response.headers), content=response.content) - - monkeypatch.setattr( - "openai.auth._workload.DefaultHttpx2Client", - partial(native_client_factory, transport=httpx2.MockTransport(handle_exchange), trust_env=False), - ) +pytestmark = pytest.mark.usefixtures("openai_token_exchange_via_httpx") @pytest.fixture diff --git a/tests/unit/proxy/guardrails/guardrail_hooks/openai/test_moderations.py b/tests/unit/proxy/guardrails/guardrail_hooks/openai/test_moderations.py index 930309d62ed..8412e72ca6a 100644 --- a/tests/unit/proxy/guardrails/guardrail_hooks/openai/test_moderations.py +++ b/tests/unit/proxy/guardrails/guardrail_hooks/openai/test_moderations.py @@ -1119,6 +1119,7 @@ class _ModerationsEndpointThatRecordsTheAuthorization: @pytest.mark.asyncio +@pytest.mark.usefixtures("openai_token_exchange_via_httpx") @respx.mock async def test_openai_moderation_sends_the_federated_bearer_when_only_workload_identity_is_set( monkeypatch: pytest.MonkeyPatch, tmp_path: Path @@ -1149,6 +1150,7 @@ async def test_openai_moderation_sends_the_federated_bearer_when_only_workload_i @pytest.mark.asyncio +@pytest.mark.usefixtures("openai_token_exchange_via_httpx") @respx.mock async def test_openai_moderation_federates_with_the_identity_in_its_own_guardrail_config( monkeypatch: pytest.MonkeyPatch, tmp_path: Path diff --git a/tests/unit/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py b/tests/unit/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py index 0186a33dbc4..c5626df405e 100644 --- a/tests/unit/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py +++ b/tests/unit/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py @@ -4058,7 +4058,7 @@ def test_openai_passthrough_forwards_verbatim_to_openai( @pytest.fixture -def openai_wif_env(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None: +def openai_wif_env(monkeypatch: pytest.MonkeyPatch, tmp_path, openai_token_exchange_via_httpx: None) -> None: from litellm.llms.openai.workload_identity import _workload_identity_auth token_file: Final = tmp_path / "subject_token.jwt" diff --git a/tests/unit/proxy/test_openai_ws_passthrough_routes.py b/tests/unit/proxy/test_openai_ws_passthrough_routes.py index 6a9cd972dc2..3c785af2437 100644 --- a/tests/unit/proxy/test_openai_ws_passthrough_routes.py +++ b/tests/unit/proxy/test_openai_ws_passthrough_routes.py @@ -182,7 +182,7 @@ TOKEN_EXCHANGE_URL: Final = "https://auth.openai.com/oauth/token" @pytest.fixture -def openai_wif_token_file(monkeypatch, tmp_path): +def openai_wif_token_file(monkeypatch, tmp_path, openai_token_exchange_via_httpx: None): token_file = tmp_path / "subject_token.jwt" token_file.write_text("subject-token-from-file") monkeypatch.delenv("OPENAI_API_BASE", raising=False) diff --git a/tests/unit/realtime_api/test_main.py b/tests/unit/realtime_api/test_main.py index 4d89ee497a5..4cc181ea41a 100644 --- a/tests/unit/realtime_api/test_main.py +++ b/tests/unit/realtime_api/test_main.py @@ -1081,7 +1081,9 @@ async def test_voice_transcript_blocked_by_guardrail(): @pytest.fixture -def federated_deployment(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> dict[str, str]: +def federated_deployment( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path, openai_token_exchange_via_httpx: None +) -> dict[str, str]: token_file: Final = tmp_path / "subject_token.jwt" token_file.write_text("subject-token") for name in ( diff --git a/tests/unit/responses/test_streaming_iterator_error_events.py b/tests/unit/responses/test_streaming_iterator_error_events.py index 04d2b9ddf81..373c562066d 100644 --- a/tests/unit/responses/test_streaming_iterator_error_events.py +++ b/tests/unit/responses/test_streaming_iterator_error_events.py @@ -608,6 +608,9 @@ def test_every_openai_sdk_response_error_code_has_explicit_status_mapping(): ("empty_image_file", 400), ("failed_to_download_image", 400), ("image_file_not_found", 400), + ("data_residency_mismatch", 400), + ("bio_policy", 400), + ("misalignment_policy_violation", 403), ("totally_unknown_future_code", 500), ("429", 429), ("503", 503), diff --git a/tests/unit/test_router/test_router.py b/tests/unit/test_router/test_router.py index 13ac55fa937..12c057c87d3 100644 --- a/tests/unit/test_router/test_router.py +++ b/tests/unit/test_router/test_router.py @@ -26971,7 +26971,7 @@ def test_router_dynamic_cooldown_correct_retry_after_time(): ] ) - openai_client = openai.OpenAI(api_key="") + openai_client = openai.OpenAI(api_key="sk-fake") cooldown_time = 30 diff --git a/tests/unit/test_router/test_router_provider_endpoints.py b/tests/unit/test_router/test_router_provider_endpoints.py index f8b57410872..cf0b0a5e187 100644 --- a/tests/unit/test_router/test_router_provider_endpoints.py +++ b/tests/unit/test_router/test_router_provider_endpoints.py @@ -95,11 +95,16 @@ def _ratelimit_headers(response: ModelResponse | CustomStreamWrapper) -> dict[st def _openai_router_client() -> AsyncOpenAI: - return AsyncOpenAI(api_key="sk-fake") + return AsyncOpenAI(api_key="sk-fake", http_client=httpx.AsyncClient()) def _azure_router_client() -> AsyncAzureOpenAI: - return AsyncAzureOpenAI(api_key="sk-fake", azure_endpoint="https://azure.test", api_version="2025-02-01-preview") + return AsyncAzureOpenAI( + api_key="sk-fake", + azure_endpoint="https://azure.test", + api_version="2025-02-01-preview", + http_client=httpx.AsyncClient(), + ) @pytest.mark.asyncio diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 2b174a1141a..165a66866d4 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -25543,7 +25543,7 @@ export interface components { /** Queries */ queries?: string[] | null; /** Query */ - query: string; + query?: string | null; /** Sources */ sources?: components["schemas"]["ActionSearchSource"][] | null; /** @@ -25632,6 +25632,25 @@ export interface components { [key: string]: string; }; }; + /** AdditionalTools */ + AdditionalTools: { + /** Id */ + id: string; + /** + * Role + * @enum {string} + */ + role: "unknown" | "user" | "assistant" | "system" | "critic" | "discriminator" | "developer" | "tool"; + /** Tools */ + tools: (components["schemas"]["FunctionTool"] | components["schemas"]["FileSearchTool"] | components["schemas"]["ComputerTool"] | components["schemas"]["ComputerUsePreviewTool"] | components["schemas"]["WebSearchTool"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ProgrammaticToolCalling"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellTool"] | components["schemas"]["CustomTool"] | components["schemas"]["NamespaceTool"] | components["schemas"]["ToolSearchTool"] | components["schemas"]["WebSearchPreviewTool"] | components["schemas"]["ApplyPatchTool"])[]; + /** + * Type + * @constant + */ + type: "additional_tools"; + } & { + [key: string]: unknown; + }; /** AdminIgnore */ AdminIgnore: { /** @@ -26277,6 +26296,8 @@ export interface components { ApplyPatchCall: { /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["ApplyPatchCallCallerDirect"] | components["schemas"]["ApplyPatchCallCallerProgram"] | null; /** Id */ id?: string | null; /** Operation */ @@ -26292,6 +26313,24 @@ export interface components { */ type: "apply_patch_call"; }; + /** ApplyPatchCallCallerDirect */ + ApplyPatchCallCallerDirect: { + /** + * Type + * @constant + */ + type: "direct"; + }; + /** ApplyPatchCallCallerProgram */ + ApplyPatchCallCallerProgram: { + /** Caller Id */ + caller_id: string; + /** + * Type + * @constant + */ + type: "program"; + }; /** * ApplyPatchCallOperationCreateFile * @description Instruction for creating a new file via the apply_patch tool. @@ -26342,6 +26381,8 @@ export interface components { ApplyPatchCallOutput: { /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["ApplyPatchCallOutputCallerDirect"] | components["schemas"]["ApplyPatchCallOutputCallerProgram"] | null; /** Id */ id?: string | null; /** Output */ @@ -26357,11 +26398,31 @@ export interface components { */ type: "apply_patch_call_output"; }; + /** ApplyPatchCallOutputCallerDirect */ + ApplyPatchCallOutputCallerDirect: { + /** + * Type + * @constant + */ + type: "direct"; + }; + /** ApplyPatchCallOutputCallerProgram */ + ApplyPatchCallOutputCallerProgram: { + /** Caller Id */ + caller_id: string; + /** + * Type + * @constant + */ + type: "program"; + }; /** * ApplyPatchTool * @description Allows the assistant to create, delete, or update files using unified diffs. */ ApplyPatchTool: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; /** * Type * @constant @@ -26375,6 +26436,8 @@ export interface components { * @description Allows the assistant to create, delete, or update files using unified diffs. */ ApplyPatchToolParam: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; /** * Type * @constant @@ -28528,6 +28591,28 @@ export interface components { /** Success And Failure */ success_and_failure: string[]; }; + /** CallerDirect */ + CallerDirect: { + /** + * Type + * @constant + */ + type: "direct"; + } & { + [key: string]: unknown; + }; + /** CallerProgram */ + CallerProgram: { + /** Caller Id */ + caller_id: string; + /** + * Type + * @constant + */ + type: "program"; + } & { + [key: string]: unknown; + }; /** * CancelEvalResponse * @description Response from cancelling an evaluation @@ -28724,6 +28809,7 @@ export interface components { /** ChatCompletionAudioObject */ ChatCompletionAudioObject: { input_audio: components["schemas"]["InputAudio"]; + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"]; /** * Type * @constant @@ -29453,6 +29539,8 @@ export interface components { * @description A tool that runs Python code to help generate a response to a prompt. */ CodeInterpreter: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; /** Container */ container: string | components["schemas"]["CodeInterpreterContainerCodeInterpreterToolAuto"]; /** @@ -29484,6 +29572,17 @@ export interface components { } & { [key: string]: unknown; }; + /** + * CompactionTrigger + * @description Compacts the current context. Must be the final input item. + */ + CompactionTrigger: { + /** + * Type + * @constant + */ + type: "compaction_trigger"; + }; /** * ComparisonFilter * @description A filter used to compare a specified attribute key to a given value using a defined comparison operation. @@ -29657,7 +29756,7 @@ export interface components { * ComputerTool * @description A tool that controls a virtual computer. * - * Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use). + * Learn more about the [computer tool](https://developers.openai.com/api/docs/guides/tools-computer-use). */ ComputerTool: { /** @@ -29672,7 +29771,7 @@ export interface components { * ComputerUsePreviewTool * @description A tool that controls a virtual computer. * - * Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use). + * Learn more about the [computer tool](https://developers.openai.com/api/docs/guides/tools-computer-use). */ ComputerUsePreviewTool: { /** Display Height */ @@ -29696,7 +29795,7 @@ export interface components { * ComputerUsePreviewToolParam * @description A tool that controls a virtual computer. * - * Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use). + * Learn more about the [computer tool](https://developers.openai.com/api/docs/guides/tools-computer-use). */ ComputerUsePreviewToolParam: { /** Display Height */ @@ -31233,9 +31332,13 @@ export interface components { * CustomTool * @description A custom tool that processes input using a specified format. * - * Learn more about [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + * Learn more about [custom tools](https://developers.openai.com/api/docs/guides/function-calling#custom-tools) */ CustomTool: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; + /** Async */ + async?: boolean | null; /** Defer Loading */ defer_loading?: boolean | null; /** Description */ @@ -31283,9 +31386,13 @@ export interface components { * CustomToolParam * @description A custom tool that processes input using a specified format. * - * Learn more about [custom tools](https://platform.openai.com/docs/guides/function-calling#custom-tools) + * Learn more about [custom tools](https://developers.openai.com/api/docs/guides/function-calling#custom-tools) */ CustomToolParam: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; + /** Async */ + async?: boolean; /** Defer Loading */ defer_loading?: boolean; /** Description */ @@ -32464,7 +32571,7 @@ export interface components { * FileSearchTool * @description A tool that searches for relevant content from uploaded files. * - * Learn more about the [file search tool](https://platform.openai.com/docs/guides/tools-file-search). + * Learn more about the [file search tool](https://developers.openai.com/api/docs/guides/tools-file-search). */ FileSearchTool: { /** Filters */ @@ -32486,7 +32593,7 @@ export interface components { * FileSearchToolParam * @description A tool that searches for relevant content from uploaded files. * - * Learn more about the [file search tool](https://platform.openai.com/docs/guides/tools-file-search). + * Learn more about the [file search tool](https://developers.openai.com/api/docs/guides/tools-file-search). */ FileSearchToolParam: { /** Filters */ @@ -32527,9 +32634,15 @@ export interface components { */ FunctionCallOutput: { /** Call Id */ - call_id: string; + call_id?: string | null; + /** Caller */ + caller?: components["schemas"]["FunctionCallOutputCallerDirect"] | components["schemas"]["FunctionCallOutputCallerProgram"] | null; /** Id */ id?: string | null; + /** Name */ + name?: string | null; + /** Namespace */ + namespace?: string | null; /** Output */ output: string | (components["schemas"]["ResponseInputTextContentParam"] | components["schemas"]["ResponseInputImageContentParam"] | components["schemas"]["ResponseInputFileContentParam"])[]; /** Status */ @@ -32540,11 +32653,31 @@ export interface components { */ type: "function_call_output"; }; + /** FunctionCallOutputCallerDirect */ + FunctionCallOutputCallerDirect: { + /** + * Type + * @constant + */ + type: "direct"; + }; + /** FunctionCallOutputCallerProgram */ + FunctionCallOutputCallerProgram: { + /** Caller Id */ + caller_id: string; + /** + * Type + * @constant + */ + type: "program"; + }; /** * FunctionShellTool * @description A tool that allows the model to execute shell commands. */ FunctionShellTool: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; /** Environment */ environment?: components["schemas"]["ContainerAuto"] | components["schemas"]["LocalEnvironment"] | components["schemas"]["ContainerReference"] | null; /** @@ -32560,6 +32693,8 @@ export interface components { * @description A tool that allows the model to execute shell commands. */ FunctionShellToolParam: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; /** Environment */ environment?: components["schemas"]["ContainerAutoParam"] | components["schemas"]["LocalEnvironmentParam"] | components["schemas"]["ContainerReferenceParam"] | null; /** @@ -32572,15 +32707,23 @@ export interface components { * FunctionTool * @description Defines a function in your own code the model can choose to call. * - * Learn more about [function calling](https://platform.openai.com/docs/guides/function-calling). + * Learn more about [function calling](https://developers.openai.com/api/docs/guides/function-calling). */ FunctionTool: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; + /** Async */ + async?: boolean | null; /** Defer Loading */ defer_loading?: boolean | null; /** Description */ description?: string | null; /** Name */ name: string; + /** Output Schema */ + output_schema?: { + [key: string]: unknown; + } | null; /** Parameters */ parameters?: { [key: string]: unknown; @@ -32599,15 +32742,23 @@ export interface components { * FunctionToolParam * @description Defines a function in your own code the model can choose to call. * - * Learn more about [function calling](https://platform.openai.com/docs/guides/function-calling). + * Learn more about [function calling](https://developers.openai.com/api/docs/guides/function-calling). */ FunctionToolParam: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; + /** Async */ + async?: boolean; /** Defer Loading */ defer_loading?: boolean; /** Description */ description?: string | null; /** Name */ name: string; + /** Output Schema */ + output_schema?: { + [key: string]: unknown; + } | null; /** Parameters */ parameters: { [key: string]: unknown; @@ -33304,6 +33455,20 @@ export interface components { */ type: "http"; }; + /** HTTPError */ + HTTPError: { + /** Code */ + code: number; + /** Message */ + message: string; + /** + * Type + * @constant + */ + type: "http_error"; + } & { + [key: string]: unknown; + }; /** HTTPValidationError */ HTTPValidationError: { /** Detail */ @@ -33409,7 +33574,7 @@ export interface components { input_fidelity?: ("high" | "low") | null; input_image_mask?: components["schemas"]["ImageGenerationInputImageMask"] | null; /** Model */ - model?: string | ("gpt-image-1" | "gpt-image-1-mini" | "gpt-image-1.5") | null; + model?: string | ("gpt-image-1" | "gpt-image-1-mini" | "gpt-image-2" | "gpt-image-2-2026-04-21" | "gpt-image-2.5-sunburst" | "gpt-image-2.5-sunburst-2026-09-08" | "gpt-image-2.5-flare" | "gpt-image-2.5-flare-2026-09-08" | "gpt-image-1.5" | "chatgpt-image-latest") | null; /** Moderation */ moderation?: ("auto" | "low") | null; /** Output Compression */ @@ -33419,9 +33584,9 @@ export interface components { /** Partial Images */ partial_images?: number | null; /** Quality */ - quality?: ("low" | "medium" | "high" | "auto") | null; + quality?: ("low" | "medium" | "high" | "xhigh" | "max" | "auto") | null; /** Size */ - size?: ("1024x1024" | "1024x1536" | "1536x1024" | "auto") | null; + size?: string | ("1024x1024" | "1024x1536" | "1536x1024" | "auto") | null; /** * Type * @constant @@ -33435,10 +33600,23 @@ export interface components { * @description An image generation request made by the model. */ ImageGenerationCall: { + /** Action */ + action?: ("generate" | "edit" | "auto") | null; + /** Background */ + background?: ("transparent" | "opaque" | "auto") | null; + error?: components["schemas"]["ImageGenerationCallError"] | null; /** Id */ id: string; + /** Output Format */ + output_format?: ("png" | "webp" | "jpeg") | null; + /** Quality */ + quality?: ("low" | "medium" | "high" | "xhigh" | "max" | "auto") | null; /** Result */ result?: string | null; + /** Revised Prompt */ + revised_prompt?: string | null; + /** Size */ + size?: string | ("1024x1024" | "1024x1536" | "1536x1024") | null; /** * Status * @enum {string} @@ -33452,6 +33630,38 @@ export interface components { } & { [key: string]: unknown; }; + /** + * ImageGenerationCallError + * @description Details of a moderation failure, when available. + */ + ImageGenerationCallError: { + /** Code */ + code?: string | null; + /** Message */ + message: string; + moderation_details?: components["schemas"]["ImageGenerationCallErrorModerationDetails"] | null; + /** Param */ + param?: string | null; + /** Type */ + type: string; + } & { + [key: string]: unknown; + }; + /** + * ImageGenerationCallErrorModerationDetails + * @description Public details for a `moderation_blocked` error, when available. + */ + ImageGenerationCallErrorModerationDetails: { + /** Categories */ + categories: string[]; + /** + * Moderation Stage + * @enum {string} + */ + moderation_stage: "input" | "output" | "unknown"; + } & { + [key: string]: unknown; + }; /** * ImageGenerationInputImageMask * @description Optional mask for inpainting. @@ -33495,7 +33705,7 @@ export interface components { */ IncompleteDetails: { /** Reason */ - reason?: ("max_output_tokens" | "content_filter") | null; + reason?: ("max_output_tokens" | "max_messages" | "content_filter" | "steered") | null; } & { [key: string]: unknown; }; @@ -35592,7 +35802,7 @@ export interface components { [key: string]: unknown; }[] | null; /** Timeout */ - timeout?: number | string | null; + timeout?: number | string | components["schemas"]["Timeout"] | null; /** Timeout By Service Tier */ timeout_by_service_tier?: { [key: string]: number; @@ -37964,9 +38174,11 @@ export interface components { /** * Mcp * @description Give the model access to additional tools via remote Model Context Protocol - * (MCP) servers. [Learn more about MCP](https://platform.openai.com/docs/guides/tools-remote-mcp). + * (MCP) servers. [Learn more about MCP](https://developers.openai.com/api/docs/guides/tools-connectors-mcp). */ Mcp: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; /** Allowed Tools */ allowed_tools?: string[] | components["schemas"]["McpAllowedToolsMcpToolFilter"] | null; /** Authorization */ @@ -37987,6 +38199,8 @@ export interface components { server_label: string; /** Server Url */ server_url?: string | null; + /** Tunnel Id */ + tunnel_id?: string | null; /** * Type * @constant @@ -38059,7 +38273,7 @@ export interface components { /** Arguments */ arguments: string; /** Error */ - error?: string | null; + error?: components["schemas"]["McpProtocolError"] | components["schemas"]["McpToolExecutionError"] | components["schemas"]["HTTPError"] | null; /** Id */ id: string; /** Name */ @@ -38115,6 +38329,20 @@ export interface components { } & { [key: string]: unknown; }; + /** McpProtocolError */ + McpProtocolError: { + /** Code */ + code: number; + /** Message */ + message: string; + /** + * Type + * @constant + */ + type: "mcp_protocol_error"; + } & { + [key: string]: unknown; + }; /** * McpRequireApprovalMcpToolApprovalFilter * @description Specify which of the MCP server's tools require approval. @@ -38153,6 +38381,18 @@ export interface components { } & { [key: string]: unknown; }; + /** McpToolExecutionError */ + McpToolExecutionError: { + /** Content */ + content: unknown; + /** + * Type + * @constant + */ + type: "mcp_tool_execution_error"; + } & { + [key: string]: unknown; + }; /** Member */ Member: { /** @@ -41508,6 +41748,55 @@ export interface components { */ version_status: string; }; + /** Program */ + Program: { + /** Call Id */ + call_id: string; + /** Code */ + code: string; + /** Fingerprint */ + fingerprint: string; + /** Id */ + id: string; + /** + * Type + * @constant + */ + type: "program"; + } & { + [key: string]: unknown; + }; + /** ProgramOutput */ + ProgramOutput: { + /** Call Id */ + call_id: string; + /** Id */ + id: string; + /** Result */ + result: string; + /** + * Status + * @enum {string} + */ + status: "completed" | "incomplete"; + /** + * Type + * @constant + */ + type: "program_output"; + } & { + [key: string]: unknown; + }; + /** ProgrammaticToolCalling */ + ProgrammaticToolCalling: { + /** + * Type + * @constant + */ + type: "programmatic_tool_calling"; + } & { + [key: string]: unknown; + }; /** Prompt */ Prompt: { litellm_params: components["schemas"]["PromptLiteLLMParams"]; @@ -41515,6 +41804,21 @@ export interface components { prompt_id: string; prompt_info?: components["schemas"]["PromptInfo"] | null; }; + /** + * PromptCacheBreakpoint + * @description Marks the exact end of a reusable prompt prefix. + * + * The breakpoint inherits its TTL from the request's `prompt_cache_options.ttl`; the boundary is not rounded to a token block. + */ + PromptCacheBreakpoint: { + /** + * Mode + * @constant + */ + mode: "explicit"; + } & { + [key: string]: unknown; + }; /** PromptCacheOptions */ PromptCacheOptions: { /** @@ -42000,18 +42304,13 @@ export interface components { }; /** * Reasoning - * @description **gpt-5 and o-series models only** - * - * Configuration options for - * [reasoning models](https://platform.openai.com/docs/guides/reasoning). + * @description The reasoning configuration applied by this update. */ Reasoning: { /** Effort */ - effort?: ("none" | "minimal" | "low" | "medium" | "high" | "xhigh") | null; - /** Generate Summary */ - generate_summary?: ("auto" | "concise" | "detailed") | null; - /** Summary */ - summary?: ("auto" | "concise" | "detailed") | null; + effort?: ("none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max") | null; + } & { + [key: string]: unknown; }; /** RegenerateKeyRequest */ RegenerateKeyRequest: { @@ -42875,6 +43174,8 @@ export interface components { ResponseApplyPatchToolCall: { /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Created By */ created_by?: string | null; /** Id */ @@ -42901,6 +43202,8 @@ export interface components { ResponseApplyPatchToolCallOutput: { /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Created By */ created_by?: string | null; /** Id */ @@ -42972,7 +43275,7 @@ export interface components { }; /** * ResponseCompactionItem - * @description A compaction item generated by the [`v1/responses/compact` API](https://platform.openai.com/docs/api-reference/responses/compact). + * @description A compaction item generated by the [`v1/responses/compact` API](https://developers.openai.com/api/reference/resources/responses/methods/compact). */ ResponseCompactionItem: { /** Created By */ @@ -42991,7 +43294,7 @@ export interface components { }; /** * ResponseCompactionItemParamParam - * @description A compaction item generated by the [`v1/responses/compact` API](https://platform.openai.com/docs/api-reference/responses/compact). + * @description A compaction item generated by the [`v1/responses/compact` API](https://developers.openai.com/api/reference/resources/responses/methods/compact). */ ResponseCompactionItemParamParam: { /** Encrypted Content */ @@ -43009,7 +43312,7 @@ export interface components { * @description A tool call to a computer use tool. * * See the - * [computer use guide](https://platform.openai.com/docs/guides/tools-computer-use) for more information. + * [computer use guide](https://developers.openai.com/api/docs/guides/tools-computer-use) for more information. */ ResponseComputerToolCall: { /** Action */ @@ -43096,7 +43399,7 @@ export interface components { * @description A tool call to a computer use tool. * * See the - * [computer use guide](https://platform.openai.com/docs/guides/tools-computer-use) for more information. + * [computer use guide](https://developers.openai.com/api/docs/guides/tools-computer-use) for more information. */ ResponseComputerToolCallParam: { /** Action */ @@ -43120,6 +43423,41 @@ export interface components { */ type: "computer_call"; }; + /** + * ResponseConfigurationUpdateItem + * @description A configuration update that applies to subsequent responses until it is + * replaced by another configuration update. + */ + ResponseConfigurationUpdateItem: { + /** Id */ + id: string; + reasoning?: components["schemas"]["Reasoning"] | null; + /** + * Type + * @constant + */ + type: "configuration_update"; + } & { + [key: string]: unknown; + }; + /** + * ResponseConfigurationUpdateItemParamParam + * @description An update to the conversation's response configuration. + * + * The configuration + * remains in effect for subsequent responses until it is replaced by another + * configuration update. + */ + ResponseConfigurationUpdateItemParamParam: { + /** Id */ + id?: string | null; + reasoning?: components["schemas"]["openai__types__responses__response_configuration_update_item_param_param__Reasoning"]; + /** + * Type + * @constant + */ + type: "configuration_update"; + }; /** * ResponseContainerReference * @description Represents a container created with /v1/containers. @@ -43140,8 +43478,12 @@ export interface components { * @description A call to a custom tool created by the model. */ ResponseCustomToolCall: { + /** Async */ + async?: boolean | null; /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Id */ id?: string | null; /** Input */ @@ -43163,8 +43505,12 @@ export interface components { * @description A call to a custom tool created by the model. */ ResponseCustomToolCallItem: { + /** Async */ + async?: boolean | null; /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Created By */ created_by?: string | null; /** Id */ @@ -43195,6 +43541,8 @@ export interface components { ResponseCustomToolCallOutputItem: { /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Created By */ created_by?: string | null; /** Id */ @@ -43221,6 +43569,8 @@ export interface components { ResponseCustomToolCallOutputParam: { /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Id */ id?: string; /** Output */ @@ -43236,8 +43586,12 @@ export interface components { * @description A call to a custom tool created by the model. */ ResponseCustomToolCallParam: { + /** Async */ + async?: boolean; /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Id */ id?: string; /** Input */ @@ -43257,7 +43611,7 @@ export interface components { * @description The results of a file search tool call. * * See the - * [file search guide](https://platform.openai.com/docs/guides/tools-file-search) for more information. + * [file search guide](https://developers.openai.com/api/docs/guides/tools-file-search) for more information. */ ResponseFileSearchToolCall: { /** Id */ @@ -43284,7 +43638,7 @@ export interface components { * @description The results of a file search tool call. * * See the - * [file search guide](https://platform.openai.com/docs/guides/tools-file-search) for more information. + * [file search guide](https://developers.openai.com/api/docs/guides/tools-file-search) for more information. */ ResponseFileSearchToolCallParam: { /** Id */ @@ -43340,7 +43694,7 @@ export interface components { * @description JSON Schema response format. * * Used to generate structured JSON responses. - * Learn more about [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs). + * Learn more about [Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs). */ ResponseFormatTextJSONSchemaConfigParam: { /** Description */ @@ -43381,6 +43735,8 @@ export interface components { action: components["schemas"]["Action"]; /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Created By */ created_by?: string | null; /** Environment */ @@ -43407,6 +43763,8 @@ export interface components { ResponseFunctionShellToolCallOutput: { /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Created By */ created_by?: string | null; /** Id */ @@ -43433,13 +43791,17 @@ export interface components { * @description A tool call to run a function. * * See the - * [function calling guide](https://platform.openai.com/docs/guides/function-calling) for more information. + * [function calling guide](https://developers.openai.com/api/docs/guides/function-calling) for more information. */ ResponseFunctionToolCall: { /** Arguments */ arguments: string; + /** Async */ + async?: boolean | null; /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Id */ id?: string | null; /** Name */ @@ -43461,13 +43823,17 @@ export interface components { * @description A tool call to run a function. * * See the - * [function calling guide](https://platform.openai.com/docs/guides/function-calling) for more information. + * [function calling guide](https://developers.openai.com/api/docs/guides/function-calling) for more information. */ ResponseFunctionToolCallItem: { /** Arguments */ arguments: string; + /** Async */ + async?: boolean | null; /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Created By */ created_by?: string | null; /** Id */ @@ -43492,11 +43858,17 @@ export interface components { /** ResponseFunctionToolCallOutputItem */ ResponseFunctionToolCallOutputItem: { /** Call Id */ - call_id: string; + call_id?: string | null; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Created By */ created_by?: string | null; /** Id */ id: string; + /** Name */ + name?: string | null; + /** Namespace */ + namespace?: string | null; /** Output */ output: string | (components["schemas"]["ResponseInputText"] | components["schemas"]["ResponseInputImage"] | components["schemas"]["ResponseInputFile"])[]; /** @@ -43517,13 +43889,17 @@ export interface components { * @description A tool call to run a function. * * See the - * [function calling guide](https://platform.openai.com/docs/guides/function-calling) for more information. + * [function calling guide](https://developers.openai.com/api/docs/guides/function-calling) for more information. */ ResponseFunctionToolCallParam: { /** Arguments */ arguments: string; + /** Async */ + async?: boolean; /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["CallerDirect"] | components["schemas"]["CallerProgram"] | null; /** Id */ id?: string; /** Name */ @@ -43546,7 +43922,7 @@ export interface components { * @description The results of a web search tool call. * * See the - * [web search guide](https://platform.openai.com/docs/guides/tools-web-search) for more information. + * [web search guide](https://developers.openai.com/api/docs/guides/tools-web-search) for more information. */ ResponseFunctionWebSearch: { /** Action */ @@ -43557,7 +43933,7 @@ export interface components { * Status * @enum {string} */ - status: "in_progress" | "searching" | "completed" | "failed"; + status: "in_progress" | "searching" | "completed" | "failed" | "incomplete"; /** * Type * @constant @@ -43571,7 +43947,7 @@ export interface components { * @description The results of a web search tool call. * * See the - * [web search guide](https://platform.openai.com/docs/guides/tools-web-search) for more information. + * [web search guide](https://developers.openai.com/api/docs/guides/tools-web-search) for more information. */ ResponseFunctionWebSearchParam: { /** Action */ @@ -43582,7 +43958,7 @@ export interface components { * Status * @enum {string} */ - status: "in_progress" | "searching" | "completed" | "failed"; + status: "in_progress" | "searching" | "completed" | "incomplete" | "failed"; /** * Type * @constant @@ -43595,7 +43971,7 @@ export interface components { */ ResponseInputFile: { /** Detail */ - detail?: ("high" | "low") | null; + detail?: ("auto" | "low" | "high") | null; /** File Data */ file_data?: string | null; /** File Id */ @@ -43604,6 +43980,7 @@ export interface components { file_url?: string | null; /** Filename */ filename?: string | null; + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"] | null; /** * Type * @constant @@ -43621,7 +43998,7 @@ export interface components { * Detail * @enum {string} */ - detail?: "low" | "high"; + detail?: "auto" | "low" | "high"; /** File Data */ file_data?: string | null; /** File Id */ @@ -43630,6 +44007,7 @@ export interface components { file_url?: string | null; /** Filename */ filename?: string | null; + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"] | null; /** * Type * @constant @@ -43645,7 +44023,7 @@ export interface components { * Detail * @enum {string} */ - detail?: "low" | "high"; + detail?: "auto" | "low" | "high"; /** File Data */ file_data?: string; /** File Id */ @@ -43654,6 +44032,7 @@ export interface components { file_url?: string; /** Filename */ filename?: string; + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"]; /** * Type * @constant @@ -43664,7 +44043,7 @@ export interface components { * ResponseInputImage * @description An image input to the model. * - * Learn about [image inputs](https://platform.openai.com/docs/guides/vision). + * Learn about [image inputs](https://developers.openai.com/api/docs/guides/images-vision). */ ResponseInputImage: { /** @@ -43676,6 +44055,7 @@ export interface components { file_id?: string | null; /** Image Url */ image_url?: string | null; + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"] | null; /** * Type * @constant @@ -43688,7 +44068,7 @@ export interface components { * ResponseInputImageContentParam * @description An image input to the model. * - * Learn about [image inputs](https://platform.openai.com/docs/guides/vision) + * Learn about [image inputs](https://developers.openai.com/api/docs/guides/images-vision) */ ResponseInputImageContentParam: { /** Detail */ @@ -43697,6 +44077,7 @@ export interface components { file_id?: string | null; /** Image Url */ image_url?: string | null; + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"] | null; /** * Type * @constant @@ -43707,7 +44088,7 @@ export interface components { * ResponseInputImageParam * @description An image input to the model. * - * Learn about [image inputs](https://platform.openai.com/docs/guides/vision). + * Learn about [image inputs](https://developers.openai.com/api/docs/guides/images-vision). */ ResponseInputImageParam: { /** @@ -43719,6 +44100,7 @@ export interface components { file_id?: string | null; /** Image Url */ image_url?: string | null; + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"]; /** * Type * @constant @@ -43751,6 +44133,7 @@ export interface components { * @description A text input to the model. */ ResponseInputText: { + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"] | null; /** Text */ text: string; /** @@ -43766,6 +44149,7 @@ export interface components { * @description A text input to the model. */ ResponseInputTextContentParam: { + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"] | null; /** Text */ text: string; /** @@ -43779,6 +44163,7 @@ export interface components { * @description A text input to the model. */ ResponseInputTextParam: { + prompt_cache_breakpoint?: components["schemas"]["PromptCacheBreakpoint"]; /** Text */ text: string; /** @@ -43793,7 +44178,7 @@ export interface components { */ ResponseItemList: { /** Data */ - data: (components["schemas"]["ResponseInputMessageItem"] | components["schemas"]["ResponseOutputMessage"] | components["schemas"]["ResponseFileSearchToolCall"] | components["schemas"]["ResponseComputerToolCall"] | components["schemas"]["ResponseComputerToolCallOutputItem"] | components["schemas"]["ResponseFunctionWebSearch"] | components["schemas"]["ResponseFunctionToolCallItem"] | components["schemas"]["ResponseFunctionToolCallOutputItem"] | components["schemas"]["ResponseToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItem"] | components["schemas"]["ResponseReasoningItem"] | components["schemas"]["ResponseCompactionItem"] | components["schemas"]["ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCall"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ResponseFunctionShellToolCall"] | components["schemas"]["ResponseFunctionShellToolCallOutput"] | components["schemas"]["ResponseApplyPatchToolCall"] | components["schemas"]["ResponseApplyPatchToolCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallItem"] | components["schemas"]["ResponseCustomToolCallOutputItem"])[]; + data: (components["schemas"]["ResponseInputMessageItem"] | components["schemas"]["ResponseOutputMessage"] | components["schemas"]["ResponseFileSearchToolCall"] | components["schemas"]["ResponseComputerToolCall"] | components["schemas"]["ResponseComputerToolCallOutputItem"] | components["schemas"]["ResponseFunctionWebSearch"] | components["schemas"]["ResponseFunctionToolCallItem"] | components["schemas"]["ResponseFunctionToolCallOutputItem"] | components["schemas"]["ResponseToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItem"] | components["schemas"]["AdditionalTools"] | components["schemas"]["ResponseConfigurationUpdateItem"] | components["schemas"]["ResponseReasoningItem"] | components["schemas"]["Program"] | components["schemas"]["ProgramOutput"] | components["schemas"]["ResponseCompactionItem"] | components["schemas"]["ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCall"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ResponseFunctionShellToolCall"] | components["schemas"]["ResponseFunctionShellToolCallOutput"] | components["schemas"]["ResponseApplyPatchToolCall"] | components["schemas"]["ResponseApplyPatchToolCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallItem"] | components["schemas"]["ResponseCustomToolCallOutputItem"])[]; /** First Id */ first_id: string; /** Has More */ @@ -43950,7 +44335,7 @@ export interface components { * @description A description of the chain of thought used by a reasoning model while generating * a response. Be sure to include these items in your `input` to the Responses API * for subsequent turns of a conversation if you are manually - * [managing context](https://platform.openai.com/docs/guides/conversation-state). + * [managing context](https://developers.openai.com/api/docs/guides/conversation-state). */ ResponseReasoningItem: { /** Content */ @@ -43976,7 +44361,7 @@ export interface components { * @description A description of the chain of thought used by a reasoning model while generating * a response. Be sure to include these items in your `input` to the Responses API * for subsequent turns of a conversation if you are manually - * [managing context](https://platform.openai.com/docs/guides/conversation-state). + * [managing context](https://developers.openai.com/api/docs/guides/conversation-state). */ ResponseReasoningItemParam: { /** Content */ @@ -44004,8 +44389,8 @@ export interface components { * * Can be plain * text or structured JSON data. Learn more: - * - [Text inputs and outputs](https://platform.openai.com/docs/guides/text) - * - [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) + * - [Text inputs and outputs](https://developers.openai.com/api/docs/guides/text) + * - [Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) */ ResponseTextConfigParam: { /** Format */ @@ -44062,7 +44447,7 @@ export interface components { */ status: "in_progress" | "completed" | "incomplete"; /** Tools */ - tools: (components["schemas"]["FunctionTool"] | components["schemas"]["FileSearchTool"] | components["schemas"]["ComputerTool"] | components["schemas"]["ComputerUsePreviewTool"] | components["schemas"]["WebSearchTool"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellTool"] | components["schemas"]["CustomTool"] | components["schemas"]["NamespaceTool"] | components["schemas"]["ToolSearchTool"] | components["schemas"]["WebSearchPreviewTool"] | components["schemas"]["ApplyPatchTool"])[]; + tools: (components["schemas"]["FunctionTool"] | components["schemas"]["FileSearchTool"] | components["schemas"]["ComputerTool"] | components["schemas"]["ComputerUsePreviewTool"] | components["schemas"]["WebSearchTool"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ProgrammaticToolCalling"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellTool"] | components["schemas"]["CustomTool"] | components["schemas"]["NamespaceTool"] | components["schemas"]["ToolSearchTool"] | components["schemas"]["WebSearchPreviewTool"] | components["schemas"]["ApplyPatchTool"])[]; /** * Type * @constant @@ -44085,7 +44470,7 @@ export interface components { /** Status */ status?: ("in_progress" | "completed" | "incomplete") | null; /** Tools */ - tools: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"])[]; + tools: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["ToolMcp"] | components["schemas"]["ToolCodeInterpreter"] | components["schemas"]["ToolProgrammaticToolCalling"] | components["schemas"]["ToolImageGeneration"] | components["schemas"]["ToolLocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["ToolSearchOutputNamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"])[]; /** * Type * @constant @@ -44104,7 +44489,7 @@ export interface components { /** Include */ include?: ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | null; /** Input */ - input: string | (components["schemas"]["EasyInputMessageParam"] | components["schemas"]["ResponsesAPIRequestParams_Message"] | components["schemas"]["ResponseOutputMessageParam"] | components["schemas"]["ResponseFileSearchToolCallParam"] | components["schemas"]["ResponseComputerToolCallParam"] | components["schemas"]["ComputerCallOutput"] | components["schemas"]["ResponseFunctionWebSearchParam"] | components["schemas"]["ResponseFunctionToolCallParam"] | components["schemas"]["FunctionCallOutput"] | components["schemas"]["ToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItemParamParam"] | components["schemas"]["ResponseReasoningItemParam"] | components["schemas"]["ResponseCompactionItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCallParam"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ShellCall"] | components["schemas"]["ShellCallOutput"] | components["schemas"]["ApplyPatchCall"] | components["schemas"]["ApplyPatchCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["ResponsesAPIRequestParams_McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallOutputParam"] | components["schemas"]["ResponseCustomToolCallParam"] | components["schemas"]["ItemReference"])[]; + input: string | (components["schemas"]["EasyInputMessageParam"] | components["schemas"]["ResponsesAPIRequestParams_Message"] | components["schemas"]["ResponseOutputMessageParam"] | components["schemas"]["ResponseFileSearchToolCallParam"] | components["schemas"]["ResponseComputerToolCallParam"] | components["schemas"]["ComputerCallOutput"] | components["schemas"]["ResponseFunctionWebSearchParam"] | components["schemas"]["ResponseFunctionToolCallParam"] | components["schemas"]["FunctionCallOutput"] | components["schemas"]["ToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_AdditionalTools"] | components["schemas"]["ResponseConfigurationUpdateItemParamParam"] | components["schemas"]["ResponseReasoningItemParam"] | components["schemas"]["ResponseCompactionItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCallParam"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ShellCall"] | components["schemas"]["ShellCallOutput"] | components["schemas"]["ApplyPatchCall"] | components["schemas"]["ApplyPatchCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["ResponsesAPIRequestParams_McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallOutputParam"] | components["schemas"]["ResponseCustomToolCallParam"] | components["schemas"]["CompactionTrigger"] | components["schemas"]["ItemReference"] | components["schemas"]["Program"] | components["schemas"]["ProgramOutput"])[]; /** Instructions */ instructions?: string | null; /** Max Output Tokens */ @@ -44129,7 +44514,7 @@ export interface components { prompt_cache_options?: components["schemas"]["PromptCacheOptions"] | null; /** Prompt Cache Retention */ prompt_cache_retention?: string | null; - reasoning?: components["schemas"]["Reasoning"] | null; + reasoning?: components["schemas"]["openai__types__shared_params__reasoning__Reasoning"] | null; /** Safety Identifier */ safety_identifier?: string | null; /** Service Tier */ @@ -44143,9 +44528,9 @@ export interface components { temperature?: number | null; text?: components["schemas"]["ResponseTextConfigParam"] | null; /** Tool Choice */ - tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; + tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceSpecificProgrammaticToolCallingParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; /** Tools */ - tools?: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"] | components["schemas"]["litellm__types__llms__openai__ComputerToolParam"] | components["schemas"]["ShellToolParam"] | components["schemas"]["OpenAIToolSearchToolParam"])[] | null; + tools?: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ProgrammaticToolCalling"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"] | components["schemas"]["litellm__types__llms__openai__ComputerToolParam"] | components["schemas"]["ShellToolParam"] | components["schemas"]["OpenAIToolSearchToolParam"])[] | null; /** Top Logprobs */ top_logprobs?: number | null; /** Top P */ @@ -44172,6 +44557,23 @@ export interface components { /** Y */ y: number; }; + /** AdditionalTools */ + ResponsesAPIRequestParams_AdditionalTools: { + /** Id */ + id?: string | null; + /** + * Role + * @constant + */ + role: "developer"; + /** Tools */ + tools: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ProgrammaticToolCalling"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"])[]; + /** + * Type + * @constant + */ + type: "additional_tools"; + }; /** * DoubleClick * @description A double click action. @@ -44194,10 +44596,23 @@ export interface components { * @description An image generation request made by the model. */ ResponsesAPIRequestParams_ImageGenerationCall: { + /** Action */ + action?: ("generate" | "edit" | "auto") | null; + /** Background */ + background?: ("transparent" | "opaque" | "auto") | null; + error?: components["schemas"]["ResponsesAPIRequestParams_ImageGenerationCallError"]; /** Id */ id: string; + /** Output Format */ + output_format?: ("png" | "webp" | "jpeg") | null; + /** Quality */ + quality?: ("low" | "medium" | "high" | "xhigh" | "max" | "auto") | null; /** Result */ result: string | null; + /** Revised Prompt */ + revised_prompt?: string | null; + /** Size */ + size?: string | ("1024x1024" | "1024x1536" | "1536x1024") | null; /** * Status * @enum {string} @@ -44209,6 +44624,21 @@ export interface components { */ type: "image_generation_call"; }; + /** + * ImageGenerationCallError + * @description Details of a moderation failure, when available. + */ + ResponsesAPIRequestParams_ImageGenerationCallError: { + /** Code */ + code: string | null; + /** Message */ + message: string; + moderation_details?: components["schemas"]["ImageGenerationCallErrorModerationDetails"]; + /** Param */ + param: string | null; + /** Type */ + type: string; + }; /** * McpApprovalResponse * @description A response to an MCP approval request. @@ -44277,7 +44707,7 @@ export interface components { /** Object */ object?: string | null; /** Output */ - output: (components["schemas"]["ResponseOutputMessage"] | components["schemas"]["ResponseFileSearchToolCall"] | components["schemas"]["ResponseFunctionToolCall"] | components["schemas"]["ResponseFunctionToolCallOutputItem"] | components["schemas"]["ResponseFunctionWebSearch"] | components["schemas"]["ResponseComputerToolCall"] | components["schemas"]["ResponseComputerToolCallOutputItem"] | components["schemas"]["ResponseReasoningItem"] | components["schemas"]["ResponseToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItem"] | components["schemas"]["ResponseCompactionItem"] | components["schemas"]["ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCall"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ResponseFunctionShellToolCall"] | components["schemas"]["ResponseFunctionShellToolCallOutput"] | components["schemas"]["ResponseApplyPatchToolCall"] | components["schemas"]["ResponseApplyPatchToolCallOutput"] | components["schemas"]["McpCall"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["McpApprovalResponse"] | components["schemas"]["ResponseCustomToolCall"] | components["schemas"]["ResponseCustomToolCallOutputItem"] | { + output: (components["schemas"]["ResponseOutputMessage"] | components["schemas"]["ResponseFileSearchToolCall"] | components["schemas"]["ResponseFunctionToolCall"] | components["schemas"]["ResponseFunctionToolCallOutputItem"] | components["schemas"]["ResponseFunctionWebSearch"] | components["schemas"]["ResponseComputerToolCall"] | components["schemas"]["ResponseComputerToolCallOutputItem"] | components["schemas"]["ResponseReasoningItem"] | components["schemas"]["Program"] | components["schemas"]["ProgramOutput"] | components["schemas"]["ResponseToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItem"] | components["schemas"]["AdditionalTools"] | components["schemas"]["ResponseCompactionItem"] | components["schemas"]["ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCall"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ResponseFunctionShellToolCall"] | components["schemas"]["ResponseFunctionShellToolCallOutput"] | components["schemas"]["ResponseApplyPatchToolCall"] | components["schemas"]["ResponseApplyPatchToolCallOutput"] | components["schemas"]["McpCall"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["McpApprovalResponse"] | components["schemas"]["ResponseCustomToolCall"] | components["schemas"]["ResponseCustomToolCallOutputItem"] | { [key: string]: unknown; })[] | (components["schemas"]["GenericResponseOutputItem"] | components["schemas"]["OutputCodeInterpreterCall"] | components["schemas"]["OutputFunctionToolCall"] | components["schemas"]["OutputImageGenerationCall"] | components["schemas"]["ResponseFunctionToolCall"] | components["schemas"]["ResponseFunctionWebSearch"] | components["schemas"]["CustomToolCallOutputItem"] | components["schemas"]["OutputToolSearchCall"])[]; /** Parallel Tool Calls */ @@ -44299,9 +44729,9 @@ export interface components { [key: string]: unknown; } | null; /** Tool Choice */ - tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; + tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceSpecificProgrammaticToolCallingParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; /** Tools */ - tools?: (components["schemas"]["FunctionTool"] | components["schemas"]["FileSearchTool"] | components["schemas"]["ComputerTool"] | components["schemas"]["ComputerUsePreviewTool"] | components["schemas"]["WebSearchTool"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellTool"] | components["schemas"]["CustomTool"] | components["schemas"]["NamespaceTool"] | components["schemas"]["ToolSearchTool"] | components["schemas"]["WebSearchPreviewTool"] | components["schemas"]["ApplyPatchTool"])[] | components["schemas"]["ResponseFunctionToolCall"][] | { + tools?: (components["schemas"]["FunctionTool"] | components["schemas"]["FileSearchTool"] | components["schemas"]["ComputerTool"] | components["schemas"]["ComputerUsePreviewTool"] | components["schemas"]["WebSearchTool"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ProgrammaticToolCalling"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellTool"] | components["schemas"]["CustomTool"] | components["schemas"]["NamespaceTool"] | components["schemas"]["ToolSearchTool"] | components["schemas"]["WebSearchPreviewTool"] | components["schemas"]["ApplyPatchTool"])[] | components["schemas"]["ResponseFunctionToolCall"][] | { [key: string]: unknown; }[] | null; /** Top P */ @@ -45450,6 +45880,8 @@ export interface components { action: components["schemas"]["ShellCallAction"]; /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["ShellCallCallerDirect"] | components["schemas"]["ShellCallCallerProgram"] | null; /** Environment */ environment?: components["schemas"]["LocalEnvironmentParam"] | components["schemas"]["ContainerReferenceParam"] | null; /** Id */ @@ -45474,6 +45906,24 @@ export interface components { /** Timeout Ms */ timeout_ms?: number | null; }; + /** ShellCallCallerDirect */ + ShellCallCallerDirect: { + /** + * Type + * @constant + */ + type: "direct"; + }; + /** ShellCallCallerProgram */ + ShellCallCallerProgram: { + /** Caller Id */ + caller_id: string; + /** + * Type + * @constant + */ + type: "program"; + }; /** * ShellCallOutput * @description The streamed output items emitted by a shell tool call. @@ -45481,6 +45931,8 @@ export interface components { ShellCallOutput: { /** Call Id */ call_id: string; + /** Caller */ + caller?: components["schemas"]["ShellCallOutputCallerDirect"] | components["schemas"]["ShellCallOutputCallerProgram"] | null; /** Id */ id?: string | null; /** Max Output Length */ @@ -45495,6 +45947,24 @@ export interface components { */ type: "shell_call_output"; }; + /** ShellCallOutputCallerDirect */ + ShellCallOutputCallerDirect: { + /** + * Type + * @constant + */ + type: "direct"; + }; + /** ShellCallOutputCallerProgram */ + ShellCallOutputCallerProgram: { + /** Caller Id */ + caller_id: string; + /** + * Type + * @constant + */ + type: "program"; + }; /** * ShellToolParam * @description Shell tool for Responses API: run commands in hosted containers or local runtime. @@ -47083,6 +47553,29 @@ export interface components { /** Tier */ tier: number; }; + /** + * Timeout + * @description Timeout configuration. + * + * **Usage**: + * + * Timeout(None) # No timeouts. + * Timeout(5.0) # 5s timeout on all operations. + * Timeout(None, connect=5.0) # 5s timeout on connect, no other timeouts. + * Timeout(5.0, connect=10.0) # 10s timeout on connect. 5s timeout elsewhere. + * Timeout(5.0, pool=None) # No timeout on acquiring connection from pool. + * # 5s timeout elsewhere. + */ + Timeout: { + /** Connect */ + connect: number | null; + /** Pool */ + pool: number | null; + /** Read */ + read: number | null; + /** Write */ + write: number | null; + }; /** * TokenCountDetailsResponse * @description Response structure for token count details with modality breakdown. @@ -47246,10 +47739,20 @@ export interface components { } & { [key: string]: unknown; }; + /** ToolChoiceSpecificProgrammaticToolCallingParam */ + ToolChoiceSpecificProgrammaticToolCallingParam: { + /** + * Type + * @constant + */ + type: "programmatic_tool_calling"; + } & { + [key: string]: unknown; + }; /** * ToolChoiceTypesParam * @description Indicates that the model should use a built-in tool to generate a response. - * [Learn more about built-in tools](https://platform.openai.com/docs/guides/tools). + * [Learn more about built-in tools](https://developers.openai.com/api/docs/guides/tools). */ ToolChoiceTypesParam: { /** @@ -47260,6 +47763,40 @@ export interface components { } & { [key: string]: unknown; }; + /** + * ToolCodeInterpreter + * @description A tool that runs Python code to help generate a response to a prompt. + */ + ToolCodeInterpreter: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; + /** Container */ + container: string | components["schemas"]["ToolCodeInterpreterContainerCodeInterpreterToolAuto"]; + /** + * Type + * @constant + */ + type: "code_interpreter"; + }; + /** + * ToolCodeInterpreterContainerCodeInterpreterToolAuto + * @description Configuration for a code interpreter container. + * + * Optionally specify the IDs of the files to run the code on. + */ + ToolCodeInterpreterContainerCodeInterpreterToolAuto: { + /** File Ids */ + file_ids?: string[]; + /** Memory Limit */ + memory_limit?: ("1g" | "4g" | "16g" | "64g") | null; + /** Network Policy */ + network_policy?: components["schemas"]["ContainerNetworkPolicyDisabledParam"] | components["schemas"]["ContainerNetworkPolicyAllowlistParam"]; + /** + * Type + * @constant + */ + type: "auto"; + }; /** ToolDetailResponse */ ToolDetailResponse: { /** Overrides */ @@ -47277,12 +47814,20 @@ export interface components { }; /** ToolFunction */ ToolFunction: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; + /** Async */ + async?: boolean | null; /** Defer Loading */ defer_loading?: boolean | null; /** Description */ description?: string | null; /** Name */ name: string; + /** Output Schema */ + output_schema?: { + [key: string]: unknown; + } | null; /** Parameters */ parameters?: unknown | null; /** Strict */ @@ -47295,6 +47840,66 @@ export interface components { } & { [key: string]: unknown; }; + /** + * ToolImageGeneration + * @description A tool that generates images using the GPT image models. + */ + ToolImageGeneration: { + /** + * Action + * @enum {string} + */ + action?: "generate" | "edit" | "auto"; + /** + * Background + * @enum {string} + */ + background?: "transparent" | "opaque" | "auto"; + /** Input Fidelity */ + input_fidelity?: ("high" | "low") | null; + input_image_mask?: components["schemas"]["ToolImageGenerationInputImageMask"]; + /** Model */ + model?: string | ("gpt-image-1" | "gpt-image-1-mini" | "gpt-image-2" | "gpt-image-2-2026-04-21" | "gpt-image-2.5-sunburst" | "gpt-image-2.5-sunburst-2026-09-08" | "gpt-image-2.5-flare" | "gpt-image-2.5-flare-2026-09-08" | "gpt-image-1.5" | "chatgpt-image-latest"); + /** + * Moderation + * @enum {string} + */ + moderation?: "auto" | "low"; + /** Output Compression */ + output_compression?: number; + /** + * Output Format + * @enum {string} + */ + output_format?: "png" | "webp" | "jpeg"; + /** Partial Images */ + partial_images?: number; + /** + * Quality + * @enum {string} + */ + quality?: "low" | "medium" | "high" | "xhigh" | "max" | "auto"; + /** Size */ + size?: string | ("1024x1024" | "1024x1536" | "1536x1024" | "auto"); + /** + * Type + * @constant + */ + type: "image_generation"; + }; + /** + * ToolImageGenerationInputImageMask + * @description Optional mask for inpainting. + * + * Contains `image_url` + * (string, optional) and `file_id` (string, optional). + */ + ToolImageGenerationInputImageMask: { + /** File Id */ + file_id?: string; + /** Image Url */ + image_url?: string; + }; /** ToolListResponse */ ToolListResponse: { /** Tools */ @@ -47302,6 +47907,98 @@ export interface components { /** Total */ total: number; }; + /** + * ToolLocalShell + * @description A tool that allows the model to execute shell commands in a local environment. + */ + ToolLocalShell: { + /** + * Type + * @constant + */ + type: "local_shell"; + }; + /** + * ToolMcp + * @description Give the model access to additional tools via remote Model Context Protocol + * (MCP) servers. [Learn more about MCP](https://developers.openai.com/api/docs/guides/tools-connectors-mcp). + */ + ToolMcp: { + /** Allowed Callers */ + allowed_callers?: ("direct" | "programmatic")[] | null; + /** Allowed Tools */ + allowed_tools?: string[] | components["schemas"]["ToolMcpAllowedToolsMcpToolFilter"] | null; + /** Authorization */ + authorization?: string; + /** + * Connector Id + * @enum {string} + */ + connector_id?: "connector_dropbox" | "connector_gmail" | "connector_googlecalendar" | "connector_googledrive" | "connector_microsoftteams" | "connector_outlookcalendar" | "connector_outlookemail" | "connector_sharepoint"; + /** Defer Loading */ + defer_loading?: boolean; + /** Headers */ + headers?: { + [key: string]: string; + } | null; + /** Require Approval */ + require_approval?: components["schemas"]["ToolMcpRequireApprovalMcpToolApprovalFilter"] | ("always" | "never") | null; + /** Server Description */ + server_description?: string; + /** Server Label */ + server_label: string; + /** Server Url */ + server_url?: string; + /** Tunnel Id */ + tunnel_id?: string; + /** + * Type + * @constant + */ + type: "mcp"; + }; + /** + * ToolMcpAllowedToolsMcpToolFilter + * @description A filter object to specify which tools are allowed. + */ + ToolMcpAllowedToolsMcpToolFilter: { + /** Read Only */ + read_only?: boolean; + /** Tool Names */ + tool_names?: string[]; + }; + /** + * ToolMcpRequireApprovalMcpToolApprovalFilter + * @description Specify which of the MCP server's tools require approval. + * + * Can be + * `always`, `never`, or a filter object associated with tools + * that require approval. + */ + ToolMcpRequireApprovalMcpToolApprovalFilter: { + always?: components["schemas"]["ToolMcpRequireApprovalMcpToolApprovalFilterAlways"]; + never?: components["schemas"]["ToolMcpRequireApprovalMcpToolApprovalFilterNever"]; + }; + /** + * ToolMcpRequireApprovalMcpToolApprovalFilterAlways + * @description A filter object to specify which tools are allowed. + */ + ToolMcpRequireApprovalMcpToolApprovalFilterAlways: { + /** Read Only */ + read_only?: boolean; + /** Tool Names */ + tool_names?: string[]; + }; + /** + * ToolMcpRequireApprovalMcpToolApprovalFilterNever + * @description A filter object to specify which tools are allowed. + */ + ToolMcpRequireApprovalMcpToolApprovalFilterNever: { + /** Read Only */ + read_only?: boolean; + /** Tool Names */ + tool_names?: string[]; + }; /** * ToolPermissionRule * @description A rule defining permission for a specific tool or tool pattern @@ -47405,6 +48102,14 @@ export interface components { /** Updated */ updated: boolean; }; + /** ToolProgrammaticToolCalling */ + ToolProgrammaticToolCalling: { + /** + * Type + * @constant + */ + type: "programmatic_tool_calling"; + }; /** ToolSearchCall */ ToolSearchCall: { /** Arguments */ @@ -47426,6 +48131,23 @@ export interface components { */ type: "tool_search_call"; }; + /** + * ToolSearchOutputNamespaceToolParam + * @description Groups function/custom tools under a shared namespace. + */ + ToolSearchOutputNamespaceToolParam: { + /** Description */ + description: string; + /** Name */ + name: string; + /** Tools */ + tools: (components["schemas"]["ToolFunction"] | components["schemas"]["CustomToolParam"])[]; + /** + * Type + * @constant + */ + type: "namespace"; + }; /** * ToolSearchTool * @description Hosted or BYOT tool search configuration for deferred tools. @@ -50046,7 +50768,7 @@ export interface components { * WebSearchPreviewTool * @description This tool searches the web for relevant results to use in a response. * - * Learn more about the [web search tool](https://platform.openai.com/docs/guides/tools-web-search). + * Learn more about the [web search tool](https://developers.openai.com/api/docs/guides/tools-web-search). */ WebSearchPreviewTool: { /** Search Content Types */ @@ -50066,7 +50788,7 @@ export interface components { * WebSearchPreviewToolParam * @description This tool searches the web for relevant results to use in a response. * - * Learn more about the [web search tool](https://platform.openai.com/docs/guides/tools-web-search). + * Learn more about the [web search tool](https://developers.openai.com/api/docs/guides/tools-web-search). */ WebSearchPreviewToolParam: { /** Search Content Types */ @@ -50088,9 +50810,11 @@ export interface components { * @description Search the Internet for sources related to the prompt. * * Learn more about the - * [web search tool](https://platform.openai.com/docs/guides/tools-web-search). + * [web search tool](https://developers.openai.com/api/docs/guides/tools-web-search). */ WebSearchTool: { + /** External Web Access */ + external_web_access?: boolean | null; filters?: components["schemas"]["Filters"] | null; /** Search Context Size */ search_context_size?: ("low" | "medium" | "high") | null; @@ -50108,9 +50832,11 @@ export interface components { * @description Search the Internet for sources related to the prompt. * * Learn more about the - * [web search tool](https://platform.openai.com/docs/guides/tools-web-search). + * [web search tool](https://developers.openai.com/api/docs/guides/tools-web-search). */ WebSearchToolParam: { + /** External Web Access */ + external_web_access?: boolean; filters?: components["schemas"]["Filters"] | null; /** * Search Context Size @@ -50303,7 +51029,7 @@ export interface components { * ComputerToolParam * @description A tool that controls a virtual computer. * - * Learn more about the [computer tool](https://platform.openai.com/docs/guides/tools-computer-use). + * Learn more about the [computer tool](https://developers.openai.com/api/docs/guides/tools-computer-use). */ openai__types__responses__computer_tool_param__ComputerToolParam: { /** @@ -50312,9 +51038,19 @@ export interface components { */ type: "computer"; }; + /** + * Reasoning + * @description Updates to reasoning configuration. Only effort is supported. + */ + openai__types__responses__response_configuration_update_item_param_param__Reasoning: { + /** Effort */ + effort?: ("none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max") | null; + }; /** * UserLocation - * @description The user's location. + * @description The approximate location of the user. + * + * If omitted or null, defaults to the United States. To avoid this fallback, pass `{"type": "approximate"}` without location fields. To localize results, provide the relevant location fields. */ openai__types__responses__web_search_preview_tool__UserLocation: { /** City */ @@ -50335,7 +51071,9 @@ export interface components { }; /** * UserLocation - * @description The user's location. + * @description The approximate location of the user. + * + * If omitted or null, defaults to the United States. To avoid this fallback, pass `{"type": "approximate"}` without location fields. To localize results, provide the relevant location fields. */ openai__types__responses__web_search_preview_tool_param__UserLocation: { /** City */ @@ -50355,6 +51093,10 @@ export interface components { /** * UserLocation * @description The approximate location of the user. + * + * If omitted or null, defaults to the + * United States. To avoid this fallback, pass `{"type": "approximate"}` without + * location fields. To localize results, provide the relevant location fields. */ openai__types__responses__web_search_tool__UserLocation: { /** City */ @@ -50373,6 +51115,10 @@ export interface components { /** * UserLocation * @description The approximate location of the user. + * + * If omitted or null, defaults to the + * United States. To avoid this fallback, pass `{"type": "approximate"}` without + * location fields. To localize results, provide the relevant location fields. */ openai__types__responses__web_search_tool_param__UserLocation: { /** City */ @@ -50389,6 +51135,23 @@ export interface components { */ type?: "approximate"; }; + /** + * Reasoning + * @description Configuration options for + * [reasoning models](https://developers.openai.com/api/docs/guides/reasoning). + */ + openai__types__shared_params__reasoning__Reasoning: { + /** Context */ + context?: ("auto" | "current_turn" | "all_turns") | null; + /** Effort */ + effort?: ("none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max") | null; + /** Generate Summary */ + generate_summary?: ("auto" | "concise" | "detailed") | null; + /** Mode */ + mode?: string | ("standard" | "pro"); + /** Summary */ + summary?: ("auto" | "concise" | "detailed") | null; + }; /** updateDeployment */ updateDeployment: { /** Blocked */ @@ -50890,7 +51653,7 @@ export interface components { [key: string]: unknown; }[] | null; /** Timeout */ - timeout?: number | string | null; + timeout?: number | string | components["schemas"]["Timeout"] | null; /** Timeout By Service Tier */ timeout_by_service_tier?: { [key: string]: number; @@ -66715,7 +67478,7 @@ export interface operations { /** Include */ include?: ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | null; /** Input */ - input: string | (components["schemas"]["EasyInputMessageParam"] | components["schemas"]["ResponsesAPIRequestParams_Message"] | components["schemas"]["ResponseOutputMessageParam"] | components["schemas"]["ResponseFileSearchToolCallParam"] | components["schemas"]["ResponseComputerToolCallParam"] | components["schemas"]["ComputerCallOutput"] | components["schemas"]["ResponseFunctionWebSearchParam"] | components["schemas"]["ResponseFunctionToolCallParam"] | components["schemas"]["FunctionCallOutput"] | components["schemas"]["ToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItemParamParam"] | components["schemas"]["ResponseReasoningItemParam"] | components["schemas"]["ResponseCompactionItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCallParam"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ShellCall"] | components["schemas"]["ShellCallOutput"] | components["schemas"]["ApplyPatchCall"] | components["schemas"]["ApplyPatchCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["ResponsesAPIRequestParams_McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallOutputParam"] | components["schemas"]["ResponseCustomToolCallParam"] | components["schemas"]["ItemReference"])[]; + input: string | (components["schemas"]["EasyInputMessageParam"] | components["schemas"]["ResponsesAPIRequestParams_Message"] | components["schemas"]["ResponseOutputMessageParam"] | components["schemas"]["ResponseFileSearchToolCallParam"] | components["schemas"]["ResponseComputerToolCallParam"] | components["schemas"]["ComputerCallOutput"] | components["schemas"]["ResponseFunctionWebSearchParam"] | components["schemas"]["ResponseFunctionToolCallParam"] | components["schemas"]["FunctionCallOutput"] | components["schemas"]["ToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_AdditionalTools"] | components["schemas"]["ResponseConfigurationUpdateItemParamParam"] | components["schemas"]["ResponseReasoningItemParam"] | components["schemas"]["ResponseCompactionItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCallParam"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ShellCall"] | components["schemas"]["ShellCallOutput"] | components["schemas"]["ApplyPatchCall"] | components["schemas"]["ApplyPatchCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["ResponsesAPIRequestParams_McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallOutputParam"] | components["schemas"]["ResponseCustomToolCallParam"] | components["schemas"]["CompactionTrigger"] | components["schemas"]["ItemReference"] | components["schemas"]["Program"] | components["schemas"]["ProgramOutput"])[]; /** Instructions */ instructions?: string | null; /** Max Output Tokens */ @@ -66740,7 +67503,7 @@ export interface operations { prompt_cache_options?: components["schemas"]["PromptCacheOptions"] | null; /** Prompt Cache Retention */ prompt_cache_retention?: string | null; - reasoning?: components["schemas"]["Reasoning"] | null; + reasoning?: components["schemas"]["openai__types__shared_params__reasoning__Reasoning"] | null; /** Safety Identifier */ safety_identifier?: string | null; /** Service Tier */ @@ -66754,9 +67517,9 @@ export interface operations { temperature?: number | null; text?: components["schemas"]["ResponseTextConfigParam"] | null; /** Tool Choice */ - tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; + tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceSpecificProgrammaticToolCallingParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; /** Tools */ - tools?: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"] | components["schemas"]["litellm__types__llms__openai__ComputerToolParam"] | components["schemas"]["ShellToolParam"] | components["schemas"]["OpenAIToolSearchToolParam"])[] | null; + tools?: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ProgrammaticToolCalling"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"] | components["schemas"]["litellm__types__llms__openai__ComputerToolParam"] | components["schemas"]["ShellToolParam"] | components["schemas"]["OpenAIToolSearchToolParam"])[] | null; /** Top Logprobs */ top_logprobs?: number | null; /** Top P */ @@ -70179,7 +70942,7 @@ export interface operations { /** Include */ include?: ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | null; /** Input */ - input: string | (components["schemas"]["EasyInputMessageParam"] | components["schemas"]["ResponsesAPIRequestParams_Message"] | components["schemas"]["ResponseOutputMessageParam"] | components["schemas"]["ResponseFileSearchToolCallParam"] | components["schemas"]["ResponseComputerToolCallParam"] | components["schemas"]["ComputerCallOutput"] | components["schemas"]["ResponseFunctionWebSearchParam"] | components["schemas"]["ResponseFunctionToolCallParam"] | components["schemas"]["FunctionCallOutput"] | components["schemas"]["ToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItemParamParam"] | components["schemas"]["ResponseReasoningItemParam"] | components["schemas"]["ResponseCompactionItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCallParam"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ShellCall"] | components["schemas"]["ShellCallOutput"] | components["schemas"]["ApplyPatchCall"] | components["schemas"]["ApplyPatchCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["ResponsesAPIRequestParams_McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallOutputParam"] | components["schemas"]["ResponseCustomToolCallParam"] | components["schemas"]["ItemReference"])[]; + input: string | (components["schemas"]["EasyInputMessageParam"] | components["schemas"]["ResponsesAPIRequestParams_Message"] | components["schemas"]["ResponseOutputMessageParam"] | components["schemas"]["ResponseFileSearchToolCallParam"] | components["schemas"]["ResponseComputerToolCallParam"] | components["schemas"]["ComputerCallOutput"] | components["schemas"]["ResponseFunctionWebSearchParam"] | components["schemas"]["ResponseFunctionToolCallParam"] | components["schemas"]["FunctionCallOutput"] | components["schemas"]["ToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_AdditionalTools"] | components["schemas"]["ResponseConfigurationUpdateItemParamParam"] | components["schemas"]["ResponseReasoningItemParam"] | components["schemas"]["ResponseCompactionItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCallParam"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ShellCall"] | components["schemas"]["ShellCallOutput"] | components["schemas"]["ApplyPatchCall"] | components["schemas"]["ApplyPatchCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["ResponsesAPIRequestParams_McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallOutputParam"] | components["schemas"]["ResponseCustomToolCallParam"] | components["schemas"]["CompactionTrigger"] | components["schemas"]["ItemReference"] | components["schemas"]["Program"] | components["schemas"]["ProgramOutput"])[]; /** Instructions */ instructions?: string | null; /** Max Output Tokens */ @@ -70204,7 +70967,7 @@ export interface operations { prompt_cache_options?: components["schemas"]["PromptCacheOptions"] | null; /** Prompt Cache Retention */ prompt_cache_retention?: string | null; - reasoning?: components["schemas"]["Reasoning"] | null; + reasoning?: components["schemas"]["openai__types__shared_params__reasoning__Reasoning"] | null; /** Safety Identifier */ safety_identifier?: string | null; /** Service Tier */ @@ -70218,9 +70981,9 @@ export interface operations { temperature?: number | null; text?: components["schemas"]["ResponseTextConfigParam"] | null; /** Tool Choice */ - tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; + tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceSpecificProgrammaticToolCallingParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; /** Tools */ - tools?: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"] | components["schemas"]["litellm__types__llms__openai__ComputerToolParam"] | components["schemas"]["ShellToolParam"] | components["schemas"]["OpenAIToolSearchToolParam"])[] | null; + tools?: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ProgrammaticToolCalling"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"] | components["schemas"]["litellm__types__llms__openai__ComputerToolParam"] | components["schemas"]["ShellToolParam"] | components["schemas"]["OpenAIToolSearchToolParam"])[] | null; /** Top Logprobs */ top_logprobs?: number | null; /** Top P */ @@ -81010,7 +81773,7 @@ export interface operations { /** Include */ include?: ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | null; /** Input */ - input: string | (components["schemas"]["EasyInputMessageParam"] | components["schemas"]["ResponsesAPIRequestParams_Message"] | components["schemas"]["ResponseOutputMessageParam"] | components["schemas"]["ResponseFileSearchToolCallParam"] | components["schemas"]["ResponseComputerToolCallParam"] | components["schemas"]["ComputerCallOutput"] | components["schemas"]["ResponseFunctionWebSearchParam"] | components["schemas"]["ResponseFunctionToolCallParam"] | components["schemas"]["FunctionCallOutput"] | components["schemas"]["ToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItemParamParam"] | components["schemas"]["ResponseReasoningItemParam"] | components["schemas"]["ResponseCompactionItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCallParam"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ShellCall"] | components["schemas"]["ShellCallOutput"] | components["schemas"]["ApplyPatchCall"] | components["schemas"]["ApplyPatchCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["ResponsesAPIRequestParams_McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallOutputParam"] | components["schemas"]["ResponseCustomToolCallParam"] | components["schemas"]["ItemReference"])[]; + input: string | (components["schemas"]["EasyInputMessageParam"] | components["schemas"]["ResponsesAPIRequestParams_Message"] | components["schemas"]["ResponseOutputMessageParam"] | components["schemas"]["ResponseFileSearchToolCallParam"] | components["schemas"]["ResponseComputerToolCallParam"] | components["schemas"]["ComputerCallOutput"] | components["schemas"]["ResponseFunctionWebSearchParam"] | components["schemas"]["ResponseFunctionToolCallParam"] | components["schemas"]["FunctionCallOutput"] | components["schemas"]["ToolSearchCall"] | components["schemas"]["ResponseToolSearchOutputItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_AdditionalTools"] | components["schemas"]["ResponseConfigurationUpdateItemParamParam"] | components["schemas"]["ResponseReasoningItemParam"] | components["schemas"]["ResponseCompactionItemParamParam"] | components["schemas"]["ResponsesAPIRequestParams_ImageGenerationCall"] | components["schemas"]["ResponseCodeInterpreterToolCallParam"] | components["schemas"]["LocalShellCall"] | components["schemas"]["LocalShellCallOutput"] | components["schemas"]["ShellCall"] | components["schemas"]["ShellCallOutput"] | components["schemas"]["ApplyPatchCall"] | components["schemas"]["ApplyPatchCallOutput"] | components["schemas"]["McpListTools"] | components["schemas"]["McpApprovalRequest"] | components["schemas"]["ResponsesAPIRequestParams_McpApprovalResponse"] | components["schemas"]["McpCall"] | components["schemas"]["ResponseCustomToolCallOutputParam"] | components["schemas"]["ResponseCustomToolCallParam"] | components["schemas"]["CompactionTrigger"] | components["schemas"]["ItemReference"] | components["schemas"]["Program"] | components["schemas"]["ProgramOutput"])[]; /** Instructions */ instructions?: string | null; /** Max Output Tokens */ @@ -81035,7 +81798,7 @@ export interface operations { prompt_cache_options?: components["schemas"]["PromptCacheOptions"] | null; /** Prompt Cache Retention */ prompt_cache_retention?: string | null; - reasoning?: components["schemas"]["Reasoning"] | null; + reasoning?: components["schemas"]["openai__types__shared_params__reasoning__Reasoning"] | null; /** Safety Identifier */ safety_identifier?: string | null; /** Service Tier */ @@ -81049,9 +81812,9 @@ export interface operations { temperature?: number | null; text?: components["schemas"]["ResponseTextConfigParam"] | null; /** Tool Choice */ - tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; + tool_choice?: ("none" | "auto" | "required") | components["schemas"]["ToolChoiceAllowedParam"] | components["schemas"]["ToolChoiceTypesParam"] | components["schemas"]["ToolChoiceFunctionParam"] | components["schemas"]["ToolChoiceMcpParam"] | components["schemas"]["ToolChoiceCustomParam"] | components["schemas"]["ToolChoiceSpecificProgrammaticToolCallingParam"] | components["schemas"]["ToolChoiceApplyPatchParam"] | components["schemas"]["ToolChoiceShellParam"] | null; /** Tools */ - tools?: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"] | components["schemas"]["litellm__types__llms__openai__ComputerToolParam"] | components["schemas"]["ShellToolParam"] | components["schemas"]["OpenAIToolSearchToolParam"])[] | null; + tools?: (components["schemas"]["FunctionToolParam"] | components["schemas"]["FileSearchToolParam"] | components["schemas"]["openai__types__responses__computer_tool_param__ComputerToolParam"] | components["schemas"]["ComputerUsePreviewToolParam"] | components["schemas"]["WebSearchToolParam"] | components["schemas"]["Mcp"] | components["schemas"]["CodeInterpreter"] | components["schemas"]["ProgrammaticToolCalling"] | components["schemas"]["ImageGeneration"] | components["schemas"]["LocalShell"] | components["schemas"]["FunctionShellToolParam"] | components["schemas"]["CustomToolParam"] | components["schemas"]["NamespaceToolParam"] | components["schemas"]["ToolSearchToolParam"] | components["schemas"]["WebSearchPreviewToolParam"] | components["schemas"]["ApplyPatchToolParam"] | components["schemas"]["litellm__types__llms__openai__ComputerToolParam"] | components["schemas"]["ShellToolParam"] | components["schemas"]["OpenAIToolSearchToolParam"])[] | null; /** Top Logprobs */ top_logprobs?: number | null; /** Top P */ From 79f1531aa07410bb30fc69c1811a65d9cef565d0 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 21 Oct 2026 00:07:58 -0700 Subject: [PATCH 08/10] fix(aiohttp): yield bytes already received before raising on a dropped stream aiohttp read methods raise the stored payload error before returning buffered bytes, so a stream whose connection dropped after the final SSE events lost them. The transport now drains the reader's buffer once and yields it before raising httpx.ReadError, and falls back to the old behavior if aiohttp's internals change --- .../llms/custom_httpx/aiohttp_transport.py | 41 ++++-- .../custom_httpx/test_aiohttp_transport.py | 127 ++++++++++++++++-- 2 files changed, 148 insertions(+), 20 deletions(-) diff --git a/litellm/llms/custom_httpx/aiohttp_transport.py b/litellm/llms/custom_httpx/aiohttp_transport.py index b8cb183c02f..57860d4332e 100644 --- a/litellm/llms/custom_httpx/aiohttp_transport.py +++ b/litellm/llms/custom_httpx/aiohttp_transport.py @@ -109,6 +109,32 @@ def map_aiohttp_exceptions() -> Generator[None, None, None]: raise mapped_exc(message) from exc +@contextlib.contextmanager +def _map_stream_read_errors() -> Generator[None, None, None]: + try: + yield + except RuntimeError as e: + if "Connection closed" not in str(e): + raise + raise httpx.ReadError(str(e)) from e + except aiohttp.http_exceptions.TransferEncodingError as e: + raise httpx.ReadError(str(e)) from e + except Exception: + with map_aiohttp_exceptions(): + raise + + +def _received_unread_bytes(content: aiohttp.StreamReader) -> bytes: + read_buffer: Final[Callable[[int], object] | None] = getattr(content, "_read_nowait", None) + if read_buffer is None: + return b"" + try: + unread: Final = read_buffer(-1) + except (AttributeError, TypeError, TimeoutError): + return b"" + return unread if isinstance(unread, bytes) else b"" + + class AiohttpResponseStream(httpx.AsyncByteStream): CHUNK_SIZE = 1024 * 16 @@ -116,18 +142,15 @@ class AiohttpResponseStream(httpx.AsyncByteStream): self._aiohttp_response = aiohttp_response async def __aiter__(self) -> typing.AsyncIterator[bytes]: + content: Final = self._aiohttp_response.content try: - async for chunk in self._aiohttp_response.content.iter_chunked(self.CHUNK_SIZE): + async for chunk in content.iter_chunked(self.CHUNK_SIZE): yield chunk - except RuntimeError as e: - if "Connection closed" not in str(e): - raise - raise httpx.ReadError(str(e)) from e - except aiohttp.http_exceptions.TransferEncodingError as e: - raise httpx.ReadError(str(e)) from e except Exception: - # For other exceptions, use the normal mapping - with map_aiohttp_exceptions(): + unread: Final = _received_unread_bytes(content) + if unread: + yield unread + with _map_stream_read_errors(): raise finally: # Release the aiohttp connection when iteration ends for any diff --git a/tests/unit/llms/custom_httpx/test_aiohttp_transport.py b/tests/unit/llms/custom_httpx/test_aiohttp_transport.py index 0765371dc4a..5696afcf89e 100644 --- a/tests/unit/llms/custom_httpx/test_aiohttp_transport.py +++ b/tests/unit/llms/custom_httpx/test_aiohttp_transport.py @@ -1276,6 +1276,20 @@ async def test_refused_connection_maps_to_httpx_connect_error( assert (cause.host, cause.port) == ("127.0.0.1", port) +async def _read_request_headers(reader: asyncio.StreamReader) -> bytes: + headers: Final = await reader.readuntil(b"\r\n\r\n") + content_length: Final = next( + ( + int(header.split(b":", 1)[1]) + for header in headers.split(b"\r\n") + if header.lower().startswith(b"content-length:") + ), + 0, + ) + await reader.readexactly(content_length) + return headers + + @pytest.mark.asyncio async def test_reset_after_request_is_not_replayed(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("NO_PROXY", "127.0.0.1") @@ -1283,17 +1297,7 @@ async def test_reset_after_request_is_not_replayed(monkeypatch: pytest.MonkeyPat received_requests: Final[list[bytes]] = [] # mutable-ok: local server captures requests async def reset_after_request(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: - headers: Final = await reader.readuntil(b"\r\n\r\n") - content_length: Final = next( - ( - int(header.split(b":", 1)[1]) - for header in headers.split(b"\r\n") - if header.lower().startswith(b"content-length:") - ), - 0, - ) - await reader.readexactly(content_length) - received_requests.append(headers) + received_requests.append(await _read_request_headers(reader)) transport_socket: Final = writer.get_extra_info("socket") assert transport_socket is not None transport_socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0)) @@ -1310,3 +1314,104 @@ async def test_reset_after_request_is_not_replayed(monkeypatch: pytest.MonkeyPat assert len(received_requests) == 1 finally: await handler.close() + + +_SSE_EVENTS_BEFORE_DROP: Final = ( + b'data: {"id":"chatcmpl-drop","object":"chat.completion.chunk","created":1,"model":"gpt-5.6",' + b'"choices":[{"index":0,"delta":{"role":"assistant","content":"1 2 "},"finish_reason":null}]}\n\n', + b'data: {"id":"chatcmpl-drop","object":"chat.completion.chunk","created":1,"model":"gpt-5.6",' + b'"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n', +) + + +async def _stream_events_then_drop_before_done(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: + await _read_request_headers(reader) + chunked_events: Final = b"".join(b"%x\r\n%s\r\n" % (len(event), event) for event in _SSE_EVENTS_BEFORE_DROP) + writer.write( + b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nTransfer-Encoding: chunked\r\n\r\n" + chunked_events + ) + await writer.drain() + writer.close() + await writer.wait_closed() + + +async def _until_connection_drop_is_received(content: aiohttp.StreamReader) -> None: + while content.exception() is None: + await asyncio.sleep(0) + + +async def _read_until_error(stream: httpx.AsyncByteStream) -> tuple[bytes, Exception | None]: + chunks: Final[list[bytes]] = [] # mutable-ok: keeps the chunks read before the stream raises + try: + async for chunk in stream: + chunks.append(chunk) + except Exception as error: + return b"".join(chunks), error + return b"".join(chunks), None + + +@pytest.mark.asyncio +async def test_bytes_received_before_a_connection_drop_are_yielded_before_read_error() -> None: + server: Final = await asyncio.start_server(_stream_events_then_drop_before_done, "127.0.0.1", 0) + port: Final = server.sockets[0].getsockname()[1] + async with server, aiohttp.ClientSession() as session: + aiohttp_response: Final = await session.post(f"http://127.0.0.1:{port}/v1/chat/completions", data=b"{}") + await _until_connection_drop_is_received(aiohttp_response.content) + received, error = await _read_until_error(AiohttpResponseStream(aiohttp_response)) + + assert received == b"".join(_SSE_EVENTS_BEFORE_DROP) + assert isinstance(error, httpx.ReadError) + + +@pytest.mark.asyncio +async def test_finished_stream_dropped_before_done_ends_cleanly_through_acompletion( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("NO_PROXY", "127.0.0.1") + monkeypatch.setenv("no_proxy", "127.0.0.1") + upstream_responses: Final[list[aiohttp.ClientResponse]] = [] # mutable-ok: trace hook captures the response + + async def capture_response( + _session: ClientSession, _context: object, params: aiohttp.TraceRequestEndParams + ) -> None: + upstream_responses.append(params.response) + + trace_config: Final = aiohttp.TraceConfig() + trace_config.on_request_end.append(capture_response) + server: Final = await asyncio.start_server(_stream_events_then_drop_before_done, "127.0.0.1", 0) + port: Final = server.sockets[0].getsockname()[1] + async with server, aiohttp.ClientSession(trace_configs=[trace_config]) as session: + stream: Final = await litellm.acompletion( + model="openai/gpt-5.6", + api_base=f"http://127.0.0.1:{port}/v1", + api_key="sk-local-test", + messages=[{"role": "user", "content": "count to two"}], + stream=True, + shared_session=session, + ) + await _until_connection_drop_is_received(upstream_responses[0].content) + chunks: Final = [chunk async for chunk in stream] + + assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks) == "1 2 " + assert chunks[-1].choices[0].finish_reason == "stop" + + +class _ContentWithChangedBufferInternals(MockContent): + def _read_nowait(self, n: int) -> bytes: + raise TypeError(f"_read_nowait({n}) no longer drains the buffer") + + +@pytest.mark.asyncio +async def test_payload_error_still_maps_to_read_error_when_buffer_drain_breaks() -> None: + mock_response: Final = MockAiohttpResponse() + mock_response.content = _ContentWithChangedBufferInternals( + chunks=[b"chunk1", b"chunk2"], + exception_to_raise=aiohttp.ClientPayloadError("Response payload is not completed"), + exception_at_chunk=1, + ) + + received, error = await _read_until_error(AiohttpResponseStream(mock_response)) # pyright: ignore[reportArgumentType] # duck-typed aiohttp response double + + assert received == b"chunk1" + assert isinstance(error, httpx.ReadError) + assert mock_response.closed is True From 924ac590b18ec96ca487304eaf0762da57ebc2f3 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 28 Oct 2026 08:42:15 -0700 Subject: [PATCH 09/10] fix(openai): end a dropped stream only after every requested choice finished, keep Azure deployment URL query params, and narrow scope A dropped connection now ends an n>1 stream cleanly only once each requested choice has sent its finish_reason. A pasted Azure deployment URL keeps its extra query params on SDK requests. The new SDK 3 refusal codes map to 500, and the file download change goes back to main. --- litellm/files/streaming.py | 33 +------ .../litellm_core_utils/streaming_handler.py | 33 ++++++- litellm/llms/azure/common_utils.py | 35 ++++---- litellm/responses/streaming_iterator.py | 6 +- .../test_streaming_handler.py | 47 +++++++++- .../llms/azure/test_azure_common_utils.py | 32 +++++++ .../test_openai_file_content_streaming.py | 86 +------------------ .../test_streaming_iterator_error_events.py | 6 +- 8 files changed, 138 insertions(+), 140 deletions(-) diff --git a/litellm/files/streaming.py b/litellm/files/streaming.py index e7139589840..e9a030e0e32 100644 --- a/litellm/files/streaming.py +++ b/litellm/files/streaming.py @@ -5,9 +5,7 @@ from typing import TYPE_CHECKING, Final, Optional, cast import anyio -from litellm.exceptions import APIConnectionError, Timeout from litellm.files.types import FileContentProvider -from litellm.llms.custom_httpx.transport_errors import TransportErrorKind, chained_transport_error_kind if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import ( @@ -61,11 +59,8 @@ class FileContentStreamingResponse: self._log_success_sync() raise except Exception as e: - public_error: Final = self._public_error(e) - self._log_failure_sync(public_error) - if public_error is e: - raise - raise public_error from e + self._log_failure_sync(e) + raise def __aiter__(self) -> "FileContentStreamingResponse": if not hasattr(self.stream_iterator, "__anext__"): @@ -82,28 +77,8 @@ class FileContentStreamingResponse: await self._log_success_async() raise except Exception as e: - public_error: Final = self._public_error(e) - await self._log_failure_async(public_error) - if public_error is e: - raise - raise public_error from e - - def _public_error(self, error: Exception) -> Exception: - match chained_transport_error_kind(error): - case TransportErrorKind.TIMEOUT: - return Timeout( - message=f"timed out reading file content: {error}", - model=self.model, - llm_provider=self.custom_llm_provider, - ) - case TransportErrorKind.DROPPED_CONNECTION: - return APIConnectionError( - message=f"connection dropped while reading file content: {error}", - llm_provider=self.custom_llm_provider, - model=self.model, - ) - case None: - return error + await self._log_failure_async(e) + raise async def aclose(self) -> None: if self._close_completed: diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index 32421f8926b..214f4803f1a 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -93,6 +93,16 @@ def _next_sync_or_exhausted(it: Iterator[object]) -> object: return _SYNC_ITER_EXHAUSTED +def _first_choice_index(chunk: ChatCompletionChunk | ModelResponseStream) -> int: + match chunk: + case ChatCompletionChunk(choices=[first, *_]): + return first.index + case ModelResponseStream(choices=[first, *_]): + return first.index + case _: + return 0 + + def _stamp_served_service_tier(response: ModelResponseStream, complete_streaming_response: ModelResponse) -> None: served_tier: Final = complete_streaming_response.model_dump().get("service_tier") if isinstance(served_tier, str) and served_tier: @@ -333,6 +343,7 @@ class CustomStreamWrapper: self._provider_response_model: str | None = None self.received_finish_reason: str | None = None self.intermittent_finish_reason: str | None = None # finish reasons that show up mid-stream + self._finished_choice_indices: frozenset[int] = frozenset() self.special_tokens = [ "<|assistant|>", "<|system|>", @@ -1091,7 +1102,7 @@ class CustomStreamWrapper: """ return delta is not None and stored_attribute(delta, attribute_name) is not None - def _copy_delta_attribute(self, source_delta, target_delta, attribute_name: str) -> None: + def _copy_delta_attribute(self, source_delta: object, target_delta: object, attribute_name: str) -> None: """ Copy a specific attribute from source delta to target delta. """ @@ -1575,6 +1586,9 @@ class CustomStreamWrapper: f"{self.custom_llm_provider} raised a streaming error - finish_reason: error, no content string given. Received Chunk={response_obj}" ) self.received_finish_reason = response_obj["finish_reason"] + self._finished_choice_indices = self._finished_choice_indices | { + _first_choice_index(chunk) if isinstance(chunk, (ChatCompletionChunk, ModelResponseStream)) else 0 + } if response_obj.get("original_chunk", None) is not None: if hasattr(response_obj["original_chunk"], "id"): model_response = self.set_model_id(response_obj["original_chunk"].id, model_response) @@ -2205,12 +2219,25 @@ class CustomStreamWrapper: return next(self.completion_stream) except Exception as e: if ( - self.received_finish_reason is None + not self._every_choice_finished() or chained_transport_error_kind(e) is not TransportErrorKind.DROPPED_CONNECTION ): raise raise StopIteration from e + def _every_choice_finished(self) -> bool: + if self.received_finish_reason is None: + return False + requested_choices: Final = self._requested_choice_count() + return requested_choices <= 1 or len(self._finished_choice_indices) >= requested_choices + + def _requested_choice_count(self) -> int: + match getattr(self.logging_obj, "optional_params", None): + case {"n": int() as requested}: + return requested + case _: + return 1 + def fetch_sync_stream(self): if self.completion_stream is None and self.make_call is not None: # Call make_call to get the completion stream @@ -2321,7 +2348,7 @@ class CustomStreamWrapper: return await self._finalize_completed_stream(cache_hit=cache_hit) except Exception as e: match chained_transport_error_kind(e): - case TransportErrorKind.DROPPED_CONNECTION if self.received_finish_reason is not None: + case TransportErrorKind.DROPPED_CONNECTION if self._every_choice_finished(): return await self._finalize_completed_stream(cache_hit=cache_hit) case TransportErrorKind.TIMEOUT: traceback_exception: Final = ( diff --git a/litellm/llms/azure/common_utils.py b/litellm/llms/azure/common_utils.py index c7127db88dc..b1f0065cec4 100644 --- a/litellm/llms/azure/common_utils.py +++ b/litellm/llms/azure/common_utils.py @@ -8,7 +8,7 @@ from contextlib import closing from functools import lru_cache from types import MappingProxyType from typing import Final, Literal, NamedTuple, cast -from urllib.parse import urlsplit, urlunsplit +from urllib.parse import SplitResult, parse_qsl, urlsplit, urlunsplit import httpx from openai import AsyncAzureOpenAI, AsyncOpenAI, AzureOpenAI, OpenAI @@ -334,23 +334,28 @@ _DEPLOYMENT_OPERATION_PATH: Final = re.compile( ) -def _azure_deployment_base_url(deployment_url: str) -> str: - parts: Final = urlsplit(deployment_url) - operation_path: Final = _DEPLOYMENT_OPERATION_PATH.fullmatch(parts.path) - deployment_path: Final = parts.path if operation_path is None else operation_path["deployment"] - return urlunsplit(parts._replace(path=deployment_path, query="", fragment="")) +def _azure_deployment_base_url(deployment_url: SplitResult) -> str: + operation_path: Final = _DEPLOYMENT_OPERATION_PATH.fullmatch(deployment_url.path) + deployment_path: Final = deployment_url.path if operation_path is None else operation_path["deployment"] + return urlunsplit(deployment_url._replace(path=deployment_path, query="", fragment="")) -def select_azure_base_url_or_endpoint(azure_client_params: dict): +def _azure_deployment_extra_query(deployment_url: SplitResult) -> Mapping[str, str]: + return {name: value for name, value in parse_qsl(deployment_url.query) if name != "api-version"} + + +def select_azure_base_url_or_endpoint(azure_client_params: Mapping[str, object]): azure_endpoint: Final = azure_client_params.get("azure_endpoint", None) - if isinstance(azure_endpoint, str): - # see : https://github.com/openai/openai-python/blob/3d61ed42aba652b547029095a7eb269ad4e1e957/src/openai/lib/azure.py#L192 - if "/openai/deployments" in azure_endpoint: - # this is base_url, not an azure_endpoint - azure_client_params["base_url"] = _azure_deployment_base_url(azure_endpoint) - azure_client_params.pop("azure_endpoint") - - return azure_client_params + # see : https://github.com/openai/openai-python/blob/3d61ed42aba652b547029095a7eb269ad4e1e957/src/openai/lib/azure.py#L192 + if not isinstance(azure_endpoint, str) or "/openai/deployments" not in azure_endpoint: + return dict(azure_client_params) + deployment_url: Final = urlsplit(azure_endpoint) + extra_query: Final = _azure_deployment_extra_query(deployment_url) + return { + **{name: value for name, value in azure_client_params.items() if name != "azure_endpoint"}, + "base_url": _azure_deployment_base_url(deployment_url), + **({"default_query": extra_query} if extra_query else {}), + } def get_azure_ad_token( diff --git a/litellm/responses/streaming_iterator.py b/litellm/responses/streaming_iterator.py index e2376f4ad71..723e0695af2 100644 --- a/litellm/responses/streaming_iterator.py +++ b/litellm/responses/streaming_iterator.py @@ -228,9 +228,9 @@ _ERROR_CODE_HTTP_STATUS: Final[Mapping[str, int]] = MappingProxyType( "insufficient_quota": 429, "vector_store_timeout": 504, "invalid_prompt": 400, - "data_residency_mismatch": 400, - "bio_policy": 400, - "misalignment_policy_violation": 403, + "data_residency_mismatch": 500, + "bio_policy": 500, + "misalignment_policy_violation": 500, "invalid_image": 400, "invalid_image_format": 400, "invalid_base64_image": 400, diff --git a/tests/unit/litellm_core_utils/test_streaming_handler.py b/tests/unit/litellm_core_utils/test_streaming_handler.py index 1c31f2c77f4..6915aaf4edc 100644 --- a/tests/unit/litellm_core_utils/test_streaming_handler.py +++ b/tests/unit/litellm_core_utils/test_streaming_handler.py @@ -4760,7 +4760,9 @@ class TransportErrorAfterChunksIterator: raise StopAsyncIteration -def _reset_test_chunk(content: Optional[str] = None, finish_reason: Optional[str] = None) -> ModelResponseStream: +def _reset_test_chunk( + content: Optional[str] = None, finish_reason: Optional[str] = None, index: int = 0 +) -> ModelResponseStream: return ModelResponseStream( id="chatcmpl-reset-test", created=1783458104, @@ -4768,7 +4770,7 @@ def _reset_test_chunk(content: Optional[str] = None, finish_reason: Optional[str object="chat.completion.chunk", choices=[ StreamingChoices( - index=0, + index=index, delta=Delta(content=content), finish_reason=finish_reason, ) @@ -4940,6 +4942,47 @@ async def test_sdk3_dropped_connection_after_finish_reason_ends_the_stream_clean assert [chunk.choices[0].finish_reason for chunk in chunks if chunk.choices[0].finish_reason] == ["stop"] +def _two_choice_stream( + finished_choices: Sequence[int], sync_mode: bool, logging_obj: Logging +) -> CustomStreamWrapper: + logging_obj.update_environment_variables(litellm_params={}, optional_params={"n": 2}) + return _openai_stream_over( + ( + _reset_test_chunk(content="Hi", index=0), + _reset_test_chunk(content="Hel", index=1), + *(_reset_test_chunk(finish_reason="stop", index=index) for index in finished_choices), + ), + _httpx2_read_error(), + sync_mode, + logging_obj, + ) + + +@pytest.mark.asyncio +@_SYNC_AND_ASYNC +async def test_sdk3_dropped_connection_while_another_requested_choice_streams_raises( + logging_obj: Logging, sync_mode: bool +): + wrapper: Final = _two_choice_stream((0,), sync_mode, logging_obj) + + with pytest.raises(MidStreamFallbackError) as excinfo: + await _collect(wrapper, sync_mode) + + assert isinstance(excinfo.value.original_exception, litellm.APIConnectionError) + + +@pytest.mark.asyncio +@_SYNC_AND_ASYNC +async def test_sdk3_dropped_connection_after_every_requested_choice_finished_ends_cleanly( + logging_obj: Logging, sync_mode: bool +): + wrapper: Final = _two_choice_stream((0, 1), sync_mode, logging_obj) + + chunks: Final = await _collect(wrapper, sync_mode) + + assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks if chunk.choices) == "HiHel" + + @pytest.mark.asyncio @_SYNC_AND_ASYNC @_SDK3_DROPPED_CONNECTIONS diff --git a/tests/unit/llms/azure/test_azure_common_utils.py b/tests/unit/llms/azure/test_azure_common_utils.py index 1dc7ff74323..6063db4d299 100644 --- a/tests/unit/llms/azure/test_azure_common_utils.py +++ b/tests/unit/llms/azure/test_azure_common_utils.py @@ -2360,3 +2360,35 @@ def test_an_azure_client_litellm_built_its_own_http_client_for_is_still_closed(m closer.reap() assert wrapper.is_closed() is True + + +def test_full_deployment_url_keeps_its_extra_query_params_on_the_sdk_request(monkeypatch: pytest.MonkeyPatch) -> None: + def echo_url(request: httpx.Request) -> httpx.Response: + return httpx.Response( + 200, + json={ + "id": "chatcmpl-url", + "object": "chat.completion", + "created": 0, + "model": "gpt", + "choices": [ + {"index": 0, "finish_reason": "stop", "message": {"role": "assistant", "content": str(request.url)}} + ], + }, + ) + + monkeypatch.setattr(litellm, "client_session", httpx.Client(transport=httpx.MockTransport(echo_url))) + client: Final = BaseAzureLLM().get_azure_openai_client( + api_key="azure-key", + api_base="https://gateway.example.com/openai/deployments/gpt/chat/completions" + "?api-version=2024-10-21&subscription-key=abc", + api_version="2025-02-01-preview", + litellm_params={}, + _is_async=False, + ) + + response: Final = client.chat.completions.create(model="gpt", messages=[{"role": "user", "content": "hi"}]) + + sent: Final = httpx.URL(response.choices[0].message.content or "") + assert sent.path == "/openai/deployments/gpt/chat/completions" + assert dict(sent.params) == {"api-version": "2025-02-01-preview", "subscription-key": "abc"} diff --git a/tests/unit/llms/openai/test_openai_file_content_streaming.py b/tests/unit/llms/openai/test_openai_file_content_streaming.py index 1455fd45e67..466918267f6 100644 --- a/tests/unit/llms/openai/test_openai_file_content_streaming.py +++ b/tests/unit/llms/openai/test_openai_file_content_streaming.py @@ -1,13 +1,6 @@ -import importlib -from types import ModuleType - import pytest -from typing import AsyncIterator, Final, Iterator, cast +from typing import AsyncIterator, Iterator, cast -from openai import AsyncOpenAI, OpenAI -from openai._types import Response as SDKResponse - -import litellm from litellm.files import main as files_main from litellm.files.streaming import FileContentStreamingResponse from litellm.files.types import FileContentStreamingResult @@ -330,80 +323,3 @@ def test_file_content_streaming_passes_exception_to_context_manager_exit(): assert response_cm.exc_info[0] is RuntimeError assert response_cm.exc_info[1] is exc_info.value assert response_cm.exc_info[2] is not None - - -@pytest.fixture(params=("httpx", "sdk")) -def http_backend(request: pytest.FixtureRequest) -> ModuleType: - return importlib.import_module("httpx" if request.param == "httpx" else SDKResponse.__module__) - - -_TRANSPORT_FAILURES: Final = pytest.mark.parametrize( - ("failure_name", "public_error"), - ( - ("ReadTimeout", litellm.Timeout), - ("ReadError", litellm.APIConnectionError), - ("RemoteProtocolError", litellm.APIConnectionError), - ), -) - - -def _body_then_failure_transport(http_backend: ModuleType, failure: Exception) -> object: - class BodyThenFailure(http_backend.SyncByteStream, http_backend.AsyncByteStream): - def __iter__(self) -> Iterator[bytes]: - yield b"hello" - raise failure - - async def __aiter__(self) -> AsyncIterator[bytes]: - yield b"hello" - raise failure - - return http_backend.MockTransport( - lambda _request: http_backend.Response( - 200, headers={"content-type": "application/octet-stream"}, stream=BodyThenFailure() - ) - ) - - -@_TRANSPORT_FAILURES -def test_file_content_stream_raises_litellm_errors_when_the_transport_fails_mid_body( - http_backend: ModuleType, failure_name: str, public_error: type[Exception] -): - failure: Final = getattr(http_backend, failure_name)("transport failed mid-body") - - with http_backend.Client(transport=_body_then_failure_transport(http_backend, failure)) as http_client: - result: Final = litellm.file_content( - file_id="file-abc123", - custom_llm_provider="openai", - client=OpenAI(api_key="transport-only", http_client=http_client), - stream=True, - chunk_size=len(b"hello"), - ) - stream: Final = cast(Iterator[bytes], result.stream_iterator) - assert next(stream) == b"hello" - with pytest.raises(public_error) as excinfo: - next(stream) - - assert excinfo.value.__cause__ is failure - - -@pytest.mark.asyncio -@_TRANSPORT_FAILURES -async def test_afile_content_stream_raises_litellm_errors_when_the_transport_fails_mid_body( - http_backend: ModuleType, failure_name: str, public_error: type[Exception] -): - failure: Final = getattr(http_backend, failure_name)("transport failed mid-body") - - async with http_backend.AsyncClient(transport=_body_then_failure_transport(http_backend, failure)) as http_client: - result: Final = await litellm.afile_content( - file_id="file-abc123", - custom_llm_provider="openai", - client=AsyncOpenAI(api_key="transport-only", http_client=http_client), - stream=True, - chunk_size=len(b"hello"), - ) - stream: Final = cast(AsyncIterator[bytes], result.stream_iterator) - assert await stream.__anext__() == b"hello" - with pytest.raises(public_error) as excinfo: - await stream.__anext__() - - assert excinfo.value.__cause__ is failure diff --git a/tests/unit/responses/test_streaming_iterator_error_events.py b/tests/unit/responses/test_streaming_iterator_error_events.py index 480946e7768..bf0eaaba98b 100644 --- a/tests/unit/responses/test_streaming_iterator_error_events.py +++ b/tests/unit/responses/test_streaming_iterator_error_events.py @@ -687,9 +687,9 @@ def test_every_openai_sdk_response_error_code_has_explicit_status_mapping(): ("empty_image_file", 400), ("failed_to_download_image", 400), ("image_file_not_found", 400), - ("data_residency_mismatch", 400), - ("bio_policy", 400), - ("misalignment_policy_violation", 403), + ("data_residency_mismatch", 500), + ("bio_policy", 500), + ("misalignment_policy_violation", 500), ("totally_unknown_future_code", 500), ("429", 429), ("503", 503), From 09b49357cbbe9fe6b3b9b3a63a8167e2313dd432 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 28 Oct 2026 11:52:23 -0700 Subject: [PATCH 10/10] fix: end text completion n=2 streams cleanly, keep Azure image gateway query params, tighten SDK 3 test fixtures --- .circleci/scripts/run_integration.sh | 6 ++ .../litellm_core_utils/streaming_handler.py | 25 ++++-- litellm/llms/azure/azure.py | 30 ++++--- pyproject.toml | 2 +- .../_retained_openai_consumer.py | 56 ++++++++++++ .../compatibility/test_openai_consumer.py | 87 ++++++++++--------- tests/unit/conftest.py | 8 +- .../test_streaming_handler.py | 44 ++++++++++ tests/unit/llms/azure/test_azure.py | 21 +++++ 9 files changed, 217 insertions(+), 62 deletions(-) create mode 100644 tests/integration/compatibility/_retained_openai_consumer.py diff --git a/.circleci/scripts/run_integration.sh b/.circleci/scripts/run_integration.sh index 00b3c5f8fdd..2731dc9a8c4 100644 --- a/.circleci/scripts/run_integration.sh +++ b/.circleci/scripts/run_integration.sh @@ -70,6 +70,12 @@ trap 'exit 143' TERM export PATH="$PWD/.venv/bin:$PATH" export PYTHONPATH="$PWD:$PWD/tests:$PWD/tests/e2e" +if [ "$suite" = extensions ]; then + retained_openai="$(mktemp -d)/retained-openai" + uv venv --quiet --python 3.12 "$retained_openai" + uv pip install --quiet --python "$retained_openai/bin/python" --exclude-newer 2026-04-29T00:00:00Z openai==2.33.0 + export RETAINED_OPENAI_PYTHON="$retained_openai/bin/python" +fi export DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:5432/circle_test" export REDIS_HOST=127.0.0.1 REDIS_PORT=6379 export LITELLM_MASTER_KEY="sk-$(openssl rand -hex 16)" LITELLM_SALT_KEY=sk-integration-salt diff --git a/litellm/litellm_core_utils/streaming_handler.py b/litellm/litellm_core_utils/streaming_handler.py index 214f4803f1a..7c826bea82a 100644 --- a/litellm/litellm_core_utils/streaming_handler.py +++ b/litellm/litellm_core_utils/streaming_handler.py @@ -14,6 +14,7 @@ from typing import TYPE_CHECKING, Any, Final, NoReturn, Protocol, TypeVar, cast import anyio import httpx +from openai.types import Completion from openai.types.chat import ChatCompletionChunk from openai.types.chat.chat_completion_chunk import Choice as ChunkChoice from openai.types.chat.chat_completion_chunk import ChoiceDelta @@ -93,12 +94,17 @@ def _next_sync_or_exhausted(it: Iterator[object]) -> object: return _SYNC_ITER_EXHAUSTED -def _first_choice_index(chunk: ChatCompletionChunk | ModelResponseStream) -> int: +_CHOICE_CHUNK_TYPES: Final = (ChatCompletionChunk, ModelResponseStream, Completion) + + +def _first_choice_index(chunk: ChatCompletionChunk | ModelResponseStream | Completion | None) -> int: match chunk: case ChatCompletionChunk(choices=[first, *_]): return first.index case ModelResponseStream(choices=[first, *_]): return first.index + case Completion(choices=[first, *_]): + return first.index case _: return 0 @@ -343,7 +349,7 @@ class CustomStreamWrapper: self._provider_response_model: str | None = None self.received_finish_reason: str | None = None self.intermittent_finish_reason: str | None = None # finish reasons that show up mid-stream - self._finished_choice_indices: frozenset[int] = frozenset() + self._finished_choice_indices: frozenset[int] | None = None self.special_tokens = [ "<|assistant|>", "<|system|>", @@ -1503,6 +1509,7 @@ class CustomStreamWrapper: print_verbose(f"completion obj content: {completion_obj['content']}") if response_obj["is_finished"]: self.received_finish_reason = response_obj["finish_reason"] + self._record_finished_choice(chunk if isinstance(chunk, _CHOICE_CHUNK_TYPES) else None) if response_obj["usage"] is not None: _text_completion_usage: Final[Usage] = response_obj["usage"] setattr( @@ -1542,6 +1549,7 @@ class CustomStreamWrapper: print_verbose(f"completion obj content: {completion_obj['content']}") if response_obj["is_finished"]: self.received_finish_reason = response_obj["finish_reason"] + self._record_finished_choice(chunk if isinstance(chunk, _CHOICE_CHUNK_TYPES) else None) elif self.custom_llm_provider == "cached_response": cached_chunk: Final = cast(ModelResponseStream, chunk) cached_choice: Final = cached_chunk.choices[0] if cached_chunk.choices else None @@ -1586,9 +1594,7 @@ class CustomStreamWrapper: f"{self.custom_llm_provider} raised a streaming error - finish_reason: error, no content string given. Received Chunk={response_obj}" ) self.received_finish_reason = response_obj["finish_reason"] - self._finished_choice_indices = self._finished_choice_indices | { - _first_choice_index(chunk) if isinstance(chunk, (ChatCompletionChunk, ModelResponseStream)) else 0 - } + self._record_finished_choice(chunk if isinstance(chunk, _CHOICE_CHUNK_TYPES) else None) if response_obj.get("original_chunk", None) is not None: if hasattr(response_obj["original_chunk"], "id"): model_response = self.set_model_id(response_obj["original_chunk"].id, model_response) @@ -2225,11 +2231,18 @@ class CustomStreamWrapper: raise raise StopIteration from e + def _record_finished_choice(self, chunk: ChatCompletionChunk | ModelResponseStream | Completion | None) -> None: + self._finished_choice_indices = (self._finished_choice_indices or frozenset()) | {_first_choice_index(chunk)} + def _every_choice_finished(self) -> bool: if self.received_finish_reason is None: return False requested_choices: Final = self._requested_choice_count() - return requested_choices <= 1 or len(self._finished_choice_indices) >= requested_choices + match self._finished_choice_indices: + case None: + return True + case finished_choices: + return requested_choices <= 1 or len(finished_choices) >= requested_choices def _requested_choice_count(self) -> int: match getattr(self.logging_obj, "optional_params", None): diff --git a/litellm/llms/azure/azure.py b/litellm/llms/azure/azure.py index 02f1ba3472a..f107ca9edae 100644 --- a/litellm/llms/azure/azure.py +++ b/litellm/llms/azure/azure.py @@ -1,7 +1,7 @@ import asyncio import json import time -from collections.abc import Callable, Coroutine +from collections.abc import Callable, Coroutine, Mapping from typing import Final import httpx @@ -13,7 +13,7 @@ from openai import ( BadRequestError, OpenAI, ) -from pydantic import BaseModel, TypeAdapter +from pydantic import BaseModel, ConfigDict, TypeAdapter import litellm from litellm.constants import AZURE_OPERATION_POLLING_TIMEOUT, DEFAULT_MAX_RETRIES @@ -140,6 +140,16 @@ def check_dynamic_azure_params( _check_dynamic_azure_params = check_dynamic_azure_params +class _AzureDefaultQuery(BaseModel): + model_config = ConfigDict(frozen=True) + + default_query: Mapping[str, str] | None = None + + +def _with_default_query(url: str, default_query: Mapping[str, str] | None) -> str: + return str(httpx.URL(url).copy_merge_params(default_query)) if default_query else url + + class AzureChatCompletion(BaseAzureLLM, BaseLLM): def __init__(self) -> None: super().__init__() @@ -1110,6 +1120,7 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): "/" ) api_version: Final[str] = azure_client_params.get("api_version", "") + default_query: Final = _AzureDefaultQuery.model_validate(azure_client_params).default_query if model is None: model = "" @@ -1134,17 +1145,12 @@ class AzureChatCompletion(BaseAzureLLM, BaseLLM): route="/openai/images/generations", ) if v1_url is not None: - return v1_url + return _with_default_query(v1_url, default_query) - if "/openai/deployments/" in api_base: - base_url_with_deployment = api_base - else: - base_url_with_deployment = api_base + "/openai/deployments/" + model - - base_url_with_deployment += "/images/generations" - base_url_with_deployment += "?api-version=" + api_version - - return base_url_with_deployment + deployment_url: Final = ( + api_base if "/openai/deployments/" in api_base else f"{api_base}/openai/deployments/{model}" + ) + return _with_default_query(f"{deployment_url}/images/generations?api-version={api_version}", default_query) async def aimage_generation( self, diff --git a/pyproject.toml b/pyproject.toml index 90f9958b9b2..4fd3208f5f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -369,7 +369,7 @@ constraint-dependencies = [ override-dependencies = [ # a2a-sdk 1.x requires packaging>=24.0; lunary 1.4.x still caps at <24.0. "packaging>=24.0", - # semantic-router (through 1.1.0) caps openai at <3.0.0 but only imports it for encoders LiteLLM never builds. + # semantic-router (through 1.1.0) caps openai at <3.0.0, yet its OpenAI encoder for auto router files runs on SDK 3. "openai>=2.20.0,<4.0.0", "cryptography>=50.0.0,<51.0", "mcp>=2.3.0,<3", diff --git a/tests/integration/compatibility/_retained_openai_consumer.py b/tests/integration/compatibility/_retained_openai_consumer.py new file mode 100644 index 00000000000..4dec85c0bb8 --- /dev/null +++ b/tests/integration/compatibility/_retained_openai_consumer.py @@ -0,0 +1,56 @@ +import asyncio +import json +import sys +from importlib.metadata import version +from typing import Final + +import httpx +from openai import AsyncOpenAI, OpenAI +from openai.types.chat import ChatCompletion + + +def _summary(response: ChatCompletion) -> dict[str, object]: + choice: Final = response.choices[0] + calls: Final = choice.message.tool_calls or () + return { + "id": response.id, + "object": response.object, + "usage": [response.usage.prompt_tokens, response.usage.completion_tokens, response.usage.total_tokens] + if response.usage + else None, + "finish_reason": choice.finish_reason, + "content": choice.message.content, + "tool_calls": [[call.id, call.function.name, json.loads(call.function.arguments)] for call in calls], + } + + +async def _asynchronous(base_url: str, key: str, parameters: dict[str, object]) -> tuple[ChatCompletion, ...]: + plain: Final = {name: value for name, value in parameters.items() if name != "tools"} + async with AsyncOpenAI( + api_key=key, base_url=base_url, max_retries=0, http_client=httpx.AsyncClient(timeout=10, trust_env=False) + ) as client: + return (await client.chat.completions.create(**parameters), await client.chat.completions.create(**plain)) + + +def main(base_url: str, key: str, parameters_json: str) -> None: + parameters: Final = json.loads(parameters_json) + plain: Final = {name: value for name, value in parameters.items() if name != "tools"} + with OpenAI( + api_key=key, base_url=base_url, max_retries=0, http_client=httpx.Client(timeout=10, trust_env=False) + ) as client: + sync_tools: Final[ChatCompletion] = client.chat.completions.create(**parameters) + sync_text: Final[ChatCompletion] = client.chat.completions.create(**plain) + async_tools, async_text = asyncio.run(_asynchronous(base_url, key, parameters)) + print( + json.dumps( + { + "openai": version("openai"), + "tools": [_summary(sync_tools), _summary(async_tools)], + "text": [_summary(sync_text), _summary(async_text)], + } + ) + ) + + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/tests/integration/compatibility/test_openai_consumer.py b/tests/integration/compatibility/test_openai_consumer.py index 6150995723b..e00617be855 100644 --- a/tests/integration/compatibility/test_openai_consumer.py +++ b/tests/integration/compatibility/test_openai_consumer.py @@ -1,7 +1,9 @@ import json +import os +import subprocess import uuid from collections.abc import Callable -from importlib.metadata import version +from pathlib import Path from typing import Final import httpx @@ -11,12 +13,22 @@ from integration._support.wire import Reply, Request, wire_server from openai import AsyncAzureOpenAI, AsyncOpenAI, AzureOpenAI, OpenAI -@pytest.mark.covers("other.compatibility.openai.retained_client_parses_tools_and_usage") -async def test_retained_openai_clients_parse_real_proxy_tool_and_usage_responses(gateway: Gateway) -> None: - assert version("openai") == "2.33.0", ( - "Retain this consumer version independently before upgrading the candidate lock" - ) +_RETAINED_OPENAI_VERSION: Final = "2.33.0" +_RETAINED_CONSUMER: Final = Path(__file__).with_name("_retained_openai_consumer.py") + +def _retained_openai_python() -> str: + python: Final = os.environ.get("RETAINED_OPENAI_PYTHON") + if not python: + pytest.fail( + f"Set RETAINED_OPENAI_PYTHON to a Python with openai=={_RETAINED_OPENAI_VERSION} installed, " + "as .circleci/scripts/run_integration.sh does for the extensions suite" + ) + return python + + +@pytest.mark.covers("other.compatibility.openai.retained_client_parses_tools_and_usage") +def test_retained_openai_clients_parse_real_proxy_tool_and_usage_responses(gateway: Gateway) -> None: def provider(request: Request) -> Reply: assert request.method == "POST" and request.target == "/v1/chat/completions" body: Final = json.loads(request.body) @@ -72,40 +84,35 @@ async def test_retained_openai_clients_parse_real_proxy_tool_and_usage_responses ], "extra_body": {"cache": {"no-cache": True}}, } - plain: Final = {name: value for name, value in parameters.items() if name != "tools"} - with OpenAI( - api_key=key, - base_url=str(gateway.client.base_url).rstrip("/") + "/v1", - max_retries=0, - http_client=httpx.Client(timeout=10, trust_env=False), - ) as sync: - first: Final = sync.chat.completions.create(**parameters) - first_text: Final = sync.chat.completions.create(**plain) - async with AsyncOpenAI( - api_key=key, - base_url=str(gateway.client.base_url).rstrip("/") + "/v1", - max_retries=0, - http_client=httpx.AsyncClient(timeout=10, trust_env=False), - ) as asynchronous: - second: Final = await asynchronous.chat.completions.create(**parameters) - second_text: Final = await asynchronous.chat.completions.create(**plain) - assert len({response.id for response in (first, second, first_text, second_text)}) == 4 - for response in (first, second, first_text, second_text): - assert response.object == "chat.completion" - assert ( - response.usage.prompt_tokens == 11 - and response.usage.completion_tokens == 4 - and response.usage.total_tokens == 15 - ) - for response in (first, second): - assert response.choices[0].finish_reason == "tool_calls" - call: Final = response.choices[0].message.tool_calls[0] - assert call.id == "synthetic-call" and call.function.name == "add" - assert json.loads(call.function.arguments) == {"a": 3, "b": 5} - for response in (first_text, second_text): - assert response.choices[0].finish_reason == "stop" - assert response.choices[0].message.content == "Synthetic answer: 8" - assert not response.choices[0].message.tool_calls + consumer: Final = subprocess.run( + ( + _retained_openai_python(), + "-I", + str(_RETAINED_CONSUMER), + str(gateway.client.base_url).rstrip("/") + "/v1", + key, + json.dumps(parameters), + ), + capture_output=True, + text=True, + timeout=60, + check=False, + ) + assert consumer.returncode == 0, consumer.stderr + observed: Final = json.loads(consumer.stdout) + assert observed["openai"] == _RETAINED_OPENAI_VERSION + responses: Final = (*observed["tools"], *observed["text"]) + assert len({response["id"] for response in responses}) == 4 + for response in responses: + assert response["object"] == "chat.completion" + assert response["usage"] == [11, 4, 15] + for response in observed["tools"]: + assert response["finish_reason"] == "tool_calls" + assert response["tool_calls"] == [["synthetic-call", "add", {"a": 3, "b": 5}]] + for response in observed["text"]: + assert response["finish_reason"] == "stop" + assert response["content"] == "Synthetic answer: 8" + assert response["tool_calls"] == [] assert len(wire.drain()) == 4 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 6c740dc45f5..3d70dd656c4 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -6,6 +6,7 @@ import sys from collections.abc import Callable, Coroutine, Iterator from dataclasses import dataclass, field from functools import partial +from importlib.metadata import version from pathlib import Path from types import MappingProxyType from typing import Final, Literal, TypeAlias @@ -435,11 +436,12 @@ def fake_provider_credentials(monkeypatch: pytest.MonkeyPatch) -> None: @pytest.fixture def openai_token_exchange_via_httpx(monkeypatch: pytest.MonkeyPatch) -> None: - workload_module: Final = sys.modules.get("openai.auth._workload") - native_client_factory: Final = getattr(workload_module, "DefaultHttpx2Client", None) - if native_client_factory is None: + if int(version("openai").split(".")[0]) < 3: return import httpx2 + import openai.auth._workload as workload_module + + native_client_factory: Final = workload_module.DefaultHttpx2Client def send_through_httpx(request: httpx2.Request) -> httpx2.Response: with httpx.Client(trust_env=False) as client: diff --git a/tests/unit/litellm_core_utils/test_streaming_handler.py b/tests/unit/litellm_core_utils/test_streaming_handler.py index 6915aaf4edc..157c75f700f 100644 --- a/tests/unit/litellm_core_utils/test_streaming_handler.py +++ b/tests/unit/litellm_core_utils/test_streaming_handler.py @@ -14,6 +14,7 @@ from unittest.mock import AsyncMock, MagicMock, Mock, patch import httpx import openai import pytest +from openai.types import Completion, CompletionChoice from openai.types.chat import ChatCompletionChunk from pydantic import BaseModel, TypeAdapter import litellm @@ -4983,6 +4984,49 @@ async def test_sdk3_dropped_connection_after_every_requested_choice_finished_end assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks if chunk.choices) == "HiHel" +def _text_completion_chunk(index: int, text: str = "", finish_reason: Optional[str] = None) -> Completion: + return Completion.model_construct( + id="cmpl-two-choices", + object="text_completion", + created=1, + model="gpt-3.5-turbo-instruct", + choices=[CompletionChoice.model_construct(index=index, text=text, finish_reason=finish_reason, logprobs=None)], + ) + + +@pytest.mark.asyncio +@_SYNC_AND_ASYNC +@pytest.mark.parametrize("provider", ("text-completion-openai", "azure_text")) +@pytest.mark.parametrize( + ("finished_choices", "ends_cleanly"), (((0, 1), True), ((1,), False)), ids=("both_finished", "choice_0_open") +) +async def test_two_choice_text_completion_stream_dropped_before_done_ends_cleanly_only_once_both_finish( + logging_obj: Logging, sync_mode: bool, provider: str, finished_choices: Sequence[int], ends_cleanly: bool +): + logging_obj.update_environment_variables(litellm_params={}, optional_params={"n": 2}) + stream: Final = _chunks_then_failure( + ( + _text_completion_chunk(0, "Hi"), + _text_completion_chunk(1, "Hel"), + *(_text_completion_chunk(index, finish_reason="stop") for index in finished_choices), + ), + _httpx2_read_error(), + ) + wrapper: Final = CustomStreamWrapper( + completion_stream=stream if sync_mode else _as_async(stream), + model="gpt-3.5-turbo-instruct", + custom_llm_provider=provider, + logging_obj=logging_obj, + ) + + if not ends_cleanly: + with pytest.raises(MidStreamFallbackError): + await _collect(wrapper, sync_mode) + return + chunks: Final = await _collect(wrapper, sync_mode) + assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks if chunk.choices) == "HiHel" + + @pytest.mark.asyncio @_SYNC_AND_ASYNC @_SDK3_DROPPED_CONNECTIONS diff --git a/tests/unit/llms/azure/test_azure.py b/tests/unit/llms/azure/test_azure.py index 7d5afde68ef..6858e90ac76 100644 --- a/tests/unit/llms/azure/test_azure.py +++ b/tests/unit/llms/azure/test_azure.py @@ -271,6 +271,27 @@ def test_process_azure_endpoint_url(api_base, model, expected_endpoint): assert result == expected_endpoint, "Unexpected endpoint" +@respx.mock +@pytest.mark.parametrize("api_version", ("2025-04-01-preview", "v1")) +def test_azure_image_generation_keeps_deployment_url_query_params(api_version: str): + route: Final = respx.post(url__regex=r"https://azure-gateway\.example\.com/.*").mock( + return_value=httpx.Response(200, json={"created": 1, "data": [{"b64_json": "aGk="}]}) + ) + + litellm.image_generation( + model="azure/gpt-image-1", + prompt="a red circle", + api_base="https://azure-gateway.example.com/openai/deployments/gpt-image-1/images/generations?subscription-key=gateway-key", + api_version=api_version, + api_key="test-azure-key", + ) + + request_url: Final = route.calls.last.request.url + assert request_url.path.endswith("/images/generations") + assert request_url.params["subscription-key"] == "gateway-key" + assert request_url.params["api-version"] == api_version + + @patch("azure.identity.UsernamePasswordCredential") @patch("azure.identity.get_bearer_token_provider") def test_get_azure_ad_token_from_username_password(mock_get_bearer_token_provider, mock_credential):