From 696196e30c459375c2bd0c18014233fd94eb443a Mon Sep 17 00:00:00 2001 From: dhruvladia-sarvam Date: Tue, 24 Mar 2026 16:29:58 +0530 Subject: [PATCH] alignment with pr 4081 --- README.md | 2 +- .../07z-interruptible-sarvam-http.py | 10 +-- .../foundational/07z-interruptible-sarvam.py | 19 ++--- scripts/evals/run-release-evals.py | 1 + src/pipecat/services/sarvam/llm.py | 80 +++++-------------- 5 files changed, 36 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 8af4f942c..2f6eb672d 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Catch new features, interviews, and how-tos on our [Pipecat TV](https://www.yout | Category | Services | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Speech-to-Text | [AssemblyAI](https://docs.pipecat.ai/server/services/stt/assemblyai), [AWS](https://docs.pipecat.ai/server/services/stt/aws), [Azure](https://docs.pipecat.ai/server/services/stt/azure), [Cartesia](https://docs.pipecat.ai/server/services/stt/cartesia), [Deepgram](https://docs.pipecat.ai/server/services/stt/deepgram), [ElevenLabs](https://docs.pipecat.ai/server/services/stt/elevenlabs), [Fal Wizper](https://docs.pipecat.ai/server/services/stt/fal), [Gladia](https://docs.pipecat.ai/server/services/stt/gladia), [Google](https://docs.pipecat.ai/server/services/stt/google), [Gradium](https://docs.pipecat.ai/server/services/stt/gradium), [Groq (Whisper)](https://docs.pipecat.ai/server/services/stt/groq), [NVIDIA Riva](https://docs.pipecat.ai/server/services/stt/riva), [OpenAI (Whisper)](https://docs.pipecat.ai/server/services/stt/openai), [SambaNova (Whisper)](https://docs.pipecat.ai/server/services/stt/sambanova), [Sarvam](https://docs.pipecat.ai/server/services/stt/sarvam), [Soniox](https://docs.pipecat.ai/server/services/stt/soniox), [Speechmatics](https://docs.pipecat.ai/server/services/stt/speechmatics), [Whisper](https://docs.pipecat.ai/server/services/stt/whisper) | -| LLMs | [Anthropic](https://docs.pipecat.ai/server/services/llm/anthropic), [AWS](https://docs.pipecat.ai/server/services/llm/aws), [Azure](https://docs.pipecat.ai/server/services/llm/azure), [Cerebras](https://docs.pipecat.ai/server/services/llm/cerebras), [DeepSeek](https://docs.pipecat.ai/server/services/llm/deepseek), [Fireworks AI](https://docs.pipecat.ai/server/services/llm/fireworks), [Gemini](https://docs.pipecat.ai/server/services/llm/gemini), [Grok](https://docs.pipecat.ai/server/services/llm/grok), [Groq](https://docs.pipecat.ai/server/services/llm/groq), [Mistral](https://docs.pipecat.ai/server/services/llm/mistral), [NVIDIA NIM](https://docs.pipecat.ai/server/services/llm/nim), [Ollama](https://docs.pipecat.ai/server/services/llm/ollama), [OpenAI](https://docs.pipecat.ai/server/services/llm/openai), [OpenRouter](https://docs.pipecat.ai/server/services/llm/openrouter), [Perplexity](https://docs.pipecat.ai/server/services/llm/perplexity), [Qwen](https://docs.pipecat.ai/server/services/llm/qwen), [SambaNova](https://docs.pipecat.ai/server/services/llm/sambanova) [Together AI](https://docs.pipecat.ai/server/services/llm/together) | +| LLMs | [Anthropic](https://docs.pipecat.ai/server/services/llm/anthropic), [AWS](https://docs.pipecat.ai/server/services/llm/aws), [Azure](https://docs.pipecat.ai/server/services/llm/azure), [Cerebras](https://docs.pipecat.ai/server/services/llm/cerebras), [DeepSeek](https://docs.pipecat.ai/server/services/llm/deepseek), [Fireworks AI](https://docs.pipecat.ai/server/services/llm/fireworks), [Gemini](https://docs.pipecat.ai/server/services/llm/gemini), [Grok](https://docs.pipecat.ai/server/services/llm/grok), [Groq](https://docs.pipecat.ai/server/services/llm/groq), [Mistral](https://docs.pipecat.ai/server/services/llm/mistral), [NVIDIA NIM](https://docs.pipecat.ai/server/services/llm/nim), [Ollama](https://docs.pipecat.ai/server/services/llm/ollama), [OpenAI](https://docs.pipecat.ai/server/services/llm/openai), [OpenRouter](https://docs.pipecat.ai/server/services/llm/openrouter), [Perplexity](https://docs.pipecat.ai/server/services/llm/perplexity), [Qwen](https://docs.pipecat.ai/server/services/llm/qwen), [SambaNova](https://docs.pipecat.ai/server/services/llm/sambanova), [Sarvam](https://docs.pipecat.ai/server/services/llm/sarvam), [Together AI](https://docs.pipecat.ai/server/services/llm/together) | | Text-to-Speech | [Async](https://docs.pipecat.ai/server/services/tts/asyncai), [AWS](https://docs.pipecat.ai/server/services/tts/aws), [Azure](https://docs.pipecat.ai/server/services/tts/azure), [Camb AI](https://docs.pipecat.ai/server/services/tts/camb), [Cartesia](https://docs.pipecat.ai/server/services/tts/cartesia), [Deepgram](https://docs.pipecat.ai/server/services/tts/deepgram), [ElevenLabs](https://docs.pipecat.ai/server/services/tts/elevenlabs), [Fish](https://docs.pipecat.ai/server/services/tts/fish), [Google](https://docs.pipecat.ai/server/services/tts/google), [Gradium](https://docs.pipecat.ai/server/services/tts/gradium), [Groq](https://docs.pipecat.ai/server/services/tts/groq), [Hume](https://docs.pipecat.ai/server/services/tts/hume), [Inworld](https://docs.pipecat.ai/server/services/tts/inworld), [LMNT](https://docs.pipecat.ai/server/services/tts/lmnt), [MiniMax](https://docs.pipecat.ai/server/services/tts/minimax), [Neuphonic](https://docs.pipecat.ai/server/services/tts/neuphonic), [NVIDIA Riva](https://docs.pipecat.ai/server/services/tts/riva), [OpenAI](https://docs.pipecat.ai/server/services/tts/openai), [Piper](https://docs.pipecat.ai/server/services/tts/piper), [Resemble](https://docs.pipecat.ai/server/services/tts/resemble), [Rime](https://docs.pipecat.ai/server/services/tts/rime), [Sarvam](https://docs.pipecat.ai/server/services/tts/sarvam), [Speechmatics](https://docs.pipecat.ai/server/services/tts/speechmatics), [XTTS](https://docs.pipecat.ai/server/services/tts/xtts) | | Speech-to-Speech | [AWS Nova Sonic](https://docs.pipecat.ai/server/services/s2s/aws), [Gemini Multimodal Live](https://docs.pipecat.ai/server/services/s2s/gemini), [Grok Voice Agent](https://docs.pipecat.ai/server/services/s2s/grok), [OpenAI Realtime](https://docs.pipecat.ai/server/services/s2s/openai), [Ultravox](https://docs.pipecat.ai/server/services/s2s/ultravox), | | Transport | [Daily (WebRTC)](https://docs.pipecat.ai/server/services/transport/daily), [FastAPI Websocket](https://docs.pipecat.ai/server/services/transport/fastapi-websocket), [SmallWebRTCTransport](https://docs.pipecat.ai/server/services/transport/small-webrtc), [WebSocket Server](https://docs.pipecat.ai/server/services/transport/websocket-server), Local | diff --git a/examples/foundational/07z-interruptible-sarvam-http.py b/examples/foundational/07z-interruptible-sarvam-http.py index 76c892fd9..09938cf96 100644 --- a/examples/foundational/07z-interruptible-sarvam-http.py +++ b/examples/foundational/07z-interruptible-sarvam-http.py @@ -23,7 +23,7 @@ from pipecat.processors.aggregators.llm_response_universal import ( ) from pipecat.runner.types import RunnerArguments from pipecat.runner.utils import create_transport -from pipecat.services.openai.llm import OpenAILLMService +from pipecat.services.sarvam.llm import SarvamLLMService from pipecat.services.sarvam.stt import SarvamSTTService from pipecat.services.sarvam.tts import SarvamHttpTTSService from pipecat.transcriptions.language import Language @@ -72,10 +72,10 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): ), ) - llm = OpenAILLMService( - api_key=os.getenv("OPENAI_API_KEY"), - settings=OpenAILLMService.Settings( - system_instruction="You are a helpful LLM in a WebRTC call. Your goal is to demonstrate your capabilities in a succinct way. Your output will be spoken aloud, so avoid special characters that can't easily be spoken, such as emojis or bullet points. Respond to what the user said in a creative and helpful way.", + llm = SarvamLLMService( + api_key=os.getenv("SARVAM_API_KEY"), + settings=SarvamLLMService.Settings( + system_instruction="You are a helpful assistant in a voice conversation. Your responses will be spoken aloud, so avoid emojis, bullet points, or other formatting that can't be spoken. Respond to what the user said in a creative, helpful, and brief way.", ), ) diff --git a/examples/foundational/07z-interruptible-sarvam.py b/examples/foundational/07z-interruptible-sarvam.py index 915ea2c3d..50192bd7f 100644 --- a/examples/foundational/07z-interruptible-sarvam.py +++ b/examples/foundational/07z-interruptible-sarvam.py @@ -13,7 +13,7 @@ from pipecat.audio.vad.silero import SileroVADAnalyzer from pipecat.frames.frames import LLMRunFrame from pipecat.pipeline.pipeline import Pipeline from pipecat.pipeline.runner import PipelineRunner -from pipecat.pipeline.task import PipelineParams, PipelineTask +from pipecat.pipeline.task import IDLE_TIMEOUT_SECS, PipelineParams, PipelineTask from pipecat.processors.aggregators.llm_context import LLMContext from pipecat.processors.aggregators.llm_response_universal import ( LLMContextAggregatorPair, @@ -21,7 +21,7 @@ from pipecat.processors.aggregators.llm_response_universal import ( ) from pipecat.runner.types import RunnerArguments from pipecat.runner.utils import create_transport -from pipecat.services.openai.llm import OpenAILLMService +from pipecat.services.sarvam.llm import SarvamLLMService from pipecat.services.sarvam.stt import SarvamSTTService from pipecat.services.sarvam.tts import SarvamTTSService from pipecat.transports.base_transport import BaseTransport, TransportParams @@ -55,21 +55,21 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): stt = SarvamSTTService( api_key=os.getenv("SARVAM_API_KEY"), settings=SarvamSTTService.Settings( - model="saarika:v2.5", + model="saaras:v3", ), ) tts = SarvamTTSService( api_key=os.getenv("SARVAM_API_KEY"), settings=SarvamTTSService.Settings( - model="bulbul:v2", - voice="manisha", + model="bulbul:v3", + voice="shubh", ), ) - llm = OpenAILLMService( - api_key=os.getenv("OPENAI_API_KEY"), - settings=OpenAILLMService.Settings( - system_instruction="You are a helpful LLM in a WebRTC call. Your goal is to demonstrate your capabilities in a succinct way. Your output will be spoken aloud, so avoid special characters that can't easily be spoken, such as emojis or bullet points. Respond to what the user said in a creative and helpful way.", + llm = SarvamLLMService( + api_key=os.getenv("SARVAM_API_KEY"), + settings=SarvamLLMService.Settings( + model="sarvam-30b", ), ) @@ -96,6 +96,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments): params=PipelineParams( enable_metrics=True, enable_usage_metrics=True, + allow_interruptions=True, ), ) diff --git a/scripts/evals/run-release-evals.py b/scripts/evals/run-release-evals.py index 625f33564..b96e608c6 100644 --- a/scripts/evals/run-release-evals.py +++ b/scripts/evals/run-release-evals.py @@ -184,6 +184,7 @@ TESTS_14 = [ ("14v-function-calling-openai.py", EVAL_WEATHER), ("14w-function-calling-mistral.py", EVAL_WEATHER), ("14x-function-calling-openpipe.py", EVAL_WEATHER), + ("14y-function-calling-sarvam.py", EVAL_WEATHER), # Video ("14d-function-calling-anthropic-video.py", EVAL_VISION_CAMERA), ("14d-function-calling-aws-video.py", EVAL_VISION_CAMERA), diff --git a/src/pipecat/services/sarvam/llm.py b/src/pipecat/services/sarvam/llm.py index 5b25e3068..3b14273cd 100644 --- a/src/pipecat/services/sarvam/llm.py +++ b/src/pipecat/services/sarvam/llm.py @@ -36,19 +36,16 @@ class SarvamLLMSettings(OpenAILLMSettings): class SarvamLLMService(OpenAILLMService): - """Sarvam LLM service using Sarvam's OpenAI-compatible chat completions API. + """A service for interacting with Sarvam's API using the OpenAI-compatible interface. - This service extends ``OpenAILLMService`` while adding Sarvam-specific behavior: - - - model allow-list validation - - request shaping for Sarvam-compatible parameters - - Sarvam auth header wiring (``api-subscription-key``) - - SDK User-Agent propagation on every API call + This service extends OpenAILLMService to connect to Sarvam's API endpoint while + maintaining full compatibility with OpenAI's interface and functionality. """ _SUPPORTED_MODELS = frozenset( {"sarvam-30b", "sarvam-30b-16k", "sarvam-105b", "sarvam-105b-32k"} ) + _TOOL_CALLING_MODELS = _SUPPORTED_MODELS Settings = SarvamLLMSettings _settings: Settings @@ -70,29 +67,20 @@ class SarvamLLMService(OpenAILLMService): default_headers: Additional HTTP headers to include in requests. **kwargs: Additional keyword arguments passed to ``OpenAILLMService``. """ - # Initialize defaults with concrete values for Sarvam-specific fields. + # Initialize only Sarvam-specific defaults; inherited defaults are + # provided by the OpenAI base service initialization. default_settings = self.Settings( model="sarvam-30b", - system_instruction=None, - frequency_penalty=NOT_GIVEN, - presence_penalty=NOT_GIVEN, - seed=NOT_GIVEN, - temperature=NOT_GIVEN, - top_p=NOT_GIVEN, - top_k=None, - max_tokens=NOT_GIVEN, - max_completion_tokens=NOT_GIVEN, - filter_incomplete_user_turns=False, - user_turn_completion_config=None, - extra={}, wiki_grounding=None, reasoning_effort=None, ) - # Apply settings delta (canonical API, always wins). + # Apply settings delta (canonical API, always wins) if settings is not None: default_settings.apply_update(settings) + self._validate_model(default_settings.model) + super().__init__( api_key=api_key, base_url=base_url, @@ -100,9 +88,12 @@ class SarvamLLMService(OpenAILLMService): default_headers=default_headers, **kwargs, ) - # Keep Sarvam-specific settings object so runtime updates include - # ``wiki_grounding`` and ``reasoning_effort`` without extra bridging. - self._settings = default_settings + # Rehydrate into Sarvam settings using inherited concrete store values + # from base initialization, then layer Sarvam-specific fields. + sarvam_settings = self.Settings.from_mapping(self._settings.given_fields()) + sarvam_settings.wiki_grounding = default_settings.wiki_grounding + sarvam_settings.reasoning_effort = default_settings.reasoning_effort + self._settings = sarvam_settings def create_client( self, @@ -156,43 +147,10 @@ class SarvamLLMService(OpenAILLMService): return params - async def _call_with_raw_sarvam_errors(self, awaitable: Awaitable[_T]) -> _T: - """Await an OpenAI call while preserving Sarvam raw error payloads. - - BaseOpenAILLMService handles pipeline-frame exceptions via push_error(), - but direct helper methods like ``get_chat_completions`` and - ``run_inference`` are often consumed directly. We normalize those errors - here so applications consistently receive server-provided messages. - """ - try: - return await awaitable - except (APITimeoutError, asyncio.TimeoutError, httpx.TimeoutException): - raise - except Exception as e: - raise RuntimeError(str(e)) from e - - async def get_chat_completions( - self, params_from_context: OpenAILLMInvocationParams - ) -> AsyncStream[ChatCompletionChunk]: - """Get streaming chat completions with Sarvam raw error passthrough.""" - return await self._call_with_raw_sarvam_errors( - super().get_chat_completions(params_from_context) - ) - - async def run_inference( - self, - context: LLMContext | OpenAILLMContext, - max_tokens: Optional[int] = None, - system_instruction: Optional[str] = None, - ) -> Optional[str]: - """Run one-shot inference and preserve Sarvam raw server errors.""" - return await self._call_with_raw_sarvam_errors( - super().run_inference( - context, - max_tokens=max_tokens, - system_instruction=system_instruction, - ) - ) + def _validate_model(self, model: str): + if model not in self._SUPPORTED_MODELS: + allowed = ", ".join(sorted(self._SUPPORTED_MODELS)) + raise ValueError(f"Unsupported Sarvam LLM model '{model}'. Allowed values: {allowed}.") def _validate_tool_parameters(self, params_from_context: OpenAILLMInvocationParams): tools = params_from_context.get("tools", NOT_GIVEN)