LangChain + 本地ChatGLM3模型构建一条简单Chain
LangChain这个工具之前比较早看到了,当时觉得它是一个把不同来源的大语言模型都转换为统一的接入口的库,可以在上面利用这个Chain式调用搞开发。我那会儿理解比较浅,就搁置在那儿没有去学,只是觉得会比较方便的构建应用,具体是什么应用没有太大概念。
最近刷B站看到一些Up主在用这个工具做开发,看了一会儿还有点儿意思,那个|
也就是链感觉和Linux管道很像啊!DNA动了!说到这个管道,我自己其实工作中写了一个库可以用来完成有顺序的相互依赖的函数调用,可以设计的更为复杂一些,比如返回多结果的函数,比如一个函数依赖前面多个函数的输出,同时可以多线程执行平行的函数,后续我会将库发布在github上。可能还需要增加一个类似于这种管道符号的支持,应该是通过符号重载,后面试一试。
不过,说实话我还是对这个库的功能不是太清楚,里面函数有点多封装的有点深,引用库有点乱(比如HumanMessage
可以从很多位置import
进来),文档有点乱(最近又更新了一个大版本,两个版本之间文档差别很大!),我的水平还不够驾驭它。不过我的直觉告诉我不宜在这个库上花费过多时间,因为最为根本的是模型的输入(prompt)和输出,如何通过调整输入得到想要的输出才是最为关键的,进而才能对正确的输出进行正确的解析和后续正确的调用,不然一切都是徒劳。LangChain只是这个过程中方便使用的一个连接,如果很清楚OpenAI的API的输入输出,可以自己写程序去完成LangChain一样的事情。
下面的介绍不分是ChatGPT4给出的,后续我理解之后会逐步更新对于这个库的理解。下面很多代码的解读是我根据对python的理解反推回去可能的情况,不一定对。
介绍
LangChain是一个用于构建大型语言模型(LLM)应用程序的开源库。它提供了一组工具和抽象,使得开发者能够更轻松地利用 LLM 的强大功能,构建复杂的自然语言处理(NLP)应用。
- 链式调用(Chains):LangChain 提供了链式调用的概念,允许开发者将一系列的自然语言处理任务串联在一起。这可以包括文本生成、文本摘要、信息提取等操作。
- 提示模板(Prompt Templates):帮助开发者构建和管理提示模板,这些模板可以用于向 LLM 发送结构化的请求。
- 文档加载和处理(Document Loaders and Processors):支持从各种数据源(如文件、API、数据库)加载文档,并提供工具对文档进行预处理和分割,以便更好地处理和分析。
- 嵌入(Embeddings):提供多种嵌入模型的封装,方便开发者将文本转换为向量表示,用于相似性搜索、聚类等任务。
- 存储(Storage):提供对嵌入和其他数据的持久化存储解决方案,支持多种数据库和存储后端。
- 检索增强生成(Retrieval-Augmented Generation, RAG):结合检索和生成模型,构建能够根据外部知识库生成答案的系统。
- 工具和代理(Tools and Agents):允许集成第三方工具和服务,构建更强大的 NLP 应用。
使用场景:
- 对话系统(Chatbots):构建智能对话系统,能够理解和生成自然语言,提供更人性化的交互体验。
- 信息检索与问答系统(QA Systems):结合文档检索和生成模型,实现对大规模知识库的问答功能。
- 自动化文本处理(Automated Text Processing):自动化完成文本摘要、翻译、分类等任务。
- 个性化推荐(Personalized Recommendations):利用用户的历史数据和嵌入模型,实现个性化推荐系统。
上面内容生成自"ChatGpt4"。
组成
- LangSmith:一个用于将过程可视化的工具。
- LangServe:将服务发布。
- Templates:写代码的模板。
- LangChain:
- 构建链式(Chain)的调用工具:将不同功能像Linux管道一样串起来。
- 构建智能体(Agent):可以根据对话智能的安排任务调用工具。
- 构建检索器(Retrieval):根据对话去文档中检索相关信息。
- 更为底层的东西(LangCore):并行、流、异步啊,暂时不用接触。
主要是了解的就是LangChain部分。
示例
这是一个官方的代码示例,暂时不需要执行。
1 | import os |
注意到上面的QianfanChatEndpoint
是需要什么QIANFAN_AK
和QIANFAN_SK
,千帆也就是百度的文心一言的API接口,这里的两个Key是需要购买的,是你的凭证和入场码之类的吧。也就是说你在使用上面的示例的时候需要联网+申请文心一言API+购买Token(刚看了千帆网站,说文心大模型两大主力模型ERNIE Speed、ERNIE Lite全面免费 ,后面我试一试),除了百度还有其他在线模型可以用,可以去这里找到Chat models。如果有Key的话只需要添加环境变量:
1 | export QIANFAN_AK=xxxxxx |
就可以执行上面代码了。
到这里我其实想问:难道不能加载本地的Chat模型吗?
我谷歌了一下:
- Why langchain focuses on OpenAI rather than local llm?
- Using Local Model Instead of API Key in Langchain
- How to use Chat Models from LLama Locally ? I don’t want to use chatopenai model
- Using Local Model Instead of API Key in Langchain
- How to use LangChain with local LLM?
- How to use local hosted LLMs in LangChain
老外也有发牢骚说为什么注重于OpenAI好像忽略了本地大语言模型,也有提问能否加载本地模型的。对于:
-
为什么LangChain关注与OpenAI而不是本地的LLM? 我理解的是OpenAI作为大语言模型方向领导者,绝对是一流的,一流的企业制定标准,很多模型的执行代码中都有`openai_api.py`之类的脚本就是为了兼容OpenAI的标准。
OpenAI API标准:OpenAI API,这是一个示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": "Hello!"
}
]
}'返回:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-3.5-turbo-0125",
"system_fingerprint": "fp_44709d6fcb",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "\n\nHello there, how may I assist you today?",
},
"logprobs": null,
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 12,
"total_tokens": 21
}
}就是按照特定的数据格式进行API的请求,然后会返回的特定数据格式的结果。
- 为什么不能用本地的模型? 其实是可以用的,只不过很麻烦。我觉得现在各自的大模型的加载预测等等都很不一样,如果说将他们都能统一起来,那么代码会非常多样,而且需要随时基于不同的模型进行更新,这对于直接Call API来说要麻烦很多,Call API就是指定了网址,指定了参数,指定各种Key就能得到结果。
这次我将尝试使用清华智谱的ChatGLM3
,上半年很受欢迎的模型来测试一下。
之前的文章中曾经本地部署了
ChatGLM4
,你可能会问为什么不用最新的?一想到这个我脑子就浮现那天满屏红色的情景。主要原因并不再与生成OpenAI的API,而是CUDA11.8的vllm的安装和导入上搞了大半天,要么是安装不了,要么就是安装了导入执行OpenAI API的时候报各种错,懒得折腾了,等后面把CUDA更新一下再弄弄。
ChatGLM3-6b模型的部署
同样的来一遍模型部署吧!
建立conda环境:
1 | conda create -n ChatGLM3 python=3.10 |
在ChatGLM3的代码里面,有关于生成OpenAI API的代码,到时候执行就行了。
1 | git clone https://github.com/THUDM/ChatGLM3.git |
如果报错什么存储不够,就这样:
1
2 mkdir tmp
export TMPDIR=./tmp
我是CUDA11.8,安装相应版本的torch:
1 | pip uninstall torch torchvision torchaudio |
生一个代码文件download_model.py
,里面写上:
1 | from modelscope import snapshot_download |
执行代码下载:
1 | python download_model.py |
执行openapi API服务,这里相当于在本地生成了一个API:
1 | cd openai_api_demo |
默认情况下是在http://127.0.0.1:8000
,如果需要更改,直接去api_server.py
找到相应位置去改就行了。
官方比较贴心还给了一个测试的代码,用langchain测试API能不能用:
1 | python langchain_openai_api.py |
测试结果:
1 | Human (or 'exit' to quit): 你是谁 |
测试本地模型的LLM功能
下面将使用刚才我们本地构建的API,使用这里langchain访问本地API接口得到返回结果:
1 | import os |
实际上直接这样也可以:
1 | llm = ChatGLM3(endpoint_url="http://127.0.0.1:8000/v1/chat/completions", max_tokens=4096) |
这个就是通过传入Message
对象进行对话的过程,除此之外,你可以把之前的对话放到messages
里面:
1 | messages = [ |
初试LangChain的Chain功能
上面只是测试一下本地模型能不能按照自己写的langchain代码运行,下面使用chain才算是第一步。这里的chain太像Linux的管道了,感觉很亲切又很疏远,疏远的是LangChain的函数太多了封装的有点深,刚入门的我有点吃不消。
1 | import os |
对于:
1 | chain = chat_prompt | ChatGLM3(endpoint_url="http://127.0.0.1:8000/v1/chat/completions") | CommaSplitParse() |
chat_prompt
这里并没有写成chat_prompt()
,对于python语法理解多一点的应该明白两者的差别,chat_prompt
实际上还是函数或者对象本身,而chat_prompt()
相当于对函数执行了得到结果了。可以试一下:chat_prompt({"text": "colors"})
,但是报错了,这里可能并不是简单理解的直接调用,可能是调用某个函数。ChatGLM3(endpoint_url="http://127.0.0.1:8000/v1/chat/completions")
,这里你可能会说这不是函数调用了吗,这里因为ChatGLM3
是一个类,这是为了传参数,然后返回了一个对象(查看源码,这个类是包含有_call
方法的),这个对象可以被当成函数去call。CommaSplitParse()
得到具体对象,上面的输出传入进来会被parse()
函数进行处理,你问为什么调用的是parse()
而不是什么xxx()
函数,应该是继承BaseOutputParser
的特性吧。
整个过程我认为是:
1 | 输入参数:{"text": "colors"} |
按照这个想法,是不是可以写一个函数,比如将最后的列表join成字符串:
1 | def merge_list(l): |
成功,看来的确如此!
好了,到这里完整走完了第一个chain的示例,是用的本地模型哦!用在线API就方便很多。
这里的from langchain_community.llms.chatglm3 import ChatGLM3
仍然是从langchain中引入的,后面我琢磨一下自己写一个类来封装本地的模型的LLM,这样会更加灵活。
比如可以添加一个量化加载方式。
ChatGLM3是有量化版本的,可以以 FP16 精度加载,对于显存较小的可以在加在模型的添加quantize(4)
方法:
1 | model = AutoModel.from_pretrained("THUDM/chatglm3-6b",trust_remote_code=True).quantize(4).cuda() |
模型量化会带来一定的性能损失,经过测试,ChatGLM3-6B 在 4-bit 量化下仍然能够进行自然流畅的生成。
同样的也可以在CPU上进行推理:
1 | model = AutoModel.from_pretrained("THUDM/chatglm3-6b", trust_remote_code=True).float() |
后面会基于这个做更进一步的应用,比如本地文档搜索的RAG应用、自动任务调用的Agent。