feat: add openrouter support

This commit is contained in:
Abhishek Kumar 2026-02-09 13:31:32 +05:30
parent 3cdede0f45
commit 4c936ae57d
12 changed files with 80 additions and 4 deletions

View file

@ -25,7 +25,7 @@ RUN pip install --user --no-cache-dir -r requirements.txt && \
# Copy and install pipecat from local submodule
COPY pipecat /tmp/pipecat
RUN pip install --user --no-cache-dir '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,local-smart-turn-v3,speechmatics]' && \
RUN pip install --user --no-cache-dir '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,sarvam,soundfile,silero,webrtc,local-smart-turn-v3,speechmatics,openrouter]' && \
# Clean up pip cache and temporary pipecat directory
rm -rf /root/.cache/pip /tmp/pipecat

View file

@ -360,6 +360,9 @@ async def search_chunks(
db_client=db_client,
api_key=embeddings_api_key,
model_id=embeddings_model or "text-embedding-3-small",
base_url=getattr(user_config.embeddings, "base_url", None)
if user_config.embeddings
else None,
)
# Perform search

View file

@ -33,6 +33,7 @@ class UserConfigurationValidator:
ServiceProviders.OPENAI.value: self._check_openai_api_key,
ServiceProviders.DEEPGRAM.value: self._check_deepgram_api_key,
ServiceProviders.GROQ.value: self._check_groq_api_key,
ServiceProviders.OPENROUTER.value: self._check_openrouter_api_key,
ServiceProviders.ELEVENLABS.value: self._validate_elevenlabs_api_key,
ServiceProviders.GOOGLE.value: self._check_google_api_key,
ServiceProviders.AZURE.value: self._check_azure_api_key,
@ -150,5 +151,8 @@ class UserConfigurationValidator:
def _check_sarvam_api_key(self, model: str, api_key: str) -> bool:
return True
def _check_openrouter_api_key(self, model: str, api_key: str) -> bool:
return True
def _check_speechmatics_api_key(self, model: str, api_key: str) -> bool:
return True

View file

