AI学习笔记 — RAG 与 中医知识的碰撞

RAG [Retrieval-Augmented Generation]

原文

《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》

proceedings.neurips.cc/paper/2020/...

预训练的自然语言模型能够从数据学习到大量深度知识,使其仅需访问参数化的隐性数据库而无需访问外部数据;但无法简单的扩展和修改记忆 ,同时可能有幻觉现象 的产生;将参数记忆和非参数记忆相结合构建混合模型,利用其直接修改和拓展记忆;REALM 和 ORQA 将语言模型和可微分的检索器(Retriever) 相结合,取得不错的结果;

将预训练的检索器(Retriever) 与预训练的seq2seq模型结合,并进行微调;检索器(Retriever) 对于输入的Query使用最大内积查找(MIPS)找到前K个文档切片(z);对于最终预测结果y,将文档切片(z)作为潜在变量,根据不同文档切片对seq2seq预测边缘化;

这种通⽤微调⽅法将参数记忆(parametric-memory)⽣成模型⾮参数记忆(non-parametric memory),我们称之为检索增强⽣成(RAG);

总结

  1. 核心思想 是将自然语言模型的记忆分为参数化记忆非参数化记忆,解决记忆动态更新和幻像问题;

    1. 参数化记忆: 存储了通过预训练学习到的语言模式和通用知识。
    2. 非参数化记忆: 使用一个可检索的动态知识库。
  2. 检索器(Retriever) 承载定制化索引知识库(Embedding) 和输入内容的检索能力,对知识库进行拆分和向量化存储称之为索引数据库;用户输入的提示词(User Prompt),检索器会对内容也进行一次向量化处理,再到向量化数据库检索出k片相关性高的切片;

  3. 生成器(Generator) 承载大语言模型,对每一片切片、用户提示词和系统提示词(System Prompt)作为上下文,返回结果;

RAG 实践

实践结果

倪海夏针灸专题为数据源,设计包含下针方法、对应视频节点的元数据结构作为索引知识库,实现症状到关键知识的精准检索

cmd 复制代码
问题1: 病人中风怎么办
回答: 如果病人中风,出现舌强不语、手握拳的症状,可找到廉泉穴进行治疗。
找穴方法为:用大拇指指尖顶到下巴处,指尖到处即为廉泉穴。下针时,针要对
着舌根,而不是直针下,下针深度一寸到一寸半即可。该穴位大部分用针治疗,一般不用灸。 

问题2: 对应视频时间节点是多少
回答: 文档中提到病人中风相关内容的视频时间是 2-00:43:13 。(第二集的43分13秒)

系统架构

  • 模型:

    • 向量模型:Doubao-embedding-large
    • 大语言模型:Doubao-pro-32k
  • 数据库: Redis

  • 框架: eino(File Loader、Transform、Embedder、Generator、Retriver基础库提供)

数据准备

视频字幕提取

火山引擎申请音视频字幕生成能力,对本地视频进行批量字幕提取;

www.volcengine.com/docs/6561/8...

markdown文件生成

使用AI归纳总结内容并生成markdown文件,效果如下:

markdown 复制代码
# 任脉与督脉
##time (1-02:18:45)

我们开始介绍任脉。

**任**,女子妊也,女人会怀孕是靠任脉。十二经络开始介绍之前,要从任脉开始介绍。找穴道要从任、督二脉为基准,找到标准,就可以很快速的找到穴道。我最怕就是鸡同鸭讲,心里知道答案,结果穴道找错了。

- **任脉**:是所有阴汇积的地方
- **督脉**:是诸阳之会!全身的动能,能量,都在督脉上面

> 我最常跟病人讲一句话就是,无论如何不要让别人碰你的脊椎骨,督脉不能碰,脊椎骨像龙骨一样,有人椎间盘凸出,有人去开刀,开完反而更坏。MAS 根本就是疫苗引起的。造成一开始脊椎就弯的,你想想,疫苗可以把所有阳气所在的督脉都打烂,这种人就不长寿。比如说我们叫天柱倾,脖子都歪过去,这样的人命在旦夕,一两天就走了。

女人怀孕全靠任脉。督脉走在后面,诸阳之会,脊椎骨上面,任督二脉交会在鼻子人中这边。刚好嘴巴讲话,讲话时任督二脉是开的,你在听课的时候,舌头是顶着上颚。这是你的牙龈,这牙齿,侧面看哦,然后这是下牙,这是嘴唇。简单的概念,舌头顶到上颚的时候,任督二脉是通的。注意看乌龟,乌龟就是这样。所以,乌龟很长寿。我在讲课的时候,嘴巴会动,而你们在听课时,是把舌头顶上去的,脑筋会很清醒,阴电阳电相通。

## 任脉穴位

任脉有三八二十四个穴道,任脉三八起会阴。

......

KnowledgeIndexing

