langchain学习笔记
langchain学习笔记
成为AI全栈工程师的路上
- 为什么叫langchain?
langchain中存在LCEL(langchain expression language),我们可以使用简单的操作连接各个组件,比如我们常用的LLMchain:
chain = prompt | model | parser
想要调用并得到模型的回答,只需要:chain.invoke({...}) ...代表参数,以键值对标识
prompt :
prompt_template, 里面可以包含占位符
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt_template = ChatPromptTemplate.from_messages(
[
("system", system_template),
("user", "hello"),
MessagesPlaceholder(variable_name="messages"), # 这里的placeholder是用来传入更多的交互信息的,只需要在后面在字典中的messages的值传入
]
)
model
接受prompt,返回补全和metadata
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4-turbo")
parser解析器
接受model的结果,解析成结果字符串
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
流式输出
直接把.invoke变成.stream(),就会变成一个可迭代对象
for r in with_message_history_chatbot.stream(
{
"messages": [HumanMessage(content=user_prompt)],
},
config=config
):
yield r
下一步,加入历史记录
一种思路是直接维护一个历史记录列表,手动append,并且把它传入prompt_template
但是官方建议使用
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
这两个类用很多特性,后面见分晓吧。
ChatMessageHistory is not runnable, so it can't be invoked
他是RunnableWithMessageHistory调用的参数
可以run的是RunnableWithMessageHistory
但它不能被加入chain,因为它不属于其中的一个流程
我们可以指定RunnableWithMessageHistory()的
- 获取历史记录的方法,
- invoke的时候实际invoke的chain
如下
def get_session_history(session_id: str) :
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
with_message_history_chatbot = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="messages"
)
调用:(必须要传入config,指定历史记录是那个对话session的)
config = {"configurable": {"session_id": session_id}}
for r in with_message_history_chatbot.stream(
{
"messages": [HumanMessage(content=user_prompt)],
},
config=config
):
yield r
"messages": [HumanMessage(content=user_prompt)]
会传给 with_message_history_chatbot
加入对话历史,构造一个新的 message
对象,同时这个对象会被 with_message_history_chatbot
传给 placeholder
,构成完整的 prompt_template
,最后传给model
加入”只记录最后十轮对话“功能
在chain前面加入一个万金油组件
from langchain_core.runnables import RunnablePassthrough
def filter_messages(messages, k=2):
return messages[-k:]
chain = (RunnablePassthrough.assign(messages=lambda x: filter_messages(x["messages"])) |
prompt_template | model | parser)
历史记录会被传入为x,取后面几个元素,再传递给prompt_template
加入检索知识功能
在chain中再链接一个组件,叫做 retriever
retriver
是由 vectorstore
的 as_retriever()
方法实例化得来的。实例化的时候可以指定检索模式和检索条数。
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 6})
vectorstore
要提前存储内容,选择合适的embedding来实例化:
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
# 存进vectorstore
vectorstore = Chroma.from_documents(documents=all_splits, embedding=OpenAIEmbeddings())
很有趣的地方,关于rag_chain的实现
我们来看一段代码,这是rag_chain的最终实现:
# 定义chain
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()} # 实际上这里就分叉了,参考runnableparallel
| prompt
| llm
| StrOutputParser()
)
用一个图来表示:
一些细节
HumanMessage()和("user", prompt)的区别:
- 后者prompt中所有的{str}格式会被解析为待传入的变量,后面你不传入会报错。如果是上一步模型的结果,并且出现了类似结构,会报错。###
vectorstore的持久化
开始的想法是直接dump运行时变量
考虑用pickle包的dump到本地
但是会遇到vectorstore对象“不能序列化”的问题。
后来发现vectorbase有自己的持久化方法
实例化的时候指定保存路径(不会保存)
vdb(persist_dictory="")
保存到本地
vdb.persist()
langserver的使用
用这个模板快速把应用部署成RESTful API
4. App definition
app = FastAPI(
title="LangChain Server",
version="1.0",
description="A simple API server using LangChain's Runnable interfaces",
)
5. Adding chain route
add_routes(
app,
chain,
path="/chain",
)
if name == "main":
import uvicorn
uvicorn.run(app, host="localhost", port=8000)
调用也十分简单和遵循直觉,只需要在client中实例化一个remotechain
from langserve import RemoteRunnable
remote_chain = RemoteRunnable("http://localhost:8000/chain/")
remote_chain.invoke({"language": "italian", "text": "hi"})
简单的streamlit应用
学习中遇到的棘手的问题/bug?
- 本地调用chain的时候没事,在client传入同样的输入,传到server的方法的时候会少参数。
- 尝试在方法中加入对这个参数的占位符需求,还是没有识别到
- 考虑自己构造一个runnable,指定输入和输出需求键
- 有时候playground少输入框,和上一个问题相关但不对等(有时候playground没有但可以成功传入)。
- 如果第一个runnable是prompt_template,占位符的参数识别的很准确