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
2 changes: 2 additions & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@ GPT_API_KEY=
GLM_API_KEY=
ALI_API_KEYS=
ARK_API_KEY=
ORCA_API_KEY=

# Optional overrides:
# DS_URL=https://api.deepseek.com/v1
# GLM_URL=https://open.bigmodel.cn/api/paas/v4
# ALI_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# ARK_URL=https://ark.cn-beijing.volces.com/api/v3/chat/completions
# ORCA_URL=https://api.orcarouter.ai/v1
# NORMOL_MODEL=deepseek-v4-flash
# HIERARCHY_LLM_MODEL=
# VLM for image/OCR flows. Default qwen3.6-flash; alternate qwen3-vl-32b-instruct
Expand Down
2 changes: 2 additions & 0 deletions apps/worker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,14 @@ GPT_API_KEY=
GLM_API_KEY=
ALI_API_KEYS=
ARK_API_KEY=
ORCA_API_KEY=

# Optional overrides:
# DS_URL=https://api.deepseek.com/v1
# GLM_URL=https://open.bigmodel.cn/api/paas/v4
# ALI_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
# ARK_URL=https://ark.cn-beijing.volces.com/api/v3/chat/completions
# ORCA_URL=https://api.orcarouter.ai/v1
# NORMOL_MODEL=deepseek-v4-flash
# HIERARCHY_LLM_MODEL=
# VLM for page tagging, OCR, chart/table bbox probes, and image flows.
Expand Down
4 changes: 4 additions & 0 deletions packages/shared-python/shared/core/config/ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class AIConfig(BaseModel):
default="https://api.deepseek.com/v1", description="DeepSeek API URL"
)
GPT_API_KEY: str = Field(default="", description="OpenAI API key")
ORCA_API_KEY: str = Field(default="", description="OrcaRouter API key")
ORCA_URL: str = Field(
default="https://api.orcarouter.ai/v1", description="OrcaRouter API URL"
)
# Default behavior: text/table summaries use deepseek-v4-flash. Hierarchy parsing
# can be overridden independently with HIERARCHY_LLM_MODEL. Existing
# environment overrides for NORMOL_MODEL / HIERARCHY_LLM_MODEL /
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ def _resolve_base_url(
if "doubao" in model_lower or model_lower.startswith("ep-"):
return self._strip_chat_completions(getattr(settings, "ARK_URL", None))

if "orcarouter" in model_lower:
orca_base = getattr(settings, "ORCA_URL", "https://api.orcarouter.ai/v1")
return self._strip_chat_completions(orca_base)

return self._strip_chat_completions(settings.DS_URL)

def _resolve_direct_api_key(
Expand All @@ -173,6 +177,9 @@ def _resolve_direct_api_key(
if "doubao" in model_lower or model_lower.startswith("ep-"):
return getattr(settings, "ARK_API_KEY", None)

if "orcarouter" in model_lower:
return getattr(settings, "ORCA_API_KEY", None)

return settings.DS_KEY

# ------------------------------------------------------------------
Expand Down
64 changes: 64 additions & 0 deletions packages/shared-python/shared/tests/test_openai_client_routing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""Unit tests for OpenAI-compatible provider credential routing (no live API)."""

from __future__ import annotations

import os
from types import SimpleNamespace
from unittest.mock import patch

os.environ.setdefault("DATABASE_URL", "postgresql+asyncpg://test:test@localhost/test")
os.environ.setdefault("TMP_PATH", "/tmp/knowhere-test")
os.environ.setdefault("S3_BUCKET_NAME", "test-uploads")
os.environ.setdefault("S3_ACCESS_KEY_ID", "test")
os.environ.setdefault("S3_SECRET_ACCESS_KEY", "test")
os.environ.setdefault("S3_TEMP_PATH", "/tmp")

from shared.services.ai.openai_compatible_client_sync import ( # noqa: E402
OpenAICompatibleClientSync,
)


def _client_with(model: str) -> OpenAICompatibleClientSync:
return OpenAICompatibleClientSync(default_model=model)


def test_orcarouter_model_resolves_base_url_and_api_key() -> None:
"""orcarouter/* models route to the OrcaRouter endpoint and ORCA_API_KEY."""
fake_settings = SimpleNamespace(
ORCA_URL="https://api.orcarouter.ai/v1",
ORCA_API_KEY="orca-test-key",
DS_URL="https://api.deepseek.com/v1",
DS_KEY="ds-test-key",
)
client = _client_with("orcarouter/fusion")
with patch(
"shared.services.ai.openai_compatible_client_sync.settings",
fake_settings,
):
assert client._resolve_base_url("orcarouter/fusion", None) == (
"https://api.orcarouter.ai/v1"
)
assert client._resolve_direct_api_key("orcarouter/fusion", None) == (
"orca-test-key"
)


def test_non_orcarouter_model_keeps_existing_routing() -> None:
"""Models without the orcarouter prefix keep the DeepSeek defaults."""
fake_settings = SimpleNamespace(
DS_URL="https://api.deepseek.com/v1",
DS_KEY="ds-test-key",
GLM_URL="https://open.bigmodel.cn/api/paas/v4",
GLM_API_KEY="glm-test-key",
)
client = _client_with("deepseek-v4-flash")
with patch(
"shared.services.ai.openai_compatible_client_sync.settings",
fake_settings,
):
assert client._resolve_base_url("deepseek-v4-flash", None) == (
"https://api.deepseek.com/v1"
)
assert client._resolve_direct_api_key("deepseek-v4-flash", None) == (
"ds-test-key"
)