使用Eino-dev插件构建向量知识库Workflow,提供常用的 AI 组件 以及集成组件编排能力; 编排后改自己申请的模型配置即可;

www.cloudwego.io/zh/docs/ein...

编排后可以看到生成了代码文件,如图上串联;入口文件是orchestration.go;输入文件路径返回ids;

Transformer

主要对输入知识库进行分割,构建向量数据的元数据;主要对标题和时间进行存储和索引;

go 复制代码
func newDocumentTransformer(ctx context.Context) (tfr document.Transformer, err error) {
    config := &markdown.HeaderConfig{
        Headers: map[string]string{
            "#":      "title",
            "##time": "time",
        },
        TrimHeaders: false}
    tfr, err = markdown.NewHeaderSplitter(ctx, config)
    if err != nil {
        return nil, err
    }
    return tfr, nil
}

元数据(metadata)存储title和time,content为段落内容;

Main

main函数批量读本地知识库文件数据,并调用orchestration.go内的函数,传入需要处理的文件路径即可;

go 复制代码
func main() {
    ctx := context.Background()
    err := indexMarkdownFiles(ctx, "./docs")
    if err != nil {
        panic(err)
    }
}

func indexMarkdownFiles(ctx context.Context, dir string) error {
    runner, err := knowledgeindexing.BuildKnowledgeIndexing(ctx)
    if err != nil {
        return fmt.Errorf("build index graph failed: %w", err)
    }

    err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            return fmt.Errorf("walk dir failed: %w", err)
        }
        if d.IsDir() {
            return nil
        }

        ids, err := runner.Invoke(ctx, document.Source{URI: path})
        if err != nil {
            return fmt.Errorf("invoke index graph failed: %w", err)
        }
        return nil
    })

    return err
}

执行main函数,我们可以看到Redis数据库已经写入向量数据,如图:

RAG System

  • lambda: 将开发者任意的函数转换成可被编排的节点,在 RAG 中,有两个转换场景

    • 将 User Prompt 消息转换成 ChatTemplate 节点的 map[string]any
    • 将 User Prompt 转换成 RedisRetriever 的输入 query
  • retriever/redis: 根据用户 Query 从 Redis Vector Database 根据语义相关性,召回和 Query 相关的上下文,以 schema.Document List 的形式返回。

  • chatTemplate: 通过字符串字面量构建 Prompt 模板,支持 文本替换符 和 消息替换符,将输入的任意map[string]any,转换成可直接输入给模型的 Message List。

  • reAct: 自动决策下一步的 Action,直至能够产生最终的回答。

  • model/ark: Ark 平台提供的能够进行对话文本补全的大模型。作为 ReAct Agent 的依赖注入。

  • DuckDuckGo: 互联网搜索工具。

Main

简单实现一个交互式问答系统,启动RAG系统并将用户输入内容传入;

go 复制代码
func main() {
    // 定义命令行参数
    useRedis := flag.Bool("redis", true, "是否使用Redis进行检索增强")
    topK := flag.Int("topk", 3, "检索的文档数量")

    flag.Parse()
    // 构建RAG系统
    ctx := context.Background()
    ragSystem, err := rag.BuildRAG(ctx, *useRedis, *topK)
    if err != 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())
        if input == "" {
            continue
        }
        // 检查退出命令
        if strings.ToLower(input) == "exit" {
            break
        }
        // 处理问题
        answer, err := ragSystem.Answer(ctx, input)
        if err != nil {
            fmt.Fprintf(os.Stderr, "处理问题时出错: %v\n", err)
            continue
        }
        // 显示回答
        fmt.Println("\n回答:")
        fmt.Println(answer)
    }

    if err := scanner.Err(); err != nil {
        fmt.Fprintf(os.Stderr, "读取输入时出错: %v\n", err)
    }
}

实践总结

开发效率

基于Eino框架可快速构建知识处理流水线,显著降低实现复杂度。

效果验证

• 成功实现知识精准定位(内容+时间节点)

• 有效解决中医知识结构化检索需求

• 待优化:上下文对话能力(后续版本迭代)

相关推荐
梁梁梁梁较瘦4 小时前
边界检查消除(BCE,Bound Check Elimination)
go
梁梁梁梁较瘦4 小时前
指针
go
梁梁梁梁较瘦4 小时前
内存申请
go
半枫荷5 小时前
七、Go语法基础(数组和切片)
go
梁梁梁梁较瘦1 天前
Go工具链
go
半枫荷1 天前
六、Go语法基础(条件控制和循环控制)
go
半枫荷2 天前
五、Go语法基础(输入和输出)
go
小王在努力看博客2 天前
CMS配合闲时同步队列,这……
go
Anthony_49263 天前
逻辑清晰地梳理Golang Context
后端·go
Dobby_054 天前
【Go】C++ 转 Go 第(二)天:变量、常量、函数与init函数
vscode·golang·go