matrix-for-agents 0.1.2__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.
Files changed (66) hide show
  1. agentmatrix/__init__.py +20 -0
  2. agentmatrix/agents/__init__.py +1 -0
  3. agentmatrix/agents/base.py +572 -0
  4. agentmatrix/agents/claude_coder.py +10 -0
  5. agentmatrix/agents/data_crawler.py +14 -0
  6. agentmatrix/agents/post_office.py +212 -0
  7. agentmatrix/agents/report_writer.py +14 -0
  8. agentmatrix/agents/secretary.py +10 -0
  9. agentmatrix/agents/stateful.py +10 -0
  10. agentmatrix/agents/user_proxy.py +82 -0
  11. agentmatrix/agents/worker.py +30 -0
  12. agentmatrix/backends/__init__.py +1 -0
  13. agentmatrix/backends/llm_client.py +414 -0
  14. agentmatrix/backends/mock_llm.py +35 -0
  15. agentmatrix/cli_runner.py +94 -0
  16. agentmatrix/core/__init__.py +0 -0
  17. agentmatrix/core/action.py +50 -0
  18. agentmatrix/core/browser/bing.py +208 -0
  19. agentmatrix/core/browser/browser_adapter.py +298 -0
  20. agentmatrix/core/browser/browser_common.py +85 -0
  21. agentmatrix/core/browser/drission_page_adapter.py +1296 -0
  22. agentmatrix/core/browser/google.py +230 -0
  23. agentmatrix/core/cerebellum.py +121 -0
  24. agentmatrix/core/events.py +22 -0
  25. agentmatrix/core/loader.py +185 -0
  26. agentmatrix/core/loader_v1.py +146 -0
  27. agentmatrix/core/log_util.py +158 -0
  28. agentmatrix/core/message.py +32 -0
  29. agentmatrix/core/prompt_engine.py +30 -0
  30. agentmatrix/core/runtime.py +211 -0
  31. agentmatrix/core/session.py +20 -0
  32. agentmatrix/db/__init__.py +1 -0
  33. agentmatrix/db/database.py +79 -0
  34. agentmatrix/db/vector_db.py +213 -0
  35. agentmatrix/docs/Design.md +109 -0
  36. agentmatrix/docs/Framework Capbilities.md +105 -0
  37. agentmatrix/docs/Planner Design.md +148 -0
  38. agentmatrix/docs/crawler_flow.md +110 -0
  39. agentmatrix/docs/report_writer.md +83 -0
  40. agentmatrix/docs/review.md +99 -0
  41. agentmatrix/docs/skill_design.md +23 -0
  42. agentmatrix/profiles/claude_coder.yml +40 -0
  43. agentmatrix/profiles/mark.yml +26 -0
  44. agentmatrix/profiles/planner.yml +21 -0
  45. agentmatrix/profiles/prompts/base.txt +88 -0
  46. agentmatrix/profiles/prompts/base_v1.txt +101 -0
  47. agentmatrix/profiles/prompts/base_v2.txt +94 -0
  48. agentmatrix/profiles/tom_the_data_crawler.yml +38 -0
  49. agentmatrix/profiles/user_proxy.yml +17 -0
  50. agentmatrix/skills/__init__.py +1 -0
  51. agentmatrix/skills/crawler_helpers.py +315 -0
  52. agentmatrix/skills/data_crawler.py +777 -0
  53. agentmatrix/skills/filesystem.py +204 -0
  54. agentmatrix/skills/notebook.py +158 -0
  55. agentmatrix/skills/project_management.py +114 -0
  56. agentmatrix/skills/report_writer.py +194 -0
  57. agentmatrix/skills/report_writer_utils.py +379 -0
  58. agentmatrix/skills/search_tool.py +383 -0
  59. agentmatrix/skills/terminal_ctrl.py +122 -0
  60. agentmatrix/skills/utils.py +33 -0
  61. agentmatrix/skills/web_searcher.py +1107 -0
  62. matrix_for_agents-0.1.2.dist-info/METADATA +44 -0
  63. matrix_for_agents-0.1.2.dist-info/RECORD +66 -0
  64. matrix_for_agents-0.1.2.dist-info/WHEEL +5 -0
  65. matrix_for_agents-0.1.2.dist-info/licenses/LICENSE +190 -0
  66. matrix_for_agents-0.1.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,212 @@
