缘起
近半年来AI非常火爆,我们深刻地体会到GPT类产品已经切实地在改变我们的生活和工作方式。我也比较关注AI相关的开源技术,最近看到了两个开源库给了我非常大的灵感:
1.LangChain:这个库把AI开发中可能会用到的相关技术都抽象成一个个小的模块,很多工具函数都开箱即用。官网里的一个向量数据库使用的例子给我留下了深刻的印象:从本地文件的读取、文本内容的拆分、向量的存储到相似向量的检索**,这整个过程每个步骤都是几个非常简洁明了的函数调用,整个流程几十行代码**。不禁让我感叹:这么容易?有了这AI开发我也能搞啊!
2.AutoGPT:在给AutoGPT设定一个目标后,它会做类OKR分解,执行,检查完成情况,制定优化计划,依此通过不断地自我prompt达成设定目标。在学习它源码的时候有一点对我的启发很大:首先代码中预定义了一堆command脚本,在和ChatGPT通话过程中会让它回复{”command”:“xxx”,“thoughts”:‘xxx’}这样的JSON,解析后可以继续执行本地脚本,从而实现自主执行,感觉这是这个项目的精华所在。其实看到JSON,作为前端开发是很兴奋了,通过调戏LLM让他给出JSON,纯前端就能玩出很多花活呢!
想到上面几个灵感比较兴奋,于是趁着周末两天撸了个“套壳”应用。同时也验证下利用PromptEngineering调戏LLM返回JSON的方式玩出花活的可能性
有观察市面上比较火的文档类问答工具ChatPDF和ChatDocs等产品,觉得非常酷。因此,我打算简单实现类似的功能:在应用程序的某个目录下放置一些文件(PDF、TXT、DOC、EXCEL),然后在聊天窗口中提出任何文档相关的问题,机器人将总结好答案并回复你。此外,我还设计了一些小交互(如文章开头GIF所示):让机器人在回复答案的同时说明参考原文,并联想一些类似的问题,方便用户持续提问。
实现分析
主要聚焦在两个问题:1.如何调教LLM让它学会本地的知识,2.“套壳应用”工程技术选型
如何喂数据给LLM?
Fine-Turning
说到让大语言模型学会一些本地知识,首先会想到的是“训练”。通常的做法是为机器准备固定格式的数据集,类似于[{question:'xxx',answer:'xxx'},...],然后使用Fine-Tuning的方法对非结构化数据进行训练。但对于像我这样的外行来说,去对数据做预处理和特征工程不是一种合适的解法。
Embedding
Embedding的理解是将高维数据向量化的过程。在LangChain中,有现成的工具函数可以完成这个任务。众所周知,GPT3.5的API有4Ktoken的长度限制,因此直接将所有的PDF文件一股脑地塞给LLM是不可行的,肯定超过了这个长度限制。LangChain中的DocumentLoaders、TextSplitters、VectorStores提供了一种巧妙的方法来绕过这个4Ktoken的限制。大致的工作原理是:首先在本地启动一个向量数据库,用来存储所有本地文档。然后,在进入GPT之前,先在本地进行一次相似度内容搜索。最后,将空间距离最近的几个答案嵌入提问中,交给LLM处理。
所以我们使用LangChain,其实并没有真正意义上去训练LLM,我们只是把本地知识库和问题当做prompt的一部分给到LLM。这么做非常的轻量级,LLM只需要做的它最擅长的事情。翻译和总结是大语言模型非常擅长的两件事情,所以如果你在目录里放的是英文文档,聊天框里你是可以通过中文和应用进行对话的。
如何搭建“套壳”应用工程?
一旦确定了AI训练数据相关的方案后,工程化就相对容易了,分析了下大致需要以下几个部分:
所以其实只需一个Next.js应用程序即可包含以上所有部分,只需一行npmstart即可启动所有模块(前端、后端、数据库)。
工程代码关键实现
在代码部分,我个人认为基于LangChain进行开发非常简洁。当大家看到下面的实现后,应该也会有同样的感觉。
首先,看这段Prompt代码,它算是这个套壳应用最核心的代码
这段代码也是我在调试过程中花费了相当多时间的。首先,我让GPT扮演文档工程师的角色,让他帮我总结与“问题”相关的内容,这也充分利用了LLM的超强总结能力。我要求LLM返回一个包含三个字段的JSON,这几个字段也是界面聊天框内吐出的答案所必需的内容,即机器人的总结回复、原文引用和相似答案。
其实中间我遇到了一个比较大的问题:就是LLM有较大的概率不会返回完整的JSON结构体。这会导致应用JSON.parse失败。解决办法有很多,例如AutoGPT中有自定义的fix_json_using_multiple_techniques函数用于修复JSON,LangChain中也有OutputFixingParser等解析函数。不过我尝试的是另一种SAO操作——“威胁”,如下:
“不要包含任何其他非JSON内容,否则你将被扣分!!!”
经实测,回答出错率的确会下降很多,但无法完全避免出错。还需要进一步研究研究,也许需要更大参数量的API来解决这个问题。
流程中其他流程相关的代码我贴一些图,大家也能感受一下LangChain的简单易用:
本地文件的解析
通过Embedding方式做文档切片并初始化向量数据库
近似向量内容检索
LLMAPI调用
聊天前端代码:把HTML+JS都算上不到300行代码
顺带安利一下,tailwind是next.js自带的CSS框架。时常在其他开源项目中看到,这次聊天页面的CSS基本上都是用tailwind编写的。上手成本非常低,基本上可以不离开HTML就将CSS全部搞定,这得益于GPT的支持。
最后几点零散想法