存取qDrant向量(Golang)

检索增强生成(Retrieval-Augmented Generation, RAG)成为解决模型知识局限性和幻觉问题的有效手段。在RAG架构中,向量数据库负责存储和检索文本的向量表示,而qDrant作为高性能的向量数据库,常被选为底层存储。本文将结合Go语言代码示例,详细介绍如何通过langchain-go和qdrant-go库存取qDrant向量,并实现一个完整的RAG流程。

环境:

  • qDrant 服务(本地或远程运行)
  • Ollama 服务(用于Embedding和文本生成)
    • 准备nomic-embed-text模型
    • 准备任意支持text的大模型
  • Go语言环境及以下库:
    • github.com/tmc/langchaingo(包含向量存储、文档加载器、文本分割器等)
    • github.com/qdrant/go-client(qDrant官方Go客户端)

新建文件夹config/
config文件含config.go,管理配置信息。包含qDrant地址、集合名称、Ollama URL、Embedding模型名和生成模型名等。

1. 集合管理:EnsureCollection 和 resetCollection

确保指定的集合存在,若不存在则创建。向量维度(768)与Embedding模型(如nomic-embed-text)匹配,距离度量采用余弦相似度。创建集合时指定的向量维度(768)必须与Embedding模型输出的维度完全一致。本例使用nomic-embed-text(维度768),若换用其他模型(如all-MiniLM-L6-v2维度384),需相应调整。
resetCollection:先删除指定集合,再重新创建,用于重置整个知识库。

func EnsureCollection(urlStr string, collectionName string) {
	myclient, err := qdrant.NewClient(&qdrant.Config{
		Host:   config.QdrantIP,
		Port:   6334,
		UseTLS: false,
	})
	if err != nil {
		log.Fatal("连接Qdrant失败:", err)
	}
	defer myclient.Close()
	ctx := context.Background()
	exists, err := myclient.CollectionExists(ctx, collectionName)
	if err != nil {
		log.Fatal("查询集合状态失败:", err)
	}
	if !exists {
		fmt.Printf("正在创建集合: %s...\n", collectionName)
		err = myclient.CreateCollection(ctx, &qdrant.CreateCollection{
			CollectionName: collectionName,
			VectorsConfig: qdrant.NewVectorsConfig(&qdrant.VectorParams{
				Size:     768, // nomic-embed-text 维度是 768:必须与 Embedding 模型一致
				Distance: qdrant.Distance_Cosine,
			}),
		})
		if err != nil {
			log.Fatal("创建集合失败:", err)
		}
		fmt.Println("集合创建成功")
	}
}
func resetCollection(name string) {
	myclient, _ := qdrant.NewClient(&qdrant.Config{
		Host: config.QdrantIP, Port: 6334, UseTLS: false,
	})
	defer myclient.Close()
	ctx := context.Background()
	_ = myclient.DeleteCollection(ctx, name)
	EnsureCollection(config.QdrantIP, name)
	fmt.Println("集合已重置,准备重新导入...")
}

2. 数据导入:IngestKnowledge

分块策略直接影响检索效果,需根据文档类型和任务调整块大小和重叠。

func IngestKnowledge(filePath string, shouldReset bool) {
    // 1. 重置或确保集合存在
    if shouldReset {
        resetCollection(config.Collection)
    } else {
        EnsureCollection(config.QdrantIP, config.Collection)
    }

    // 2. 根据文件类型选择加载器(PDF或文本)
    f, _ := os.Open(filePath)
    defer f.Close()
    var loader documentloaders.Loader
    if strings.HasSuffix(filePath, ".pdf") {
        loader = documentloaders.NewPDF(f, fileSize)
    } else {
        loader = documentloaders.NewText(f)
    }

    // 3. 加载并分割文档
    docs, _ := loader.LoadAndSplit(ctx, textsplitter.NewRecursiveCharacter(
        textsplitter.WithChunkSize(300),
        textsplitter.WithChunkOverlap(100),
    ))

    // 4. 初始化Embedder和向量存储
    embedLLM, _ := ollama.New(ollama.WithModel(config.EmbedModel), ollama.WithServerURL(config.OllamaURL))
    embedder, _ := embeddings.NewEmbedder(embedLLM)
    store, _ := qdrantl.New(
        qdrantl.WithURL(url.URL{Scheme: "http", Host: config.QdrantURL}),
        qdrantl.WithCollectionName(config.Collection),
        qdrantl.WithEmbedder(embedder),
    )

    _, err = store.AddDocuments(ctx, docs) // 5. 添加文档到qDrant
}

3. 查询与生成:UpdateRAG

确保集合存在,初始化Embedder和向量存储(与导入时相同)。向集合中添加几个示例文档块(演示用)。执行相似性搜索:将查询文本向量化,在qDrant中检索最相似的3个文档块。将检索结果拼接为上下文,构造提示词,调用生成模型(Ollama)得到最终回答。

注意:实际应用中,文档导入和查询生成通常是分开的步骤,此处合并仅为展示完整流程。

func UpdateRAG() {
    // 1. 确保集合存在,初始化Embedder和向量存储
    EnsureCollection(config.QdrantIP, config.Collection)
    embedLLM, _ := ollama.New(ollama.WithModel(config.EmbedModel), ollama.WithServerURL(config.OllamaURL))
    embedder, _ := embeddings.NewEmbedder(embedLLM)
    store, _ := qdrantl.New(
        qdrantl.WithURL(url.URL{Scheme: "http", Host: config.QdrantURL}),
        qdrantl.WithCollectionName(config.Collection),
        qdrantl.WithEmbedder(embedder),
    )

    // 2. 准备示例文档并分割(此处仅为演示,实际可从文件加载)
    docs := []schema.Document{ ... }
    splitter := textsplitter.NewRecursiveCharacter(textsplitter.WithChunkSize(200), textsplitter.WithChunkOverlap(20))
    var chunks []schema.Document
    for _, doc := range docs {
        texts, _ := splitter.SplitText(doc.PageContent)
        for _, t := range texts {
            chunks = append(chunks, schema.Document{PageContent: t, Metadata: doc.Metadata})
        }
    }
    store.AddDocuments(ctx, chunks)

    // 3. 执行相似性搜索
    query := "Qdrant 适合用在什么场景?"
    results, _ := store.SimilaritySearch(ctx, query, 3)

    // 4. 构建提示词并调用LLM生成回答
    contextText := ""
    for _, doc := range results {
        contextText += doc.PageContent + "\n"
    }
    llm, _ := ollama.New(ollama.WithModel(config.GenerateModel), ollama.WithServerURL(config.OllamaURL))
    prompt := fmt.Sprintf(`
你是一个基于知识库回答问题的助手。
只能根据【知识库】内容作答,如果无法得到答案,请回答“不知道”。
【知识库】
%s
【问题】
%s
`, contextText, query)
    answer, _ := llms.GenerateFromSinglePrompt(ctx, llm, prompt)
    fmt.Println(answer)
}

4. 运行示例

func main() {
	IngestKnowledge("./xxix.pdf", true) //指向需要索引的知识文件
        UpdateRAG()
}
输出:
文档已成功写入 Qdrant

Model回答:
Qdrant 是一个高性能的向量数据库,常用于 RAG(检索增强生成)系统,因此适合用于构建基于语义搜索、推荐系统、异常检测等需要向量相似性检索的场景。