基于大模型的播客RAG系统搭建攻略(三):开源代码和后续计划

前文链接:

仓库地址https://github.com/liujinmarshall/podcast_rag

首先插播一条新闻。在两周之前,我常用的播客客户端小宇宙推出了会员plus计划:

我们的会员产品“小宇宙PLUS”现已上线,提供「AI播客总结」「收藏时点」「会员周报」「个性图标」「多彩主题色」等多个功能,一起来看看!

小宇宙PLUS会员,现已包含以下权益 ——

· AI播客总结
服务于播客重度用户的一些高信息密度节目场景。例如:你可以通过AI播客总结判断一期没有shownotes的节目是否值得听,也可以高效得到一期财经、商业、科技等领域节目的内容速报。更多使用场景,期待你的探索。

· 收藏时点
你可以通过「收藏时点」功能,随时标记最感兴趣的时点,记录此刻的私人笔记,并在【我的收藏】中查看汇总。即使正处于锁屏状态下也无需担心,iOS系统中,无需解锁也可以通过左下角的收藏按钮进行收藏时点的操作,一键收藏此刻的吉光片羽。

· 会员周报
每周一中午12:00左右,我们将为会员用户送上专属会员周报,并为你自动汇总并总结你可能错过的精彩节目,为你持续挖掘更多的兴趣内容。欢迎在【个人-会员入口】处进入查看。

· 个性图标与多彩主题色
看腻了你的小宇宙?成为小宇宙PLUS会员,即刻更换你的小宇宙图标和主题色,深空、毛绒、键帽、粉白、薄荷、深蓝……超多可选,让你的小宇宙与众不同。

当中第一项AI播客总结正好完美契合这个项目的功能,虽然我强烈怀疑这个会员是否真的值一年120元。因此我也在程序中加上了单集总结的功能。实现非常简单,通过发送提示词+播客转录稿实现。

计划中的功能列表:

  • 支持英文播客:加入英文提示词,并根据播客语言选择对应的提示词。
  • 切换embedding:现在使用的OpenAI text embedding的开销,对于个人用户来说不算很大,但也不小。需要切换到更便宜的版本或者本地推理的text embedding。目前计划使用MTEB榜单中排名较为靠前但显存需求不大的gte-Qwen2-1.5B-instruct
  • 一键运行所有任务:当前程序被分为不同的子程序,需要统一一个运行入口。
  • 新增播客总结:对于每次增量更新的内容进行小结。

以下是中文版的使用说明:

仓库地址https://github.com/liujinmarshall/podcast_rag

使用 RAG (检索增强生成) 技术来理解播客并提问

已测试 Python 版本:3.9 (应该也适用于更新的版本和不太旧的版本)

前提条件

  • Python 环境以及并了解如何运行 Python 程序
  • Google Gemini API 密钥和 OpenAI API 密钥

步骤

  1. 克隆此 Github 仓库;
  2. 创建一个名为 “data” 的目录;
  3. 将 podcasts.example.csv 复制到 data/podcasts.csv,并添加 RSS URL 条目;
  4. 通过 requirements.txt 安装 Python 依赖 (pip install -r requirments.txt);
  5. 将 Google Gemini API 密钥导出为 GEMINI_API_KEY,并将 OpenAI API 密钥导出为 OPENAI_API_KEY;
  6. 运行以下脚本以实现以下功能(使用 python xxx.py):
    1. download.py: 将播客音频媒体文件以增量模式下载到本地。每个播客一个目录。
    2. transcribe.py: 将音频媒体文件转录成文本。每个播客一个目录。
    3. index.py: 将转录文本分块并索引到向量数据库 (ChromaDB) 中。
    4. query.py: 启动一个聊天机器人来查询问题:
      • 运行程序;
        • 在浏览器中打开链接并开始提问 。
    5. summarize.py: 将转录文本总结成简洁的摘要。每个播客一个目录。
    6. delete_files.py: 删除音频媒体文件(超过 24 小时的文件),以防文件上传超出配额。上传超过 48 小时的文件将被自动清除

语言支持

  • 中文
  • 英语 (待添加)

