Refactor for new on_context_updated callback and new frame properties
This commit is contained in:
17
CHANGELOG.md
17
CHANGELOG.md
@@ -9,10 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added support in `FunctionCallResultFrame` for controlling LLM completions
|
- Added `FunctionCallResultProperties` dataclass to provide a structured way to
|
||||||
via `override_run_llm` flag. When set to `True`, the `run_llm` parameter
|
control function call behavior, including:
|
||||||
determines whether a completion is triggered, allowing finer control over LLM
|
|
||||||
behavior in function calls.
|
- `run_llm`: Controls whether to trigger LLM completion
|
||||||
|
- `on_context_updated`: Optional callback triggered after context update
|
||||||
|
|
||||||
- Added a new foundational example `07e-interruptible-playht-http.py` for easy
|
- Added a new foundational example `07e-interruptible-playht-http.py` for easy
|
||||||
testing of `PlayHTHttpTTSService`.
|
testing of `PlayHTHttpTTSService`.
|
||||||
@@ -35,12 +36,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Added `aws_session_token` to the `PollyTTSService`.
|
- Modified `OpenAIAssistantContextAggregator` to support controlled completions
|
||||||
|
and to emit context update callbacks via `FunctionCallResultProperties`.
|
||||||
|
|
||||||
- Modified `OpenAIAssistantContextAggregator` to respect `override_run_llm`
|
- Added `aws_session_token` to the `PollyTTSService`.
|
||||||
flag when processing function call results. This allows external control over
|
|
||||||
whether function calls trigger LLM completions while maintaining backward
|
|
||||||
compatibility with existing code.
|
|
||||||
|
|
||||||
- Changed the default model for `PlayHTHttpTTSService` to `Play3.0-mini-http`.
|
- Changed the default model for `PlayHTHttpTTSService` to `Play3.0-mini-http`.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any, List, Literal, Mapping, Optional, Tuple
|
from typing import Any, Awaitable, Callable, List, Literal, Mapping, Optional, Tuple
|
||||||
|
|
||||||
from pipecat.audio.vad.vad_analyzer import VADParams
|
from pipecat.audio.vad.vad_analyzer import VADParams
|
||||||
from pipecat.clocks.base_clock import BaseClock
|
from pipecat.clocks.base_clock import BaseClock
|
||||||
@@ -321,6 +321,14 @@ class LLMEnablePromptCachingFrame(DataFrame):
|
|||||||
enable: bool
|
enable: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FunctionCallResultProperties:
|
||||||
|
"""Properties for a function call result frame."""
|
||||||
|
|
||||||
|
run_llm: Optional[bool] = None
|
||||||
|
on_context_updated: Optional[Callable[[], Awaitable[None]]] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FunctionCallResultFrame(DataFrame):
|
class FunctionCallResultFrame(DataFrame):
|
||||||
"""A frame containing the result of an LLM function (tool) call."""
|
"""A frame containing the result of an LLM function (tool) call."""
|
||||||
@@ -329,8 +337,7 @@ class FunctionCallResultFrame(DataFrame):
|
|||||||
tool_call_id: str
|
tool_call_id: str
|
||||||
arguments: str
|
arguments: str
|
||||||
result: Any
|
result: Any
|
||||||
run_llm: bool = True
|
properties: Optional[FunctionCallResultProperties] = None
|
||||||
override_run_llm: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -218,30 +218,20 @@ class OpenAILLMContext:
|
|||||||
await llm.push_frame(progress_frame_upstream, FrameDirection.UPSTREAM)
|
await llm.push_frame(progress_frame_upstream, FrameDirection.UPSTREAM)
|
||||||
|
|
||||||
# Define a callback function that pushes a FunctionCallResultFrame upstream & downstream.
|
# Define a callback function that pushes a FunctionCallResultFrame upstream & downstream.
|
||||||
async def function_call_result_callback(result):
|
async def function_call_result_callback(result, *, properties=None):
|
||||||
# Extract result and frame parameters if provided
|
|
||||||
if isinstance(result, dict) and "result" in result:
|
|
||||||
frame_run_llm = result.get("run_llm", run_llm)
|
|
||||||
frame_override = result.get("override_run_llm", False)
|
|
||||||
else:
|
|
||||||
frame_run_llm = run_llm
|
|
||||||
frame_override = False
|
|
||||||
|
|
||||||
result_frame_downstream = FunctionCallResultFrame(
|
result_frame_downstream = FunctionCallResultFrame(
|
||||||
function_name=function_name,
|
function_name=function_name,
|
||||||
tool_call_id=tool_call_id,
|
tool_call_id=tool_call_id,
|
||||||
arguments=arguments,
|
arguments=arguments,
|
||||||
result=result,
|
result=result,
|
||||||
run_llm=frame_run_llm,
|
properties=properties,
|
||||||
override_run_llm=frame_override,
|
|
||||||
)
|
)
|
||||||
result_frame_upstream = FunctionCallResultFrame(
|
result_frame_upstream = FunctionCallResultFrame(
|
||||||
function_name=function_name,
|
function_name=function_name,
|
||||||
tool_call_id=tool_call_id,
|
tool_call_id=tool_call_id,
|
||||||
arguments=arguments,
|
arguments=arguments,
|
||||||
result=result,
|
result=result,
|
||||||
run_llm=frame_run_llm,
|
properties=properties,
|
||||||
override_run_llm=frame_override,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await llm.push_frame(result_frame_downstream, FrameDirection.DOWNSTREAM)
|
await llm.push_frame(result_frame_downstream, FrameDirection.DOWNSTREAM)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from pipecat.frames.frames import (
|
|||||||
Frame,
|
Frame,
|
||||||
FunctionCallInProgressFrame,
|
FunctionCallInProgressFrame,
|
||||||
FunctionCallResultFrame,
|
FunctionCallResultFrame,
|
||||||
|
FunctionCallResultProperties,
|
||||||
LLMEnablePromptCachingFrame,
|
LLMEnablePromptCachingFrame,
|
||||||
LLMFullResponseEndFrame,
|
LLMFullResponseEndFrame,
|
||||||
LLMFullResponseStartFrame,
|
LLMFullResponseStartFrame,
|
||||||
@@ -742,6 +743,7 @@ class AnthropicAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
return
|
return
|
||||||
|
|
||||||
run_llm = False
|
run_llm = False
|
||||||
|
properties: Optional[FunctionCallResultProperties] = None
|
||||||
|
|
||||||
aggregation = self._aggregation
|
aggregation = self._aggregation
|
||||||
self._reset()
|
self._reset()
|
||||||
@@ -749,6 +751,7 @@ class AnthropicAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
try:
|
try:
|
||||||
if self._function_call_result:
|
if self._function_call_result:
|
||||||
frame = self._function_call_result
|
frame = self._function_call_result
|
||||||
|
properties = frame.properties
|
||||||
self._function_call_result = None
|
self._function_call_result = None
|
||||||
if frame.result:
|
if frame.result:
|
||||||
assistant_message = {"role": "assistant", "content": []}
|
assistant_message = {"role": "assistant", "content": []}
|
||||||
@@ -775,13 +778,11 @@ class AnthropicAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if frame.override_run_llm:
|
if properties and properties.run_llm is not None:
|
||||||
# Explicit override
|
# If the tool call result has a run_llm property, use it
|
||||||
print("Explicit override")
|
run_llm = properties.run_llm
|
||||||
run_llm = frame.run_llm
|
|
||||||
else:
|
else:
|
||||||
# Default behavior
|
# Default behavior
|
||||||
print("Default behavior")
|
|
||||||
run_llm = True
|
run_llm = True
|
||||||
elif aggregation:
|
elif aggregation:
|
||||||
self._context.add_message({"role": "assistant", "content": aggregation})
|
self._context.add_message({"role": "assistant", "content": aggregation})
|
||||||
@@ -800,6 +801,10 @@ class AnthropicAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
if run_llm:
|
if run_llm:
|
||||||
await self._user_context_aggregator.push_context_frame()
|
await self._user_context_aggregator.push_context_frame()
|
||||||
|
|
||||||
|
# Emit the on_context_updated callback once the function call result is added to the context
|
||||||
|
if properties and properties.on_context_updated is not None:
|
||||||
|
await properties.on_context_updated()
|
||||||
|
|
||||||
# Push context frame
|
# Push context frame
|
||||||
frame = OpenAILLMContextFrame(self._context)
|
frame = OpenAILLMContextFrame(self._context)
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from pipecat.frames.frames import (
|
|||||||
AudioRawFrame,
|
AudioRawFrame,
|
||||||
ErrorFrame,
|
ErrorFrame,
|
||||||
Frame,
|
Frame,
|
||||||
|
FunctionCallResultProperties,
|
||||||
LLMFullResponseEndFrame,
|
LLMFullResponseEndFrame,
|
||||||
LLMFullResponseStartFrame,
|
LLMFullResponseStartFrame,
|
||||||
LLMMessagesFrame,
|
LLMMessagesFrame,
|
||||||
@@ -245,6 +246,7 @@ class GoogleAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|||||||
return
|
return
|
||||||
|
|
||||||
run_llm = False
|
run_llm = False
|
||||||
|
properties: Optional[FunctionCallResultProperties] = None
|
||||||
|
|
||||||
aggregation = self._aggregation
|
aggregation = self._aggregation
|
||||||
self._reset()
|
self._reset()
|
||||||
@@ -252,6 +254,7 @@ class GoogleAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|||||||
try:
|
try:
|
||||||
if self._function_call_result:
|
if self._function_call_result:
|
||||||
frame = self._function_call_result
|
frame = self._function_call_result
|
||||||
|
properties = frame.properties
|
||||||
self._function_call_result = None
|
self._function_call_result = None
|
||||||
if frame.result:
|
if frame.result:
|
||||||
logger.debug(f"FunctionCallResultFrame result: {frame.arguments}")
|
logger.debug(f"FunctionCallResultFrame result: {frame.arguments}")
|
||||||
@@ -282,11 +285,11 @@ class GoogleAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if frame.override_run_llm:
|
if properties and properties.run_llm is not None:
|
||||||
# Explicit override
|
# If the tool call result has a run_llm property, use it
|
||||||
run_llm = frame.run_llm
|
run_llm = properties.run_llm
|
||||||
else:
|
else:
|
||||||
# Default behavior
|
# Default behavior is to run the LLM if there are no function calls in progress
|
||||||
run_llm = not bool(self._function_calls_in_progress)
|
run_llm = not bool(self._function_calls_in_progress)
|
||||||
else:
|
else:
|
||||||
if aggregation.strip():
|
if aggregation.strip():
|
||||||
@@ -308,6 +311,10 @@ class GoogleAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|||||||
if run_llm:
|
if run_llm:
|
||||||
await self._user_context_aggregator.push_context_frame()
|
await self._user_context_aggregator.push_context_frame()
|
||||||
|
|
||||||
|
# Emit the on_context_updated callback once the function call result is added to the context
|
||||||
|
if properties and properties.on_context_updated is not None:
|
||||||
|
await properties.on_context_updated()
|
||||||
|
|
||||||
# Push context frame
|
# Push context frame
|
||||||
frame = OpenAILLMContextFrame(self._context)
|
frame = OpenAILLMContextFrame(self._context)
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from pipecat.frames.frames import FunctionCallResultProperties
|
||||||
from pipecat.metrics.metrics import LLMTokenUsage
|
from pipecat.metrics.metrics import LLMTokenUsage
|
||||||
from pipecat.processors.aggregators.openai_llm_context import (
|
from pipecat.processors.aggregators.openai_llm_context import (
|
||||||
OpenAILLMContext,
|
OpenAILLMContext,
|
||||||
@@ -32,6 +34,7 @@ class GrokAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|||||||
return
|
return
|
||||||
|
|
||||||
run_llm = False
|
run_llm = False
|
||||||
|
properties: Optional[FunctionCallResultProperties] = None
|
||||||
|
|
||||||
aggregation = self._aggregation
|
aggregation = self._aggregation
|
||||||
self._reset()
|
self._reset()
|
||||||
@@ -39,6 +42,7 @@ class GrokAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|||||||
try:
|
try:
|
||||||
if self._function_call_result:
|
if self._function_call_result:
|
||||||
frame = self._function_call_result
|
frame = self._function_call_result
|
||||||
|
properties = frame.properties
|
||||||
self._function_call_result = None
|
self._function_call_result = None
|
||||||
if frame.result:
|
if frame.result:
|
||||||
# Grok requires an empty content field for function calls
|
# Grok requires an empty content field for function calls
|
||||||
@@ -65,12 +69,13 @@ class GrokAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|||||||
"tool_call_id": frame.tool_call_id,
|
"tool_call_id": frame.tool_call_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if frame.override_run_llm:
|
if properties and properties.run_llm is not None:
|
||||||
# Explicit override
|
# If the tool call result has a run_llm property, use it
|
||||||
run_llm = frame.run_llm
|
run_llm = properties.run_llm
|
||||||
else:
|
else:
|
||||||
# Default behavior
|
# Default behavior is to run the LLM if there are no function calls in progress
|
||||||
run_llm = not bool(self._function_calls_in_progress)
|
run_llm = not bool(self._function_calls_in_progress)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._context.add_message({"role": "assistant", "content": aggregation})
|
self._context.add_message({"role": "assistant", "content": aggregation})
|
||||||
|
|
||||||
@@ -88,6 +93,10 @@ class GrokAssistantContextAggregator(OpenAIAssistantContextAggregator):
|
|||||||
if run_llm:
|
if run_llm:
|
||||||
await self._user_context_aggregator.push_context_frame()
|
await self._user_context_aggregator.push_context_frame()
|
||||||
|
|
||||||
|
# Emit the on_context_updated callback once the function call result is added to the context
|
||||||
|
if properties and properties.on_context_updated is not None:
|
||||||
|
await properties.on_context_updated()
|
||||||
|
|
||||||
frame = OpenAILLMContextFrame(self._context)
|
frame = OpenAILLMContextFrame(self._context)
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from pipecat.frames.frames import (
|
|||||||
Frame,
|
Frame,
|
||||||
FunctionCallInProgressFrame,
|
FunctionCallInProgressFrame,
|
||||||
FunctionCallResultFrame,
|
FunctionCallResultFrame,
|
||||||
|
FunctionCallResultProperties,
|
||||||
LLMFullResponseEndFrame,
|
LLMFullResponseEndFrame,
|
||||||
LLMFullResponseStartFrame,
|
LLMFullResponseStartFrame,
|
||||||
LLMMessagesFrame,
|
LLMMessagesFrame,
|
||||||
@@ -549,6 +550,7 @@ class OpenAIAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
return
|
return
|
||||||
|
|
||||||
run_llm = False
|
run_llm = False
|
||||||
|
properties: Optional[FunctionCallResultProperties] = None
|
||||||
|
|
||||||
aggregation = self._aggregation
|
aggregation = self._aggregation
|
||||||
self._reset()
|
self._reset()
|
||||||
@@ -556,6 +558,7 @@ class OpenAIAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
try:
|
try:
|
||||||
if self._function_call_result:
|
if self._function_call_result:
|
||||||
frame = self._function_call_result
|
frame = self._function_call_result
|
||||||
|
properties = frame.properties
|
||||||
self._function_call_result = None
|
self._function_call_result = None
|
||||||
if frame.result:
|
if frame.result:
|
||||||
self._context.add_message(
|
self._context.add_message(
|
||||||
@@ -580,13 +583,13 @@ class OpenAIAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
"tool_call_id": frame.tool_call_id,
|
"tool_call_id": frame.tool_call_id,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if properties and properties.run_llm is not None:
|
||||||
if frame.override_run_llm:
|
# If the tool call result has a run_llm property, use it
|
||||||
# Explicit override
|
run_llm = properties.run_llm
|
||||||
run_llm = frame.run_llm
|
|
||||||
else:
|
else:
|
||||||
# Default behavior
|
# Default behavior is to run the LLM if there are no function calls in progress
|
||||||
run_llm = not bool(self._function_calls_in_progress)
|
run_llm = not bool(self._function_calls_in_progress)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._context.add_message({"role": "assistant", "content": aggregation})
|
self._context.add_message({"role": "assistant", "content": aggregation})
|
||||||
|
|
||||||
@@ -604,6 +607,10 @@ class OpenAIAssistantContextAggregator(LLMAssistantContextAggregator):
|
|||||||
if run_llm:
|
if run_llm:
|
||||||
await self._user_context_aggregator.push_context_frame()
|
await self._user_context_aggregator.push_context_frame()
|
||||||
|
|
||||||
|
# Emit the on_context_updated callback once the function call result is added to the context
|
||||||
|
if properties and properties.on_context_updated is not None:
|
||||||
|
await properties.on_context_updated()
|
||||||
|
|
||||||
# Push context frame
|
# Push context frame
|
||||||
frame = OpenAILLMContextFrame(self._context)
|
frame = OpenAILLMContextFrame(self._context)
|
||||||
await self.push_frame(frame)
|
await self.push_frame(frame)
|
||||||
|
|||||||
Reference in New Issue
Block a user