smart_brain 0.1.2 → 0.2.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/MEMPAL_GUIDE.md +1074 -0
  4. data/README.en.md +173 -173
  5. data/README.md +467 -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/mcp.md +93 -0
  17. data/docs/memory_types.md +278 -0
  18. data/docs/multi_scope_memory_refactor_plan.md +483 -0
  19. data/docs/multi_scope_migration.md +65 -0
  20. data/docs/policies.md +308 -0
  21. data/docs/retrieval_plan.md +231 -0
  22. data/docs/smartbrain_design.md +299 -0
  23. data/docs/user_guide.md +546 -0
  24. data/example.rb +91 -91
  25. data/examples/01_memory_basic.rb +57 -0
  26. data/examples/02_governance.rb +63 -0
  27. data/examples/03_postgres_persistence.rb +63 -0
  28. data/examples/04_ollama_llm.rb +69 -0
  29. data/examples/05_smart_rag_integration.rb +79 -0
  30. data/examples/06_multi_scope_memory.rb +50 -0
  31. data/examples/README.md +49 -0
  32. data/exe/smart_brain +168 -0
  33. data/lib/smart_brain/adapters/smart_rag/direct_client.rb +16 -5
  34. data/lib/smart_brain/adapters/smart_rag/http_client.rb +16 -5
  35. data/lib/smart_brain/adapters/smart_rag/null_client.rb +7 -2
  36. data/lib/smart_brain/adapters/smart_rag/scope_filter.rb +60 -0
  37. data/lib/smart_brain/configuration.rb +57 -0
  38. data/lib/smart_brain/consolidator/working_summary.rb +80 -12
  39. data/lib/smart_brain/context_composer/composer.rb +40 -3
  40. data/lib/smart_brain/contracts/retrieval_plan.rb +10 -0
  41. data/lib/smart_brain/contracts/scope_context.rb +46 -0
  42. data/lib/smart_brain/contracts/scope_ref.rb +25 -0
  43. data/lib/smart_brain/db.rb +109 -0
  44. data/lib/smart_brain/event_store/in_memory.rb +6 -2
  45. data/lib/smart_brain/event_store/postgres.rb +199 -0
  46. data/lib/smart_brain/fusion/merger.rb +31 -2
  47. data/lib/smart_brain/governance/briefing.rb +146 -0
  48. data/lib/smart_brain/governance/fact_check.rb +110 -0
  49. data/lib/smart_brain/governance/knowledge_graph.rb +60 -0
  50. data/lib/smart_brain/governance/lifecycle.rb +225 -0
  51. data/lib/smart_brain/governance/tiers.rb +60 -0
  52. data/lib/smart_brain/memory_extractor/extractor.rb +25 -7
  53. data/lib/smart_brain/memory_store/in_memory.rb +202 -17
  54. data/lib/smart_brain/memory_store/postgres.rb +500 -0
  55. data/lib/smart_brain/model_provider/base.rb +87 -0
  56. data/lib/smart_brain/model_provider/factory.rb +49 -0
  57. data/lib/smart_brain/model_provider/ollama.rb +60 -0
  58. data/lib/smart_brain/model_provider/openai.rb +60 -0
  59. data/lib/smart_brain/model_provider/stub.rb +26 -0
  60. data/lib/smart_brain/model_provider.rb +7 -0
  61. data/lib/smart_brain/observability/tracker.rb +39 -1
  62. data/lib/smart_brain/retrievers/exact_retriever.rb +6 -0
  63. data/lib/smart_brain/retrievers/memory_retriever.rb +59 -5
  64. data/lib/smart_brain/runtime.rb +288 -16
  65. data/lib/smart_brain/scopes/conflict_resolver.rb +67 -0
  66. data/lib/smart_brain/scopes/registry.rb +133 -0
  67. data/lib/smart_brain/scopes/resolver.rb +32 -0
  68. data/lib/smart_brain/server/http_app.rb +143 -0
  69. data/lib/smart_brain/server/mcp_server.rb +385 -0
  70. data/lib/smart_brain/server/service.rb +129 -0
  71. data/lib/smart_brain/support/levenshtein.rb +35 -0
  72. data/lib/smart_brain/version.rb +5 -5
  73. data/lib/smart_brain.rb +80 -35
  74. metadata +88 -36