注意

请遵守播客提供商的用户协议。此仓库仅供个人研究使用。

基于大模型的播客RAG系统搭建攻略(二):进阶之路与避坑指南

第一部分:https://onlymarshall.com/2025/01/05/build-personal-podcast-rag/

第三部分:https://onlymarshall.com/2025/01/26/build-personal-podcast-rag-part-3/

(注:本文在Gemini模型协助下完成。不得不感慨大模型很多时候思考的比人类周全,人类只要提供一些基本元素,大模型就可以把内容打磨地得非常完善)

上一篇我们介绍了播客搜索问答系统的基本架构和初步选型,特别是在语音转录方面的一些尝试和思考。今天我们继续深入,聊聊在系统搭建过程中遇到的更具体的技术挑战以及一些解决方案。

索引/检索的优化与挑战

上一篇我们提到索引和检索环节主要依赖文本向量模型和向量数据库。看似简单,但真正落地会遇到不少问题,尤其是在播客这种持续更新的内容源上。

增量更新:让知识库保持新鲜

播客的一大特点是持续更新。每周甚至每天都会有新的内容产生。如果每次更新都重新处理所有播客数据,那将是巨大的资源浪费。因此,增量更新是必须要考虑的。我们的目标是只处理新增的播客和更新的播客。这需要我们在“播客下载/爬虫”阶段做一些改进:

  • 记录已处理的播客和音频文件: 我们需要一个机制来记录哪些播客和对应的音频文件已经被处理过了。一个简单的做法是维护一个数据库(或者是简单的CSV文件),记录播客的feed URL以及每个episode的发布时间或唯一的GUID。
  • 对比更新时间或文件大小: 在爬取时,对于已经记录的播客,我们可以通过HEAD请求获取最新的episode信息,对比发布时间或GUID。对于新的episode,或者发布时间有更新的episode,再进行下载和后续处理。
  • 文件大小与HEAD请求的妙用: 除了对比发布时间,我们还可以利用HTTP HEAD请求来获取音频文件的大小。如果新爬取的音频文件大小与之前记录的不同,则说明文件可能被修改过,需要重新下载和处理。这对于一些播客会修改音频描述或者替换音频文件的情况很有用。HEAD请求相比于直接下载整个文件要高效得多,大大节省了网络资源。
  • 处理删除的播客: 有些播客可能会下架某集播客。我们需要定期检查已处理的播客列表,对比最新的feed内容,将已删除的播客集从向量数据库中移除,保持数据的一致性。
  • 保留时间:播客毕竟是音频文件,几百期的内容加起来也有几十个GB,如果总是保留所有的冷文件,特别是对于个人来说,存储的成本也没有必要。

有了以上这些功能,我们每天只要定时跑任务就可以自动完成对比、下载、转录、处理以及更新数据库的工作。

大模型转录的“后遗症”:重复文字问题

上一篇我们提到利用大模型的长上下文能力进行音频转录非常方便。但实际使用中,我们发现一个潜在的问题:重复文字和错误退出。

问题示例

尤其是在音频质量不高、语速较快或者多人对话的场景下,大模型有时会重复生成一些片段的文字。这可能是由于模型对音频的理解不够准确,或者在拼接长文本时出现偏差。

如何解决重复文字问题?

  • 检测错误并重新转录:这种情况有点像代码中因为并发造成的bug,而大模型运行本身也有一定的随机性,所以很大概率重新转录就可以解决这个问题。
  • 更精细的音频切分: 虽然我们避免了为了输入长度而进行的粗暴切分,但适当的、基于语义的切分仍然有必要。例如,可以根据静音段落或者明显的语意停顿进行切分,这样可以减少模型处理长音频的压力,降低重复的概率。
  • 后处理去重: 在得到转录文本后,可以进行一些后处理操作。例如,可以设定一个滑动窗口,如果窗口内的文本重复出现,则进行删除。当然,这需要谨慎操作,避免误删有效信息。
  • Prompt优化: 在给大模型下达转录指令时,可以加入一些提示,例如“请勿重复生成已出现过的文字”,虽然效果可能有限,但有时也能起到一定的作用。
  • 尝试不同的模型参数或模型: 不同的模型或者相同的模型在不同的参数设置下,表现可能会有差异。可以尝试调整解码策略或者使用更擅长处理此类问题的模型。

