smart_brain 0.1.2 → 0.3.0

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/MEMPAL_GUIDE.md +1074 -0
  4. data/README.en.md +206 -173
  5. data/README.md +593 -173
  6. data/config/brain.yml +69 -1
  7. data/conversation_demo.rb +438 -438
  8. data/db/migrate/002_turn_events_payload.sql +9 -0
  9. data/db/migrate/003_tiers_and_lifecycle.sql +28 -0
  10. data/db/migrate/004_kg_edges.sql +30 -0
  11. data/db/migrate/005_domains_and_memory_scopes.sql +163 -0
  12. data/docs/coding_todo.md +139 -0
  13. data/docs/context_package.md +220 -0
  14. data/docs/evidence_pack.md +190 -0
  15. data/docs/gap_vs_mempal.md +161 -0
  16. data/docs/installation.md +198 -0
  17. data/docs/mcp.md +93 -0
  18. data/docs/media_memory_schema.md +271 -0
  19. data/docs/memory_types.md +278 -0
  20. data/docs/multi_scope_memory_refactor_plan.md +483 -0
  21. data/docs/multi_scope_migration.md +65 -0
  22. data/docs/policies.md +308 -0
  23. data/docs/retrieval_plan.md +233 -0
  24. data/docs/smartbrain_design.md +299 -0
  25. data/docs/user_guide.md +547 -0
  26. data/example.rb +91 -91
  27. data/examples/01_memory_basic.rb +57 -0
  28. data/examples/02_governance.rb +63 -0
  29. data/examples/03_postgres_persistence.rb +63 -0
  30. data/examples/04_ollama_llm.rb +69 -0
  31. data/examples/05_smart_rag_integration.rb +79 -0
  32. data/examples/06_multi_scope_memory.rb +50 -0
  33. data/examples/07_media_memory.rb +53 -0
  34. data/examples/README.md +49 -0
  35. data/exe/smart_brain +168 -0
  36. data/lib/smart_brain/adapters/smart_rag/direct_client.rb +57 -5
  37. data/lib/smart_brain/adapters/smart_rag/http_client.rb +118 -5
  38. data/lib/smart_brain/adapters/smart_rag/http_transport.rb +138 -0
  39. data/lib/smart_brain/adapters/smart_rag/media_metadata_extractor.rb +255 -0
  40. data/lib/smart_brain/adapters/smart_rag/null_client.rb +44 -2
  41. data/lib/smart_brain/adapters/smart_rag/scope_filter.rb +60 -0
  42. data/lib/smart_brain/configuration.rb +60 -0
  43. data/lib/smart_brain/consolidator/working_summary.rb +80 -12
  44. data/lib/smart_brain/context_composer/composer.rb +40 -3
  45. data/lib/smart_brain/contracts/retrieval_plan.rb +10 -0
  46. data/lib/smart_brain/contracts/scope_context.rb +46 -0
  47. data/lib/smart_brain/contracts/scope_ref.rb +25 -0
  48. data/lib/smart_brain/db.rb +109 -0
  49. data/lib/smart_brain/event_store/in_memory.rb +6 -2
  50. data/lib/smart_brain/event_store/postgres.rb +199 -0
  51. data/lib/smart_brain/fusion/merger.rb +31 -2
  52. data/lib/smart_brain/governance/briefing.rb +146 -0
  53. data/lib/smart_brain/governance/fact_check.rb +110 -0
  54. data/lib/smart_brain/governance/knowledge_graph.rb +60 -0
  55. data/lib/smart_brain/governance/lifecycle.rb +225 -0
  56. data/lib/smart_brain/governance/tiers.rb +60 -0
  57. data/lib/smart_brain/memory_extractor/extractor.rb +25 -7
  58. data/lib/smart_brain/memory_store/in_memory.rb +202 -17
  59. data/lib/smart_brain/memory_store/postgres.rb +500 -0
  60. data/lib/smart_brain/model_provider/base.rb +87 -0
  61. data/lib/smart_brain/model_provider/factory.rb +49 -0
  62. data/lib/smart_brain/model_provider/ollama.rb +60 -0
  63. data/lib/smart_brain/model_provider/openai.rb +60 -0
  64. data/lib/smart_brain/model_provider/stub.rb +26 -0
  65. data/lib/smart_brain/model_provider.rb +7 -0
  66. data/lib/smart_brain/observability/tracker.rb +39 -1
  67. data/lib/smart_brain/retrievers/exact_retriever.rb +6 -0
  68. data/lib/smart_brain/retrievers/memory_retriever.rb +59 -5
  69. data/lib/smart_brain/runtime.rb +306 -16
  70. data/lib/smart_brain/scopes/conflict_resolver.rb +67 -0
  71. data/lib/smart_brain/scopes/registry.rb +133 -0
  72. data/lib/smart_brain/scopes/resolver.rb +32 -0
  73. data/lib/smart_brain/server/http_app.rb +143 -0
  74. data/lib/smart_brain/server/mcp_server.rb +385 -0
  75. data/lib/smart_brain/server/service.rb +129 -0
  76. data/lib/smart_brain/support/levenshtein.rb +35 -0
  77. data/lib/smart_brain/version.rb +5 -5
  78. data/lib/smart_brain.rb +93 -35
  79. metadata +100 -54
