Skip to content

企业级 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.mod

2. 核心模块定义 (Interface)

internal/usecasedomain 层定义接口,依赖接口而非实现。

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. 工程化考量

  1. 配置管理: 使用 viper 加载 YAML 配置。
  2. 日志监控: 使用 zap 记录日志,接入 Prometheus 监控检索耗时和 Token 消耗。
  3. 异步处理: 文档上传后的 Embedding 过程应该放入消息队列(Kafka/RabbitMQ)异步执行,避免阻塞 HTTP 接口。

总结

一个优秀的 Golang RAG 项目应该是模块化的。当你需要把 VectorStore 从 Milvus 切换到 Weaviate 时,只需要实现一个新的 VectorStore 接口实现类,而不需要修改一行 UseCase 代码。

🚀 学习遇到瓶颈?想进大厂?

看完这篇技术文章,如果还是觉得不够系统,或者想在实战中快速提升?
王中阳的就业陪跑训练营,提供定制化学习路线 + 企业级实战项目 + 简历优化 + 模拟面试。

了解训练营详情