企业级 RAG 项目实战 (Project Structure)
从 Demo 到生产环境,最大的挑战是工程化。本章将提供一个基于 Clean Architecture (整洁架构) 的 Golang RAG 项目标准结构。
1. 项目目录结构
我们推荐如下的目录结构,将业务逻辑、RAG 引擎、接口层解耦。
text
go-rag-service/
├── cmd/
│ └── api/
│ └── main.go # 程序入口
├── config/ # 配置文件结构体
├── internal/
│ ├── api/ # HTTP/gRPC 接口层
│ │ ├── handler.go # 处理 HTTP 请求
│ │ └── router.go # 路由定义
│ ├── entity/ # 领域实体 (Document, Chunk)
│ ├── usecase/ # 业务逻辑层 (协调 RAG 流程)
│ │ └── chat_usecase.go # 核心对话逻辑
│ └── infrastructure/ # 基础设施层 (具体实现)
│ │ ├── llm/ # LLM 适配器 (OpenAI, Ollama)
│ │ ├── vectorstore/ # 向量库适配器 (Milvus, Weaviate)
│ │ ├── loader/ # 文档加载器
│ │ └── pdf/ # PDF 解析具体实现
├── pkg/ # 公共工具包
│ └── textsplitter/ # 切分工具
└── go.mod2. 核心模块定义 (Interface)
在 internal/usecase 或 domain 层定义接口,依赖接口而非实现。
go
// internal/entity/rag_interface.go
package entity
import "context"
// LLMProvider 定义大模型行为
type LLMProvider interface {
Generate(ctx context.Context, prompt string) (string, error)
Embed(ctx context.Context, texts []string) ([][]float32, error)
}
// VectorStore 定义向量库行为
type VectorStore interface {
Save(ctx context.Context, docs []Document) error
Search(ctx context.Context, queryVec []float32, topK int) ([]Document, error)
}
// DocumentLoader 定义加载行为
type DocumentLoader interface {
Load(ctx context.Context, source string) ([]Document, error)
}3. 业务逻辑实现 (UseCase)
UseCase 层负责编排整个 RAG 流程,它不关心底层用的是 Milvus 还是 PGVector,只调用接口。
go
// internal/usecase/chat_usecase.go
package usecase
import (
"context"
"fmt"
"go-rag-service/internal/entity"
)
type ChatUseCase struct {
llm entity.LLMProvider
repo entity.VectorStore
}
func NewChatUseCase(llm entity.LLMProvider, repo entity.VectorStore) *ChatUseCase {
return &ChatUseCase{llm: llm, repo: repo}
}
func (uc *ChatUseCase) Chat(ctx context.Context, query string) (string, error) {
// 1. Query Embedding
queryVecs, err := uc.llm.Embed(ctx, []string{query})
if err != nil {
return "", err
}
// 2. Retrieval
docs, err := uc.repo.Search(ctx, queryVecs[0], 5)
if err != nil {
return "", err
}
// 3. Build Context
contextStr := ""
for _, doc := range docs {
contextStr += fmt.Sprintf("- %s\n", doc.Content)
}
// 4. Generate
prompt := fmt.Sprintf("Context:\n%s\nQuestion: %s", contextStr, query)
return uc.llm.Generate(ctx, prompt)
}4. 依赖注入 (Main)
在 cmd/api/main.go 中组装所有组件。
go
package main
import (
"go-rag-service/internal/api"
"go-rag-service/internal/infrastructure/llm"
"go-rag-service/internal/infrastructure/vectorstore"
"go-rag-service/internal/usecase"
"github.com/gin-gonic/gin"
)
func main() {
// 1. 初始化基础设施
milvusStore := vectorstore.NewMilvusStore("localhost:19530")
openAIClient := llm.NewOpenAIClient("sk-xxxx")
// 2. 初始化业务逻辑 (注入依赖)
chatUC := usecase.NewChatUseCase(openAIClient, milvusStore)
// 3. 初始化 HTTP Handler
handler := api.NewChatHandler(chatUC)
// 4. 启动 Server
r := gin.Default()
r.POST("/chat", handler.HandleChat)
r.Run(":8080")
}5. 工程化考量
- 配置管理: 使用
viper加载 YAML 配置。 - 日志监控: 使用
zap记录日志,接入 Prometheus 监控检索耗时和 Token 消耗。 - 异步处理: 文档上传后的 Embedding 过程应该放入消息队列(Kafka/RabbitMQ)异步执行,避免阻塞 HTTP 接口。
总结
一个优秀的 Golang RAG 项目应该是模块化的。当你需要把 VectorStore 从 Milvus 切换到 Weaviate 时,只需要实现一个新的 VectorStore 接口实现类,而不需要修改一行 UseCase 代码。
