Meilisearch 集成与 RAG 内容检索总结
Meilisearch 集成与 RAG 内容检索总结
本次集成的 Meilisearch 已经不只是普通站内搜索,而是 AI Chat 的 RAG 内容检索层。
当前流程是:
用户问题 → AI 提取搜索关键词 → Meilisearch 检索博客文章 → 构建 RAG 上下文 → 注入 system prompt → 大模型基于上下文回答
因此它属于 RAG 中的 Retrieval(检索)+ Context Building(上下文构建) 部分。
不过它不是向量数据库 RAG,目前使用的是 Meilisearch 的全文检索能力,而不是 embedding 相似度检索。
本次集成目标
- 使用
meilisearch-rails接入 Meilisearch。 - 让
Post文章支持全文搜索。 - 让 AI 聊天优先从博客内容中检索资料再回答。
- 修复 AI 编造文章链接的问题。
- 当 RAG 已检索到内容时,减少不必要的工具调用。
- 支持 Kamal 生产环境部署 Meilisearch accessory。
主要改动文件
Gemfile
新增:
gem "meilisearch-rails"
用于 Rails 模型接入 Meilisearch。
config/initializers/meilisearch.rb
新增 Meilisearch 配置:
MeiliSearch::Rails.configuration = {
meilisearch_url: ENV.fetch("MEILISEARCH_URL", "http://localhost:7700"),
meilisearch_api_key: ENV.fetch("MEILI_MASTER_KEY", "blog_meili_key"),
per_environment: false
}
说明:
- 本地默认连接
http://localhost:7700。 - 生产环境通过
MEILISEARCH_URL指向 Kamal accessory。 MEILI_MASTER_KEY用于访问 Meilisearch。
app/models/post.rb
Post 模型加入:
include MeiliSearch::Rails
并配置索引:
meilisearch index_uid: "posts", synchronous: true, if: :published? do
attributes :title, :slug, :excerpt, :content, :published_at, :sticky
attribute :category_name do
category&.name
end
attribute :category_slug do
category&.slug
end
attribute :tag_names do
tags.map(&:name)
end
attribute :tag_slugs do
tags.map(&:slug)
end
searchable_attributes [:title, :content, :excerpt, :category_name, :tag_names]
filterable_attributes [:category_slug, :tag_slugs, :sticky]
sortable_attributes [:published_at, :sticky]
displayed_attributes [:id, :title, :slug, :excerpt, :content, :category_name, :category_slug, :tag_names, :tag_slugs, :published_at, :sticky]
end
关键点:
if: :published?:只索引已发布文章。searchable_attributes:设置可搜索字段。displayed_attributes必须包含:id,否则meilisearch-rails无法把搜索结果映射回Post对象。
app/services/rag_retrieval_service.rb
新增 RAG 检索服务,职责:
- 接收用户问题。
- 用 AI 提取搜索关键词。
- 调用
Post.search(...)使用 Meilisearch 检索文章。 - 将结果格式化为 RAG 上下文。
- 返回可注入 system prompt 的内容。
示例:
用户问题:有哪些诗歌
AI 提取关键词:诗歌
Meilisearch 检索:Post.search("诗歌")
RAG 上下文:文章标题、链接、分类、标签、摘要、正文片段
AI 回答:基于上下文列出相关文章
RAG 上下文中包含完整文章链接:
https://moli.ink/posts/{slug}
这用于避免 AI 编造类似 https://suimeng.net/article/1 的错误链接。
app/jobs/ai_chat_response_job.rb
主要改动:
1. 注入 RAG 上下文
build_messages 会取最后一条用户消息,调用:
RagRetrievalService.new.formatted_context(last_user_msg.content)
如果检索到内容,就追加到 system prompt。
2. RAG 有结果时减少工具调用
当 has_rag = true 时,只提供 GetPostTool:
tools = has_rag ? tools_definition([ GetPostTool ]) : tools_definition
这样模型可以直接基于 RAG 上下文回答,不再重复调用:
search_postslist_categorieslist_posts_by_category
只有用户明确要求查看完整正文时,才调用 get_post。
3. 约束链接来源
system prompt 中明确要求:
- 如果上下文有检索内容,直接回答。
- 引用文章链接时必须使用上下文或工具返回的
url字段。 - 禁止编造链接。
Tool 层改动
涉及文件:
app/tools/search_posts_tool.rbapp/tools/get_post_tool.rbapp/tools/list_posts_by_category_tool.rb
主要变化:
search_posts从 SQLILIKE改为Post.search(...)。- 工具返回结果中增加
url字段。 - URL 统一使用:
https://moli.ink/posts/{slug}
本地开发配置
文件:Procfile.dev
新增 Meilisearch 进程:
meilisearch: /home/sui/meilisearch --master-key=blog_meili_key --http-addr localhost:7700 --db-path ./tmp/meili_data
本地可使用较短的 blog_meili_key。
生产部署配置
文件:config/deploy.yml
App 环境变量
env:
secret:
- MEILI_MASTER_KEY
clear:
MEILISEARCH_URL: http://javen_blog-meilisearch:7700
MEILISEARCH_URL 是 Rails app 容器访问 Meilisearch accessory 容器的 Docker 内网地址。
Meilisearch accessory
accessories:
meilisearch:
image: getmeili/meilisearch:v1.12
host: 43.166.1.239
port: "127.0.0.1:7700:7700"
env:
clear:
MEILI_ENV: production
secret:
- MEILI_MASTER_KEY
directories:
- data:/meili_data
说明:
- Meilisearch 作为 Kamal accessory 运行。
- 数据持久化到服务器目录。
- 端口只绑定到服务器本机
127.0.0.1,不直接暴露公网。 - 生产环境
MEILI_MASTER_KEY必须至少 16 字节。
生产环境部署步骤
本地执行:
export MEILI_MASTER_KEY=<至少16字节的安全随机字符串>
kamal accessory reboot meilisearch
kamal deploy
进入生产 console:
kamal console
重建索引:
Post.ms_reindex!
验证搜索:
Post.search("雪花").map { |p| [p.id, p.title, p.slug] }
Post.search("诗歌").map { |p| [p.id, p.title, p.slug] }
遇到的问题与解决
Post.reindex! 不存在
错误:
undefined method 'reindex!' for class Post
正确命令:
Post.ms_reindex!
Post.ms_reindex! 最初不存在
原因可能是代码未部署、控制台连接旧容器或 gem 未加载。
处理方式:
- 确认生产环境已部署最新代码。
- 退出旧 console,重新进入。
- 确认
meilisearch-rails已在生产镜像中安装。
DNS 解析失败
错误:
Failed to open TCP connection to javen_blog-meilisearch:7700
getaddrinfo: Temporary failure in name resolution
原因:
- Meilisearch accessory 未正常运行。
- app 容器与 accessory 不在同一个
kamalDocker 网络。
排查:
ssh ubuntu@43.166.xxx.xxx 'docker network inspect kamal --format "{{json .Containers}}"'
解决:
kamal accessory reboot meilisearch
kamal deploy
Meilisearch 容器反复重启
日志:
The master key must be at least 16 bytes in a production environment.
原因:
blog_meili_key
只有 14 字节,生产环境不允许。
解决:
export MEILI_MASTER_KEY=<至少16字节的安全随机字符串>
kamal accessory reboot meilisearch
kamal deploy
并确认 .kamal/secrets 中存在:
MEILI_MASTER_KEY=$MEILI_MASTER_KEY
当前能力边界
当前是基于 Meilisearch 的轻量 RAG:
- 支持全文关键词检索。
- 支持文章标题、正文、摘要、分类、标签搜索。
- 支持把检索结果注入大模型上下文。
- 支持减少 AI 编造链接。
- 支持在 RAG 有结果时减少工具调用。
但目前还不是 embedding/vector RAG:
- 没有使用向量数据库。
- 没有对文章分 chunk 做 embedding。
- 对语义相似问题的召回能力主要依赖关键词和 Meilisearch 的全文检索。
后续可优化方向
- 将长文章切成 chunk,再按 chunk 检索,提升长文问答效果。
- 增加向量 embedding 检索,支持更强语义召回。
- 将 Meilisearch 检索结果和向量检索结果合并排序。
- 将 RAG 命中来源记录到聊天消息中,便于调试。
- 为后台文章发布、更新、删除增加索引状态提示。