SmartBrain
SmartBrain(v0.2.0)是一个面向 Agent 的记忆运行时(Memory Runtime)与上下文编排器(Context Composer)。
在多轮 Agent 对话中,SmartBrain 解决的核心问题是:如何高效地记录、检索、融合记忆,并装配出"最小充分"的上下文发给 LLM。
核心职责
-
commit_turn:记录事件真相并沉淀结构化长期记忆(写链路) -
compose_context:在每轮请求前生成最小充分上下文(读 + 装配链路) - 联动 SmartRAG:对话记忆由 SmartBrain 管理(EventStore / MemoryStore),资源检索由 SmartRAG 提供(适配层)
架构边界
SmartBrain 把 Agent 的可用信息分为两类,分工明确:
| 类别 | 管理者 | 内容 | 特点 |
|---|---|---|---|
| 对话记忆 (Conversation Memory) | SmartBrain | 原始事件(turn/messages/tool_calls/refs)、结构化记忆(profile/preferences/tasks/decisions/entities/events)、滚动摘要(working_summary) | 强时序、更新频繁、有写入门控与冲突管理 |
| 资源记忆 (Resource Memory) | SmartRAG | 文档/网页/代码仓库等长期资源 | 相对静态、内容大、需要高质量检索 |
SmartRAG 是 SmartBrain 的"资源知识库后端"。SmartBrain 不复制 SmartRAG 的索引能力,只通过契约适配层调用它。
当前进展
当前仓库已实现并打通:
-
commit_turn/compose_context主链路 - Retention / Consolidation / Retrieval / Composition 四类策略
- 检索器:exact(关键词匹配) + relational(实体/引用关联)
- 融合层:去重、词重叠加分重排、多样性约束、预算截断、memory/resource 比例控制
- SmartRAG 适配器:
NullClient(默认空)/HttpClient(HTTP 远程)/DirectClient(进程内直接调用) - 契约与可观测:
request_id→plan_id→context_id全链路追踪 - RSpec 测试(单元 + 集成 + PostgreSQL 回归)
安装
从 RubyGems 安装(推荐给最终用户)
gem install smart_brain装完即可用,默认零外部依赖(memory 后端 + stub LLM):
smart_brain --version
smart_brain status完整安装/配置/可选能力(PostgreSQL 持久化、Ollama/OpenAI LLM、SmartRAG 资源检索)见 docs/installation.md。
从源码开发
bundle install如遇本地权限或 shared gem 污染:
bundle config set --local path 'vendor/bundle'
bundle config set --local disable_shared_gems 'true'核心 API
SmartBrain 提供以下核心工作流 API:
SmartBrain.configure(config_path: nil, smart_rag_client: nil, clock: -> { Time.now.utc })
初始化运行时并注入 SmartRAG 客户端(可选)。
-
config_path:策略配置文件路径,默认config/brain.yml -
smart_rag_client:SmartRAG 适配器实例,默认NullClient(资源证据为空) -
clock:时间源函数,默认Time.now.utc
SmartBrain.commit_turn(session_id:, turn_events:, domain_id: nil, scope_context: nil)
写入事件、抽取记忆、冲突处理、摘要更新。返回 CommitResult。
turn_events 支持的事件类型:
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
messages |
Array | 消息列表,每条含 role 和 content
|
[{ role: 'user', content: '...' }] |
decisions |
Array | 决策/选择/承诺 | [{ key: 'decision:pg:pool_size', decision: '...' }] |
tasks |
Array | 任务,含状态流转 | [{ key: 'task:brain:mvp', status: 'doing' }] |
goals |
Array | 长期目标 | [{ key: 'goal:learn:ruby', goal: '...' }] |
entities |
Array | 重要实体 | [{ key: 'entity:lang:ruby', name: 'Ruby', canonical: 'ruby', kind: 'language', remember: true }] |
preferences |
Array | 偏好,需 confirmed: true 才写入长期记忆 |
[{ key: 'pref:writing:tone', value: '...', confirmed: true }] |
events |
Array | 里程碑/异常等高价值事件 | — |
refs |
Array | 文件/URL/产物引用 | — |
retractions |
Array | 撤回旧记忆 | [{ type: 'decisions', key: 'decision:old:topic' }] |
写入门控策略:
-
必写:
tasks、decisions(confidence: 0.9) -
条件写:
goals(需显示声明)、preferences(需confirmed: true)、entities(需出现频率 ≥ 2 或含 URL/路径等结构信号) - 不写入:闲聊、未确认推测
冲突处理:
- 覆盖型(
preferences/goals/tasks):新值写入,旧值 →status=superseded - 多版本并存(
decisions/events):保留历史 - 撤回(
retracted):旧条目 →status=retracted
返回值:
{
ok: true,
commit_id: "uuid",
session_id: "...",
turn_id: "uuid",
memory_written: { count: 3, items: [...], conflicts: [...] },
summary: { triggered: true, trigger_reason: "turn_threshold", text: "..." },
explain: { retention: [...], conflicts: [...], summary: {...} }
}SmartBrain.compose_context(session_id:, user_message:, agent_state: {}, domain_id: nil, scope_context: nil)
生成 ContextPackage,内部执行 5 阶段流水线:
Plan → Retrieve(双路并行)→ Fuse → Compose → ContextPackage
-
Plan:
RetrievalPlanner分析意图,生成RetrievalPlan(主查询 + 扩展查询),自动判断是否启用资源检索(enable_resource_retrieval: 'auto'时,关键词"查资料/引用/论文/文档/compare/对比/来源"触发) -
Retrieve:双路并行 —
- 记忆侧:
ExactRetriever(词重叠匹配,支持中英文分词)+RelationalRetriever(实体/引用关联) - 资源侧:通过 SmartRAG 适配器检索文档资源(仅当 planner 启用时)
- 记忆侧:
-
Fuse:
Merger归一化 → 去重 → 词重叠加分重排 → 多样性约束(同文档 ≤ 3 条,同来源 ≤ 2 条)→ 预算裁剪(总条数 ≤evidence_max_items,snippet ≤max_snippet_chars)→ memory/resource 比例控制(默认 40/60) -
Compose:
ContextComposer按固定槽位装配:system_blocks → developer_blocks → working_summary → recent_turns → evidence → user_message -
输出:
ContextPackage(经过契约校验)
多 Scope 记忆
0.2.0 支持在同一 domain 内联合读取 global/project/expert/task 记忆,并将长期记忆写入明确授权的 scope:
scope_context = {
read: [
{ type: 'global', id: 'default' },
{ type: 'project', id: 'project-001' },
{ type: 'task', id: 'task-030' }
],
write: [
{ type: 'project', id: 'project-001' },
{ type: 'task', id: 'task-030' }
],
default_write: { type: 'task', id: 'task-030' }
}
SmartBrain.commit_turn(
domain_id: 'tenant-a', session_id: 'conversation-1', scope_context: scope_context,
turn_events: { decisions: [{ key: 'decision:db', decision: 'use PostgreSQL' }] }
)write 必须是 read 的子集,default_write 必须包含在 write 中。单条结构化记忆可以用 scope_ref 覆盖默认写 scope,但目标仍须在 write 中。提供 scope_context 时必须同时提供 domain_id;两者都省略时继续使用隔离的 legacy/session:<session_id>,兼容 0.1.x 调用。
返回值(ContextPackage):
{
version: '0.1',
context_id: "uuid", # 本次装配唯一 ID
session_id: "...",
working_summary: "...", # 滚动摘要文本
recent_turns: [...], # 最近 N 轮对话
evidence: [ # 融合后的证据列表
{
id: "...",
source: 'memory' | 'resource',
source_uri: "...",
title: "...",
snippet: "...",
mode: 'exact' | 'relational' | 'hybrid',
score: 0.85,
ref: { memory_item_id: "..." }
}
],
user_message: { role: 'user', content: "..." },
constraints: {
token_budget: { limit: 8000, used_estimate: 1200 },
diversity: { by_document: 3, by_source: 2 },
truncation: { snippets_max_chars: 800, recent_turns_max: 8 }
},
debug: {
trace: { request_id: "uuid", plan_id: "uuid", context_id: "uuid" },
planner: { purpose: "qa", queries: ["..."] },
why_selected: ["item-1 score=0.85 source=memory"],
ignored: [],
dropped: [{ id: "...", reason: "diversity" }]
}
}SmartBrain.diagnostics
返回 compose/commit 观测日志与指标快照:
{
compose_logs: [...],
commit_logs: [...],
metrics: {
compose_p95_ms: 45.2,
memory_resource_ratio: "3/2",
token_over_budget_rate: 0.0
}
}全链路追踪:每个请求有 request_id → plan_id → context_id 三连 ID,贯穿 Plan → Retrieve → Fuse → Compose 全流程。
策略配置
SmartBrain 的四类策略通过 config/brain.yml 配置:
| 策略类别 | 生效点 | 关键参数 |
|---|---|---|
| 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
|
| Retrieval(检索) | compose_context |
top_k: 30、candidate_k: 200、enable_resource_retrieval: auto、query_expansion: { enabled: true, max_queries: 8 }
|
| 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"
|
| Observability(可观测) | 全局 | trace: true |
项目结构
lib/smart_brain.rb # Ruby 入口 API
lib/smart_brain/version.rb # 版本号
lib/smart_brain/configuration.rb # YAML 配置加载
lib/smart_brain/runtime.rb # 主运行时编排(commit_turn / compose_context)
lib/smart_brain/contracts/ # 契约校验
retrieval_plan.rb # RetrievalPlan 校验
evidence_pack.rb # EvidencePack 校验
context_package.rb # ContextPackage 校验
lib/smart_brain/event_store/
in_memory.rb # 事件存储(当前内存实现,含 entity_frequencies 统计)
lib/smart_brain/memory_store/
in_memory.rb # 记忆存储(当前内存实现,含 upsert 冲突管理)
lib/smart_brain/memory_extractor/
extractor.rb # 从事件中抽取结构化记忆(写入门控)
lib/smart_brain/consolidator/
working_summary.rb # 滚动摘要维护(turn_threshold / token_pressure / stage_event 触发)
lib/smart_brain/retrieval_planner/
planner.rb # 生成 RetrievalPlan(query expansion + 资源检索启停判断 + filter hints)
lib/smart_brain/retrievers/
exact_retriever.rb # 词重叠关键词匹配(支持中英文)
relational_retriever.rb # 实体/引用关联检索
memory_retriever.rb # 记忆检索门面(exact + relational 合并)
lib/smart_brain/fusion/
merger.rb # 多源融合(归一化 → 去重 → 词重叠加分重排 → 多样性 → 预算截断)
lib/smart_brain/context_composer/
composer.rb # 固定槽位上下文装配 + token 估算
lib/smart_brain/adapters/smart_rag/
null_client.rb # 默认空适配器(资源证据为空)
http_client.rb # HTTP 远程调用适配器(支持超时降级)
direct_client.rb # 进程内直接调用适配器
lib/smart_brain/observability/
tracker.rb # 日志与指标(P95 / memory_resource_ratio / token_over_budget_rate)
config/
example_llm.yml # LLM 配置示例(ollama + silicon_flow)
docs/
smartbrain_design.md # 总体设计文档
policies.md # 策略规范文档
memory_types.md # 记忆类型规范(9 种 type + key 规则)
context_package.md # ContextPackage 协议
retrieval_plan.md # RetrievalPlan 协议
evidence_pack.md # EvidencePack 协议
media_memory_schema.md # 媒体记忆契约、生产能力和迁移约束
spec/
spec_helper.rb
commit_turn_spec.rb # commit_turn 单元测试
compose_context_spec.rb # compose_context 单元测试
fusion_merger_spec.rb # 融合层测试
integration_smart_rag_adapter_spec.rb # SmartRAG 适配器集成测试
observability_metrics_spec.rb # 可观测性测试
planner_policy_spec.rb # 检索计划策略测试
regression_context_spec.rb # 回归测试
example.rb # 简单示例
conversation_demo.rb # 完整多轮对话演示(SmartBrain + SmartRAG + SmartAgent)
快速开始
仅 SmartBrain(不依赖外部服务)
require_relative 'lib/smart_brain'
SmartBrain.configure
SmartBrain.commit_turn(
session_id: 'demo',
turn_events: {
messages: [
{ role: 'user', content: '请记住:默认数据库是 Postgres。' },
{ role: 'assistant', content: '已记录。' }
],
decisions: [
{ key: 'decision:smartbrain:storage', decision: 'Use Postgres by default' }
]
}
)
context = SmartBrain.compose_context(
session_id: 'demo',
user_message: '继续并总结关键结论'
)
puts context[:context_id]
puts context.dig(:debug, :trace, :request_id)
puts context.dig(:debug, :trace, :plan_id)多轮对话循环(与 SmartAgent + SmartRAG 联动)
require 'smart_brain'
require 'smart_agent'
require 'smart_rag'
require_relative 'lib/smart_brain/adapters/smart_rag/direct_client'
# 1. 初始化 SmartRAG
rag = SmartRAG::SmartRAG.new(database: {...}, llm: {...})
client = SmartBrain::Adapters::SmartRag::DirectClient.new(rag: rag)
# 2. 初始化 SmartBrain
SmartBrain.configure(smart_rag_client: client)
# 3. 初始化 SmartAgent
engine = SmartAgent::Engine.new('./config/example_agent.yml')
agent = engine.build_agent(:brain_assistant)
# 4. 多轮对话循环
session_id = "demo-session"
user_message = "Ruby 类名应该用什么命名风格?"
# 4a. 获取上下文(内部可能调用 SmartRAG 检索文档资源)
context = SmartBrain.compose_context(
session_id: session_id,
user_message: user_message,
agent_state: { turn: 1 }
)
# 4b. 调用 Agent(LLM)生成回复
response = agent.please(context.to_json)
# 4c. 提交本轮(沉淀记忆)
commit = SmartBrain.commit_turn(
session_id: session_id,
turn_events: {
messages: [
{ role: 'user', content: user_message },
{ role: 'assistant', content: response.to_s }
],
decisions: [
{ key: 'decision:ruby:class_naming', decision: '使用 CamelCase' }
],
entities: [
{ key: 'entity:lang:ruby', name: 'Ruby', canonical: 'ruby', kind: 'language', remember: true }
]
}
)
# 5. 查看诊断信息
pp SmartBrain.diagnosticsSmartRAG 集成方式
1) NullClient(默认)
不配置 smart_rag_client 时,资源证据为空,仅使用记忆侧证据。适合纯对话记忆场景。
SmartBrain.configure # 无需额外配置2) HttpClient(HTTP 远程)
通过 SmartRAG HTTP 服务调用远端资源库,支持 JSON URL 导入、multipart 文件上传和超时降级:
client = SmartBrain::Adapters::SmartRag::HttpClient.for_url(
base_url: 'http://127.0.0.1:9393',
timeout_seconds: 30,
headers: { 'Authorization' => "Bearer #{ENV.fetch('SMARTRAG_TOKEN')}" }
)
SmartBrain.configure(smart_rag_client: client)
SmartBrain.add_video(source: '/data/demo.mp4', options: { tags: ['demo'] })本地路径自动使用 multipart 上传,HTTP/HTTPS source 使用 JSON URL 导入。OCR、视觉描述和转写 callable 不能跨进程序列化,必须配置在远端 SmartRAG HttpApp 的 extractors: 中。
也可以继续通过自定义 transport lambda 集成其他协议:
transport = lambda do |plan, timeout_seconds:|
{
plan_id: 'p1',
supports_language_filter: true,
evidences: []
}
end
scope_mapper = lambda do |domain_id:, scopes:|
{ filters: { topic_ids: scopes.map { |scope| "#{domain_id}:#{scope[:type]}:#{scope[:id]}" } } }
end
client = SmartBrain::Adapters::SmartRag::HttpClient.new(
transport: transport, timeout_seconds: 2, scope_mapper: scope_mapper
)
SmartBrain.configure(smart_rag_client: client)3) DirectClient(进程内直接调用)
直接依赖 smart_rag gem,进程内调用。需要配置 PostgreSQL 连接和 LLM。
require 'smart_rag'
require_relative 'lib/smart_brain/adapters/smart_rag/direct_client'
rag_config = SmartRAG::Config.load(ENV.fetch('SMARTRAG_CONFIG_PATH'))
rag = SmartRAG::SmartRAG.new(rag_config)
scope_mapper = lambda do |domain_id:, scopes:|
{ filters: { topic_ids: scopes.map { |scope| "#{domain_id}:#{scope[:type]}:#{scope[:id]}" } } }
end
client = SmartBrain::Adapters::SmartRag::DirectClient.new(rag: rag, scope_mapper: scope_mapper)
SmartBrain.configure(smart_rag_client: client)Mapper 返回的 filters 会作为 scope_filters 发送给 SmartRAG。业务 scope 存在时,后端必须返回 scope_filter_applied: true;mapper 缺失、映射失败或后端未确认时,adapter 默认 fail closed,丢弃资源证据并在 warnings 和 explain.ignored_fields 中说明原因。
多媒体记忆(MVP)
SmartBrain 通过 SmartRAG 保存图片、音频和视频的技术元数据,并把描述、OCR 或转写文本作为普通 section 建立全文及向量索引:
SmartBrain.add_image(
source: '/data/whiteboard.jpg',
options: {
title: 'Q3 路线图白板',
tags: ['roadmap'],
image_describer: ->(path) { vision_client.describe(path) },
ocr_extractor: ->(path) { ocr_client.extract(path) }
}
)
SmartBrain.add_audio(
source: '/data/weekly-meeting.wav',
options: {
tags: ['meeting'],
audio_transcriber: ->(path) { whisper_client.transcribe(path) }
}
)
SmartBrain.add_video(
source: '/data/product-demo.mp4',
options: {
tags: ['demo'],
# start/end 使用秒;也可以返回 start_ms/end_ms。
video_transcriber: ->(audio_path) {
whisper_client.transcribe(audio_path, timestamps: true)
# => { segments: [{ text: '打开设置', start: 5.0, end: 9.0 }] }
},
frame_describer: ->(frame_path, timestamp_ms) {
vision_client.describe(frame_path, timestamp_ms: timestamp_ms)
},
frame_interval_seconds: 30,
max_frames: 12
}
)add_media 会根据扩展名和 MIME 类型自动判断类型;add_image、add_audio、add_video 固定类型。Pillow 或 ffprobe 缺失、语义提取器未配置或调用失败时,导入仍会保存文件名、标签和可获得的技术元数据,返回 status: "partial" 及 warnings。
按媒体类型限制资源检索:
SmartBrain.compose_context(
session_id: 'demo',
user_message: '查资料:路线图白板上写了什么?',
resource_filters: { media_type: ['image'] }
)SmartRAG 的 P1 配置可启用本地内容寻址存储(SHA-256 去重),并内置 OpenAI-compatible 图片描述/音频转写与可选 Tesseract OCR。未启用内容存储时仍保存原文件路径或 URL。
异步导入和状态查询:
job = SmartBrain.enqueue_media(
source: '/data/long-demo.mp4',
options: { media_type: 'video', tags: ['demo'] }
)
status = SmartBrain.media_job(job_id: job[:job_id])
failed = SmartBrain.media_jobs(status: 'failed', limit: 20)
SmartBrain.retry_media_job(job_id: failed[:jobs].first[:id])
SmartBrain.cancel_media_job(job_id: job[:job_id]) # 仅 queued 状态可取消
queue = SmartBrain.media_job_statisticsHTTP 客户端会发送 options.async=true,服务端返回 202;SmartRAG worker 负责消费 PostgreSQL media_jobs 队列。P2 支持任务分页/状态过滤、取消、人工重试、卡死任务恢复、保留期清理和健康指标。默认 NullClient 对写入和任务查询返回 unsupported。
P3 支持按调用方隔离的幂等入队。HTTP 模式可把幂等键放在 options 中;服务端也接受标准请求头 Idempotency-Key:
job = SmartBrain.enqueue_media(
source: '/data/long-demo.mp4',
options: { media_type: 'video', idempotency_key: 'demo-video-v1' }
)部署当前版本前,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 键不影响指纹,数组顺序仍有意义。
HttpClient 会将该 409 转成 SmartBrain 的失败结果:status: "failed",并在 warnings 中包含 SmartRAG HTTP 409。调用方应保持原载荷和原幂等键进行网络重试;业务载荷发生变化时必须生成新键。DirectClient 进程内调用时,SmartRAG 的 MediaJobQueue::IdempotencyConflict 会直接向上传播。
多实例部署应把 SmartRAG media.content_store.provider 配置成 s3,可连接 AWS S3 或 MinIO。P3 worker 使用 heartbeat 租约避免长任务被错误恢复,并维护媒体对象引用计数和零引用垃圾回收。HTTP Bearer token 可继续通过 HttpClient.for_url(headers: ...) 设置。
SmartRAG 迁移 016 将认证 principal 写入文档 owner,并把它应用到检索、文档读取/列表/删除和统计。HTTP 检索会把 principal 对应的文档 ID 下推给搜索后端,并在生成 EvidencePack 前根据 PostgreSQL owner 再次过滤候选;即使后端忽略 document_ids,也不能返回其他租户证据。failed 任务保留期间,其 staging 对象不会被 GC 删除,人工重试仍可读取原始媒体;同步导入在后续处理失败时留下的零引用对象可被 GC 回收。
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”。
视频的转写片段和关键帧描述分别保存为 section。检索命中后,EvidencePack 的 metadata 会包含 extraction_kind、start_ms、end_ms 或 frame_timestamp_ms,调用方可据此跳转到视频位置。视频处理依赖 ffmpeg;缺失时导入降级为 partial。
运行示例
example.rb
SmartBrain + SmartAgent + SmartPrompt + SmartRAG 联动示例(2 轮对话)。
bundle exec ruby example.rb依赖的本地文件:
config/example_agent.ymlconfig/example_llm.ymlagents/brain_assistant.rbworkers/brain_assistant.rbtemplates/brain_assistant.erb
conversation_demo.rb
完整多轮对话演示(5 轮),包含 SmartRAG 文档存储、检索、SmartAgent LLM 调用和 SmartBrain 记忆沉淀全流程。
bundle exec ruby conversation_demo.rb测试
bundle exec rspec测试覆盖 commit/compose、存储、治理、KG、协议适配、多 scope 隔离以及 PostgreSQL 迁移和持久化。
常见问题
1) cannot load such file -- sequel/extensions/pgvector
当前示例已在 example.rb 做兼容处理(Sequel.extension 'pgvector' + 去除 database.extensions 连接参数)。
2) Config file not found: config/llm_config.yml
example.rb 已将 SmartRAG 里 EmbeddingService 的 config_path 注入为 ./config/example_llm.yml。
3) ruby-lsp: not found
gem install --user-install ruby-lsp debug并将用户 gem bin 加入 PATH。
路线图
- 将 EventStore/MemoryStore 从内存实现切换到 Postgres 持久化实现
- 接入真实 reranker / embedding 模型(当前为规则分 + 词重叠加分)
- 完善 SmartRAG ingest 与跨会话评估工具
- 扩展
SemanticRetriever(依赖 pgvector/embedding) - 支持
memory_chunks表(FTS 索引 + 可选 embedding 字段)