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.

@@ -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
- logger = logging.getLogger("voice")
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
- task = asyncio.create_task(self.run_voice_agent(breakout_room=breakout_room))
115
- task.add_done_callback(on_done)
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
- def create_agent(self):
128
- return Agent(
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.realtime.RealtimeModel(
152
- # it's necessary to turn off turn detection in the Realtime API in order to use
153
- # LiveKit's turn detection model
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:
@@ -1 +1 @@
1
- __version__ = "0.0.19"
1
+ __version__ = "0.0.21"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-livekit
3
- Version: 0.0.19
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.11
17
- Requires-Dist: livekit-plugins-openai~=1.0.11
18
- Requires-Dist: livekit-plugins-cartesia~=1.0.11
19
- Requires-Dist: livekit-plugins-elevenlabs~=1.0.11
20
- Requires-Dist: livekit-plugins-playai~=1.0.11
21
- Requires-Dist: livekit-plugins-silero~=1.0.11
22
- Requires-Dist: livekit-plugins-turn-detector~=1.0.11
23
- Requires-Dist: meshagent-api~=0.0.19
24
- Requires-Dist: meshagent-tools~=0.0.19
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=9OK8yo7gxBYvFxF5sp_Pg19ucyUGzDpUXRaYqvJmYCQ,22
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=_kKMrOmxJ8324yZ1QdauuHlFb43P6xYbPeJzXH_nRwU,5410
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.19.dist-info/licenses/LICENSE,sha256=eTt0SPW-sVNdkZe9PS_S8WfCIyLjRXRl7sUBWdlteFg,10254
9
- meshagent_livekit-0.0.19.dist-info/METADATA,sha256=mp25p_3muvEtcy3lk5XcJsMbyCVmwZCyKz_GMO6gR5E,924
10
- meshagent_livekit-0.0.19.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
11
- meshagent_livekit-0.0.19.dist-info/top_level.txt,sha256=GlcXnHtRP6m7zlG3Df04M35OsHtNXy_DY09oFwWrH74,10
12
- meshagent_livekit-0.0.19.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5