# 设计决策 > 记录系统设计中的关键决策、权衡和理由。 --- ## 1. Skills 通过工具加载 vs 预先注入 ### 问题 Skills 包含大量能力描述,如何提供给 Agent? ### 方案对比 | 方案 | 优点 | 缺点 | |------|------|------| | **预先注入到 system prompt** | 实现简单 | 浪费 token,Agent 无法选择需要的 skill | | **作为工具动态加载** | 按需加载,Agent 自主选择 | 需要实现 skill 工具 | ### 决策 **选择:作为工具动态加载** **理由**: 1. **Token 效率**:只加载需要的 skill,避免浪费 context 2. **Agent 自主性**:LLM 根据任务决定需要哪些 skill 3. **可扩展性**:可以有数百个 skills,不影响单次调用的 token 消耗 4. **业界参考**:OpenCode 和 Claude API 文档都采用此方式 **参考**: - OpenCode 的 skill 系统 - Claude API 文档中的工具使用模式 --- ## 2. Skills 用文件系统 vs 数据库 ### 问题 Skills 如何存储? ### 方案对比 | 方案 | 优点 | 缺点 | |------|------|------| | **文件系统(Markdown)** | 易于编辑,支持版本控制,零依赖 | 搜索能力弱 | | **数据库** | 搜索强大,支持元数据 | 编辑困难,需要额外服务 | ### 决策 **选择:文件系统(Markdown)** **理由**: 1. **易于编辑**:直接用文本编辑器或 IDE 编辑 2. **版本控制**:通过 Git 管理 skill 的历史变更 3. **零依赖**:不需要数据库服务 4. **人类可读**:Markdown 格式,便于人工审查和修改 5. **搜索需求低**:Skill 数量有限(几十到几百个),文件扫描足够快 **实现**: ``` ~/.reson/skills/ # 全局 skills └── error-handling/SKILL.md ./project/.reson/skills/ # 项目级 skills └── api-integration/SKILL.md ``` --- ## 3. Experiences 用数据库 vs 文件 ### 问题 Experiences 如何存储? ### 方案对比 | 方案 | 优点 | 缺点 | |------|------|------| | **文件系统** | 简单,零依赖 | 搜索慢,不支持向量检索 | | **数据库(PostgreSQL + pgvector)** | 向量检索,统计分析,高性能 | 需要数据库服务 | ### 决策 **选择:数据库(PostgreSQL + pgvector)** **理由**: 1. **向量检索必需**:Experiences 需要根据任务语义匹配,文件系统无法支持 2. **统计分析**:需要追踪 success_rate, usage_count 等指标 3. **数量大**:Experiences 会随着使用不断增长(数千到数万条) 4. **动态更新**:每次执行后可能更新统计信息,数据库更适合 **实现**: ```sql CREATE TABLE experiences ( exp_id TEXT PRIMARY KEY, scope TEXT, condition TEXT, rule TEXT, evidence JSONB, confidence FLOAT, usage_count INT, success_rate FLOAT, embedding vector(1536), -- 向量检索 created_at TIMESTAMP, updated_at TIMESTAMP ); CREATE INDEX ON experiences USING ivfflat (embedding vector_cosine_ops); ``` --- ## 4. 不需要事件系统 ### 问题 是否需要事件总线(EventBus)来通知任务状态变化? ### 决策 **选择:不需要事件系统** **理由**: 1. **后台场景**:Agent 主要在后台运行,不需要实时通知 2. **已有追踪**:Trace/Step 已完整记录所有信息 3. **按需查询**:需要监控时,查询 Trace 即可 4. **简化架构**:避免引入额外的复杂性 **替代方案**: - 需要告警时,直接在 AgentRunner 中调用通知函数 - 需要实时监控时,轮询 TraceStore --- ## 5. Trace/Step 用文件系统 vs 数据库 ### 问题 Trace 和 Step 如何存储? ### 方案对比 | 方案 | 优点 | 缺点 | |------|------|------| | **文件系统(JSON)** | 简单,易于调试,可直接查看 | 搜索和分析能力弱 | | **数据库** | 搜索强大,支持复杂查询 | 初期复杂,调试困难 | ### 决策 **选择:文件系统(JSON)用于 MVP,后期可选数据库** **理由(MVP阶段)**: 1. **快速迭代**:JSON 文件易于查看和调试 2. **零依赖**:不需要数据库服务 3. **数据量小**:单个项目的 traces 数量有限 **后期迁移到数据库的时机**: - Traces 数量超过 1 万条 - 需要复杂的查询和分析(如"查找所有失败的 traces") - 需要聚合统计(如"Agent 的平均成功率") **实现接口保持一致**: ```python class TraceStore(Protocol): async def save(self, trace: Trace) -> None: ... async def get(self, trace_id: str) -> Trace: ... # ... ``` 通过 Protocol 定义,可以无缝切换实现。 --- ## 6. 工具系统的双层记忆管理 ### 问题 工具返回的数据可能很大(如 Browser-Use 的 extract 返回 10K tokens),如何避免占用过多 context? ### 方案对比 | 方案 | 优点 | 缺点 | |------|------|------| | **单一输出** | 简单 | 大数据会持续占用 context | | **双层记忆**(output + long_term_memory) | 节省 context,避免重复传输 | 稍微复杂 | ### 决策 **选择:双层记忆管理** **设计**: ```python @dataclass class ToolResult: title: str output: str # 临时内容(可能很长) long_term_memory: Optional[str] # 永久记忆(简短摘要) include_output_only_once: bool # output 是否只给 LLM 看一次 ``` **效果**: ``` [User] 提取 amazon.com 的商品价格 [Assistant] 调用 extract_page_data(url="amazon.com") [Tool] # Extracted page data <完整的 10K tokens 数据...> Summary: Extracted 10000 chars from amazon.com [User] 现在保存到文件 [Assistant] 调用 write_file(content="...") [Tool] (此时不再包含 10K tokens,只有摘要) Summary: Extracted 10000 chars from amazon.com ``` **理由**: 1. **Context 效率**:大量数据只传输一次 2. **保留关键信息**:摘要永久保留在对话历史中 3. **Browser-Use 兼容**:直接映射到 Browser-Use 的 ActionResult 设计 **参考**:Browser-Use 的 ActionResult.extracted_content 和 long_term_memory --- ## 7. 工具的域名过滤 ### 问题 某些工具只在特定网站可用(如 Google 搜索技巧),是否需要域名过滤? ### 决策 **选择:支持域名过滤(可选)** **设计**: ```python @tool(url_patterns=["*.google.com", "www.google.*"]) async def google_advanced_search(...): """仅在 Google 页面可用的工具""" ... ``` **理由**: 1. **减少 context**:在 Google 页面,35 工具 → 20 工具(节省 40%) 2. **减少 LLM 困惑**:工具数量少了,LLM 更容易选择正确工具 3. **灵活性**:默认 `url_patterns=None`,所有页面可用 **实现**: - URL 模式匹配引擎(`tools/url_matcher.py`) - 动态工具过滤(`registry.get_schemas_for_url()`) --- ## 8. 敏感数据处理 ### 问题 浏览器自动化需要输入密码、Token,但不想在对话历史中显示明文,如何处理? ### 决策 **选择:占位符替换机制** **设计**: ```python # LLM 输出占位符 arguments = { "password": "github_password", "totp": "github_2fa_bu_2fa_code" } # 执行前自动替换 sensitive_data = { "*.github.com": { "github_password": "secret123", "github_2fa_bu_2fa_code": "JBSWY3DPEHPK3PXP" # TOTP secret } } ``` **理由**: 1. **保护隐私**:对话历史中只有占位符,不泄露实际密码 2. **域名匹配**:不同网站使用不同密钥,防止密钥泄露 3. **TOTP 支持**:自动生成 2FA 验证码,无需手动输入 4. **Browser-Use 兼容**:直接映射到 Browser-Use 的敏感数据处理 **实现**: - 递归替换(`tools/sensitive.py`) - 支持嵌套结构(dict, list, str) - 自动 TOTP 生成(pyotp) **参考**:Browser-Use 的 _replace_sensitive_data --- ## 9. 工具使用统计 ### 问题 是否需要记录工具调用统计(调用次数、成功率、执行时间)? ### 决策 **选择:内建统计支持** **设计**: ```python class ToolStats: call_count: int success_count: int failure_count: int total_duration: float last_called: Optional[float] ``` **理由**: 1. **监控健康**:识别失败率高的工具 2. **性能优化**:识别执行慢的工具 3. **优化排序**:高频工具排前面,减少 LLM 选择时间 4. **零成本**:自动记录,性能影响 <0.01ms **用途**: - 监控工具健康状况(失败率、延迟) - 优化工具顺序(高频工具排前面) - 识别问题工具(低成功率、高延迟) --- ## 10. 工具参数的可编辑性 ### 问题 LLM 生成的工具参数是否允许用户编辑? ### 决策 **选择:支持可选的参数编辑** **设计**: ```python @tool(editable_params=["query", "filters"]) async def advanced_search( query: str, filters: Optional[Dict] = None, uid: str = "" ) -> ToolResult: """高级搜索(用户可编辑 query 和 filters)""" ... ``` **理由**: 1. **人类监督**:Agent 生成的参数可能不准确,允许人工微调 2. **灵活性**:大多数工具不需要编辑(默认 `editable_params=[]`) 3. **UI 集成**:前端可以展示可编辑的参数供用户修改 **适用场景**: - 搜索查询 - 内容创建 - 需要人工微调的参数 --- ## 总结 这些设计决策的核心原则: 1. **灵活性优先**:大多数特性都是可选的,保持系统简洁 2. **Token 效率**:通过动态加载、双层记忆等机制优化 context 使用 3. **可扩展性**:通过 Protocol 定义接口,便于后期切换实现 4. **安全性**:敏感数据占位符、域名匹配等机制保护隐私 5. **可观测性**:内建统计、完整追踪,便于监控和调试