|
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;display: table;padding: 0px 0.2em;color: rgb(255, 255, 255);background: rgb(15, 76, 129);">前言 大家好,这里是白泽。这篇文章将讲解如何使用 Redis 的向量检索与 LLM 构建一个 RAG 知识库,知识库存储内容是 Eino 框架的介绍。每次尝试从 Redis 向量索引中获取 top k 条相关信息,并使用 LLM 进行总结回复;当没有相关知识,则提示未查找到文档,限制大模型自由发挥。 ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">使用到的技术栈如下:ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">语言:go1.22ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">工作流框架:Eino(字节开源的大模型工作流开发框架)ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">向量存储与检索:RedisingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">大语言模型:doubao-pro-32k-241215ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">向量化模型:doubao-embedding-large-text-240915ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;font-style: normal;padding: 1em;border-radius: 6px;color: rgba(0, 0, 0, 0.5);background: rgb(247, 247, 247);">ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 1em;display: block;letter-spacing: 0.1em;color: rgb(63, 63, 63);">?项目已经开源,地址如下:https://github.com/BaiZe1998/go-learningingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 1em;display: block;letter-spacing: 0.1em;color: rgb(63, 63, 63);">这里说明一下,当前案例中,索引构建阶段的代码取自:https://github.com/cloudwego/eino-examples系统架构系统架构回答生成阶段查询检索阶段索引构建阶段Markdown文件文件加载器文档分割器嵌入模型文档向量Redis向量数据库用户问题嵌入模型查询向量KNN向量搜索TopK相关文档提示构建增强提示大语言模型生成回答检索器\nRetrieverRAG系统生成器\nGenerator参数配置\ntopK等 项目运行
cd eino_assistant docker-compose up -d #通过这种方式启动的 redis 内置了一部分已经完成向量化的 Eino 文档数据
#在知识库构建阶段,需要使用到文档向量化的模型 #在检索增强阶段,需要使用语言大模型进行总结回复 cd eino_assistant source .env
#使用 redis 作为文档数据库,同时每次检索3条 go run eino/rag/cmd/main.go --redis=true --topk=3
问题> Agent 是什么
===== 检索到 3 个相关文档 =====
文档[1] 相似度: 0.7705 标题: 无标题 ---------------------------------------- ## **Agent 是什么** Agent(智能代理)是一个能够感知环境并采取行动以实现特定目标的系统。在 AI 应用中,Agent 通过结合大语言模型的理解能力和预定义工具的执行能力,可以自主地完成复杂的任务。是未来 AI 应用到生活生产中...
文档[2] 相似度: 0.7606 标题: 无标题 ---------------------------------------- ## **总结** 介绍了使用 Eino 框架构建 Agent 的基本方法。通过 Chain、Tool Calling 和 ReAct 等不同方式,我们可以根据实际需求灵活地构建 AI Agent。 Agent 是 AI 技术发展的重要方向。它不仅能够理解用户意图,还能主动采取行动,通过�...
文档[3] 相似度: 0.7603 标题: 无标题 ---------------------------------------- ## **Agent 是什么** Agent(智能代理)是一个能够感知环境并采取行动以实现特定目标的系统。在 AI 应用中,Agent 通过结合大语言模型的理解能力和预定义工具的执行能力,可以自主地完成复杂的任务。是未来 AI 应用到生活生产中...
==============================
回答: Agent(智能代理)是一个能够感知环境并采取行动以实现特定目标的系统。在 AI 应用中,Agent 通过结合大语言模型的理解能力和预定义工具的执行能力,可以自主地完成复杂的任务。是未来 AI 应用到生活生产中主要的形态。
本文中示例的代码片段详见:[eino-examples/quickstart/taskagent](https://github.com/cloudwego/eino-examples/blob/master/quickstart/taskagent/main.go)
问题> 什么是大数据
===== 检索到 3 个相关文档 =====
文档[1] 相似度: 0.7647 标题: 无标题 ---------------------------------------- --- Description: "" date: "2025-01-07" lastmod: "" tags: [] title: Tool weight: 0 ---
文档[2] 相似度: 0.7488 标题: 无标题 ---------------------------------------- --- Description: "" date: "2025-01-06" lastmod: "" tags: [] title: Document weight: 0 ---
文档[3] 相似度: 0.7419 标题: 无标题 ---------------------------------------- --- Description: "" date: "2025-01-06" lastmod: "" tags: [] title: Embedding weight: 0 ---
==============================
回答: 很抱歉,我不知道什么是大数据,文档中没有提供相关信息。
#在 cmd/knowledgeindexing 目录下新建一个 big_data.md 文档,内容如下: #大数据 大数据(Big Data)是指规模庞大、结构复杂且无法通过传统数据处理工具在合理时间内进行有效捕捉、管理和处理的数据集合。其核心价值在于通过专业化分析挖掘数据中蕴含的信息,从而提升决策力、优化流程并创造新价值。
- 7. 重新生成文档向量,将大数据信息添加到 Redis 索引中
yucong@yucongdeMacBook-Air eino_assistant % cd cmd/knowledgeindexing yucong@yucongdeMacBook-Air knowledgeindexing % go run ./ [start] indexing file: eino-docs/_index.md [done] indexing file: eino-docs/_index.md, len of parts: 4 [start] indexing file: eino-docs/agent_llm_with_tools.md [done] indexing file: eino-docs/agent_llm_with_tools.md, len of parts: 1 [start] indexing file: eino-docs/big_data.md [done] indexing file: eino-docs/big_data.md, len of parts: 1 # 可以看到被切分了 index success
问题> 什么是大数据
===== 检索到 3 个相关文档 =====
文档[1] 相似度: 0.8913 标题: 大数据 ---------------------------------------- #大数据 大数据(Big Data)是指规模庞大、结构复杂且无法通过传统数据处理工具在合理时间内进行有效捕捉、管理和处理的数据集合。其核心价值在于通过专业化分析挖掘数据中蕴含的信息,从而提升决策力、优化流程并创造�...
文档[2] 相似度: 0.7647 标题: 无标题 ---------------------------------------- --- Description: "" date: "2025-01-07" lastmod: "" tags: [] title: Tool weight: 0 ---
文档[3] 相似度: 0.7488 标题: 无标题 ---------------------------------------- --- Description: "" date: "2025-01-06" lastmod: "" tags: [] title: Document weight: 0 ---
==============================
回答: 大数据(Big Data)是指规模庞大、结构复杂且无法通过传统数据处理工具在合理时间内进行有效捕捉、管理和处理的数据集合。其核心价值在于通过专业化分析挖掘数据中蕴含的信息,从而提升决策力、优化流程并创造新价值。
核心业务流程索引构建阶段这一部分参见:eino_assistant/eino/knowledgeindexing 目录代码
流程图:  索引的构建阶段,本质也是一个工作流,因此可以通过 Goland 的 Eino Dev 插件进行可视化绘制,完成之后点击生成流程框架代码,然后填充一些业务实现即可:  - • 文档分割:按标题、段落等逻辑单位将文档分割成小段(根据 # 拆分)
- • 向量生成:使用嵌入模型,将文本转换为高维向量(4096)
- • Redis存储:将文档内容、元数据和向量存储到Redis哈希结构中
检索阶段参见:eino_assistant/eino/rag/retriver.go
- • 查询向量化:使用同样的嵌入模型将问题转换为向量
- • KNN搜索:在Redis中执行KNN(K近邻)向量搜索
- • 相关文档获取:获取与问题语义最相关的TopK个文档
// Retrieve 检索与查询最相关的文档 func(r *RedisRetriever)Retrieve(ctx context.Context, querystring, topKint) ([]*schema.Document,error) { // 生成查询向量 queryVectors, err := r.embedder.EmbedStrings(ctx, []string{query}) iferr !=nil{ returnnil, fmt.Errorf("生成查询向量失败: %w", err) }
iflen(queryVectors) ==0||len(queryVectors[0]) ==0{ returnnil, fmt.Errorf("嵌入模型返回空向量") }
queryVector := queryVectors[0]
// 构建向量搜索查询 searchQuery := fmt.Sprintf("(*)=>[KNN %d @%s $query_vector AS %s]", topK, redispkg.VectorField, redispkg.DistanceField)
// 执行向量搜索 res, err := r.client.Do(ctx, "FT.SEARCH", r.indexName,// 执行搜索的索引名称 searchQuery, // 向量搜索查询语句 "PARAMS","2",// 参数声明,后面有2个参数 "query_vector", vectorToBytes(queryVector),// 查询向量的二进制表示 "DIALECT","2",// 查询方言版本 "SORTBY", redispkg.DistanceField,// 结果排序字段 "RETURN","3", redispkg.ContentField, redispkg.MetadataField, redispkg.DistanceField,// 返回字段 ).Result()
iferr !=nil{ returnnil, fmt.Errorf("执行向量搜索失败: %w", err) }
// 将Redis结果转换为Document对象 returnr.parseSearchResults(res) }
回答生成阶段参见:eino_assistant/eino/rag/generator.go
- • 提示构建:将检索到的文档和用户问题组合成增强提示
- • LLM调用:将增强提示发送给大语言模型(ARK doubao)
- • 回答生成:模型根据提供的上下文生成针对用户问题的回答
// Generate 生成回答 func(g *ArkGenerator)Generate(ctx context.Context, querystring, documents []*schema.Document) (string,error) { // 组合上下文信息 context :="" iflen(documents) >0{ contextParts :=make([]string,len(documents)) fori, doc :=rangedocuments { // 如果元数据中有标题,添加标题信息 titleInfo :="" iftitle, ok := doc.MetaData["title"].(string); ok && title !=""{ titleInfo = fmt.Sprintf("标题: %s\n", title) } contextParts[i] = fmt.Sprintf("文档片段[%d]:\n%s%s\n", i+1, titleInfo, doc.Content) } context = strings.Join(contextParts,"\n---\n") }
// 构建提示 systemPrompt :="你是一个知识助手。基于提供的文档回答用户问题。如果文档中没有相关信息,请诚实地表明你不知道,不要编造答案。" userPrompt := query
ifcontext !=""{ userPrompt = fmt.Sprintf("基于以下信息回答我的问题:\n\n%s\n\n问题:%s", context, query) }
// 构建请求 messages := []chatMessage{ {Role:"system", Content: systemPrompt}, {Role:"user", Content: userPrompt}, }
reqBody := chatRequest{ Model: g.modelName, Messages: messages, }
// 序列化请求体 jsonData, err := json.Marshal(reqBody) iferr !=nil{ return"", fmt.Errorf("序列化请求失败: %w", err) }
// 创建HTTP请求 endpoint := fmt.Sprintf("%s/chat/completions", g.baseURL) req, err := http.NewRequestWithContext(ctx,"POST", endpoint, bytes.NewBuffer(jsonData)) iferr !=nil{ return"", fmt.Errorf("创建HTTP请求失败: %w", err) }
// 添加头信息 req.Header.Set("Content-Type","application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", g.apiKey))
// 发送请求 client := &http.Client{} resp, err := client.Do(req) iferr !=nil{ return"", fmt.Errorf("发送请求失败: %w", err) } deferresp.Body.Close()
// 读取响应 body, err := io.ReadAll(resp.Body) iferr !=nil{ return"", fmt.Errorf("读取响应失败: %w", err) }
// 检查响应状态 ifresp.StatusCode != http.StatusOK { return"", fmt.Errorf("API返回错误: %s, 状态码: %d",string(body), resp.StatusCode) }
// 解析响应 varchatResp chatResponse iferr := json.Unmarshal(body, &chatResp); err !=nil{ return"", fmt.Errorf("解析响应失败: %w", err) }
// 提取回答 iflen(chatResp.Choices) >0{ returnchatResp.Choices[0].Message.Content,nil }
return"", fmt.Errorf("API没有返回有效回答") }
主循环funcmain(){ // 定义命令行参数 useRedis := flag.Bool("redis",true,"是否使用Redis进行检索增强") topK := flag.Int("topk",3,"检索的文档数量")
flag.Parse()
// 检查环境变量 env.MustHasEnvs("ARK_API_KEY")
// 构建RAG系统 ctx := context.Background() ragSystem, err := rag.BuildRAG(ctx, *useRedis, *topK) iferr !=nil{ fmt.Fprintf(os.Stderr,"构建RAG系统失败: %v\n", err) os.Exit(1) }
// 显示启动信息 if*useRedis { fmt.Println("启动RAG系统 (使用Redis检索)") }else{ fmt.Println("启动RAG系统 (不使用检索)") } fmt.Println("输入问题或输入'exit'退出")
// 创建输入扫描器 scanner := bufio.NewScanner(os.Stdin)
// 主循环 for{ fmt.Print("\n问题> ")
// 读取用户输入 if!scanner.Scan() { break }
input := strings.TrimSpace(scanner.Text()) ifinput ==""{ continue }
// 检查退出命令 ifstrings.ToLower(input) =="exit"{ break }
// 处理问题 answer, err := ragSystem.Answer(ctx, input) iferr !=nil{ fmt.Fprintf(os.Stderr,"处理问题时出错: %v\n", err) continue }
// 显示回答 fmt.Println("\n回答:") fmt.Println(answer) }
iferr := scanner.Err(); err !=nil{ fmt.Fprintf(os.Stderr,"读取输入时出错: %v\n", err) }
fmt.Println("再见!") }
|