Text Embedding的中文选择:Gemini的踩坑

上一篇我们提到在文本向量模型上“跌了个跟头”。这里就来展开说说。

最初,我们可能自然而然地会选择一些通用的、在英文领域表现优秀的Embedding模型。既然Gemini也提供了文本Embedding,我们自然也选择这个模型。

但是在生成Embedding、存入数据库,再通过查询的Embedding进行检索之后,我发现了一个严重的问题:Gemini的文本模型似乎完全没办法理解中文

以下面的例子看,我们通过Gemini text-embedding-004模型,完全不同问题产生的embedding前10维完全一样。如果加上了一些英文字母的扰动,生成的结果就完全不一样。推测原因应该是tokenization的阶段把所有的非英文字符全部映射成了<UNK>,所以结果没有区别。

解决的方案也很简单:换成OpenAI的Embedding模型(text-embedding-3-small),因为使用了ChromaDB的代码,切换embedding function只要改动几行代码就可以实现。

后注:最终在某个文档页面发现了这个模型并不是多语言的版本,但似乎在GenAI embedContent的API里只能调用embedding-001和text-embedding-004,如果要调用多语言模型需要换到其他API。

Chunk大小的权衡

将长篇的转录文本切分成更小的chunk(块)是进行向量化的必要步骤。 Chunk的大小直接影响着检索的准确性和效率。

  • Chunk太小: 可能无法包含完整的语义信息,导致检索时丢失上下文,返回的结果不够相关。例如,一句关于“苹果公司新发布的手机”的信息被切分成“苹果公司”和“新发布的手机”两个chunk,单独检索“苹果公司”时,可能返回大量无关的结果。
  • Chunk太大: 虽然包含了更多的上下文,但会增加向量的计算成本和存储成本。同时,如果一个chunk内包含多个主题,可能会导致检索时返回与用户问题不太相关的部分。
  • Embedding模型限制:大部分embedding模型限制输入在几个K token左右,比如Gemini GenAI是输入上限2K,输出768维,OpenAI输入是8k,输出1536维

如何选择合适的Chunk大小?

这是一个需要根据实际播客内容和模型能力进行实验和调整的过程。一些可以参考的策略:

  • 基于句子或段落切分: 这是比较常用的方法,可以保证chunk的语义完整性。
  • 固定大小切分并加上重叠: 将文本按照固定token数量切分,并在相邻的chunk之间添加一定的重叠部分,以保留上下文信息。例如,一个chunk包含100个token,重叠20个token,那么下一个chunk从第81个token开始。
  • 利用语义切分工具: 一些工具可以根据语义相似性来切分文本,使得chunk内部的语义更加连贯。

在我们的实践中,我们尝试了不同的chunk大小,并结合向量检索的效果进行评估。 出于简单的考虑我们选择了固定大小切分并加上重叠。参数是2000个token一个chunk,重叠是200个token,通过tiktoken的包进行本地的计算(因为不知道embedding模型的tokenzier配置,所以这里只是近似)

小结

这篇博文我们深入探讨了播客RAG系统搭建过程中的一些关键技术点和挑战,包括增量更新、大模型转录的重复问题、中文Embedding模型的选择以及chunk大小的权衡。虽然已经有了大模型的加持可以很快编写核心骨架代码(大概降低了一半的时间),但是需求的不断迭代和看到结果后目标不停的调整,还是有相当大量的人为介入。编程门槛的降低,可以让非专业人士和专业人士摆脱一些低级的信息查找,把时间投入更有效率的地方。

后续我会整理一下代码之后发布开源(现在大部分都在notebook里)。

基于大模型的播客RAG系统搭建攻略(一):缘起,架构和选型

声明:本文仅介绍如何通过技术手段搭建系统,请遵守各个播客的用户协议。

第二部分:https://onlymarshall.com/2025/01/11/build-personal-podcast-rag-part-2/

