Update AzureLLMService to use OpenAILLMService
This commit is contained in:
@@ -11,10 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- `GroqLLMService` and `GrokLLMService` for Groq and Grok API integration, with
|
- `GroqLLMService` and `GrokLLMService` for Groq and Grok API integration, with
|
||||||
OpenAI-compatible interface
|
OpenAI-compatible interface
|
||||||
- New examples demonstrating function calling with Groq and Grok
|
- New examples demonstrating function calling with Groq, Grok, and Azure OpenAI
|
||||||
|
|
||||||
- `14f-function-calling-groq.py`
|
- `14f-function-calling-groq.py`
|
||||||
- `14g-function-calling-grok.py`
|
- `14g-function-calling-grok.py`
|
||||||
|
- `14h-function-calling-azure.py`
|
||||||
|
|
||||||
- In order to obtain the audio stored by the `AudioBufferProcessor` you can now
|
- In order to obtain the audio stored by the `AudioBufferProcessor` you can now
|
||||||
also register an `on_audio_data` event handler. The `on_audio_data` handler
|
also register an `on_audio_data` event handler. The `on_audio_data` handler
|
||||||
@@ -43,6 +44,9 @@ async def on_audio_data(processor, audio, sample_rate, num_channels):
|
|||||||
- Updated STT and TTS services with language options that match the supported
|
- Updated STT and TTS services with language options that match the supported
|
||||||
languages for each service.
|
languages for each service.
|
||||||
|
|
||||||
|
- Updated the `AzureLLMService` to use the `OpenAILLMService`. Updated the
|
||||||
|
`api_version` to `2024-09-01-preview`.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
- Removed `AppFrame`. This was used as a special user custom frame, but there's
|
- Removed `AppFrame`. This was used as a special user custom frame, but there's
|
||||||
|
|||||||
141
examples/foundational/14h-function-calling-azure.py
Normal file
141
examples/foundational/14h-function-calling-azure.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from loguru import logger
|
||||||
|
from openai.types.chat import ChatCompletionToolParam
|
||||||
|
from runner import configure
|
||||||
|
|
||||||
|
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
||||||
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
|
from pipecat.pipeline.runner import PipelineRunner
|
||||||
|
from pipecat.pipeline.task import PipelineParams, PipelineTask
|
||||||
|
from pipecat.services.azure import AzureLLMService
|
||||||
|
from pipecat.services.cartesia import CartesiaTTSService
|
||||||
|
from pipecat.services.openai import OpenAILLMContext
|
||||||
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
|
|
||||||
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
logger.remove(0)
|
||||||
|
logger.add(sys.stderr, level="DEBUG")
|
||||||
|
|
||||||
|
|
||||||
|
async def start_fetch_weather(function_name, llm, context):
|
||||||
|
# note: we can't push a frame to the LLM here. the bot
|
||||||
|
# can interrupt itself and/or cause audio overlapping glitches.
|
||||||
|
# possible question for Aleix and Chad about what the right way
|
||||||
|
# to trigger speech is, now, with the new queues/async/sync refactors.
|
||||||
|
# await llm.push_frame(TextFrame("Let me check on that."))
|
||||||
|
logger.debug(f"Starting fetch_weather_from_api with function_name: {function_name}")
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_weather_from_api(function_name, tool_call_id, args, llm, context, result_callback):
|
||||||
|
await result_callback({"conditions": "nice", "temperature": "75"})
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
(room_url, token) = await configure(session)
|
||||||
|
|
||||||
|
transport = DailyTransport(
|
||||||
|
room_url,
|
||||||
|
token,
|
||||||
|
"Respond bot",
|
||||||
|
DailyParams(
|
||||||
|
audio_out_enabled=True,
|
||||||
|
transcription_enabled=True,
|
||||||
|
vad_enabled=True,
|
||||||
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
tts = CartesiaTTSService(
|
||||||
|
api_key=os.getenv("CARTESIA_API_KEY"),
|
||||||
|
voice_id="79a125e8-cd45-4c13-8a67-188112f4dd22", # British Lady
|
||||||
|
)
|
||||||
|
|
||||||
|
llm = AzureLLMService(
|
||||||
|
api_key=os.getenv("AZURE_CHATGPT_API_KEY"),
|
||||||
|
endpoint=os.getenv("AZURE_CHATGPT_ENDPOINT"),
|
||||||
|
model=os.getenv("AZURE_CHATGPT_MODEL"),
|
||||||
|
)
|
||||||
|
# Register a function_name of None to get all functions
|
||||||
|
# sent to the same callback with an additional function_name parameter.
|
||||||
|
llm.register_function(None, fetch_weather_from_api, start_callback=start_fetch_weather)
|
||||||
|
|
||||||
|
tools = [
|
||||||
|
ChatCompletionToolParam(
|
||||||
|
type="function",
|
||||||
|
function={
|
||||||
|
"name": "get_current_weather",
|
||||||
|
"description": "Get the current weather",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"location": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The city and state, e.g. San Francisco, CA",
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["celsius", "fahrenheit"],
|
||||||
|
"description": "The temperature unit to use. Infer this from the users location.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["location", "format"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
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 and helpful way.",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
context = OpenAILLMContext(messages, tools)
|
||||||
|
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,
|
||||||
|
enable_metrics=True,
|
||||||
|
enable_usage_metrics=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@transport.event_handler("on_first_participant_joined")
|
||||||
|
async def on_first_participant_joined(transport, participant):
|
||||||
|
await transport.capture_participant_transcription(participant["id"])
|
||||||
|
# Kick off the conversation.
|
||||||
|
await task.queue_frames([context_aggregator.user().get_context_frame()])
|
||||||
|
|
||||||
|
runner = PipelineRunner()
|
||||||
|
|
||||||
|
await runner.run(task)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -39,7 +39,7 @@ Website = "https://pipecat.ai"
|
|||||||
anthropic = [ "anthropic~=0.34.0" ]
|
anthropic = [ "anthropic~=0.34.0" ]
|
||||||
assemblyai = [ "assemblyai~=0.34.0" ]
|
assemblyai = [ "assemblyai~=0.34.0" ]
|
||||||
aws = [ "boto3~=1.35.27" ]
|
aws = [ "boto3~=1.35.27" ]
|
||||||
azure = [ "azure-cognitiveservices-speech~=1.40.0" ]
|
azure = [ "azure-cognitiveservices-speech~=1.40.0", "openai~=1.50.2" ]
|
||||||
canonical = [ "aiofiles~=24.1.0" ]
|
canonical = [ "aiofiles~=24.1.0" ]
|
||||||
cartesia = [ "cartesia~=1.0.13", "websockets~=13.1" ]
|
cartesia = [ "cartesia~=1.0.13", "websockets~=13.1" ]
|
||||||
daily = [ "daily-python~=0.13.0" ]
|
daily = [ "daily-python~=0.13.0" ]
|
||||||
|
|||||||
@@ -25,13 +25,9 @@ from pipecat.frames.frames import (
|
|||||||
TTSStoppedFrame,
|
TTSStoppedFrame,
|
||||||
URLImageRawFrame,
|
URLImageRawFrame,
|
||||||
)
|
)
|
||||||
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
|
|
||||||
from pipecat.services.ai_services import ImageGenService, STTService, TTSService
|
from pipecat.services.ai_services import ImageGenService, STTService, TTSService
|
||||||
from pipecat.services.openai import (
|
from pipecat.services.openai import (
|
||||||
BaseOpenAILLMService,
|
OpenAILLMService,
|
||||||
OpenAIAssistantContextAggregator,
|
|
||||||
OpenAIContextAggregatorPair,
|
|
||||||
OpenAIUserContextAggregator,
|
|
||||||
)
|
)
|
||||||
from pipecat.transcriptions.language import Language
|
from pipecat.transcriptions.language import Language
|
||||||
from pipecat.utils.time import time_now_iso8601
|
from pipecat.utils.time import time_now_iso8601
|
||||||
@@ -398,33 +394,44 @@ def sample_rate_to_output_format(sample_rate: int) -> SpeechSynthesisOutputForma
|
|||||||
return sample_rate_map.get(sample_rate, SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm)
|
return sample_rate_map.get(sample_rate, SpeechSynthesisOutputFormat.Raw24Khz16BitMonoPcm)
|
||||||
|
|
||||||
|
|
||||||
class AzureLLMService(BaseOpenAILLMService):
|
class AzureLLMService(OpenAILLMService):
|
||||||
|
"""A service for interacting with Azure OpenAI using the OpenAI-compatible interface.
|
||||||
|
|
||||||
|
This service extends OpenAILLMService to connect to Azure's OpenAI endpoint while
|
||||||
|
maintaining full compatibility with OpenAI's interface and functionality.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_key (str): The API key for accessing Azure OpenAI
|
||||||
|
endpoint (str): The Azure endpoint URL
|
||||||
|
model (str): The model identifier to use
|
||||||
|
api_version (str, optional): Azure API version. Defaults to "2024-09-01-preview"
|
||||||
|
**kwargs: Additional keyword arguments passed to OpenAILLMService
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, *, api_key: str, endpoint: str, model: str, api_version: str = "2023-12-01-preview"
|
self,
|
||||||
|
*,
|
||||||
|
api_key: str,
|
||||||
|
endpoint: str,
|
||||||
|
model: str,
|
||||||
|
api_version: str = "2024-09-01-preview",
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
# Initialize variables before calling parent __init__() because that
|
# Initialize variables before calling parent __init__() because that
|
||||||
# will call create_client() and we need those values there.
|
# will call create_client() and we need those values there.
|
||||||
self._endpoint = endpoint
|
self._endpoint = endpoint
|
||||||
self._api_version = api_version
|
self._api_version = api_version
|
||||||
super().__init__(api_key=api_key, model=model)
|
super().__init__(api_key=api_key, model=model, **kwargs)
|
||||||
|
|
||||||
def create_client(self, api_key=None, base_url=None, **kwargs):
|
def create_client(self, api_key=None, base_url=None, **kwargs):
|
||||||
|
"""Create OpenAI-compatible client for Azure OpenAI endpoint."""
|
||||||
|
logger.debug(f"Creating Azure OpenAI client with endpoint {self._endpoint}")
|
||||||
return AsyncAzureOpenAI(
|
return AsyncAzureOpenAI(
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
azure_endpoint=self._endpoint,
|
azure_endpoint=self._endpoint,
|
||||||
api_version=self._api_version,
|
api_version=self._api_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_context_aggregator(
|
|
||||||
context: OpenAILLMContext, *, assistant_expect_stripped_words: bool = True
|
|
||||||
) -> OpenAIContextAggregatorPair:
|
|
||||||
user = OpenAIUserContextAggregator(context)
|
|
||||||
assistant = OpenAIAssistantContextAggregator(
|
|
||||||
user, expect_stripped_words=assistant_expect_stripped_words
|
|
||||||
)
|
|
||||||
return OpenAIContextAggregatorPair(_user=user, _assistant=assistant)
|
|
||||||
|
|
||||||
|
|
||||||
class AzureBaseTTSService(TTSService):
|
class AzureBaseTTSService(TTSService):
|
||||||
class InputParams(BaseModel):
|
class InputParams(BaseModel):
|
||||||
|
|||||||
Reference in New Issue
Block a user