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.
- agentmatrix/__init__.py +20 -0
- agentmatrix/agents/__init__.py +1 -0
- agentmatrix/agents/base.py +572 -0
- agentmatrix/agents/claude_coder.py +10 -0
- agentmatrix/agents/data_crawler.py +14 -0
- agentmatrix/agents/post_office.py +212 -0
- agentmatrix/agents/report_writer.py +14 -0
- agentmatrix/agents/secretary.py +10 -0
- agentmatrix/agents/stateful.py +10 -0
- agentmatrix/agents/user_proxy.py +82 -0
- agentmatrix/agents/worker.py +30 -0
- agentmatrix/backends/__init__.py +1 -0
- agentmatrix/backends/llm_client.py +414 -0
- agentmatrix/backends/mock_llm.py +35 -0
- agentmatrix/cli_runner.py +94 -0
- agentmatrix/core/__init__.py +0 -0
- agentmatrix/core/action.py +50 -0
- agentmatrix/core/browser/bing.py +208 -0
- agentmatrix/core/browser/browser_adapter.py +298 -0
- agentmatrix/core/browser/browser_common.py +85 -0
- agentmatrix/core/browser/drission_page_adapter.py +1296 -0
- agentmatrix/core/browser/google.py +230 -0
- agentmatrix/core/cerebellum.py +121 -0
- agentmatrix/core/events.py +22 -0
- agentmatrix/core/loader.py +185 -0
- agentmatrix/core/loader_v1.py +146 -0
- agentmatrix/core/log_util.py +158 -0
- agentmatrix/core/message.py +32 -0
- agentmatrix/core/prompt_engine.py +30 -0
- agentmatrix/core/runtime.py +211 -0
- agentmatrix/core/session.py +20 -0
- agentmatrix/db/__init__.py +1 -0
- agentmatrix/db/database.py +79 -0
- agentmatrix/db/vector_db.py +213 -0
- agentmatrix/docs/Design.md +109 -0
- agentmatrix/docs/Framework Capbilities.md +105 -0
- agentmatrix/docs/Planner Design.md +148 -0
- agentmatrix/docs/crawler_flow.md +110 -0
- agentmatrix/docs/report_writer.md +83 -0
- agentmatrix/docs/review.md +99 -0
- agentmatrix/docs/skill_design.md +23 -0
- agentmatrix/profiles/claude_coder.yml +40 -0
- agentmatrix/profiles/mark.yml +26 -0
- agentmatrix/profiles/planner.yml +21 -0
- agentmatrix/profiles/prompts/base.txt +88 -0
- agentmatrix/profiles/prompts/base_v1.txt +101 -0
- agentmatrix/profiles/prompts/base_v2.txt +94 -0
- agentmatrix/profiles/tom_the_data_crawler.yml +38 -0
- agentmatrix/profiles/user_proxy.yml +17 -0
- agentmatrix/skills/__init__.py +1 -0
- agentmatrix/skills/crawler_helpers.py +315 -0
- agentmatrix/skills/data_crawler.py +777 -0
- agentmatrix/skills/filesystem.py +204 -0
- agentmatrix/skills/notebook.py +158 -0
- agentmatrix/skills/project_management.py +114 -0
- agentmatrix/skills/report_writer.py +194 -0
- agentmatrix/skills/report_writer_utils.py +379 -0
- agentmatrix/skills/search_tool.py +383 -0
- agentmatrix/skills/terminal_ctrl.py +122 -0
- agentmatrix/skills/utils.py +33 -0
- agentmatrix/skills/web_searcher.py +1107 -0
- matrix_for_agents-0.1.2.dist-info/METADATA +44 -0
- matrix_for_agents-0.1.2.dist-info/RECORD +66 -0
- matrix_for_agents-0.1.2.dist-info/WHEEL +5 -0
- matrix_for_agents-0.1.2.dist-info/licenses/LICENSE +190 -0
- 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
|
+
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."""
|