Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions unstract/sdk1/src/unstract/sdk1/adapters/base1.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ def _validate_branded_openai_compatible(

_NVIDIA_BUILD_API_BASE = "https://integrate.api.nvidia.com/v1"
_OPENROUTER_API_BASE = "https://openrouter.ai/api/v1"
_ORCAROUTER_API_BASE = "https://api.orcarouter.ai/v1"
_MINIMAX_API_BASE = "https://api.minimax.io/v1"
_OPENROUTER_PROVIDER_PREFIX = "openrouter/"
_MINIMAX_PROVIDER_PREFIX = "minimax/"
Expand Down Expand Up @@ -578,6 +579,17 @@ def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]:
)


class OrcaRouterLLMParameters(OpenAICompatibleLLMParameters):
"""OpenAI-compatible adapter for OrcaRouter's model routing gateway."""

# Required str so a directly-constructed instance stays valid.
api_base: str = _ORCAROUTER_API_BASE

@staticmethod
def validate(adapter_metadata: dict[str, "Any"]) -> dict[str, "Any"]:
return _validate_branded_openai_compatible(adapter_metadata, _ORCAROUTER_API_BASE)


class MiniMaxLLMParameters(BaseChatCompletionParameters):
"""Adapter for MiniMax's OpenAI- and Anthropic-compatible APIs."""

Expand Down
2 changes: 2 additions & 0 deletions unstract/sdk1/src/unstract/sdk1/adapters/llm1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from unstract.sdk1.adapters.llm1.openai import OpenAILLMAdapter
from unstract.sdk1.adapters.llm1.openai_compatible import OpenAICompatibleLLMAdapter
from unstract.sdk1.adapters.llm1.openrouter import OpenRouterLLMAdapter
from unstract.sdk1.adapters.llm1.orcarouter import OrcaRouterLLMAdapter
from unstract.sdk1.adapters.llm1.vertexai import VertexAILLMAdapter

adapters: dict[str, dict[str, Any]] = {}
Expand All @@ -30,5 +31,6 @@
"OpenAILLMAdapter",
"OpenAICompatibleLLMAdapter",
"OpenRouterLLMAdapter",
"OrcaRouterLLMAdapter",
"VertexAILLMAdapter",
]
46 changes: 46 additions & 0 deletions unstract/sdk1/src/unstract/sdk1/adapters/llm1/orcarouter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from typing import Any

from unstract.sdk1.adapters.base1 import BaseAdapter, OrcaRouterLLMParameters
from unstract.sdk1.adapters.enums import AdapterTypes

DESCRIPTION = (
"Adapter for OrcaRouter's OpenAI-compatible model routing gateway "
"(orcarouter.ai). Supply a model name and your OrcaRouter API key; the "
"endpoint is preconfigured."
)


class OrcaRouterLLMAdapter(OrcaRouterLLMParameters, BaseAdapter):
@staticmethod
def get_id() -> str:
return "orcarouter|cc86ba36-f289-46a9-b60e-b2e8940c4385"

@staticmethod
def get_metadata() -> dict[str, Any]:
return {
"name": "OrcaRouter",
"version": "1.0.0",
"adapter": OrcaRouterLLMAdapter,
"description": DESCRIPTION,
"is_active": True,
}

@staticmethod
def get_name() -> str:
return "OrcaRouter"

@staticmethod
def get_description() -> str:
return DESCRIPTION

@staticmethod
def get_provider() -> str:
return "orcarouter"

@staticmethod
def get_icon() -> str:
return "/icons/adapter-icons/OrcaRouter.png"