data/README.md CHANGED
@@ -1,173 +1,593 @@
1
- # SmartBrain
2
-
3
- SmartBrain 是一个面向 Agent 的记忆运行时(Memory Runtime)与上下文编排器(Context Composer)。
4
-
5
- 它的核心职责:
6
- - `commit_turn`:记录事件真相并沉淀结构化记忆
7
- - `compose_context`:在每轮请求前组装最小充分上下文
8
- - 联动 SmartRAG:对话记忆由 SmartBrain 管理,资源检索由 SmartRAG 提供
9
-
10
- ## 当前进展
11
-
12
- 当前仓库已实现并打通:
13
- - `commit_turn` / `compose_context` 主链路
14
- - Retention / Consolidation / Retrieval / Composition 策略
15
- - 检索器:exact + relational
16
- - 融合层:去重、规则重排、多样性、预算截断
17
- - SmartRAG 适配器:`NullClient` / `HttpClient` / `DirectClient`
18
- - 契约与可观测:`request_id` / `plan_id` / `context_id` 全链路追踪
19
- - RSpec 测试(单元 + 集成 + 回归)
20
-
21
- ## 项目结构
22
-
23
- - `lib/smart_brain.rb`:入口 API
24
- - `lib/smart_brain/runtime.rb`:主运行时编排
25
- - `lib/smart_brain/contracts/`:RetrievalPlan / EvidencePack / ContextPackage 校验
26
- - `lib/smart_brain/observability/`:日志与指标
27
- - `lib/smart_brain/event_store/`:事件存储(当前内存实现)
28
- - `lib/smart_brain/memory_store/`:记忆存储(当前内存实现)
29
- - `lib/smart_brain/retrievers/`:exact/relational 检索
30
- - `lib/smart_brain/fusion/`:多源融合
31
- - `lib/smart_brain/context_composer/`:上下文装配
32
- - `lib/smart_brain/adapters/smart_rag/`:SmartRAG 适配层
33
- - `config/brain.yml`:策略配置
34
- - `example.rb`:SmartBrain + SmartAgent + SmartPrompt + SmartRAG 联动示例
35
- - `docs/`:设计与协议文档
36
-
37
- ## 安装
38
-
39
- ```bash
40
- bundle install
41
- ```
42
-
43
- 如遇本地权限或 shared gem 污染,建议:
44
-
45
- ```bash
46
- bundle config set --local path 'vendor/bundle'
47
- bundle config set --local disable_shared_gems 'true'
48
- ```
49
-
50
- ## 快速开始(仅 SmartBrain)
51
-
52
- ```ruby
53
- require_relative 'lib/smart_brain'
54
-
55
- SmartBrain.configure
56
-
57
- SmartBrain.commit_turn(
58
- session_id: 'demo',
59
- turn_events: {
60
- messages: [
61
- { role: 'user', content: '请记住:默认数据库是 Postgres。' },
62
- { role: 'assistant', content: '已记录。' }
63
- ],
64
- decisions: [
65
- { key: 'decision:smartbrain:storage', decision: 'Use Postgres by default' }
66
- ]
67
- }
68
- )
69
-
70
- context = SmartBrain.compose_context(
71
- session_id: 'demo',
72
- user_message: '继续并总结关键结论'
73
- )
74
-
75
- puts context[:context_id]
76
- puts context.dig(:debug, :trace, :request_id)
77
- puts context.dig(:debug, :trace, :plan_id)
78
- ```
79
-
80
- ## SmartRAG 集成方式
81
-
82
- ### 1) NullClient(默认)
83
-
84
- 不配置 `smart_rag_client` 时,资源证据为空,仅使用记忆侧证据。
85
-
86
- ### 2) HttpClient
87
-
88
- ```ruby
89
- transport = lambda do |plan, timeout_seconds:|
90
- {
91
- plan_id: 'p1',
92
- supports_language_filter: true,
93
- evidences: []
94
- }
95
- end
96
-
97
- client = SmartBrain::Adapters::SmartRag::HttpClient.new(transport: transport, timeout_seconds: 2)
98
- SmartBrain.configure(smart_rag_client: client)
99
- ```
100
-
101
- ### 3) DirectClient(当前示例使用)
102
-
103
- ```ruby
104
- require 'smart_rag'
105
- require_relative 'lib/smart_brain/adapters/smart_rag/direct_client'
106
-
107
- rag_config = SmartRAG::Config.load(ENV.fetch('SMARTRAG_CONFIG_PATH'))
108
- rag = SmartRAG::SmartRAG.new(rag_config)
109
- client = SmartBrain::Adapters::SmartRag::DirectClient.new(rag: rag)
110
-
111
- SmartBrain.configure(smart_rag_client: client)
112
- ```
113
-
114
- ## example.rb 说明(已更新)
115
-
116
- `example.rb` 演示完整链路:
117
- 1. SmartBrain `compose_context`
118
- 2. SmartAgent 通过 `call_worker` 调用 SmartPrompt worker
119
- 3. SmartBrain `commit_turn`
120
- 4. 打印 `evidence(memory/resource)` 验证 SmartRAG 是否参与
121
-
122
- 示例依赖的本地文件:
123
- - `config/example_agent.yml`
124
- - `config/example_llm.yml`
125
- - `agents/brain_assistant.rb`
126
- - `workers/brain_assistant.rb`
127
- - `templates/brain_assistant.erb`
128
-
129
- 运行:
130
-
131
- ```bash
132
- bundle exec ruby example.rb
133
- ```
134
-
135
- ## 核心 API
136
-
137
- ### `SmartBrain.configure(config_path: nil, smart_rag_client: nil, clock: -> { Time.now.utc })`
138
- 初始化运行时并注入 SmartRAG 客户端(可选)。
139
-
140
- ### `SmartBrain.commit_turn(session_id:, turn_events:)`
141
- 写入事件、抽取记忆、冲突处理、摘要更新。
142
-
143
- ### `SmartBrain.compose_context(session_id:, user_message:, agent_state: {})`
144
- 生成 `ContextPackage`,内部包含检索计划与证据融合结果。
145
-
146
- ### `SmartBrain.diagnostics`
147
- 返回 compose/commit 观测日志与指标快照。
148
-
149
- ## 测试
150
-
151
- ```bash
152
- rspec
153
- ```
154
-
155
- ## 常见问题
156
-
157
- ### 1) `cannot load such file -- sequel/extensions/pgvector`
158
- 当前示例已在 `example.rb` 做兼容处理(`Sequel.extension 'pgvector'` + 去除 `database.extensions` 连接参数)。
159
-
160
- ### 2) `Config file not found: config/llm_config.yml`
161
- `example.rb` 已将 SmartRAG 里 EmbeddingService 的 `config_path` 注入为 `./config/example_llm.yml`。
162
-
163
- ### 3) `ruby-lsp: not found`
164
- ```bash
165
- gem install --user-install ruby-lsp debug
166
- ```
167
- 并将用户 gem bin 加入 `PATH`。
168
-
169
- ## 路线图
170
-
171
- - EventStore/MemoryStore 从内存实现切换到 Postgres 实现
172
- - 接入真实 reranker / embedding 模型
173
- - 完善 SmartRAG ingest 与跨会话评估工具
1
+ # SmartBrain
2
+
3
+ SmartBrain(v0.2.0)是一个面向 Agent 的**记忆运行时(Memory Runtime)与上下文编排器(Context Composer)**。
4
+
5
+ 在多轮 Agent 对话中,SmartBrain 解决的核心问题是:如何高效地记录、检索、融合记忆,并装配出"最小充分"的上下文发给 LLM。
6
+
7
+ ## 核心职责
8
+
9
+ - **`commit_turn`**:记录事件真相并沉淀结构化长期记忆(写链路)
10
+ - **`compose_context`**:在每轮请求前生成最小充分上下文(读 + 装配链路)
11
+ - **联动 SmartRAG**:对话记忆由 SmartBrain 管理(EventStore / MemoryStore),资源检索由 SmartRAG 提供(适配层)
12
+
13
+ ## 架构边界
14
+
15
+ SmartBrain Agent 的可用信息分为两类,分工明确:
16
+
17
+ | 类别 | 管理者 | 内容 | 特点 |
18
+ |------|--------|------|------|
19
+ | **对话记忆** (Conversation Memory) | SmartBrain | 原始事件(turn/messages/tool_calls/refs)、结构化记忆(profile/preferences/tasks/decisions/entities/events)、滚动摘要(working_summary) | 强时序、更新频繁、有写入门控与冲突管理 |
20
+ | **资源记忆** (Resource Memory) | SmartRAG | 文档/网页/代码仓库等长期资源 | 相对静态、内容大、需要高质量检索 |
21
+
22
+ > SmartRAG 是 SmartBrain 的"资源知识库后端"。SmartBrain 不复制 SmartRAG 的索引能力,只通过契约适配层调用它。
23
+
24
+ ## 当前进展
25
+
26
+ 当前仓库已实现并打通:
27
+
28
+ - `commit_turn` / `compose_context` 主链路
29
+ - Retention / Consolidation / Retrieval / Composition 四类策略
30
+ - 检索器:exact(关键词匹配) + relational(实体/引用关联)
31
+ - 融合层:去重、词重叠加分重排、多样性约束、预算截断、memory/resource 比例控制
32
+ - SmartRAG 适配器:`NullClient`(默认空)/ `HttpClient`(HTTP 远程)/ `DirectClient`(进程内直接调用)
33
+ - 契约与可观测:`request_id` → `plan_id` → `context_id` 全链路追踪
34
+ - RSpec 测试(单元 + 集成 + PostgreSQL 回归)
35
+
36
+ ## 安装
37
+
38
+ ### 从 RubyGems 安装(推荐给最终用户)
39
+
40
+ ```bash
41
+ gem install smart_brain
42
+ ```
43
+
44
+ 装完即可用,默认零外部依赖(memory 后端 + stub LLM):
45
+
46
+ ```bash
47
+ smart_brain --version
48
+ smart_brain status
49
+ ```
50
+
51
+ 完整安装/配置/可选能力(PostgreSQL 持久化、Ollama/OpenAI LLM、SmartRAG 资源检索)见 **[`docs/installation.md`](docs/installation.md)**。
52
+
53
+ ### 从源码开发
54
+
55
+ ```bash
56
+ bundle install
57
+ ```
58
+
59
+ 如遇本地权限或 shared gem 污染:
60
+
61
+ ```bash
62
+ bundle config set --local path 'vendor/bundle'
63
+ bundle config set --local disable_shared_gems 'true'
64
+ ```
65
+
66
+ ## 核心 API
67
+
68
+ SmartBrain 提供以下核心工作流 API:
69
+
70
+ ### `SmartBrain.configure(config_path: nil, smart_rag_client: nil, clock: -> { Time.now.utc })`
71
+
72
+ 初始化运行时并注入 SmartRAG 客户端(可选)。
73
+
74
+ - `config_path`:策略配置文件路径,默认 `config/brain.yml`
75
+ - `smart_rag_client`:SmartRAG 适配器实例,默认 `NullClient`(资源证据为空)
76
+ - `clock`:时间源函数,默认 `Time.now.utc`
77
+
78
+ ### `SmartBrain.commit_turn(session_id:, turn_events:, domain_id: nil, scope_context: nil)`
79
+
80
+ 写入事件、抽取记忆、冲突处理、摘要更新。返回 `CommitResult`。
81
+
82
+ **`turn_events` 支持的事件类型:**
83
+
84
+ | 字段 | 类型 | 说明 | 示例 |
85
+ |------|------|------|------|
86
+ | `messages` | Array | 消息列表,每条含 `role` 和 `content` | `[{ role: 'user', content: '...' }]` |
87
+ | `decisions` | Array | 决策/选择/承诺 | `[{ key: 'decision:pg:pool_size', decision: '...' }]` |
88
+ | `tasks` | Array | 任务,含状态流转 | `[{ key: 'task:brain:mvp', status: 'doing' }]` |
89
+ | `goals` | Array | 长期目标 | `[{ key: 'goal:learn:ruby', goal: '...' }]` |
90
+ | `entities` | Array | 重要实体 | `[{ key: 'entity:lang:ruby', name: 'Ruby', canonical: 'ruby', kind: 'language', remember: true }]` |
91
+ | `preferences` | Array | 偏好,需 `confirmed: true` 才写入长期记忆 | `[{ key: 'pref:writing:tone', value: '...', confirmed: true }]` |
92
+ | `events` | Array | 里程碑/异常等高价值事件 | — |
93
+ | `refs` | Array | 文件/URL/产物引用 | — |
94
+ | `retractions` | Array | 撤回旧记忆 | `[{ type: 'decisions', key: 'decision:old:topic' }]` |
95
+
96
+ **写入门控策略:**
97
+ - **必写**:`tasks`、`decisions`(confidence: 0.9)
98
+ - **条件写**:`goals`(需显示声明)、`preferences`(需 `confirmed: true`)、`entities`(需出现频率 ≥ 2 或含 URL/路径等结构信号)
99
+ - **不写入**:闲聊、未确认推测
100
+
101
+ **冲突处理:**
102
+ - 覆盖型(`preferences/goals/tasks`):新值写入,旧值 → `status=superseded`
103
+ - 多版本并存(`decisions/events`):保留历史
104
+ - 撤回(`retracted`):旧条目 → `status=retracted`
105
+
106
+ **返回值:**
107
+
108
+ ```ruby
109
+ {
110
+ ok: true,
111
+ commit_id: "uuid",
112
+ session_id: "...",
113
+ turn_id: "uuid",
114
+ memory_written: { count: 3, items: [...], conflicts: [...] },
115
+ summary: { triggered: true, trigger_reason: "turn_threshold", text: "..." },
116
+ explain: { retention: [...], conflicts: [...], summary: {...} }
117
+ }
118
+ ```
119
+
120
+ ### `SmartBrain.compose_context(session_id:, user_message:, agent_state: {}, domain_id: nil, scope_context: nil)`
121
+
122
+ 生成 `ContextPackage`,内部执行 5 阶段流水线:
123
+
124
+ ```
125
+ Plan → Retrieve(双路并行)→ Fuse → Compose → ContextPackage
126
+ ```
127
+
128
+ 1. **Plan**:`RetrievalPlanner` 分析意图,生成 `RetrievalPlan`(主查询 + 扩展查询),自动判断是否启用资源检索(`enable_resource_retrieval: 'auto'` 时,关键词"查资料/引用/论文/文档/compare/对比/来源"触发)
129
+ 2. **Retrieve**:双路并行 —
130
+ - 记忆侧:`ExactRetriever`(词重叠匹配,支持中英文分词)+ `RelationalRetriever`(实体/引用关联)
131
+ - 资源侧:通过 SmartRAG 适配器检索文档资源(仅当 planner 启用时)
132
+ 3. **Fuse**:`Merger` 归一化 → 去重 → 词重叠加分重排 → 多样性约束(同文档 ≤ 3 条,同来源 ≤ 2 条)→ 预算裁剪(总条数 ≤ `evidence_max_items`,snippet ≤ `max_snippet_chars`)→ memory/resource 比例控制(默认 40/60)
133
+ 4. **Compose**:`ContextComposer` 按固定槽位装配:
134
+ ```
135
+ system_blocks developer_blocks → working_summary →
136
+ recent_turns → evidence → user_message
137
+ ```
138
+ 5. **输出**:`ContextPackage`(经过契约校验)
139
+
140
+ ### Scope 记忆
141
+
142
+ 0.2.0 支持在同一 domain 内联合读取 `global/project/expert/task` 记忆,并将长期记忆写入明确授权的 scope:
143
+
144
+ ```ruby
145
+ scope_context = {
146
+ read: [
147
+ { type: 'global', id: 'default' },
148
+ { type: 'project', id: 'project-001' },
149
+ { type: 'task', id: 'task-030' }
150
+ ],
151
+ write: [
152
+ { type: 'project', id: 'project-001' },
153
+ { type: 'task', id: 'task-030' }
154
+ ],
155
+ default_write: { type: 'task', id: 'task-030' }
156
+ }
157
+
158
+ SmartBrain.commit_turn(
159
+ domain_id: 'tenant-a', session_id: 'conversation-1', scope_context: scope_context,
160
+ turn_events: { decisions: [{ key: 'decision:db', decision: 'use PostgreSQL' }] }
161
+ )
162
+ ```
163
+
164
+ `write` 必须是 `read` 的子集,`default_write` 必须包含在 `write` 中。单条结构化记忆可以用 `scope_ref` 覆盖默认写 scope,但目标仍须在 `write` 中。提供 `scope_context` 时必须同时提供 `domain_id`;两者都省略时继续使用隔离的 `legacy/session:<session_id>`,兼容 0.1.x 调用。
165
+
166
+ **返回值(ContextPackage):**
167
+
168
+ ```ruby
169
+ {
170
+ version: '0.1',
171
+ context_id: "uuid", # 本次装配唯一 ID
172
+ session_id: "...",
173
+ working_summary: "...", # 滚动摘要文本
174
+ recent_turns: [...], # 最近 N 轮对话
175
+ evidence: [ # 融合后的证据列表
176
+ {
177
+ id: "...",
178
+ source: 'memory' | 'resource',
179
+ source_uri: "...",
180
+ title: "...",
181
+ snippet: "...",
182
+ mode: 'exact' | 'relational' | 'hybrid',
183
+ score: 0.85,
184
+ ref: { memory_item_id: "..." }
185
+ }
186
+ ],
187
+ user_message: { role: 'user', content: "..." },
188
+ constraints: {
189
+ token_budget: { limit: 8000, used_estimate: 1200 },
190
+ diversity: { by_document: 3, by_source: 2 },
191
+ truncation: { snippets_max_chars: 800, recent_turns_max: 8 }
192
+ },
193
+ debug: {
194
+ trace: { request_id: "uuid", plan_id: "uuid", context_id: "uuid" },
195
+ planner: { purpose: "qa", queries: ["..."] },
196
+ why_selected: ["item-1 score=0.85 source=memory"],
197
+ ignored: [],
198
+ dropped: [{ id: "...", reason: "diversity" }]
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### `SmartBrain.diagnostics`
204
+
205
+ 返回 compose/commit 观测日志与指标快照:
206
+
207
+ ```ruby
208
+ {
209
+ compose_logs: [...],
210
+ commit_logs: [...],
211
+ metrics: {
212
+ compose_p95_ms: 45.2,
213
+ memory_resource_ratio: "3/2",
214
+ token_over_budget_rate: 0.0
215
+ }
216
+ }
217
+ ```
218
+
219
+ 全链路追踪:每个请求有 `request_id` → `plan_id` → `context_id` 三连 ID,贯穿 Plan → Retrieve → Fuse → Compose 全流程。
220
+
221
+ ## 策略配置
222
+
223
+ SmartBrain 的四类策略通过 `config/brain.yml` 配置:
224
+
225
+ | 策略类别 | 生效点 | 关键参数 |
226
+ |---------|--------|---------|
227
+ | **Retention**(写入门控) | `commit_turn` | `entity_gate: { window_turns: 20, freq_threshold: 2 }`、`confidence: { user_asserted: 0.8, tool_derived: 0.9, inferred: 0.6 }`、`summarize_after_turns: 12` |
228
+ | **Retrieval**(检索) | `compose_context` | `top_k: 30`、`candidate_k: 200`、`enable_resource_retrieval: auto`、`query_expansion: { enabled: true, max_queries: 8 }` |
229
+ | **Composition**(装配) | `compose_context` | `recent_turns_max: 8`、`evidence_max_items: 12`、`max_snippet_chars: 800`、`diversity: { by_document: 3, by_source_uri: 2 }`、`memory_resource_ratio: "40/60"` |
230
+ | **Observability**(可观测) | 全局 | `trace: true` |
231
+
232
+ ## 项目结构
233
+
234
+ ```
235
+ lib/smart_brain.rb # Ruby 入口 API
236
+ lib/smart_brain/version.rb # 版本号
237
+ lib/smart_brain/configuration.rb # YAML 配置加载
238
+ lib/smart_brain/runtime.rb # 主运行时编排(commit_turn / compose_context)
239
+ lib/smart_brain/contracts/ # 契约校验
240
+ retrieval_plan.rb # RetrievalPlan 校验
241
+ evidence_pack.rb # EvidencePack 校验
242
+ context_package.rb # ContextPackage 校验
243
+ lib/smart_brain/event_store/
244
+ in_memory.rb # 事件存储(当前内存实现,含 entity_frequencies 统计)
245
+ lib/smart_brain/memory_store/
246
+ in_memory.rb # 记忆存储(当前内存实现,含 upsert 冲突管理)
247
+ lib/smart_brain/memory_extractor/
248
+ extractor.rb # 从事件中抽取结构化记忆(写入门控)
249
+ lib/smart_brain/consolidator/
250
+ working_summary.rb # 滚动摘要维护(turn_threshold / token_pressure / stage_event 触发)
251
+ lib/smart_brain/retrieval_planner/
252
+ planner.rb # 生成 RetrievalPlan(query expansion + 资源检索启停判断 + filter hints)
253
+ lib/smart_brain/retrievers/
254
+ exact_retriever.rb # 词重叠关键词匹配(支持中英文)
255
+ relational_retriever.rb # 实体/引用关联检索
256
+ memory_retriever.rb # 记忆检索门面(exact + relational 合并)
257
+ lib/smart_brain/fusion/
258
+ merger.rb # 多源融合(归一化 → 去重 → 词重叠加分重排 → 多样性 → 预算截断)
259
+ lib/smart_brain/context_composer/
260
+ composer.rb # 固定槽位上下文装配 + token 估算
261
+ lib/smart_brain/adapters/smart_rag/
262
+ null_client.rb # 默认空适配器(资源证据为空)
263
+ http_client.rb # HTTP 远程调用适配器(支持超时降级)
264
+ direct_client.rb # 进程内直接调用适配器
265
+ lib/smart_brain/observability/
266
+ tracker.rb # 日志与指标(P95 / memory_resource_ratio / token_over_budget_rate)
267
+ config/
268
+ example_llm.yml # LLM 配置示例(ollama + silicon_flow)
269
+ docs/
270
+ smartbrain_design.md # 总体设计文档
271
+ policies.md # 策略规范文档
272
+ memory_types.md # 记忆类型规范(9 种 type + key 规则)
273
+ context_package.md # ContextPackage 协议
274
+ retrieval_plan.md # RetrievalPlan 协议
275
+ evidence_pack.md # EvidencePack 协议
276
+ media_memory_schema.md # 媒体记忆契约、生产能力和迁移约束
277
+ spec/
278
+ spec_helper.rb
279
+ commit_turn_spec.rb # commit_turn 单元测试
280
+ compose_context_spec.rb # compose_context 单元测试
281
+ fusion_merger_spec.rb # 融合层测试
282
+ integration_smart_rag_adapter_spec.rb # SmartRAG 适配器集成测试
283
+ observability_metrics_spec.rb # 可观测性测试
284
+ planner_policy_spec.rb # 检索计划策略测试
285
+ regression_context_spec.rb # 回归测试
286
+ example.rb # 简单示例
287
+ conversation_demo.rb # 完整多轮对话演示(SmartBrain + SmartRAG + SmartAgent)
288
+ ```
289
+
290
+ ## 快速开始
291
+
292
+ ### 仅 SmartBrain(不依赖外部服务)
293
+
294
+ ```ruby
295
+ require_relative 'lib/smart_brain'
296
+
297
+ SmartBrain.configure
298
+
299
+ SmartBrain.commit_turn(
300
+ session_id: 'demo',
301
+ turn_events: {
302
+ messages: [
303
+ { role: 'user', content: '请记住:默认数据库是 Postgres。' },
304
+ { role: 'assistant', content: '已记录。' }
305
+ ],
306
+ decisions: [
307
+ { key: 'decision:smartbrain:storage', decision: 'Use Postgres by default' }
308
+ ]
309
+ }
310
+ )
311
+
312
+ context = SmartBrain.compose_context(
313
+ session_id: 'demo',
314
+ user_message: '继续并总结关键结论'
315
+ )
316
+
317
+ puts context[:context_id]
318
+ puts context.dig(:debug, :trace, :request_id)
319
+ puts context.dig(:debug, :trace, :plan_id)
320
+ ```
321
+
322
+ ### 多轮对话循环(与 SmartAgent + SmartRAG 联动)
323
+
324
+ ```ruby
325
+ require 'smart_brain'
326
+ require 'smart_agent'
327
+ require 'smart_rag'
328
+ require_relative 'lib/smart_brain/adapters/smart_rag/direct_client'
329
+
330
+ # 1. 初始化 SmartRAG
331
+ rag = SmartRAG::SmartRAG.new(database: {...}, llm: {...})
332
+ client = SmartBrain::Adapters::SmartRag::DirectClient.new(rag: rag)
333
+
334
+ # 2. 初始化 SmartBrain
335
+ SmartBrain.configure(smart_rag_client: client)
336
+
337
+ # 3. 初始化 SmartAgent
338
+ engine = SmartAgent::Engine.new('./config/example_agent.yml')
339
+ agent = engine.build_agent(:brain_assistant)
340
+
341
+ # 4. 多轮对话循环
342
+ session_id = "demo-session"
343
+ user_message = "Ruby 类名应该用什么命名风格?"
344
+
345
+ # 4a. 获取上下文(内部可能调用 SmartRAG 检索文档资源)
346
+ context = SmartBrain.compose_context(
347
+ session_id: session_id,
348
+ user_message: user_message,
349
+ agent_state: { turn: 1 }
350
+ )
351
+
352
+ # 4b. 调用 Agent(LLM)生成回复
353
+ response = agent.please(context.to_json)
354
+
355
+ # 4c. 提交本轮(沉淀记忆)
356
+ commit = SmartBrain.commit_turn(
357
+ session_id: session_id,
358
+ turn_events: {
359
+ messages: [
360
+ { role: 'user', content: user_message },
361
+ { role: 'assistant', content: response.to_s }
362
+ ],
363
+ decisions: [
364
+ { key: 'decision:ruby:class_naming', decision: '使用 CamelCase' }
365
+ ],
366
+ entities: [
367
+ { key: 'entity:lang:ruby', name: 'Ruby', canonical: 'ruby', kind: 'language', remember: true }
368
+ ]
369
+ }
370
+ )
371
+
372
+ # 5. 查看诊断信息
373
+ pp SmartBrain.diagnostics
374
+ ```
375
+
376
+ ## SmartRAG 集成方式
377
+
378
+ ### 1) NullClient(默认)
379
+
380
+ 不配置 `smart_rag_client` 时,资源证据为空,仅使用记忆侧证据。适合纯对话记忆场景。
381
+
382
+ ```ruby
383
+ SmartBrain.configure # 无需额外配置
384
+ ```
385
+
386
+ ### 2) HttpClient(HTTP 远程)
387
+
388
+ 通过 SmartRAG HTTP 服务调用远端资源库,支持 JSON URL 导入、multipart 文件上传和超时降级:
389
+
390
+ ```ruby
391
+ client = SmartBrain::Adapters::SmartRag::HttpClient.for_url(
392
+ base_url: 'http://127.0.0.1:9393',
393
+ timeout_seconds: 30,
394
+ headers: { 'Authorization' => "Bearer #{ENV.fetch('SMARTRAG_TOKEN')}" }
395
+ )
396
+ SmartBrain.configure(smart_rag_client: client)
397
+
398
+ SmartBrain.add_video(source: '/data/demo.mp4', options: { tags: ['demo'] })
399
+ ```
400
+
401
+ 本地路径自动使用 multipart 上传,HTTP/HTTPS source 使用 JSON URL 导入。OCR、视觉描述和转写 callable 不能跨进程序列化,必须配置在远端 SmartRAG `HttpApp` 的 `extractors:` 中。
402
+
403
+ 也可以继续通过自定义 transport lambda 集成其他协议:
404
+
405
+ ```ruby
406
+ transport = lambda do |plan, timeout_seconds:|
407
+ {
408
+ plan_id: 'p1',
409
+ supports_language_filter: true,
410
+ evidences: []
411
+ }
412
+ end
413
+
414
+ scope_mapper = lambda do |domain_id:, scopes:|
415
+ { filters: { topic_ids: scopes.map { |scope| "#{domain_id}:#{scope[:type]}:#{scope[:id]}" } } }
416
+ end
417
+ client = SmartBrain::Adapters::SmartRag::HttpClient.new(
418
+ transport: transport, timeout_seconds: 2, scope_mapper: scope_mapper
419
+ )
420
+ SmartBrain.configure(smart_rag_client: client)
421
+ ```
422
+
423
+ ### 3) DirectClient(进程内直接调用)
424
+
425
+ 直接依赖 `smart_rag` gem,进程内调用。需要配置 PostgreSQL 连接和 LLM。
426
+
427
+ ```ruby
428
+ require 'smart_rag'
429
+ require_relative 'lib/smart_brain/adapters/smart_rag/direct_client'
430
+
431
+ rag_config = SmartRAG::Config.load(ENV.fetch('SMARTRAG_CONFIG_PATH'))
432
+ rag = SmartRAG::SmartRAG.new(rag_config)
433
+ scope_mapper = lambda do |domain_id:, scopes:|
434
+ { filters: { topic_ids: scopes.map { |scope| "#{domain_id}:#{scope[:type]}:#{scope[:id]}" } } }
435
+ end
436
+ client = SmartBrain::Adapters::SmartRag::DirectClient.new(rag: rag, scope_mapper: scope_mapper)
437
+
438
+ SmartBrain.configure(smart_rag_client: client)
439
+ ```
440
+
441
+ Mapper 返回的 `filters` 会作为 `scope_filters` 发送给 SmartRAG。业务 scope 存在时,后端必须返回 `scope_filter_applied: true`;mapper 缺失、映射失败或后端未确认时,adapter 默认 fail closed,丢弃资源证据并在 `warnings` 和 `explain.ignored_fields` 中说明原因。
442
+
443
+ ## 多媒体记忆(MVP)
444
+
445
+ SmartBrain 通过 SmartRAG 保存图片、音频和视频的技术元数据,并把描述、OCR 或转写文本作为普通 section 建立全文及向量索引:
446
+
447
+ ```ruby
448
+ SmartBrain.add_image(
449
+ source: '/data/whiteboard.jpg',
450
+ options: {
451
+ title: 'Q3 路线图白板',
452
+ tags: ['roadmap'],
453
+ image_describer: ->(path) { vision_client.describe(path) },
454
+ ocr_extractor: ->(path) { ocr_client.extract(path) }
455
+ }
456
+ )
457
+
458
+ SmartBrain.add_audio(
459
+ source: '/data/weekly-meeting.wav',
460
+ options: {
461
+ tags: ['meeting'],
462
+ audio_transcriber: ->(path) { whisper_client.transcribe(path) }
463
+ }
464
+ )
465
+
466
+ SmartBrain.add_video(
467
+ source: '/data/product-demo.mp4',
468
+ options: {
469
+ tags: ['demo'],
470
+ # start/end 使用秒;也可以返回 start_ms/end_ms。
471
+ video_transcriber: ->(audio_path) {
472
+ whisper_client.transcribe(audio_path, timestamps: true)
473
+ # => { segments: [{ text: '打开设置', start: 5.0, end: 9.0 }] }
474
+ },
475
+ frame_describer: ->(frame_path, timestamp_ms) {
476
+ vision_client.describe(frame_path, timestamp_ms: timestamp_ms)
477
+ },
478
+ frame_interval_seconds: 30,
479
+ max_frames: 12
480
+ }
481
+ )
482
+ ```
483
+
484
+ `add_media` 会根据扩展名和 MIME 类型自动判断类型;`add_image`、`add_audio`、`add_video` 固定类型。Pillow 或 ffprobe 缺失、语义提取器未配置或调用失败时,导入仍会保存文件名、标签和可获得的技术元数据,返回 `status: "partial"` 及 `warnings`。
485
+
486
+ 按媒体类型限制资源检索:
487
+
488
+ ```ruby
489
+ SmartBrain.compose_context(
490
+ session_id: 'demo',
491
+ user_message: '查资料:路线图白板上写了什么?',
492
+ resource_filters: { media_type: ['image'] }
493
+ )
494
+ ```
495
+
496
+ SmartRAG 的 P1 配置可启用本地内容寻址存储(SHA-256 去重),并内置 OpenAI-compatible 图片描述/音频转写与可选 Tesseract OCR。未启用内容存储时仍保存原文件路径或 URL。
497
+
498
+ 异步导入和状态查询:
499
+
500
+ ```ruby
501
+ job = SmartBrain.enqueue_media(
502
+ source: '/data/long-demo.mp4',
503
+ options: { media_type: 'video', tags: ['demo'] }
504
+ )
505
+ status = SmartBrain.media_job(job_id: job[:job_id])
506
+
507
+ failed = SmartBrain.media_jobs(status: 'failed', limit: 20)
508
+ SmartBrain.retry_media_job(job_id: failed[:jobs].first[:id])
509
+ SmartBrain.cancel_media_job(job_id: job[:job_id]) # 仅 queued 状态可取消
510
+ queue = SmartBrain.media_job_statistics
511
+ ```
512
+
513
+ HTTP 客户端会发送 `options.async=true`,服务端返回 202;SmartRAG worker 负责消费 PostgreSQL `media_jobs` 队列。P2 支持任务分页/状态过滤、取消、人工重试、卡死任务恢复、保留期清理和健康指标。默认 `NullClient` 对写入和任务查询返回 `unsupported`。
514
+
515
+ P3 支持按调用方隔离的幂等入队。HTTP 模式可把幂等键放在 options 中;服务端也接受标准请求头 `Idempotency-Key`:
516
+
517
+ ```ruby
518
+ job = SmartBrain.enqueue_media(
519
+ source: '/data/long-demo.mp4',
520
+ options: { media_type: 'video', idempotency_key: 'demo-video-v1' }
521
+ )
522
+ ```
523
+
524
+ 部署当前版本前,SmartRAG 数据库必须执行到迁移 `017_add_media_job_request_fingerprint`。该迁移为历史任务回填规范化 SHA-256 请求指纹,并将指纹列设为必填。同一 principal 下,用相同幂等键重复提交相同 operation、source 和 options,会返回原任务并标记 `deduplicated: true`;相同键对应不同 source 或 options 时,SmartRAG 返回 HTTP `409` 和 `code: "idempotency_conflict"`。嵌套 Hash 的键顺序以及 symbol/string 键不影响指纹,数组顺序仍有意义。
525
+
526
+ `HttpClient` 会将该 409 转成 SmartBrain 的失败结果:`status: "failed"`,并在 `warnings` 中包含 `SmartRAG HTTP 409`。调用方应保持原载荷和原幂等键进行网络重试;业务载荷发生变化时必须生成新键。`DirectClient` 进程内调用时,SmartRAG 的 `MediaJobQueue::IdempotencyConflict` 会直接向上传播。
527
+
528
+ 多实例部署应把 SmartRAG `media.content_store.provider` 配置成 `s3`,可连接 AWS S3 或 MinIO。P3 worker 使用 heartbeat 租约避免长任务被错误恢复,并维护媒体对象引用计数和零引用垃圾回收。HTTP Bearer token 可继续通过 `HttpClient.for_url(headers: ...)` 设置。
529
+
530
+ SmartRAG 迁移 016 将认证 principal 写入文档 owner,并把它应用到检索、文档读取/列表/删除和统计。HTTP 检索会把 principal 对应的文档 ID 下推给搜索后端,并在生成 EvidencePack 前根据 PostgreSQL owner 再次过滤候选;即使后端忽略 `document_ids`,也不能返回其他租户证据。failed 任务保留期间,其 staging 对象不会被 GC 删除,人工重试仍可读取原始媒体;同步导入在后续处理失败时留下的零引用对象可被 GC 回收。
531
+
532
+ SmartRAG 提供显式启用的真实集成测试。`SMARTRAG_MINIO_E2E=1` 的 MinIO 用例覆盖跨实例上传/下载、异步 worker 读取 `s3://` staging URI、对象引用和真实 GC 删除;`media_tenant_isolation_spec.rb` 与 `media_p3_spec.rb` 覆盖 Bearer 多租户检索、非法 token、幂等 409 和并发唯一键竞争。具体环境变量和命令见 SmartRAG README 的 “Real storage and isolation verification”。
533
+
534
+ 视频的转写片段和关键帧描述分别保存为 section。检索命中后,EvidencePack 的 `metadata` 会包含 `extraction_kind`、`start_ms`、`end_ms` 或 `frame_timestamp_ms`,调用方可据此跳转到视频位置。视频处理依赖 `ffmpeg`;缺失时导入降级为 `partial`。
535
+
536
+ ## 运行示例
537
+
538
+ ### example.rb
539
+
540
+ SmartBrain + SmartAgent + SmartPrompt + SmartRAG 联动示例(2 轮对话)。
541
+
542
+ ```bash
543
+ bundle exec ruby example.rb
544
+ ```
545
+
546
+ 依赖的本地文件:
547
+ - `config/example_agent.yml`
548
+ - `config/example_llm.yml`
549
+ - `agents/brain_assistant.rb`
550
+ - `workers/brain_assistant.rb`
551
+ - `templates/brain_assistant.erb`
552
+
553
+ ### conversation_demo.rb
554
+
555
+ 完整多轮对话演示(5 轮),包含 SmartRAG 文档存储、检索、SmartAgent LLM 调用和 SmartBrain 记忆沉淀全流程。
556
+
557
+ ```bash
558
+ bundle exec ruby conversation_demo.rb
559
+ ```
560
+
561
+ ## 测试
562
+
563
+ ```bash
564
+ bundle exec rspec
565
+ ```
566
+
567
+ 测试覆盖 commit/compose、存储、治理、KG、协议适配、多 scope 隔离以及 PostgreSQL 迁移和持久化。
568
+
569
+ ## 常见问题
570
+
571
+ ### 1) `cannot load such file -- sequel/extensions/pgvector`
572
+
573
+ 当前示例已在 `example.rb` 做兼容处理(`Sequel.extension 'pgvector'` + 去除 `database.extensions` 连接参数)。
574
+
575
+ ### 2) `Config file not found: config/llm_config.yml`
576
+
577
+ `example.rb` 已将 SmartRAG 里 EmbeddingService 的 `config_path` 注入为 `./config/example_llm.yml`。
578
+
579
+ ### 3) `ruby-lsp: not found`
580
+
581
+ ```bash
582
+ gem install --user-install ruby-lsp debug
583
+ ```
584
+
585
+ 并将用户 gem bin 加入 `PATH`。
586
+
587
+ ## 路线图
588
+
589
+ - 将 EventStore/MemoryStore 从内存实现切换到 Postgres 持久化实现
590
+ - 接入真实 reranker / embedding 模型(当前为规则分 + 词重叠加分)
591
+ - 完善 SmartRAG ingest 与跨会话评估工具
592
+ - 扩展 `SemanticRetriever`(依赖 pgvector/embedding)
593
+ - 支持 `memory_chunks` 表(FTS 索引 + 可选 embedding 字段)