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,213 @@
|
|
|
1
|
+
|
|
2
|
+
import asyncio
|
|
3
|
+
import chromadb
|
|
4
|
+
from chromadb.config import Settings
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
|
+
from typing import List, Dict, Optional
|
|
7
|
+
from contextlib import asynccontextmanager
|
|
8
|
+
from ..core.log_util import AutoLoggerMixin
|
|
9
|
+
from chromadb.utils import embedding_functions
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
# ==========================================
|
|
13
|
+
# 1. 手写一个 Async ReadWriteLock (写优先)
|
|
14
|
+
# ==========================================
|
|
15
|
+
class AsyncReadWriteLock:
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self._readers = 0
|
|
18
|
+
self._writers = 0 # 当前活跃的写者(0或1)
|
|
19
|
+
self._waiting_writers = 0 # 正在排队的写者(用于防止写饥饿)
|
|
20
|
+
self._condition = asyncio.Condition()
|
|
21
|
+
|
|
22
|
+
@asynccontextmanager
|
|
23
|
+
async def read_lock(self):
|
|
24
|
+
"""读锁上下文管理器:允许多个Reader,除非有Writer在写或在等"""
|
|
25
|
+
async with self._condition:
|
|
26
|
+
# 如果有活跃写者 OR 有写者在排队,读者必须等(写优先策略)
|
|
27
|
+
while self._writers > 0 or self._waiting_writers > 0:
|
|
28
|
+
await self._condition.wait()
|
|
29
|
+
self._readers += 1
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
yield
|
|
33
|
+
finally:
|
|
34
|
+
async with self._condition:
|
|
35
|
+
self._readers -= 1
|
|
36
|
+
# 如果没有读者了,唤醒所有等待者(包括写者)
|
|
37
|
+
if self._readers == 0:
|
|
38
|
+
self._condition.notify_all()
|
|
39
|
+
|
|
40
|
+
@asynccontextmanager
|
|
41
|
+
async def write_lock(self):
|
|
42
|
+
"""写锁上下文管理器:独占,需要等待所有Reader和Writer退出"""
|
|
43
|
+
async with self._condition:
|
|
44
|
+
self._waiting_writers += 1
|
|
45
|
+
# 等待直到没有活跃写者 且 没有活跃读者
|
|
46
|
+
while self._writers > 0 or self._readers > 0:
|
|
47
|
+
await self._condition.wait()
|
|
48
|
+
self._waiting_writers -= 1
|
|
49
|
+
self._writers = 1
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
yield
|
|
53
|
+
finally:
|
|
54
|
+
async with self._condition:
|
|
55
|
+
self._writers = 0
|
|
56
|
+
self._condition.notify_all()
|
|
57
|
+
|
|
58
|
+
# ==========================================
|
|
59
|
+
# 2. 改进后的 VectorDB
|
|
60
|
+
# ==========================================
|
|
61
|
+
class VectorDB(AutoLoggerMixin):
|
|
62
|
+
_instance = None
|
|
63
|
+
_init_lock = asyncio.Lock() # 仅用于单例创建的简单锁
|
|
64
|
+
|
|
65
|
+
def __new__(cls, *args, **kwargs):
|
|
66
|
+
if cls._instance is None:
|
|
67
|
+
cls._instance = super().__new__(cls)
|
|
68
|
+
return cls._instance
|
|
69
|
+
|
|
70
|
+
def __init__(self, persist_directory, collection_names):
|
|
71
|
+
"""
|
|
72
|
+
collection_names: 必须在初始化时传入所有需要管理的集合名称列表
|
|
73
|
+
"""
|
|
74
|
+
if hasattr(self, '_initialized') and self._initialized:
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
self.persist_directory = persist_directory
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# 初始化 Chroma Client
|
|
81
|
+
try:
|
|
82
|
+
# 设置 SQLite WAL 模式有助于并发,但 Python 层仍需控制
|
|
83
|
+
settings = Settings(anonymized_telemetry=False, allow_reset=False, )
|
|
84
|
+
self.client = chromadb.PersistentClient(path=persist_directory, settings=settings)
|
|
85
|
+
|
|
86
|
+
# --- 关键策略:读写分离线程池 ---
|
|
87
|
+
# 1. 读线程池:并发数设为 CPU 核心数 x 2 或固定值,用于 Query
|
|
88
|
+
self.read_executor = ThreadPoolExecutor(max_workers=8, thread_name_prefix="ChromaReader")
|
|
89
|
+
# 2. 写线程池:必须是 1,用于 Add/Update/Delete
|
|
90
|
+
self.write_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="ChromaWriter")
|
|
91
|
+
|
|
92
|
+
# --- 关键组件:读写锁 ---
|
|
93
|
+
self.rw_lock = AsyncReadWriteLock()
|
|
94
|
+
self.echo(">>> Loading Embedding Function...")
|
|
95
|
+
os.environ["HF_HUB_OFFLINE"] = "1" # 完全离线模式
|
|
96
|
+
os.environ["TRANSFORMERS_OFFLINE"] = "1" # transformers 库离线模式
|
|
97
|
+
self.embedding_function = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="BAAI/bge-large-zh-v1.5")
|
|
98
|
+
self.echo(">>> Embedding Function Loaded.")
|
|
99
|
+
# --- 预加载 Collections ---
|
|
100
|
+
self.collections = {}
|
|
101
|
+
if collection_names:
|
|
102
|
+
for name in collection_names:
|
|
103
|
+
# 同步获取,初始化时不需要异步,保证启动即就绪
|
|
104
|
+
self.collections[name] = self.client.get_or_create_collection(name=name, embedding_function=self.embedding_function)
|
|
105
|
+
self.logger.info(f"Collection '{name}' loaded.")
|
|
106
|
+
|
|
107
|
+
self._initialized = True
|
|
108
|
+
self.logger.info("VectorDB initialized with ReadWrite separation.")
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
self.logger.exception(f"Initialization failed: {e}")
|
|
112
|
+
raise
|
|
113
|
+
|
|
114
|
+
def get_collection(self, collection_name: str):
|
|
115
|
+
"""获取已加载的 Collection"""
|
|
116
|
+
return self.collections.get(collection_name, None)
|
|
117
|
+
|
|
118
|
+
# 辅助:获取 EventLoop
|
|
119
|
+
def _get_loop(self):
|
|
120
|
+
try:
|
|
121
|
+
return asyncio.get_running_loop()
|
|
122
|
+
except RuntimeError:
|
|
123
|
+
return asyncio.get_event_loop()
|
|
124
|
+
|
|
125
|
+
# ==========================
|
|
126
|
+
# 写操作 (串行,独占)
|
|
127
|
+
# ==========================
|
|
128
|
+
async def add_documents(self, collection_name: str, documents: List[str], metadatas=None, ids=None):
|
|
129
|
+
if collection_name not in self.collections:
|
|
130
|
+
self.logger.error(f"Collection {collection_name} not found in pre-loaded list.")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
# 1. 获取应用层写锁 (会等待所有正在进行的读操作结束)
|
|
134
|
+
async with self.rw_lock.write_lock():
|
|
135
|
+
loop = self._get_loop()
|
|
136
|
+
collection = self.collections[collection_name]
|
|
137
|
+
|
|
138
|
+
def _sync_add():
|
|
139
|
+
# 这里运行在单线程池中
|
|
140
|
+
collection.add(documents=documents, metadatas=metadatas, ids=ids)
|
|
141
|
+
|
|
142
|
+
# 2. 扔到 写线程池 执行
|
|
143
|
+
await loop.run_in_executor(self.write_executor, _sync_add)
|
|
144
|
+
self.logger.info(f"Write complete: {len(documents)} docs to {collection_name}")
|
|
145
|
+
|
|
146
|
+
# ==========================
|
|
147
|
+
# 读操作 (并行,共享)
|
|
148
|
+
# ==========================
|
|
149
|
+
async def query(self, collection_name: str, query_texts: List[str],where, n_results=10):
|
|
150
|
+
if collection_name not in self.collections:
|
|
151
|
+
raise ValueError(f"Collection {collection_name} does not exist")
|
|
152
|
+
|
|
153
|
+
# 1. 获取应用层读锁 (允许多个 Query 同时进入,但如果有 Writer 排队则等待)
|
|
154
|
+
async with self.rw_lock.read_lock():
|
|
155
|
+
loop = self._get_loop()
|
|
156
|
+
collection = self.collections[collection_name]
|
|
157
|
+
|
|
158
|
+
def _sync_query():
|
|
159
|
+
# 这里运行在多线程池中,真正的并行读取
|
|
160
|
+
return collection.query(query_texts=query_texts,where=where, n_results=n_results)
|
|
161
|
+
|
|
162
|
+
# 2. 扔到 读线程池 执行
|
|
163
|
+
results = await loop.run_in_executor(self.read_executor, _sync_query)
|
|
164
|
+
return results
|
|
165
|
+
|
|
166
|
+
async def close(self):
|
|
167
|
+
self.logger.info("Shutting down executors...")
|
|
168
|
+
self.read_executor.shutdown(wait=False)
|
|
169
|
+
self.write_executor.shutdown(wait=True) # 写操作最好等待完成
|
|
170
|
+
self.logger.info("Shutdown complete.")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ==========================
|
|
175
|
+
# 验证代码
|
|
176
|
+
# ==========================
|
|
177
|
+
async def main():
|
|
178
|
+
# 初始化:明确指定需要的集合
|
|
179
|
+
db = VectorDB(collection_names=["knowledge_base", "user_logs"])
|
|
180
|
+
|
|
181
|
+
# 1. 写入数据 (独占)
|
|
182
|
+
print("--- Start Writing ---")
|
|
183
|
+
await db.add_documents(
|
|
184
|
+
"knowledge_base",
|
|
185
|
+
documents=["Python is cool", "Asyncio is tricky"],
|
|
186
|
+
ids=["id1", "id2"]
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# 2. 并发读取测试
|
|
190
|
+
print("--- Start Concurrent Reading ---")
|
|
191
|
+
async def simulate_query(idx):
|
|
192
|
+
print(f"Query {idx} starting...")
|
|
193
|
+
# 这里的 query 会并行执行
|
|
194
|
+
res = await db.query("knowledge_base", ["Python"], n_results=1)
|
|
195
|
+
print(f"Query {idx} finished. Result ID: {res['ids'][0]}")
|
|
196
|
+
|
|
197
|
+
# 模拟 5 个并发查询
|
|
198
|
+
tasks = [simulate_query(i) for i in range(5)]
|
|
199
|
+
|
|
200
|
+
# 模拟在查询中间插入一个写入,验证写锁是否能阻断读
|
|
201
|
+
async def delayed_write():
|
|
202
|
+
await asyncio.sleep(0.01) # 让读先跑一点
|
|
203
|
+
print("!!! Urgent Write Trying to acquire lock !!!")
|
|
204
|
+
await db.add_documents("knowledge_base", ["Interrupted"], ids=["id3"])
|
|
205
|
+
print("!!! Urgent Write Finished !!!")
|
|
206
|
+
|
|
207
|
+
tasks.append(delayed_write())
|
|
208
|
+
|
|
209
|
+
await asyncio.gather(*tasks)
|
|
210
|
+
await db.close()
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# AgentMatrix 架构设计文档 (v2.0)
|
|
2
|
+
|
|
3
|
+
### 1. 核心设计哲学 (Design Philosophy)
|
|
4
|
+
|
|
5
|
+
* **拟人化与社会化 (Anthropomorphic & Social)**:
|
|
6
|
+
* Agent 是独立的“数字员工”,而非简单的函数接口。
|
|
7
|
+
* Agent 之间通过自然语言(邮件)进行社交协作,而非 API 调用。
|
|
8
|
+
* 系统容忍模糊性,Agent 像人类一样通过沟通来消除歧义。
|
|
9
|
+
|
|
10
|
+
* **双脑架构与谈判机制 (Dual-Brain & Negotiation)**:
|
|
11
|
+
* **大脑 (Brain/LLM)**: 负责高层认知、推理、人设扮演(Persona)和模糊意图生成。
|
|
12
|
+
* **小脑 (Cerebellum/SLM)**: 负责接口适配、参数检查和结构化翻译。
|
|
13
|
+
* **谈判 (Negotiation)**: 当大脑意图不清时,小脑不会强行执行,而是发起“内部质询(Internal Query)”,与大脑进行多轮对话以补全信息。
|
|
14
|
+
|
|
15
|
+
* **框架嵌入 (Frame Embedding)**:
|
|
16
|
+
* **驾驶舱隐喻 (Pilot in the Cockpit)**: 将 Agent 分为“意识(Pilot)”与“躯体(System)”。
|
|
17
|
+
* Prompt 不再是指令集,而是“物理法则”。Agent 在一个回合制的世界中,通过操作仪表盘(Output Format)来驱动躯体。
|
|
18
|
+
|
|
19
|
+
* **显式意志驱动 (Volition-Driven Flow)**:
|
|
20
|
+
* **Action is Sync**: 对大脑而言,所有动作(发邮件、读文件)都是瞬间完成的反馈。
|
|
21
|
+
* **Wait is Explicit**: 系统不再自动挂起。挂起(Wait)是一个主动的认知决策。Agent 必须显式决定“我现在没事做了,我要等待外部信号”。
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### 2. 关键概念定义 (Key Concepts)
|
|
26
|
+
|
|
27
|
+
#### A. 基础设施 (The World)
|
|
28
|
+
* **The Matrix**: 运行时容器,提供时间、空间(文件系统沙箱)和通讯设施。
|
|
29
|
+
* **PostOffice**: 异步消息总线。负责邮件投递、服务发现(Yellow Page)。
|
|
30
|
+
* **Signals**: 系统内唯一的信息载体。无论是新邮件、文件内容、执行报错还是小脑的提问,统统被封装为统一的 **Signal** 格式投喂给大脑。
|
|
31
|
+
|
|
32
|
+
#### B. 智能体结构 (Agent Anatomy)
|
|
33
|
+
1. **Pilot (The Mind)**:
|
|
34
|
+
* **System Prompt**: 定义人设(我是谁)。
|
|
35
|
+
* **Protocol Constraint**: 定义物理法则(我要怎么动)。
|
|
36
|
+
* **Capability Menu**: 只有名字和描述的能力菜单(大脑只看菜单,不看配方)。
|
|
37
|
+
2. **Cerebellum (The Interface)**:
|
|
38
|
+
* 持有完整的 **Tools Manifest**(JSON Schema)。
|
|
39
|
+
* 作为“看门人”,拦截无效意图,发起参数协商。
|
|
40
|
+
3. **Body (The Execution Unit)**:
|
|
41
|
+
* 无状态的执行器。负责将小脑输出的 JSON 映射为具体的 Python 函数调用。
|
|
42
|
+
|
|
43
|
+
#### C. 信号系统 (The Signal System)
|
|
44
|
+
Agent 与世界的交互被抽象为纯粹的 **I/O 流**:
|
|
45
|
+
* **Input**: `[INCOMING MAIL]`, `[BODY FEEDBACK]`, `[INTERNAL QUERY]`
|
|
46
|
+
* **Output**: `THOUGHT` (思维链) + `ACTION SIGNAL` (具体指令)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### 3. 运行机制 (Runtime Flow)
|
|
51
|
+
|
|
52
|
+
#### A. The "Turn-Based" Loop (主循环)
|
|
53
|
+
Agent 的生命周期由一系列离散的 **回合 (Turn)** 组成:
|
|
54
|
+
|
|
55
|
+
1. **Awake (Signal Received)**:
|
|
56
|
+
* Agent 处于冻结状态,直到收到一个 Signal。
|
|
57
|
+
2. **Reasoning (Thought)**:
|
|
58
|
+
* 大脑阅读历史上下文和最新信号。
|
|
59
|
+
* 生成 **THOUGHT**: 进行隐式的思维链推导(例如:分析局势、制定计划、检查遗漏)。
|
|
60
|
+
3. **Decision (Action Signal)**:
|
|
61
|
+
* 大脑输出 **ACTION SIGNAL**: 明确下一步要做的一个动作。
|
|
62
|
+
* *Rule*: "Pass the Baton" —— 输出动作后,大脑立即停止生成,交出控制权。
|
|
63
|
+
4. **Negotiation (The Filter)**:
|
|
64
|
+
* 小脑拦截 Signal。
|
|
65
|
+
* **Check**: 参数齐了吗?意图在能力范围内吗?
|
|
66
|
+
* **If No**: 小脑生成 `[INTERNAL QUERY]`,回滚到第 1 步(Agent 收到质询信号,被唤醒进行解释)。
|
|
67
|
+
* **If Yes**: 生成结构化 JSON。
|
|
68
|
+
5. **Execution (The Body)**:
|
|
69
|
+
* 执行具体的 Python 函数。
|
|
70
|
+
6. **Feedback Loop**:
|
|
71
|
+
* **Action Result**: 动作产生结果(如“邮件已发送”或“文件内容”),封装为 `[BODY FEEDBACK]`,**立即** 触发下一回合(回到第 1 步)。
|
|
72
|
+
* **Explicit Wait**: 如果动作是 `rest_n_wait`,则系统**挂起** Session,直到有外部新邮件进入。
|
|
73
|
+
|
|
74
|
+
#### B. 状态机流转
|
|
75
|
+
* **Active**: 正在处理 Signal,或刚刚执行完动作收到反馈。
|
|
76
|
+
* **Negotiating**: 正在与小脑进行内部参数对齐。
|
|
77
|
+
* **Waiting**: 执行了 `rest_n_wait`,处于休眠状态,等待 PostOffice 的唤醒。
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### 4. 交互契约 (The Protocol)
|
|
82
|
+
|
|
83
|
+
#### A. 思维链与动作分离
|
|
84
|
+
为了提升智能并保证稳定性,输出被严格分为两块:
|
|
85
|
+
* **THOUGHT**: 允许 Agent 自言自语。这是“暗知识”,用于推理和决策,小脑不看这一部分。
|
|
86
|
+
* **ACTION SIGNAL**: 必须是纯净的指令。这是“工单”,小脑只执行这一部分。
|
|
87
|
+
|
|
88
|
+
#### B. 显式等待 (Explicit Wait)
|
|
89
|
+
* **Fire-and-Forget**: 发送邮件 (`send_email`) 不会自动结束任务。Agent 会收到“发送成功”的反馈,然后它必须决定是继续工作(如写代码)还是休息。
|
|
90
|
+
* **Yield**: 只有调用 `rest_n_wait`,Agent 才会交出 CPU 时间片。这使得“连续多步操作”成为可能(如:发信 -> 查文件 -> 再发信 -> 休息)。
|
|
91
|
+
|
|
92
|
+
#### C. 原子化工具 (Atomic Capabilities)
|
|
93
|
+
* 工具不再包含复杂的业务逻辑(如“回复上一个人”)。
|
|
94
|
+
* 工具回归原子化(如“发给某人”)。
|
|
95
|
+
* **路由逻辑**(发给谁?)完全由大脑基于上下文在 `THOUGHT` 阶段决定,小脑只负责填空。
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### 5. 总结
|
|
100
|
+
|
|
101
|
+
AgentMatrix v2.0 的核心在于**“解放大脑,约束接口”**。
|
|
102
|
+
|
|
103
|
+
* 我们不再用复杂的代码逻辑去猜测 Agent 发完邮件后想干什么。
|
|
104
|
+
* 我们将“决定权”完全交还给 Agent 的**自由意志**(通过 THOUGHT 和 Explicit Wait)。
|
|
105
|
+
* 同时,利用 **Negotiation** 机制作为安全网,确保这种自由意志不会转化为错误的系统调用。
|
|
106
|
+
|
|
107
|
+
这是一个**以认知为中心 (Cognition-Centric)** 而非以流程为中心 (Process-Centric) 的系统架构。
|
|
108
|
+
|
|
109
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# **机制(Mechanism)与策略(Policy)的分离**。
|
|
2
|
+
|
|
3
|
+
* **机制(Framework/Library)**:提供“怎么做”的能力(例如:发信、查人、存状态)。
|
|
4
|
+
* **策略(Application)**:决定“做什么”(例如:先让Planner做计划,再让Coder写代码)。
|
|
5
|
+
|
|
6
|
+
虽然业务逻辑千变万化,但为了让 Agent 组织能“转”起来,框架层必须提供比“发邮件”更高一层的**基本原子能力**。
|
|
7
|
+
|
|
8
|
+
应该沉淀在 **Framework** 层的 5 个核心基础能力(除了 Communication 之外):
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
### 1. **Service Discovery & Capability Manifest (服务发现与能力清单)**
|
|
13
|
+
|
|
14
|
+
在现实公司里,你入职第一天,HR 会给你一张表:“这是 IT 部门的小王,修电脑找他;这是财务的小李,报销找他。”
|
|
15
|
+
|
|
16
|
+
**框架层应实现:**
|
|
17
|
+
* **动态通讯录 (Directory)**: 不仅仅是 `Name -> Agent实例` 的映射。
|
|
18
|
+
* **能力描述协议 (Manifest)**: 每个 Agent 注册时,必须通过框架提供的标准格式声明自己的能力。
|
|
19
|
+
* 例如:`{ "role": "Coder", "skills": ["python", "pandas"], "description": "处理数据清洗" }`
|
|
20
|
+
* **语义查询接口**: 允许 Agent 向框架(邮局)提问:“谁最擅长画图?”(框架负责做简单的语义匹配返回名字)。
|
|
21
|
+
|
|
22
|
+
**应用层决定:**
|
|
23
|
+
* 到底有哪些 Agent,他们的人设和具体技能是什么。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
### 2. **Structured Task Lifecycle (结构化任务生命周期)**
|
|
28
|
+
|
|
29
|
+
现在的“邮件”只是纯文本。但在正规组织里,任务是有“元数据”的。
|
|
30
|
+
|
|
31
|
+
**框架层应实现:**
|
|
32
|
+
* **标准化的工单状态 (Ticket Status)**: 所有的任务交互,除了 Body 之外,框架强制附加一个 `status` 字段:`OPEN` -> `IN_PROGRESS` -> `BLOCKED` -> `RESOLVED` -> `CLOSED`。
|
|
33
|
+
* **父子任务追踪 (Traceability)**: 框架自动维护 `parent_task_id`。如果 Planner 发起的任务 ID 是 `T-100`,Coder 为了完成它发出的求助信 ID 应该是 `T-100-1`。框架要提供 API 方便地查询 `T-100` 衍生出的所有子任务是否都 `RESOLVED` 了。
|
|
34
|
+
|
|
35
|
+
**应用层决定:**
|
|
36
|
+
* 什么情况下把状态改为 Blocked,什么算是 Resolved。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### 3. **Escalation Mechanism (异常升级/求助机制)**
|
|
41
|
+
|
|
42
|
+
如果 Coder 卡住了,或者 LLM 崩溃了,或者 Secretary 报错了,Agent 该怎么办?不能只是 Log 一下就死掉了。
|
|
43
|
+
|
|
44
|
+
**框架层应实现:**
|
|
45
|
+
* **默认的 `escalate()` 接口**: 每个 Agent 基类都有这个方法。
|
|
46
|
+
* **Chain of Command (指挥链)**: 框架允许在注册 Agent 时配置 `supervisor`(上级)。
|
|
47
|
+
* **兜底逻辑**: 当 Agent 调用 `self.escalate("不知道怎么做")` 时,框架自动把这封信转发给它的 `supervisor`(通常是 Planner 或 Human Admin),并附带报错堆栈。
|
|
48
|
+
|
|
49
|
+
**应用层决定:**
|
|
50
|
+
* 谁是谁的上级?Planner 收到下属的报错后,是重试、换人还是问用户?
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### 4. **Shared Blackboard / Workspace (公共白板)**
|
|
55
|
+
|
|
56
|
+
你提到的“Basic Memory”(大家都知道谁是谁,项目进行到哪了)。邮件是一对一的,有时候需要一个“全员可见”的地方。
|
|
57
|
+
|
|
58
|
+
**框架层应实现:**
|
|
59
|
+
* **Blackboard 对象**: 一个线程安全的、支持订阅变更的 KV 存储。
|
|
60
|
+
* **广播机制 (Broadcasting)**: 允许 Planner 发送 `@all` 的通告(例如:“项目需求变更了,大家注意”)。这不同于点对点邮件,它会塞入所有人的 Context 或作为一个高优先级 System Message。
|
|
61
|
+
* **只读/读写权限控制**: 普通 Agent 可能对白板只有读权限,只有 Planner 有写权限。
|
|
62
|
+
|
|
63
|
+
**应用层决定:**
|
|
64
|
+
* 白板上写什么?是“项目进度表”,还是“当前分析结论”,还是“全局变量”。
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### 5. **Standard Output Protocol (标准化输出协议)**
|
|
69
|
+
|
|
70
|
+
虽然输入输出是自然语言,但为了让流程可控,Agent 的输出**动作**必须被框架标准化,而不是让 LLM 随意发挥。
|
|
71
|
+
|
|
72
|
+
**框架层应实现:**
|
|
73
|
+
* **Action Primitive (原子动作定义)**: 框架定义好几种基本的“意图类型”:
|
|
74
|
+
* `SEND_MAIL` (发信)
|
|
75
|
+
* `CALL_TOOL` (调工具)
|
|
76
|
+
* `THINK` (仅记录想法,不发信)
|
|
77
|
+
* `FINISH` (当前工单完结)
|
|
78
|
+
* `ASK_USER` (请求人类介入)
|
|
79
|
+
* **Output Parser (强制解析器)**: 框架提供针对不同 LLM 的解析层,强制把 LLM 的输出映射到上述原子动作上。如果解析失败,框架自动驳回给 LLM 重试,不让 Application 层操心格式错误。
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### 总结:框架 vs 应用
|
|
84
|
+
|
|
85
|
+
| 功能点 | **框架 (AgentMailOS) 负责** | **应用 (Your Business App) 负责** |
|
|
86
|
+
| :--- | :--- | :--- |
|
|
87
|
+
| **通信** | 邮件传输、路由、死信处理 | 谁给谁发、发什么内容 |
|
|
88
|
+
| **组织** | 维护通讯录、提供群发/公告能力 | 定义组织架构(扁平还是层级) |
|
|
89
|
+
| **记忆** | 提供白板存储、提供数据库归档 | 决定白板上存什么、怎么利用历史 |
|
|
90
|
+
| **流程** | 维护 `Open/Closed` 状态机、父子任务关联 | 决定先分析还是先写代码 (SOP) |
|
|
91
|
+
| **容错** | 提供 `escalate()` 接口、自动重试机制 | 决定遇到困难时是问人还是忽略 |
|
|
92
|
+
| **动作** | 定义标准动作 (Send, Tool, Finish) | Prompt Engineering (人设) |
|
|
93
|
+
|
|
94
|
+
### “Planner 模式”在这个框架里怎么跑?
|
|
95
|
+
|
|
96
|
+
在这种设计下,你的 Planner 模式会非常顺畅:
|
|
97
|
+
|
|
98
|
+
1. **App 初始化**:在框架的 `Blackboard` 上写上:“项目目标:分析 A 股票”。
|
|
99
|
+
2. **Planner (App层逻辑)**:读取白板,决定第一步是“收集数据”。
|
|
100
|
+
3. **框架层**:Planner 发出一封 `OPEN` 状态的邮件给 Researcher。框架记录 `Parent: Project_1, Child: Task_1_1`。
|
|
101
|
+
4. **Researcher**:干活。
|
|
102
|
+
5. **框架层**:Researcher 调用 `FINISH` 动作。框架把 `Task_1_1` 标记为 `RESOLVED`,并自动通知 Planner。
|
|
103
|
+
6. **Planner**:收到通知,读取 Researcher 的成果(更新到白板),然后决定下一步。
|
|
104
|
+
|
|
105
|
+
这样,**“流程把控”**(Planner 的核心职责)变成了应用层的逻辑,而**“状态跟踪、通知回调、错误兜底”**变成了框架层的免费能力。这就是最理想的封装。
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# 对于最顶端的Planner,它需要维护的对话可能非常长,应该如何设计?
|
|
2
|
+
|
|
3
|
+
这是Agent 系统的**核心瓶颈**:**Context Window Management(上下文窗口管理)** 与 **Information Abstraction(信息抽象)** 的矛盾。
|
|
4
|
+
|
|
5
|
+
1. **底层员工(Coder/Secretary)**:任务明确、生命周期短。处理完一个具体函数或查询,Context 就可以丢弃。
|
|
6
|
+
2. **顶层管理者(Planner)**:生命周期极长(贯穿整个项目),信息密度极大。
|
|
7
|
+
|
|
8
|
+
如果简单地把所有下属的汇报邮件(包含大量代码、报错日志、数据片段)都堆进 Planner 的 Context 里,无论多大的 Context Window 都会很快爆掉,而且噪音太大,LLM 会变笨。
|
|
9
|
+
|
|
10
|
+
为了解决这个问题,我们需要引入一种**通用机制**,我称之为 **“动态文档(Living Artifact)”模式**,或者叫 **“状态驱动(State-Driven)”模式**,而不是传统的“对话驱动(History-Driven)”。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
### 1. 核心理念:Planner 不是 Chatbot,是“状态维护者”
|
|
15
|
+
|
|
16
|
+
对于 Planner,我们不能让它依赖线性的 `List<Message>` 来记忆。我们必须**强制**它维护一份 **“项目状态文档(Project State Document)”**。
|
|
17
|
+
|
|
18
|
+
* **传统模式 (Chat)**:
|
|
19
|
+
* Input: `[User: 做A, Planner: 让Coder做A, Coder: 报错xxx, Planner: 试一下y, Coder: 好了, output is Z]`
|
|
20
|
+
* *问题:Coder 的报错细节和调试过程,对 Planner 的长期记忆是垃圾信息。*
|
|
21
|
+
|
|
22
|
+
* **动态文档模式 (State-Driven)**:
|
|
23
|
+
* Planner 不记忆“对话流”,只记忆一个 **JSON/Markdown 对象**(我们称之为 **The Plan**)。
|
|
24
|
+
* 每次收到新邮件,Planner 的工作不是“回复”,而是 **“更新文档”**,然后基于文档决定下一步。
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
### 2. 机制设计:双循环结构 (The Update-Act Loop)
|
|
29
|
+
|
|
30
|
+
为了在框架层面支持这种能力,我们需要给 Planner 这种特殊的 Agent 设计一种**双步骤**的思考流。
|
|
31
|
+
|
|
32
|
+
当 Planner 收到一封邮件(比如 Coder 的汇报)时:
|
|
33
|
+
|
|
34
|
+
#### 第一步:Reflect & Update (消化与更新)
|
|
35
|
+
* **输入**:当前的项目文档(The Plan) + 新收到的邮件(The Email)。
|
|
36
|
+
* **System Prompt**:“你是一个项目经理。这是当前的项目计划状态。这是刚收到的下属汇报。请根据汇报内容,**更新**项目计划(标记完成、添加新步骤、记录关键结果)。不要回复邮件,只输出更新后的计划。”
|
|
37
|
+
* **输出**:新的项目文档(The New Plan)。
|
|
38
|
+
* **动作**:框架用“New Plan”替换掉内存里的“Old Plan”。
|
|
39
|
+
|
|
40
|
+
#### 第二步:Act & Dispatch (决策与分发)
|
|
41
|
+
* **输入**:更新后的项目文档(The New Plan)。
|
|
42
|
+
* **System Prompt**:“这是当前最新的项目计划。请检查还有哪些未完成的任务。决定下一步该给谁发邮件,发什么指令。”
|
|
43
|
+
* **输出**:一封或多封发给下属的邮件。
|
|
44
|
+
* **动作**:投递邮件。
|
|
45
|
+
|
|
46
|
+
#### 第三步:Garbage Collection (关键!丢弃上下文)
|
|
47
|
+
* **动作**:**直接丢弃** 刚才那封 Coder 发来的原始邮件和第一步的思考过程。
|
|
48
|
+
* **保留**:只保留 **最新的 Project Plan** 作为下一轮的 Context。
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### 3. 这个机制解决了什么问题?
|
|
53
|
+
|
|
54
|
+
1. **无限的生命周期**:
|
|
55
|
+
* 因为 Planner 每次只需要看“最新的 Plan”,而不需要看“过去 100 轮的邮件”。Plan 的大小通常是可控的(几 KB),而邮件历史是无限增长的。
|
|
56
|
+
* Planner 永远处于 **Context 几乎为空** 的清爽状态,只关注当下状态。
|
|
57
|
+
|
|
58
|
+
2. **自动的信息压缩(Compression)**:
|
|
59
|
+
* Coder 发来:“我试了库 A 报错,试了库 B 报错,最后用库 C 成功了,结果是 X。”
|
|
60
|
+
* Planner 在“第一步”更新文档时,只会把 Plan 更新为:`Task 1: Completed. Result: X. (Note: Library C used)`。
|
|
61
|
+
* 中间的报错过程被自动过滤掉了。这就是**抽象**。
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### 4. 框架内如何实现?(Framework Implementation)
|
|
66
|
+
|
|
67
|
+
这不应该是 Application 层的瞎写,而应该是 Framework 提供的标准能力。
|
|
68
|
+
|
|
69
|
+
我们给 `BaseAgent` 扩展一个子类 `StatefulAgent`(专门给 Planner 用)。
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# agents/stateful.py
|
|
73
|
+
|
|
74
|
+
class StatefulAgent(BaseAgent):
|
|
75
|
+
def __init__(self, name, backend):
|
|
76
|
+
super().__init__(name, backend)
|
|
77
|
+
# 这就是那个“动态文档”,可以是 JSON,也可以是 Markdown
|
|
78
|
+
self.project_state = {
|
|
79
|
+
"objective": "",
|
|
80
|
+
"tasks": [],
|
|
81
|
+
"key_findings": []
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async def step(self, session: TaskSession):
|
|
85
|
+
# 获取最新的一封信(导致这次激活的那封)
|
|
86
|
+
incoming_mail = session.history[-1]['content']
|
|
87
|
+
|
|
88
|
+
# === Phase 1: State Update (只更新记忆,不发信) ===
|
|
89
|
+
update_prompt = f"""
|
|
90
|
+
当前状态: {json.dumps(self.project_state)}
|
|
91
|
+
收到消息: {incoming_mail}
|
|
92
|
+
|
|
93
|
+
任务:
|
|
94
|
+
1. 根据消息更新任务状态 (TODO -> DONE)。
|
|
95
|
+
2. 将关键结果摘要写入 key_findings。
|
|
96
|
+
3. 如果有新问题,添加到 tasks。
|
|
97
|
+
|
|
98
|
+
输出: 仅输出更新后的 JSON 状态。
|
|
99
|
+
"""
|
|
100
|
+
new_state_json = await self.brain.think(self.name, [{"role": "user", "content": update_prompt}])
|
|
101
|
+
|
|
102
|
+
# 更新内存
|
|
103
|
+
try:
|
|
104
|
+
self.project_state = json.loads(new_state_json)
|
|
105
|
+
# 存入数据库快照,方便回溯
|
|
106
|
+
self.save_state_snapshot()
|
|
107
|
+
except:
|
|
108
|
+
print("State update failed")
|
|
109
|
+
|
|
110
|
+
# === Phase 2: Action Decision (只看状态,不看历史邮件) ===
|
|
111
|
+
action_prompt = f"""
|
|
112
|
+
当前项目状态: {json.dumps(self.project_state)}
|
|
113
|
+
|
|
114
|
+
任务:
|
|
115
|
+
基于当前状态,判断下一步该做什么?
|
|
116
|
+
- 如果所有任务完成,回复用户。
|
|
117
|
+
- 如果有 OPEN 的任务,给相应的 Worker 发邮件。
|
|
118
|
+
|
|
119
|
+
输出: [Action: EMAIL] ...
|
|
120
|
+
"""
|
|
121
|
+
response = await self.brain.think(self.name, [{"role": "user", "content": action_prompt}])
|
|
122
|
+
|
|
123
|
+
# ... 解析 response 并发信 (同 WorkerAgent) ...
|
|
124
|
+
|
|
125
|
+
# === Phase 3: Context Clearing ===
|
|
126
|
+
# 极其激进的策略:清空 Session History,只保留最新的状态
|
|
127
|
+
# 因为所有必要信息都已经“沉淀”到 project_state 里了
|
|
128
|
+
session.history = []
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 5. 对比总结:Planner vs Secretary
|
|
132
|
+
|
|
133
|
+
这下角色的差异就非常明显了:
|
|
134
|
+
|
|
135
|
+
| 特性 | **Secretary (工具人)** | **Worker (Coder/Analyst)** | **Planner (管理者)** |
|
|
136
|
+
| :--- | :--- | :--- | :--- |
|
|
137
|
+
| **核心机制** | Function Calling | ReAct / Standard Chat | **State Update Loop** |
|
|
138
|
+
| **记忆类型** | 无记忆 (Stateless) | 短期会话 (Session History) | **长期结构化状态 (Artifact)** |
|
|
139
|
+
| **Context策略**| 用完即弃 | 任务结束即弃 | **不断重写与覆盖** |
|
|
140
|
+
| **主要能力** | 翻译 (NL $\to$ Code) | 执行 (NL $\to$ Output) | **归纳与调度 (Info $\to$ State)** |
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
* **Planner** 必须继承自 `StatefulAgent`。
|
|
145
|
+
* 它的 System Prompt 不再是“你是一个助手”,而是定义 **State Schema(状态的结构)**。
|
|
146
|
+
* 例如:“你的内存由 `Plan List` 和 `Data Summary` 组成。你永远不要相信你的聊天记录,只相信你的内存。”
|
|
147
|
+
|
|
148
|
+
这种设计让 Planner 即使运行了一个月,处理了 1000 封邮件,它每次发令时发给 LLM 的 Token 数可能依然只有 2k (Current State),而不是 200k (Full History)。这才是能够长期稳定运行的关键。
|