第三部分:https://onlymarshall.com/2025/01/26/build-personal-podcast-rag-part-3/

缘起

开始听播客是在2016年初,那个时候刚拥有了人生第一辆车,也搬到了郊区自己的房子里,有比较固定的时间和空间。不知道是什么原因知道了在喜马拉雅上遇到了机核网的播客,那会儿已经开播了好几年,如获至宝,在两个月内把Gadio Pro栏目(特别是麦教授和四十二参与的)从第一期开始听完。之后随着机核的发展,转战机核App,直到2020年播客爆发,App也换成了小宇宙(意外发现就在五角场创智天地的前公司马路对面),形成了固定的收听习惯,订阅几十个中外栏目。

订阅的这些播客栏目,有些还是有相当的信息量。因为产品的音频形态,信息检索比较麻烦,有些播客即便刚刚听过,想回过头来找忘记或者漏掉的信息,也和很难定位到具体的时间点位重听。或者新接触一个播客,想知道以前聊过的内容里有没有自己感兴趣的话题可以快速加入收听列表。

架构

信息检索系统与搜索引擎

信息检索本质上可以分解成三个模块:

  • 下载/爬虫
  • 处理/索引
  • 检索/响应服务

传统的搜索引擎,这三部分对应了:

  • 爬虫系统:按照超链接把互联网的网页(文档)下载到本地
  • 索引系统:清洗数据,提取关键词,建立倒排索引(关键词->文档),索引更新
  • 检索系统:查询分析、排序结果文档

之所以要构建这三个系统,特别是爬虫和索引系统,主要原因在于整个互联网数据量太大,因此需要提前把文本内容下载并存储成检索友好的格式,这样用户通过关键词搜索就可以在几百毫秒之内得到结果。

Source: ByteByteGo https://blog.bytebytego.com/p/ep104-how-do-search-engines-work

RAG

随着大模型的出现,语义搜索也有了新的思路。这两年比较流行的方案是RAG(Retrieval Augementation Generation,检索增强生成,一个很拗口的名字)。我们也将采用这个方案。

RAG主要影响索引系统和检索系统,尤其是后者。传统的信息检索系统通过关键词匹配的方式搜索,直接把结果文档推给用户(再加上匹配到的关键词以及上下文高亮),但用户还需要自行处理加工。用户本质上是带着问题来的,比如“鸡蛋会增加胆固醇吗?”,“如何申请延迟提交报税表格”,关键词查询往往离最后的答案还有一定距离。

RAG利用大模型出色的语义处理能力,通过向量搜索(因为查询不再是关键词而是一个问题,传统的关键词查找加上倒排索引无法完成任务)查找到相关的文档,然后交给大模型去回答问题。RAG可以胜任语义搜索/问答的任务,但相对地使系统开销大大增加(10x),响应速度明显变慢(10x)。

来源:https://www.gptsecurity.info/2024/05/26/RAG/

播客搜索/问答架构

回到一开始的问题,我们搭建的系统也是由这几个模块组成:

  • 播客下载/爬虫:通过公开的RSS爬取播客音频和介绍信息;
  • 播客转录:把音频和介绍信息发送给语音到文字的引擎,变成文字稿,同时将文字稿分割处理向量化,放进向量数据库;
  • 播客搜索:搭建RAG系统,把问题转成向量进行向量搜索得到相关的文字稿,再把文字稿作为上下文和问题一起发送给大模型,得到最终回答并返回给用户。

选型

开发工具

最重要的开发工具是用来生成代码的大模型,这个年代做这种原型开发似乎已经没人从零开始写代码,尤其是还需要很多查找文档或者调试的工作。这次开发没有用很火的Cursor(舍不得花20刀月费)或者Github Copilot(公司有,但自己的设备也要花钱),而是体验阶段的Gemini 2.0 Flash Thinking Experimental搭配Google AI Studio。另外只是自己使用,暂时没有上线的需求,所以只在Jupyter Notebook里运行。

Google AI Studio写需求生成代码

语音转录

