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框架可快速构建知识处理流水线,显著降低实现复杂度。

效果验证

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

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

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

相关推荐
hacker_LeeFei8 小时前
Goland使用手册(1)
go
白总Server8 小时前
Golang dig框架与GraphQL的完美结合
java·大数据·前端·javascript·后端·go·graphql
qqxhb11 小时前
零基础设计模式——行为型模式 - 中介者模式
java·设计模式·go·中介者模式
GetcharZp15 小时前
「Golang黑科技」RobotGo自动化神器,鼠标键盘控制、屏幕截图、全局监听全解析!
后端·go
程序员爱钓鱼17 小时前
Go同步原语与数据竞争:原子操作(atomic)
后端·面试·go
没逻辑1 天前
Go 内存逃逸与泄漏排查实战
go
卜锦元1 天前
Go中GMP调度模型是如何优雅地应对G阻塞?
go
qqxhb1 天前
零基础设计模式——行为型模式 - 观察者模式
java·观察者模式·设计模式·go
卜锦元1 天前
Go中GMP调度模型详解(通俗易懂)
go