feat(openrouter): add langchain-openrouter provider package (#35211)

Add a first-party `langchain-openrouter` partner package
(`ChatOpenRouter`) that wraps the official `openrouter` Python SDK,
providing native support for OpenRouter-specific features that
`ChatOpenAI` intentionally does not handle.

Also adds scope-clarifying docstrings to `ChatOpenAI` / `BaseChatOpenAI`
warning users away from using `base_url` overrides with third-party
providers.

---

Closes #31325
Closes #32967
Closes #32977
Closes #32981
Closes #33643
Closes #33757
Closes #34056
Closes #34797
Closes #34962

Supersedes #33902, #34867 (thank you @elonfeng and @okamototk for your
initial work on this!)

---

Bugs with upstream sdk:
- https://github.com/OpenRouterTeam/python-sdk/issues/38
- https://github.com/OpenRouterTeam/python-sdk/issues/51
- https://github.com/OpenRouterTeam/python-sdk/issues/52
This commit is contained in:
Mason Daugherty
2026-02-15 02:09:13 -05:00
committed by GitHub
parent b97c629f9a
commit f9fd7be695
29 changed files with 8958 additions and 4 deletions

View File

@@ -102,6 +102,7 @@ jobs:
nomic
ollama
openai
openrouter
perplexity
qdrant
xai

View File