一开始想直接用OpenAI的whisper,价格如果嫌贵(一分钟$0.006刀,对应1集1个小时$0.36,100集要36刀也不是很便宜),也可以考虑本地host(但没有具体benchmark手头的4080可以跑多快)。主要的限制有两个:

  • 模型比较旧,处理prompt(比如时间轴,嘉宾识别)的能力不强;
  • 最大处理能力25MB,一个小时的音频大概是50~70MB,需要把音频切块。可以使用mp3lst等开源工具在段落切分(根据dB),但是不同音频的切分threshold可能不同,切分如果太细也会影响转录的精度。

前期工具调研在这一步卡了几天,后来想到可以直接用长上下文的大模型进行转录。尝试了市面上主流的几个模型以后,发现Google Gemini Flash 1.5非常合适:

  • 转录效果不错,可以基本正确生成时间轴,识别讲话的嘉宾;
  • 上下文窗口足够大,1M token可以容纳超过5个小时的音频(最大支持9.5小时);
    • 免费!免费档每天1500次配额,一集播客大概1-3次请求即可完成转录,转录一集大概3分钟完成,正好足够单线程处理);后面的收费档,一集一小时播客的成本大概$0.18(更正应为 $0.018,比官方价格贵因为上下文长且有好几次的对话),是whisper的一半(更正:应为5%),如果使用1.5 Flash 8B价格还能再减半(效果差点)。

解决了输入长度的问题,下一步卡在了输出这一步。为了避免单次请求占用过多的GPU资源,大部分模型的输出卡在了4k/8k,大概只够放半个小时的音频转录内容。难道又要回到音频切分的老路?其实解决办法很简单,在与大模型对话中继续提示:“请根据上面已经生成的内容继续生成转录文字稿。”,就会在上次停下的地方继续转录,直到完成所有的转录。这也是大模型编程神奇的地方:通过自然语言下发指令,而不是严格定义的编程规格/接口。

利用大模型的长上下文来做转录固然比较方便,但是这会导致需要下一次请求附带整个对话(之前转录的内容加上音频),成本直接翻N倍(如果是N次输出)。如果考虑成本的话,进行切分再拼起来是更好的选择。

索引/检索

这块没有特别的要求,基本上各厂商的文本对话大模型和文本向量模型就能满足需求。不过后面在文本向量模型上跌了个大跟头,后面的文章细说。

本地向量数据库选用了ChromaDB,直接让大模型替我做了选择。

附录:问问小宇宙

小宇宙官方也有类似的应用问问小宇宙,UI挺特别的,但是似乎产品一直没有迭代,召回也比较一般(似乎是基于每篇播客的AI总结做的,因此漏掉很多细节)。

树莓派上手记(2)

 

树莓派到手以后,先别急着确认收货。除了外观完好,总得加电拉出来遛遛。网上的资料很多,最快把机子跑起来的方式有以下几种:

 

  1. 烧录系统,HDMI外接显示器,加上USB键鼠控制

  2. 烧录系统,网口插上DHCP服务的路由器,SSH登录

  3. 烧录系统,USB无线网卡,SSH登录

  4. 烧录系统,USB/串口转TTL连接GPIO登录

 

大家可以自由选择:

  • 小白用户首选1,有图形界面,用起来和普通电脑没什么差别;

  • 程序员或者没有显示器的首选2,Putty/Terminal谁用谁知道,而且也可以装VNC;

  • 家里没有LAN口或者网线的选3,不过还需要购买三十几块钱的无线网卡一块;

  • 文艺用户选4,不解释。

 

烧录系统

 

树莓派官网上有详细的指南,简述如下

1. 到官网下载最新的raw image。系统没什么推荐,一般用raspbian的多,就是wheezy的树莓派版,自带GUI;如果打算用作影音中心可以用RaspBMC;arch应该比较精简,其他的不了解。

2. 解压缩zip文件。

3. 准备好SD卡和读卡器(如果需要的话),接上自己的电脑。

4. 烧录系统,各个操作系统(指各位手上的PC/Mac)方法不一,可以参考 http://elinux.org/RPi_Easy_SD_Card_Setup

