meshagent-livekit 0.5.18__py3-none-any.whl → 0.6.1__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/meeting_transcriber.py +294 -0
- meshagent/livekit/version.py +1 -1
- {meshagent_livekit-0.5.18.dist-info → meshagent_livekit-0.6.1.dist-info}/METADATA +9 -9
- meshagent_livekit-0.6.1.dist-info/RECORD +12 -0
- meshagent_livekit-0.5.18.dist-info/RECORD +0 -11
- {meshagent_livekit-0.5.18.dist-info → meshagent_livekit-0.6.1.dist-info}/WHEEL +0 -0
- {meshagent_livekit-0.5.18.dist-info → meshagent_livekit-0.6.1.dist-info}/licenses/LICENSE +0 -0
- {meshagent_livekit-0.5.18.dist-info → meshagent_livekit-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from livekit import rtc
|
|
8
|
+
from livekit.agents import (
|
|
9
|
+
Agent,
|
|
10
|
+
AgentSession,
|
|
11
|
+
RoomInputOptions,
|
|
12
|
+
RoomIO,
|
|
13
|
+
RoomOutputOptions,
|
|
14
|
+
StopResponse,
|
|
15
|
+
llm,
|
|
16
|
+
utils,
|
|
17
|
+
)
|
|
18
|
+
from livekit.plugins import openai, silero
|
|
19
|
+
from meshagent.api import MeshDocument, SchemaRegistration, SchemaRegistry
|
|
20
|
+
from meshagent.agents import SingleRoomAgent
|
|
21
|
+
from meshagent.tools import RemoteToolkit, ToolContext, Tool
|
|
22
|
+
from meshagent.api.room_server_client import Requirement
|
|
23
|
+
from meshagent.livekit.agents.voice import VoiceConnection
|
|
24
|
+
from meshagent.agents.schemas.transcript import transcript_schema
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("meeting_transcriber")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StartTranscriptionTool(Tool):
|
|
30
|
+
def __init__(self, *, transcriber: "MeetingTranscriber"):
|
|
31
|
+
self.transcriber = transcriber
|
|
32
|
+
super().__init__(
|
|
33
|
+
name="start_transcription",
|
|
34
|
+
input_schema={
|
|
35
|
+
"type": "object",
|
|
36
|
+
"required": [
|
|
37
|
+
"breakout_room",
|
|
38
|
+
"path",
|
|
39
|
+
],
|
|
40
|
+
"additionalProperties": False,
|
|
41
|
+
"properties": {
|
|
42
|
+
"breakout_room": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
},
|
|
45
|
+
"path": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
async def execute(self, context: ToolContext, *, breakout_room: str, path: str):
|
|
53
|
+
await self.transcriber.start_transcription(
|
|
54
|
+
breakout_room=breakout_room, path=path
|
|
55
|
+
)
|
|
56
|
+
return {"status": "started"}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class StopTranscriptionTool(Tool):
|
|
60
|
+
def __init__(self, *, transcriber: "MeetingTranscriber"):
|
|
61
|
+
self.transcriber = transcriber
|
|
62
|
+
super().__init__(
|
|
63
|
+
name="stop_transcription",
|
|
64
|
+
input_schema={
|
|
65
|
+
"type": "object",
|
|
66
|
+
"required": [
|
|
67
|
+
"breakout_room",
|
|
68
|
+
],
|
|
69
|
+
"additionalProperties": False,
|
|
70
|
+
"properties": {
|
|
71
|
+
"breakout_room": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def execute(self, context: ToolContext, *, breakout_room: str):
|
|
79
|
+
await self.transcriber.stop_transcription(
|
|
80
|
+
breakout_room=breakout_room,
|
|
81
|
+
)
|
|
82
|
+
return {"status": "stopped"}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class MeetingTranscriber(SingleRoomAgent):
|
|
86
|
+
def __init__(self, name: str, requires: Optional[list[Requirement]] = None):
|
|
87
|
+
super().__init__(
|
|
88
|
+
name=name,
|
|
89
|
+
requires=requires,
|
|
90
|
+
)
|
|
91
|
+
self._toolkit = RemoteToolkit(
|
|
92
|
+
name="transcription",
|
|
93
|
+
tools=[
|
|
94
|
+
StartTranscriptionTool(transcriber=self),
|
|
95
|
+
StopTranscriptionTool(transcriber=self),
|
|
96
|
+
],
|
|
97
|
+
)
|
|
98
|
+
self._vad = None
|
|
99
|
+
self._transcription_tasks = dict[str, tuple[asyncio.Task, asyncio.Future]]()
|
|
100
|
+
|
|
101
|
+
async def start(self, *, room):
|
|
102
|
+
await super().start(room=room)
|
|
103
|
+
await self._toolkit.start(room=room)
|
|
104
|
+
await room.local_participant.set_attribute("supports_voice", True)
|
|
105
|
+
await room.messaging.enable()
|
|
106
|
+
|
|
107
|
+
self._vad = silero.VAD.load()
|
|
108
|
+
|
|
109
|
+
async def start_transcription(self, *, breakout_room: Optional[str], path: str):
|
|
110
|
+
stop_fut = asyncio.Future()
|
|
111
|
+
|
|
112
|
+
async def transcribe():
|
|
113
|
+
await self.room.local_participant.set_attribute(
|
|
114
|
+
f"transcribing.{breakout_room}", True
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
async with VoiceConnection(
|
|
119
|
+
room=self.room, breakout_room=breakout_room
|
|
120
|
+
) as conn:
|
|
121
|
+
doc = await self.room.sync.open(path=path, create=True)
|
|
122
|
+
|
|
123
|
+
transcriber = MultiUserTranscriber(conn, doc, self._vad)
|
|
124
|
+
transcriber.start()
|
|
125
|
+
|
|
126
|
+
for participant in conn.livekit_room.remote_participants.values():
|
|
127
|
+
# handle all existing participants
|
|
128
|
+
transcriber.on_participant_connected(participant)
|
|
129
|
+
|
|
130
|
+
await stop_fut
|
|
131
|
+
|
|
132
|
+
await self.room.local_participant.set_attribute(
|
|
133
|
+
f"transcribing.{breakout_room}", False
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
await self.room.sync.close(path=path)
|
|
137
|
+
|
|
138
|
+
await transcriber.aclose()
|
|
139
|
+
except Exception as ex:
|
|
140
|
+
logger.error(f"error during transcription {ex}", exc_info=ex)
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
await self.room.local_participant.set_attribute(
|
|
144
|
+
f"transcribing.{breakout_room}", False
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if breakout_room not in self._transcription_tasks:
|
|
148
|
+
self._transcription_tasks[breakout_room] = (
|
|
149
|
+
asyncio.create_task(transcribe()),
|
|
150
|
+
stop_fut,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
async def stop_transcription(self, *, breakout_room: Optional[str]):
|
|
154
|
+
if breakout_room in self._transcription_tasks:
|
|
155
|
+
task, fut = self._transcription_tasks.pop(breakout_room)
|
|
156
|
+
fut.set_result(True)
|
|
157
|
+
await asyncio.gather(task)
|
|
158
|
+
|
|
159
|
+
async def stop(self):
|
|
160
|
+
await self._toolkit.stop()
|
|
161
|
+
|
|
162
|
+
tasks = []
|
|
163
|
+
for breakout_room, _ in self._transcription_tasks.items():
|
|
164
|
+
task, fut = self._transcription_tasks.pop(breakout_room)
|
|
165
|
+
fut.set_result(True)
|
|
166
|
+
tasks.append(task)
|
|
167
|
+
|
|
168
|
+
await asyncio.gather(*tasks)
|
|
169
|
+
await super().stop()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TranscriptRegistry(SchemaRegistry):
|
|
173
|
+
def __init__(self):
|
|
174
|
+
name = "transcript"
|
|
175
|
+
super().__init__(
|
|
176
|
+
name=f"meshagent.schema.{name}",
|
|
177
|
+
validate_webhook_secret=False,
|
|
178
|
+
schemas=[SchemaRegistration(name=name, schema=transcript_schema)],
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class Transcriber(Agent):
|
|
183
|
+
def __init__(self, *, participant, doc: MeshDocument):
|
|
184
|
+
super().__init__(
|
|
185
|
+
instructions="not-needed",
|
|
186
|
+
stt=openai.STT(),
|
|
187
|
+
)
|
|
188
|
+
self.doc = doc
|
|
189
|
+
self.participant = participant
|
|
190
|
+
|
|
191
|
+
async def on_user_turn_completed(
|
|
192
|
+
self, chat_ctx: llm.ChatContext, new_message: llm.ChatMessage
|
|
193
|
+
):
|
|
194
|
+
segments = self.doc.root
|
|
195
|
+
segments.append_child(
|
|
196
|
+
"segment",
|
|
197
|
+
{
|
|
198
|
+
"text": new_message.text_content,
|
|
199
|
+
"participant_name": self.participant.name,
|
|
200
|
+
"participant_id": self.participant.sid,
|
|
201
|
+
"time": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
raise StopResponse()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class MultiUserTranscriber:
|
|
208
|
+
def __init__(self, ctx: VoiceConnection, doc: MeshDocument, vad):
|
|
209
|
+
self.ctx = ctx
|
|
210
|
+
self.doc = doc
|
|
211
|
+
self.vad = vad
|
|
212
|
+
self._sessions: dict[str, AgentSession] = {}
|
|
213
|
+
self._tasks: set[asyncio.Task] = set()
|
|
214
|
+
|
|
215
|
+
def start(self):
|
|
216
|
+
self.ctx.livekit_room.on("participant_connected", self.on_participant_connected)
|
|
217
|
+
self.ctx.livekit_room.on(
|
|
218
|
+
"participant_disconnected", self.on_participant_disconnected
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
async def aclose(self):
|
|
222
|
+
await utils.aio.cancel_and_wait(*self._tasks)
|
|
223
|
+
|
|
224
|
+
await asyncio.gather(
|
|
225
|
+
*[self._close_session(session) for session in self._sessions.values()]
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
self.ctx.livekit_room.off(
|
|
229
|
+
"participant_connected", self.on_participant_connected
|
|
230
|
+
)
|
|
231
|
+
self.ctx.livekit_room.off(
|
|
232
|
+
"participant_disconnected", self.on_participant_disconnected
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def on_participant_connected(self, participant: rtc.RemoteParticipant):
|
|
236
|
+
if participant.identity in self._sessions:
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
logger.info(f"starting session for {participant.identity}")
|
|
240
|
+
task = asyncio.create_task(self._start_session(participant))
|
|
241
|
+
self._tasks.add(task)
|
|
242
|
+
|
|
243
|
+
def on_task_done(task: asyncio.Task):
|
|
244
|
+
try:
|
|
245
|
+
self._sessions[participant.identity] = task.result()
|
|
246
|
+
finally:
|
|
247
|
+
self._tasks.discard(task)
|
|
248
|
+
|
|
249
|
+
task.add_done_callback(on_task_done)
|
|
250
|
+
|
|
251
|
+
def on_participant_disconnected(self, participant: rtc.RemoteParticipant):
|
|
252
|
+
if (session := self._sessions.pop(participant.identity)) is None:
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
logger.info(f"closing session for {participant.identity}")
|
|
256
|
+
task = asyncio.create_task(self._close_session(session))
|
|
257
|
+
self._tasks.add(task)
|
|
258
|
+
task.add_done_callback(lambda _: self._tasks.discard(task))
|
|
259
|
+
|
|
260
|
+
async def _start_session(self, participant: rtc.RemoteParticipant) -> AgentSession:
|
|
261
|
+
if participant.identity in self._sessions:
|
|
262
|
+
return self._sessions[participant.identity]
|
|
263
|
+
|
|
264
|
+
session = AgentSession(
|
|
265
|
+
vad=self.vad,
|
|
266
|
+
)
|
|
267
|
+
room_io = RoomIO(
|
|
268
|
+
agent_session=session,
|
|
269
|
+
room=self.ctx.livekit_room,
|
|
270
|
+
participant=participant,
|
|
271
|
+
input_options=RoomInputOptions(
|
|
272
|
+
# text input is not supported for multiple room participants
|
|
273
|
+
# if needed, register the text stream handler by yourself
|
|
274
|
+
# and route the text to different sessions based on the participant identity
|
|
275
|
+
text_enabled=False,
|
|
276
|
+
delete_room_on_close=False,
|
|
277
|
+
),
|
|
278
|
+
output_options=RoomOutputOptions(
|
|
279
|
+
transcription_enabled=True,
|
|
280
|
+
audio_enabled=False,
|
|
281
|
+
),
|
|
282
|
+
)
|
|
283
|
+
await room_io.start()
|
|
284
|
+
await session.start(
|
|
285
|
+
agent=Transcriber(
|
|
286
|
+
participant=participant,
|
|
287
|
+
doc=self.doc,
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
return session
|
|
291
|
+
|
|
292
|
+
async def _close_session(self, sess: AgentSession) -> None:
|
|
293
|
+
await sess.drain()
|
|
294
|
+
await sess.aclose()
|
meshagent/livekit/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.6.1"
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshagent-livekit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Livekit support for Meshagent
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Documentation, https://docs.meshagent.com
|
|
7
7
|
Project-URL: Website, https://www.meshagent.com
|
|
8
8
|
Project-URL: Source, https://www.meshagent.com
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
12
|
Requires-Dist: pytest~=8.4
|
|
13
13
|
Requires-Dist: pytest-asyncio~=0.26
|
|
14
14
|
Requires-Dist: strip-markdown~=1.3
|
|
15
15
|
Requires-Dist: livekit-api>=1.0
|
|
16
|
-
Requires-Dist: livekit-agents~=1.
|
|
17
|
-
Requires-Dist: livekit-plugins-openai~=1.
|
|
18
|
-
Requires-Dist: livekit-plugins-silero~=1.
|
|
19
|
-
Requires-Dist: livekit-plugins-turn-detector~=1.
|
|
20
|
-
Requires-Dist: meshagent-api~=0.
|
|
21
|
-
Requires-Dist: meshagent-tools~=0.
|
|
16
|
+
Requires-Dist: livekit-agents~=1.2
|
|
17
|
+
Requires-Dist: livekit-plugins-openai~=1.2
|
|
18
|
+
Requires-Dist: livekit-plugins-silero~=1.2
|
|
19
|
+
Requires-Dist: livekit-plugins-turn-detector~=1.2
|
|
20
|
+
Requires-Dist: meshagent-api~=0.6.1
|
|
21
|
+
Requires-Dist: meshagent-tools~=0.6.1
|
|
22
22
|
Dynamic: license-file
|
|
23
23
|
|
|
24
24
|
# [Meshagent](https://www.meshagent.com)
|
|
@@ -27,7 +27,7 @@ Dynamic: license-file
|
|
|
27
27
|
The ``meshagent.livekit`` package equips agents with real-time audio and voice capabilities via the LiveKit SDK.
|
|
28
28
|
|
|
29
29
|
### VoiceBot
|
|
30
|
-
The ``VoiceBot`` agent handles two-way voice conversations allowing users to interact with the agent verbally. Agents based on the ``VoiceBot`` class can be given the same tools as ``ChatBot`` based agents. This means you only need to write a tool once and the same tool can be used across both text and voice based agents. Check out the [Build
|
|
30
|
+
The ``VoiceBot`` agent handles two-way voice conversations allowing users to interact with the agent verbally. Agents based on the ``VoiceBot`` class can be given the same tools as ``ChatBot`` based agents. This means you only need to write a tool once and the same tool can be used across both text and voice based agents. Check out the [Build a Voice Agent](https://docs.meshagent.com/agents/standard/buildanddeployvoicebot) example to learn how to create a simple Voice Agent without tools then add built-in MeshAgent tools and custom tools to the agent.
|
|
31
31
|
|
|
32
32
|
---
|
|
33
33
|
### Learn more about MeshAgent on our website or check out the docs for additional examples!
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
meshagent/livekit/__init__.py,sha256=X78Z4yEg5XfkNKH0HiIdG4k1q5ktB-ampTuXHLNFrAw,58
|
|
2
|
+
meshagent/livekit/livekit_protocol.py,sha256=5Zu4ymLWEGt5SGXLNu94gOeyjnjhaV6uTS2FhSdODqs,1470
|
|
3
|
+
meshagent/livekit/livekit_protocol_test.py,sha256=o7yYxXad4tMazcxFkq44yW-A9tJ0Lk6WdZpG5ifxcU4,2980
|
|
4
|
+
meshagent/livekit/version.py,sha256=baAcEjLSYFIeNZF51tOMmA_zAMhN8HvKael-UU-Ruec,22
|
|
5
|
+
meshagent/livekit/agents/meeting_transcriber.py,sha256=_DHFCuOGCE3LBBBDoF1xGTBMHXaVm2yzqya-OO4g6xg,9778
|
|
6
|
+
meshagent/livekit/agents/transcriber.py,sha256=S992oVVBt3ShWDQQWprLjyl6Yh0hyNRd8d3qCmg_toU,5795
|
|
7
|
+
meshagent/livekit/agents/voice.py,sha256=STgjMSqzUgV9UAmleOy1vkgRXP93MDSYgiOO6Lo0peU,11964
|
|
8
|
+
meshagent_livekit-0.6.1.dist-info/licenses/LICENSE,sha256=eTt0SPW-sVNdkZe9PS_S8WfCIyLjRXRl7sUBWdlteFg,10254
|
|
9
|
+
meshagent_livekit-0.6.1.dist-info/METADATA,sha256=_z2iTjyR9cJEo2Y_6IMJGSD5kKs6-cm7Cf23Qb7b2wo,1749
|
|
10
|
+
meshagent_livekit-0.6.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
meshagent_livekit-0.6.1.dist-info/top_level.txt,sha256=GlcXnHtRP6m7zlG3Df04M35OsHtNXy_DY09oFwWrH74,10
|
|
12
|
+
meshagent_livekit-0.6.1.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
meshagent/livekit/__init__.py,sha256=X78Z4yEg5XfkNKH0HiIdG4k1q5ktB-ampTuXHLNFrAw,58
|
|
2
|
-
meshagent/livekit/livekit_protocol.py,sha256=5Zu4ymLWEGt5SGXLNu94gOeyjnjhaV6uTS2FhSdODqs,1470
|
|
3
|
-
meshagent/livekit/livekit_protocol_test.py,sha256=o7yYxXad4tMazcxFkq44yW-A9tJ0Lk6WdZpG5ifxcU4,2980
|
|
4
|
-
meshagent/livekit/version.py,sha256=8HarzfUUN0oBs56RqaWFFfXKWTGi5Mt2jU6YSekUkis,23
|
|
5
|
-
meshagent/livekit/agents/transcriber.py,sha256=S992oVVBt3ShWDQQWprLjyl6Yh0hyNRd8d3qCmg_toU,5795
|
|
6
|
-
meshagent/livekit/agents/voice.py,sha256=STgjMSqzUgV9UAmleOy1vkgRXP93MDSYgiOO6Lo0peU,11964
|
|
7
|
-
meshagent_livekit-0.5.18.dist-info/licenses/LICENSE,sha256=eTt0SPW-sVNdkZe9PS_S8WfCIyLjRXRl7sUBWdlteFg,10254
|
|
8
|
-
meshagent_livekit-0.5.18.dist-info/METADATA,sha256=pOGhINg_FthW2_8GHEUdunQm3Yn6kW-pTmBQIiowwmY,1763
|
|
9
|
-
meshagent_livekit-0.5.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
-
meshagent_livekit-0.5.18.dist-info/top_level.txt,sha256=GlcXnHtRP6m7zlG3Df04M35OsHtNXy_DY09oFwWrH74,10
|
|
11
|
-
meshagent_livekit-0.5.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|