先來個溫馨的小提醒:
這篇文章雖然較為全面地介紹了 LangChain馒吴,但都是點到為止俭厚,只是讓你了解一下它的皮毛而已,適合小白選手澡刹。
So呻征,如果你是 LangChain 的小白,看完之后還是一頭霧水罢浇,那就請毫不留情地陆赋,狠狠地 .................................... 給我點贊吧 ( ???) ??!有了你的鼓勵己莺,我會再接再厲的奏甫!(? ?_?)?
What?
丹尼爾:蛋兄,剛剛聽到別人在說 LangChain凌受,你知道是啥玩意嗎?
蛋先生:哦思杯,LangChain 啊胜蛉,一個開發(fā)框架
丹尼爾:開發(fā)啥的框架?
蛋先生:一個用于開發(fā)語言模型驅動的應用的框架
丹尼爾:哦色乾,開發(fā)這種應用誊册,不就是寫寫 Prompt 提示語,調調語言模型 API 的事么暖璧?
蛋先生:沒錯案怯。但 LangChain 使得 Prompt 的編寫,API 的調用更加標準化
丹尼爾:就這樣嗎澎办?
蛋先生:當然不止嘲碱,它還有很多很酷的功能
丹尼爾:比如?
蛋先生:它可以連接外部數(shù)據(jù)源局蚀,根據(jù)輸入檢索相關數(shù)據(jù)作為上下文給到語言模型麦锯,使得語言模型可以回答訓練數(shù)據(jù)之外的問題。這是由 LangChain 的 Retrieval
來實現(xiàn)的
丹尼爾:太酷了琅绅,我想到了一個場景扶欣,比如通過它來連接客服的回答話術庫,這樣就可以讓語言模型搖身一變,變成一個專業(yè)的客服了
蛋先生:恩料祠,這是一個很好的場景
丹尼爾:還有其它更酷的功能嗎骆捧?
蛋先生:它可以讓語言模型來自行決定采取哪些行動
丹尼爾:這個就不是很明白了
蛋先生:接著你那個客服的例子繼續(xù)說。如果用戶問的問題是關于公司產(chǎn)品的髓绽,我們就想讓語言模型使用客服的話術庫來回答凑懂;如果是其它問題,就讓語言模型用它自己的知識來直接回答梧宫。如果是你接谨,你會怎么實現(xiàn)?
丹尼爾:我想我會先通過語言模型來判斷用戶的問題是否關于公司產(chǎn)品塘匣。如果是脓豪,就走連接話術庫的邏輯;如果不是忌卤,就走讓語言模型直接回答的邏輯
蛋先生:恩扫夜,你這種就是 hardcode 邏輯的方式。還有一種更加 amazing 的 方式驰徊,就是讓語言模型自行決定采取哪種行為笤闯。這個由 LangChain 的 Agent
來實現(xiàn)。
丹尼爾:聽上去太酷了棍厂,怎么用呢颗味?
蛋先生:莫急,待我慢慢道來
Why?
丹尼爾:蛋兄牺弹,你剛剛說到 LangChain 使得 Prompt 的編寫浦马,API 的調用更加標準化,標準化了肯定是好的张漂,但好處很大嗎晶默?我用語言模型的 SDK 不也用得好好的嗎?
蛋先生:那你先給一個使用 SDK 與語言模型交互的例子唄
丹尼爾:這還不簡單航攒,我就用這個吧:fireworks.ai (注:這個平臺提供免費的資源磺陡,訪問也方便)
from fireworks.client import Fireworks
client = Fireworks(api_key="<FIREWORKS_API_KEY>")
response = client.chat.completions.create(
model="accounts/fireworks/models/llama-v2-7b-chat",
messages=[{
"role": "user",
"content": "Who are you?",
}],
)
print(response.choices[0].message.content)
輸出:
Hello! I'm just an AI assistant, here to help you in any way I can. My purpose is to provide helpful and respectful responses, always being safe and socially unbiased. I'm here to assist you in a positive and ethical manner, and I'm happy to help you with any questions or tasks you may have. Is there anything specific you would like me to help you with?
蛋先生:很好,再給另外一個語言模型的例子唄
丹尼爾:額漠畜,一樣的操作啊币他,你這是在消遣我嗎?好吧盆驹,那我就再給一個百度的文心一言的例子
import os
import qianfan
os.environ["QIANFAN_AK"] = "<QIANFAN_AK>"
os.environ["QIANFAN_SK"] = "<QIANFAN_SK>"
chat_comp = qianfan.ChatCompletion()
resp = chat_comp.do(messages=[{
"role": "user",
"content": "Who are you?"
}])
print(resp.body['result'])
輸出:
您好圆丹,我是百度研發(fā)的知識增強大語言模型,中文名是文心一言躯喇,英文名是ERNIE Bot辫封。我能夠與人對話互動硝枉,回答問題,協(xié)助創(chuàng)作倦微,高效便捷地幫助人們獲取信息妻味、知識和靈感。
蛋先生:Good欣福,現(xiàn)在假設我一開始使用 fireworks 來開發(fā)應用责球,過程中發(fā)現(xiàn)效果不太理想,想換成文心一言呢拓劝?
丹尼爾:Oh~雏逾,各個語言模型的 SDK 的接口定義是不一樣的,替換起來確實麻煩郑临。來吧栖博,是時候開始你的表演了
蛋先生:我們直接來看下通過 LangChain 使用 fireworks 和 文心一言 的代碼示例吧,畢竟 No Code No BB 嘛
- fireworks LangChain 示例
import os
from langchain_community.chat_models.fireworks import ChatFireworks
os.environ["FIREWORKS_API_KEY"] = '<FIREWORKS_API_KEY>'
model = ChatFireworks(model="accounts/fireworks/models/llama-v2-13b-chat")
res = model.invoke("Who are you?")
print(res.content)
- 文心一言 LangChain 示例
import os
from langchain_community.chat_models import QianfanChatEndpoint
os.environ["QIANFAN_AK"] = "<QIANFAN_AK>"
os.environ["QIANFAN_SK"] = "<QIANFAN_SK>"
model = QianfanChatEndpoint(model="ERNIE-Bot-turbo")
res = model.invoke("Who are you?")
print(res.content)
丹尼爾:好像看出來了厢洞,標準化之后仇让,要更換語言模型變得非常方便了,只需要更換下 model 的實例化就行了
蛋先生:是的躺翻,這只是個最簡單的例子丧叽,LangChain 還有很多種優(yōu)雅的方式來切換不同的模型。從此以后我們就可以專注于 Prompt 的開發(fā)了公你。語言模型嘛踊淳,哪個合適換哪個
How?
丹尼爾:好了,我決定入坑 LangChain 了省店,那咱們進一步聊聊嚣崭?
蛋先生:當然可以!我們從簡單到復雜懦傍,結合代碼和流程圖來展示 LangChain 的一些用法。先來最簡單的代替 SDK 的用法芦劣,這個上邊已經(jīng)有提到了
res = model.invoke("tell me a short joke about a cat")
print(res.content)
[圖片上傳失敗...(image-307658-1709280564539)]
丹尼爾:恩粗俱,這個 so easy,一瞄就懂
蛋先生:OK虚吟,那接下來我們來使用 PromptTemplate寸认,通過變量的方式來控制模板里的部分內容
from langchain_core.prompts import ChatPromptTemplate
...
prompt = ChatPromptTemplate.from_template("tell me a short joke about {topic}")
chain = prompt | model
res = chain.invoke({"topic": "a cat"})
print(res.content)
[圖片上傳失敗...(image-ac6583-1709280564539)]
丹尼爾:使用 PromptTemplate 的方式來寫 prompt,確實比字符串的拼接要優(yōu)雅不少
蛋先生:再加個簡單的輸出轉換吧
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
...
prompt = ChatPromptTemplate.from_template("tell me a short joke about {topic}")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
res = chain.invoke({"topic": "a cat"})
print(res)
[圖片上傳失敗...(image-441f77-1709280564539)]
丹尼爾:終于知道為啥叫 chain
了
蛋先生:繼續(xù)串慰?
丹尼爾:繼續(xù)...
蛋先生:接下來這段代碼可能有點長哦
from langchain_community.embeddings import QianfanEmbeddingsEndpoint
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import faiss
from langchain_community.chat_models import QianfanChatEndpoint
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# 1
docs = WebBaseLoader("https://docs.smith.langchain.com").load()
embeddings = QianfanEmbeddingsEndpoint()
documents = RecursiveCharacterTextSplitter(chunk_size=900).split_documents(docs)
vector = faiss.FAISS.from_documents(documents, embeddings)
retriever = vector.as_retriever(search_kwargs={'k': 4})
# 2
setup_and_retrieval = RunnableParallel(
{"context": retriever, "input": RunnablePassthrough()}
)
prompt = ChatPromptTemplate.from_template("""Answer the following question based only on the provided context:
<context>
{context}
</context>
Question: {input}""")
model = QianfanChatEndpoint(streaming=False, model="ERNIE-Bot-turbo")
output_parser = StrOutputParser()
# 3
retrieval_chain = setup_and_retrieval | prompt | model | output_parser
res = retrieval_chain.invoke("how can langsmith help with testing?")
print(res)
丹尼爾:請把“可能”去掉偏塞,謝謝
蛋先生:但邏輯其實并不復雜,主要分為三塊
1)加載網(wǎng)頁文檔邦鲫,通過 Embeddings
將文檔內容轉成向量并存儲在向量數(shù)據(jù)庫 FAISS
中灸叼,retriever
就是一個可以根據(jù)輸入從向量數(shù)據(jù)庫獲取相關文檔的檢索工具
2)聲明 chain 的各個步驟
3)將各個步驟按順序 chain 起來
丹尼爾:等等神汹,看著有點腦殼疼。Embeddings古今?向量屁魏?向量數(shù)據(jù)庫?
蛋先生:咱們今天是“初探”捉腥,所以也只能簡單講講氓拼,不然很多同學就要昏昏欲睡了
丹尼爾:沒問題,有個大概印象也好
蛋先生:首先抵碟,為什么要將文本轉成向量呢桃漾?因為通過計算兩個向量的距離,我們就可以量化地評估它們的相關性拟逮。距離越小撬统,通常意味著文本之間的相關性越高。我們這里是需要檢索與輸入相關的文檔內容唱歧,將其作為會話上下文提供給語言模型宪摧。如果是整個文檔都傳過去,是不是就太大了呢颅崩?
丹尼爾:哦几于,原來向量有這么高級的功能啊
蛋先生:沒錯。然后要將文本轉成向量沿后,就需要用到 Embeddings(詞嵌入)技術沿彭。Embeddings 在歷史上有過多種方法,如基于統(tǒng)計的計數(shù)方法尖滚,基于神經(jīng)網(wǎng)絡的推理方法等喉刘。 QianfanEmbeddingsEndpoint
正是一個利用深度學習訓練得到的 Embeddings 模型服務,輸入為文本漆弄,輸出為向量
丹尼爾:大概有點明白了
蛋先生:那我們接著看下流程圖
[圖片上傳失敗...(image-b6a110-1709280564539)]
丹尼爾:能否為小弟我解釋一下上面這個流程圖的前半部分
蛋先生:當然睦裳!首先輸入是 "how can langsmith help with testing?";接著有個并行的邏輯撼唾,一個是通過 Retriever 根據(jù)輸入檢索相關的文檔內容作為 context 的值廉邑,另一個則是直接 pass 將輸入作為 input 的值;然后就是將數(shù)據(jù)傳給 Prompt 模板倒谷,最終就可以得到傳給語言模型的 PromptValue 了
丹尼爾:Soga
蛋先生:注意蛛蒙,壓軸要登場了哦,現(xiàn)在讓我們來請出大名鼎鼎的 Agent 吧
from langchain import hub
from langchain.agents import AgentExecutor, create_json_chat_agent
from langchain.tools import tool
from langchain_community.chat_models.fireworks import ChatFireworks
@tool
def leng(word: str) -> str:
"""Please use this tool if you want to find the length of the word."""
return len(word)
@tool
def lower(word: str) -> str:
"""Please use this tool if you need to change the word to lowercase."""
return f'dx_{word.lower()}'
tools = [leng, lower]
model = ChatFireworks(model="accounts/fireworks/models/llama-v2-70b-chat")
prompt = hub.pull("hwchase17/react-chat-json")
agent = create_json_chat_agent(model, tools, prompt, stop_sequence=False)
agent_executor = AgentExecutor(
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True, max_iterations=5)
res = agent_executor.invoke({"input": "Make this word lowercase: 'Daniel'"})
print(res)
{'input': "Make this word lowercase: 'Daniel'", 'output': "The lowercase version of 'Daniel' is 'dx_daniel'"}
丹尼爾:好耶渤愁,快點講解一下吧
蛋先生:首先我們聲明了兩個工具:一個是 leng(用于求字符串長度)牵祟,一個是 lower(用于將字符串變成小寫)。這里為了證明結果是通過我們的工具來得到結果的抖格,所以特意在 lower 的實現(xiàn)中加了個 dx_ 前綴
丹尼爾:等等诺苹,hub.pull("hwchase17/react-chat-json")
是什么神秘代碼咕晋?
蛋先生:這是 LangChain hub 社區(qū)上共享的用于實現(xiàn) Agent 的眾多 Prompt 中的一個,你可以在這里找到很多有用的 Prompt筝尾。畢竟捡需,語言工程也是一種藝術,也是需要實踐積累的筹淫。
丹尼爾:明白站辉,請繼續(xù)
蛋先生:通過 Agent,語言模型就可以根據(jù)輸入自行判斷應該使用哪個工具了
丹尼爾:哇损姜,這太神奇了饰剥!我對它是怎么自行判斷很感興趣
蛋先生:簡單來說,語言模型可以根據(jù)輸入摧阅,再根據(jù)各個工具的描述汰蓉,來判斷哪個工具更適合,然后將結果輸出為可以讓 LangChain 理解的執(zhí)行指令(比如 JSON)
丹尼爾:太棒了棒卷!現(xiàn)在我對 LangChain 有了一個大致的了解顾孽,希望以后還能跟你繼續(xù)深入探討
蛋先生:機會有滴是,咱們后會有期比规!ヾ( ̄▽ ̄)ByeBye