dv-pipecat-ai 0.0.85.dev7__py3-none-any.whl → 0.0.85.dev698__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dv-pipecat-ai might be problematic. Click here for more details.
- {dv_pipecat_ai-0.0.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev698.dist-info}/METADATA +78 -117
- {dv_pipecat_ai-0.0.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev698.dist-info}/RECORD +156 -122
- pipecat/adapters/base_llm_adapter.py +38 -1
- pipecat/adapters/services/anthropic_adapter.py +9 -14
- pipecat/adapters/services/aws_nova_sonic_adapter.py +5 -0
- pipecat/adapters/services/bedrock_adapter.py +236 -13
- pipecat/adapters/services/gemini_adapter.py +12 -8
- pipecat/adapters/services/open_ai_adapter.py +19 -7
- pipecat/adapters/services/open_ai_realtime_adapter.py +5 -0
- pipecat/audio/filters/krisp_viva_filter.py +193 -0
- pipecat/audio/filters/noisereduce_filter.py +15 -0
- pipecat/audio/turn/base_turn_analyzer.py +9 -1
- pipecat/audio/turn/smart_turn/base_smart_turn.py +14 -8
- pipecat/audio/turn/smart_turn/data/__init__.py +0 -0
- pipecat/audio/turn/smart_turn/data/smart-turn-v3.0.onnx +0 -0
- pipecat/audio/turn/smart_turn/http_smart_turn.py +6 -2
- pipecat/audio/turn/smart_turn/local_smart_turn.py +1 -1
- pipecat/audio/turn/smart_turn/local_smart_turn_v2.py +1 -1
- pipecat/audio/turn/smart_turn/local_smart_turn_v3.py +124 -0
- pipecat/audio/vad/data/README.md +10 -0
- pipecat/audio/vad/vad_analyzer.py +13 -1
- pipecat/extensions/voicemail/voicemail_detector.py +5 -5
- pipecat/frames/frames.py +120 -87
- pipecat/observers/loggers/debug_log_observer.py +3 -3
- pipecat/observers/loggers/llm_log_observer.py +7 -3
- pipecat/observers/loggers/user_bot_latency_log_observer.py +22 -10
- pipecat/pipeline/runner.py +12 -4
- pipecat/pipeline/service_switcher.py +64 -36
- pipecat/pipeline/task.py +85 -24
- pipecat/processors/aggregators/dtmf_aggregator.py +28 -22
- pipecat/processors/aggregators/{gated_openai_llm_context.py → gated_llm_context.py} +9 -9
- pipecat/processors/aggregators/gated_open_ai_llm_context.py +12 -0
- pipecat/processors/aggregators/llm_response.py +6 -7
- pipecat/processors/aggregators/llm_response_universal.py +19 -15
- pipecat/processors/aggregators/user_response.py +6 -6
- pipecat/processors/aggregators/vision_image_frame.py +24 -2
- pipecat/processors/audio/audio_buffer_processor.py +43 -8
- pipecat/processors/filters/stt_mute_filter.py +2 -0
- pipecat/processors/frame_processor.py +103 -17
- pipecat/processors/frameworks/langchain.py +8 -2
- pipecat/processors/frameworks/rtvi.py +209 -68
- pipecat/processors/frameworks/strands_agents.py +170 -0
- pipecat/processors/logger.py +2 -2
- pipecat/processors/transcript_processor.py +4 -4
- pipecat/processors/user_idle_processor.py +3 -6
- pipecat/runner/run.py +270 -50
- pipecat/runner/types.py +2 -0
- pipecat/runner/utils.py +51 -10
- pipecat/serializers/exotel.py +5 -5
- pipecat/serializers/livekit.py +20 -0
- pipecat/serializers/plivo.py +6 -9
- pipecat/serializers/protobuf.py +6 -5
- pipecat/serializers/telnyx.py +2 -2
- pipecat/serializers/twilio.py +43 -23
- pipecat/services/ai_service.py +2 -6
- pipecat/services/anthropic/llm.py +2 -25
- pipecat/services/asyncai/tts.py +2 -3
- pipecat/services/aws/__init__.py +1 -0
- pipecat/services/aws/llm.py +122 -97
- pipecat/services/aws/nova_sonic/__init__.py +0 -0
- pipecat/services/aws/nova_sonic/context.py +367 -0
- pipecat/services/aws/nova_sonic/frames.py +25 -0
- pipecat/services/aws/nova_sonic/llm.py +1155 -0
- pipecat/services/aws/stt.py +1 -3
- pipecat/services/aws_nova_sonic/__init__.py +19 -1
- pipecat/services/aws_nova_sonic/aws.py +11 -1151
- pipecat/services/aws_nova_sonic/context.py +13 -355
- pipecat/services/aws_nova_sonic/frames.py +13 -17
- pipecat/services/azure/realtime/__init__.py +0 -0
- pipecat/services/azure/realtime/llm.py +65 -0
- pipecat/services/azure/stt.py +15 -0
- pipecat/services/cartesia/tts.py +2 -2
- pipecat/services/deepgram/__init__.py +1 -0
- pipecat/services/deepgram/flux/__init__.py +0 -0
- pipecat/services/deepgram/flux/stt.py +636 -0
- pipecat/services/elevenlabs/__init__.py +2 -1
- pipecat/services/elevenlabs/stt.py +254 -276
- pipecat/services/elevenlabs/tts.py +5 -5
- pipecat/services/fish/tts.py +2 -2
- pipecat/services/gemini_multimodal_live/events.py +38 -524
- pipecat/services/gemini_multimodal_live/file_api.py +23 -173
- pipecat/services/gemini_multimodal_live/gemini.py +41 -1403
- pipecat/services/gladia/stt.py +56 -72
- pipecat/services/google/__init__.py +1 -0
- pipecat/services/google/gemini_live/__init__.py +3 -0
- pipecat/services/google/gemini_live/file_api.py +189 -0
- pipecat/services/google/gemini_live/llm.py +1582 -0
- pipecat/services/google/gemini_live/llm_vertex.py +184 -0
- pipecat/services/google/llm.py +15 -11
- pipecat/services/google/llm_openai.py +3 -3
- pipecat/services/google/llm_vertex.py +86 -16
- pipecat/services/google/tts.py +7 -3
- pipecat/services/heygen/api.py +2 -0
- pipecat/services/heygen/client.py +8 -4
- pipecat/services/heygen/video.py +2 -0
- pipecat/services/hume/__init__.py +5 -0
- pipecat/services/hume/tts.py +220 -0
- pipecat/services/inworld/tts.py +6 -6
- pipecat/services/llm_service.py +15 -5
- pipecat/services/lmnt/tts.py +2 -2
- pipecat/services/mcp_service.py +4 -2
- pipecat/services/mem0/memory.py +6 -5
- pipecat/services/mistral/llm.py +29 -8
- pipecat/services/moondream/vision.py +42 -16
- pipecat/services/neuphonic/tts.py +2 -2
- pipecat/services/openai/__init__.py +1 -0
- pipecat/services/openai/base_llm.py +27 -20
- pipecat/services/openai/realtime/__init__.py +0 -0
- pipecat/services/openai/realtime/context.py +272 -0
- pipecat/services/openai/realtime/events.py +1106 -0
- pipecat/services/openai/realtime/frames.py +37 -0
- pipecat/services/openai/realtime/llm.py +829 -0
- pipecat/services/openai/tts.py +16 -8
- pipecat/services/openai_realtime/__init__.py +27 -0
- pipecat/services/openai_realtime/azure.py +21 -0
- pipecat/services/openai_realtime/context.py +21 -0
- pipecat/services/openai_realtime/events.py +21 -0
- pipecat/services/openai_realtime/frames.py +21 -0
- pipecat/services/openai_realtime_beta/azure.py +16 -0
- pipecat/services/openai_realtime_beta/openai.py +17 -5
- pipecat/services/playht/tts.py +31 -4
- pipecat/services/rime/tts.py +3 -4
- pipecat/services/sarvam/tts.py +2 -6
- pipecat/services/simli/video.py +2 -2
- pipecat/services/speechmatics/stt.py +1 -7
- pipecat/services/stt_service.py +34 -0
- pipecat/services/tavus/video.py +2 -2
- pipecat/services/tts_service.py +9 -9
- pipecat/services/vision_service.py +7 -6
- pipecat/tests/utils.py +4 -4
- pipecat/transcriptions/language.py +41 -1
- pipecat/transports/base_input.py +17 -42
- pipecat/transports/base_output.py +42 -26
- pipecat/transports/daily/transport.py +199 -26
- pipecat/transports/heygen/__init__.py +0 -0
- pipecat/transports/heygen/transport.py +381 -0
- pipecat/transports/livekit/transport.py +228 -63
- pipecat/transports/local/audio.py +6 -1
- pipecat/transports/local/tk.py +11 -2
- pipecat/transports/network/fastapi_websocket.py +1 -1
- pipecat/transports/smallwebrtc/connection.py +98 -19
- pipecat/transports/smallwebrtc/request_handler.py +204 -0
- pipecat/transports/smallwebrtc/transport.py +65 -23
- pipecat/transports/tavus/transport.py +23 -12
- pipecat/transports/websocket/client.py +41 -5
- pipecat/transports/websocket/fastapi.py +21 -11
- pipecat/transports/websocket/server.py +14 -7
- pipecat/transports/whatsapp/api.py +8 -0
- pipecat/transports/whatsapp/client.py +47 -0
- pipecat/utils/base_object.py +54 -22
- pipecat/utils/string.py +12 -1
- pipecat/utils/tracing/service_decorators.py +21 -21
- {dv_pipecat_ai-0.0.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev698.dist-info}/WHEEL +0 -0
- {dv_pipecat_ai-0.0.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev698.dist-info}/licenses/LICENSE +0 -0
- {dv_pipecat_ai-0.0.85.dev7.dist-info → dv_pipecat_ai-0.0.85.dev698.dist-info}/top_level.txt +0 -0
- /pipecat/services/{aws_nova_sonic → aws/nova_sonic}/ready.wav +0 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import copy
|
|
10
10
|
import json
|
|
11
11
|
from dataclasses import dataclass
|
|
12
|
-
from typing import Any, Dict, List,
|
|
12
|
+
from typing import Any, Dict, List, TypedDict
|
|
13
13
|
|
|
14
14
|
from anthropic import NOT_GIVEN, NotGiven
|
|
15
15
|
from anthropic.types.message_param import MessageParam
|
|
@@ -28,10 +28,7 @@ from pipecat.processors.aggregators.llm_context import (
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class AnthropicLLMInvocationParams(TypedDict):
|
|
31
|
-
"""Context-based parameters for invoking Anthropic's LLM API.
|
|
32
|
-
|
|
33
|
-
This is a placeholder until support for universal LLMContext machinery is added for Anthropic.
|
|
34
|
-
"""
|
|
31
|
+
"""Context-based parameters for invoking Anthropic's LLM API."""
|
|
35
32
|
|
|
36
33
|
system: str | NotGiven
|
|
37
34
|
messages: List[MessageParam]
|
|
@@ -45,13 +42,16 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|
|
45
42
|
to the specific format required by Anthropic's Claude models for function calling.
|
|
46
43
|
"""
|
|
47
44
|
|
|
45
|
+
@property
|
|
46
|
+
def id_for_llm_specific_messages(self) -> str:
|
|
47
|
+
"""Get the identifier used in LLMSpecificMessage instances for Anthropic."""
|
|
48
|
+
return "anthropic"
|
|
49
|
+
|
|
48
50
|
def get_llm_invocation_params(
|
|
49
51
|
self, context: LLMContext, enable_prompt_caching: bool
|
|
50
52
|
) -> AnthropicLLMInvocationParams:
|
|
51
53
|
"""Get Anthropic-specific LLM invocation parameters from a universal LLM context.
|
|
52
54
|
|
|
53
|
-
This is a placeholder until support for universal LLMContext machinery is added for Anthropic.
|
|
54
|
-
|
|
55
55
|
Args:
|
|
56
56
|
context: The LLM context containing messages, tools, etc.
|
|
57
57
|
enable_prompt_caching: Whether prompt caching should be enabled.
|
|
@@ -59,7 +59,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|
|
59
59
|
Returns:
|
|
60
60
|
Dictionary of parameters for invoking Anthropic's LLM API.
|
|
61
61
|
"""
|
|
62
|
-
messages = self._from_universal_context_messages(self.
|
|
62
|
+
messages = self._from_universal_context_messages(self.get_messages(context))
|
|
63
63
|
return {
|
|
64
64
|
"system": messages.system,
|
|
65
65
|
"messages": (
|
|
@@ -76,8 +76,6 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|
|
76
76
|
|
|
77
77
|
Removes or truncates sensitive data like image content for safe logging.
|
|
78
78
|
|
|
79
|
-
This is a placeholder until support for universal LLMContext machinery is added for Anthropic.
|
|
80
|
-
|
|
81
79
|
Args:
|
|
82
80
|
context: The LLM context containing messages.
|
|
83
81
|
|
|
@@ -85,7 +83,7 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|
|
85
83
|
List of messages in a format ready for logging about Anthropic.
|
|
86
84
|
"""
|
|
87
85
|
# Get messages in Anthropic's format
|
|
88
|
-
messages = self._from_universal_context_messages(self.
|
|
86
|
+
messages = self._from_universal_context_messages(self.get_messages(context)).messages
|
|
89
87
|
|
|
90
88
|
# Sanitize messages for logging
|
|
91
89
|
messages_for_logging = []
|
|
@@ -99,9 +97,6 @@ class AnthropicLLMAdapter(BaseLLMAdapter[AnthropicLLMInvocationParams]):
|
|
|
99
97
|
messages_for_logging.append(msg)
|
|
100
98
|
return messages_for_logging
|
|
101
99
|
|
|
102
|
-
def _get_messages(self, context: LLMContext) -> List[LLMContextMessage]:
|
|
103
|
-
return context.get_messages("anthropic")
|
|
104
|
-
|
|
105
100
|
@dataclass
|
|
106
101
|
class ConvertedMessages:
|
|
107
102
|
"""Container for Anthropic-formatted messages converted from universal context."""
|
|
@@ -31,6 +31,11 @@ class AWSNovaSonicLLMAdapter(BaseLLMAdapter[AWSNovaSonicLLMInvocationParams]):
|
|
|
31
31
|
specific function-calling format, enabling tool use with Nova Sonic models.
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
|
+
@property
|
|
35
|
+
def id_for_llm_specific_messages(self) -> str:
|
|
36
|
+
"""Get the identifier used in LLMSpecificMessage instances for AWS Nova Sonic."""
|
|
37
|
+
raise NotImplementedError("Universal LLMContext is not yet supported for AWS Nova Sonic.")
|
|
38
|
+
|
|
34
39
|
def get_llm_invocation_params(self, context: LLMContext) -> AWSNovaSonicLLMInvocationParams:
|
|
35
40
|
"""Get AWS Nova Sonic-specific LLM invocation parameters from a universal LLM context.
|
|
36
41
|
|
|
@@ -6,21 +6,33 @@
|
|
|
6
6
|
|
|
7
7
|
"""AWS Bedrock LLM adapter for Pipecat."""
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import base64
|
|
10
|
+
import copy
|
|
11
|
+
import json
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any, Dict, List, Literal, Optional, TypedDict
|
|
14
|
+
|
|
15
|
+
from loguru import logger
|
|
10
16
|
|
|
11
17
|
from pipecat.adapters.base_llm_adapter import BaseLLMAdapter
|
|
12
18
|
from pipecat.adapters.schemas.function_schema import FunctionSchema
|
|
13
19
|
from pipecat.adapters.schemas.tools_schema import ToolsSchema
|
|
14
|
-
from pipecat.processors.aggregators.llm_context import
|
|
20
|
+
from pipecat.processors.aggregators.llm_context import (
|
|
21
|
+
LLMContext,
|
|
22
|
+
LLMContextMessage,
|
|
23
|
+
LLMContextToolChoice,
|
|
24
|
+
LLMSpecificMessage,
|
|
25
|
+
LLMStandardMessage,
|
|
26
|
+
)
|
|
15
27
|
|
|
16
28
|
|
|
17
29
|
class AWSBedrockLLMInvocationParams(TypedDict):
|
|
18
|
-
"""Context-based parameters for invoking AWS Bedrock's LLM API.
|
|
19
|
-
|
|
20
|
-
This is a placeholder until support for universal LLMContext machinery is added for Bedrock.
|
|
21
|
-
"""
|
|
30
|
+
"""Context-based parameters for invoking AWS Bedrock's LLM API."""
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
system: Optional[List[dict[str, Any]]] # [{"text": "system message"}]
|
|
33
|
+
messages: List[dict[str, Any]]
|
|
34
|
+
tools: List[dict[str, Any]]
|
|
35
|
+
tool_choice: LLMContextToolChoice
|
|
24
36
|
|
|
25
37
|
|
|
26
38
|
class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
|
@@ -30,33 +42,244 @@ class AWSBedrockLLMAdapter(BaseLLMAdapter[AWSBedrockLLMInvocationParams]):
|
|
|
30
42
|
into AWS Bedrock's expected tool format for function calling capabilities.
|
|
31
43
|
"""
|
|
32
44
|
|
|
45
|
+
@property
|
|
46
|
+
def id_for_llm_specific_messages(self) -> str:
|
|
47
|
+
"""Get the identifier used in LLMSpecificMessage instances for AWS Bedrock."""
|
|
48
|
+
return "aws"
|
|
49
|
+
|
|
33
50
|
def get_llm_invocation_params(self, context: LLMContext) -> AWSBedrockLLMInvocationParams:
|
|
34
51
|
"""Get AWS Bedrock-specific LLM invocation parameters from a universal LLM context.
|
|
35
52
|
|
|
36
|
-
This is a placeholder until support for universal LLMContext machinery is added for Bedrock.
|
|
37
|
-
|
|
38
53
|
Args:
|
|
39
54
|
context: The LLM context containing messages, tools, etc.
|
|
40
55
|
|
|
41
56
|
Returns:
|
|
42
57
|
Dictionary of parameters for invoking AWS Bedrock's LLM API.
|
|
43
58
|
"""
|
|
44
|
-
|
|
59
|
+
messages = self._from_universal_context_messages(self.get_messages(context))
|
|
60
|
+
return {
|
|
61
|
+
"system": messages.system,
|
|
62
|
+
"messages": messages.messages,
|
|
63
|
+
# NOTE: LLMContext's tools are guaranteed to be a ToolsSchema (or NOT_GIVEN)
|
|
64
|
+
"tools": self.from_standard_tools(context.tools) or [],
|
|
65
|
+
# To avoid refactoring in AWSBedrockLLMService, we just pass through tool_choice.
|
|
66
|
+
# Eventually (when we don't have to maintain the non-LLMContext code path) we should do
|
|
67
|
+
# the conversion to Bedrock's expected format here rather than in AWSBedrockLLMService.
|
|
68
|
+
"tool_choice": context.tool_choice,
|
|
69
|
+
}
|
|
45
70
|
|
|
46
71
|
def get_messages_for_logging(self, context) -> List[Dict[str, Any]]:
|
|
47
72
|
"""Get messages from a universal LLM context in a format ready for logging about AWS Bedrock.
|
|
48
73
|
|
|
49
74
|
Removes or truncates sensitive data like image content for safe logging.
|
|
50
75
|
|
|
51
|
-
This is a placeholder until support for universal LLMContext machinery is added for Bedrock.
|
|
52
|
-
|
|
53
76
|
Args:
|
|
54
77
|
context: The LLM context containing messages.
|
|
55
78
|
|
|
56
79
|
Returns:
|
|
57
80
|
List of messages in a format ready for logging about AWS Bedrock.
|
|
58
81
|
"""
|
|
59
|
-
|
|
82
|
+
# Get messages in Anthropic's format
|
|
83
|
+
messages = self._from_universal_context_messages(self.get_messages(context)).messages
|
|
84
|
+
|
|
85
|
+
# Sanitize messages for logging
|
|
86
|
+
messages_for_logging = []
|
|
87
|
+
for message in messages:
|
|
88
|
+
msg = copy.deepcopy(message)
|
|
89
|
+
if "content" in msg:
|
|
90
|
+
if isinstance(msg["content"], list):
|
|
91
|
+
for item in msg["content"]:
|
|
92
|
+
if item.get("image"):
|
|
93
|
+
item["image"]["source"]["bytes"] = "..."
|
|
94
|
+
messages_for_logging.append(msg)
|
|
95
|
+
return messages_for_logging
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class ConvertedMessages:
|
|
99
|
+
"""Container for Anthropic-formatted messages converted from universal context."""
|
|
100
|
+
|
|
101
|
+
messages: List[dict[str, Any]]
|
|
102
|
+
system: Optional[str]
|
|
103
|
+
|
|
104
|
+
def _from_universal_context_messages(
|
|
105
|
+
self, universal_context_messages: List[LLMContextMessage]
|
|
106
|
+
) -> ConvertedMessages:
|
|
107
|
+
system = None
|
|
108
|
+
messages = []
|
|
109
|
+
|
|
110
|
+
# first, map messages using self._from_universal_context_message(m)
|
|
111
|
+
try:
|
|
112
|
+
messages = [self._from_universal_context_message(m) for m in universal_context_messages]
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(f"Error mapping messages: {e}")
|
|
115
|
+
|
|
116
|
+
# See if we should pull the system message out of our messages list
|
|
117
|
+
if messages and messages[0]["role"] == "system":
|
|
118
|
+
system = messages[0]["content"]
|
|
119
|
+
messages.pop(0)
|
|
120
|
+
|
|
121
|
+
# Convert any subsequent "system"-role messages to "user"-role
|
|
122
|
+
# messages, as AWS Bedrock doesn't support system input messages.
|
|
123
|
+
for message in messages:
|
|
124
|
+
if message["role"] == "system":
|
|
125
|
+
message["role"] = "user"
|
|
126
|
+
|
|
127
|
+
# Merge consecutive messages with the same role.
|
|
128
|
+
i = 0
|
|
129
|
+
while i < len(messages) - 1:
|
|
130
|
+
current_message = messages[i]
|
|
131
|
+
next_message = messages[i + 1]
|
|
132
|
+
if current_message["role"] == next_message["role"]:
|
|
133
|
+
# Convert content to list of dictionaries if it's a string
|
|
134
|
+
if isinstance(current_message["content"], str):
|
|
135
|
+
current_message["content"] = [
|
|
136
|
+
{"type": "text", "text": current_message["content"]}
|
|
137
|
+
]
|
|
138
|
+
if isinstance(next_message["content"], str):
|
|
139
|
+
next_message["content"] = [{"type": "text", "text": next_message["content"]}]
|
|
140
|
+
# Concatenate the content
|
|
141
|
+
current_message["content"].extend(next_message["content"])
|
|
142
|
+
# Remove the next message from the list
|
|
143
|
+
messages.pop(i + 1)
|
|
144
|
+
else:
|
|
145
|
+
i += 1
|
|
146
|
+
|
|
147
|
+
# Avoid empty content in messages
|
|
148
|
+
for message in messages:
|
|
149
|
+
if isinstance(message["content"], str) and message["content"] == "":
|
|
150
|
+
message["content"] = "(empty)"
|
|
151
|
+
elif isinstance(message["content"], list) and len(message["content"]) == 0:
|
|
152
|
+
message["content"] = [{"type": "text", "text": "(empty)"}]
|
|
153
|
+
|
|
154
|
+
return self.ConvertedMessages(messages=messages, system=system)
|
|
155
|
+
|
|
156
|
+
def _from_universal_context_message(self, message: LLMContextMessage) -> dict[str, Any]:
|
|
157
|
+
if isinstance(message, LLMSpecificMessage):
|
|
158
|
+
return copy.deepcopy(message.message)
|
|
159
|
+
return self._from_standard_message(message)
|
|
160
|
+
|
|
161
|
+
def _from_standard_message(self, message: LLMStandardMessage) -> dict[str, Any]:
|
|
162
|
+
"""Convert standard format message to AWS Bedrock format.
|
|
163
|
+
|
|
164
|
+
Handles conversion of text content, tool calls, and tool results.
|
|
165
|
+
Empty text content is converted to "(empty)".
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
message: Message in standard format.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Message in AWS Bedrock format.
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
Standard format input::
|
|
175
|
+
|
|
176
|
+
{
|
|
177
|
+
"role": "assistant",
|
|
178
|
+
"tool_calls": [
|
|
179
|
+
{
|
|
180
|
+
"id": "123",
|
|
181
|
+
"function": {"name": "search", "arguments": '{"q": "test"}'}
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
AWS Bedrock format output::
|
|
187
|
+
|
|
188
|
+
{
|
|
189
|
+
"role": "assistant",
|
|
190
|
+
"content": [
|
|
191
|
+
{
|
|
192
|
+
"toolUse": {
|
|
193
|
+
"toolUseId": "123",
|
|
194
|
+
"name": "search",
|
|
195
|
+
"input": {"q": "test"}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
"""
|
|
201
|
+
message = copy.deepcopy(message)
|
|
202
|
+
if message["role"] == "tool":
|
|
203
|
+
# Try to parse the content as JSON if it looks like JSON
|
|
204
|
+
try:
|
|
205
|
+
if message["content"].strip().startswith("{") and message[
|
|
206
|
+
"content"
|
|
207
|
+
].strip().endswith("}"):
|
|
208
|
+
content_json = json.loads(message["content"])
|
|
209
|
+
tool_result_content = [{"json": content_json}]
|
|
210
|
+
else:
|
|
211
|
+
tool_result_content = [{"text": message["content"]}]
|
|
212
|
+
except:
|
|
213
|
+
tool_result_content = [{"text": message["content"]}]
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"role": "user",
|
|
217
|
+
"content": [
|
|
218
|
+
{
|
|
219
|
+
"toolResult": {
|
|
220
|
+
"toolUseId": message["tool_call_id"],
|
|
221
|
+
"content": tool_result_content,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if message.get("tool_calls"):
|
|
228
|
+
tc = message["tool_calls"]
|
|
229
|
+
ret = {"role": "assistant", "content": []}
|
|
230
|
+
for tool_call in tc:
|
|
231
|
+
function = tool_call["function"]
|
|
232
|
+
arguments = json.loads(function["arguments"])
|
|
233
|
+
new_tool_use = {
|
|
234
|
+
"toolUse": {
|
|
235
|
+
"toolUseId": tool_call["id"],
|
|
236
|
+
"name": function["name"],
|
|
237
|
+
"input": arguments,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
ret["content"].append(new_tool_use)
|
|
241
|
+
return ret
|
|
242
|
+
|
|
243
|
+
# Handle text content
|
|
244
|
+
content = message.get("content")
|
|
245
|
+
if isinstance(content, str):
|
|
246
|
+
if content == "":
|
|
247
|
+
return {"role": message["role"], "content": [{"text": "(empty)"}]}
|
|
248
|
+
else:
|
|
249
|
+
return {"role": message["role"], "content": [{"text": content}]}
|
|
250
|
+
elif isinstance(content, list):
|
|
251
|
+
new_content = []
|
|
252
|
+
for item in content:
|
|
253
|
+
# fix empty text
|
|
254
|
+
if item.get("type", "") == "text":
|
|
255
|
+
text_content = item["text"] if item["text"] != "" else "(empty)"
|
|
256
|
+
new_content.append({"text": text_content})
|
|
257
|
+
# handle image_url -> image conversion
|
|
258
|
+
if item["type"] == "image_url":
|
|
259
|
+
new_item = {
|
|
260
|
+
"image": {
|
|
261
|
+
"format": "jpeg",
|
|
262
|
+
"source": {
|
|
263
|
+
"bytes": base64.b64decode(item["image_url"]["url"].split(",")[1])
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
new_content.append(new_item)
|
|
268
|
+
# In the case where there's a single image in the list (like what
|
|
269
|
+
# would result from a UserImageRawFrame), ensure that the image
|
|
270
|
+
# comes before text
|
|
271
|
+
image_indices = [i for i, item in enumerate(new_content) if "image" in item]
|
|
272
|
+
text_indices = [i for i, item in enumerate(new_content) if "text" in item]
|
|
273
|
+
if len(image_indices) == 1 and text_indices:
|
|
274
|
+
img_idx = image_indices[0]
|
|
275
|
+
first_txt_idx = text_indices[0]
|
|
276
|
+
if img_idx > first_txt_idx:
|
|
277
|
+
# Move image before the first text
|
|
278
|
+
image_item = new_content.pop(img_idx)
|
|
279
|
+
new_content.insert(first_txt_idx, image_item)
|
|
280
|
+
return {"role": message["role"], "content": new_content}
|
|
281
|
+
|
|
282
|
+
return message
|
|
60
283
|
|
|
61
284
|
@staticmethod
|
|
62
285
|
def _to_bedrock_function_format(function: FunctionSchema) -> Dict[str, Any]:
|
|
@@ -54,6 +54,11 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|
|
54
54
|
- Extracting and sanitizing messages from the LLM context for logging with Gemini.
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
|
+
@property
|
|
58
|
+
def id_for_llm_specific_messages(self) -> str:
|
|
59
|
+
"""Get the identifier used in LLMSpecificMessage instances for Google."""
|
|
60
|
+
return "google"
|
|
61
|
+
|
|
57
62
|
def get_llm_invocation_params(self, context: LLMContext) -> GeminiLLMInvocationParams:
|
|
58
63
|
"""Get Gemini-specific LLM invocation parameters from a universal LLM context.
|
|
59
64
|
|
|
@@ -63,7 +68,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|
|
63
68
|
Returns:
|
|
64
69
|
Dictionary of parameters for Gemini's API.
|
|
65
70
|
"""
|
|
66
|
-
messages = self._from_universal_context_messages(self.
|
|
71
|
+
messages = self._from_universal_context_messages(self.get_messages(context))
|
|
67
72
|
return {
|
|
68
73
|
"system_instruction": messages.system_instruction,
|
|
69
74
|
"messages": messages.messages,
|
|
@@ -82,9 +87,11 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|
|
82
87
|
Includes both converted standard tools and any custom Gemini-specific tools.
|
|
83
88
|
"""
|
|
84
89
|
functions_schema = tools_schema.standard_tools
|
|
85
|
-
formatted_standard_tools =
|
|
86
|
-
{"function_declarations": [func.to_default_dict() for func in functions_schema]}
|
|
87
|
-
|
|
90
|
+
formatted_standard_tools = (
|
|
91
|
+
[{"function_declarations": [func.to_default_dict() for func in functions_schema]}]
|
|
92
|
+
if functions_schema
|
|
93
|
+
else []
|
|
94
|
+
)
|
|
88
95
|
custom_gemini_tools = []
|
|
89
96
|
if tools_schema.custom_tools:
|
|
90
97
|
custom_gemini_tools = tools_schema.custom_tools.get(AdapterType.GEMINI, [])
|
|
@@ -103,7 +110,7 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|
|
103
110
|
List of messages in a format ready for logging about Gemini.
|
|
104
111
|
"""
|
|
105
112
|
# Get messages in Gemini's format
|
|
106
|
-
messages = self._from_universal_context_messages(self.
|
|
113
|
+
messages = self._from_universal_context_messages(self.get_messages(context)).messages
|
|
107
114
|
|
|
108
115
|
# Sanitize messages for logging
|
|
109
116
|
messages_for_logging = []
|
|
@@ -119,9 +126,6 @@ class GeminiLLMAdapter(BaseLLMAdapter[GeminiLLMInvocationParams]):
|
|
|
119
126
|
messages_for_logging.append(obj)
|
|
120
127
|
return messages_for_logging
|
|
121
128
|
|
|
122
|
-
def _get_messages(self, context: LLMContext) -> List[LLMContextMessage]:
|
|
123
|
-
return context.get_messages("google")
|
|
124
|
-
|
|
125
129
|
@dataclass
|
|
126
130
|
class ConvertedMessages:
|
|
127
131
|
"""Container for Google-formatted messages converted from universal context."""
|
|
@@ -24,6 +24,7 @@ from pipecat.processors.aggregators.llm_context import (
|
|
|
24
24
|
LLMContext,
|
|
25
25
|
LLMContextMessage,
|
|
26
26
|
LLMContextToolChoice,
|
|
27
|
+
LLMSpecificMessage,
|
|
27
28
|
NotGiven,
|
|
28
29
|
)
|
|
29
30
|
|
|
@@ -47,6 +48,11 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
|
|
47
48
|
- Extracting and sanitizing messages from the LLM context for logging about OpenAI.
|
|
48
49
|
"""
|
|
49
50
|
|
|
51
|
+
@property
|
|
52
|
+
def id_for_llm_specific_messages(self) -> str:
|
|
53
|
+
"""Get the identifier used in LLMSpecificMessage instances for OpenAI."""
|
|
54
|
+
return "openai"
|
|
55
|
+
|
|
50
56
|
def get_llm_invocation_params(self, context: LLMContext) -> OpenAILLMInvocationParams:
|
|
51
57
|
"""Get OpenAI-specific LLM invocation parameters from a universal LLM context.
|
|
52
58
|
|
|
@@ -57,7 +63,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
|
|
57
63
|
Dictionary of parameters for OpenAI's ChatCompletion API.
|
|
58
64
|
"""
|
|
59
65
|
return {
|
|
60
|
-
"messages": self._from_universal_context_messages(self.
|
|
66
|
+
"messages": self._from_universal_context_messages(self.get_messages(context)),
|
|
61
67
|
# NOTE; LLMContext's tools are guaranteed to be a ToolsSchema (or NOT_GIVEN)
|
|
62
68
|
"tools": self.from_standard_tools(context.tools),
|
|
63
69
|
"tool_choice": context.tool_choice,
|
|
@@ -91,7 +97,7 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
|
|
91
97
|
List of messages in a format ready for logging about OpenAI.
|
|
92
98
|
"""
|
|
93
99
|
msgs = []
|
|
94
|
-
for message in self.
|
|
100
|
+
for message in self.get_messages(context):
|
|
95
101
|
msg = copy.deepcopy(message)
|
|
96
102
|
if "content" in msg:
|
|
97
103
|
if isinstance(msg["content"], list):
|
|
@@ -99,19 +105,25 @@ class OpenAILLMAdapter(BaseLLMAdapter[OpenAILLMInvocationParams]):
|
|
|
99
105
|
if item["type"] == "image_url":
|
|
100
106
|
if item["image_url"]["url"].startswith("data:image/"):
|
|
101
107
|
item["image_url"]["url"] = "data:image/..."
|
|
108
|
+
if item["type"] == "input_audio":
|
|
109
|
+
item["input_audio"]["data"] = "..."
|
|
102
110
|
if "mime_type" in msg and msg["mime_type"].startswith("image/"):
|
|
103
111
|
msg["data"] = "..."
|
|
104
112
|
msgs.append(msg)
|
|
105
113
|
return msgs
|
|
106
114
|
|
|
107
|
-
def _get_messages(self, context: LLMContext) -> List[LLMContextMessage]:
|
|
108
|
-
return context.get_messages("openai")
|
|
109
|
-
|
|
110
115
|
def _from_universal_context_messages(
|
|
111
116
|
self, messages: List[LLMContextMessage]
|
|
112
117
|
) -> List[ChatCompletionMessageParam]:
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
result = []
|
|
119
|
+
for message in messages:
|
|
120
|
+
if isinstance(message, LLMSpecificMessage):
|
|
121
|
+
# Extract the actual message content from LLMSpecificMessage
|
|
122
|
+
result.append(message.message)
|
|
123
|
+
else:
|
|
124
|
+
# Standard message, pass through unchanged
|
|
125
|
+
result.append(message)
|
|
126
|
+
return result
|
|
115
127
|
|
|
116
128
|
def _from_standard_tool_choice(
|
|
117
129
|
self, tool_choice: LLMContextToolChoice | NotGiven
|
|
@@ -30,6 +30,11 @@ class OpenAIRealtimeLLMAdapter(BaseLLMAdapter):
|
|
|
30
30
|
OpenAI's Realtime API for function calling capabilities.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
+
@property
|
|
34
|
+
def id_for_llm_specific_messages(self) -> str:
|
|
35
|
+
"""Get the identifier used in LLMSpecificMessage instances for OpenAI Realtime."""
|
|
36
|
+
raise NotImplementedError("Universal LLMContext is not yet supported for OpenAI Realtime.")
|
|
37
|
+
|
|
33
38
|
def get_llm_invocation_params(self, context: LLMContext) -> OpenAIRealtimeLLMInvocationParams:
|
|
34
39
|
"""Get OpenAI Realtime-specific LLM invocation parameters from a universal LLM context.
|
|
35
40
|
|