meshagent-livekit 0.0.19__py3-none-any.whl → 0.0.21__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 meshagent-livekit might be problematic. Click here for more details.
- meshagent/livekit/agents/voice.py +183 -24
- meshagent/livekit/version.py +1 -1
- {meshagent_livekit-0.0.19.dist-info → meshagent_livekit-0.0.21.dist-info}/METADATA +10 -10
- {meshagent_livekit-0.0.19.dist-info → meshagent_livekit-0.0.21.dist-info}/RECORD +7 -7
- {meshagent_livekit-0.0.19.dist-info → meshagent_livekit-0.0.21.dist-info}/WHEEL +1 -1
- {meshagent_livekit-0.0.19.dist-info → meshagent_livekit-0.0.21.dist-info}/licenses/LICENSE +0 -0
- {meshagent_livekit-0.0.19.dist-info → meshagent_livekit-0.0.21.dist-info}/top_level.txt +0 -0
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import asyncio
|
|
3
3
|
|
|
4
|
-
from meshagent.api import RoomMessage
|
|
4
|
+
from meshagent.api import RoomMessage, ErrorResponse, JsonResponse, FileResponse, Requirement, Participant, JsonResponse, EmptyResponse, TextResponse
|
|
5
5
|
from meshagent.api.room_server_client import RoomClient
|
|
6
6
|
|
|
7
|
+
from meshagent.agents import ToolResponseAdapter
|
|
8
|
+
from meshagent.tools import ToolContext, Toolkit
|
|
7
9
|
from livekit.agents import Agent, AgentSession
|
|
10
|
+
from livekit.agents.llm import RawFunctionTool, ToolError, function_tool
|
|
11
|
+
|
|
12
|
+
from livekit.agents import BackgroundAudioPlayer, AudioConfig, BuiltinAudioClip
|
|
13
|
+
|
|
14
|
+
from typing import Annotated
|
|
8
15
|
from livekit.plugins import openai, silero
|
|
9
16
|
#from livekit.plugins.turn_detector.multilingual import MultilingualModel
|
|
10
17
|
import uuid
|
|
11
18
|
import asyncio
|
|
12
19
|
import logging
|
|
13
20
|
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
14
26
|
|
|
15
27
|
from livekit.plugins import openai
|
|
16
28
|
|
|
17
29
|
from livekit.plugins import openai, silero
|
|
18
30
|
from livekit import rtc
|
|
19
|
-
from livekit.agents import Agent, AgentSession
|
|
31
|
+
from livekit.agents import Agent, AgentSession, RunContext
|
|
20
32
|
|
|
21
33
|
from typing import Optional
|
|
22
34
|
|
|
@@ -26,12 +38,39 @@ from meshagent.api.schema_util import merge, prompt_schema
|
|
|
26
38
|
|
|
27
39
|
from meshagent.agents import SingleRoomAgent
|
|
28
40
|
|
|
41
|
+
from livekit.plugins.turn_detector.multilingual import MultilingualModel
|
|
29
42
|
|
|
30
|
-
|
|
43
|
+
import re
|
|
31
44
|
|
|
45
|
+
logger = logging.getLogger("voice")
|
|
32
46
|
|
|
33
|
-
from meshagent.agents.agent import AgentCallContext
|
|
34
47
|
|
|
48
|
+
def _replace_non_matching(text: str, allowed_chars: str, replacement: str) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Replaces every character in `text` that does not match the given
|
|
51
|
+
`allowed_chars` regex set with `replacement`.
|
|
52
|
+
|
|
53
|
+
Parameters:
|
|
54
|
+
-----------
|
|
55
|
+
text : str
|
|
56
|
+
The input string on which the replacement is to be done.
|
|
57
|
+
allowed_chars : str
|
|
58
|
+
A string defining the set of allowed characters (part of a character set).
|
|
59
|
+
For example, "a-zA-Z0-9" will keep only letters and digits.
|
|
60
|
+
replacement : str
|
|
61
|
+
The string to replace non-matching characters with.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
--------
|
|
65
|
+
str
|
|
66
|
+
A new string where all characters not in `allowed_chars` are replaced.
|
|
67
|
+
"""
|
|
68
|
+
# Build a regex that matches any character NOT in allowed_chars
|
|
69
|
+
pattern = rf"[^{allowed_chars}]"
|
|
70
|
+
return re.sub(pattern, replacement, text)
|
|
71
|
+
|
|
72
|
+
def safe_tool_name(name: str):
|
|
73
|
+
return _replace_non_matching(name, "a-zA-Z0-9_-", "_")
|
|
35
74
|
|
|
36
75
|
class VoiceConnection:
|
|
37
76
|
def __init__(self, *, room: RoomClient, breakout_room: str):
|
|
@@ -69,10 +108,19 @@ class Voicebot(SingleRoomAgent):
|
|
|
69
108
|
rules: Optional[list[str]] = None,
|
|
70
109
|
auto_greet_prompt: Optional[str] = None,
|
|
71
110
|
greeting: Optional[str] = None,
|
|
111
|
+
tool_adapter: ToolResponseAdapter = None,
|
|
112
|
+
toolkits: list[Toolkit] = None,
|
|
113
|
+
requires: list[Requirement] = None
|
|
72
114
|
):
|
|
115
|
+
if toolkits == None:
|
|
116
|
+
toolkits = []
|
|
117
|
+
|
|
118
|
+
self.toolkits = toolkits
|
|
119
|
+
|
|
73
120
|
if rules == None:
|
|
74
121
|
rules = [ "You are a helpful assistant communicating through voice." ]
|
|
75
122
|
|
|
123
|
+
self.tool_adapter = tool_adapter
|
|
76
124
|
self.auto_greet_prompt = auto_greet_prompt
|
|
77
125
|
self.greeting = greeting
|
|
78
126
|
|
|
@@ -90,7 +138,8 @@ class Voicebot(SingleRoomAgent):
|
|
|
90
138
|
name=name,
|
|
91
139
|
description=description,
|
|
92
140
|
title=title,
|
|
93
|
-
labels=labels
|
|
141
|
+
labels=labels,
|
|
142
|
+
requires=requires
|
|
94
143
|
)
|
|
95
144
|
|
|
96
145
|
async def start(self, *, room):
|
|
@@ -111,9 +160,16 @@ class Voicebot(SingleRoomAgent):
|
|
|
111
160
|
except Exception as e:
|
|
112
161
|
logger.error(f"{e}", exc_info=e)
|
|
113
162
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
163
|
+
for participant in self.room.messaging.remote_participants:
|
|
164
|
+
|
|
165
|
+
if participant.id == message.from_participant_id:
|
|
166
|
+
|
|
167
|
+
task = asyncio.create_task(self.run_voice_agent(participant=participant, breakout_room=breakout_room))
|
|
168
|
+
task.add_done_callback(on_done)
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
logger.error(f"unable to find participant {message.from_participant_id}")
|
|
172
|
+
|
|
117
173
|
|
|
118
174
|
async def _wait_for_disconnect(self, room: rtc.Room):
|
|
119
175
|
disconnected = asyncio.Future()
|
|
@@ -122,13 +178,106 @@ class Voicebot(SingleRoomAgent):
|
|
|
122
178
|
room.on("disconnected", on_disconnected)
|
|
123
179
|
|
|
124
180
|
logger.info("waiting for disconnection")
|
|
125
|
-
await disconnected
|
|
181
|
+
await disconnected
|
|
182
|
+
|
|
183
|
+
async def make_function_tools(self, *, context: ToolContext):
|
|
184
|
+
|
|
185
|
+
toolkits = [
|
|
186
|
+
*await self.get_required_toolkits(context=context),
|
|
187
|
+
*self.toolkits
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
tools = []
|
|
191
|
+
|
|
192
|
+
for toolkit in toolkits:
|
|
126
193
|
|
|
127
|
-
|
|
128
|
-
|
|
194
|
+
for tool in toolkit.tools:
|
|
195
|
+
|
|
196
|
+
tools.append(self._make_function_tool(toolkits, context, tool.name, tool.description, tool.input_schema))
|
|
197
|
+
|
|
198
|
+
return tools
|
|
199
|
+
|
|
200
|
+
def _make_function_tool(
|
|
201
|
+
self, toolkits: list[Toolkit], context: ToolContext, name: str, description: str | None, input_schema: dict
|
|
202
|
+
) -> RawFunctionTool:
|
|
203
|
+
|
|
204
|
+
name = safe_tool_name(name)
|
|
205
|
+
async def _tool_called(raw_arguments: dict) -> Any:
|
|
206
|
+
try:
|
|
207
|
+
|
|
208
|
+
tool = None
|
|
209
|
+
for toolkit in toolkits:
|
|
210
|
+
for t in toolkit.tools:
|
|
211
|
+
if safe_tool_name(t.name) == name:
|
|
212
|
+
tool = t
|
|
213
|
+
|
|
214
|
+
if tool is None:
|
|
215
|
+
raise ToolError(
|
|
216
|
+
f"Could not find tool {name}"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
logger.info(f"executing tool {name}: {raw_arguments}")
|
|
221
|
+
tool_result = await tool.execute(context=context, **raw_arguments)
|
|
222
|
+
except Exception as e:
|
|
223
|
+
logger.error(f"failed to call tool {tool.name}: {e}")
|
|
224
|
+
return ToolError("f{e}")
|
|
225
|
+
if self.tool_adapter == None:
|
|
226
|
+
|
|
227
|
+
if isinstance(tool_result, ErrorResponse):
|
|
228
|
+
raise ToolError(tool_result.text)
|
|
229
|
+
|
|
230
|
+
if isinstance(tool_result, JsonResponse):
|
|
231
|
+
return json.dumps(tool_result.json)
|
|
232
|
+
|
|
233
|
+
if isinstance(tool_result, TextResponse):
|
|
234
|
+
return tool_result.text
|
|
235
|
+
|
|
236
|
+
if isinstance(tool_result, EmptyResponse):
|
|
237
|
+
return "success"
|
|
238
|
+
|
|
239
|
+
if tool_result == None:
|
|
240
|
+
return "success"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
raise ToolError(
|
|
244
|
+
f"Tool '{name}' returned an unexpected result {type(tool_result)}, attach a tool response adapter"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
else:
|
|
248
|
+
|
|
249
|
+
text = await self.tool_adapter.to_plain_text(room=context.room, response=tool_result)
|
|
250
|
+
if text == None:
|
|
251
|
+
text = "success"
|
|
252
|
+
return text
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
logger.error("unable to call tool", exc_info=e)
|
|
256
|
+
raise
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
return function_tool(
|
|
260
|
+
_tool_called,
|
|
261
|
+
raw_schema={"name": name, "description": description, "strict" : True, "parameters": input_schema},
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
async def create_agent(self, *, context: ToolContext, session: AgentSession):
|
|
265
|
+
|
|
266
|
+
@function_tool
|
|
267
|
+
async def say(context: RunContext, text: str):
|
|
268
|
+
"says something out loud to the user"
|
|
269
|
+
logger.info(f"saying: {text}")
|
|
270
|
+
session.say(text)
|
|
271
|
+
return "success"
|
|
272
|
+
|
|
273
|
+
return Agent(
|
|
129
274
|
instructions="\n".join(self.rules),
|
|
130
|
-
allow_interruptions=True
|
|
131
|
-
|
|
275
|
+
allow_interruptions=True,
|
|
276
|
+
tools=[
|
|
277
|
+
*await self.make_function_tools(context=context),
|
|
278
|
+
say
|
|
279
|
+
]
|
|
280
|
+
)
|
|
132
281
|
|
|
133
282
|
# agent = Agent(
|
|
134
283
|
# instructions="""
|
|
@@ -144,32 +293,42 @@ class Voicebot(SingleRoomAgent):
|
|
|
144
293
|
def create_session(self) -> AgentSession:
|
|
145
294
|
|
|
146
295
|
session = AgentSession(
|
|
296
|
+
max_tool_steps=50,
|
|
147
297
|
allow_interruptions=True,
|
|
148
298
|
vad=silero.VAD.load(),
|
|
149
299
|
stt=openai.STT(),
|
|
150
300
|
tts=openai.TTS(voice="echo"),
|
|
151
|
-
llm=openai.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
voice="alloy",
|
|
155
|
-
turn_detection=None,
|
|
156
|
-
input_audio_transcription=None,
|
|
157
|
-
|
|
158
|
-
),
|
|
301
|
+
llm=openai.LLM(),
|
|
302
|
+
#turn_detection=MultilingualModel(),
|
|
303
|
+
|
|
159
304
|
)
|
|
160
305
|
return session
|
|
161
306
|
|
|
162
307
|
|
|
163
|
-
async def run_voice_agent(self, *, breakout_room: str):
|
|
308
|
+
async def run_voice_agent(self, *, participant: Participant, breakout_room: str):
|
|
164
309
|
|
|
165
310
|
async with VoiceConnection(room=self.room, breakout_room=breakout_room) as connection:
|
|
166
311
|
|
|
167
|
-
|
|
168
312
|
logger.info("starting voice agent")
|
|
169
313
|
|
|
170
|
-
agent = self.create_agent()
|
|
171
314
|
session = self.create_session()
|
|
172
315
|
|
|
316
|
+
agent = await self.create_agent(context=ToolContext(
|
|
317
|
+
room=self.room,
|
|
318
|
+
caller=self.room.local_participant,
|
|
319
|
+
on_behalf_of=participant
|
|
320
|
+
), session=session)
|
|
321
|
+
|
|
322
|
+
background_audio = BackgroundAudioPlayer(
|
|
323
|
+
thinking_sound=[
|
|
324
|
+
#AudioConfig(
|
|
325
|
+
# os.path.dirname(os.path.abspath(__file__)) +"/sfx/thinking.mp3", volume=0.2),
|
|
326
|
+
AudioConfig(BuiltinAudioClip.KEYBOARD_TYPING, volume=0.3),
|
|
327
|
+
AudioConfig(BuiltinAudioClip.KEYBOARD_TYPING2, volume=0.4),
|
|
328
|
+
],
|
|
329
|
+
)
|
|
330
|
+
await background_audio.start(room=connection.livekit_room, agent_session=session)
|
|
331
|
+
|
|
173
332
|
await session.start(agent=agent, room=connection.livekit_room)
|
|
174
333
|
|
|
175
334
|
if self.auto_greet_prompt != None:
|
meshagent/livekit/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.21"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-livekit
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.21
|
|
4
4
|
Summary: Livekit support for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://meshagent.com
|
|
@@ -13,13 +13,13 @@ Requires-Dist: pytest~=8.3.5
|
|
|
13
13
|
Requires-Dist: pytest-asyncio~=0.26.0
|
|
14
14
|
Requires-Dist: strip-markdown~=1.3
|
|
15
15
|
Requires-Dist: livekit-api~=1.0.2
|
|
16
|
-
Requires-Dist: livekit-agents~=1.0.
|
|
17
|
-
Requires-Dist: livekit-plugins-openai~=1.0.
|
|
18
|
-
Requires-Dist: livekit-plugins-cartesia~=1.0.
|
|
19
|
-
Requires-Dist: livekit-plugins-elevenlabs~=1.0.
|
|
20
|
-
Requires-Dist: livekit-plugins-playai~=1.0.
|
|
21
|
-
Requires-Dist: livekit-plugins-silero~=1.0.
|
|
22
|
-
Requires-Dist: livekit-plugins-turn-detector~=1.0.
|
|
23
|
-
Requires-Dist: meshagent-api~=0.0.
|
|
24
|
-
Requires-Dist: meshagent-tools~=0.0.
|
|
16
|
+
Requires-Dist: livekit-agents~=1.0.19
|
|
17
|
+
Requires-Dist: livekit-plugins-openai~=1.0.19
|
|
18
|
+
Requires-Dist: livekit-plugins-cartesia~=1.0.19
|
|
19
|
+
Requires-Dist: livekit-plugins-elevenlabs~=1.0.19
|
|
20
|
+
Requires-Dist: livekit-plugins-playai~=1.0.19
|
|
21
|
+
Requires-Dist: livekit-plugins-silero~=1.0.19
|
|
22
|
+
Requires-Dist: livekit-plugins-turn-detector~=1.0.19
|
|
23
|
+
Requires-Dist: meshagent-api~=0.0.21
|
|
24
|
+
Requires-Dist: meshagent-tools~=0.0.21
|
|
25
25
|
Dynamic: license-file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
meshagent/livekit/__init__.py,sha256=8zLGg-DfQhnDl2Ky0n-zXpN-8e-g7iR0AcaI4l4Vvpk,32
|
|
2
2
|
meshagent/livekit/livekit_protocol.py,sha256=K9yP-qpxag5_7TXlKjFEx3cOJJJpYI_z6zGzFHoN1Hs,1421
|
|
3
3
|
meshagent/livekit/livekit_protocol_test.py,sha256=n_ZQjt7n4u7TM7eENzH8L0tw8LvypS_JHF_PuJ2o6h4,2836
|
|
4
|
-
meshagent/livekit/version.py,sha256=
|
|
4
|
+
meshagent/livekit/version.py,sha256=9_nCx08vWHyj8RiwIYqwqah3T4SPB46e0jnaNSxxfDc,22
|
|
5
5
|
meshagent/livekit/agents/transcriber.py,sha256=Dq1Ijx4gmA-0jQGM-f3w7X-JIZpkRCFDxWae9AOwz-k,12290
|
|
6
|
-
meshagent/livekit/agents/voice.py,sha256=
|
|
6
|
+
meshagent/livekit/agents/voice.py,sha256=uVpm-YeP1oXkg6gk1zFSwVEdp5XGQVimgs3DdNjEpbg,11230
|
|
7
7
|
meshagent/livekit/tools/speech.py,sha256=UMhdHhTo04xdzHhvvCeTayT_YT86dzx4ZERRF18C0-o,10188
|
|
8
|
-
meshagent_livekit-0.0.
|
|
9
|
-
meshagent_livekit-0.0.
|
|
10
|
-
meshagent_livekit-0.0.
|
|
11
|
-
meshagent_livekit-0.0.
|
|
12
|
-
meshagent_livekit-0.0.
|
|
8
|
+
meshagent_livekit-0.0.21.dist-info/licenses/LICENSE,sha256=eTt0SPW-sVNdkZe9PS_S8WfCIyLjRXRl7sUBWdlteFg,10254
|
|
9
|
+
meshagent_livekit-0.0.21.dist-info/METADATA,sha256=HNq297tsUcj91no9YiCAUOlJZCGfQTMlpVmaUBeUF_8,924
|
|
10
|
+
meshagent_livekit-0.0.21.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
|
|
11
|
+
meshagent_livekit-0.0.21.dist-info/top_level.txt,sha256=GlcXnHtRP6m7zlG3Df04M35OsHtNXy_DY09oFwWrH74,10
|
|
12
|
+
meshagent_livekit-0.0.21.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|