@ -15,6 +15,7 @@ class ServiceProviders(str, Enum):
OPENAI = "openai"
DEEPGRAM = "deepgram"
GROQ = "groq"
OPENROUTER = "openrouter"
CARTESIA = "cartesia"
# NEUPHONIC = "neuphonic"
ELEVENLABS = "elevenlabs"
@ -30,6 +31,7 @@ class BaseServiceConfiguration(BaseModel):
ServiceProviders.OPENAI,
ServiceProviders.DEEPGRAM,
ServiceProviders.GROQ,
ServiceProviders.OPENROUTER,
ServiceProviders.ELEVENLABS,
ServiceProviders.GOOGLE,
ServiceProviders.AZURE,
@ -131,6 +133,15 @@ GROQ_MODELS = [
"llama-3.1-8b-instant",
"openai/gpt-oss-120b",
]
OPENROUTER_MODELS = [
"openai/gpt-4.1",
"openai/gpt-4.1-mini",
"anthropic/claude-sonnet-4",
"google/gemini-2.5-flash",
"google/gemini-2.0-flash",
"meta-llama/llama-3.3-70b-instruct",
"deepseek/deepseek-chat-v3-0324",
]
AZURE_MODELS = ["gpt-4.1-mini"]
DOGRAH_LLM_MODELS = ["default", "accurate", "fast", "lite", "zen"]
@ -160,6 +171,16 @@ class GroqLLMService(BaseLLMConfiguration):
api_key: str
@register_llm
class OpenRouterLLMConfiguration(BaseLLMConfiguration):
provider: Literal[ServiceProviders.OPENROUTER] = ServiceProviders.OPENROUTER
model: str = Field(
default="openai/gpt-4.1", json_schema_extra={"examples": OPENROUTER_MODELS}
)
api_key: str
base_url: str = Field(default="https://openrouter.ai/api/v1")
@register_llm
class AzureLLMService(BaseLLMConfiguration):
provider: Literal[ServiceProviders.AZURE] = ServiceProviders.AZURE
@ -183,6 +204,7 @@ LLMConfig = Annotated[
Union[
OpenAILLMService,
GroqLLMService,
OpenRouterLLMConfiguration,
GoogleLLMService,
AzureLLMService,
DograhLLMService,
@ -497,8 +519,22 @@ class OpenAIEmbeddingsConfiguration(BaseEmbeddingsConfiguration):
api_key: str
OPENROUTER_EMBEDDING_MODELS = ["openai/text-embedding-3-small"]
@register_embeddings
class OpenRouterEmbeddingsConfiguration(BaseEmbeddingsConfiguration):
provider: Literal[ServiceProviders.OPENROUTER] = ServiceProviders.OPENROUTER
model: str = Field(
default="openai/text-embedding-3-small",
json_schema_extra={"examples": OPENROUTER_EMBEDDING_MODELS},
)
api_key: str
base_url: str = Field(default="https://openrouter.ai/api/v1")
EmbeddingsConfig = Annotated[
Union[OpenAIEmbeddingsConfiguration],
Union[OpenAIEmbeddingsConfiguration, OpenRouterEmbeddingsConfiguration],
Field(discriminator="provider"),
]

View file

@ -50,6 +50,7 @@ class OpenAIEmbeddingService(BaseEmbeddingService):
api_key: Optional[str] = None,
model_id: str = DEFAULT_MODEL_ID,
max_tokens: int = 512,
base_url: Optional[str] = None,
):
"""Initialize the OpenAI embedding service.
@ -59,6 +60,7 @@ class OpenAIEmbeddingService(BaseEmbeddingService):
initialized and operations will fail with a clear error.
model_id: OpenAI embedding model ID (default: text-embedding-3-small)
max_tokens: Maximum number of tokens per chunk (default: 512)
base_url: Optional base URL for the API (e.g. for OpenRouter)
"""
self.db = db_client
self.model_id = model_id
@ -67,7 +69,10 @@ class OpenAIEmbeddingService(BaseEmbeddingService):
# Only initialize OpenAI client if API key is provided
self._api_key_configured = bool(api_key)
if self._api_key_configured:
self.client = AsyncOpenAI(api_key=api_key)
client_kwargs = {"api_key": api_key}
if base_url:
client_kwargs["base_url"] = base_url
self.client = AsyncOpenAI(**client_kwargs)
logger.info(f"OpenAI embedding service initialized with model: {model_id}")
else:
self.client = None

View file

@ -538,9 +538,11 @@ async def _run_pipeline(
# Extract embeddings configuration from user config
embeddings_api_key = None
embeddings_model = None
embeddings_base_url = None
if user_config and user_config.embeddings:
embeddings_api_key = user_config.embeddings.api_key
embeddings_model = user_config.embeddings.model
embeddings_base_url = getattr(user_config.embeddings, "base_url", None)
engine = PipecatEngine(
llm=llm,
@ -550,6 +552,7 @@ async def _run_pipeline(
node_transition_callback=node_transition_callback,
embeddings_api_key=embeddings_api_key,
embeddings_model=embeddings_model,
embeddings_base_url=embeddings_base_url,
)
# Create pipeline components with audio configuration

View file

@ -19,6 +19,7 @@ from pipecat.services.groq.llm import GroqLLMService
from pipecat.services.openai.llm import OpenAILLMService
from pipecat.services.openai.stt import OpenAISTTService
from pipecat.services.openai.tts import OpenAITTSService
from pipecat.services.openrouter.llm import OpenRouterLLMService
from pipecat.services.sarvam.stt import SarvamSTTService
from pipecat.services.sarvam.tts import SarvamTTSService
from pipecat.services.speechmatics.stt import SpeechmaticsSTTService
@ -253,6 +254,13 @@ def create_llm_service(user_config):
model=model,
params=OpenAILLMService.InputParams(temperature=0.1),
)
elif user_config.llm.provider == ServiceProviders.OPENROUTER.value:
return OpenRouterLLMService(
api_key=user_config.llm.api_key,
model=model,
base_url=user_config.llm.base_url,
params=OpenAILLMService.InputParams(temperature=0.1),
)
elif user_config.llm.provider == ServiceProviders.GOOGLE.value:
# Use the correct InputParams class for Google to avoid propagating OpenAI-specific
# NOT_GIVEN sentinels that break Pydantic validation in GoogleLLMService.

View file

@ -70,6 +70,7 @@ class PipecatEngine:
] = None,
embeddings_api_key: Optional[str] = None,
embeddings_model: Optional[str] = None,
embeddings_base_url: Optional[str] = None,
):
self.task = task
self.llm = llm
@ -110,6 +111,7 @@ class PipecatEngine:
# Embeddings configuration (passed from run_pipeline.py)
self._embeddings_api_key: Optional[str] = embeddings_api_key
self._embeddings_model: Optional[str] = embeddings_model
self._embeddings_base_url: Optional[str] = embeddings_base_url
async def _get_organization_id(self) -> Optional[int]:
"""Get and cache the organization ID from workflow run."""
@ -352,6 +354,7 @@ class PipecatEngine:
limit=3, # Return top 3 most relevant chunks
embeddings_api_key=self._embeddings_api_key,
embeddings_model=self._embeddings_model,
embeddings_base_url=self._embeddings_base_url,
)
await function_call_params.result_callback(result)

