livekit-plugins-aws 1.1.4__py3-none-any.whl → 1.1.5__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 livekit-plugins-aws might be problematic. Click here for more details.
- livekit/plugins/aws/__init__.py +32 -8
- livekit/plugins/aws/experimental/realtime/__init__.py +15 -0
- livekit/plugins/aws/experimental/realtime/events.py +521 -0
- livekit/plugins/aws/experimental/realtime/pretty_printer.py +49 -0
- livekit/plugins/aws/experimental/realtime/realtime_model.py +1208 -0
- livekit/plugins/aws/experimental/realtime/turn_tracker.py +172 -0
- livekit/plugins/aws/log.py +4 -0
- livekit/plugins/aws/version.py +1 -1
- {livekit_plugins_aws-1.1.4.dist-info → livekit_plugins_aws-1.1.5.dist-info}/METADATA +11 -5
- livekit_plugins_aws-1.1.5.dist-info/RECORD +17 -0
- livekit_plugins_aws-1.1.4.dist-info/RECORD +0 -12
- {livekit_plugins_aws-1.1.4.dist-info → livekit_plugins_aws-1.1.5.dist-info}/WHEEL +0 -0
livekit/plugins/aws/__init__.py
CHANGED
|
@@ -14,24 +14,48 @@
|
|
|
14
14
|
|
|
15
15
|
"""AWS plugin for LiveKit Agents
|
|
16
16
|
|
|
17
|
-
Support for AWS AI including Bedrock, Polly, and
|
|
17
|
+
Support for AWS AI including Bedrock, Polly, Transcribe and optionally Nova Sonic.
|
|
18
18
|
|
|
19
19
|
See https://docs.livekit.io/agents/integrations/aws/ for more information.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
from .stt import STT, SpeechStream
|
|
24
|
-
from .tts import TTS, ChunkedStream
|
|
25
|
-
from .version import __version__
|
|
22
|
+
import typing # noqa: I001
|
|
26
23
|
|
|
27
|
-
__all__ = ["STT", "SpeechStream", "TTS", "ChunkedStream", "LLM", "__version__"]
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
if typing.TYPE_CHECKING:
|
|
26
|
+
from .experimental import realtime
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def __getattr__(name: str) -> typing.Any:
|
|
30
|
+
if name == "realtime":
|
|
31
|
+
try:
|
|
32
|
+
from .experimental import realtime
|
|
33
|
+
except ImportError as e:
|
|
34
|
+
raise ImportError(
|
|
35
|
+
"The 'realtime' module requires optional dependencies. "
|
|
36
|
+
"Please install them with: pip install 'livekit-plugins-aws[realtime]'"
|
|
37
|
+
) from e
|
|
38
|
+
|
|
39
|
+
return realtime
|
|
40
|
+
|
|
41
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
from .llm import LLM # noqa: E402
|
|
45
|
+
from .stt import STT, SpeechStream # noqa: E402
|
|
46
|
+
from .tts import TTS, ChunkedStream # noqa: E402
|
|
47
|
+
from .version import __version__ # noqa: E402
|
|
48
|
+
|
|
49
|
+
__all__ = ["STT", "SpeechStream", "TTS", "ChunkedStream", "LLM", "realtime", "__version__"]
|
|
50
|
+
|
|
51
|
+
from livekit.agents import Plugin # noqa: E402
|
|
52
|
+
|
|
53
|
+
from .log import logger # noqa: E402
|
|
30
54
|
|
|
31
55
|
|
|
32
56
|
class AWSPlugin(Plugin):
|
|
33
57
|
def __init__(self) -> None:
|
|
34
|
-
super().__init__(__name__, __version__, __package__)
|
|
58
|
+
super().__init__(__name__, __version__, __package__, logger)
|
|
35
59
|
|
|
36
60
|
|
|
37
61
|
Plugin.register_plugin(AWSPlugin())
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .realtime_model import RealtimeModel, RealtimeSession
|
|
2
|
+
|
|
3
|
+
__all__ = [
|
|
4
|
+
"RealtimeSession",
|
|
5
|
+
"RealtimeModel",
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
# Cleanup docs of unexported modules
|
|
9
|
+
_module = dir()
|
|
10
|
+
NOT_IN_ALL = [m for m in _module if m not in __all__]
|
|
11
|
+
|
|
12
|
+
__pdoc__ = {}
|
|
13
|
+
|
|
14
|
+
for n in NOT_IN_ALL:
|
|
15
|
+
__pdoc__[n] = False
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
3
|
+
from typing import Any, Literal, Optional, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
|
|
7
|
+
from livekit.agents import llm
|
|
8
|
+
|
|
9
|
+
from ...log import logger
|
|
10
|
+
|
|
11
|
+
MEDIA_TYPE = Literal["text/plain", "audio/lpcm", "application/json"]
|
|
12
|
+
TYPE = Literal["TEXT", "AUDIO", "TOOL"]
|
|
13
|
+
VOICE_ID = Literal["matthew", "tiffany", "amy"]
|
|
14
|
+
ROLE = Literal["USER", "ASSISTANT", "TOOL", "SYSTEM"]
|
|
15
|
+
GENERATION_STAGE = Literal["SPECULATIVE", "FINAL"]
|
|
16
|
+
STOP_REASON = Literal["PARTIAL_TURN", "END_TURN", "INTERRUPTED"]
|
|
17
|
+
SAMPLE_RATE_HERTZ = Literal[8_000, 16_000, 24_000]
|
|
18
|
+
AUDIO_ENCODING = Literal["base64"] # all audio data must be base64 encoded
|
|
19
|
+
SAMPLE_SIZE_BITS = Literal[16] # only supports 16-bit audio
|
|
20
|
+
CHANNEL_COUNT = Literal[1] # only supports monochannel audio
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseModel(BaseModel):
|
|
24
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InferenceConfiguration(BaseModel):
|
|
28
|
+
maxTokens: int = Field(default=1024, ge=1, le=10_000, frozen=True)
|
|
29
|
+
topP: float = Field(default=0.9, ge=0.0, le=1.0, frozen=True)
|
|
30
|
+
temperature: float = Field(default=0.7, ge=0.0, le=1.0, frozen=True)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AudioInputConfiguration(BaseModel):
|
|
34
|
+
mediaType: MEDIA_TYPE = "audio/lpcm"
|
|
35
|
+
sampleRateHertz: SAMPLE_RATE_HERTZ = Field(default=16000)
|
|
36
|
+
sampleSizeBits: SAMPLE_SIZE_BITS = 16
|
|
37
|
+
channelCount: CHANNEL_COUNT = 1
|
|
38
|
+
audioType: str = "SPEECH"
|
|
39
|
+
encoding: AUDIO_ENCODING = "base64"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AudioOutputConfiguration(BaseModel):
|
|
43
|
+
mediaType: MEDIA_TYPE = "audio/lpcm"
|
|
44
|
+
sampleRateHertz: SAMPLE_RATE_HERTZ = Field(default=24_000)
|
|
45
|
+
sampleSizeBits: SAMPLE_SIZE_BITS = 16
|
|
46
|
+
channelCount: CHANNEL_COUNT = 1
|
|
47
|
+
voiceId: VOICE_ID = Field(...)
|
|
48
|
+
encoding: AUDIO_ENCODING = "base64"
|
|
49
|
+
audioType: str = "SPEECH"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TextInputConfiguration(BaseModel):
|
|
53
|
+
mediaType: MEDIA_TYPE = "text/plain"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TextOutputConfiguration(BaseModel):
|
|
57
|
+
mediaType: MEDIA_TYPE = "text/plain"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ToolUseOutputConfiguration(BaseModel):
|
|
61
|
+
mediaType: MEDIA_TYPE = "application/json"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ToolResultInputConfiguration(BaseModel):
|
|
65
|
+
toolUseId: str
|
|
66
|
+
type: TYPE = "TEXT"
|
|
67
|
+
textInputConfiguration: TextInputConfiguration = TextInputConfiguration()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ToolInputSchema(BaseModel):
|
|
71
|
+
json_: str = Field(
|
|
72
|
+
default_factory=lambda: json.dumps(
|
|
73
|
+
{
|
|
74
|
+
"type": "object",
|
|
75
|
+
"properties": {},
|
|
76
|
+
"required": [],
|
|
77
|
+
}
|
|
78
|
+
),
|
|
79
|
+
alias="json",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ToolSpec(BaseModel):
|
|
84
|
+
name: str
|
|
85
|
+
description: str
|
|
86
|
+
inputSchema: ToolInputSchema
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Tool(BaseModel):
|
|
90
|
+
toolSpec: ToolSpec
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ToolConfiguration(BaseModel):
|
|
94
|
+
toolChoice: dict[str, dict[str, str]] | None = None
|
|
95
|
+
tools: list[Tool]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class SessionStart(BaseModel):
|
|
99
|
+
inferenceConfiguration: InferenceConfiguration
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class InputTextContentStart(BaseModel):
|
|
103
|
+
promptName: str
|
|
104
|
+
contentName: str
|
|
105
|
+
type: TYPE = "TEXT"
|
|
106
|
+
interactive: bool = False
|
|
107
|
+
role: ROLE
|
|
108
|
+
textInputConfiguration: TextInputConfiguration
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class InputAudioContentStart(BaseModel):
|
|
112
|
+
promptName: str
|
|
113
|
+
contentName: str
|
|
114
|
+
type: TYPE = "AUDIO"
|
|
115
|
+
interactive: bool = True
|
|
116
|
+
role: ROLE = "USER"
|
|
117
|
+
audioInputConfiguration: AudioInputConfiguration
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class InputToolContentStart(BaseModel):
|
|
121
|
+
promptName: str
|
|
122
|
+
contentName: str
|
|
123
|
+
type: TYPE = "TOOL"
|
|
124
|
+
interactive: bool = False
|
|
125
|
+
role: ROLE = "TOOL"
|
|
126
|
+
toolResultInputConfiguration: ToolResultInputConfiguration
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class PromptStart(BaseModel):
|
|
130
|
+
promptName: str
|
|
131
|
+
textOutputConfiguration: TextOutputConfiguration
|
|
132
|
+
audioOutputConfiguration: AudioOutputConfiguration
|
|
133
|
+
toolUseOutputConfiguration: ToolUseOutputConfiguration
|
|
134
|
+
toolConfiguration: ToolConfiguration
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class TextInput(BaseModel):
|
|
138
|
+
promptName: str
|
|
139
|
+
contentName: str
|
|
140
|
+
content: str
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class AudioInput(BaseModel):
|
|
144
|
+
promptName: str
|
|
145
|
+
contentName: str
|
|
146
|
+
content: str
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ToolResult(BaseModel):
|
|
150
|
+
promptName: str
|
|
151
|
+
contentName: str
|
|
152
|
+
content: str
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ContentEndEvent(BaseModel):
|
|
156
|
+
promptName: str
|
|
157
|
+
contentName: str
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class PromptEnd(BaseModel):
|
|
161
|
+
promptName: str
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class SessionEnd(BaseModel):
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class SessionStartEvent(BaseModel):
|
|
169
|
+
sessionStart: SessionStart
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class InputTextContentStartEvent(BaseModel):
|
|
173
|
+
contentStart: InputTextContentStart
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class InputAudioContentStartEvent(BaseModel):
|
|
177
|
+
contentStart: InputAudioContentStart
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class InputToolContentStartEvent(BaseModel):
|
|
181
|
+
contentStart: InputToolContentStart
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class PromptStartEvent(BaseModel):
|
|
185
|
+
promptStart: PromptStart
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class TextInputContentEvent(BaseModel):
|
|
189
|
+
textInput: TextInput
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class AudioInputContentEvent(BaseModel):
|
|
193
|
+
audioInput: AudioInput
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class ToolResultContentEvent(BaseModel):
|
|
197
|
+
toolResult: ToolResult
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class InputContentEndEvent(BaseModel):
|
|
201
|
+
contentEnd: ContentEndEvent
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class PromptEndEvent(BaseModel):
|
|
205
|
+
promptEnd: PromptEnd
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class SessionEndEvent(BaseModel):
|
|
209
|
+
sessionEnd: SessionEnd
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class Event(BaseModel):
|
|
213
|
+
event: Union[
|
|
214
|
+
SessionStartEvent,
|
|
215
|
+
InputTextContentStartEvent,
|
|
216
|
+
InputAudioContentStartEvent,
|
|
217
|
+
InputToolContentStartEvent,
|
|
218
|
+
PromptStartEvent,
|
|
219
|
+
TextInputContentEvent,
|
|
220
|
+
AudioInputContentEvent,
|
|
221
|
+
ToolResultContentEvent,
|
|
222
|
+
InputContentEndEvent,
|
|
223
|
+
PromptEndEvent,
|
|
224
|
+
SessionEndEvent,
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class SonicEventBuilder:
|
|
229
|
+
def __init__(self, prompt_name: str, audio_content_name: str):
|
|
230
|
+
self.prompt_name = prompt_name
|
|
231
|
+
self.audio_content_name = audio_content_name
|
|
232
|
+
|
|
233
|
+
@classmethod
|
|
234
|
+
def get_event_type(cls, json_data: dict) -> str:
|
|
235
|
+
if event := json_data.get("event"):
|
|
236
|
+
if event.get("contentStart", {}).get("type") == "AUDIO":
|
|
237
|
+
return "audio_output_content_start"
|
|
238
|
+
elif event.get("contentEnd", {}).get("type") == "AUDIO":
|
|
239
|
+
return "audio_output_content_end"
|
|
240
|
+
elif event.get("contentStart", {}).get("type") == "TEXT":
|
|
241
|
+
return "text_output_content_start"
|
|
242
|
+
elif event.get("contentEnd", {}).get("type") == "TEXT":
|
|
243
|
+
return "text_output_content_end"
|
|
244
|
+
elif event.get("contentStart", {}).get("type") == "TOOL":
|
|
245
|
+
return "tool_output_content_start"
|
|
246
|
+
elif event.get("contentEnd", {}).get("type") == "TOOL":
|
|
247
|
+
return "tool_output_content_end"
|
|
248
|
+
elif event.get("textOutput"):
|
|
249
|
+
return "text_output_content"
|
|
250
|
+
elif event.get("audioOutput"):
|
|
251
|
+
return "audio_output_content"
|
|
252
|
+
elif event.get("toolUse"):
|
|
253
|
+
return "tool_output_content"
|
|
254
|
+
elif "completionStart" in event:
|
|
255
|
+
return "completion_start"
|
|
256
|
+
elif "completionEnd" in event:
|
|
257
|
+
return "completion_end"
|
|
258
|
+
elif "usageEvent" in event:
|
|
259
|
+
return "usage"
|
|
260
|
+
else:
|
|
261
|
+
return "other_event"
|
|
262
|
+
|
|
263
|
+
def create_text_content_block(
|
|
264
|
+
self,
|
|
265
|
+
content_name: str,
|
|
266
|
+
role: ROLE,
|
|
267
|
+
content: str,
|
|
268
|
+
) -> list[str]:
|
|
269
|
+
return [
|
|
270
|
+
self.create_text_content_start_event(content_name, role),
|
|
271
|
+
self.create_text_content_event(content_name, content),
|
|
272
|
+
self.create_content_end_event(content_name),
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
def create_tool_content_block(
|
|
276
|
+
self,
|
|
277
|
+
content_name: str,
|
|
278
|
+
tool_use_id: str,
|
|
279
|
+
content: str,
|
|
280
|
+
) -> list[str]:
|
|
281
|
+
return [
|
|
282
|
+
self.create_tool_content_start_event(content_name, tool_use_id),
|
|
283
|
+
self.create_tool_result_event(content_name, content),
|
|
284
|
+
self.create_content_end_event(content_name),
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
def create_prompt_end_block(self) -> list[str]:
|
|
288
|
+
return [
|
|
289
|
+
self.create_content_end_event(self.audio_content_name, is_audio=True),
|
|
290
|
+
self.create_prompt_end_event(),
|
|
291
|
+
self.create_session_end_event(),
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
def create_prompt_start_block(
|
|
295
|
+
self,
|
|
296
|
+
voice_id: VOICE_ID,
|
|
297
|
+
sample_rate: SAMPLE_RATE_HERTZ,
|
|
298
|
+
system_content: str,
|
|
299
|
+
chat_ctx: llm.ChatContext,
|
|
300
|
+
tool_configuration: Optional[Union[ToolConfiguration, dict[str, Any], str]] = None,
|
|
301
|
+
max_tokens: int = 1024,
|
|
302
|
+
top_p: float = 0.9,
|
|
303
|
+
temperature: float = 0.7,
|
|
304
|
+
) -> list[str]:
|
|
305
|
+
system_content_name = str(uuid.uuid4())
|
|
306
|
+
init_events = [
|
|
307
|
+
self.create_session_start_event(max_tokens, top_p, temperature),
|
|
308
|
+
self.create_prompt_start_event(voice_id, sample_rate, tool_configuration),
|
|
309
|
+
*self.create_text_content_block(system_content_name, "SYSTEM", system_content),
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
# note: tool call events are not supported yet
|
|
313
|
+
if chat_ctx.items:
|
|
314
|
+
logger.debug("initiating session with chat context")
|
|
315
|
+
for item in chat_ctx.items:
|
|
316
|
+
ctx_content_name = str(uuid.uuid4())
|
|
317
|
+
init_events.extend(
|
|
318
|
+
self.create_text_content_block(
|
|
319
|
+
ctx_content_name, item.role.upper(), "".join(item.content)
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return init_events
|
|
324
|
+
|
|
325
|
+
def create_session_start_event(
|
|
326
|
+
self,
|
|
327
|
+
max_tokens: int = 1024,
|
|
328
|
+
top_p: float = 0.9,
|
|
329
|
+
temperature: float = 0.7,
|
|
330
|
+
) -> str:
|
|
331
|
+
event = Event(
|
|
332
|
+
event=SessionStartEvent(
|
|
333
|
+
sessionStart=SessionStart(
|
|
334
|
+
inferenceConfiguration=InferenceConfiguration(
|
|
335
|
+
maxTokens=max_tokens,
|
|
336
|
+
topP=top_p,
|
|
337
|
+
temperature=temperature,
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
)
|
|
342
|
+
return event.model_dump_json(exclude_none=False)
|
|
343
|
+
|
|
344
|
+
def create_audio_content_start_event(
|
|
345
|
+
self,
|
|
346
|
+
sample_rate: SAMPLE_RATE_HERTZ = 16_000,
|
|
347
|
+
) -> str:
|
|
348
|
+
event = Event(
|
|
349
|
+
event=InputAudioContentStartEvent(
|
|
350
|
+
contentStart=InputAudioContentStart(
|
|
351
|
+
promptName=self.prompt_name,
|
|
352
|
+
contentName=self.audio_content_name,
|
|
353
|
+
audioInputConfiguration=AudioInputConfiguration(
|
|
354
|
+
sampleRateHertz=sample_rate,
|
|
355
|
+
),
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
360
|
+
|
|
361
|
+
def create_text_content_start_event(
|
|
362
|
+
self,
|
|
363
|
+
content_name: str,
|
|
364
|
+
role: ROLE,
|
|
365
|
+
) -> str:
|
|
366
|
+
event = Event(
|
|
367
|
+
event=InputTextContentStartEvent(
|
|
368
|
+
contentStart=InputTextContentStart(
|
|
369
|
+
promptName=self.prompt_name,
|
|
370
|
+
contentName=content_name,
|
|
371
|
+
role=role,
|
|
372
|
+
textInputConfiguration=TextInputConfiguration(),
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
377
|
+
|
|
378
|
+
def create_tool_content_start_event(
|
|
379
|
+
self,
|
|
380
|
+
content_name: str,
|
|
381
|
+
tool_use_id: str,
|
|
382
|
+
) -> str:
|
|
383
|
+
event = Event(
|
|
384
|
+
event=InputToolContentStartEvent(
|
|
385
|
+
contentStart=InputToolContentStart(
|
|
386
|
+
promptName=self.prompt_name,
|
|
387
|
+
contentName=content_name,
|
|
388
|
+
toolResultInputConfiguration=ToolResultInputConfiguration(
|
|
389
|
+
toolUseId=tool_use_id,
|
|
390
|
+
textInputConfiguration=TextInputConfiguration(),
|
|
391
|
+
),
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
396
|
+
|
|
397
|
+
def create_audio_input_event(
|
|
398
|
+
self,
|
|
399
|
+
audio_content: str,
|
|
400
|
+
) -> str:
|
|
401
|
+
event = Event(
|
|
402
|
+
event=AudioInputContentEvent(
|
|
403
|
+
audioInput=AudioInput(
|
|
404
|
+
promptName=self.prompt_name,
|
|
405
|
+
contentName=self.audio_content_name,
|
|
406
|
+
content=audio_content,
|
|
407
|
+
)
|
|
408
|
+
)
|
|
409
|
+
)
|
|
410
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
411
|
+
|
|
412
|
+
def create_text_content_event(
|
|
413
|
+
self,
|
|
414
|
+
content_name: str,
|
|
415
|
+
content: str,
|
|
416
|
+
) -> str:
|
|
417
|
+
event = Event(
|
|
418
|
+
event=TextInputContentEvent(
|
|
419
|
+
textInput=TextInput(
|
|
420
|
+
promptName=self.prompt_name,
|
|
421
|
+
contentName=content_name,
|
|
422
|
+
content=content,
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
)
|
|
426
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
427
|
+
|
|
428
|
+
def create_tool_result_event(
|
|
429
|
+
self,
|
|
430
|
+
content_name: str,
|
|
431
|
+
content: Union[str, dict[str, Any]],
|
|
432
|
+
) -> str:
|
|
433
|
+
if isinstance(content, dict):
|
|
434
|
+
content_str = json.dumps(content)
|
|
435
|
+
else:
|
|
436
|
+
content_str = content
|
|
437
|
+
|
|
438
|
+
event = Event(
|
|
439
|
+
event=ToolResultContentEvent(
|
|
440
|
+
toolResult=ToolResult(
|
|
441
|
+
promptName=self.prompt_name,
|
|
442
|
+
contentName=content_name,
|
|
443
|
+
content=content_str,
|
|
444
|
+
)
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
448
|
+
|
|
449
|
+
def create_content_end_event(
|
|
450
|
+
self,
|
|
451
|
+
content_name: str,
|
|
452
|
+
is_audio: bool = False,
|
|
453
|
+
) -> str:
|
|
454
|
+
event = Event(
|
|
455
|
+
event=InputContentEndEvent(
|
|
456
|
+
contentEnd=ContentEndEvent(
|
|
457
|
+
promptName=self.prompt_name,
|
|
458
|
+
contentName=content_name if not is_audio else self.audio_content_name,
|
|
459
|
+
)
|
|
460
|
+
)
|
|
461
|
+
)
|
|
462
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
463
|
+
|
|
464
|
+
def create_prompt_end_event(self) -> str:
|
|
465
|
+
event = Event(
|
|
466
|
+
event=PromptEndEvent(
|
|
467
|
+
promptEnd=PromptEnd(promptName=self.prompt_name),
|
|
468
|
+
)
|
|
469
|
+
)
|
|
470
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
471
|
+
|
|
472
|
+
def create_session_end_event(self) -> str:
|
|
473
|
+
event = Event(
|
|
474
|
+
event=SessionEndEvent(sessionEnd=SessionEnd()),
|
|
475
|
+
)
|
|
476
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
477
|
+
|
|
478
|
+
def create_prompt_start_event(
|
|
479
|
+
self,
|
|
480
|
+
voice_id: VOICE_ID,
|
|
481
|
+
sample_rate: SAMPLE_RATE_HERTZ,
|
|
482
|
+
tool_configuration: Optional[Union[ToolConfiguration, dict[str, Any], str]] = None,
|
|
483
|
+
) -> str:
|
|
484
|
+
tool_configuration = tool_configuration or ToolConfiguration(tools=[])
|
|
485
|
+
for tool in tool_configuration.tools:
|
|
486
|
+
logger.debug(f"TOOL JSON SCHEMA: {tool.toolSpec.inputSchema}")
|
|
487
|
+
tool_objects = [
|
|
488
|
+
Tool(
|
|
489
|
+
toolSpec=ToolSpec(
|
|
490
|
+
name=tool.toolSpec.name,
|
|
491
|
+
description=tool.toolSpec.description,
|
|
492
|
+
inputSchema=ToolInputSchema(json_=tool.toolSpec.inputSchema.json_),
|
|
493
|
+
)
|
|
494
|
+
)
|
|
495
|
+
for tool in tool_configuration.tools
|
|
496
|
+
]
|
|
497
|
+
|
|
498
|
+
if tool_configuration is None:
|
|
499
|
+
tool_configuration = ToolConfiguration(tools=[])
|
|
500
|
+
elif isinstance(tool_configuration, str):
|
|
501
|
+
tool_configuration = ToolConfiguration(**json.loads(tool_configuration))
|
|
502
|
+
elif isinstance(tool_configuration, dict):
|
|
503
|
+
tool_configuration = ToolConfiguration(**tool_configuration)
|
|
504
|
+
|
|
505
|
+
tool_objects = list(tool_configuration.tools)
|
|
506
|
+
event = Event(
|
|
507
|
+
event=PromptStartEvent(
|
|
508
|
+
promptStart=PromptStart(
|
|
509
|
+
promptName=self.prompt_name,
|
|
510
|
+
textOutputConfiguration=TextOutputConfiguration(),
|
|
511
|
+
audioOutputConfiguration=AudioOutputConfiguration(
|
|
512
|
+
voiceId=voice_id, sampleRateHertz=sample_rate
|
|
513
|
+
),
|
|
514
|
+
toolUseOutputConfiguration=ToolUseOutputConfiguration(),
|
|
515
|
+
toolConfiguration=ToolConfiguration(
|
|
516
|
+
tools=tool_objects, toolChoice=tool_configuration.toolChoice
|
|
517
|
+
),
|
|
518
|
+
)
|
|
519
|
+
)
|
|
520
|
+
)
|
|
521
|
+
return event.model_dump_json(exclude_none=True, by_alias=True)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from .events import SonicEventBuilder
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger("livekit.plugins.aws")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# https://jakob-bagterp.github.io/colorist-for-python/ansi-escape-codes/standard-16-colors/#bright-colors
|
|
10
|
+
class AnsiColors:
|
|
11
|
+
RED = "\033[91m"
|
|
12
|
+
GREEN = "\033[92m"
|
|
13
|
+
YELLOW = "\033[93m"
|
|
14
|
+
BLUE = "\033[94m"
|
|
15
|
+
MAGENTA = "\033[95m"
|
|
16
|
+
CYAN = "\033[96m"
|
|
17
|
+
|
|
18
|
+
BOLD = "\033[1m"
|
|
19
|
+
UNDERLINE = "\033[4m"
|
|
20
|
+
ENDC = "\033[0m"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
EVENT_COLOR_MAP = {
|
|
24
|
+
"audio_output_content_start": AnsiColors.GREEN,
|
|
25
|
+
"audio_output_content_end": AnsiColors.GREEN,
|
|
26
|
+
"text_output_content_start": AnsiColors.BLUE,
|
|
27
|
+
"text_output_content_end": AnsiColors.BLUE,
|
|
28
|
+
"tool_output_content_start": AnsiColors.YELLOW,
|
|
29
|
+
"tool_output_content_end": AnsiColors.YELLOW,
|
|
30
|
+
"text_output_content": AnsiColors.BLUE,
|
|
31
|
+
"audio_output_content": AnsiColors.GREEN,
|
|
32
|
+
"tool_output_content": AnsiColors.YELLOW,
|
|
33
|
+
"completion_start": AnsiColors.MAGENTA,
|
|
34
|
+
"completion_end": AnsiColors.MAGENTA,
|
|
35
|
+
"usage": AnsiColors.CYAN,
|
|
36
|
+
"other_event": AnsiColors.UNDERLINE,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def log_event_data(event_data: dict) -> None:
|
|
41
|
+
event_type = SonicEventBuilder.get_event_type(event_data)
|
|
42
|
+
color = EVENT_COLOR_MAP[event_type]
|
|
43
|
+
logger.debug(
|
|
44
|
+
f"{color}{event_type.upper()}: {json.dumps(event_data, indent=2)}{AnsiColors.ENDC}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def log_message(message: str, color: str) -> None:
|
|
49
|
+
logger.debug(f"{color}{message}{AnsiColors.ENDC}")
|