@@ -273,6 +273,11 @@ SERIALIZABLE_MAPPING: dict[tuple[str, ...], tuple[str, ...]] = {
"chat_models",
"ChatGroq",
),
("langchain_openrouter", "chat_models", "ChatOpenRouter"): (
"langchain_openrouter",
"chat_models",
"ChatOpenRouter",
),
("langchain_xai", "chat_models", "ChatXAI"): (
"langchain_xai",
"chat_models",

View File

@@ -66,6 +66,7 @@ _BUILTIN_PROVIDERS: dict[str, tuple[str, str, Callable[..., BaseChatModel]]] = {
"nvidia": ("langchain_nvidia_ai_endpoints", "ChatNVIDIA", _call),
"ollama": ("langchain_ollama", "ChatOllama", _call),
"openai": ("langchain_openai", "ChatOpenAI", _call),
"openrouter": ("langchain_openrouter", "ChatOpenRouter", _call),
"perplexity": ("langchain_perplexity", "ChatPerplexity", _call),
"together": ("langchain_together", "ChatTogether", _call),
"upstage": ("langchain_upstage", "ChatUpstage", _call),
@@ -277,6 +278,7 @@ def init_chat_model(
- `ibm` -> [`langchain-ibm`](https://docs.langchain.com/oss/python/integrations/providers/ibm)
- `nvidia` -> [`langchain-nvidia-ai-endpoints`](https://docs.langchain.com/oss/python/integrations/providers/nvidia)
- `xai` -> [`langchain-xai`](https://docs.langchain.com/oss/python/integrations/providers/xai)
- `openrouter` -> [`langchain-openrouter`](https://docs.langchain.com/oss/python/integrations/providers/openrouter)
- `perplexity` -> [`langchain-perplexity`](https://docs.langchain.com/oss/python/integrations/providers/perplexity)
- `upstage` -> [`langchain-upstage`](https://docs.langchain.com/oss/python/integrations/providers/upstage)

View File

@@ -527,7 +527,7 @@ typing = [
[[package]]
name = "langchain-core"
version = "1.2.11"
version = "1.2.12"
source = { editable = "../core" }
dependencies = [
{ name = "jsonpatch" },
@@ -695,7 +695,7 @@ test-integration = [
{ name = "httpx", specifier = ">=0.27.0,<1.0.0" },
{ name = "numpy", marker = "python_full_version < '3.13'", specifier = ">=1.26.4" },
{ name = "numpy", marker = "python_full_version >= '3.13'", specifier = ">=2.1.0" },
{ name = "pillow", specifier = ">=10.3.0,<12.0.0" },
{ name = "pillow", specifier = ">=10.3.0,<13.0.0" },
]
typing = [
{ name = "langchain-core", editable = "../core" },

View File

@@ -1,4 +1,15 @@
"""OpenAI chat wrapper."""
"""OpenAI chat wrapper.
!!! warning "API scope"
`ChatOpenAI` targets
[official OpenAI API specifications](https://github.com/openai/openai-openapi)
only. Non-standard response fields added by third-party providers (e.g.,
`reasoning_content`, `reasoning_details`) are **not** extracted or
preserved. If you are pointing `base_url` at a provider such as
OpenRouter, vLLM, or DeepSeek, use the corresponding provider-specific
LangChain package instead (e.g., `ChatDeepSeek`, `ChatOpenRouter`).
"""
from __future__ import annotations
@@ -511,7 +522,14 @@ _DictOrPydantic: TypeAlias = dict | _BM
class BaseChatOpenAI(BaseChatModel):
"""Base wrapper around OpenAI large language models for chat."""
"""Base wrapper around OpenAI large language models for chat.
This base class targets
[official OpenAI API specifications](https://github.com/openai/openai-openapi)
only. Non-standard response fields added by third-party providers (e.g.,
`reasoning_content`) are not extracted. Use a provider-specific subclass for
full provider support.
"""
client: Any = Field(default=None, exclude=True)
@@ -2262,6 +2280,16 @@ class BaseChatOpenAI(BaseChatModel):
class ChatOpenAI(BaseChatOpenAI): # type: ignore[override]
r"""Interface to OpenAI chat model APIs.
!!! warning "API scope"
`ChatOpenAI` targets
[official OpenAI API specifications](https://github.com/openai/openai-openapi)
only. Non-standard response fields added by third-party providers (e.g.,
`reasoning_content`, `reasoning_details`) are **not** extracted or
preserved. If you are pointing `base_url` at a provider such as
OpenRouter, vLLM, or DeepSeek, use the corresponding provider-specific
LangChain package instead (e.g., `ChatDeepSeek`, `ChatOpenRouter`).
???+ info "Setup"
Install `langchain-openai` and set environment variable `OPENAI_API_KEY`.

1
libs/partners/openrouter/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
__pycache__

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 LangChain, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,65 @@
.PHONY: all format lint type test tests integration_tests help extended_tests
# Default target executed when no arguments are given to make.
all: help
.EXPORT_ALL_VARIABLES:
UV_FROZEN = true
# Define a variable for the test file path.
TEST_FILE ?= tests/unit_tests/
integration_test integration_tests: TEST_FILE = tests/integration_tests/
# unit tests are run with the --disable-socket flag to prevent network calls
test tests:
uv run --group test pytest --disable-socket --allow-unix-socket $(TEST_FILE)
test_watch:
uv run --group test ptw --snapshot-update --now . -- -vv $(TEST_FILE)
# integration tests are run without the --disable-socket flag to allow network calls
integration_test integration_tests:
uv run --group test --group test_integration pytest --timeout=30 $(TEST_FILE)
######################
# LINTING AND FORMATTING
######################
# Define a variable for Python and notebook files.
PYTHON_FILES=.
MYPY_CACHE=.mypy_cache
lint format: PYTHON_FILES=.
lint_diff format_diff: PYTHON_FILES=$(shell git diff --relative=libs/partners/openrouter --name-only --diff-filter=d master | grep -E '\.py$$|\.ipynb$$')
lint_package: PYTHON_FILES=langchain_openrouter
lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
[ "$(PYTHON_FILES)" = "" ] || uv run --all-groups ruff check $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || uv run --all-groups ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) && uv run --all-groups mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
type:
mkdir -p $(MYPY_CACHE) && uv run --all-groups mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
format format_diff:
[ "$(PYTHON_FILES)" = "" ] || uv run --all-groups ruff format $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || uv run --all-groups ruff check --fix $(PYTHON_FILES)
check_imports: $(shell find langchain_openrouter -name '*.py')
uv run --all-groups python ./scripts/check_imports.py $^
######################
# HELP
######################
help:
@echo '----'
@echo 'check_imports - check imports'
@echo 'format - run code formatters'
@echo 'lint - run linters'
@echo 'type - run type checking'
@echo 'test - run unit tests'
@echo 'tests - run unit tests'
@echo 'test TEST_FILE=<test_file> - run all tests in file'

View File

@@ -0,0 +1,30 @@
# langchain-openrouter
[![PyPI - Version](https://img.shields.io/pypi/v/langchain-openrouter?label=%20)](https://pypi.org/project/langchain-openrouter/#history)
[![PyPI - License](https://img.shields.io/pypi/l/langchain-openrouter)](https://opensource.org/licenses/MIT)
[![PyPI - Downloads](https://img.shields.io/pepy/dt/langchain-openrouter)](https://pypistats.org/packages/langchain-openrouter)
[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/langchain.svg?style=social&label=Follow%20%40LangChain)](https://x.com/langchain)
## Quick Install
```bash
pip install langchain-openrouter
```
## 🤔 What is this?
This package contains the LangChain integration with [OpenRouter](https://openrouter.ai/), a unified API for hundreds of AI models across many providers.
## 📖 Documentation
For full documentation, see the [API reference](https://reference.langchain.com/python/integrations/langchain_openrouter/). For conceptual guides, tutorials, and examples on using these classes, see the [LangChain Docs](https://docs.langchain.com/oss/python/integrations/providers/openrouter).
## 📕 Releases & Versioning
See our [Releases](https://docs.langchain.com/oss/python/release-policy) and [Versioning](https://docs.langchain.com/oss/python/versioning) policies.
## 💁 Contributing
As an open-source project in a rapidly developing field, we are extremely open to contributions, whether it be in the form of a new feature, improved infrastructure, or better documentation.
For detailed information on how to contribute, see the [Contributing Guide](https://docs.langchain.com/oss/python/contributing/overview).

View File

@@ -0,0 +1,7 @@
"""LangChain OpenRouter integration."""
from langchain_openrouter.chat_models import ChatOpenRouter
__all__ = [
"ChatOpenRouter",
]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
"""Model profile data. All edits should be made in profile_augmentations.toml."""

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "langchain-openrouter"
description = "An integration package connecting OpenRouter and LangChain"
license = { text = "MIT" }
readme = "README.md"
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
version = "0.0.2"
requires-python = ">=3.10.0,<4.0.0"
dependencies = [
"langchain-core>=1.2.11,<2.0.0",
"openrouter>=0.6.0,<1.0.0",
]
[project.urls]
Homepage = "https://docs.langchain.com/oss/python/integrations/providers/openrouter"
Documentation = "https://reference.langchain.com/python/integrations/langchain_openrouter/"
Repository = "https://github.com/langchain-ai/langchain"
Issues = "https://github.com/langchain-ai/langchain/issues"
Changelog = "https://github.com/langchain-ai/langchain/releases?q=%22langchain-openrouter%22"
Twitter = "https://x.com/LangChain"
Slack = "https://www.langchain.com/join-community"
Reddit = "https://www.reddit.com/r/LangChain/"
[dependency-groups]
test = [
"pytest>=9.0.0,<10.0.0",
"pytest-asyncio>=1.3.0,<2.0.0",
"pytest-socket>=0.7.0,<1.0.0",
"pytest-watcher>=0.6.3,<1.0.0",
"pytest-timeout>=2.4.0,<3.0.0",
"langchain-tests",
]
test_integration = []
lint = ["ruff>=0.15.0,<0.16.0"]
dev = ["langchain-core"]
typing = ["mypy>=1.19.1,<2.0.0"]
[tool.uv.sources]
langchain-core = { path = "../../core", editable = true }
langchain-tests = { path = "../../standard-tests", editable = true }
[tool.mypy]
disallow_untyped_defs = "True"
[tool.ruff.format]
docstring-code-format = true
[tool.ruff.lint]
select = [ "ALL" ]
ignore = [
"COM812", # Conflicts with formatter
"PLR0913", # Too many arguments
# TODO
"ANN401",
"TC002",
"TC003",
]
unfixable = ["B028"] # People should intentionally tune the stacklevel
[tool.ruff.lint.pydocstyle]
convention = "google"
ignore-var-parameters = true # ignore missing documentation for *args and **kwargs parameters
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"
[tool.coverage.run]
omit = ["tests/*"]
[tool.pytest.ini_options]
addopts = "--strict-markers --strict-config --durations=5"
markers = [
"compile: mark placeholder test used to compile integration tests without running them",
]
asyncio_mode = "auto"
[tool.ruff.lint.extend-per-file-ignores]
"tests/**/*.py" = [
"S101", # Tests need assertions
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
"SLF001", # Private member access
"PLR2004", # Magic values are fine in tests
"D102",
# TODO
"ARG002", # Unused method argument:
]
"scripts/*.py" = [
"INP001", # Not a package
]

View File

@@ -0,0 +1 @@
"""Scripts for langchain-openrouter."""

View File

@@ -0,0 +1,19 @@
"""Script to check imports of given Python files."""
import sys
import traceback
from importlib.machinery import SourceFileLoader
if __name__ == "__main__":
files = sys.argv[1:]
has_failure = False
for file in files:
try:
SourceFileLoader("x", file).load_module()
except Exception: # noqa: PERF203, BLE001
has_failure = True
print(file) # noqa: T201
traceback.print_exc()
print() # noqa: T201
sys.exit(1 if has_failure else 0)

View File

@@ -0,0 +1 @@
"""Tests for langchain-openrouter."""

View File

@@ -0,0 +1,39 @@
"""Conftest for OpenRouter tests."""
from typing import Any
import pytest
from langchain_tests.conftest import CustomPersister, CustomSerializer, base_vcr_config
from vcr import VCR # type: ignore[import-untyped]
def remove_request_headers(request: Any) -> Any:
"""Redact all request headers to avoid leaking secrets."""
for k in request.headers:
request.headers[k] = "**REDACTED**"
return request
def remove_response_headers(response: dict) -> dict:
"""Redact all response headers."""
for k in response["headers"]:
response["headers"][k] = "**REDACTED**"
return response
@pytest.fixture(scope="session")
def vcr_config() -> dict:
"""Extend the default configuration coming from langchain_tests."""
config = base_vcr_config()
config["before_record_request"] = remove_request_headers
config["before_record_response"] = remove_response_headers
config["serializer"] = "yaml.gz"
config["path_transformer"] = VCR.ensure_suffix(".yaml.gz")
return config
def pytest_recording_configure(config: dict, vcr: VCR) -> None: # noqa: ARG001
"""Register custom VCR persister and serializer."""
vcr.register_persister(CustomPersister())
vcr.register_serializer("yaml.gz", CustomSerializer())

View File

@@ -0,0 +1 @@
"""Integration tests for langchain-openrouter."""

View File

@@ -0,0 +1,69 @@
"""Integration tests for `ChatOpenRouter` chat model."""
from __future__ import annotations
import pytest
from langchain_core.messages import AIMessageChunk, BaseMessageChunk
from pydantic import BaseModel, Field
from langchain_openrouter.chat_models import ChatOpenRouter
def test_basic_invoke() -> None:
"""Test basic invocation."""
model = ChatOpenRouter(model="openai/gpt-4o-mini", temperature=0)
response = model.invoke("Say 'hello' and nothing else.")
assert response.content
assert response.response_metadata.get("model_provider") == "openrouter"
def test_streaming() -> None:
"""Test streaming."""
model = ChatOpenRouter(model="openai/gpt-4o-mini", temperature=0)
full: BaseMessageChunk | None = None
for chunk in model.stream("Say 'hello' and nothing else."):
full = chunk if full is None else full + chunk
assert isinstance(full, AIMessageChunk)
assert full.content
def test_tool_calling() -> None:
"""Test tool calling via OpenRouter."""
class GetWeather(BaseModel):
"""Get the current weather in a given location."""
location: str = Field(description="The city and state")
model = ChatOpenRouter(model="openai/gpt-4o-mini", temperature=0)
model_with_tools = model.bind_tools([GetWeather])
response = model_with_tools.invoke("What's the weather in San Francisco?")
assert response.tool_calls
def test_structured_output() -> None:
"""Test structured output via OpenRouter."""
class Joke(BaseModel):
"""A joke."""
setup: str = Field(description="The setup of the joke")
punchline: str = Field(description="The punchline of the joke")
model = ChatOpenRouter(model="openai/gpt-4o-mini", temperature=0)
structured = model.with_structured_output(Joke)
result = structured.invoke("Tell me a joke about programming")
assert isinstance(result, Joke)
assert result.setup
assert result.punchline
@pytest.mark.xfail(reason="Depends on reasoning model availability on OpenRouter.")
def test_reasoning_content() -> None:
"""Test reasoning content from a reasoning model."""
model = ChatOpenRouter(
model="openai/o3-mini",
reasoning={"effort": "low"},
)
response = model.invoke("What is 2 + 2?")
assert response.content

View File

@@ -0,0 +1,8 @@
"""Test compilation of integration tests."""
import pytest
@pytest.mark.compile
def test_placeholder() -> None:
"""Used for compiling integration tests without running any real tests."""

View File

@@ -0,0 +1,120 @@
"""Standard integration tests for `ChatOpenRouter`."""
from langchain_core.messages import AIMessage, AIMessageChunk
from langchain_tests.integration_tests import ChatModelIntegrationTests
from langchain_openrouter.chat_models import ChatOpenRouter
MODEL_NAME = "openai/gpt-4o-mini"
class TestChatOpenRouter(ChatModelIntegrationTests):
"""Test `ChatOpenRouter` chat model."""
@property
def chat_model_class(self) -> type[ChatOpenRouter]:
"""Return class of chat model being tested."""
return ChatOpenRouter
@property
def chat_model_params(self) -> dict:
"""Parameters to create chat model instance for testing."""
return {
"model": MODEL_NAME,
"temperature": 0,
}
@property
def returns_usage_metadata(self) -> bool:
# Don't want to implement tests for now
return False
@property
def supports_json_mode(self) -> bool:
return False
@property
def supports_image_inputs(self) -> bool:
return True
@property
def supports_image_urls(self) -> bool:
return True
@property
def supports_video_inputs(self) -> bool:
return True
@property
def model_override_value(self) -> str:
return "openai/gpt-4o"
AUDIO_MODEL = "google/gemini-2.5-flash"
REASONING_MODEL = "openai/o3-mini"
class TestChatOpenRouterMultiModal(ChatModelIntegrationTests):
"""Tests for audio input and reasoning output capabilities.
Uses an audio-capable model as the base and creates separate model
instances for reasoning tests.
"""
@property
def chat_model_class(self) -> type[ChatOpenRouter]:
return ChatOpenRouter
@property
def chat_model_params(self) -> dict:
return {
"model": AUDIO_MODEL,
"temperature": 0,
}
@property
def returns_usage_metadata(self) -> bool:
# Don't want to implement tests for now
return False
@property
def supports_json_mode(self) -> bool:
return False
@property
def supports_image_inputs(self) -> bool:
return True
@property
def supports_image_urls(self) -> bool:
return True
@property
def supports_audio_inputs(self) -> bool:
return True
@property
def supports_video_inputs(self) -> bool:
return True
@property
def model_override_value(self) -> str:
return "openai/gpt-4o"
def invoke_with_reasoning_output(self, *, stream: bool = False) -> AIMessage:
"""Invoke a reasoning model to exercise reasoning token tracking."""
llm = ChatOpenRouter(
model=REASONING_MODEL,
reasoning={"effort": "medium"},
)
prompt = (
"Explain the relationship between the 2008/9 economic crisis and "
"the startup ecosystem in the early 2010s"
)
if stream:
full: AIMessageChunk | None = None
for chunk in llm.stream(prompt):
full = chunk if full is None else full + chunk # type: ignore[assignment]
assert full is not None
return full
return llm.invoke(prompt)

View File

@@ -0,0 +1 @@
"""Unit tests for langchain-openrouter."""

View File

@@ -0,0 +1,30 @@
# serializer version: 1
# name: TestChatOpenRouterUnit.test_serdes[serialized]
dict({
'id': list([
'langchain_openrouter',
'chat_models',
'ChatOpenRouter',
]),
'kwargs': dict({
'max_retries': 2,
'max_tokens': 100,
'model_name': 'openai/gpt-4o-mini',
'n': 1,
'openrouter_api_key': dict({
'id': list([
'OPENROUTER_API_KEY',
]),
'lc': 1,
'type': 'secret',
}),
'request_timeout': 60,
'stop': list([
]),
'temperature': 0.0,
}),
'lc': 1,
'name': 'ChatOpenRouter',
'type': 'constructor',
})
# ---

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
"""Test `langchain_openrouter` public API surface."""
from langchain_openrouter import __all__
EXPECTED_ALL = [
"ChatOpenRouter",
]
def test_all_imports() -> None:
"""Verify that __all__ exports match the expected public API."""
assert sorted(EXPECTED_ALL) == sorted(__all__)

View File

@@ -0,0 +1,63 @@
"""Standard unit tests for `ChatOpenRouter`."""
from langchain_tests.unit_tests import ChatModelUnitTests
from langchain_openrouter.chat_models import ChatOpenRouter
MODEL_NAME = "openai/gpt-4o-mini"
class TestChatOpenRouterUnit(ChatModelUnitTests):
"""Standard unit tests for `ChatOpenRouter` chat model."""
@property
def chat_model_class(self) -> type[ChatOpenRouter]:
"""Chat model class being tested."""
return ChatOpenRouter
@property
def init_from_env_params(self) -> tuple[dict, dict, dict]:
"""Parameters to initialize from environment variables."""
return (
{
"OPENROUTER_API_KEY": "api_key",
},
{
"model": MODEL_NAME,
},
{
"openrouter_api_key": "api_key",
},
)
@property
def chat_model_params(self) -> dict:
"""Parameters to create chat model instance for testing."""
return {
"model": MODEL_NAME,
"api_key": "test-api-key",
}
@property
def supports_image_inputs(self) -> bool:
return True
@property
def supports_image_urls(self) -> bool:
return True
@property
def supports_audio_inputs(self) -> bool:
return True
@property
def supports_video_inputs(self) -> bool:
return True
@property
def supports_pdf_inputs(self) -> bool:
return True
@property
def model_override_value(self) -> str:
return "openai/gpt-4o"

1790
libs/partners/openrouter/uv.lock generated Normal file

File diff suppressed because it is too large Load Diff