@staticmethod
def get_adapter_type() -> AdapterTypes:
return AdapterTypes.LLM
112 changes: 112 additions & 0 deletions unstract/sdk1/src/unstract/sdk1/adapters/llm1/static/orcarouter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
{
"title": "OrcaRouter",
"type": "object",
"required": [
"adapter_name",
"api_key",
"model"
],
"properties": {
"adapter_name": {
"type": "string",
"title": "Name",
"default": "",
"description": "Provide a unique name for this adapter instance. Example: orcarouter-1"
},
"api_key": {
"type": "string",
"title": "API Key",
"format": "password",
"description": "Your OrcaRouter API key from [orcarouter.ai](https://www.orcarouter.ai). Keys begin with `sk-orca-`."
},
"model": {
"type": "string",
"title": "Model",
"description": "The model slug as listed on [orcarouter.ai/models](https://www.orcarouter.ai/models). Examples: openai/gpt-5.5, anthropic/claude-opus-4.8, deepseek/deepseek-v4-flash"
},
"api_base": {
"type": "string",
"format": "url",
"title": "API Base",
"default": "https://api.orcarouter.ai/v1",
"description": "OrcaRouter endpoint. Pre-filled with the default; change only if OrcaRouter moves the base URL."
},
"max_tokens": {
"type": "number",
"minimum": 0,
"multipleOf": 1,
"title": "Maximum Output Tokens",
"default": 4096,
"description": "Maximum number of output tokens to limit LLM replies. Leave it empty to use the provider default. Sent as `max_completion_tokens` when Enable Reasoning is on."
},
"max_retries": {
"type": "number",
"minimum": 0,
"multipleOf": 1,
"title": "Max Retries",
"default": 5,
"description": "The maximum number of times to retry a request if it fails."
},
"timeout": {
"type": "number",
"minimum": 0,
"multipleOf": 1,
"title": "Timeout",
"default": 900,
"description": "Timeout in seconds."
},
"enable_reasoning": {
"type": "boolean",
"title": "Enable Reasoning",
"default": false,
"description": "Toggle on for reasoning models to drop `temperature` and send `max_completion_tokens` and `reasoning_effort` via `extra_body` so the upstream API accepts them."
}
},
"allOf": [
{
"if": {
"properties": {
"enable_reasoning": {
"const": true
}
},
"required": [
"enable_reasoning"
]
},
"then": {
"properties": {
"reasoning_effort": {
"type": "string",
"enum": [
"low",
"medium",
"high"
],
"default": "medium",
"title": "Reasoning Effort",
"description": "Sets the reasoning strength when Enable Reasoning is on."
}
},
"required": [
"reasoning_effort"
]
}
},
{
"if": {
"properties": {
"enable_reasoning": {
"const": false
}
},
"required": [
"enable_reasoning"
]
},
"then": {
"properties": {}
}
}
]
}
28 changes: 26 additions & 2 deletions unstract/sdk1/tests/test_branded_openai_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
NvidiaBuildLLMParameters,
OpenAICompatibleEmbeddingParameters,
OpenRouterLLMParameters,
OrcaRouterLLMParameters,
)
from unstract.sdk1.adapters.constants import Common
from unstract.sdk1.adapters.embedding1 import adapters as embedding_adapters
Expand All @@ -18,9 +19,11 @@
from unstract.sdk1.adapters.llm1.minimax import MiniMaxLLMAdapter
from unstract.sdk1.adapters.llm1.nvidia_build import NvidiaBuildLLMAdapter
from unstract.sdk1.adapters.llm1.openrouter import OpenRouterLLMAdapter
from unstract.sdk1.adapters.llm1.orcarouter import OrcaRouterLLMAdapter

_NVIDIA_BUILD_API_BASE = "https://integrate.api.nvidia.com/v1"
_OPENROUTER_API_BASE = "https://openrouter.ai/api/v1"
_ORCAROUTER_API_BASE = "https://api.orcarouter.ai/v1"
_MINIMAX_API_BASE = "https://api.minimax.io/v1"
_MINIMAX_ANTHROPIC_API_BASE = "https://api.minimax.io/anthropic"
_MINIMAX_CN_API_BASE = "https://api.minimaxi.com/v1"
Expand All @@ -32,7 +35,12 @@

@pytest.mark.parametrize(
"adapter",
[MiniMaxLLMAdapter, NvidiaBuildLLMAdapter, OpenRouterLLMAdapter],
[
MiniMaxLLMAdapter,
NvidiaBuildLLMAdapter,
OpenRouterLLMAdapter,
OrcaRouterLLMAdapter,
],
)
def test_branded_llm_adapter_is_registered(adapter: type) -> None:
adapter_id = adapter.get_id()
Expand All @@ -47,6 +55,15 @@ def test_nvidia_llm_prefixes_model_via_custom_openai() -> None:
assert validated["api_base"] == _NVIDIA_BUILD_API_BASE


def test_orcarouter_llm_prefixes_model_via_custom_openai() -> None:
validated = OrcaRouterLLMParameters.validate(
{"model": "deepseek/deepseek-v4-flash", "api_key": "k"}
)

assert validated["model"] == "custom_openai/deepseek/deepseek-v4-flash"
assert validated["api_base"] == _ORCAROUTER_API_BASE


@pytest.mark.parametrize("model", ["MiniMax-M3", "MiniMax-M2.7"])
@pytest.mark.parametrize(
("api_base", "provider"),
Expand Down Expand Up @@ -268,6 +285,7 @@ def test_openrouter_reasoning_survives_revalidation() -> None:
(MiniMaxLLMParameters, _MINIMAX_API_BASE),
(NvidiaBuildLLMParameters, _NVIDIA_BUILD_API_BASE),
(OpenRouterLLMParameters, _OPENROUTER_API_BASE),
(OrcaRouterLLMParameters, _ORCAROUTER_API_BASE),
],
)
def test_branded_llm_blank_api_base_falls_back_to_default(
Expand All @@ -280,7 +298,12 @@ def test_branded_llm_blank_api_base_falls_back_to_default(

@pytest.mark.parametrize(
"params",
[MiniMaxLLMParameters, NvidiaBuildLLMParameters, OpenRouterLLMParameters],
[
MiniMaxLLMParameters,
NvidiaBuildLLMParameters,
OpenRouterLLMParameters,
OrcaRouterLLMParameters,
],
)
def test_branded_llm_honours_api_base_override(params: type) -> None:
validated = params.validate(
Expand All @@ -296,6 +319,7 @@ def test_branded_llm_honours_api_base_override(params: type) -> None:
(MiniMaxLLMAdapter, _MINIMAX_API_BASE),
(NvidiaBuildLLMAdapter, _NVIDIA_BUILD_API_BASE),
(OpenRouterLLMAdapter, _OPENROUTER_API_BASE),
(OrcaRouterLLMAdapter, _ORCAROUTER_API_BASE),
],
)
def test_branded_llm_schema_exposes_api_base_with_default(
Expand Down