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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/MEMPAL_GUIDE.md +1074 -0
- data/README.en.md +173 -173
- data/README.md +467 -173
- data/config/brain.yml +69 -1
- data/conversation_demo.rb +438 -438
- data/db/migrate/002_turn_events_payload.sql +9 -0
- data/db/migrate/003_tiers_and_lifecycle.sql +28 -0
- data/db/migrate/004_kg_edges.sql +30 -0
- data/db/migrate/005_domains_and_memory_scopes.sql +163 -0
- data/docs/coding_todo.md +139 -0
- data/docs/context_package.md +220 -0
- data/docs/evidence_pack.md +190 -0
- data/docs/gap_vs_mempal.md +161 -0
- data/docs/mcp.md +93 -0
- data/docs/memory_types.md +278 -0
- data/docs/multi_scope_memory_refactor_plan.md +483 -0
- data/docs/multi_scope_migration.md +65 -0
- data/docs/policies.md +308 -0
- data/docs/retrieval_plan.md +231 -0
- data/docs/smartbrain_design.md +299 -0
- data/docs/user_guide.md +546 -0
- data/example.rb +91 -91
- data/examples/01_memory_basic.rb +57 -0
- data/examples/02_governance.rb +63 -0
- data/examples/03_postgres_persistence.rb +63 -0
- data/examples/04_ollama_llm.rb +69 -0
- data/examples/05_smart_rag_integration.rb +79 -0
- data/examples/06_multi_scope_memory.rb +50 -0
- data/examples/README.md +49 -0
- data/exe/smart_brain +168 -0
- data/lib/smart_brain/adapters/smart_rag/direct_client.rb +16 -5
- data/lib/smart_brain/adapters/smart_rag/http_client.rb +16 -5
- data/lib/smart_brain/adapters/smart_rag/null_client.rb +7 -2
- data/lib/smart_brain/adapters/smart_rag/scope_filter.rb +60 -0
- data/lib/smart_brain/configuration.rb +57 -0
- data/lib/smart_brain/consolidator/working_summary.rb +80 -12
- data/lib/smart_brain/context_composer/composer.rb +40 -3
- data/lib/smart_brain/contracts/retrieval_plan.rb +10 -0
- data/lib/smart_brain/contracts/scope_context.rb +46 -0
- data/lib/smart_brain/contracts/scope_ref.rb +25 -0
- data/lib/smart_brain/db.rb +109 -0
- data/lib/smart_brain/event_store/in_memory.rb +6 -2
- data/lib/smart_brain/event_store/postgres.rb +199 -0
- data/lib/smart_brain/fusion/merger.rb +31 -2
- data/lib/smart_brain/governance/briefing.rb +146 -0
- data/lib/smart_brain/governance/fact_check.rb +110 -0
- data/lib/smart_brain/governance/knowledge_graph.rb +60 -0
- data/lib/smart_brain/governance/lifecycle.rb +225 -0
- data/lib/smart_brain/governance/tiers.rb +60 -0
- data/lib/smart_brain/memory_extractor/extractor.rb +25 -7
- data/lib/smart_brain/memory_store/in_memory.rb +202 -17
- data/lib/smart_brain/memory_store/postgres.rb +500 -0
- data/lib/smart_brain/model_provider/base.rb +87 -0
- data/lib/smart_brain/model_provider/factory.rb +49 -0
- data/lib/smart_brain/model_provider/ollama.rb +60 -0
- data/lib/smart_brain/model_provider/openai.rb +60 -0
- data/lib/smart_brain/model_provider/stub.rb +26 -0
- data/lib/smart_brain/model_provider.rb +7 -0
- data/lib/smart_brain/observability/tracker.rb +39 -1
- data/lib/smart_brain/retrievers/exact_retriever.rb +6 -0
- data/lib/smart_brain/retrievers/memory_retriever.rb +59 -5
- data/lib/smart_brain/runtime.rb +288 -16
- data/lib/smart_brain/scopes/conflict_resolver.rb +67 -0
- data/lib/smart_brain/scopes/registry.rb +133 -0
- data/lib/smart_brain/scopes/resolver.rb +32 -0
- data/lib/smart_brain/server/http_app.rb +143 -0
- data/lib/smart_brain/server/mcp_server.rb +385 -0
- data/lib/smart_brain/server/service.rb +129 -0
- data/lib/smart_brain/support/levenshtein.rb +35 -0
- data/lib/smart_brain/version.rb +5 -5
- data/lib/smart_brain.rb +80 -35
- 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
|