a) Windows:下载Win32DiskImager,以管理员权限运行。界面很简单,记得千万别选错驱动器

b) Mac:用dd命令烧系统

 i. 用diskutil先找到disk           diskutil list

 ii. 然后卸载(后面不跟sXXX)  diskutil unmountDisk /dev/diskX    

iii. 写入系统                            sudo dd bs=1m if=<img> of=/dev/diskX

c) Linux:基本和Mac差不多,不过要用df看设备号

5. 等一段时间就好了,我3G的系统烧了半个小时左右吧

 

加电配置

 

插好SD卡,接好线就可以开机了(记得最后接电源或者使用有断路开关的USB线)。我是通过网线连接的,还需要在路由器里看IP地址:

 

然后直接用putty或者终端连接树莓派了,用户名是pi,密码是raspberry。需要root直接用sudo

 

 

 

烧录上去的文件系统只有2.6G,需要扩展到整张SD卡

 

 

 

运行sudo raspi-config进入设置,应该是基于ncurse库编写,有点像BIOS,不过其实这会儿Linux早跑起来了。这里还可以更改启动选项和超频(记得加风扇)。

 

 

 

扩展后文件系统就有7.2G了

 

 

至此,一台运行Linux的开发板就setup好了,我想对于有基本Linux知识的读者应该可以无所不为了,接下去就针对具体需求自由发挥。

 

题图来源:维基百科。这是真正的树莓,我从来没见过没吃过。据说树莓主要是红的品种,也有紫的:

 

 

小白小黑开了个微信公众号,公众号 – “黑白杂谈”, 微信号 talking_ted,或者扫描下面的二维码,欢迎大家踊关注。

树莓派上手记(1)

 
 

树莓派(Raspberry Pi)是由英国同名基金会开发的单板机(不是单片机),主要定位于教育市场,让学生可以在低价的硬件平台上编写程序。在我看来主要是一个开发板。

 

以下引自维基百科:

树莓派配备一枚700MHz博通出产的ARM架构BCM2835处理器,256MB内存(B型已升级到512MB内存),使用SD卡当作储存媒体,且拥有一个Ethernet,两个USB接口,以及HDMI(支持声音输出)和RCA端子输出。Raspberry Pi只有一张信用卡大小,体积大概是一个火柴盒大小,可以执行像雷神之锤III竞技场的游戏和进行1080p影片的播放。操作系统采用开源的Linux系统,比如Debian、ArchLinux,自带的Iceweasel、KOffice等软件能够满足基本的网络浏览,文字处理以及计算机学习的需要,分A,B两种型号,其中售价分别是A型25美元,B型35美元。

 

总体来说,树莓派就是一个能跑Linux的超级微型电脑,只不过去掉了所有的外设(显示、存储、键盘),只剩下芯片组。它的功耗很低,只有3.5w,很适合在家里全天开启跑各种基本服务,如博客、下载等。更有甚者一下子拿了64个板子做了一个集群,也有搞Hadoop的。

 

 

树莓派刚出来的时候略有耳闻,那会儿也关注了Arduino,后来也逐渐淡忘。最近天冷,我们家早上起来一般到另外一个房间换衣服,这时候就难免忍受刺骨的温度和冰凉的衣服。吃早饭的时候我就想起了树莓派:通过学习空调遥控器的信号,给空调发红外遥控指令(需要红外收发模块)。另外回家前也可以通过手机提前打开房间的空调。

 

需求是第一生产力,趁着年末休假的空当,我在淘宝上下单。有行货水货,价格差几十块钱。另外还要单独购买SD卡作为开发板的主存储介质。我随便在亚马逊上买了金士顿的Class 10的8G卡,40块钱不到。另外如果没有读卡器的话还要再捎一个。我倒是多买了一个,到今天才发现老版的Macbook Pro自带SD读卡器。

 

第二天就收到了。是英国的element14的版本。

 

各个接口可以对照下面的图看,右边突出来的一块是插好的SD卡。

 

 

两张图方向不一样,大家将就着看。

 

 

第一期简要介绍了树莓派的来源和应用,下一期开始烧制系统,加电,跑起来!