meshagent-agents 0.0.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-agents might be problematic. Click here for more details.

@@ -0,0 +1,5 @@
1
+ from .agent import Agent, AgentCallContext, AgentChatContext, RequiredToolkit, TaskRunner, SingleRoomAgent
2
+ from .development import connect_development_agent
3
+ from .listener import Listener, ListenerContext
4
+ from .hosting import RemoteTaskRunnerServer
5
+ from .adapter import ToolResponseAdapter, LLMAdapter
@@ -0,0 +1,39 @@
1
+ from abc import ABC, abstractmethod
2
+ from .agent import AgentChatContext
3
+ from jsonschema import validate
4
+ from meshagent.tools.toolkit import Response, Toolkit
5
+ from meshagent.api import RoomClient
6
+ from typing import Any, Optional
7
+
8
+ class ToolResponseAdapter(ABC):
9
+ def __init__(self):
10
+ pass
11
+
12
+ @abstractmethod
13
+ async def to_plain_text(self, *, room: RoomClient, response: Response):
14
+ pass
15
+
16
+ @abstractmethod
17
+ async def append_messages(self, *, context: AgentChatContext, tool_call: Any, room: RoomClient, response: Response):
18
+ pass
19
+
20
+
21
+ class LLMAdapter(ABC):
22
+
23
+ def create_chat_context(self) -> AgentChatContext:
24
+ return AgentChatContext()
25
+
26
+ @abstractmethod
27
+ async def next(self,
28
+ *,
29
+ context: AgentChatContext,
30
+ room: RoomClient,
31
+ toolkits: Toolkit,
32
+ tool_adapter: Optional[ToolResponseAdapter] = None,
33
+ output_schema: Optional[dict] = None,
34
+ ) -> Any:
35
+ pass
36
+
37
+ def validate(response: dict, output_schema: dict):
38
+ validate(response, output_schema)
39
+
@@ -0,0 +1,427 @@
1
+ import json
2
+ from copy import deepcopy
3
+
4
+ from typing import Optional
5
+
6
+ from meshagent.api.room_server_client import RoomException, RequiredToolkit, Requirement, RequiredSchema
7
+ from meshagent.api import WebSocketClientProtocol, ToolDescription, ToolkitDescription, Participant, RemoteParticipant, meshagent_base_url, StorageEntry
8
+ from meshagent.api.protocol import Protocol
9
+ from meshagent.tools.toolkit import Toolkit, Tool, ToolContext
10
+ from meshagent.api.room_server_client import RoomClient, RoomException
11
+ from meshagent.api.schema_document import Document
12
+ from jsonschema import validate
13
+ from typing import Callable, Awaitable
14
+ from .context import AgentCallContext, AgentChatContext
15
+ from .schema import no_arguments_schema
16
+ import logging
17
+ import asyncio
18
+ from typing import Optional
19
+
20
+ logger = logging.getLogger("agent")
21
+ logger.setLevel(logging.INFO)
22
+
23
+ class AgentException(RoomException):
24
+ pass
25
+
26
+
27
+ class RoomTool(Tool):
28
+ def __init__(self, *, toolkit_name: str, name, input_schema, title = None, description = None, rules = None, thumbnail_url = None, participant_id: Optional[str] = None, on_behalf_of_id: Optional[str] = None, defs: Optional[dict] = None):
29
+ self._toolkit_name = toolkit_name
30
+ self._participant_id = participant_id
31
+ self._on_behalf_of_id = on_behalf_of_id
32
+
33
+ super().__init__(name=name, input_schema=input_schema, title=title, description=description, rules=rules, thumbnail_url=thumbnail_url, defs=defs)
34
+
35
+ async def execute(self, context, **kwargs):
36
+ return await context.room.agents.invoke_tool(
37
+ toolkit=self._toolkit_name,
38
+ tool=self.name,
39
+ participant_id=self._participant_id,
40
+ on_behalf_of_id=self._on_behalf_of_id,
41
+ arguments=kwargs,
42
+
43
+ )
44
+
45
+ class Agent:
46
+
47
+ def __init__(self, *, name: str, title: Optional[str] = None, description: Optional[str] = None, requires: Optional[list[Requirement]] = None, labels: Optional[list[str]] = None):
48
+ self._name = name
49
+ if title == None:
50
+ title = name
51
+ self._title = title
52
+ if description == None:
53
+ description = ""
54
+
55
+ self._description = description
56
+ if requires == None:
57
+ requires = []
58
+ self._requires = requires
59
+
60
+ if labels == None:
61
+ labels = []
62
+
63
+ self._labels = labels
64
+
65
+ @property
66
+ def name(self):
67
+ return self._name
68
+
69
+ @property
70
+ def description(self):
71
+ return self._description
72
+
73
+ @property
74
+ def title(self):
75
+ return self._title
76
+
77
+ @property
78
+ def requires(self):
79
+ return self._requires
80
+
81
+ @property
82
+ def labels(self):
83
+ return self._labels
84
+
85
+ async def init_chat_context(self) -> AgentChatContext:
86
+ return AgentChatContext()
87
+
88
+ def to_json(self) -> dict:
89
+ return {
90
+ "name" : self.name,
91
+ "title" : self.title,
92
+ "description" : self.description,
93
+ "requires" : list(map(lambda x: x.to_json(), self.requires)),
94
+ "labels" : self.labels
95
+ }
96
+
97
+ class SingleRoomAgent(Agent):
98
+
99
+ def __init__(self, *, name, title = None, description = None, requires = None, labels: Optional[list[str]] = None):
100
+ super().__init__(name=name, title=title, description=description, requires=requires, labels=labels)
101
+ self._room = None
102
+
103
+ async def start(self, *, room: RoomClient) -> None:
104
+
105
+ if self._room != None:
106
+ raise RoomException("room is already started")
107
+
108
+ self._room = room
109
+
110
+ async def stop(self) -> None:
111
+ self._room = None
112
+ pass
113
+
114
+ @property
115
+ def room(self):
116
+ return self._room
117
+
118
+ async def install_requirements(self, participant_id: Optional[str] = None):
119
+
120
+ schemas_by_name = dict[str, StorageEntry]()
121
+
122
+ schemas = await self._room.storage.list(path=".schemas")
123
+
124
+
125
+ for schema in schemas:
126
+ schemas_by_name[schema.name] = schema
127
+
128
+ toolkits_by_name = dict[str, ToolkitDescription]()
129
+
130
+ visible_tools = await self._room.agents.list_toolkits(participant_id=participant_id)
131
+
132
+ for toolkit_description in visible_tools:
133
+ toolkits_by_name[toolkit_description.name] = toolkit_description
134
+
135
+ installed = False
136
+
137
+ for requirement in self.requires:
138
+
139
+ if isinstance(requirement, RequiredToolkit):
140
+ if requirement.name not in toolkits_by_name:
141
+
142
+ installed = True
143
+
144
+ logger.info(f"Installing required tool {requirement.name}")
145
+
146
+ if requirement.name.startswith("https://"):
147
+ url = requirement.name
148
+ else:
149
+ url = f"{meshagent_base_url()}/toolkits/{requirement.name}"
150
+
151
+ await self._room.agents.make_call(url=url, name=requirement.name, arguments={})
152
+
153
+ elif isinstance(requirement, RequiredSchema):
154
+
155
+ if requirement.name not in schemas_by_name:
156
+
157
+ installed = True
158
+
159
+ logger.info(f"Installing required tool {requirement.name}")
160
+
161
+ if requirement.name.startswith("https://"):
162
+ url = requirement.name
163
+ else:
164
+ url = f"{meshagent_base_url()}/schemas/{requirement.name}"
165
+
166
+ await self._room.agents.make_call(url=url, name=requirement.name, arguments={})
167
+
168
+ else:
169
+ raise RoomException("unsupported requirement")
170
+
171
+ if installed:
172
+ await asyncio.sleep(5)
173
+
174
+ async def get_required_tools(self, participant_id: str) -> list[Toolkit]:
175
+
176
+ toolkits_by_name = dict[str, ToolkitDescription]()
177
+
178
+ toolkits = list[Toolkit]()
179
+
180
+ visible_tools = await self._room.agents.list_toolkits(participant_id=participant_id)
181
+
182
+ for toolkit_description in visible_tools:
183
+ toolkits_by_name[toolkit_description.name] = toolkit_description
184
+
185
+ for required_toolkit in self.requires:
186
+
187
+ if isinstance(required_toolkit, RequiredToolkit):
188
+
189
+ toolkit = toolkits_by_name.get(required_toolkit.name, None)
190
+ if toolkit == None:
191
+ raise RoomException(f"unable to locate required toolkit {required_toolkit.name}")
192
+
193
+ room_tools = list[RoomTool]()
194
+
195
+
196
+ if required_toolkit.tools == None:
197
+
198
+ for tool_description in toolkit.tools:
199
+ tool = RoomTool(
200
+ on_behalf_of_id=participant_id,
201
+ toolkit_name=toolkit.name,
202
+ name=tool_description.name,
203
+ description=tool_description.description,
204
+ input_schema=tool_description.input_schema,
205
+ title=tool_description.title,
206
+ thumbnail_url=tool_description.thumbnail_url,
207
+ participant_id=participant_id,
208
+ defs = tool_description.defs
209
+ )
210
+ room_tools.append(tool)
211
+
212
+ else:
213
+ tools_by_name = dict[str, ToolDescription]()
214
+ for tool_description in toolkit.tools:
215
+ tools_by_name[tool_description.name] = tool_description
216
+
217
+ for required_tool in required_toolkit.tools:
218
+
219
+ tool_description = tools_by_name.get(required_tool, None)
220
+ if tool_description == None:
221
+ raise RoomException(f"unable to locate required tool {required_tool}")
222
+
223
+ tool = RoomTool(
224
+ on_behalf_of_id=participant_id,
225
+ toolkit_name=toolkit.name,
226
+ name=tool_description.name,
227
+ description=tool_description.description,
228
+ input_schema=tool_description.input_schema,
229
+ title=tool_description.title,
230
+ thumbnail_url=tool_description.thumbnail_url,
231
+ participant_id=participant_id,
232
+ defs = tool_description.defs
233
+ )
234
+ room_tools.append(tool)
235
+
236
+ toolkits.append(Toolkit(
237
+ name = toolkit.name,
238
+ title = toolkit.title,
239
+ description = toolkit.description,
240
+ thumbnail_url = toolkit.thumbnail_url,
241
+ tools = room_tools,
242
+ ))
243
+
244
+ return toolkits
245
+
246
+ class TaskRunner(SingleRoomAgent):
247
+
248
+ def __init__(self, *, name, title = None, description = None, requires = None, supports_tools : Optional[bool] = None, input_schema: dict, output_schema: dict, labels: Optional[list[str]] = None):
249
+ super().__init__(name=name, title=title, description=description, requires=requires, labels=labels)
250
+
251
+ self._registration_id = None
252
+
253
+ if input_schema == None:
254
+ input_schema = no_arguments_schema(
255
+ description="execute the agent",
256
+ )
257
+
258
+ if supports_tools == None:
259
+ supports_tools = False
260
+
261
+ self._supports_tools = supports_tools
262
+ self._input_schema = input_schema
263
+ self._output_schema = output_schema
264
+
265
+
266
+
267
+ async def validate_arguments(self, arguments: dict):
268
+ validate(arguments, self.input_schema)
269
+
270
+ async def validate_response(self, response: dict):
271
+ if self.output_schema != None:
272
+ validate(response, self.output_schema)
273
+
274
+ async def ask(self, *, context: AgentCallContext, arguments: dict) -> dict:
275
+ raise Exception("Not implemented")
276
+
277
+ @property
278
+ def supports_tools(self):
279
+ return self._supports_tools
280
+
281
+ @property
282
+ def input_schema(self):
283
+ return self._input_schema
284
+
285
+ @property
286
+ def output_schema(self):
287
+ return self._output_schema
288
+
289
+ def to_json(self) -> dict:
290
+ return {
291
+ "name" : self.name,
292
+ "title" : self.title,
293
+ "description" : self.description,
294
+ "input_schema" : self.input_schema,
295
+ "output_schema" : self.output_schema,
296
+ "requires" : list(map(lambda x: x.to_json(), self.requires)),
297
+ "supports_tools" : self.supports_tools,
298
+ "labels" : self.labels
299
+ }
300
+
301
+ async def _register(self):
302
+ self._registration_id = (await self._room.send_request("agent.register_agent", {
303
+ "name": self.name,
304
+ "title" : self.title,
305
+ "description" : self.description,
306
+ "input_schema" : self.input_schema,
307
+ "output_schema" : self.output_schema,
308
+ "requires" : list(map(lambda x : x.to_json(), self.requires)),
309
+ "supports_tools" : self.supports_tools,
310
+ "labels" : self.labels
311
+ }))["id"]
312
+
313
+
314
+ async def _unregister(self):
315
+ await self._room.send_request("agent.unregister_agent", {
316
+ "id": self._registration_id
317
+ })
318
+ self._registration_id = None
319
+
320
+
321
+ async def start(self, *, room: RoomClient):
322
+
323
+ await super().start(room=room)
324
+
325
+ self._room.protocol.register_handler("agent.ask", self._ask)
326
+ await self._register()
327
+
328
+
329
+ async def stop(self):
330
+ if self.room.protocol.is_open:
331
+ await self._unregister()
332
+ else:
333
+ logger.info(f"disconnected '{self.name}' from room, this will automatically happen when all the users leave the room. agents will not keep the room open")
334
+
335
+ self._room.protocol.unregister_handler("agent.ask", self._ask)
336
+
337
+ await super().stop()
338
+
339
+ async def _ask(self, protocol:Protocol, message_id:int, msg_type:str, data:bytes):
340
+
341
+
342
+ async def worker():
343
+ # Decode and parse the message
344
+ message = json.loads(data.decode('utf-8'))
345
+ logger.info("got message %s", message)
346
+ args = message["arguments"]
347
+ task_id = message["task_id"]
348
+ context_json = message["context"]
349
+ toolkits_json = message["toolkits"]
350
+
351
+
352
+ #context_json = message["context"]
353
+
354
+ try:
355
+ chat_context = await self.init_chat_context()
356
+
357
+ caller : Participant | None = None
358
+
359
+ for participant in self._room.messaging.get_participants():
360
+ if message["caller_id"] == participant.id:
361
+ caller = participant
362
+ break
363
+
364
+ if caller == None:
365
+ caller = RemoteParticipant(
366
+ id=message["caller_id"],
367
+ role="user",
368
+ attributes={}
369
+ )
370
+
371
+
372
+ await self.install_requirements(participant_id=caller.id)
373
+
374
+
375
+ context = AgentCallContext(chat=chat_context, room=self.room, caller=caller)
376
+
377
+ for toolkit_json in toolkits_json:
378
+ tools = []
379
+ for tool_json in toolkit_json["tools"]:
380
+ tools.append(RoomTool(
381
+ on_behalf_of_id=message["caller_id"],
382
+ participant_id=message["caller_id"],
383
+ toolkit_name=toolkit_json["name"],
384
+ name=tool_json["name"],
385
+ title=tool_json["title"],
386
+ description=tool_json["description"],
387
+ input_schema=tool_json["input_schema"],
388
+ thumbnail_url=toolkit_json["thumbnail_url"],
389
+ defs=tool_json.get("defs", None)
390
+ ))
391
+
392
+ context.toolkits.append(Toolkit(
393
+ name=toolkit_json["name"],
394
+ title=toolkit_json["title"],
395
+ description=toolkit_json["description"],
396
+ thumbnail_url=toolkit_json["thumbnail_url"],
397
+ tools=tools,
398
+ ))
399
+
400
+ response = await self.ask(context=context, arguments=args)
401
+
402
+ await protocol.send(type="agent.ask_response", data=json.dumps({
403
+ "task_id" : task_id,
404
+ "response" : response,
405
+ }))
406
+
407
+ except Exception as e:
408
+ logger.error("Task runner failed to complete task", exc_info=e)
409
+ await protocol.send(type="agent.ask_response", data=json.dumps({
410
+ "task_id" : task_id,
411
+ "error" : str(e),
412
+ }))
413
+
414
+ def on_done(task: asyncio.Task):
415
+ task.result()
416
+
417
+ task = asyncio.create_task(worker())
418
+ task.add_done_callback(on_done)
419
+
420
+ class RunTaskTool(Tool):
421
+ def __init__(self, *, agent_name: str, input_schema: dict, rules = None, thumbnail_url = None):
422
+ super().__init__(name=agent_name, input_schema=input_schema, rules=rules, thumbnail_url=thumbnail_url)
423
+
424
+ self._agent_name = agent_name
425
+
426
+ async def execute(self, context: ToolContext, **kwargs):
427
+ return await context.room.agents.ask(agent=self._agent_name, arguments=kwargs)