分类: 解决方案

任何领域的问题解决办法,可以是程序语言的问题,也可以是系统故障的问题

  • 存取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(检索增强生成)系统,因此适合用于构建基于语义搜索、推荐系统、异常检测等需要向量相似性检索的场景。
  • 对象存储(Object Storage Service)

    OSS(对象存储服务)以存储空间(Bucket)作为数据的逻辑容器,用来统一管理对象。每个Bucket都具有唯一性,并绑定了固定的存储类别、访问权限策略以及所属地域,用户可以通过Bucket对应的访问域名在互联网上定位并访问其中的数据。

    在OSS中,对象(Object)是最基本的数据存储单元,可以理解为“文件 + 属性信息”的整体。一个对象由Key、Metadata和Data三部分组成。Key即对象的唯一名称,是经过UTF-8编码的字符串,在同一个Bucket内不能重复;Metadata是对象的描述信息,以键值对形式存在,用于说明对象的各种属性;Data则是对象实际存储的二进制数据内容。Metadata又分为系统元数据和用户元数据,其中系统元数据由OSS自动维护,用于对象管理和传输,例如内容长度、最后修改时间、ETag等;用户元数据则由用户在上传时自定义,用来补充描述对象的业务属性。

    OSS根据数据访问频率和成本需求提供了多种存储类别。标准存储具备高可靠性、高可用性和高性能,访问时延低、吞吐能力强,适合需要频繁访问的热点数据场景,如网站图片、音视频内容和移动应用资源。低频存储面向访问次数较少但仍需实时读取的数据,存储成本低于标准存储,适合长期备份类数据,但对象存储时间不足30天提前删除会产生费用。归档存储则以最低的存储成本支持长期保存的数据,适合日志、档案和影视素材等几乎不访问的数据,取回时需要等待一定时间,并且对最短存储周期有更严格的限制。

    在底层能力上,OSS通过多副本和纠删码等冗余机制,将数据分布在跨可用区甚至跨设备的存储节点上,即使多台设备同时发生故障,也能保证数据不丢失、业务不中断,并自动完成冗余修复。同时,系统会周期性校验数据完整性,发现异常后利用冗余数据进行重建,进一步保障数据可靠性。

    OSS还提供对象级别的强一致性保障。所有对象操作都是原子的,不存在中间状态:操作要么成功,要么失败。只要用户收到成功响应,数据就已经处于可用状态。例如对象上传完成后即可立即读取,更新对象时,其他并发读请求只会读到旧数据,而不会读到部分或损坏的数据。这种强一致性特性使得OSS在使用方式上更接近传统存储系统,简化了应用架构设计。

    在数据组织方式上,OSS采用Key-Value模型,而不是传统文件系统的目录树结构。对象的Key是访问数据的唯一标识,即使Key看起来像“FolderA/FolderB/file.jpg”,在OSS内部也只是一个普通字符串,并不代表真实存在的层级目录。因此,不同Key的访问效率基本一致,不会因为“目录层级”变深而降低性能。所谓的目录操作,本质上是对前缀匹配的一批对象进行处理,成本较高,也不被推荐频繁使用。

    由于OSS不支持对象的在线修改,即使只改动一个字节,也需要重新上传整个对象,因此它天然适合“一次写入,多次读取”的业务模型。依托分布式架构和Key-Value存储方式,OSS能够支持海量数据和高并发访问,非常适合静态资源分发、备份归档等场景。

    在访问和管理方式上,用户可以通过网页形式的管理控制台直观地创建Bucket、上传下载文件并配置权限;也可以使用支持S3接口的客户端工具(如S3Browser)进行本地化管理;对于开发场景,OSS提供了封装良好的SDK,方便应用程序直接调用对象存储能力;同时还提供基于HTTPS、数据格式为JSON的RESTful API,满足更底层或统计类的接口调用需求。

  • 移除内核崩溃转储(kdump)

    当Linux内核崩溃时,kdump 会利用预留的一段内存(称为 crash kernel)启动一个最小化的内核环境,从而将故障时的内存数据保存到硬盘。崩溃日志可用于后期故障排查。云主机的内存比较宝贵,如果是一台4G内存的主机,除去KVM的硬件预留,还有内核崩溃转储预留,可能实际available ram为3.6G左右。如果不需要捕获系统崩溃信息,可以再腾出一些空间。有些云服务器厂商的系统默认配置kdump,也就是说存在云主机运行内存不足额的情况,这边需要手动更新配置。

    查看系统当前的内存预留:

    sudo dmesg | grep -i memory

    修改 /etc/default/grub ,去除crashkernel相关字段:

    GRUB_CMDLINE_LINUX=" vga=792 console=tty0 console=ttyS0,115200n8 net.ifnames=0 noibrs nvme_core.io_timeout=4294967295 nvme_core.admin_timeout=4294967295 iommu=pt crashkernel=0M-1G:0M,1G-4G:192M,4G-128G:384M,128G-:512M crash_kexec_post_notifiers=1"

    改完grub后,需要更新配置:

    sudo grub-mkconfig -o /boot/grub/grub.cfg
    
    sudo grub-mkconfig -o /boot/efi/EFI/ubuntu/grub.cfg

    再次重启主机,内存会释放。

  • 自定义Dolphin右键新建按钮

    日常在Dolphin中会用到右键-新建Librioffice Draw,这些办公类快捷按钮会在软件安装的时候自动添加,做笔记常用到markdown.md文件,配一个入口按钮方便点开创建即用。

    1. 创建模板文件夹
    ~/.local/share/templates && cd ~/.local/share/templates
    1. 新建文件
    touch MarkDown.md
    1. 新建快捷信息,并填写
    vim create_markdown.desktop
    [Desktop Entry]
    Name=Markdown
    Comment=快速新建Markdown文件
    Type=Link
    URL=MarkDown.md
    Icon=application-vnd.oasis.opendocument.text