1
+ import asyncio
2
+ from typing import Dict, Optional
3
+ from ..db.database import AgentMailDB
4
+ import os
5
+ import json
6
+ import textwrap
7
+ from pathlib import Path
8
+ from ..core.message import Email
9
+ from ..core.log_util import AutoLoggerMixin
10
+ class PostOffice(AutoLoggerMixin):
11
+ def __init__(self, matrix_path):
12
+ self.directory = {}
13
+ self.queue = asyncio.Queue()
14
+ email_db_path = os.path.join(matrix_path,".matrix" , "matrix_mails.db")
15
+ self.email_db = AgentMailDB(email_db_path) # 初始化数据库连接
16
+ self._paused = False
17
+ self.vector_db = None
18
+
19
+ # 初始化 user_sessions 管理
20
+ self.user_sessions = {}
21
+ self.user_sessions_file = os.path.join(matrix_path, ".matrix", "user_sessions.json")
22
+ self._load_user_sessions()
23
+
24
+ def _load_user_sessions(self):
25
+ """从文件加载 user_sessions 数据"""
26
+ try:
27
+ file_path = Path(self.user_sessions_file)
28
+ if file_path.exists():
29
+ with open(file_path, 'r', encoding='utf-8') as f:
30
+ self.user_sessions = json.load(f)
31
+ self.logger.info(f"Loaded {len(self.user_sessions)} user sessions from {self.user_sessions_file}")
32
+ else:
33
+ # 文件不存在,确保目录存在
34
+ file_path.parent.mkdir(parents=True, exist_ok=True)
35
+ self.user_sessions = {}
36
+ self.logger.info(f"User sessions file not found. Starting with empty sessions.")
37
+ except json.JSONDecodeError as e:
38
+ self.logger.error(f"Failed to parse user sessions file: {e}. Starting with empty sessions.")
39
+ self.user_sessions = {}
40
+ except Exception as e:
41
+ self.logger.exception(f"Error loading user sessions: {e}. Starting with empty sessions.")
42
+ self.user_sessions = {}
43
+
44
+ def _save_user_sessions(self):
45
+ """保存 user_sessions 数据到文件(使用原子写入)"""
46
+ try:
47
+ file_path = Path(self.user_sessions_file)
48
+ # 确保目录存在
49
+ file_path.parent.mkdir(parents=True, exist_ok=True)
50
+
51
+ # 使用原子写入:先写临时文件,再重命名
52
+ temp_file = file_path.with_suffix('.tmp')
53
+ with open(temp_file, 'w', encoding='utf-8') as f:
54
+ json.dump(self.user_sessions, f, ensure_ascii=False, indent=2)
55
+
56
+ # 原子重命名
57
+ temp_file.replace(file_path)
58
+
59
+ self.logger.debug(f"Saved {len(self.user_sessions)} user sessions to {self.user_sessions_file}")
60
+ except Exception as e:
61
+ self.logger.exception(f"Failed to save user sessions: {e}")
62
+
63
+
64
+
65
+
66
+ def register(self, agent):
67
+ self.directory[agent.name] = agent
68
+ agent.post_office = self
69
+
70
+ def unregister(self, agent):
71
+ del self.directory[agent.name]
72
+
73
+ def yellow_page(self):
74
+ yellow_page = ""
75
+ for name, agent in self.directory.items():
76
+ # 给 description 添加 2 个空格的缩进
77
+
78
+ description = textwrap.indent(agent.description, " ")
79
+
80
+ # 给 instruction 添加 4 个空格的缩进
81
+ instruction = textwrap.indent(agent.instruction_to_caller, " ")
82
+
83
+ yellow_page += f"- {name}: \n"
84
+ yellow_page += f"{description} \n"
85
+
86
+ yellow_page += f" [How to talk to {agent.name}]\n"
87
+ yellow_page += f"{instruction}\n\n"
88
+ return yellow_page
89
+
90
+ def yellow_page_exclude_me(self, myname):
91
+ yellow_page = ""
92
+ for name, agent in self.directory.items():
93
+ # 给 description 添加 2 个空格的缩进
94
+ if name == myname:
95
+ continue
96
+ description = textwrap.indent(agent.description, " ")
97
+
98
+ # 给 instruction 添加 4 个空格的缩进
99
+ instruction = textwrap.indent(agent.instruction_to_caller, " ")
100
+
101
+ yellow_page += f"- {name}: \n"
102
+ yellow_page += f"{description} \n"
103
+
104
+ yellow_page += f" [How to talk to {agent.name}]\n"
105
+ yellow_page += f"{instruction}\n\n"
106
+ return yellow_page
107
+
108
+ def get_contact_list(self, exclude =None):
109
+ contact_list = []
110
+ for name, agent in self.directory.items():
111
+ if name == exclude:
112
+ continue
113
+ contact_list.append(name)
114
+ return contact_list
115
+
116
+
117
+
118
+
119
+
120
+ def pause(self):
121
+ self._paused = True
122
+
123
+ def resume(self):
124
+ self._paused = False
125
+
126
+ async def dispatch(self, email):
127
+ self.email_db.log_email(email)
128
+ self.vector_db.add_documents("email", [str(email)],
129
+ metadatas={"created_at": email.timestamp,
130
+ "sender": email.sender,
131
+ "recipient": email.recipient,
132
+ "user_session_id": email.user_session_id
133
+ },
134
+ ids=[email.id]
135
+ )
136
+
137
+ # 维护 user_sessions
138
+ if email.user_session_id:
139
+ if email.user_session_id not in self.user_sessions:
140
+ # 新的 session,添加记录
141
+ self.user_sessions[email.user_session_id] = {
142
+ "name": email.subject,
143
+ "last_email_time": str(email.timestamp)
144
+ }
145
+ self.logger.info(f"New user session created: {email.user_session_id} - {email.subject}")
146
+ else:
147
+ # 已存在的 session,更新时间戳
148
+ self.user_sessions[email.user_session_id]["last_email_time"] = str(email.timestamp)
149
+ self.logger.debug(f"User session updated: {email.user_session_id}")
150
+
151
+ # 同步到磁盘
152
+ self._save_user_sessions()
153
+
154
+ self.logger.debug(f"Sending email from {email.sender} to {email.recipient} ")
155
+ await self.queue.put(email)
156
+ self.logger.debug("Mail delivered")
157
+
158
+ async def run(self):
159
+ self.logger.info("[PostOffice] Service Started")
160
+ while True:
161
+ if not self._paused:
162
+ email = await self.queue.get()
163
+ if email.recipient in self.directory:
164
+ target = self.directory[email.recipient]
165
+ await target.inbox.put(email)
166
+ else:
167
+ self.logger.warning(f"Dropped mail to {email.recipient}")
168
+ self.queue.task_done()
169
+ else:
170
+ await asyncio.sleep(0.1)
171
+
172
+ def get_mails_by_range(self, user_session_id, agent_name, start=0, end=1):
173
+ """查询某个Agent的指定Range的邮件
174
+ Args:
175
+ agent_name: Agent名称
176
+ start: 起始索引(0表示最新邮件)
177
+ end: 结束索引
178
+ Returns:
179
+ 指定范围内的邮件列表
180
+ """
181
+ email_records = self.email_db.get_mails_by_range(user_session_id, agent_name, start, end)
182
+ emails = []
183
+ for record in email_records:
184
+ email = Email(
185
+ id=record['id'],
186
+ timestamp=record['timestamp'],
187
+ sender=record['sender'],
188
+ recipient=record['recipient'],
189
+ subject=record['subject'],
190
+ body=record['body'],
191
+ in_reply_to=record['in_reply_to'],
192
+ user_session_id=record.get('user_session_id', None)
193
+ )
194
+ emails.append(email)
195
+ return emails
196
+
197
+ def get_user_sessions(self, user_session_id: Optional[str] = None) -> Dict:
198
+ """
199
+ 获取 user_sessions 数据
200
+
201
+ Args:
202
+ user_session_id: 可选,如果提供则返回指定 session 的数据,否则返回所有 sessions
203
+
204
+ Returns:
205
+ 如果提供了 user_session_id:返回该 session 的信息字典,不存在则返回 None
206
+ 如果未提供:返回所有 sessions 的副本(避免外部修改)
207
+ """
208
+ if user_session_id:
209
+ return self.user_sessions.get(user_session_id)
210
+ else:
211
+ # 返回深拷贝,避免外部修改影响内部数据
212
+ return json.loads(json.dumps(self.user_sessions))
@@ -0,0 +1,14 @@
1
+ from ..agents.base import BaseAgent
2
+ from ..skills.report_writer import ReportWriterSkillMixin
3
+ import asyncio
4
+ import logging
5
+
6
+ class ReportWriter(BaseAgent, ReportWriterSkillMixin):
7
+ _custom_log_level = logging.DEBUG
8
+ def __init__(self, profile ):
9
+ super().__init__(profile)
10
+ self.research_state = None
11
+ #self.sem = asyncio.Semaphore(5)
12
+
13
+
14
+
@@ -0,0 +1,10 @@
1
+ import json
2
+ from ..agents.base import BaseAgent
3
+
4
+
5
+ class SecretaryAgent(BaseAgent):
6
+ def __init__(self, profile ):
7
+ super().__init__(profile)
8
+
9
+
10
+
@@ -0,0 +1,10 @@
1
+ from ..agents.base import BaseAgent
2
+ from ..skills.project_management import ProjectManagementMixin
3
+ from ..skills.notebook import NotebookMixin
4
+ import json
5
+ class StatefulAgent(BaseAgent, ProjectManagementMixin, NotebookMixin):
6
+ def __init__(self, profile):
7
+ super().__init__(profile)
8
+ # 这就是那个“动态文档”,可以是 JSON,也可以是 Markdown
9
+ self.project_board = None
10
+ self.vector_db = None
@@ -0,0 +1,82 @@
1
+ from ..agents.base import BaseAgent
2
+ from ..core.message import Email
3
+ from typing import Callable, Awaitable, Optional
4
+ import datetime
5
+ import uuid
6
+
7
+ # 定义一个回调函数的类型:接收一封邮件,返回 None (异步)
8
+ OnMailReceived = Callable[[Email], Awaitable[None]]
9
+
10
+ class UserProxyAgent(BaseAgent):
11
+ def __init__(self, profile) :
12
+ super().__init__(profile)
13
+ self.on_mail_received = None
14
+
15
+
16
+ def set_mail_handler(self, on_mail_received: OnMailReceived):
17
+ self.on_mail_received = on_mail_received
18
+
19
+
20
+ async def process_email(self, email: Email):
21
+ """
22
+ 当 Matrix 里的 Agent (如 Planner) 给 User 发信时触发
23
+ """
24
+ print(f"[{self.name}] 收到邮件: {email.subject}")
25
+ # 1. 记录日志 (BaseAgent 能力)
26
+ await self.emit("USER_INTERACTION", f"收到来自 {email.sender} 的消息", payload={
27
+ "type": "MAIL_RECEIVED",
28
+ "mail_id": email.id,
29
+ "sender": email.sender,
30
+ "subject": email.subject,
31
+ "body": email.body,
32
+ "in_reply_to": email.in_reply_to
33
+ })
34
+
35
+ # 2. 触发外部钩子 (如果定义了的话)
36
+ if self.on_mail_received:
37
+ # 把这封信扔给外部程序,至于外部程序是打印还是弹窗,我不关心
38
+ await self.on_mail_received(email)
39
+ else:
40
+ # 默认行为:简单的打印到控制台,防止没有任何反应
41
+ print(f"\n[UserProxy 收到邮件] From: {email.sender}\nSubject: {email.subject}\nBody: {email.body}\n")
42
+
43
+ async def speak(self, user_session_id, to: str, subject, content: str =None, reply_to_id: str = None):
44
+ """
45
+ [Public API]
46
+ 这是给宿主程序调用的方法。
47
+ 相当于用户开口说话。
48
+ """
49
+ if content is None:
50
+ content = subject
51
+ subject = None
52
+
53
+ if not subject:
54
+ resp = await self.cerebellum.think(f"""
55
+ 请你根据以下邮件内容生成一个高度扼要的邮件主题。明了的说明事情本质就行,不需要具体细节内容,例如“一个问题”,“下载任务” 等等
56
+
57
+ [邮件内容]
58
+ {content}
59
+
60
+ [输出要求]
61
+ 你可以尽量简短的说明思路,但输出的最后一行必须是、也只能是邮件主题本身。例如如果邮件主题是ABC,那么最后一行就是ABC。不多不少,不要增加任何标点符号
62
+ """)
63
+ subject = resp['reply'] or ""
64
+ #取subject 最后一行
65
+ subject = subject.split("\n")[-1]
66
+ #给subject 后面加上日期yyyy-mm-dd
67
+
68
+ subject = subject + " " + datetime.datetime.now().strftime("%Y-%m-%d")
69
+ subject = subject.strip()
70
+
71
+
72
+ email = Email(
73
+ sender=self.name,
74
+ recipient=to,
75
+ subject=subject,
76
+ body=content,
77
+ in_reply_to=reply_to_id,
78
+ user_session_id=user_session_id
79
+ )
80
+ await self.post_office.dispatch(email)
81
+
82
+
@@ -0,0 +1,30 @@
1
+ from ..agents.base import BaseAgent
2
+
3
+ class WorkerAgent(BaseAgent):
4
+ async def step(self, session):
5
+ await self.emit("THINKING", "正在思考...")
6
+
7
+ # 调用 Mock LLM
8
+ response = await self.brain.think(self.name, session.history)
9
+
10
+ # 简单的协议解析 (Action Protocol)
11
+ # 格式: [Action: EMAIL] To: X | Subject: Y | Body: Z
12
+ # 格式: [Action: REPLY] Body
13
+
14
+ if "[Action: EMAIL]" in response:
15
+ # 解析 (简化版字符串操作)
16
+ content = response.split("]", 1)[1].strip()
17
+ parts = {p.split(":")[0].strip(): p.split(":")[1].strip() for p in content.split("|")}
18
+
19
+ to = parts.get("To")
20
+ subj = parts.get("Subject")
21
+ body = parts.get("Body")
22
+
23
+ session.history.append({"role": "assistant", "content": response})
24
+ # 这里认为是中间步骤,expect_reply = True
25
+ await self.send_email(to, subj, body, session, expect_reply=True)
26
+
27
+ elif "[Action: REPLY]" in response:
28
+ body = response.split("REPLY]", 1)[1].strip()
29
+ # 最终回复
30
+ await self.send_email(session.original_sender, "Re: Task", body, session, expect_reply=False)
@@ -0,0 +1 @@
1
+ """Backends module for Agent-Matrix framework."""