View file

@ -28,6 +28,7 @@ async def retrieve_from_knowledge_base(
limit: int = 3,
embeddings_api_key: Optional[str] = None,
embeddings_model: Optional[str] = None,
embeddings_base_url: Optional[str] = None,
) -> Dict[str, Any]:
"""Retrieve relevant information from the knowledge base using vector similarity search.
@ -43,6 +44,7 @@ async def retrieve_from_knowledge_base(
limit: Maximum number of chunks to return (default: 3)
embeddings_api_key: Optional API key for embedding service
embeddings_model: Optional model ID for embedding service
embeddings_base_url: Optional base URL for embedding service
Returns:
Dictionary containing:
@ -70,6 +72,7 @@ async def retrieve_from_knowledge_base(
limit,
embeddings_api_key,
embeddings_model,
embeddings_base_url,
)
# Create span with parent context
@ -106,6 +109,7 @@ async def retrieve_from_knowledge_base(
limit,
embeddings_api_key,
embeddings_model,
embeddings_base_url,
)
# Add result metadata to span
@ -179,6 +183,7 @@ async def retrieve_from_knowledge_base(
limit,
embeddings_api_key,
embeddings_model,
embeddings_base_url,
)
else:
# Tracing is disabled - perform retrieval without tracing
@ -199,6 +204,7 @@ async def _perform_retrieval(
limit: int,
embeddings_api_key: Optional[str] = None,
embeddings_model: Optional[str] = None,
embeddings_base_url: Optional[str] = None,
) -> Dict[str, Any]:
"""Internal function to perform the actual retrieval operation.
@ -213,6 +219,7 @@ async def _perform_retrieval(
max_tokens=128, # This is only used for chunking, not for retrieval
api_key=embeddings_api_key,
model_id=embeddings_model or "text-embedding-3-small",
base_url=embeddings_base_url,
)
# Perform vector similarity search

View file

@ -124,11 +124,13 @@ async def process_knowledge_base_document(
# Try to get user's embeddings configuration
embeddings_api_key = None
embeddings_model = None
embeddings_base_url = None
if document.created_by:
user_config = await db_client.get_user_configurations(document.created_by)
if user_config.embeddings:
embeddings_api_key = user_config.embeddings.api_key
embeddings_model = user_config.embeddings.model
embeddings_base_url = getattr(user_config.embeddings, "base_url", None)
logger.info(f"Using user embeddings config: model={embeddings_model}")
# Check if API key is configured
@ -148,6 +150,7 @@ async def process_knowledge_base_document(
max_tokens=max_tokens,
api_key=embeddings_api_key,
model_id=embeddings_model or "text-embedding-3-small",
base_url=embeddings_base_url,
)
# Step 1: Convert document with docling