data/conversation_demo.rb CHANGED
@@ -1,438 +1,438 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # =============================================================================
5
- # SmartBrain + SmartRAG + SmartAgent 真实多轮对话演示
6
- #
7
- # 本示例演示:
8
- # 1. 真实调用 SmartRAG 进行文档存储和检索
9
- # 2. 真实调用 SmartAgent 与 LLM (Ollama) 对话
10
- # 3. SmartBrain 在对话中维持记忆和上下文
11
- # =============================================================================
12
-
13
- require 'logger'
14
- require 'json'
15
-
16
- # 环境变量配置 - 使用轨迹流动 Kimi-K2.5 模型
17
- ENV['SMARTRAG_DB_HOST'] ||= '192.168.1.48'
18
- ENV['SMARTRAG_DB_PORT'] ||= '5432'
19
- ENV['SMARTRAG_DB_NAME'] ||= 'smart_rag_development'
20
- ENV['SMARTRAG_DB_USER'] ||= 'rag_user'
21
- ENV['SMARTRAG_DB_PASSWORD'] ||= 'rag_pwd'
22
- ENV['EMBEDDING_MODEL'] = 'qwen3-embedding'
23
-
24
- # 硅基流动 API 配置
25
- SILICON_FLOW_API_KEY = ENV['SILICON_FLOW_API_KEY']
26
- SILICON_FLOW_ENDPOINT = "https://api.siliconflow.cn/v1/"
27
- SILICON_FLOW_MODEL = 'Pro/moonshotai/Kimi-K2.5'
28
-
29
- # 加载依赖
30
- require 'smart_prompt'
31
- require 'smart_agent'
32
- require_relative 'lib/smart_brain'
33
- require_relative 'lib/smart_brain/adapters/smart_rag/direct_client'
34
-
35
- # 加载 SmartRAG
36
- begin
37
- require 'sequel'
38
- Sequel.extension 'pgvector'
39
- rescue LoadError
40
- # pgvector extension not available
41
- end
42
-
43
- begin
44
- require 'smart_rag'
45
- rescue LoadError => e
46
- warn "SmartRAG load failed: #{e.message}"
47
- exit 1
48
- end
49
-
50
- puts "=" * 80
51
- puts "SmartBrain + SmartRAG + SmartAgent 真实多轮对话演示"
52
- puts "使用模型: 硅基流动 Pro/moonshotai/Kimi-K2.5"
53
- puts "=" * 80
54
-
55
- # =============================================================================
56
- # 步骤 1: 初始化 SmartRAG
57
- # =============================================================================
58
-
59
- puts "\n📚 初始化 SmartRAG..."
60
-
61
- null_logger = Logger.new(File.open(File::NULL, 'w'))
62
-
63
- rag_config = {
64
- database: {
65
- adapter: 'postgresql',
66
- host: ENV['SMARTRAG_DB_HOST'] || '192.168.1.48',
67
- port: (ENV['SMARTRAG_DB_PORT'] || '5432').to_i,
68
- database: ENV['SMARTRAG_DB_NAME'] || 'smart_rag_development',
69
- user: ENV['SMARTRAG_DB_USER'] || 'rag_user',
70
- password: ENV['SMARTRAG_DB_PASSWORD'] || 'rag_pwd'
71
- },
72
- llm: {
73
- provider: 'openai',
74
- api_key: SILICON_FLOW_API_KEY,
75
- endpoint: SILICON_FLOW_ENDPOINT,
76
- model: SILICON_FLOW_MODEL,
77
- temperature: 0.3
78
- },
79
- # Embedding 配置 - 禁用(轨迹流动暂不支持 embedding)
80
- embedding: {
81
- config_path: File.expand_path('./config/llm_config.yml', __dir__)
82
- },
83
- # 禁用所有 SmartRAG 内部日志输出
84
- logger: null_logger
85
- }
86
-
87
- begin
88
- rag = SmartRAG::SmartRAG.new(rag_config)
89
-
90
- stats = rag.statistics
91
- puts "✓ SmartRAG 初始化成功"
92
- puts " - 文档数: #{stats[:document_count]}"
93
- puts " - 段落数: #{stats[:section_count]}"
94
- puts " - 主题数: #{stats[:topic_count]}"
95
- rescue StandardError => e
96
- warn "✗ SmartRAG 初始化失败: #{e.message}"
97
- warn "请确保 PostgreSQL 正在运行且数据库已配置"
98
- exit 1
99
- end
100
-
101
- # =============================================================================
102
- # 步骤 2: 准备知识库文档(如果还没有)
103
- # =============================================================================
104
-
105
- puts "\n📄 检查知识库文档..."
106
-
107
- # 定义要添加的示例文档
108
- documents = [
109
- {
110
- title: "Ruby Programming Best Practices",
111
- url: "https://example.com/ruby-best-practices",
112
- content: <<~DOC
113
- # Ruby Programming Best Practices
114
-
115
- ## Naming Conventions
116
-
117
- - Use snake_case for methods and variables: `user_name`, `total_count`
118
- - Use CamelCase for class and module names: `UserAccount`, `OrderProcessor`
119
- - Use SCREAMING_SNAKE_CASE for constants: `MAX_RETRIES`, `DEFAULT_TIMEOUT`
120
-
121
- ## Code Organization
122
-
123
- - Keep methods short and focused (under 20 lines)
124
- - Use single responsibility principle
125
- - Prefer composition over inheritance
126
- - Write self-documenting code with clear method names
127
-
128
- ## Error Handling
129
-
130
- - Use specific exception classes
131
- - Avoid rescuing Exception class
132
- - Use ensure for cleanup operations
133
- DOC
134
- },
135
- {
136
- title: "PostgreSQL Performance Optimization",
137
- url: "https://example.com/postgresql-performance",
138
- content: <<~DOC
139
- # PostgreSQL Performance Optimization
140
-
141
- ## Connection Pooling
142
-
143
- - Use connection poolers like PgBouncer for high-concurrency applications
144
- - Recommended pool size: (core_count * 2) + effective_spindle_count
145
- - Monitor connection usage with pg_stat_activity
146
-
147
- ## Indexing Strategies
148
-
149
- - Create indexes on frequently queried columns
150
- - Use partial indexes for filtered queries
151
- - Consider covering indexes for index-only scans
152
- - Regularly analyze tables for query planner
153
-
154
- ## Query Optimization
155
-
156
- - Use EXPLAIN ANALYZE to understand query plans
157
- - Avoid SELECT * in production queries
158
- - Use appropriate data types for columns
159
- DOC
160
- },
161
- {
162
- title: "Introduction to pgvector",
163
- url: "https://example.com/pgvector-intro",
164
- content: <<~DOC
165
- # Introduction to pgvector
166
-
167
- ## Overview
168
-
169
- pgvector is a PostgreSQL extension for vector similarity search.
170
- It allows you to store and query high-dimensional vectors directly in PostgreSQL.
171
-
172
- ## Key Features
173
-
174
- - Vector data type with up to 16,000 dimensions
175
- - ivfflat and hnsw indexes for fast approximate nearest neighbor search
176
- - L2, inner product, and cosine distance metrics
177
- - ACID compliance through PostgreSQL
178
-
179
- ## Installation
180
-
181
- CREATE EXTENSION pgvector;
182
-
183
- CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));
184
-
185
- ## Use Cases
186
-
187
- - Semantic search for AI applications
188
- - Recommendation systems
189
- - Image similarity search
190
- - Document clustering
191
- DOC
192
- }
193
- ]
194
-
195
- # 检查并添加文档
196
- documents.each do |doc|
197
- # 检查是否已存在
198
- existing = rag.list_documents(search: doc[:title])
199
- if existing[:documents].any? { |d| d[:title] == doc[:title] }
200
- puts " ✓ 文档已存在: #{doc[:title]}"
201
- else
202
- begin
203
- # 创建临时文件
204
- require 'tempfile'
205
- Tempfile.create(['doc', '.md']) do |file|
206
- file.write(doc[:content])
207
- file.flush
208
-
209
- result = rag.add_document(
210
- file.path,
211
- title: doc[:title],
212
- url: doc[:url],
213
- generate_embeddings: false,
214
- generate_tags: false
215
- )
216
- puts " ✓ 添加文档: #{doc[:title]} (ID: #{result[:document_id]})"
217
- end
218
- rescue StandardError => e
219
- puts " ✗ 添加失败: #{doc[:title]} - #{e.message}"
220
- end
221
- end
222
- end
223
-
224
- # =============================================================================
225
- # 步骤 3: 初始化 SmartBrain 和 SmartAgent
226
- # =============================================================================
227
-
228
- puts "\n🧠 初始化 SmartBrain..."
229
-
230
- rag_client = SmartBrain::Adapters::SmartRag::DirectClient.new(rag: rag)
231
- SmartBrain.configure(smart_rag_client: rag_client)
232
- puts "✓ SmartBrain 初始化成功"
233
-
234
- puts "\n🤖 初始化 SmartAgent..."
235
- engine = SmartAgent::Engine.new('./config/example_agent.yml')
236
- agent = engine.build_agent(:brain_assistant)
237
- puts "✓ SmartAgent 初始化成功"
238
- puts " - 使用模型: #{SILICON_FLOW_MODEL}"
239
-
240
- # =============================================================================
241
- # 步骤 4: 定义多轮对话
242
- # =============================================================================
243
-
244
- session_id = "real-demo-#{Time.now.to_i}"
245
-
246
- puts "\n" + "=" * 80
247
- puts "开始多轮对话 (Session: #{session_id})"
248
- puts "=" * 80
249
-
250
- # 定义对话流程
251
- conversations = [
252
- {
253
- turn: 1,
254
- user_message: "你好,我正在学习 Ruby,想了解一下命名规范。",
255
- extract_events: {
256
- goals: [
257
- { key: 'goal:learn:ruby', goal: '学习 Ruby 编程规范' }
258
- ],
259
- entities: [
260
- { key: 'entity:lang:ruby', name: 'Ruby', canonical: 'ruby', kind: 'language', remember: true }
261
- ]
262
- }
263
- },
264
- {
265
- turn: 2,
266
- user_message: "类名应该用什么风格?",
267
- extract_events: {
268
- decisions: [
269
- { key: 'decision:ruby:class_naming', decision: 'Ruby 类名使用 CamelCase' }
270
- ]
271
- }
272
- },
273
- {
274
- turn: 3,
275
- user_message: "明白了。现在我打算用 PostgreSQL 作为数据库,有什么性能建议吗?",
276
- extract_events: {
277
- goals: [
278
- { key: 'goal:learn:postgresql', goal: '学习 PostgreSQL 性能优化' }
279
- ],
280
- entities: [
281
- { key: 'entity:db:postgresql', name: 'PostgreSQL', canonical: 'postgresql', kind: 'database', remember: true }
282
- ]
283
- }
284
- },
285
- {
286
- turn: 4,
287
- user_message: "连接池大小一般怎么设置?",
288
- extract_events: {
289
- decisions: [
290
- { key: 'decision:pg:pool_size', decision: '连接池大小公式: (core_count * 2) + effective_spindle_count' }
291
- ]
292
- }
293
- },
294
- {
295
- turn: 5,
296
- user_message: "我听说有个叫 pgvector 的扩展,它适合什么场景?",
297
- extract_events: {
298
- entities: [
299
- { key: 'entity:ext:pgvector', name: 'pgvector', canonical: 'pgvector', kind: 'extension', remember: true }
300
- ]
301
- }
302
- }
303
- ]
304
-
305
- # =============================================================================
306
- # 步骤 5: 执行多轮对话
307
- # =============================================================================
308
-
309
- def build_worker_input(context, user_message)
310
- evidence_text = ""
311
- if context[:evidence] && !context[:evidence].empty?
312
- evidence_text = "\n\n## Evidence from Knowledge Base\n\n"
313
- context[:evidence].first(3).each_with_index do |ev, idx|
314
- evidence_text += "#{idx + 1}. #{ev[:title]}\n"
315
- evidence_text += " #{ev[:snippet]}\n\n"
316
- end
317
- end
318
-
319
- # working_summary is a String, not a Hash
320
- summary_text = ""
321
- if context[:working_summary] && context[:working_summary].is_a?(String)
322
- summary_text = "\n\n## Conversation Summary\n\n#{context[:working_summary]}"
323
- end
324
-
325
- # Build the text parameter for the template
326
- context_text = "## User Message\n#{user_message}\n"
327
- context_text += summary_text if summary_text && !summary_text.empty?
328
- context_text += evidence_text if evidence_text && !evidence_text.empty?
329
-
330
- # Return string (agent.please expects a string)
331
- context_text
332
- end
333
-
334
- conversations.each do |conv|
335
- puts "\n" + "-" * 80
336
- puts "【第 #{conv[:turn]} 轮】"
337
- puts "-" * 80
338
-
339
- user_message = conv[:user_message]
340
- puts "\n👤 用户: #{user_message}"
341
-
342
- # 1. SmartBrain 组合上下文
343
- context = SmartBrain.compose_context(
344
- session_id: session_id,
345
- user_message: user_message,
346
- agent_state: { turn: conv[:turn] }
347
- )
348
-
349
- # 显示检索结果
350
- if context[:evidence] && !context[:evidence].empty?
351
- puts "\n🔍 SmartBrain 检索结果:"
352
- memory_count = context[:evidence].count { |e| e[:source] == 'memory' }
353
- resource_count = context[:evidence].count { |e| e[:source] == 'resource' }
354
- puts " 记忆: #{memory_count} | 资源: #{resource_count}"
355
-
356
- context[:evidence].first(3).each do |ev|
357
- icon = ev[:source] == 'memory' ? '💭' : '📄'
358
- puts " #{icon} #{ev[:title]} (score: #{(ev[:score] || 0).round(2)})"
359
- end
360
- end
361
-
362
- # 2. SmartAgent 调用 LLM
363
- puts "\n🤖 SmartAgent 调用 LLM..."
364
- begin
365
- assistant_response = agent.please(build_worker_input(context, user_message))
366
-
367
- # 只显示响应的前一部分,避免输出过长
368
- display_text = assistant_response.to_s.strip
369
- if display_text.length > 300
370
- display_text = display_text[0..300] + "..."
371
- end
372
- puts "\n📝 助手回复:"
373
- puts " #{display_text.gsub("\n", "\n ")}"
374
- rescue StandardError => e
375
- puts " ✗ LLM 调用失败: #{e.message}"
376
- puts " 错误类型: #{e.class}"
377
- puts " 堆栈: #{e.backtrace.first(5).join("\n ")}"
378
- assistant_response = "抱歉,我暂时无法回答这个问题。"
379
- end
380
-
381
- # 3. SmartBrain 提交本轮
382
- turn_events = {
383
- messages: [
384
- { role: 'user', content: user_message },
385
- { role: 'assistant', content: assistant_response.to_s }
386
- ]
387
- }
388
-
389
- # 添加提取的事件
390
- if conv[:extract_events]
391
- turn_events.merge!(conv[:extract_events])
392
- end
393
-
394
- commit = SmartBrain.commit_turn(
395
- session_id: session_id,
396
- turn_events: turn_events
397
- )
398
-
399
- puts "\n💾 SmartBrain 提交:"
400
- puts " - commit_id: #{commit[:commit_id][0..7]}..."
401
- puts " - 记忆项: #{commit[:memory_written] ? commit[:memory_written][:count] : 0} 条"
402
- if commit[:summary] && commit[:summary][:triggered]
403
- puts " - 总结更新: #{commit[:summary][:trigger_reason]}"
404
- end
405
- end
406
-
407
- # =============================================================================
408
- # 步骤 6: 展示对话总结
409
- # =============================================================================
410
-
411
- puts "\n" + "=" * 80
412
- puts "对话总结"
413
- puts "=" * 80
414
-
415
- diagnostics = SmartBrain.diagnostics
416
-
417
- # 获取 Working Summary
418
- final_summary = diagnostics.dig(:summaries, session_id)
419
- if final_summary
420
- puts "\n📝 Working Summary:"
421
- puts final_summary[:text] if final_summary[:text]
422
- end
423
-
424
- # 统计信息
425
- session_turns = diagnostics[:turns]&.select { |t| t[:session_id] == session_id } || []
426
- puts "\n📊 统计信息:"
427
- puts " - 总轮数: #{session_turns.size}"
428
- puts " - Session ID: #{session_id}"
429
-
430
- # 检索到的资源证据
431
- resource_evidence_count = session_turns.sum do |turn|
432
- (turn[:context]&.dig(:evidence) || []).count { |e| e[:source] == 'resource' }
433
- end
434
- puts " - 资源证据检索次数: #{resource_evidence_count}"
435
-
436
- puts "\n" + "=" * 80
437
- puts "演示结束!"
438
- puts "=" * 80
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # =============================================================================
5
+ # SmartBrain + SmartRAG + SmartAgent 真实多轮对话演示
6
+ #
7
+ # 本示例演示:
8
+ # 1. 真实调用 SmartRAG 进行文档存储和检索
9
+ # 2. 真实调用 SmartAgent 与 LLM (Ollama) 对话
10
+ # 3. SmartBrain 在对话中维持记忆和上下文
11
+ # =============================================================================
12
+
13
+ require 'logger'
14
+ require 'json'
15
+
16
+ # 环境变量配置 - 使用轨迹流动 Kimi-K2.5 模型
17
+ ENV['SMARTRAG_DB_HOST'] ||= '192.168.1.48'
18
+ ENV['SMARTRAG_DB_PORT'] ||= '5432'
19
+ ENV['SMARTRAG_DB_NAME'] ||= 'smart_rag_development'
20
+ ENV['SMARTRAG_DB_USER'] ||= 'rag_user'
21
+ ENV['SMARTRAG_DB_PASSWORD'] ||= 'rag_pwd'
22
+ ENV['EMBEDDING_MODEL'] = 'qwen3-embedding'
23
+
24
+ # 硅基流动 API 配置
25
+ SILICON_FLOW_API_KEY = ENV['SILICON_FLOW_API_KEY']
26
+ SILICON_FLOW_ENDPOINT = "https://api.siliconflow.cn/v1/"
27
+ SILICON_FLOW_MODEL = 'Pro/moonshotai/Kimi-K2.5'
28
+
29
+ # 加载依赖
30
+ require 'smart_prompt'
31
+ require 'smart_agent'
32
+ require_relative 'lib/smart_brain'
33
+ require_relative 'lib/smart_brain/adapters/smart_rag/direct_client'
34
+
35
+ # 加载 SmartRAG
36
+ begin
37
+ require 'sequel'
38
+ Sequel.extension 'pgvector'
39
+ rescue LoadError
40
+ # pgvector extension not available
41
+ end
42
+
43
+ begin
44
+ require 'smart_rag'
45
+ rescue LoadError => e
46
+ warn "SmartRAG load failed: #{e.message}"
47
+ exit 1
48
+ end
49
+
50
+ puts "=" * 80
51
+ puts "SmartBrain + SmartRAG + SmartAgent 真实多轮对话演示"
52
+ puts "使用模型: 硅基流动 Pro/moonshotai/Kimi-K2.5"
53
+ puts "=" * 80
54
+
55
+ # =============================================================================
56
+ # 步骤 1: 初始化 SmartRAG
57
+ # =============================================================================
58
+
59
+ puts "\n📚 初始化 SmartRAG..."
60
+
61
+ null_logger = Logger.new(File.open(File::NULL, 'w'))
62
+
63
+ rag_config = {
64
+ database: {
65
+ adapter: 'postgresql',
66
+ host: ENV['SMARTRAG_DB_HOST'] || '192.168.1.48',
67
+ port: (ENV['SMARTRAG_DB_PORT'] || '5432').to_i,
68
+ database: ENV['SMARTRAG_DB_NAME'] || 'smart_rag_development',
69
+ user: ENV['SMARTRAG_DB_USER'] || 'rag_user',
70
+ password: ENV['SMARTRAG_DB_PASSWORD'] || 'rag_pwd'
71
+ },
72
+ llm: {
73
+ provider: 'openai',
74
+ api_key: SILICON_FLOW_API_KEY,
75
+ endpoint: SILICON_FLOW_ENDPOINT,
76
+ model: SILICON_FLOW_MODEL,
77
+ temperature: 0.3
78
+ },
79
+ # Embedding 配置 - 禁用(轨迹流动暂不支持 embedding)
80
+ embedding: {
81
+ config_path: File.expand_path('./config/llm_config.yml', __dir__)
82
+ },
83
+ # 禁用所有 SmartRAG 内部日志输出
84
+ logger: null_logger
85
+ }
86
+
87
+ begin
88
+ rag = SmartRAG::SmartRAG.new(rag_config)
89
+
90
+ stats = rag.statistics
91
+ puts "✓ SmartRAG 初始化成功"
92
+ puts " - 文档数: #{stats[:document_count]}"
93
+ puts " - 段落数: #{stats[:section_count]}"
94
+ puts " - 主题数: #{stats[:topic_count]}"
95
+ rescue StandardError => e
96
+ warn "✗ SmartRAG 初始化失败: #{e.message}"
97
+ warn "请确保 PostgreSQL 正在运行且数据库已配置"
98
+ exit 1
99
+ end
100
+
101
+ # =============================================================================
102
+ # 步骤 2: 准备知识库文档(如果还没有)
103
+ # =============================================================================
104
+
105
+ puts "\n📄 检查知识库文档..."
106
+
107
+ # 定义要添加的示例文档
108
+ documents = [
109
+ {
110
+ title: "Ruby Programming Best Practices",
111
+ url: "https://example.com/ruby-best-practices",
112
+ content: <<~DOC
113
+ # Ruby Programming Best Practices
114
+
115
+ ## Naming Conventions
116
+
117
+ - Use snake_case for methods and variables: `user_name`, `total_count`
118
+ - Use CamelCase for class and module names: `UserAccount`, `OrderProcessor`
119
+ - Use SCREAMING_SNAKE_CASE for constants: `MAX_RETRIES`, `DEFAULT_TIMEOUT`
120
+
121
+ ## Code Organization
122
+
123
+ - Keep methods short and focused (under 20 lines)
124
+ - Use single responsibility principle
125
+ - Prefer composition over inheritance
126
+ - Write self-documenting code with clear method names
127
+
128
+ ## Error Handling
129
+
130
+ - Use specific exception classes
131
+ - Avoid rescuing Exception class
132
+ - Use ensure for cleanup operations
133
+ DOC
134
+ },
135
+ {
136
+ title: "PostgreSQL Performance Optimization",
137
+ url: "https://example.com/postgresql-performance",
138
+ content: <<~DOC
139
+ # PostgreSQL Performance Optimization
140
+
141
+ ## Connection Pooling
142
+
143
+ - Use connection poolers like PgBouncer for high-concurrency applications
144
+ - Recommended pool size: (core_count * 2) + effective_spindle_count
145
+ - Monitor connection usage with pg_stat_activity
146
+
147
+ ## Indexing Strategies
148
+
149
+ - Create indexes on frequently queried columns
150
+ - Use partial indexes for filtered queries
151
+ - Consider covering indexes for index-only scans
152
+ - Regularly analyze tables for query planner
153
+
154
+ ## Query Optimization
155
+
156
+ - Use EXPLAIN ANALYZE to understand query plans
157
+ - Avoid SELECT * in production queries
158
+ - Use appropriate data types for columns
159
+ DOC
160
+ },
161
+ {
162
+ title: "Introduction to pgvector",
163
+ url: "https://example.com/pgvector-intro",
164
+ content: <<~DOC
165
+ # Introduction to pgvector
166
+
167
+ ## Overview
168
+
169
+ pgvector is a PostgreSQL extension for vector similarity search.
170
+ It allows you to store and query high-dimensional vectors directly in PostgreSQL.
171
+
172
+ ## Key Features
173
+
174
+ - Vector data type with up to 16,000 dimensions
175
+ - ivfflat and hnsw indexes for fast approximate nearest neighbor search
176
+ - L2, inner product, and cosine distance metrics
177
+ - ACID compliance through PostgreSQL
178
+
179
+ ## Installation
180
+
181
+ CREATE EXTENSION pgvector;
182
+
183
+ CREATE TABLE items (id bigserial PRIMARY KEY, embedding vector(3));
184
+
185
+ ## Use Cases
186
+
187
+ - Semantic search for AI applications
188
+ - Recommendation systems
189
+ - Image similarity search
190
+ - Document clustering
191
+ DOC
192
+ }
193
+ ]
194
+
195
+ # 检查并添加文档
196
+ documents.each do |doc|
197
+ # 检查是否已存在
198
+ existing = rag.list_documents(search: doc[:title])
199
+ if existing[:documents].any? { |d| d[:title] == doc[:title] }
200
+ puts " ✓ 文档已存在: #{doc[:title]}"
201
+ else
202
+ begin
203
+ # 创建临时文件
204
+ require 'tempfile'
205
+ Tempfile.create(['doc', '.md']) do |file|
206
+ file.write(doc[:content])
207
+ file.flush
208
+
209
+ result = rag.add_document(
210
+ file.path,
211
+ title: doc[:title],
212
+ url: doc[:url],
213
+ generate_embeddings: false,
214
+ generate_tags: false
215
+ )
216
+ puts " ✓ 添加文档: #{doc[:title]} (ID: #{result[:document_id]})"
217
+ end
218
+ rescue StandardError => e
219
+ puts " ✗ 添加失败: #{doc[:title]} - #{e.message}"
220
+ end
221
+ end
222
+ end
223
+
224
+ # =============================================================================
225
+ # 步骤 3: 初始化 SmartBrain 和 SmartAgent
226
+ # =============================================================================
227
+
228
+ puts "\n🧠 初始化 SmartBrain..."
229
+
230
+ rag_client = SmartBrain::Adapters::SmartRag::DirectClient.new(rag: rag)
231
+ SmartBrain.configure(smart_rag_client: rag_client)
232
+ puts "✓ SmartBrain 初始化成功"
233
+
234
+ puts "\n🤖 初始化 SmartAgent..."
235
+ engine = SmartAgent::Engine.new('./config/example_agent.yml')
236
+ agent = engine.build_agent(:brain_assistant)
237
+ puts "✓ SmartAgent 初始化成功"
238
+ puts " - 使用模型: #{SILICON_FLOW_MODEL}"
239
+
240
+ # =============================================================================
241
+ # 步骤 4: 定义多轮对话
242
+ # =============================================================================
243
+
244
+ session_id = "real-demo-#{Time.now.to_i}"
245
+
246
+ puts "\n" + "=" * 80
247
+ puts "开始多轮对话 (Session: #{session_id})"
248
+ puts "=" * 80
249
+
250
+ # 定义对话流程
251
+ conversations = [
252
+ {
253
+ turn: 1,
254
+ user_message: "你好,我正在学习 Ruby,想了解一下命名规范。",
255
+ extract_events: {
256
+ goals: [
257
+ { key: 'goal:learn:ruby', goal: '学习 Ruby 编程规范' }
258
+ ],
259
+ entities: [
260
+ { key: 'entity:lang:ruby', name: 'Ruby', canonical: 'ruby', kind: 'language', remember: true }
261
+ ]
262
+ }
263
+ },
264
+ {
265
+ turn: 2,
266
+ user_message: "类名应该用什么风格?",
267
+ extract_events: {
268
+ decisions: [
269
+ { key: 'decision:ruby:class_naming', decision: 'Ruby 类名使用 CamelCase' }
270
+ ]
271
+ }
272
+ },
273
+ {
274
+ turn: 3,
275
+ user_message: "明白了。现在我打算用 PostgreSQL 作为数据库,有什么性能建议吗?",
276
+ extract_events: {
277
+ goals: [
278
+ { key: 'goal:learn:postgresql', goal: '学习 PostgreSQL 性能优化' }
279
+ ],
280
+ entities: [
281
+ { key: 'entity:db:postgresql', name: 'PostgreSQL', canonical: 'postgresql', kind: 'database', remember: true }
282
+ ]
283
+ }
284
+ },
285
+ {
286
+ turn: 4,
287
+ user_message: "连接池大小一般怎么设置?",
288
+ extract_events: {
289
+ decisions: [
290
+ { key: 'decision:pg:pool_size', decision: '连接池大小公式: (core_count * 2) + effective_spindle_count' }
291
+ ]
292
+ }
293
+ },
294
+ {
295
+ turn: 5,
296
+ user_message: "我听说有个叫 pgvector 的扩展,它适合什么场景?",
297
+ extract_events: {
298
+ entities: [
299
+ { key: 'entity:ext:pgvector', name: 'pgvector', canonical: 'pgvector', kind: 'extension', remember: true }
300
+ ]
301
+ }
302
+ }
303
+ ]
304
+
305
+ # =============================================================================
306
+ # 步骤 5: 执行多轮对话
307
+ # =============================================================================
308
+
309
+ def build_worker_input(context, user_message)
310
+ evidence_text = ""
311
+ if context[:evidence] && !context[:evidence].empty?
312
+ evidence_text = "\n\n## Evidence from Knowledge Base\n\n"
313
+ context[:evidence].first(3).each_with_index do |ev, idx|
314
+ evidence_text += "#{idx + 1}. #{ev[:title]}\n"
315
+ evidence_text += " #{ev[:snippet]}\n\n"
316
+ end
317
+ end
318
+
319
+ # working_summary is a String, not a Hash
320
+ summary_text = ""
321
+ if context[:working_summary] && context[:working_summary].is_a?(String)
322
+ summary_text = "\n\n## Conversation Summary\n\n#{context[:working_summary]}"
323
+ end
324
+
325
+ # Build the text parameter for the template
326
+ context_text = "## User Message\n#{user_message}\n"
327
+ context_text += summary_text if summary_text && !summary_text.empty?
328
+ context_text += evidence_text if evidence_text && !evidence_text.empty?
329
+
330
+ # Return string (agent.please expects a string)
331
+ context_text
332
+ end
333
+
334
+ conversations.each do |conv|
335
+ puts "\n" + "-" * 80
336
+ puts "【第 #{conv[:turn]} 轮】"
337
+ puts "-" * 80
338
+
339
+ user_message = conv[:user_message]
340
+ puts "\n👤 用户: #{user_message}"
341
+
342
+ # 1. SmartBrain 组合上下文
343
+ context = SmartBrain.compose_context(
344
+ session_id: session_id,
345
+ user_message: user_message,
346
+ agent_state: { turn: conv[:turn] }
347
+ )
348
+
349
+ # 显示检索结果
350
+ if context[:evidence] && !context[:evidence].empty?
351
+ puts "\n🔍 SmartBrain 检索结果:"
352
+ memory_count = context[:evidence].count { |e| e[:source] == 'memory' }
353
+ resource_count = context[:evidence].count { |e| e[:source] == 'resource' }
354
+ puts " 记忆: #{memory_count} | 资源: #{resource_count}"
355
+
356
+ context[:evidence].first(3).each do |ev|
357
+ icon = ev[:source] == 'memory' ? '💭' : '📄'
358
+ puts " #{icon} #{ev[:title]} (score: #{(ev[:score] || 0).round(2)})"
359
+ end
360
+ end
361
+
362
+ # 2. SmartAgent 调用 LLM
363
+ puts "\n🤖 SmartAgent 调用 LLM..."
364
+ begin
365
+ assistant_response = agent.please(build_worker_input(context, user_message))
366
+
367
+ # 只显示响应的前一部分,避免输出过长
368
+ display_text = assistant_response.to_s.strip
369
+ if display_text.length > 300
370
+ display_text = display_text[0..300] + "..."
371
+ end
372
+ puts "\n📝 助手回复:"
373
+ puts " #{display_text.gsub("\n", "\n ")}"
374
+ rescue StandardError => e
375
+ puts " ✗ LLM 调用失败: #{e.message}"
376
+ puts " 错误类型: #{e.class}"
377
+ puts " 堆栈: #{e.backtrace.first(5).join("\n ")}"
378
+ assistant_response = "抱歉,我暂时无法回答这个问题。"
379
+ end
380
+
381
+ # 3. SmartBrain 提交本轮
382
+ turn_events = {
383
+ messages: [
384
+ { role: 'user', content: user_message },
385
+ { role: 'assistant', content: assistant_response.to_s }
386
+ ]
387
+ }
388
+
389
+ # 添加提取的事件
390
+ if conv[:extract_events]
391
+ turn_events.merge!(conv[:extract_events])
392
+ end
393
+
394
+ commit = SmartBrain.commit_turn(
395
+ session_id: session_id,
396
+ turn_events: turn_events
397
+ )
398
+
399
+ puts "\n💾 SmartBrain 提交:"
400
+ puts " - commit_id: #{commit[:commit_id][0..7]}..."
401
+ puts " - 记忆项: #{commit[:memory_written] ? commit[:memory_written][:count] : 0} 条"
402
+ if commit[:summary] && commit[:summary][:triggered]
403
+ puts " - 总结更新: #{commit[:summary][:trigger_reason]}"
404
+ end
405
+ end
406
+
407
+ # =============================================================================
408
+ # 步骤 6: 展示对话总结
409
+ # =============================================================================
410
+
411
+ puts "\n" + "=" * 80
412
+ puts "对话总结"
413
+ puts "=" * 80
414
+
415
+ diagnostics = SmartBrain.diagnostics
416
+
417
+ # 获取 Working Summary
418
+ final_summary = diagnostics.dig(:summaries, session_id)
419
+ if final_summary
420
+ puts "\n📝 Working Summary:"
421
+ puts final_summary[:text] if final_summary[:text]
422
+ end
423
+
424
+ # 统计信息
425
+ session_turns = diagnostics[:turns]&.select { |t| t[:session_id] == session_id } || []
426
+ puts "\n📊 统计信息:"
427
+ puts " - 总轮数: #{session_turns.size}"
428
+ puts " - Session ID: #{session_id}"
429
+
430
+ # 检索到的资源证据
431
+ resource_evidence_count = session_turns.sum do |turn|
432
+ (turn[:context]&.dig(:evidence) || []).count { |e| e[:source] == 'resource' }
433
+ end
434
+ puts " - 资源证据检索次数: #{resource_evidence_count}"
435
+
436
+ puts "\n" + "=" * 80
437
+ puts "演示结束!"
438
+ puts "=" * 80