-
Notifications
You must be signed in to change notification settings - Fork 2k
feat(sdk): add OrcaRouter gateway integration to wren-langchain and wren-pydantic #2686
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,25 @@ Complete runnable demos: | |
| conditional edges). Use this when you need custom routing, state, or | ||
| streaming. | ||
|
|
||
| ### Routing through OrcaRouter | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Use an H2 heading. Line 29 skips from the H1 document title to an H3 heading. Change 🧰 Tools🪛 markdownlint-cli2 (0.23.2)[warning] 29-29: Heading levels should only increment by one level at a time (MD001, heading-increment) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| [OrcaRouter](https://www.orcarouter.ai) is a unified model gateway with an | ||
| OpenAI-compatible endpoint. Point a `ChatOpenAI` at it to route the agent | ||
| through OrcaRouter — the examples above pick it up automatically when | ||
| `ORCAROUTER_API_KEY` is set: | ||
|
|
||
| ```bash | ||
| export ORCAROUTER_API_KEY=sk-orca-... # required | ||
| export ORCAROUTER_MODEL=orcarouter/auto # optional, default: orcarouter/auto | ||
| export ORCAROUTER_BASE_URL=https://api.orcarouter.ai/v1 # optional, default above | ||
| python examples/langchain_demo.py | ||
| ``` | ||
|
|
||
| OrcaRouter gives you one key for 150+ models across providers, and also runs | ||
| gateway-level, zero-trust security for AI agents on the same endpoint — | ||
| screening every prompt/response and governing every tool call on a | ||
| default-deny basis, with no application code changes. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| This package assumes you have already used the Wren CLI to prepare a project: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,6 +84,8 @@ dev = [ | |
| "ruff>=0.4", | ||
| # Tests run real DuckDB + LanceDB integrations, so memory deps are required. | ||
| "wrenai[memory]>=0.13.1", | ||
| # OrcaRouter gateway factory is tested directly; requires langchain-openai. | ||
| "langchain-openai>=0.1", | ||
|
Comment on lines
+87
to
+88
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 5 'langchain-openai|dependencies|optional-dependencies|dependency-groups' \
sdk/wren-langchain/pyproject.toml
rg -n -C 4 'create_orcarouter_chat_model|langchain_openai|ORCAROUTER_API_KEY' \
sdk/wren-langchain/src/wren_langchain/orcarouter.py \
sdk/wren-langchain/README.md \
docs/core/sdk/langchain.mdRepository: Canner/WrenAI Length of output: 8656 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,105p' sdk/wren-langchain/pyproject.toml
printf '\n--- package exports and example imports ---\n'
rg -n -C 5 'orcarouter|create_orcarouter_chat_model|ChatOpenAI|init_chat_model|ORCAROUTER_API_KEY' \
sdk/wren-langchain/src sdk/wren-langchain/examples sdk/wren-langchain/README.md docs/core/sdk/langchain.md
printf '\n--- sibling SDK dependency declarations ---\n'
rg -n -C 4 'langchain-openai|orcarouter|ORCAROUTER' \
sdk/wren-pydantic sdk/wren-langchain -g 'pyproject.toml' -g '*.py' -g '*.md'Repository: Canner/WrenAI Length of output: 50371 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- installation instructions ---'
rg -n -C 3 'pip install|uv pip install|wren-langchain\[|langchain-openai' \
sdk/wren-langchain/README.md docs/core/sdk/langchain.md \
-g '*.md' -g '*.rst' -g '*.toml'
printf '%s\n' '--- focused README and documentation sections ---'
sed -n '1,55p' sdk/wren-langchain/README.md
sed -n '1,70p' docs/core/sdk/langchain.md
printf '%s\n' '--- parsed project metadata ---'
python3 - <<'PY'
import tomllib
from pathlib import Path
path = Path("sdk/wren-langchain/pyproject.toml")
data = tomllib.loads(path.read_text())
project = data["project"]
print("runtime dependencies:", project["dependencies"])
print("optional extras:")
for name, deps in project.get("optional-dependencies", {}).items():
print(f" {name}: {deps}")
PYRepository: Canner/WrenAI Length of output: 7862 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- README installation and prerequisites ---'
sed -n '55,105p' sdk/wren-langchain/README.md
printf '%s\n' '--- project documentation around model setup ---'
sed -n '65,100p' docs/core/sdk/langchain.md
sed -n '170,190p' docs/core/sdk/langchain.md
printf '%s\n' '--- exact changed lines ---'
git diff -- sdk/wren-langchain/pyproject.tomlRepository: Canner/WrenAI Length of output: 4182 Provide
🤖 Prompt for AI Agents |
||
| ] | ||
|
|
||
| [project.urls] | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| """OrcaRouter gateway integration for wren-langchain. | ||
|
|
||
| [OrcaRouter](https://www.orcarouter.ai) is an OpenAI-compatible model gateway: one | ||
| key routes to 150+ models across providers, and the same endpoint runs | ||
| gateway-level, zero-trust security for AI agents. This module builds a | ||
| ``langchain_openai.ChatOpenAI`` pointed at OrcaRouter's endpoint. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| if TYPE_CHECKING: | ||
| from langchain_openai import ChatOpenAI | ||
|
|
||
| #: Default base URL for the OrcaRouter OpenAI-compatible endpoint. | ||
| DEFAULT_ORCAROUTER_BASE_URL = "https://api.orcarouter.ai/v1" | ||
| #: Default model id — OrcaRouter's smart auto-routing model. | ||
| DEFAULT_ORCAROUTER_MODEL = "orcarouter/auto" | ||
|
|
||
|
|
||
| def create_orcarouter_chat_model(*, temperature: float = 0) -> ChatOpenAI: | ||
| """Return a ``ChatOpenAI`` routed through OrcaRouter. | ||
|
|
||
| Requires ``ORCAROUTER_API_KEY`` in the environment. ``ORCAROUTER_BASE_URL`` | ||
| and ``ORCAROUTER_MODEL`` override the defaults. | ||
|
|
||
| Raises: | ||
| ImportError: if ``langchain-openai`` is not installed. | ||
| ValueError: if ``ORCAROUTER_API_KEY`` is not set. | ||
| """ | ||
| try: | ||
| from langchain_openai import ChatOpenAI # noqa: PLC0415 | ||
| except ImportError as exc: # pragma: no cover - exercised via dev extra in CI | ||
| raise ImportError( | ||
| "langchain-openai is required for OrcaRouter routing." | ||
| ) from exc | ||
|
|
||
| api_key = os.environ.get("ORCAROUTER_API_KEY") | ||
| if not api_key: | ||
| raise ValueError( | ||
| "ORCAROUTER_API_KEY is required to use the OrcaRouter gateway." | ||
| ) | ||
|
|
||
| return ChatOpenAI( | ||
| model=os.environ.get("ORCAROUTER_MODEL", DEFAULT_ORCAROUTER_MODEL), | ||
| base_url=os.environ.get("ORCAROUTER_BASE_URL", DEFAULT_ORCAROUTER_BASE_URL), | ||
| api_key=api_key, | ||
| temperature=temperature, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| """Tests for the OrcaRouter gateway factory.""" | ||
|
|
||
| import pytest | ||
|
|
||
| from wren_langchain.orcarouter import ( | ||
| DEFAULT_ORCAROUTER_BASE_URL, | ||
| DEFAULT_ORCAROUTER_MODEL, | ||
| create_orcarouter_chat_model, | ||
| ) | ||
|
|
||
| pytest.importorskip("langchain_openai") | ||
|
|
||
|
|
||
| def test_requires_api_key(monkeypatch): | ||
| monkeypatch.delenv("ORCAROUTER_API_KEY", raising=False) | ||
| with pytest.raises(ValueError, match="ORCAROUTER_API_KEY"): | ||
| create_orcarouter_chat_model() | ||
|
|
||
|
|
||
| def test_defaults(monkeypatch): | ||
| monkeypatch.setenv("ORCAROUTER_API_KEY", "sk-orca-test") | ||
| model = create_orcarouter_chat_model() | ||
| assert model.model_name == DEFAULT_ORCAROUTER_MODEL | ||
| assert model.openai_api_base == DEFAULT_ORCAROUTER_BASE_URL | ||
| assert model.openai_api_key.get_secret_value() == "sk-orca-test" | ||
|
Comment on lines
+20
to
+25
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Isolate default-value tests from inherited environment overrides. Both default tests can fail when the shell or CI environment already defines
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def test_env_overrides(monkeypatch): | ||
| monkeypatch.setenv("ORCAROUTER_API_KEY", "sk-orca-test") | ||
| monkeypatch.setenv("ORCAROUTER_MODEL", "anthropic/claude-sonnet-5") | ||
| monkeypatch.setenv("ORCAROUTER_BASE_URL", "https://proxy.example.com/v1") | ||
| model = create_orcarouter_chat_model() | ||
| assert model.model_name == "anthropic/claude-sonnet-5" | ||
| assert model.openai_api_base == "https://proxy.example.com/v1" | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -30,6 +30,25 @@ Runnable demos: | |||||
| - [`examples/pydantic_ai_structured_demo.py`](./examples/pydantic_ai_structured_demo.py) — | ||||||
| same shape with `output_type=` for structured / validated agent output. | ||||||
|
|
||||||
| ### Routing through OrcaRouter | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Use an H2 heading for the new section.
Proposed fix-### Routing through OrcaRouter
+## Routing through OrcaRouter📝 Committable suggestion
Suggested change
🧰 Tools🪛 markdownlint-cli2 (0.23.2)[warning] 33-33: Heading levels should only increment by one level at a time (MD001, heading-increment) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||
|
|
||||||
| [OrcaRouter](https://www.orcarouter.ai) is a unified model gateway with an | ||||||
| OpenAI-compatible endpoint. Point an `OpenAIChatModel` at it to route the | ||||||
| agent through OrcaRouter — the examples above pick it up automatically when | ||||||
| `ORCAROUTER_API_KEY` is set: | ||||||
|
|
||||||
| ```bash | ||||||
| export ORCAROUTER_API_KEY=sk-orca-... # required | ||||||
| export ORCAROUTER_MODEL=orcarouter/auto # optional, default: orcarouter/auto | ||||||
| export ORCAROUTER_BASE_URL=https://api.orcarouter.ai/v1 # optional, default above | ||||||
| python examples/pydantic_ai_demo.py | ||||||
| ``` | ||||||
|
|
||||||
| OrcaRouter gives you one key for 150+ models across providers, and also runs | ||||||
| gateway-level, zero-trust security for AI agents on the same endpoint — | ||||||
| screening every prompt/response and governing every tool call on a | ||||||
| default-deny basis, with no application code changes. | ||||||
|
|
||||||
| ## Prerequisites | ||||||
|
|
||||||
| This package assumes you have already used the Wren CLI to prepare a project: | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Canner/WrenAI
Length of output: 5712
🏁 Script executed:
Repository: Canner/WrenAI
Length of output: 20808
🌐 Web query:
LangChain Python init_chat_model openai provider api_key base_url ChatOpenAI environment variable documentation💡 Result:
The
init_chat_modelfunction in LangChain provides a unified interface to initialize various chat models, including those compatible with the OpenAI API [1][2]. Usage withinit_chat_modelYou can useinit_chat_modelto instantiate OpenAI-compatible models (such as those from Together AI or vLLM) by providing the model name, specifyingmodel_provider="openai", and passing additional parameters likeapi_keyandbase_urldirectly [1]: model = init_chat_model( model="MODEL_NAME", model_provider="openai", base_url="BASE_URL", api_key="YOUR_API_KEY",) Parameters passed askwargsininit_chat_modelare forwarded to the underlying model's constructor [3][2]. Environment Variables and Configuration When usingChatOpenAIorinit_chat_modelwith an OpenAI-compatible provider, LangChain follows a specific resolution order for configuration [4][5]: 1. Explicit arguments: Passingapi_keyorbase_urldirectly to the constructor takes highest priority [4][5]. 2. Environment Variables: - For API Keys: TheOPENAI_API_KEYenvironment variable is automatically detected if theapi_keyargument is not provided [6][4]. - For Base URLs: LangChain checksOPENAI_API_BASE(read by LangChain) first, followed byOPENAI_BASE_URL(read by the underlying OpenAI SDK client) [4][5]. Security Note When usinginit_chat_modelwithconfigurable_fields, avoid settingconfigurable_fields="any"if you are accepting untrusted input, as this allows parameters likeapi_keyandbase_urlto be overridden at runtime [3][2]. Explicitly list allowed fields instead [3][2].Citations:
🏁 Script executed:
Repository: Canner/WrenAI
Length of output: 41826
Provide
ORCAROUTER_API_KEYto the LangGraph model.When users uncomment this replacement with only the documented environment variable,
init_chat_model()falls back toOPENAI_API_KEY. Usecreate_orcarouter_chat_model()fromwren_langchain.orcarouter, or passapi_key=os.environ["ORCAROUTER_API_KEY"].🤖 Prompt for AI Agents