services(openai): send LLMResponseStartFrame for each completion
This commit is contained in:
@@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added initial interruptions support.
|
- Added initial interruptions support. The assitant contexts (or aggregators)
|
||||||
|
should now be placed after the output transport. This way, only the completed
|
||||||
|
spoken context is added to the assistant context.
|
||||||
|
|
||||||
- Added `VADParams` so you can control voice confidence level and others.
|
- Added `VADParams` so you can control voice confidence level and others.
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ async def main(room_url: str, token):
|
|||||||
llm,
|
llm,
|
||||||
tts,
|
tts,
|
||||||
transport.output(),
|
transport.output(),
|
||||||
tma_out])
|
tma_out
|
||||||
|
])
|
||||||
|
|
||||||
task = PipelineTask(pipeline, allow_interruptions=True)
|
task = PipelineTask(pipeline, allow_interruptions=True)
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ class FrameProcessor:
|
|||||||
|
|
||||||
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
async def push_frame(self, frame: Frame, direction: FrameDirection = FrameDirection.DOWNSTREAM):
|
||||||
if direction == FrameDirection.DOWNSTREAM and self._next:
|
if direction == FrameDirection.DOWNSTREAM and self._next:
|
||||||
if not isinstance(frame, AudioRawFrame):
|
logger.trace(f"Pushing {frame} from {self} to {self._next}")
|
||||||
logger.trace(f"Pushing {frame} from {self} to {self._next}")
|
|
||||||
await self._next.process_frame(frame, direction)
|
await self._next.process_frame(frame, direction)
|
||||||
elif direction == FrameDirection.UPSTREAM and self._prev:
|
elif direction == FrameDirection.UPSTREAM and self._prev:
|
||||||
logger.trace(f"Pushing {frame} upstream from {self} to {self._prev}")
|
logger.trace(f"Pushing {frame} upstream from {self} to {self._prev}")
|
||||||
|
|||||||
@@ -74,10 +74,12 @@ class TTSService(AIService):
|
|||||||
await self._push_tts_frames(text)
|
await self._push_tts_frames(text)
|
||||||
|
|
||||||
async def _push_tts_frames(self, text: str):
|
async def _push_tts_frames(self, text: str):
|
||||||
await self.push_frame(TextFrame(text))
|
|
||||||
await self.push_frame(TTSStartedFrame())
|
await self.push_frame(TTSStartedFrame())
|
||||||
await self.process_generator(self.run_tts(text))
|
await self.process_generator(self.run_tts(text))
|
||||||
await self.push_frame(TTSStoppedFrame())
|
await self.push_frame(TTSStoppedFrame())
|
||||||
|
# We send the original text after the audio. This way, if we are
|
||||||
|
# interrupted, the text is not added to the assistant context.
|
||||||
|
await self.push_frame(TextFrame(text))
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
if isinstance(frame, TextFrame):
|
if isinstance(frame, TextFrame):
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ class BaseOpenAILLMService(LLMService):
|
|||||||
function_name = ""
|
function_name = ""
|
||||||
arguments = ""
|
arguments = ""
|
||||||
|
|
||||||
await self.push_frame(LLMResponseStartFrame())
|
|
||||||
|
|
||||||
chunk_stream: AsyncStream[ChatCompletionChunk] = (
|
chunk_stream: AsyncStream[ChatCompletionChunk] = (
|
||||||
await self._stream_chat_completions(context)
|
await self._stream_chat_completions(context)
|
||||||
)
|
)
|
||||||
@@ -132,15 +130,15 @@ class BaseOpenAILLMService(LLMService):
|
|||||||
# completes
|
# completes
|
||||||
arguments += tool_call.function.arguments
|
arguments += tool_call.function.arguments
|
||||||
elif chunk.choices[0].delta.content:
|
elif chunk.choices[0].delta.content:
|
||||||
|
await self.push_frame(LLMResponseStartFrame())
|
||||||
await self.push_frame(TextFrame(chunk.choices[0].delta.content))
|
await self.push_frame(TextFrame(chunk.choices[0].delta.content))
|
||||||
|
await self.push_frame(LLMResponseEndFrame())
|
||||||
|
|
||||||
# if we got a function name and arguments, yield the frame with all the info so
|
# if we got a function name and arguments, yield the frame with all the info so
|
||||||
# frame consumers can take action based on the function call.
|
# frame consumers can take action based on the function call.
|
||||||
# if function_name and arguments:
|
# if function_name and arguments:
|
||||||
# yield LLMFunctionCallFrame(function_name=function_name, arguments=arguments)
|
# yield LLMFunctionCallFrame(function_name=function_name, arguments=arguments)
|
||||||
|
|
||||||
await self.push_frame(LLMResponseEndFrame())
|
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
context = None
|
context = None
|
||||||
if isinstance(frame, OpenAILLMContextFrame):
|
if isinstance(frame, OpenAILLMContextFrame):
|
||||||
|
|||||||
Reference in New Issue
Block a user