user image requests can now be related to function calls
This commit is contained in:
@@ -39,7 +39,12 @@ async def get_weather(function_name, tool_call_id, arguments, llm, context, resu
|
|||||||
|
|
||||||
async def get_image(function_name, tool_call_id, arguments, llm, context, result_callback):
|
async def get_image(function_name, tool_call_id, arguments, llm, context, result_callback):
|
||||||
question = arguments["question"]
|
question = arguments["question"]
|
||||||
await llm.request_image_frame(user_id=video_participant_id, text_content=question)
|
await llm.request_image_frame(
|
||||||
|
user_id=video_participant_id,
|
||||||
|
function_name=function_name,
|
||||||
|
tool_call_id=tool_call_id,
|
||||||
|
text_content=question,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
|||||||
@@ -39,7 +39,12 @@ async def get_weather(function_name, tool_call_id, arguments, llm, context, resu
|
|||||||
async def get_image(function_name, tool_call_id, arguments, llm, context, result_callback):
|
async def get_image(function_name, tool_call_id, arguments, llm, context, result_callback):
|
||||||
logger.debug(f"!!! IN get_image {video_participant_id}, {arguments}")
|
logger.debug(f"!!! IN get_image {video_participant_id}, {arguments}")
|
||||||
question = arguments["question"]
|
question = arguments["question"]
|
||||||
await llm.request_image_frame(user_id=video_participant_id, text_content=question)
|
await llm.request_image_frame(
|
||||||
|
user_id=video_participant_id,
|
||||||
|
function_name=function_name,
|
||||||
|
tool_call_id=tool_call_id,
|
||||||
|
text_content=question,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
@@ -141,7 +146,7 @@ indicate you should use the get_image tool are:
|
|||||||
await transport.capture_participant_transcription(participant["id"])
|
await transport.capture_participant_transcription(participant["id"])
|
||||||
await transport.capture_participant_video(video_participant_id, framerate=0)
|
await transport.capture_participant_video(video_participant_id, framerate=0)
|
||||||
# Kick off the conversation.
|
# Kick off the conversation.
|
||||||
await tts.say("Hi! Ask me about the weather in San Francisco.")
|
await task.queue_frames([context_aggregator.user().get_context_frame()])
|
||||||
|
|
||||||
runner = PipelineRunner()
|
runner = PipelineRunner()
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,12 @@ async def get_weather(function_name, tool_call_id, arguments, llm, context, resu
|
|||||||
async def get_image(function_name, tool_call_id, arguments, llm, context, result_callback):
|
async def get_image(function_name, tool_call_id, arguments, llm, context, result_callback):
|
||||||
logger.debug(f"!!! IN get_image {video_participant_id}, {arguments}")
|
logger.debug(f"!!! IN get_image {video_participant_id}, {arguments}")
|
||||||
question = arguments["question"]
|
question = arguments["question"]
|
||||||
await llm.request_image_frame(user_id=video_participant_id, text_content=question)
|
await llm.request_image_frame(
|
||||||
|
user_id=video_participant_id,
|
||||||
|
function_name=function_name,
|
||||||
|
tool_call_id=tool_call_id,
|
||||||
|
text_content=question,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
|||||||
@@ -54,7 +54,12 @@ async def fetch_weather_from_api(function_name, tool_call_id, args, llm, context
|
|||||||
|
|
||||||
async def get_image(function_name, tool_call_id, arguments, llm, context, result_callback):
|
async def get_image(function_name, tool_call_id, arguments, llm, context, result_callback):
|
||||||
question = arguments["question"]
|
question = arguments["question"]
|
||||||
await llm.request_image_frame(user_id=video_participant_id, text_content=question)
|
await llm.request_image_frame(
|
||||||
|
user_id=video_participant_id,
|
||||||
|
function_name=function_name,
|
||||||
|
tool_call_id=tool_call_id,
|
||||||
|
text_content=question,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_saved_conversation_filenames(
|
async def get_saved_conversation_filenames(
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ from pipecat.frames.frames import (
|
|||||||
OutputImageRawFrame,
|
OutputImageRawFrame,
|
||||||
SpriteFrame,
|
SpriteFrame,
|
||||||
TextFrame,
|
TextFrame,
|
||||||
UserImageRawFrame,
|
|
||||||
UserImageRequestFrame,
|
UserImageRequestFrame,
|
||||||
)
|
)
|
||||||
from pipecat.pipeline.parallel_pipeline import ParallelPipeline
|
from pipecat.pipeline.parallel_pipeline import ParallelPipeline
|
||||||
|
|||||||
@@ -662,13 +662,19 @@ class TransportMessageUrgentFrame(SystemFrame):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class UserImageRequestFrame(SystemFrame):
|
class UserImageRequestFrame(SystemFrame):
|
||||||
"""A frame user to request an image from the given user."""
|
"""A frame to request an image from the given user. The frame might be
|
||||||
|
generated by a function call in which case the corresponding fields will be
|
||||||
|
properly set.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
context: Optional[Any] = None
|
context: Optional[Any] = None
|
||||||
|
function_name: Optional[str] = None
|
||||||
|
tool_call_id: Optional[str] = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name}, user: {self.user_id}"
|
return f"{self.name}(user: {self.user_id}, function: {self.function_name}, request: {self.tool_call_id})"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -698,10 +704,11 @@ class UserImageRawFrame(InputImageRawFrame):
|
|||||||
"""An image associated to a user."""
|
"""An image associated to a user."""
|
||||||
|
|
||||||
user_id: str
|
user_id: str
|
||||||
|
request: Optional[UserImageRequestFrame] = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
pts = format_pts(self.pts)
|
pts = format_pts(self.pts)
|
||||||
return f"{self.name}(pts: {pts}, user: {self.user_id}, size: {self.size}, format: {self.format})"
|
return f"{self.name}(pts: {pts}, user: {self.user_id}, size: {self.size}, format: {self.format}, request: {self.request})"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -715,18 +722,6 @@ class VisionImageRawFrame(InputImageRawFrame):
|
|||||||
return f"{self.name}(pts: {pts}, text: [{self.text}], size: {self.size}, format: {self.format})"
|
return f"{self.name}(pts: {pts}, text: [{self.text}], size: {self.size}, format: {self.format})"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class UserImageMessageFrame(SystemFrame):
|
|
||||||
"""An image associated to a user."""
|
|
||||||
|
|
||||||
user_image_raw_frame: UserImageRawFrame
|
|
||||||
text: Optional[str] = None
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
pts = format_pts(self.pts)
|
|
||||||
return f"{self.name}(pts: {pts}, image: {self.user_image_raw_frame}, text: {self.text})"
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Control frames
|
# Control frames
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from pipecat.frames.frames import (
|
|||||||
StartInterruptionFrame,
|
StartInterruptionFrame,
|
||||||
TextFrame,
|
TextFrame,
|
||||||
TranscriptionFrame,
|
TranscriptionFrame,
|
||||||
UserImageMessageFrame,
|
UserImageRawFrame,
|
||||||
UserStartedSpeakingFrame,
|
UserStartedSpeakingFrame,
|
||||||
UserStoppedSpeakingFrame,
|
UserStoppedSpeakingFrame,
|
||||||
)
|
)
|
||||||
@@ -401,7 +401,7 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
|
|||||||
async def handle_function_call_cancel(self, frame: FunctionCallCancelFrame):
|
async def handle_function_call_cancel(self, frame: FunctionCallCancelFrame):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def handle_image_frame_message(self, frame: UserImageMessageFrame):
|
async def handle_user_image_frame(self, frame: UserImageRawFrame):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
@@ -428,8 +428,8 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
|
|||||||
await self._handle_function_call_result(frame)
|
await self._handle_function_call_result(frame)
|
||||||
elif isinstance(frame, FunctionCallCancelFrame):
|
elif isinstance(frame, FunctionCallCancelFrame):
|
||||||
await self._handle_function_call_cancel(frame)
|
await self._handle_function_call_cancel(frame)
|
||||||
elif isinstance(frame, UserImageMessageFrame):
|
elif isinstance(frame, UserImageRawFrame) and frame.request and frame.request.tool_call_id:
|
||||||
await self._handle_image_frame_message(frame)
|
await self._handle_user_image_frame(frame)
|
||||||
elif isinstance(frame, BotStoppedSpeakingFrame):
|
elif isinstance(frame, BotStoppedSpeakingFrame):
|
||||||
await self.push_aggregation()
|
await self.push_aggregation()
|
||||||
else:
|
else:
|
||||||
@@ -510,8 +510,8 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
|
|||||||
await self.handle_function_call_cancel(frame)
|
await self.handle_function_call_cancel(frame)
|
||||||
del self._function_calls_in_progress[frame.tool_call_id]
|
del self._function_calls_in_progress[frame.tool_call_id]
|
||||||
|
|
||||||
async def _handle_image_frame_message(self, frame: UserImageMessageFrame):
|
async def _handle_user_image_frame(self, frame: UserImageRawFrame):
|
||||||
await self.handle_image_frame_message(frame)
|
await self.handle_user_image_frame(frame)
|
||||||
await self.push_aggregation()
|
await self.push_aggregation()
|
||||||
await self.push_context_frame(FrameDirection.UPSTREAM)
|
await self.push_context_frame(FrameDirection.UPSTREAM)
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ class OpenAILLMContext:
|
|||||||
self._messages: List[ChatCompletionMessageParam] = messages if messages else []
|
self._messages: List[ChatCompletionMessageParam] = messages if messages else []
|
||||||
self._tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = tool_choice
|
self._tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = tool_choice
|
||||||
self._tools: List[ChatCompletionToolParam] | NotGiven | ToolsSchema = tools
|
self._tools: List[ChatCompletionToolParam] | NotGiven | ToolsSchema = tools
|
||||||
self._user_image_request_context = {}
|
|
||||||
self._llm_adapter: Optional[BaseLLMAdapter] = None
|
self._llm_adapter: Optional[BaseLLMAdapter] = None
|
||||||
|
|
||||||
def get_llm_adapter(self) -> Optional[BaseLLMAdapter]:
|
def get_llm_adapter(self) -> Optional[BaseLLMAdapter]:
|
||||||
|
|||||||
@@ -253,9 +253,22 @@ class LLMService(AIService):
|
|||||||
elif None in self._start_callbacks.keys():
|
elif None in self._start_callbacks.keys():
|
||||||
return await self._start_callbacks[None](function_name, self, context)
|
return await self._start_callbacks[None](function_name, self, context)
|
||||||
|
|
||||||
async def request_image_frame(self, user_id: str, *, text_content: Optional[str] = None):
|
async def request_image_frame(
|
||||||
|
self,
|
||||||
|
user_id: str,
|
||||||
|
*,
|
||||||
|
function_name: Optional[str] = None,
|
||||||
|
tool_call_id: Optional[str] = None,
|
||||||
|
text_content: Optional[str] = None,
|
||||||
|
):
|
||||||
await self.push_frame(
|
await self.push_frame(
|
||||||
UserImageRequestFrame(user_id=user_id, context=text_content), FrameDirection.UPSTREAM
|
UserImageRequestFrame(
|
||||||
|
user_id=user_id,
|
||||||
|
function_name=function_name,
|
||||||
|
tool_call_id=tool_call_id,
|
||||||
|
context=text_content,
|
||||||
|
),
|
||||||
|
FrameDirection.UPSTREAM,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _run_function_call(
|
async def _run_function_call(
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ from pipecat.frames.frames import (
|
|||||||
LLMMessagesFrame,
|
LLMMessagesFrame,
|
||||||
LLMTextFrame,
|
LLMTextFrame,
|
||||||
LLMUpdateSettingsFrame,
|
LLMUpdateSettingsFrame,
|
||||||
UserImageMessageFrame,
|
|
||||||
UserImageRawFrame,
|
UserImageRawFrame,
|
||||||
UserImageRequestFrame,
|
|
||||||
VisionImageRawFrame,
|
VisionImageRawFrame,
|
||||||
)
|
)
|
||||||
from pipecat.metrics.metrics import LLMTokenUsage
|
from pipecat.metrics.metrics import LLMTokenUsage
|
||||||
@@ -674,42 +672,7 @@ class AnthropicLLMContext(OpenAILLMContext):
|
|||||||
|
|
||||||
|
|
||||||
class AnthropicUserContextAggregator(LLMUserContextAggregator):
|
class AnthropicUserContextAggregator(LLMUserContextAggregator):
|
||||||
def __init__(self, context: OpenAILLMContext | AnthropicLLMContext, **kwargs):
|
pass
|
||||||
super().__init__(context=context, **kwargs)
|
|
||||||
|
|
||||||
async def process_frame(self, frame, direction):
|
|
||||||
await super().process_frame(frame, direction)
|
|
||||||
# Our parent method has already called push_frame(). So we can't interrupt the
|
|
||||||
# flow here and we don't need to call push_frame() ourselves. Possibly something
|
|
||||||
# to talk through (tagging @aleix). At some point we might need to refactor these
|
|
||||||
# context aggregators.
|
|
||||||
try:
|
|
||||||
if isinstance(frame, UserImageRequestFrame):
|
|
||||||
# The LLM sends a UserImageRequestFrame upstream. Cache any context provided with
|
|
||||||
# that frame so we can use it when we assemble the image message in the assistant
|
|
||||||
# context aggregator.
|
|
||||||
if frame.context:
|
|
||||||
if isinstance(frame.context, str):
|
|
||||||
self._context._user_image_request_context[frame.user_id] = frame.context
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
f"Unexpected UserImageRequestFrame context type: {type(frame.context)}"
|
|
||||||
)
|
|
||||||
del self._context._user_image_request_context[frame.user_id]
|
|
||||||
else:
|
|
||||||
if frame.user_id in self._context._user_image_request_context:
|
|
||||||
del self._context._user_image_request_context[frame.user_id]
|
|
||||||
elif isinstance(frame, UserImageRawFrame):
|
|
||||||
# Push a new AnthropicImageMessageFrame with the text context we cached
|
|
||||||
# downstream to be handled by our assistant context aggregator. This is
|
|
||||||
# necessary so that we add the message to the context in the right order.
|
|
||||||
text = self._context._user_image_request_context.get(frame.user_id) or ""
|
|
||||||
if text:
|
|
||||||
del self._context._user_image_request_context[frame.user_id]
|
|
||||||
frame = UserImageMessageFrame(user_image_raw_frame=frame, text=text)
|
|
||||||
await self.push_frame(frame)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing frame: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -723,9 +686,6 @@ class AnthropicUserContextAggregator(LLMUserContextAggregator):
|
|||||||
|
|
||||||
|
|
||||||
class AnthropicAssistantContextAggregator(LLMAssistantContextAggregator):
|
class AnthropicAssistantContextAggregator(LLMAssistantContextAggregator):
|
||||||
def __init__(self, context: OpenAILLMContext | AnthropicLLMContext, **kwargs):
|
|
||||||
super().__init__(context=context, **kwargs)
|
|
||||||
|
|
||||||
async def handle_function_call_in_progress(self, frame: FunctionCallInProgressFrame):
|
async def handle_function_call_in_progress(self, frame: FunctionCallInProgressFrame):
|
||||||
assistant_message = {"role": "assistant", "content": []}
|
assistant_message = {"role": "assistant", "content": []}
|
||||||
assistant_message["content"].append(
|
assistant_message["content"].append(
|
||||||
@@ -776,10 +736,13 @@ class AnthropicAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
):
|
):
|
||||||
content["content"] = result
|
content["content"] = result
|
||||||
|
|
||||||
async def handle_image_frame_message(self, frame: UserImageMessageFrame):
|
async def handle_user_image_frame(self, frame: UserImageRawFrame):
|
||||||
self._context.add_image_frame_message(
|
await self._update_function_call_result(
|
||||||
format=frame.user_image_raw_frame.format,
|
frame.request.function_name, frame.request.tool_call_id, "COMPLETED"
|
||||||
size=frame.user_image_raw_frame.size,
|
)
|
||||||
image=frame.user_image_raw_frame.image,
|
self._context.add_image_frame_message(
|
||||||
text=frame.text,
|
format=frame.format,
|
||||||
|
size=frame.size,
|
||||||
|
image=frame.image,
|
||||||
|
text=frame.request.context,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ from pipecat.frames.frames import (
|
|||||||
TTSStartedFrame,
|
TTSStartedFrame,
|
||||||
TTSStoppedFrame,
|
TTSStoppedFrame,
|
||||||
TTSTextFrame,
|
TTSTextFrame,
|
||||||
UserImageMessageFrame,
|
UserImageRawFrame,
|
||||||
UserStartedSpeakingFrame,
|
UserStartedSpeakingFrame,
|
||||||
UserStoppedSpeakingFrame,
|
UserStoppedSpeakingFrame,
|
||||||
)
|
)
|
||||||
@@ -119,7 +119,7 @@ class GeminiMultimodalLiveUserContextAggregator(OpenAIUserContextAggregator):
|
|||||||
|
|
||||||
|
|
||||||
class GeminiMultimodalLiveAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
class GeminiMultimodalLiveAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
||||||
async def handle_image_frame_message(self, frame: UserImageMessageFrame):
|
async def handle_user_image_frame(self, frame: UserImageRawFrame):
|
||||||
# We don't want to store any images in the context. Revisit this later
|
# We don't want to store any images in the context. Revisit this later
|
||||||
# when the API evolves.
|
# when the API evolves.
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ from pipecat.frames.frames import (
|
|||||||
TTSStartedFrame,
|
TTSStartedFrame,
|
||||||
TTSStoppedFrame,
|
TTSStoppedFrame,
|
||||||
URLImageRawFrame,
|
URLImageRawFrame,
|
||||||
UserImageMessageFrame,
|
UserImageRawFrame,
|
||||||
VisionImageRawFrame,
|
VisionImageRawFrame,
|
||||||
)
|
)
|
||||||
from pipecat.metrics.metrics import LLMTokenUsage
|
from pipecat.metrics.metrics import LLMTokenUsage
|
||||||
@@ -624,12 +624,15 @@ class GoogleAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|||||||
if part.function_response and part.function_response.id == tool_call_id:
|
if part.function_response and part.function_response.id == tool_call_id:
|
||||||
part.function_response.response = result
|
part.function_response.response = result
|
||||||
|
|
||||||
async def handle_image_frame_message(self, frame: UserImageMessageFrame):
|
async def handle_user_image_frame(self, frame: UserImageRawFrame):
|
||||||
|
await self._update_function_call_result(
|
||||||
|
frame.request.function_name, frame.request.tool_call_id, "COMPLETED"
|
||||||
|
)
|
||||||
self._context.add_image_frame_message(
|
self._context.add_image_frame_message(
|
||||||
format=frame.user_image_raw_frame.format,
|
format=frame.format,
|
||||||
size=frame.user_image_raw_frame.size,
|
size=frame.size,
|
||||||
image=frame.user_image_raw_frame.image,
|
image=frame.image,
|
||||||
text=frame.text,
|
text=frame.request.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ from pipecat.frames.frames import (
|
|||||||
TTSStartedFrame,
|
TTSStartedFrame,
|
||||||
TTSStoppedFrame,
|
TTSStoppedFrame,
|
||||||
URLImageRawFrame,
|
URLImageRawFrame,
|
||||||
UserImageMessageFrame,
|
|
||||||
UserImageRawFrame,
|
UserImageRawFrame,
|
||||||
UserImageRequestFrame,
|
|
||||||
VisionImageRawFrame,
|
VisionImageRawFrame,
|
||||||
)
|
)
|
||||||
from pipecat.metrics.metrics import LLMTokenUsage
|
from pipecat.metrics.metrics import LLMTokenUsage
|
||||||
@@ -557,46 +555,10 @@ class OpenAITTSService(TTSService):
|
|||||||
|
|
||||||
|
|
||||||
class OpenAIUserContextAggregator(LLMUserContextAggregator):
|
class OpenAIUserContextAggregator(LLMUserContextAggregator):
|
||||||
def __init__(self, context: OpenAILLMContext, **kwargs):
|
pass
|
||||||
super().__init__(context=context, **kwargs)
|
|
||||||
|
|
||||||
async def process_frame(self, frame, direction):
|
|
||||||
await super().process_frame(frame, direction)
|
|
||||||
# Our parent method has already called push_frame(). So we can't interrupt the
|
|
||||||
# flow here and we don't need to call push_frame() ourselves.
|
|
||||||
try:
|
|
||||||
if isinstance(frame, UserImageRequestFrame):
|
|
||||||
# The LLM sends a UserImageRequestFrame upstream. Cache any context provided with
|
|
||||||
# that frame so we can use it when we assemble the image message in the assistant
|
|
||||||
# context aggregator.
|
|
||||||
if frame.context:
|
|
||||||
if isinstance(frame.context, str):
|
|
||||||
self._context._user_image_request_context[frame.user_id] = frame.context
|
|
||||||
else:
|
|
||||||
logger.error(
|
|
||||||
f"Unexpected UserImageRequestFrame context type: {type(frame.context)}"
|
|
||||||
)
|
|
||||||
del self._context._user_image_request_context[frame.user_id]
|
|
||||||
else:
|
|
||||||
if frame.user_id in self._context._user_image_request_context:
|
|
||||||
del self._context._user_image_request_context[frame.user_id]
|
|
||||||
elif isinstance(frame, UserImageRawFrame):
|
|
||||||
# Push a new OpenAIImageMessageFrame with the text context we cached
|
|
||||||
# downstream to be handled by our assistant context aggregator. This is
|
|
||||||
# necessary so that we add the message to the context in the right order.
|
|
||||||
text = self._context._user_image_request_context.get(frame.user_id) or ""
|
|
||||||
if text:
|
|
||||||
del self._context._user_image_request_context[frame.user_id]
|
|
||||||
frame = UserImageMessageFrame(user_image_raw_frame=frame, text=text)
|
|
||||||
await self.push_frame(frame)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing frame: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
class OpenAIAssistantContextAggregator(LLMAssistantContextAggregator):
|
class OpenAIAssistantContextAggregator(LLMAssistantContextAggregator):
|
||||||
def __init__(self, context: OpenAILLMContext, **kwargs):
|
|
||||||
super().__init__(context=context, **kwargs)
|
|
||||||
|
|
||||||
async def handle_function_call_in_progress(self, frame: FunctionCallInProgressFrame):
|
async def handle_function_call_in_progress(self, frame: FunctionCallInProgressFrame):
|
||||||
self._context.add_message(
|
self._context.add_message(
|
||||||
{
|
{
|
||||||
@@ -645,10 +607,13 @@ class OpenAIAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
):
|
):
|
||||||
message["content"] = result
|
message["content"] = result
|
||||||
|
|
||||||
async def handle_image_frame_message(self, frame: UserImageMessageFrame):
|
async def handle_user_image_frame(self, frame: UserImageRawFrame):
|
||||||
self._context.add_image_frame_message(
|
await self._update_function_call_result(
|
||||||
format=frame.user_image_raw_frame.format,
|
frame.request.function_name, frame.request.tool_call_id, "COMPLETED"
|
||||||
size=frame.user_image_raw_frame.size,
|
)
|
||||||
image=frame.user_image_raw_frame.image,
|
self._context.add_image_frame_message(
|
||||||
text=frame.text,
|
format=frame.format,
|
||||||
|
size=frame.size,
|
||||||
|
image=frame.image,
|
||||||
|
text=frame.request.context,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -898,7 +898,7 @@ class DailyInputTransport(BaseInputTransport):
|
|||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
|
|
||||||
if isinstance(frame, UserImageRequestFrame):
|
if isinstance(frame, UserImageRequestFrame):
|
||||||
await self.request_participant_image(frame.user_id)
|
await self.request_participant_image(frame)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Frames
|
# Frames
|
||||||
@@ -935,16 +935,16 @@ class DailyInputTransport(BaseInputTransport):
|
|||||||
self._video_renderers[participant_id] = {
|
self._video_renderers[participant_id] = {
|
||||||
"framerate": framerate,
|
"framerate": framerate,
|
||||||
"timestamp": 0,
|
"timestamp": 0,
|
||||||
"render_next_frame": False,
|
"render_next_frame": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
await self._client.capture_participant_video(
|
await self._client.capture_participant_video(
|
||||||
participant_id, self._on_participant_video_frame, framerate, video_source, color_format
|
participant_id, self._on_participant_video_frame, framerate, video_source, color_format
|
||||||
)
|
)
|
||||||
|
|
||||||
async def request_participant_image(self, participant_id: str):
|
async def request_participant_image(self, frame: UserImageRequestFrame):
|
||||||
if participant_id in self._video_renderers:
|
if frame.user_id in self._video_renderers:
|
||||||
self._video_renderers[participant_id]["render_next_frame"] = True
|
self._video_renderers[frame.user_id]["render_next_frame"].append(frame)
|
||||||
|
|
||||||
async def _on_participant_video_frame(self, participant_id: str, buffer, size, format):
|
async def _on_participant_video_frame(self, participant_id: str, buffer, size, format):
|
||||||
render_frame = False
|
render_frame = False
|
||||||
@@ -953,17 +953,24 @@ class DailyInputTransport(BaseInputTransport):
|
|||||||
prev_time = self._video_renderers[participant_id]["timestamp"]
|
prev_time = self._video_renderers[participant_id]["timestamp"]
|
||||||
framerate = self._video_renderers[participant_id]["framerate"]
|
framerate = self._video_renderers[participant_id]["framerate"]
|
||||||
|
|
||||||
|
# Some times we render frames because of a request.
|
||||||
|
request_frame = None
|
||||||
|
|
||||||
if framerate > 0:
|
if framerate > 0:
|
||||||
next_time = prev_time + 1 / framerate
|
next_time = prev_time + 1 / framerate
|
||||||
render_frame = (next_time - curr_time) < 0.1
|
render_frame = (next_time - curr_time) < 0.1
|
||||||
|
|
||||||
elif self._video_renderers[participant_id]["render_next_frame"]:
|
elif self._video_renderers[participant_id]["render_next_frame"]:
|
||||||
self._video_renderers[participant_id]["render_next_frame"] = False
|
request_frame = self._video_renderers[participant_id]["render_next_frame"].pop(0)
|
||||||
render_frame = True
|
render_frame = True
|
||||||
|
|
||||||
if render_frame:
|
if render_frame:
|
||||||
frame = UserImageRawFrame(
|
frame = UserImageRawFrame(
|
||||||
user_id=participant_id, image=buffer, size=size, format=format
|
user_id=participant_id,
|
||||||
|
request=request_frame,
|
||||||
|
image=buffer,
|
||||||
|
size=size,
|
||||||
|
format=format,
|
||||||
)
|
)
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
self._video_renderers[participant_id]["timestamp"] = curr_time
|
self._video_renderers[participant_id]["timestamp"] = curr_time
|
||||||
|
|||||||
Reference in New Issue
Block a user