检索增强生成(Retrieval-Augmented Generation, RAG)是一种将大型语言模型(LLM)与外部知识库相结合的技术,能够有效提升模型回答的准确性和时效性。本文将介绍如何基于 CloudWeGo 开源的 Eino 框架,使用 Go 语言从零开始构建一个完整的 RAG 应用。

RAG 核心架构

一个典型的 RAG 应用流程主要包含两个阶段:数据索引检索生成

  1. 数据索引(Indexing):这是离线预处理阶段。

    • 加载(Load):从数据源(如文件、数据库)加载原始文档。
    • 分割(Split):将加载的文档切割成较小的、语义完整的文本块(Chunks)。
    • 向量化(Embed):使用 Embedding 模型将每个文本块转换为向量(Vector)。
    • 存储(Store):将文本块及其对应的向量存入专门的向量数据库中,并建立索引。
  2. 检索生成(Retrieval & Generation):这是在线实时处理阶段。

    • 用户提问:用户输入一个问题。
    • 问题向量化:使用相同的 Embedding 模型将用户问题转换为向量。
    • 检索(Retrieve):在向量数据库中,根据用户问题的向量,检索出最相似或最相关的若干个文本块。
    • 生成(Generate):将用户原始问题和检索到的文本块一起组合成一个提示(Prompt),然后将其提交给 LLM,由 LLM 生成最终的、基于外部知识的回答。

基于 Eino 框架的核心组件实现

Eino 框架为构建 LLM 应用提供了模块化的组件,可以帮助我们快速实现 RAG 流程中的各个关键部分。

1. Loader:数据加载器

Loader 的职责是从各种数据源读取原始资料。数据源可以是本地文件(如 TXT、Markdown、PDF),也可以是数据库或网络 API。Eino 提供了标准化的接口,让我们能方便地接入不同类型的数据。

一个基础的 Loader 需要实现一个 Load 方法,读取数据并将其转换为统一的文档格式。

// 伪代码示例
package loader

// Document 表示加载后的标准文档结构
type Document struct {
    Content string
    Metadata map[string]interface{}
}

// Loader 定义了加载器的接口
type Loader interface {
    Load(source string) ([]Document, error)
}

2. Splitter:文本分割器

由于 LLM 的上下文窗口大小有限,直接将长文档输入模型是不可行的。Splitter 的作用就是将长文本切分成大小适中的文本块。合理的切分策略至关重要,它直接影响后续的检索效果。

常见的分割策略包括:

  • 固定长度分割:按固定字符数或 Token 数进行切分。
  • 字符分割:按特定字符(如换行符 \n)进行分割。
  • 递归字符分割:一种更智能的策略,它会尝试按一组不同的分隔符(如 \n\n, \n, )进行递归分割,以尽可能保持语义完整性。
// 伪代码示例
package splitter

import "your_project/loader"

// Splitter 定义了分割器的接口
type Splitter interface {
    Split(documents []loader.Document) ([]loader.Document, error)
}

3. Indexer:索引器

Indexer 负责将分割后的文本块进行向量化,并存入向量数据库。这个过程是构建知识库的核心。

  1. 选择 Embedding 模型:选择一个合适的模型将文本转换为高维向量。开源或商业模型均可。
  2. 调用模型生成向量:遍历所有文本块,调用 Embedding 模型 API 获取对应的向量表示。
  3. 存入向量数据库:将文本块原文和其向量一并存入向量数据库(如 FAISS、Milvus、ChromaDB 等)。数据库会对这些向量建立索引,以支持高效的相似度搜索。

4. Retriever:检索器

当用户提出问题后,Retriever 开始工作。它的任务是在海量的知识库中,快速、准确地找到与问题最相关的信息。

  1. 查询向量化:将用户的查询语句通过同一个 Embedding 模型转换为查询向量。
  2. 相似度搜索:在向量数据库中执行相似度搜索(如余弦相似度、欧氏距离等),找出与查询向量最接近的 Top-K 个文本块向量。
  3. 返回相关文本:将这 Top-K 个向量对应的原始文本块作为上下文信息返回。

整合与生成

最后一步,我们将检索到的上下文信息与用户的原始问题整合成一个完整的提示,并将其发送给 LLM。

一个典型的提示模板如下:

请根据以下提供的上下文信息来回答用户的问题。如果上下文中没有相关信息,请直接回答“我不知道”。

上下文:
---
{retrieved_context}
---

用户问题:{user_question}

将检索到的文本块填充到 {retrieved_context},用户问题填充到 {user_question},然后将整个提示发送给 LLM API,即可获得最终的答案。

总结

通过 Eino 框架,我们可以将复杂的 RAG 流程拆解为一系列清晰、独立的组件(Loader, Splitter, Indexer, Retriever)。这种模块化的设计不仅简化了开发流程,也使得各个组件可以被灵活替换和优化,从而更容易地构建出健壮、高效的 Go RAG 应用。

项目源码参考github.com/OuterCyrex/Eino-example


👉 如果你需要 ChatGPT 代充 / Claude / Claude Code / 镜像 / 中转 API