meshagent-livekit 0.0.19__tar.gz → 0.0.21__tar.gz
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-0.0.19 → meshagent_livekit-0.0.21}/CHANGELOG.md +6 -0
- {meshagent_livekit-0.0.19/meshagent_livekit.egg-info → meshagent_livekit-0.0.21}/PKG-INFO +10 -10
- meshagent_livekit-0.0.21/meshagent/livekit/agents/voice.py +346 -0
- meshagent_livekit-0.0.21/meshagent/livekit/version.py +1 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21/meshagent_livekit.egg-info}/PKG-INFO +10 -10
- meshagent_livekit-0.0.21/meshagent_livekit.egg-info/requires.txt +13 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/pyproject.toml +9 -9
- meshagent_livekit-0.0.19/meshagent/livekit/agents/voice.py +0 -187
- meshagent_livekit-0.0.19/meshagent/livekit/version.py +0 -1
- meshagent_livekit-0.0.19/meshagent_livekit.egg-info/requires.txt +0 -13
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/LICENSE +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/MANIFEST.in +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/README.md +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent/livekit/__init__.py +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent/livekit/agents/transcriber.py +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent/livekit/livekit_protocol.py +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent/livekit/livekit_protocol_test.py +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent/livekit/tools/speech.py +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent_livekit.egg-info/SOURCES.txt +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent_livekit.egg-info/dependency_links.txt +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent_livekit.egg-info/top_level.txt +0 -0
- {meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/setup.cfg +0 -0
|
@@ -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
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
from meshagent.api import RoomMessage, ErrorResponse, JsonResponse, FileResponse, Requirement, Participant, JsonResponse, EmptyResponse, TextResponse
|
|
5
|
+
from meshagent.api.room_server_client import RoomClient
|
|
6
|
+
|
|
7
|
+
from meshagent.agents import ToolResponseAdapter
|
|
8
|
+
from meshagent.tools import ToolContext, Toolkit
|
|
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
|
|
15
|
+
from livekit.plugins import openai, silero
|
|
16
|
+
#from livekit.plugins.turn_detector.multilingual import MultilingualModel
|
|
17
|
+
import uuid
|
|
18
|
+
import asyncio
|
|
19
|
+
import logging
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from livekit.plugins import openai
|
|
28
|
+
|
|
29
|
+
from livekit.plugins import openai, silero
|
|
30
|
+
from livekit import rtc
|
|
31
|
+
from livekit.agents import Agent, AgentSession, RunContext
|
|
32
|
+
|
|
33
|
+
from typing import Optional
|
|
34
|
+
|
|
35
|
+
from copy import deepcopy
|
|
36
|
+
|
|
37
|
+
from meshagent.api.schema_util import merge, prompt_schema
|
|
38
|
+
|
|
39
|
+
from meshagent.agents import SingleRoomAgent
|
|
40
|
+
|
|
41
|
+
from livekit.plugins.turn_detector.multilingual import MultilingualModel
|
|
42
|
+
|
|
43
|
+
import re
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger("voice")
|
|
46
|
+
|
|
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_-", "_")
|
|
74
|
+
|
|
75
|
+
class VoiceConnection:
|
|
76
|
+
def __init__(self, *, room: RoomClient, breakout_room: str):
|
|
77
|
+
self.room = room
|
|
78
|
+
self.breakout_room = breakout_room
|
|
79
|
+
|
|
80
|
+
async def __aenter__(self):
|
|
81
|
+
|
|
82
|
+
client = self.room
|
|
83
|
+
|
|
84
|
+
room_options = rtc.RoomOptions(auto_subscribe=True)
|
|
85
|
+
|
|
86
|
+
room = rtc.Room()
|
|
87
|
+
|
|
88
|
+
self.livekit_room = room
|
|
89
|
+
|
|
90
|
+
connection_info = await client.livekit.get_connection_info(breakout_room=self.breakout_room)
|
|
91
|
+
|
|
92
|
+
await room.connect(url=connection_info.url, token=connection_info.token, options=room_options)
|
|
93
|
+
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
async def __aexit__(self, exc_type, exc, tb):
|
|
97
|
+
await self.livekit_room.disconnect()
|
|
98
|
+
|
|
99
|
+
class Voicebot(SingleRoomAgent):
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
name: str,
|
|
104
|
+
input_schema: Optional[dict] = None, # the base schema, voice agent parameters will be added
|
|
105
|
+
title: Optional[str] = None,
|
|
106
|
+
description: Optional[str] = None,
|
|
107
|
+
labels: Optional[list[str]] = None,
|
|
108
|
+
rules: Optional[list[str]] = None,
|
|
109
|
+
auto_greet_prompt: Optional[str] = None,
|
|
110
|
+
greeting: Optional[str] = None,
|
|
111
|
+
tool_adapter: ToolResponseAdapter = None,
|
|
112
|
+
toolkits: list[Toolkit] = None,
|
|
113
|
+
requires: list[Requirement] = None
|
|
114
|
+
):
|
|
115
|
+
if toolkits == None:
|
|
116
|
+
toolkits = []
|
|
117
|
+
|
|
118
|
+
self.toolkits = toolkits
|
|
119
|
+
|
|
120
|
+
if rules == None:
|
|
121
|
+
rules = [ "You are a helpful assistant communicating through voice." ]
|
|
122
|
+
|
|
123
|
+
self.tool_adapter = tool_adapter
|
|
124
|
+
self.auto_greet_prompt = auto_greet_prompt
|
|
125
|
+
self.greeting = greeting
|
|
126
|
+
|
|
127
|
+
self.rules = rules
|
|
128
|
+
|
|
129
|
+
if input_schema == None:
|
|
130
|
+
input_schema = None
|
|
131
|
+
|
|
132
|
+
input_schema = merge(
|
|
133
|
+
schema=input_schema,
|
|
134
|
+
additional_properties={
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
super().__init__(
|
|
138
|
+
name=name,
|
|
139
|
+
description=description,
|
|
140
|
+
title=title,
|
|
141
|
+
labels=labels,
|
|
142
|
+
requires=requires
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
async def start(self, *, room):
|
|
146
|
+
await super().start(room=room)
|
|
147
|
+
await room.local_participant.set_attribute("supports_voice", True)
|
|
148
|
+
await room.messaging.enable()
|
|
149
|
+
room.messaging.on("message", self.on_message)
|
|
150
|
+
|
|
151
|
+
def on_message(self, message: RoomMessage):
|
|
152
|
+
if message.type == "voice_call":
|
|
153
|
+
breakout_room = message.message["breakout_room"]
|
|
154
|
+
|
|
155
|
+
logger.info(f"joining breakout room {breakout_room}")
|
|
156
|
+
|
|
157
|
+
def on_done(task: asyncio.Task):
|
|
158
|
+
try:
|
|
159
|
+
task.result()
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(f"{e}", exc_info=e)
|
|
162
|
+
|
|
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
|
+
|
|
173
|
+
|
|
174
|
+
async def _wait_for_disconnect(self, room: rtc.Room):
|
|
175
|
+
disconnected = asyncio.Future()
|
|
176
|
+
def on_disconnected(_):
|
|
177
|
+
disconnected.set_result(True)
|
|
178
|
+
room.on("disconnected", on_disconnected)
|
|
179
|
+
|
|
180
|
+
logger.info("waiting for disconnection")
|
|
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:
|
|
193
|
+
|
|
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(
|
|
274
|
+
instructions="\n".join(self.rules),
|
|
275
|
+
allow_interruptions=True,
|
|
276
|
+
tools=[
|
|
277
|
+
*await self.make_function_tools(context=context),
|
|
278
|
+
say
|
|
279
|
+
]
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# agent = Agent(
|
|
283
|
+
# instructions="""
|
|
284
|
+
# You are a helpful assistant communicating through voice.
|
|
285
|
+
# """,
|
|
286
|
+
# stt=openai.STT(),
|
|
287
|
+
# llm=openai.LLM(model="gpt-4o"),
|
|
288
|
+
# tts=openai.TTS(),
|
|
289
|
+
# vad=silero.VAD.load(),
|
|
290
|
+
# allow_interruptions=True
|
|
291
|
+
#)
|
|
292
|
+
|
|
293
|
+
def create_session(self) -> AgentSession:
|
|
294
|
+
|
|
295
|
+
session = AgentSession(
|
|
296
|
+
max_tool_steps=50,
|
|
297
|
+
allow_interruptions=True,
|
|
298
|
+
vad=silero.VAD.load(),
|
|
299
|
+
stt=openai.STT(),
|
|
300
|
+
tts=openai.TTS(voice="echo"),
|
|
301
|
+
llm=openai.LLM(),
|
|
302
|
+
#turn_detection=MultilingualModel(),
|
|
303
|
+
|
|
304
|
+
)
|
|
305
|
+
return session
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
async def run_voice_agent(self, *, participant: Participant, breakout_room: str):
|
|
309
|
+
|
|
310
|
+
async with VoiceConnection(room=self.room, breakout_room=breakout_room) as connection:
|
|
311
|
+
|
|
312
|
+
logger.info("starting voice agent")
|
|
313
|
+
|
|
314
|
+
session = self.create_session()
|
|
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
|
+
|
|
332
|
+
await session.start(agent=agent, room=connection.livekit_room)
|
|
333
|
+
|
|
334
|
+
if self.auto_greet_prompt != None:
|
|
335
|
+
session.generate_reply(user_input=self.auto_greet_prompt)
|
|
336
|
+
|
|
337
|
+
if self.greeting != None:
|
|
338
|
+
session.say(self.greeting)
|
|
339
|
+
|
|
340
|
+
logger.info("started voice agent")
|
|
341
|
+
await self._wait_for_disconnect(room=connection.livekit_room)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
@@ -0,0 +1 @@
|
|
|
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
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
pytest~=8.3.5
|
|
2
|
+
pytest-asyncio~=0.26.0
|
|
3
|
+
strip-markdown~=1.3
|
|
4
|
+
livekit-api~=1.0.2
|
|
5
|
+
livekit-agents~=1.0.19
|
|
6
|
+
livekit-plugins-openai~=1.0.19
|
|
7
|
+
livekit-plugins-cartesia~=1.0.19
|
|
8
|
+
livekit-plugins-elevenlabs~=1.0.19
|
|
9
|
+
livekit-plugins-playai~=1.0.19
|
|
10
|
+
livekit-plugins-silero~=1.0.19
|
|
11
|
+
livekit-plugins-turn-detector~=1.0.19
|
|
12
|
+
meshagent-api~=0.0.21
|
|
13
|
+
meshagent-tools~=0.0.21
|
|
@@ -14,15 +14,15 @@ dependencies = [
|
|
|
14
14
|
"pytest-asyncio~=0.26.0",
|
|
15
15
|
"strip-markdown~=1.3",
|
|
16
16
|
"livekit-api~=1.0.2",
|
|
17
|
-
"livekit-agents~=1.0.
|
|
18
|
-
"livekit-plugins-openai~=1.0.
|
|
19
|
-
"livekit-plugins-cartesia~=1.0.
|
|
20
|
-
"livekit-plugins-elevenlabs~=1.0.
|
|
21
|
-
"livekit-plugins-playai~=1.0.
|
|
22
|
-
"livekit-plugins-silero~=1.0.
|
|
23
|
-
"livekit-plugins-turn-detector~=1.0.
|
|
24
|
-
"meshagent-api~=0.0.
|
|
25
|
-
"meshagent-tools~=0.0.
|
|
17
|
+
"livekit-agents~=1.0.19",
|
|
18
|
+
"livekit-plugins-openai~=1.0.19",
|
|
19
|
+
"livekit-plugins-cartesia~=1.0.19",
|
|
20
|
+
"livekit-plugins-elevenlabs~=1.0.19",
|
|
21
|
+
"livekit-plugins-playai~=1.0.19",
|
|
22
|
+
"livekit-plugins-silero~=1.0.19",
|
|
23
|
+
"livekit-plugins-turn-detector~=1.0.19",
|
|
24
|
+
"meshagent-api~=0.0.21",
|
|
25
|
+
"meshagent-tools~=0.0.21"
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import asyncio
|
|
3
|
-
|
|
4
|
-
from meshagent.api import RoomMessage
|
|
5
|
-
from meshagent.api.room_server_client import RoomClient
|
|
6
|
-
|
|
7
|
-
from livekit.agents import Agent, AgentSession
|
|
8
|
-
from livekit.plugins import openai, silero
|
|
9
|
-
#from livekit.plugins.turn_detector.multilingual import MultilingualModel
|
|
10
|
-
import uuid
|
|
11
|
-
import asyncio
|
|
12
|
-
import logging
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from livekit.plugins import openai
|
|
16
|
-
|
|
17
|
-
from livekit.plugins import openai, silero
|
|
18
|
-
from livekit import rtc
|
|
19
|
-
from livekit.agents import Agent, AgentSession
|
|
20
|
-
|
|
21
|
-
from typing import Optional
|
|
22
|
-
|
|
23
|
-
from copy import deepcopy
|
|
24
|
-
|
|
25
|
-
from meshagent.api.schema_util import merge, prompt_schema
|
|
26
|
-
|
|
27
|
-
from meshagent.agents import SingleRoomAgent
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
logger = logging.getLogger("voice")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
from meshagent.agents.agent import AgentCallContext
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class VoiceConnection:
|
|
37
|
-
def __init__(self, *, room: RoomClient, breakout_room: str):
|
|
38
|
-
self.room = room
|
|
39
|
-
self.breakout_room = breakout_room
|
|
40
|
-
|
|
41
|
-
async def __aenter__(self):
|
|
42
|
-
|
|
43
|
-
client = self.room
|
|
44
|
-
|
|
45
|
-
room_options = rtc.RoomOptions(auto_subscribe=True)
|
|
46
|
-
|
|
47
|
-
room = rtc.Room()
|
|
48
|
-
|
|
49
|
-
self.livekit_room = room
|
|
50
|
-
|
|
51
|
-
connection_info = await client.livekit.get_connection_info(breakout_room=self.breakout_room)
|
|
52
|
-
|
|
53
|
-
await room.connect(url=connection_info.url, token=connection_info.token, options=room_options)
|
|
54
|
-
|
|
55
|
-
return self
|
|
56
|
-
|
|
57
|
-
async def __aexit__(self, exc_type, exc, tb):
|
|
58
|
-
await self.livekit_room.disconnect()
|
|
59
|
-
|
|
60
|
-
class Voicebot(SingleRoomAgent):
|
|
61
|
-
|
|
62
|
-
def __init__(
|
|
63
|
-
self,
|
|
64
|
-
name: str,
|
|
65
|
-
input_schema: Optional[dict] = None, # the base schema, voice agent parameters will be added
|
|
66
|
-
title: Optional[str] = None,
|
|
67
|
-
description: Optional[str] = None,
|
|
68
|
-
labels: Optional[list[str]] = None,
|
|
69
|
-
rules: Optional[list[str]] = None,
|
|
70
|
-
auto_greet_prompt: Optional[str] = None,
|
|
71
|
-
greeting: Optional[str] = None,
|
|
72
|
-
):
|
|
73
|
-
if rules == None:
|
|
74
|
-
rules = [ "You are a helpful assistant communicating through voice." ]
|
|
75
|
-
|
|
76
|
-
self.auto_greet_prompt = auto_greet_prompt
|
|
77
|
-
self.greeting = greeting
|
|
78
|
-
|
|
79
|
-
self.rules = rules
|
|
80
|
-
|
|
81
|
-
if input_schema == None:
|
|
82
|
-
input_schema = None
|
|
83
|
-
|
|
84
|
-
input_schema = merge(
|
|
85
|
-
schema=input_schema,
|
|
86
|
-
additional_properties={
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
super().__init__(
|
|
90
|
-
name=name,
|
|
91
|
-
description=description,
|
|
92
|
-
title=title,
|
|
93
|
-
labels=labels
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
async def start(self, *, room):
|
|
97
|
-
await super().start(room=room)
|
|
98
|
-
await room.local_participant.set_attribute("supports_voice", True)
|
|
99
|
-
await room.messaging.enable()
|
|
100
|
-
room.messaging.on("message", self.on_message)
|
|
101
|
-
|
|
102
|
-
def on_message(self, message: RoomMessage):
|
|
103
|
-
if message.type == "voice_call":
|
|
104
|
-
breakout_room = message.message["breakout_room"]
|
|
105
|
-
|
|
106
|
-
logger.info(f"joining breakout room {breakout_room}")
|
|
107
|
-
|
|
108
|
-
def on_done(task: asyncio.Task):
|
|
109
|
-
try:
|
|
110
|
-
task.result()
|
|
111
|
-
except Exception as e:
|
|
112
|
-
logger.error(f"{e}", exc_info=e)
|
|
113
|
-
|
|
114
|
-
task = asyncio.create_task(self.run_voice_agent(breakout_room=breakout_room))
|
|
115
|
-
task.add_done_callback(on_done)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
async def _wait_for_disconnect(self, room: rtc.Room):
|
|
119
|
-
disconnected = asyncio.Future()
|
|
120
|
-
def on_disconnected(_):
|
|
121
|
-
disconnected.set_result(True)
|
|
122
|
-
room.on("disconnected", on_disconnected)
|
|
123
|
-
|
|
124
|
-
logger.info("waiting for disconnection")
|
|
125
|
-
await disconnected
|
|
126
|
-
|
|
127
|
-
def create_agent(self):
|
|
128
|
-
return Agent(
|
|
129
|
-
instructions="\n".join(self.rules),
|
|
130
|
-
allow_interruptions=True
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
# agent = Agent(
|
|
134
|
-
# instructions="""
|
|
135
|
-
# You are a helpful assistant communicating through voice.
|
|
136
|
-
# """,
|
|
137
|
-
# stt=openai.STT(),
|
|
138
|
-
# llm=openai.LLM(model="gpt-4o"),
|
|
139
|
-
# tts=openai.TTS(),
|
|
140
|
-
# vad=silero.VAD.load(),
|
|
141
|
-
# allow_interruptions=True
|
|
142
|
-
#)
|
|
143
|
-
|
|
144
|
-
def create_session(self) -> AgentSession:
|
|
145
|
-
|
|
146
|
-
session = AgentSession(
|
|
147
|
-
allow_interruptions=True,
|
|
148
|
-
vad=silero.VAD.load(),
|
|
149
|
-
stt=openai.STT(),
|
|
150
|
-
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
|
-
),
|
|
159
|
-
)
|
|
160
|
-
return session
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
async def run_voice_agent(self, *, breakout_room: str):
|
|
164
|
-
|
|
165
|
-
async with VoiceConnection(room=self.room, breakout_room=breakout_room) as connection:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
logger.info("starting voice agent")
|
|
169
|
-
|
|
170
|
-
agent = self.create_agent()
|
|
171
|
-
session = self.create_session()
|
|
172
|
-
|
|
173
|
-
await session.start(agent=agent, room=connection.livekit_room)
|
|
174
|
-
|
|
175
|
-
if self.auto_greet_prompt != None:
|
|
176
|
-
session.generate_reply(user_input=self.auto_greet_prompt)
|
|
177
|
-
|
|
178
|
-
if self.greeting != None:
|
|
179
|
-
session.say(self.greeting)
|
|
180
|
-
|
|
181
|
-
logger.info("started voice agent")
|
|
182
|
-
await self._wait_for_disconnect(room=connection.livekit_room)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.19"
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
pytest~=8.3.5
|
|
2
|
-
pytest-asyncio~=0.26.0
|
|
3
|
-
strip-markdown~=1.3
|
|
4
|
-
livekit-api~=1.0.2
|
|
5
|
-
livekit-agents~=1.0.11
|
|
6
|
-
livekit-plugins-openai~=1.0.11
|
|
7
|
-
livekit-plugins-cartesia~=1.0.11
|
|
8
|
-
livekit-plugins-elevenlabs~=1.0.11
|
|
9
|
-
livekit-plugins-playai~=1.0.11
|
|
10
|
-
livekit-plugins-silero~=1.0.11
|
|
11
|
-
livekit-plugins-turn-detector~=1.0.11
|
|
12
|
-
meshagent-api~=0.0.19
|
|
13
|
-
meshagent-tools~=0.0.19
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent/livekit/agents/transcriber.py
RENAMED
|
File without changes
|
|
File without changes
|
{meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent/livekit/livekit_protocol_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent_livekit.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{meshagent_livekit-0.0.19 → meshagent_livekit-0.0.21}/meshagent_livekit.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|