From e04da334d7e1232eb8b79a610e22c41b14724c28 Mon Sep 17 00:00:00 2001 From: sahil suman Date: Sat, 11 Jan 2025 17:50:58 +0530 Subject: [PATCH] add support for openrouter. Signed-off-by: sahil suman --- dot-env.template | 3 + .../07u-interruptible-openrouter.py | 96 +++++++++++++++++++ src/pipecat/services/openrouter.py | 62 ++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 examples/foundational/07u-interruptible-openrouter.py create mode 100644 src/pipecat/services/openrouter.py diff --git a/dot-env.template b/dot-env.template index 597c85260..a4a978ba4 100644 --- a/dot-env.template +++ b/dot-env.template @@ -60,3 +60,6 @@ SIMLI_FACE_ID=... # Krisp KRISP_MODEL_PATH=... + +# OpenRouter +OPENROUTER_API_KEY=... diff --git a/examples/foundational/07u-interruptible-openrouter.py b/examples/foundational/07u-interruptible-openrouter.py new file mode 100644 index 000000000..9aee79e16 --- /dev/null +++ b/examples/foundational/07u-interruptible-openrouter.py @@ -0,0 +1,96 @@ +import asyncio +import os +import sys + +import aiohttp +from dotenv import load_dotenv +from loguru import logger +from runner import configure + +from pipecat.frames.frames import LLMMessagesFrame +from pipecat.pipeline.pipeline import Pipeline +from pipecat.pipeline.runner import PipelineRunner +from pipecat.pipeline.task import PipelineTask, PipelineParams +from pipecat.services.azure import AzureTTSService +from pipecat.services.openrouter import OpenRouterLLMService +from pipecat.services.openai import OpenAILLMContext +from pipecat.transports.services.daily import DailyParams, DailyTransport +from pipecat.audio.vad.silero import SileroVADAnalyzer + +load_dotenv(override=True) + +logger.remove(0) +logger.add(sys.stderr, level="DEBUG") + + +async def main(): + async with aiohttp.ClientSession() as session: + (room_url, token) = await configure(session) + + transport = DailyTransport( + room_url, + token, + "Chatbot", + DailyParams( + audio_out_enabled=True, + transcription_enabled=True, + vad_enabled=True, + vad_analyzer=SileroVADAnalyzer(), + vad_audio_passthrough=True, + ), + ) + + tts = AzureTTSService( + api_key=os.getenv("AZURE_API_KEY"), + region="eastus", + voice="en-US-JennyNeural", + params=AzureTTSService.InputParams( + language="en-US", + rate="1.1", + style="chat", + ), + ) + + llm = OpenRouterLLMService( + api_key=os.getenv("OPENROUTER_API_KEY"), model="microsoft/phi-4" + ) + messages = [ + { + "role": "system", + "content": "You are a helpful LLM in a WebRTC call. Your goal is to demonstrate your capabilities in a succinct way. Your output will be converted to audio so don't include special characters in your answers. Respond to what the user said in a creative, helpful, and brief way.", + } + ] + context = OpenAILLMContext(messages=messages) + context_aggregator = llm.create_context_aggregator(context) + + pipeline = Pipeline( + [ + transport.input(), + context_aggregator.user(), + llm, + tts, + transport.output(), + context_aggregator.assistant(), + ] + ) + + task = PipelineTask( + pipeline, + PipelineParams(allow_interruptions=True), + ) + + @transport.event_handler("on_first_participant_joined") + async def on_first_participant_joined(transport, participant): + await transport.capture_participant_transcription(participant["id"]) + messages.append( + {"role": "system", "content": "Please introduce yourself to the user."} + ) + await task.queue_frames([LLMMessagesFrame(messages)]) + + runner = PipelineRunner() + + await runner.run(task) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/pipecat/services/openrouter.py b/src/pipecat/services/openrouter.py new file mode 100644 index 000000000..62552f83a --- /dev/null +++ b/src/pipecat/services/openrouter.py @@ -0,0 +1,62 @@ +# +# Copyright (c) 2025, Daily +# +# SPDX-License-Identifier: BSD 2-Clause License +# + +from typing import Dict, List + +from loguru import logger + +from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext +from pipecat.services.openai import OpenAILLMService + +try: + from openai import AsyncStream, OpenAI + from openai.types.chat import ChatCompletionChunk, ChatCompletionMessageParam +except ModuleNotFoundError as e: + logger.error(f"Exception: {e}") + logger.error( + "In order to use OpenRouter, you need to `pip install pipecat-ai[openai]`. Also, set `OPENROUTER_API_KEY` environment variable." + ) + raise Exception(f"Missing module: {e}") + + +class OpenRouterLLMService(OpenAILLMService): + def __init__( + self, + *, + api_key: str | None = None, + model: str = "openai/gpt-4o-latest", + base_url: str = "https://openrouter.ai/api/v1", + **kwargs, + ): + super().__init__( + api_key=api_key, + base_url=base_url, + model=model, + **kwargs, + ) + + def create_client(self, api_key=None, base_url=None, **kwargs): + logger.debug(f"Creating OpenRouter client with api {base_url}") + return super().create_client(api_key, base_url, **kwargs) + + async def get_chat_completions( + self, context: OpenAILLMContext, messages: List[ChatCompletionMessageParam] + ) -> AsyncStream[ChatCompletionChunk]: + params = { + "model": self.model_name, + "stream": True, + "messages": messages, + "temperature": self._settings["temperature"], + "top_p": self._settings["top_p"], + "max_tokens": self._settings["max_tokens"], + "frequency_penalty": self._settings["frequency_penalty"], + "presence_penalty": self._settings["presence_penalty"], + } + + params.update(self._settings["extra"]) + + chunks = await self._client.chat.completions.create(**params) + return chunks