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,316 @@
1
+ from .agent import SingleRoomAgent, AgentChatContext
2
+ from meshagent.api.chan import Chan
3
+ from meshagent.api import RoomMessage, RoomException, RoomClient, RemoteParticipant
4
+ from meshagent.tools import Toolkit
5
+ from .adapter import LLMAdapter, ToolResponseAdapter
6
+ import asyncio
7
+ from typing import Optional
8
+ import logging
9
+ from meshagent.tools import MultiToolkit
10
+ import urllib
11
+
12
+ logging.basicConfig()
13
+ logger = logging.getLogger("chat")
14
+ logger.setLevel(logging.INFO)
15
+
16
+
17
+ # todo: thread should stop when participant stops?
18
+
19
+ class ChatBot(SingleRoomAgent):
20
+ def __init__(self, *, name, title = None, description = None, requires = None, llm_adapter: LLMAdapter, tool_adapter: Optional[ToolResponseAdapter] = None, toolkits: Optional[list[Toolkit]] = None, rules : Optional[list[str]] = None, auto_greet_prompt : Optional[str] = None, auto_greet_message : Optional[str] = None, empty_state_title : Optional[str] = None, labels: Optional[str] = None):
21
+ super().__init__(
22
+ name=name,
23
+ title=title,
24
+ description=description,
25
+ requires=requires,
26
+ labels=labels
27
+ )
28
+
29
+ if toolkits == None:
30
+ toolkits = []
31
+
32
+ self._llm_adapter = llm_adapter
33
+ self._tool_adapter = tool_adapter
34
+
35
+ self._message_channels = dict[str, Chan[RoomMessage]]()
36
+
37
+ self._room : RoomClient | None = None
38
+ self._toolkits = toolkits
39
+
40
+ if rules == None:
41
+ rules = []
42
+
43
+ self._rules = rules
44
+ self._is_typing = dict[str,asyncio.Task]()
45
+ self._auto_greet_prompt = auto_greet_prompt
46
+ self._auto_greet_message = auto_greet_message
47
+
48
+ if empty_state_title == None:
49
+ empty_state_title = "How can I help you?"
50
+ self._empty_state_title = empty_state_title
51
+
52
+
53
+ async def greet(self, *, chat_context: AgentChatContext, messages: Chan[RoomMessage], participant: RemoteParticipant):
54
+
55
+ if self._auto_greet_prompt != None:
56
+ messages.send_nowait(RoomMessage(from_participant_id=participant.id, type="chat", message={"text": self._auto_greet_prompt }))
57
+
58
+ if self._auto_greet_message != None:
59
+ chat_context.append_user_message(self._auto_greet_message)
60
+
61
+ await self.room.messaging.send_message(to=RemoteParticipant(id=participant.id), type="chat", message={ "text" : self._auto_greet_message })
62
+
63
+
64
+ async def finalize_toolkits(self, toolkits) -> list[Toolkit]:
65
+
66
+ toaster = None
67
+
68
+ for toolkit in toolkits:
69
+
70
+ if toolkit.name == "meshagent.ui":
71
+
72
+ for tool in toolkit.tools:
73
+
74
+ if tool.name == "show_toast":
75
+
76
+ toaster = tool
77
+
78
+ if toaster != None:
79
+
80
+ def multi_tool(toolkit: Toolkit):
81
+ if toaster in toolkit.tools:
82
+ return toolkit
83
+
84
+ return MultiToolkit(required=[ toaster ], base_toolkit=toolkit )
85
+
86
+ toolkits = list(map(multi_tool, toolkits))
87
+
88
+ return toolkits
89
+
90
+ async def _spawn_thread(self, participant_id: str, messages: Chan[RoomMessage]):
91
+
92
+
93
+ chat_context = await self.init_chat_context()
94
+
95
+
96
+ chat_context.append_rules(
97
+ rules=[
98
+ *self._rules,
99
+ "think step by step",
100
+ ]
101
+ )
102
+
103
+ opened = False
104
+ chat_with_participant = None
105
+
106
+ for participant in self._room.messaging.get_participants():
107
+ if participant.id == participant_id:
108
+ chat_with_participant = participant
109
+ break
110
+
111
+ if chat_with_participant == None:
112
+ raise RoomException(f"caller did not have messaging turned on")
113
+
114
+ messaging = self._room.messaging
115
+
116
+ current_file = None
117
+
118
+ step_schema = {
119
+ "type" : "object",
120
+ "required" : ["text","finished"],
121
+ "additionalProperties" : False,
122
+ "description" : "execute a step",
123
+ "properties" : {
124
+ "text" : {
125
+ "description" : "a reply to the user or status to display during an intermediate step",
126
+ "type" : "string"
127
+ },
128
+ "finished" : {
129
+ "description" : "whether the agent has finished answering the user's last message. you MUST set this to true if there are no more tool calls to be made or you are stuck in a loop.",
130
+ "type" : "boolean"
131
+ }
132
+ }
133
+ }
134
+
135
+ installed = False
136
+
137
+ while True:
138
+
139
+ while True:
140
+
141
+
142
+
143
+ received = await messages.recv()
144
+
145
+
146
+
147
+ if current_file != chat_with_participant.get_attribute("current_file"):
148
+ logger.info(f"participant is now looking at {chat_with_participant.get_attribute("current_file")}")
149
+ current_file = chat_with_participant.get_attribute("current_file")
150
+
151
+ if current_file != None:
152
+ chat_context.append_assistant_message(message=f"the user is currently viewing the file at the path: {current_file}")
153
+
154
+ elif current_file != None:
155
+ chat_context.append_assistant_message(message=f"the user is not current viewing any files")
156
+
157
+
158
+
159
+ if installed == False:
160
+ installed = True
161
+ await self.install_requirements(participant_id=participant_id)
162
+
163
+
164
+ if received.type == "opened":
165
+
166
+
167
+ if opened == False:
168
+
169
+ opened = True
170
+
171
+ await self.greet(chat_context=chat_context, participant=chat_with_participant, messages=messages)
172
+
173
+ if received.type == "chat":
174
+
175
+
176
+ await self._room.messaging.send_message(to=chat_with_participant, type="thinking", message={"thinking":True})
177
+
178
+ if chat_with_participant.id == received.from_participant_id:
179
+ self.room.developer.log_nowait(type="llm.message", data={ "context" : chat_context.id, "participant_id" : self.room.local_participant.id, "participant_name" : self.room.local_participant.get_attribute("name"), "message" : { "content" : { "role" : "user", "text" : received.message["text"] } } })
180
+
181
+ text = received.message["text"]
182
+ attachments = received.message.get("attachments", [])
183
+
184
+ for attachment in attachments:
185
+
186
+ chat_context.append_assistant_message(message=f"the user attached a file '{attachment["filename"]}' with the content: '{attachment["content"]}'")
187
+
188
+
189
+ chat_context.append_user_message(message=text)
190
+
191
+
192
+ # if user is typing, wait for typing to stop
193
+ while True:
194
+
195
+ if chat_with_participant.id not in self._is_typing:
196
+ break
197
+
198
+ await asyncio.sleep(.5)
199
+
200
+ if messages.empty() == True:
201
+ break
202
+
203
+
204
+ try:
205
+ while True:
206
+
207
+ toolkits = [
208
+ *self._toolkits,
209
+ *await self.get_required_tools(participant_id=chat_with_participant.id)
210
+ ]
211
+
212
+ toolkits = await self.finalize_toolkits(toolkits)
213
+
214
+ response = await self._llm_adapter.next(
215
+ context=chat_context,
216
+ room=self._room,
217
+ toolkits=toolkits,
218
+ tool_adapter=self._tool_adapter,
219
+ output_schema=step_schema,
220
+ )
221
+
222
+ text = response["text"]
223
+
224
+
225
+ if response["finished"] or len(toolkits) == 0:
226
+ await self._room.messaging.send_message(
227
+ to=chat_with_participant,
228
+ type="chat",
229
+ message={
230
+ "text": text
231
+ }
232
+ )
233
+ break
234
+ else:
235
+ await self._room.messaging.send_message(
236
+ to=chat_with_participant,
237
+ type="status",
238
+ message={
239
+ "text": text
240
+ }
241
+ )
242
+ chat_context.append_user_message(message="proceed to the next step if you are ready")
243
+
244
+ finally:
245
+
246
+ await self._room.messaging.send_message(to=chat_with_participant, type="thinking", message={"thinking":False})
247
+
248
+
249
+
250
+ def _get_message_channel(self, participant_id: str) -> Chan[RoomMessage]:
251
+ if participant_id not in self._message_channels:
252
+ chan = Chan[RoomMessage]()
253
+ self._message_channels[participant_id] = chan
254
+
255
+ def thread_done(task: asyncio.Task):
256
+
257
+ self._message_channels.pop(participant_id)
258
+ try:
259
+ task.result()
260
+ logger.info("ending chat thread")
261
+ except Exception as e:
262
+ logger.error("chat thread error", exc_info=e)
263
+
264
+ task = asyncio.create_task(self._spawn_thread(participant_id=participant_id, messages=chan))
265
+ task.add_done_callback(thread_done)
266
+
267
+ chan = self._message_channels[participant_id]
268
+
269
+ return chan
270
+
271
+ async def start(self, *, room):
272
+
273
+ await super().start(room=room)
274
+
275
+
276
+ await self.room.local_participant.set_attribute("empty_state_title", self._empty_state_title)
277
+
278
+ def on_message(message: RoomMessage):
279
+ messages = self._get_message_channel(participant_id=message.from_participant_id)
280
+ if message.type == "chat" or message.type == "opened":
281
+ messages.send_nowait(message)
282
+
283
+ elif message.type == "typing":
284
+
285
+
286
+ def callback(task: asyncio.Task):
287
+ try:
288
+ task.result()
289
+ except:
290
+ pass
291
+
292
+ async def remove_timeout(id: str):
293
+ await asyncio.sleep(1)
294
+ self._is_typing.pop(id)
295
+
296
+ if message.from_participant_id in self._is_typing:
297
+ self._is_typing[message.from_participant_id].cancel()
298
+
299
+ timeout = asyncio.create_task(remove_timeout(id=message.from_participant_id))
300
+ timeout.add_done_callback(callback)
301
+
302
+ self._is_typing[message.from_participant_id] = timeout
303
+
304
+ room.messaging.on("message", on_message)
305
+
306
+ if self._auto_greet_prompt != None or self._auto_greet_message != None:
307
+ def on_participant_added(participant:RemoteParticipant):
308
+
309
+ # will spawn the initial thread
310
+ self._get_message_channel(participant_id=participant.id)
311
+
312
+
313
+ room.messaging.on("participant_added", on_participant_added)
314
+
315
+ await room.messaging.enable()
316
+
@@ -0,0 +1,90 @@
1
+
2
+ from typing import Optional
3
+ from copy import deepcopy
4
+ from meshagent.api import RoomClient
5
+ from meshagent.tools.toolkit import Toolkit
6
+ from meshagent.api.participant import Participant
7
+
8
+ import uuid
9
+
10
+ class AgentChatContext:
11
+ def __init__(self, *, messages: Optional[list[dict]] = None, system_role: Optional[str] = None):
12
+ self.id = str(uuid.uuid4())
13
+ if messages == None:
14
+ messages = list[dict]()
15
+ self._messages = messages.copy()
16
+ if system_role == None:
17
+ system_role = "system"
18
+ self._system_role = system_role
19
+
20
+ @property
21
+ def messages(self):
22
+ return self._messages
23
+
24
+ @property
25
+ def system_role(self):
26
+ return self._system_role
27
+
28
+ def append_rules(self, rules: list[str]):
29
+
30
+ system_message = None
31
+
32
+ for m in self.messages:
33
+ if m["role"] == self.system_role:
34
+ system_message = m
35
+ break
36
+
37
+ if system_message == None:
38
+ system_message = {"role": self.system_role, "content" : ""}
39
+ self.messages.insert(0, system_message)
40
+
41
+ plan = f"Rules:\n-{'\n-'.join(rules)}\n"
42
+ system_message["content"] = system_message["content"] + plan
43
+
44
+ def append_assistant_message(self, message: str) -> None:
45
+ self.messages.append({ "role" : "assistant", "content" : message })
46
+
47
+ def append_user_message(self, message: str) -> None:
48
+ self.messages.append({ "role" : "user", "content" : message })
49
+
50
+ def append_user_image(self, url: str) -> None:
51
+ self.messages.append({ "role" : "user", "content" : [ { "type" : "image_url", "image_url": {"url": url, "detail": "auto"} } ] })
52
+
53
+
54
+ def copy(self) -> 'AgentChatContext':
55
+ return AgentChatContext(messages=deepcopy(self.messages), system_role=self._system_role)
56
+
57
+ def to_json(self) -> dict:
58
+ return {
59
+ "messages" : self.messages,
60
+ "system_role" : self.system_role
61
+ }
62
+
63
+ @staticmethod
64
+ def from_json(json: dict):
65
+ return AgentChatContext(messages=json["messages"], system_role=json.get("system_role", None))
66
+
67
+ class AgentCallContext:
68
+ def __init__(self, *, chat: AgentChatContext, room: RoomClient, toolkits: Optional[list[Toolkit]] = None, caller: Optional[Participant] = None):
69
+ self._room = room
70
+ if toolkits == None:
71
+ toolkits = list[Toolkit]()
72
+ self._toolkits = toolkits
73
+ self._chat = chat
74
+ self._caller = caller
75
+
76
+ @property
77
+ def toolkits(self):
78
+ return self._toolkits
79
+
80
+ @property
81
+ def chat(self):
82
+ return self._chat
83
+
84
+ @property
85
+ def caller(self):
86
+ return self._caller
87
+
88
+ @property
89
+ def room(self):
90
+ return self._room
@@ -0,0 +1,32 @@
1
+ from .agent import SingleRoomAgent
2
+ from meshagent.api import websocket_protocol, RoomClient, MeshSchema, RoomException
3
+ import asyncio
4
+ import signal
5
+ import json
6
+
7
+ from aiohttp import web
8
+
9
+
10
+ async def connect_development_agent(*, room_name:str, agent: SingleRoomAgent):
11
+
12
+ async with RoomClient(
13
+ protocol = websocket_protocol(participant_name=agent.name, room_name=room_name, role="agent")) as room:
14
+
15
+ await agent.start(room=room)
16
+
17
+ try:
18
+
19
+ term = asyncio.Future()
20
+
21
+ def clean_termination(signal, frame):
22
+ term.set_result(True)
23
+
24
+ signal.signal(signal.SIGTERM, clean_termination)
25
+ signal.signal(signal.SIGABRT, clean_termination)
26
+
27
+ await asyncio.wait([asyncio.ensure_future(room.protocol.wait_for_close()), term], return_when=asyncio.FIRST_COMPLETED)
28
+
29
+ finally:
30
+
31
+ await agent.stop()
32
+
@@ -0,0 +1,117 @@
1
+ import logging
2
+
3
+ from meshagent.api import websocket_protocol, RoomMessage
4
+ from meshagent.api.webhooks import WebhookServer, RoomStartedEvent, RoomEndedEvent, WebSocketClientProtocol, CallEvent
5
+ from meshagent.api.room_server_client import RoomClient
6
+ from meshagent.agents import SingleRoomAgent
7
+ from aiohttp import web
8
+ import asyncio
9
+
10
+ import logging
11
+ from typing import Callable, Optional, TypeVar
12
+
13
+ from .agent import TaskRunner
14
+
15
+ logger = logging.getLogger("hosting")
16
+ logger.setLevel(logging.INFO)
17
+
18
+
19
+ class RemoteTaskRunnerServer[T:TaskRunner](WebhookServer):
20
+ def __init__(self, *, cls: Optional[T] = None, path: Optional[str] = None, app: Optional[web.Application] = None, host = None, port = None, webhook_secret = None, create_agent: Optional[Callable[[dict],TaskRunner]] = None, validate_webhook_secret: Optional[bool] = None):
21
+ super().__init__(path=path, app=app, host=host, port=port, webhook_secret=webhook_secret, validate_webhook_secret=validate_webhook_secret)
22
+
23
+ if create_agent == None:
24
+ def default_create_agent(arguments: dict) -> TaskRunner:
25
+ return cls(**arguments)
26
+
27
+ create_agent = default_create_agent
28
+
29
+ self._create_agent = create_agent
30
+
31
+ async def _spawn(self, *, room_name: str, room_url: str, token: str, arguments: Optional[dict] = None):
32
+ agent = self._create_agent(arguments=arguments)
33
+
34
+ async def run():
35
+ async with RoomClient(protocol=WebSocketClientProtocol(url= room_url, token=token)) as room:
36
+
37
+ dismissed = asyncio.Future()
38
+
39
+ def on_message(message: RoomMessage):
40
+ if message.type == "dismiss":
41
+ logger.info(f"dismissed by {message.from_participant_id}")
42
+ dismissed.set_result(True)
43
+
44
+ room.messaging.on("message", on_message)
45
+
46
+ await agent.start(room=room)
47
+
48
+ done, pending = await asyncio.wait([
49
+ dismissed,
50
+ asyncio.ensure_future(room.protocol.wait_for_close())
51
+ ], return_when=asyncio.FIRST_COMPLETED)
52
+
53
+
54
+ await agent.stop()
55
+
56
+ def on_done(task: asyncio.Task):
57
+ try:
58
+ result = task.result()
59
+ except Exception as e:
60
+ logger.error("agent encountered an error", exc_info=e)
61
+
62
+ task = asyncio.create_task(run())
63
+ task.add_done_callback(on_done)
64
+
65
+ async def on_call(self, event: CallEvent):
66
+ await self._spawn(room_name=event.room_name, room_url=event.room_url, token=event.token, arguments=event.arguments)
67
+
68
+
69
+ class RemoteAgentServer[T:SingleRoomAgent](WebhookServer):
70
+ def __init__(self, *, cls: Optional[T] = None, path: Optional[str] = None, app: Optional[web.Application] = None, host = None, port = None, webhook_secret = None, create_agent: Optional[Callable[[dict], SingleRoomAgent]] = None, validate_webhook_secret: Optional[bool] = None):
71
+ super().__init__(path=path, app=app, host=host, port=port, webhook_secret=webhook_secret, validate_webhook_secret=validate_webhook_secret)
72
+
73
+ if create_agent == None:
74
+ def default_create_agent(arguments: dict) -> SingleRoomAgent:
75
+ return cls(**arguments)
76
+
77
+ create_agent = default_create_agent
78
+
79
+ self._create_agent = create_agent
80
+
81
+ async def _spawn(self, *, room_name: str, room_url: str, token: str, arguments: Optional[dict] = None):
82
+
83
+ logger.info(f"room: {room_name} url: {room_url} token: {token} arguments: {arguments}")
84
+ agent = self._create_agent(arguments=arguments)
85
+
86
+ async def run():
87
+ async with RoomClient(protocol=WebSocketClientProtocol(url=room_url, token=token)) as room:
88
+
89
+ dismissed = asyncio.Future()
90
+
91
+ def on_message(message: RoomMessage):
92
+ if message.type == "dismiss":
93
+ logger.info(f"dismissed by {message.from_participant_id}")
94
+ dismissed.set_result(True)
95
+
96
+ room.messaging.on("message", on_message)
97
+
98
+ await agent.start(room=room)
99
+
100
+ done, pending = await asyncio.wait([
101
+ dismissed,
102
+ asyncio.ensure_future(room.protocol.wait_for_close())
103
+ ], return_when=asyncio.FIRST_COMPLETED)
104
+
105
+ await agent.stop()
106
+
107
+ def on_done(task: asyncio.Task):
108
+ try:
109
+ result = task.result()
110
+ except Exception as e:
111
+ logger.error("agent encountered an error", exc_info=e)
112
+
113
+ task = asyncio.create_task(run())
114
+ task.add_done_callback(on_done)
115
+
116
+ async def on_call(self, event: CallEvent):
117
+ await self._spawn(room_name=event.room_name, room_url=event.room_url, token=event.token, arguments=event.arguments)