-
Notifications
You must be signed in to change notification settings - Fork 312
feat(example): add OrcaRouter as a selectable LLM provider for description generation #246
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 | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,9 +15,17 @@ class Settings(BaseSettings): | |||||||||||||||||||
| openai_api_key: str = "" | ||||||||||||||||||||
| openai_model: str = "gpt-4o-mini" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # LLM provider used for description generation: "openrouter" (default) or "orcarouter" | ||||||||||||||||||||
| llm_provider: str = "openrouter" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # OpenRouter | ||||||||||||||||||||
| openrouter_api_key: str = "" | ||||||||||||||||||||
| openrouter_model: str = "google/gemini-3.1-pro-preview" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # OrcaRouter | ||||||||||||||||||||
| orcarouter_api_key: str = "" | ||||||||||||||||||||
| orcarouter_model: str = "google/gemini-2.5-flash" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| llm_max_parallel: int = 20 | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Superlinked Inference Engine | ||||||||||||||||||||
|
|
@@ -35,6 +43,13 @@ def database_url(self) -> str: | |||||||||||||||||||
| db_path = self.sqlite_path.resolve() | ||||||||||||||||||||
| return f"sqlite:///{db_path}" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| @property | ||||||||||||||||||||
| def llm_model(self) -> str: | ||||||||||||||||||||
| """Default LLM model for the active provider.""" | ||||||||||||||||||||
| if self.llm_provider.strip().lower() == "orcarouter": | ||||||||||||||||||||
| return self.orcarouter_model | ||||||||||||||||||||
| return self.openrouter_model | ||||||||||||||||||||
|
Comment on lines
+49
to
+51
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Return the model for the active provider.
Proposed fix+ provider = self.llm_provider.strip().lower()
- if self.llm_provider.strip().lower() == "orcarouter":
+ if provider == "orcarouter":
return self.orcarouter_model
+ if provider == "openai":
+ return self.openai_model
return self.openrouter_model📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
|
|
||||||||||||||||||||
| model_config = {"env_file": ".env", "env_file_encoding": "utf-8"} | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,18 @@ | ||
| import asyncio | ||
| import logging | ||
| from typing import Optional | ||
|
|
||
| from openai import OpenAI | ||
|
|
||
| from app.config import settings | ||
| from app.services import orcarouter, openrouter | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| _client: OpenAI | None = None | ||
|
|
||
|
|
||
| def _get_client() -> OpenAI: | ||
| def _get_openai_client() -> OpenAI: | ||
| global _client | ||
| if _client is None: | ||
| if not settings.openai_api_key: | ||
|
|
@@ -20,9 +23,9 @@ def _get_client() -> OpenAI: | |
| return _client | ||
|
|
||
|
|
||
| def generate_text(prompt: str, max_tokens: int = 4096) -> str: | ||
| def _generate_openai(prompt: str, max_tokens: int = 4096) -> str: | ||
| """Send a prompt to OpenAI and return the assistant's response text.""" | ||
| client = _get_client() | ||
| client = _get_openai_client() | ||
| logger.info( | ||
| "Calling OpenAI %s (prompt length: %d chars, max_tokens: %d)", | ||
| settings.openai_model, | ||
|
|
@@ -38,3 +41,44 @@ def generate_text(prompt: str, max_tokens: int = 4096) -> str: | |
| text = response.choices[0].message.content or "" | ||
| logger.info("OpenAI response: %d chars", len(text)) | ||
| return text.strip() | ||
|
|
||
|
|
||
| def generate_text( | ||
| prompt: str, | ||
| max_tokens: int = 4096, | ||
| model: Optional[str] = None, | ||
| ) -> str: | ||
| """Generate text with the configured provider (defaults to OpenRouter). | ||
|
|
||
| Supported providers (LLM_PROVIDER): ``openrouter`` (default), | ||
| ``orcarouter``, and ``openai``. | ||
| """ | ||
| provider = settings.llm_provider.strip().lower() | ||
| if provider == "orcarouter": | ||
| return orcarouter.generate_text(prompt, max_tokens=max_tokens, model=model) | ||
| if provider == "openai": | ||
| return _generate_openai(prompt, max_tokens=max_tokens) | ||
| return openrouter.generate_text(prompt, max_tokens=max_tokens, model=model) | ||
|
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
config='examples/sie-hugging-face-mteb-semantic-search/backend/app/config.py'
rg -n -C 6 'llm_provider|Literal|field_validator|model_validator' "$config"Repository: superlinked/sie Length of output: 973 🏁 Script executed: #!/bin/bash
set -euo pipefail
file='examples/sie-hugging-face-mteb-semantic-search/backend/app/services/llm.py'
sed -n '1,120p' "$file"Repository: superlinked/sie Length of output: 2853 Reject unsupported provider values
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| async def generate_text_async( | ||
| prompt: str, | ||
| max_tokens: int = 4096, | ||
| model: Optional[str] = None, | ||
| semaphore: asyncio.Semaphore | None = None, | ||
| ) -> str: | ||
| """Async generate_text with the configured provider (defaults to OpenRouter). | ||
|
|
||
| Supported providers (LLM_PROVIDER): ``openrouter`` (default), | ||
| ``orcarouter``, and ``openai`` (sync fallback). | ||
| """ | ||
| provider = settings.llm_provider.strip().lower() | ||
| if provider == "orcarouter": | ||
| return await orcarouter.generate_text_async( | ||
| prompt, max_tokens=max_tokens, model=model, semaphore=semaphore | ||
| ) | ||
| if provider == "openai": | ||
| return await asyncio.to_thread(_generate_openai, prompt, max_tokens) | ||
|
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. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Preserve the shared concurrency limit for OpenAI calls. The CLI passes a semaphore to the dispatcher, but the OpenAI async branch calls 📍 Affects 2 files
🤖 Prompt for AI Agents🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Forward model overrides to the OpenAI provider. The provider-neutral API and CLI accept a model override, but the OpenAI branch drops it and always uses 📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| return await openrouter.generate_text_async( | ||
| prompt, max_tokens=max_tokens, model=model, semaphore=semaphore | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,7 +71,7 @@ def generate_text( | |
| ) -> str: | ||
| """Send a prompt to OpenRouter and return the assistant's response text.""" | ||
| client = _get_client() | ||
| model_name = model or settings.openrouter_model | ||
| model_name = model or settings.llm_model | ||
|
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. Hey @dulcestentaciones2920-debug, Thanks for the detailed writeup and for the live test. So the feature you want is really a single setting, not a provider abstraction: That's ~5 changed lines instead of 250, and it also covers other providers |
||
|
|
||
| logger.info( | ||
| "Calling OpenRouter model=%s (prompt length: %d chars, max_tokens: %d)", | ||
|
|
@@ -97,7 +97,7 @@ async def generate_text_async( | |
| ) -> str: | ||
| """Async version of generate_text with automatic retry on 429 rate-limit.""" | ||
| client = _get_async_client() | ||
| model_name = model or settings.openrouter_model | ||
| model_name = model or settings.llm_model | ||
|
|
||
| async with semaphore or asyncio.Semaphore(1): | ||
| for attempt in range(_RETRY_MAX): | ||
|
|
||
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.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Document the
openaiprovider in all setup paths.LLM_PROVIDERlistsopenaias supported, and the backend supports it. This README still lists only OpenRouter and OrcaRouter in the overview, prerequisite, and environment examples. It also omitsOPENAI_API_KEYandOPENAI_MODEL. A user who selectsLLM_PROVIDER=openaicannot complete the documented setup. Add OpenAI to these provider lists and add its configuration example.Also applies to: 58-58, 68-68, 162-162, 194-198, 207-229
🤖 Prompt for AI Agents