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,146 @@
1
+ import yaml
2
+ import importlib
3
+ import os
4
+ from typing import List, Any
5
+ from dotenv import load_dotenv
6
+ import json
7
+ from ..backends.llm_client import LLMClient
8
+ from ..core.cerebellum import Cerebellum
9
+ from ..core.log_util import AutoLoggerMixin
10
+
11
+
12
+ class AgentLoader(AutoLoggerMixin):
13
+ def __init__(self, profile_path):
14
+ # Loader 持有运行时需要的公共资源 (如 LLM 客户端)
15
+ # 这样实例化 Agent 时可以注入进去
16
+ self.profile_path = profile_path
17
+ #从profile path 下加载 .env
18
+ #print(f"loading from : {profile_path}")
19
+ env_file = os.path.join(profile_path, ".env")
20
+ if not os.path.exists(env_file):
21
+ raise FileNotFoundError(f"环境变量文件不存在: {self.profile_path}")
22
+
23
+ # 检查文件权限
24
+ if not os.access(env_file, os.R_OK):
25
+ raise PermissionError(f"没有读取文件的权限: {self.profile_path}")
26
+
27
+
28
+ vars = load_dotenv(env_file)
29
+ #加载 profile_path 下的 llm_config.json 文件
30
+ llm_config_file = os.path.join(profile_path, "llm_config.json")
31
+
32
+
33
+
34
+ with open(llm_config_file, 'r', encoding='utf-8') as f:
35
+ self.llm_config = json.load(f)
36
+
37
+ # === 关键检查:必须存在 default_slm 配置 ===
38
+ if "default_slm" not in self.llm_config:
39
+ raise ValueError("llm_config.json 中必须包含 'default_slm' 配置,用于驱动默认小脑。")
40
+
41
+ for config in self.llm_config.values():
42
+
43
+ if "API_KEY" in config:
44
+ api_key = config["API_KEY"]
45
+ if os.getenv(api_key) is not None:
46
+ config["API_KEY"] = os.getenv(api_key)
47
+
48
+
49
+ prompts_path = os.path.join(self.profile_path, "prompts")
50
+ self.prompts = {}
51
+ for prompt_txt in os.listdir(prompts_path):
52
+
53
+ if prompt_txt.endswith(".txt"):
54
+ self.logger.info(f">>> 加载Prompt模板 {prompt_txt}...")
55
+ with open(os.path.join(prompts_path, prompt_txt), "r", encoding='utf-8') as f:
56
+ self.prompts[prompt_txt[:-4]] = f.read()
57
+
58
+ self.logger.info(self.prompts)
59
+
60
+
61
+
62
+
63
+
64
+
65
+ def load_from_file(self, file_path: str) -> Any:
66
+ """从 JSON 文件加载并实例化一个 Agent"""
67
+ self.logger.info(f">>> 加载Agent配置文件 {file_path}...")
68
+ with open(file_path, 'r', encoding='utf-8') as f:
69
+ profile = yaml.safe_load(f)
70
+
71
+ # 1. 解析实现类信息
72
+ module_name = profile["module"]
73
+ class_name = profile["class_name"]
74
+
75
+ #remove module_name and class_name from profile
76
+ del profile["module"]
77
+ del profile["class_name"]
78
+
79
+ # 2. 动态导入 (Reflection)
80
+ try:
81
+ module = importlib.import_module(module_name)
82
+ agent_class = getattr(module, class_name)
83
+ except (ImportError, AttributeError) as e:
84
+ raise ImportError(f"无法加载 Agent 类: {module_name}.{class_name}. 错误: {e}")
85
+
86
+ # 注入 prompt template, 默认是"base"
87
+ if "prompte_template" not in profile:
88
+ profile["prompt_template"] = "base"
89
+ prompt_template_name = profile.get("prompt_template")
90
+ self.logger.info(f">>> 加载Prompt模板 {prompt_template_name}...")
91
+ if prompt_template_name in self.prompts:
92
+ prompt = self.prompts[prompt_template_name]
93
+ profile["full_prompt"] = prompt
94
+ else:
95
+ raise ValueError(f"加载Agent {file_path} 失败,Prompt 模板 {prompt_template_name} 未找到。")
96
+
97
+ # 3. 实例化
98
+ agent_instance = agent_class(profile.copy())
99
+
100
+ # 4. 设置LLM backend
101
+ backend_model = agent_instance.backend_model
102
+ agent_instance.brain = self._create_llm_client(backend_model)
103
+ print(f"Agent {agent_instance.name} brain set to {backend_model} / {backend_model}")
104
+
105
+ # === 设置小脑 (Cerebellum) ===
106
+ cerebellum_config = profile.get("cerebellum")
107
+ slm_client = None
108
+
109
+ if cerebellum_config:
110
+ # A. 如果 Agent 定义了自己的小脑模型
111
+ print(cerebellum_config)
112
+ slm_model = cerebellum_config.get("backend_model", "default_slm")
113
+ slm_client = self._create_llm_client(slm_model)
114
+ print(f"[{agent_instance.name}] Using custom SLM: {slm_model}")
115
+ else:
116
+ # B. 如果没定义,强制使用 default_slm
117
+ slm_client = self._create_llm_client("default_slm")
118
+ print(f"[{agent_instance.name}] Using system default SLM.")
119
+
120
+ # 注入小脑
121
+ agent_instance.cerebellum = Cerebellum(slm_client, agent_instance.name)
122
+
123
+
124
+
125
+
126
+ return agent_instance
127
+
128
+ def _create_llm_client(self, model_name):
129
+ llm_config = self.llm_config[model_name]
130
+ url = llm_config['url']
131
+ api_key = llm_config['API_KEY']
132
+ model_name = llm_config['model_name']
133
+ llm_client = LLMClient(url,api_key, model_name)
134
+ return llm_client
135
+
136
+ def load_all(self) -> List[Any]:
137
+ agents = {}
138
+
139
+ for filename in os.listdir(self.profile_path):
140
+ if filename.endswith(".yml"):
141
+ full_path = os.path.join(self.profile_path, filename)
142
+ print(f"Loading agent from {filename}...")
143
+ agent = self.load_from_file(full_path)
144
+ agents[agent.name] = agent
145
+
146
+ return agents
@@ -0,0 +1,158 @@
1
+ import os
2
+ import logging
3
+ from logging.handlers import RotatingFileHandler
4
+ from typing import Optional
5
+
6
+
7
+ # 1. 定义过滤器:决定什么能上控制台
8
+ class ConsoleDisplayFilter(logging.Filter):
9
+ def filter(self, record):
10
+ # 规则 A: 错误(ERROR/CRITICAL) 必须显示
11
+ if record.levelno >= logging.ERROR:
12
+ return True
13
+
14
+ # 规则 B: 如果日志携带了 'echo' 标记且为 True,则显示
15
+ # 使用 getattr 安全获取,防止报错
16
+ if getattr(record, 'echo', False):
17
+ return True
18
+
19
+ # 其他(普通的 INFO/DEBUG)都不显示
20
+ return False
21
+
22
+ # ==========================================
23
+ # 1. LogFactory: 负责干活(创建 Logger 和 Handler)
24
+ # ==========================================
25
+ class LogFactory:
26
+ _log_dir = "./logs"
27
+ _formatter = logging.Formatter(
28
+ '%(asctime)s - [%(threadName)s] - %(name)s - %(levelname)s - %(message)s'
29
+ )
30
+ _default_level = logging.INFO # 全局默认日志级别
31
+
32
+ @classmethod
33
+ def set_log_dir(cls, path: str):
34
+ cls._log_dir = path
35
+ os.makedirs(cls._log_dir, exist_ok=True)
36
+
37
+ @classmethod
38
+ def set_level(cls, level: int):
39
+ """【新增】设置全局默认日志级别"""
40
+ cls._default_level = level
41
+
42
+ @classmethod
43
+ def get_logger(cls, logger_name: str, filename: str,level: int = None) -> logging.Logger:
44
+ if not os.path.exists(cls._log_dir):
45
+ os.makedirs(cls._log_dir, exist_ok=True)
46
+
47
+ logger = logging.getLogger(logger_name)
48
+ # 【关键修改】决定当前 logger 的级别
49
+ target_level = level if level is not None else cls._default_level
50
+ logger.setLevel(target_level)
51
+ logger.propagate = False
52
+
53
+ if logger.handlers:
54
+ return logger
55
+
56
+ # --- 1. 文件 Handler (保持不变: 收录所有 INFO+) ---
57
+ file_path = os.path.join(cls._log_dir, filename)
58
+ file_handler = RotatingFileHandler(
59
+ file_path, maxBytes=10*1024*1024, backupCount=5, encoding='utf-8'
60
+ )
61
+ file_handler.setLevel(logging.DEBUG)
62
+ file_handler.setFormatter(cls._formatter)
63
+ logger.addHandler(file_handler)
64
+
65
+ # --- 2. 控制台 Handler (修改点) ---
66
+ console_handler = logging.StreamHandler()
67
+
68
+ # 【关键点 1】: 将级别降低到 INFO,否则 INFO 级别的日志根本进不来
69
+ console_handler.setLevel(logging.INFO)
70
+
71
+ # 【关键点 2】: 添加过滤器,由过滤器来把关谁能显示
72
+ console_handler.addFilter(ConsoleDisplayFilter())
73
+
74
+ console_handler.setFormatter(cls._formatter)
75
+ logger.addHandler(console_handler)
76
+
77
+ return logger
78
+
79
+
80
+ # ==========================================
81
+ # 2. AutoLoggerMixin: 负责决策(决定文件名)
82
+ # ==========================================
83
+ class AutoLoggerMixin:
84
+ """
85
+ 智能日志 Mixin。
86
+
87
+ 优先级策略:
88
+ 1. 如果设置了 _log_from_attr = "xxx",则文件名取 self.xxx + ".log"
89
+ 2. 如果设置了 _custom_log_filename = "yyy.log",则文件名为 "yyy.log"
90
+ 3. 默认使用 类名.log
91
+ """
92
+
93
+ # 【配置项 1】指定从哪个实例属性获取名字 (例如 "name", "id")
94
+ _log_from_attr: Optional[str] = None
95
+
96
+ # 【配置项 2】指定固定的文件名
97
+ _custom_log_filename: Optional[str] = None
98
+
99
+ _custom_log_level: Optional[int] = None
100
+
101
+ @property
102
+ def logger(self) -> logging.Logger:
103
+ # 懒加载:只有第一次调用时才初始化
104
+ if not hasattr(self, '_internal_logger'):
105
+ self._init_logger()
106
+ return self._internal_logger
107
+
108
+ def _init_logger(self):
109
+ filename = self._determine_log_filename()
110
+
111
+ # 这里的 logger_name 很关键:
112
+ # 如果是基于实例属性(如name='ABC'),logger_name 最好也叫 'ABC',
113
+ # 这样 logging 模块能正确区分不同的 logger 对象。
114
+ # 如果是基于类名,就用类名。
115
+
116
+ # 为了保证唯一性,我们使用 "类名_文件名" 作为 logger 内部的 key
117
+ logger_name = f"{self.__class__.__name__}_{filename}"
118
+
119
+ # 将 mixin 中定义的级别传给工厂
120
+ self._internal_logger = LogFactory.get_logger(
121
+ logger_name,
122
+ filename,
123
+ level=self._custom_log_level
124
+ )
125
+
126
+ def _determine_log_filename(self) -> str:
127
+ """核心逻辑:决定日志文件名"""
128
+
129
+ # 策略 1: 基于实例属性 (例如 self.name)
130
+ if self._log_from_attr:
131
+ if hasattr(self, self._log_from_attr):
132
+ attr_value = getattr(self, self._log_from_attr)
133
+ # 简单的清理,防止文件名非法字符
134
+ safe_name = str(attr_value).strip().replace('/', '_').replace('\\', '_')
135
+ return f"{safe_name}.log"
136
+ else:
137
+ # 如果指定的属性不存在,回退到默认
138
+ print(f"[Warn] Attribute '{self._log_from_attr}' not found in {self}, fallback to class name.")
139
+
140
+ # 策略 2: 固定文件名
141
+ if self._custom_log_filename:
142
+ return self._custom_log_filename
143
+
144
+ # 策略 3: 默认类名
145
+ return f"{self.__class__.__name__}.log"
146
+
147
+ def echo(self, msg: str, *args, **kwargs):
148
+ """
149
+ 专用方法:既写日志文件,也输出到控制台。
150
+ 用法: self.echo("Server started on port %s", 8080)
151
+ """
152
+ # 自动注入 extra={'echo': True}
153
+ if 'extra' not in kwargs:
154
+ kwargs['extra'] = {}
155
+ kwargs['extra']['echo'] = True
156
+
157
+ # 调用原生 logger
158
+ self.logger.info(msg, *args, **kwargs)
@@ -0,0 +1,32 @@
1
+ import uuid
2
+ from dataclasses import dataclass, field
3
+ from typing import Optional, Dict, Any
4
+ from datetime import datetime
5
+ import textwrap
6
+
7
+ @dataclass
8
+ class Email:
9
+ sender: str
10
+ recipient: str
11
+ subject: str
12
+ body: str
13
+ in_reply_to: Optional[str] = None # 核心:引用链
14
+ id: str = field(default_factory=lambda: str(uuid.uuid4()))
15
+ timestamp: datetime = field(default_factory=datetime.now)
16
+ user_session_id: Optional[str] = None # 用于区分不同用户会话
17
+
18
+ def __repr__(self):
19
+ reply_mark = f" (Re: {self.in_reply_to[:8]})" if self.in_reply_to else ""
20
+ return f"<Email {self.id[:8]} From:{self.sender} To:{self.recipient}{reply_mark}>"
21
+
22
+ def __str__(self):
23
+ return textwrap.dedent(f"""
24
+ ==== Mail ====
25
+ From: {self.sender}
26
+ To: {self.recipient}
27
+ Subject: {self.subject}
28
+ Date: {self.timestamp}
29
+
30
+ {self.body}
31
+ ================
32
+ """)
@@ -0,0 +1,30 @@
1
+ from jinja2 import Environment, FileSystemLoader
2
+
3
+ class PromptEngine:
4
+ def __init__(self, profile_path="./profiles", prompt_path="./prompts"):
5
+ self.env = Environment(loader=FileSystemLoader(prompt_path))
6
+ # 预加载一些全局配置
7
+ self.global_context = {
8
+ "env_description": "Local Development Environment",
9
+ "core_value": "Reliability and Structure"
10
+ }
11
+
12
+ def render_system_prompt(self, agent_profile, team_manifests):
13
+ """
14
+ 为指定 Agent 生成完整的 System Prompt
15
+ """
16
+ # 1. 选择模板:如果 profile 指定了模板则用指定的,否则根据角色判断
17
+ template_name = agent_profile.get("prompt_template", "base.j2")
18
+ template = self.env.get_template(template_name)
19
+
20
+ # 2. 准备数据
21
+ context = {
22
+ **self.global_context,
23
+ "agent_name": agent_profile["name"],
24
+ "agent_role": agent_profile.get("role", "Worker"),
25
+ "agent_description": agent_profile["description"],
26
+ "team_members": team_manifests, # 这是一个包含 name, desc, instructions 的列表
27
+ }
28
+
29
+ # 3. 渲染
30
+ return template.render(**context)
@@ -0,0 +1,211 @@
1
+ # runtime.py 或 snapshot_manager.py
2
+ import json
3
+
4
+ from datetime import datetime
5
+ from ..core.message import Email
6
+ from dataclasses import asdict
7
+ from ..core.loader import AgentLoader
8
+ import asyncio
9
+ import os
10
+
11
+ import chromadb
12
+
13
+ from chromadb.config import Settings
14
+ from ..agents.post_office import PostOffice
15
+ from ..core.log_util import LogFactory, AutoLoggerMixin
16
+ from ..db.vector_db import VectorDB
17
+
18
+
19
+ from ..core.message import Email
20
+
21
+
22
+ # all event format:
23
+ # Who(event source)
24
+ # Status(the status of the event source)
25
+ # Even Type
26
+ # Even Time
27
+ # Event Content
28
+ # Event Payload
29
+
30
+
31
+ VECTOR_DB_COLLECTIONS_NAMES =["email", "notebook"]
32
+
33
+ async def default_event_printer(event):
34
+ pass
35
+ #self.echo(event)
36
+
37
+ class AgentMatrix(AutoLoggerMixin):
38
+ def __init__(self, agent_profile_path, matrix_path, async_event_call_back = default_event_printer):
39
+ # === 全局实例 ===
40
+ log_path = os.path.join(matrix_path,".matrix", "logs")
41
+ LogFactory.set_log_dir(log_path)
42
+
43
+ self.async_event_call_back = async_event_call_back
44
+ self.agent_profile_path= agent_profile_path
45
+
46
+ self.matrix_path = matrix_path
47
+
48
+ self.agents = None
49
+ self.post_office = None
50
+ self.post_office_task = None
51
+ self.running_agent_tasks = []
52
+ self.running = False
53
+ self.echo(">>> 初始化世界资源...")
54
+ self._prepare_world_resource()
55
+ self.echo(">>> 初始化Agents...")
56
+ self._prepare_agents()
57
+ self.echo(">>> 加载世界状态...")
58
+ self.load_matrix()
59
+ def json_serializer(self,obj):
60
+ """JSON serializer for objects not serializable by default json code"""
61
+ try:
62
+ if isinstance(obj, (datetime,)):
63
+ return obj.isoformat()
64
+ # 尝试获取对象的详细信息
65
+ obj_info = f"Type: {type(obj)}, Value: {str(obj)[:100]}, Dir: {[attr for attr in dir(obj) if not attr.startswith('_')][:5]}"
66
+ raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable\nObject info: {obj_info}")
67
+ except Exception as e:
68
+ raise TypeError(f"Error serializing object: {str(e)}\nObject info: {obj_info}")
69
+
70
+
71
+ #准备世界资源,如向量数据库等
72
+ def _prepare_world_resource(self):
73
+
74
+ self.echo(">>> Loading Vector Database...")
75
+ chroma_path = os.path.join(self.matrix_path, ".matrix", "chroma_db")
76
+ self.vector_db = VectorDB(chroma_path, VECTOR_DB_COLLECTIONS_NAMES)
77
+ self.echo(">>> Vector Database Loaded.")
78
+ # 恢复 PostOffice 状态
79
+ self.post_office = PostOffice(self.matrix_path)
80
+ self.post_office.vector_db = self.vector_db
81
+ self.post_office_task = asyncio.create_task(self.post_office.run())
82
+ self.echo(">>> PostOffice Loaded and Running.")
83
+
84
+ def _prepare_agents(self):
85
+ loader = AgentLoader(self.agent_profile_path)
86
+
87
+ # 3. 自动加载所有 Agent
88
+ self.agents = loader.load_all()
89
+ for agent in self.agents.values():
90
+ agent.async_event_call_back = self.async_event_call_back
91
+
92
+
93
+
94
+ async def save_matrix(self):
95
+ """一键休眠"""
96
+ self.echo(">>> 正在冻结世界...")
97
+ self.post_office.pause()
98
+ # 2. 取消所有正在运行的agent任务
99
+ for task in self.running_agent_tasks:
100
+ task.cancel()
101
+ # 3. 等待所有任务完成
102
+ if self.running_agent_tasks:
103
+ asyncio.gather(*self.running_agent_tasks, return_exceptions=True)
104
+ self.running_agent_tasks.clear()
105
+
106
+ # 4. 停止邮局任务
107
+ if self.post_office_task:
108
+ self.post_office_task.cancel()
109
+ try:
110
+ await self.post_office_task
111
+ #asyncio.get_event_loop().run_until_complete(self.post_office_task)
112
+ except asyncio.CancelledError:
113
+ pass
114
+ self.post_office_task = None
115
+
116
+ world_state = {
117
+ "timestamp": str(datetime.now()),
118
+ "agents": {},
119
+ "post_office": []
120
+ }
121
+
122
+ # 1. 冻结所有 Agent
123
+ for agent in self.agents.values():
124
+ world_state["agents"][agent.name] = agent.dump_state()
125
+
126
+ # 2. 冻结邮局 (如果有还在路由的信)
127
+ # 逻辑同 Agent Inbox
128
+ po_queue = []
129
+ while not self.post_office.queue.empty():
130
+ email = self.post_office.queue.get_nowait()
131
+ po_queue.append(asdict(email))
132
+ self.post_office.queue.task_done()
133
+ world_state["post_office"] = po_queue
134
+ filepath = os.path.join(self.matrix_path, ".matrix", "matrix_snapshot.json")
135
+ # 3. 写入磁盘
136
+ try:
137
+ with open(filepath, "w", encoding='utf-8') as f:
138
+ json.dump(world_state, f, indent=2, ensure_ascii=False, default=self.json_serializer)
139
+ except TypeError as e:
140
+ self.logger.error(f"JSON序列化错误: {str(e)}")
141
+ # 打印world_state的结构,帮助定位问题
142
+ self.logger.debug("World state structure:")
143
+ self.logger.debug(world_state)
144
+ raise
145
+
146
+ self.echo(f">>> 世界已保存至 {filepath}")
147
+ # 9. 清理资源
148
+ self.running = False
149
+
150
+ def load_matrix(self):
151
+
152
+ """一键复活"""
153
+ self.echo(f">>> 正在从 {self.matrix_path} 恢复世界...")
154
+
155
+ matrix_snapshot_path = os.path.join(self.matrix_path,".matrix" , "matrix_snapshot.json")
156
+ os.makedirs(os.path.dirname(matrix_snapshot_path), exist_ok=True)
157
+ #加载向量数据库
158
+
159
+ try:
160
+ with open(matrix_snapshot_path, "r", encoding='utf-8') as f:
161
+ content = f.read().strip()
162
+ if not content: # 文件为空
163
+ self.echo(f">>> {matrix_snapshot_path} 为空,创建新的世界状态...")
164
+ world_state = {}
165
+ with open(matrix_snapshot_path, "w", encoding='utf-8') as f:
166
+ json.dump(world_state, f, ensure_ascii=False, indent=2)
167
+ else:
168
+ world_state = json.loads(content) # 使用 json.loads 而不是 json.load
169
+ except FileNotFoundError:
170
+ self.echo(f">>> 未找到 {matrix_snapshot_path},创建新的世界状态...")
171
+ world_state = {}
172
+ with open(matrix_snapshot_path, "w", encoding='utf-8') as f:
173
+ json.dump(world_state, f, ensure_ascii=False, indent=2)
174
+
175
+ # 1. 恢复 Agent 状态
176
+ if world_state and "agents" in world_state:
177
+
178
+ for agent in self.agents.values():
179
+ if agent.name in world_state["agents"]:
180
+ agent_data = world_state["agents"][agent.name]
181
+ agent.load_state(agent_data)
182
+ self.echo(f">>> 恢复 Agent {agent.name} 状态成功!" )
183
+
184
+
185
+
186
+
187
+ self.running_agent_tasks =[]
188
+ # 3. 注册到邮局
189
+ for agent in self.agents.values():
190
+ agent.workspace_root = self.matrix_path #设置root path
191
+ self.post_office.register(agent)
192
+ if hasattr(agent, 'vector_db'):
193
+ agent.vector_db = self.vector_db
194
+
195
+ self.running_agent_tasks.append(asyncio.create_task(agent.run()))
196
+ self.echo(f">>> Agent {agent.name} 已注册到邮局!")
197
+ self.logger.info(f"Agent {agent.name} prompt:")
198
+ self.logger.info(f"{agent.get_prompt()}")
199
+
200
+
201
+ # 4. 恢复投递
202
+ self.post_office.resume()
203
+ if world_state and 'post_office' in world_state:
204
+ for email_dict in world_state["post_office"]:
205
+ self.post_office.queue.put_nowait(Email(**email_dict))
206
+
207
+
208
+ self.running = True
209
+ self.echo(">>> 世界已恢复,系统继续运行!")
210
+ yellow_page = self.post_office.yellow_page()
211
+ self.echo(f">>> 当前世界中的 Agent 有:\n{yellow_page}")
@@ -0,0 +1,20 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import List, Dict
3
+ from dataclasses import asdict
4
+
5
+ @dataclass
6
+ class TaskSession:
7
+ session_id: str # 等于触发该任务的原始邮件 ID
8
+ original_sender: str # 谁派的活
9
+ history: List[Dict] # 对话历史
10
+ status: str = "RUNNING" # RUNNING, WAITING
11
+ user_session_id: str = None # 关联的用户会话 ID
12
+
13
+
14
+ def to_dict(self):
15
+ return asdict(self)
16
+
17
+ @classmethod
18
+ def from_dict(cls, data):
19
+ # 可以在这里做一些类型转换,比如把字符串转回 datetime
20
+ return cls(**data)
@@ -0,0 +1 @@
1
+ """Database module for Agent-Matrix framework."""
@@ -0,0 +1,79 @@
1
+ import sqlite3
2
+ import json
3
+ from datetime import datetime
4
+ from ..core.log_util import AutoLoggerMixin
5
+
6
+ class AgentMailDB(AutoLoggerMixin):
7
+ def __init__(self, db_path):
8
+ self.conn = sqlite3.connect(db_path, check_same_thread=False)
9
+ self.create_tables()
10
+
11
+ def create_tables(self):
12
+ cursor = self.conn.cursor()
13
+ # 1. 邮件归档表 (The Global Mailbox)
14
+ cursor.execute('''
15
+ CREATE TABLE IF NOT EXISTS emails (
16
+ id TEXT PRIMARY KEY,
17
+ timestamp TEXT,
18
+ sender TEXT,
19
+ recipient TEXT,
20
+ subject TEXT,
21
+ body TEXT,
22
+ in_reply_to TEXT,
23
+ user_session_id TEXT,
24
+ metadata TEXT -- 存 JSON 格式的附件或其他信息
25
+ )
26
+ ''')
27
+ self.conn.commit()
28
+
29
+ def log_email(self, email):
30
+ """记录每一封信"""
31
+ cursor = self.conn.cursor()
32
+ cursor.execute('''
33
+ INSERT OR IGNORE INTO emails
34
+ (id, timestamp, sender, recipient, subject, body, in_reply_to,user_session_id)
35
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
36
+ ''', (
37
+ email.id,
38
+ email.timestamp.isoformat(),
39
+ email.sender,
40
+ email.recipient,
41
+ email.subject,
42
+ email.body,
43
+ email.in_reply_to,
44
+ email.user_session_id
45
+ ))
46
+ self.conn.commit()
47
+
48
+ def get_mailbox(self, agent_name, limit=50):
49
+ """查询某个 Agent 的收件箱历史"""
50
+ cursor = self.conn.cursor()
51
+ cursor.execute('''
52
+ SELECT * FROM emails
53
+ WHERE recipient = ? OR sender = ?
54
+ ORDER BY timestamp DESC LIMIT ?
55
+ ''', (agent_name, agent_name, limit))
56
+ columns = [col[0] for col in cursor.description]
57
+ return [dict(zip(columns, row)) for row in cursor.fetchall()]
58
+
59
+ def get_mails_by_range(self, agent_name, user_session_id, start=0, end=1):
60
+ """查询某个Agent的指定日期范围的邮件
61
+ Args:
62
+ agent_name: Agent名称
63
+ start: 起始索引(0表示最新邮件)
64
+ end: 结束索引
65
+ Returns:
66
+ 指定范围内的邮件列表
67
+ """
68
+ cursor = self.conn.cursor()
69
+ cursor.execute('''
70
+ SELECT * FROM emails
71
+ WHERE user_session_id = ? and (recipient = ? OR sender = ?)
72
+ ORDER BY timestamp DESC
73
+ LIMIT ? OFFSET ?
74
+ ''', (user_session_id, agent_name, agent_name, end - start + 1, start))
75
+ columns = [col[0] for col in cursor.description]
76
+ return [dict(zip(columns, row)) for row in cursor.fetchall()]
77
+
78
+
79
+