鍵.png)
GraphRAG:基于PolarDB+通義千問api+LangChain的知識(shí)圖譜定制實(shí)踐
我想做一個(gè)嘗試,看看能不能用盡量清晰的邏輯,給“AI外行人士”(當(dāng)然,我也是)引入一下LangChain,試著從工程角度去理解LangChain的設(shè)計(jì)和使用。同時(shí)大家也可以將此文檔作為LangChain的“10分鐘快速上手”手冊(cè),本意是希望幫助需要的同學(xué)實(shí)現(xiàn)AI工程的Bootstrap。
文中所有的示例代碼都已托管到GitHub:https://github.com/TuGraph-contrib/langchain-demo,喜歡實(shí)操的小伙伴可以邊閱讀,邊復(fù)現(xiàn)。
正式開始前,還是有必要從定義(What)開始。LangChain是2022年10月底,由哈佛大學(xué)的Harrison Chase發(fā)起的基于開源大語言模型的AI工程開發(fā)框架。當(dāng)然也可以問一下AI:
我:LangChain是什么?
GPT-4:LangChain 是一個(gè)開源的語言模型工具鏈框架,旨在使研究人員和開發(fā)人員能夠更輕松地構(gòu)建、實(shí)驗(yàn)和部署以自然語言處理(NLP)為中心的應(yīng)用程序。它提供了多種組件和工具,可幫助用戶利用最近的語言模型進(jìn)展,如大型 Transformer 模型等,并且可以與 Hugging Face 等平臺(tái)集成。 LangChain 的核心理念是將語言模型用作協(xié)作工具,通過它,開發(fā)者可以構(gòu)建出處理復(fù)雜任務(wù)的系統(tǒng),并且可以高效地對(duì)接不同的數(shù)據(jù)源和應(yīng)用程序接口(APIs)。這個(gè)框架試圖簡化連接不同的輸入和輸出流,以及在流中實(shí)現(xiàn)語言模型的過程。
顧名思義,LangChain中的“Lang”自然是指大語言模型,“Chain”即“鏈”,也就是將大模型與其他組件連接成鏈,借此構(gòu)建AI工程應(yīng)用。那么LangChain該如何(How)做到這一點(diǎn)的呢?解答這個(gè)問題之前,需要先回答什么是工程?什么是AI工程?
我們先Review一下“工程”的百科定義:
工程是指以某組設(shè)想的目標(biāo)為依據(jù),應(yīng)用有關(guān)的科學(xué)知識(shí)和技術(shù)手段,通過有組織的一群人將某個(gè)(或某些)現(xiàn)有實(shí)體(自然的或人造的)轉(zhuǎn)化為具有預(yù)期使用價(jià)值的人造產(chǎn)品過程。
其中,“目標(biāo)”定義了要解決的問題域,決定了工程的頂層設(shè)計(jì)和能力邊界,同時(shí)也定義了“產(chǎn)品”的最終形態(tài)。提升“人”的ROI是工程設(shè)計(jì)的價(jià)值歸屬。“實(shí)體”是工程的生產(chǎn)材料輸入,“科學(xué) | 技術(shù)”是工程有序運(yùn)行的基礎(chǔ),對(duì)它們的合理利用可以提升工程的整體產(chǎn)出效率。
于是,我們可以這樣解讀“AI工程”中的關(guān)鍵概念:
因此,如果我們是LangChain的設(shè)計(jì)者,希望構(gòu)建通用的AI工程框架。需要回答如下問題:
當(dāng)然,作為“事后諸葛”,這些問題目前有比較明確的答案:
很明顯,LLM作為LangChain能力的基礎(chǔ),是了解LangChain工程化設(shè)計(jì)的前提。接下來我們就從最基礎(chǔ)的LLM API使用談起,一步步了解LangChain的工程化構(gòu)建過程及其背后的設(shè)計(jì)理念。
export OPENAI_API_KEY="<Your-OpenAI-SK>"
pip install langchain langchain-openai
架構(gòu)設(shè)計(jì)領(lǐng)域有個(gè)比較流行的術(shù)語——樂高架構(gòu),當(dāng)然也可以叫可插拔架構(gòu)。說白就是通過對(duì)系統(tǒng)基本組件的合理抽象,找到構(gòu)造復(fù)雜系統(tǒng)的統(tǒng)一規(guī)律和可達(dá)路徑,從而實(shí)現(xiàn)在降低系統(tǒng)實(shí)現(xiàn)復(fù)雜度的同時(shí),提升系統(tǒng)整體的擴(kuò)展性。(非官方表達(dá),大家能Get到我的意思就好……)
LangChain實(shí)際上也遵循了樂高架構(gòu)的思想。當(dāng)然,作為最關(guān)鍵的樂高組件之一,LLM的能力自然是我們優(yōu)先了解的對(duì)象,那我們就從OpenAI的API開始吧!
文本生成模型服務(wù)是OpenAI提供的最核心的API服務(wù),自ChatGPT發(fā)布后經(jīng)歷過幾次版本迭代。
當(dāng)下最新的是Chat Completion API[2],是AI與LLM交互的核心入口。
代碼示例參考:
import os
import requests
# API Key
api_key = os.getenv('OPENAI_API_KEY')
# 頭部信息
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_key}'
}
# 準(zhǔn)備數(shù)據(jù)
data = {
'model': 'gpt-4',
'messages': [{'role': 'user', 'content': '什么是圖計(jì)算?'}],
'temperature': 0.7
}
# 調(diào)用API
url = 'https://api.openai.com/v1/chat/completions'
response = requests.post(url, json=data, headers=headers)
answer = response.json()['choices'][0]['message']['content']
print(answer)
代碼示例輸出:
圖計(jì)算是一種計(jì)算模型,用于處理大規(guī)模圖形結(jié)構(gòu)的數(shù)據(jù),并執(zhí)行各種復(fù)雜的算法和計(jì)算。這種計(jì)算模型主要用于社交網(wǎng)絡(luò)分析、Web搜索、生物信息學(xué)、網(wǎng)絡(luò)路由優(yōu)化、數(shù)據(jù)挖掘等領(lǐng)域。圖計(jì)算模型的核心是將數(shù)據(jù)表示為圖形結(jié)構(gòu)(節(jié)點(diǎn)和邊),這樣可以更好地揭示數(shù)據(jù)之間的關(guān)系和互動(dòng)。在圖計(jì)算中,算法通常以迭代的方式運(yùn)行,每次迭代都會(huì)更新圖中節(jié)點(diǎn)的狀態(tài),直到達(dá)到某種停止條件。
早先的Completion API[3]已經(jīng)在2023年7月后不再維護(hù),和最新的Chat Completion API參數(shù)和結(jié)果格式有所不同,最明顯的是Prompt是以純文本方式傳遞,而非Message格式。
# 準(zhǔn)備數(shù)據(jù)
data = {
'model': 'gpt-3.5-turbo-instruct',
'prompt': ['什么是圖計(jì)算?'],
'max_tokens': 1024
}
# 調(diào)用API
url = 'https://api.openai.com/v1/completions'
response = requests.post(url, json=data, headers=headers)
answer = response.json()['choices'][0]['text']
print(answer)
除了文本生成服務(wù),OpenAI也提供了大量的LLM的周邊服務(wù),以協(xié)助AI工程構(gòu)建更復(fù)雜的應(yīng)用能力。如:函數(shù)調(diào)用、嵌入、微調(diào)、多模態(tài)等,具體可參考OpenAI開發(fā)文檔[4]的內(nèi)容。
自2022年11月底ChatGPT發(fā)布以來,AI的大門才真正地向人類打開,其中給用戶留下最深印象的功能,自然是智能對(duì)話。OpenAI的Chat Completion API參數(shù)支持傳入消息歷史,可以輕松地實(shí)現(xiàn)簡單的對(duì)話服務(wù)。
代碼示例參考:
# 對(duì)話歷史
messages = []
def chat_with_ai(message):
# 記錄歷史
messages.append({'role': 'user', 'content': message})
print(f'me: {message}')
# 對(duì)話請(qǐng)求
data = {
'model': 'gpt-4',
'messages': messages,
'temperature': 0.7
}
url = 'https://api.openai.com/v1/chat/completions'
response = requests.post(url, json=data, headers=headers)
# 解析回答
if response.status_code == 200:
answer = response.json()['choices'][0]['message']['content']
messages.append({'role': 'assistant', 'content': answer})
print(f"ai: {answer}")
else:
print(f'Error: {response.status_code}', response.json())
# 多輪對(duì)話
chat_with_ai('什么是圖計(jì)算?')
chat_with_ai('剛才我問了什么問題?')
代碼示例輸出:
me: 什么是圖計(jì)算? ai: 圖計(jì)算是一種計(jì)算模型,用于處理大規(guī)模圖形結(jié)構(gòu)數(shù)據(jù)的計(jì)算和分析。在這種計(jì)算模型中,數(shù)據(jù)被表示為圖形,其中節(jié)點(diǎn)代表實(shí)體,邊代表實(shí)體之間的關(guān)系。圖計(jì)算可以用于解決許多實(shí)際問題,如社交網(wǎng)絡(luò)分析、網(wǎng)絡(luò)路由、生物信息學(xué)等。圖計(jì)算的主要挑戰(zhàn)是如何有效地處理大規(guī)模的圖形數(shù)據(jù),并提供快速的計(jì)算和分析結(jié)果。 me: 剛才我問了什么問題? ai: 你問的問題是:“什么是圖計(jì)算?”
到目前為止,我們還只是用OpenAI最原始的RESTful API構(gòu)建LLM工程能力,甚至連OpenAI提供的SDK都未使用。顯然這不是一個(gè)高效的方式,使用前邊安裝的LangChain-OpenAI集成包langchain-openai
可以大大降低代碼的開發(fā)成本。
代碼示例參考:
from langchain_openai import ChatOpenAI
# 調(diào)用Chat Completion API
llm = ChatOpenAI(model_name='gpt-4')
response = llm.invoke('什么是圖計(jì)算?')
print(response)
代碼示例輸出:
content=’圖計(jì)算是一種計(jì)算模型,主要用于處理圖形結(jié)構(gòu)數(shù)據(jù)的計(jì)算和分析。圖計(jì)算的對(duì)象是圖,圖由節(jié)點(diǎn)和邊組成,節(jié)點(diǎn)代表實(shí)體對(duì)象,邊代表實(shí)體對(duì)象之間的關(guān)系。圖計(jì)算主要用于解決實(shí)體關(guān)系復(fù)雜、關(guān)系密集的問題,如社交網(wǎng)絡(luò)分析、網(wǎng)絡(luò)拓?fù)浞治觥⑼扑]系統(tǒng)等。圖計(jì)算的主要任務(wù)是通過對(duì)圖中節(jié)點(diǎn)和邊的計(jì)算,發(fā)現(xiàn)和提取出圖中隱含的知識(shí)和信息。’
對(duì)于文本生成模型服務(wù)來說,實(shí)際的輸入和輸出本質(zhì)上都是字符串,因此直接裸調(diào)用LLM服務(wù)帶來的問題是要在輸入格式化和輸出結(jié)果解析上做大量的重復(fù)的文本處理工作。LangChain當(dāng)然考慮到這一點(diǎn),提供了Prompt[5]和OutputParser[6]抽象,用戶可以根據(jù)自己的需要選擇具體的實(shí)現(xiàn)類型使用。
代碼示例參考:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 創(chuàng)建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創(chuàng)建Prompt
prompt = ChatPromptTemplate.from_template("{question}")
# 創(chuàng)建輸出解析器
output_parser = StrOutputParser()
# 調(diào)用LLM
message = prompt.invoke({'question': '什么是圖計(jì)算?'})
response = llm.invoke(message)
answer = output_parser.invoke(response)
print(answer)
模型的IO組件確實(shí)可以減少重復(fù)的文本處理工作,但形式上依然不夠清晰,這里就引入了LangChain中的關(guān)鍵概念:鏈(Chain)。
LangChain的表達(dá)式語言(LCEL[7])通過重載__or__
運(yùn)算符的思路,構(gòu)建了類似Unix管道運(yùn)算符的設(shè)計(jì),實(shí)現(xiàn)更簡潔的LLM調(diào)用形式。
代碼示例參考:
# 創(chuàng)建Chain
chain = prompt | llm | output_parser
# 調(diào)用Chain
answer = chain.invoke({'question': '什么是圖計(jì)算?'})
print(answer)
至此,我們終于看到了LangChain版的“HelloWorld”……
當(dāng)然,為了簡化Chain的參數(shù)調(diào)用格式,也可以借助RunnablePassthrough
透傳上游參數(shù)輸入。
代碼示例參考:
from langchain_core.runnables import RunnablePassthrough
# 創(chuàng)建Chain
chain = {"question": RunnablePassthrough()} | prompt | llm | output_parser
# 調(diào)用Chain
answer = chain.invoke('什么是圖計(jì)算?')
print(answer)
另外,Chain也可以分叉、合并,組合出更復(fù)雜的DAG計(jì)算圖結(jié)構(gòu)。
代碼示例參考:
from operator import itemgetter
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
# 創(chuàng)建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創(chuàng)建輸出解析器
output_parser = StrOutputParser()
# 創(chuàng)建Prompt
topic_prompt = ChatPromptTemplate.from_template("生成一種'{input}'的名稱")
good_prompt = ChatPromptTemplate.from_template("列舉{topic}的好處:")
bad_prompt = ChatPromptTemplate.from_template("列舉{topic}的壞處:")
summary_prompt = ChatPromptTemplate.from_messages(
[
("ai", "{topic}"),
("human", "好處:\n{good}\n\n壞處:\n{bad}"),
("system", "生成最終結(jié)論"),
]
)
# 創(chuàng)建組合Chain
topic_chain = topic_prompt | llm | output_parser | {"topic": RunnablePassthrough()}
goods_chain = good_prompt | llm | output_parser
bads_chain = bad_prompt | llm | output_parser
summary_chain = summary_prompt | llm | output_parser
chain = (
topic_chain
| {
"good": goods_chain,
"bad": bads_chain,
"topic": itemgetter("topic"),
}
| summary_chain
)
# 調(diào)用chain
answer = chain.invoke({"input": '常見水果'})
print(answer)
代碼示例輸出:
蘋果是一種營養(yǎng)豐富的水果,具有幫助消化、保護(hù)心臟、降低糖尿病風(fēng)險(xiǎn)、強(qiáng)化免疫系統(tǒng)、幫助減肥、保護(hù)視力、預(yù)防哮喘、抗癌和提升記憶力等多種好處。然而,過度食用或者不適當(dāng)?shù)氖秤梅绞揭部赡軒硪恍┎焕绊懀缫l(fā)過敏、導(dǎo)致腹瀉、對(duì)牙齒造成傷害、可能攜帶農(nóng)藥殘留、影響正常飲食和鈣質(zhì)吸收、增加蛀牙風(fēng)險(xiǎn)和引發(fā)胃痛等。因此,我們?cè)谙硎芴O果帶來的好處的同時(shí),也需要注意適量和正確的食用方式。
通過調(diào)用chain.get_graph().print_ascii()
可以查看Chain的計(jì)算圖結(jié)構(gòu)。當(dāng)然,使用LangSmith能更清晰的跟蹤每一步的計(jì)算結(jié)果。
Tips:開啟LangSmith需要申請(qǐng)LangChain的AK[8],并配置環(huán)境變量:
export LANGCHAIN_TRACING_V2=”true”
export LANGCHAIN_API_KEY=”<Your-LangChain-AK>”
基于LCEL確實(shí)能描述比較復(fù)雜的LangChain計(jì)算圖結(jié)構(gòu),但依然有DAG天然的設(shè)計(jì)限制,即不能支持“循環(huán)”。于是LangChain社區(qū)推出了一個(gè)新的項(xiàng)目——LangGraph[9],期望基于LangChain構(gòu)建支持循環(huán)和跨多鏈的計(jì)算圖結(jié)構(gòu),以描述更復(fù)雜的,甚至具備自動(dòng)化屬性的AI工程應(yīng)用邏輯,比如智能體應(yīng)用。其具體使用方式可以參考LangGraph文檔[10]。
LangGraph聲稱其設(shè)計(jì)理念受Pregel/Beam的啟發(fā),構(gòu)建支持多步迭代的計(jì)算能力,這部分設(shè)計(jì)理念和我們?cè)O(shè)計(jì)的支持“流/批/圖”計(jì)算一體化的圖計(jì)算引擎TuGraph也十分相似,感興趣的朋友可以訪問TuGraph Analytics[11]項(xiàng)目進(jìn)行學(xué)習(xí)。
通過Chain,LangChain相當(dāng)于以“工作流”的形式,將LLM與IO組件進(jìn)行了有秩序的連接,從而具備構(gòu)建復(fù)雜AI工程流程的能力。而我們都知道LLM提供的文本生成服務(wù)本身不提供記憶功能,需要用戶自己管理對(duì)話歷史。因此引入Memory組件[12],可以很好地?cái)U(kuò)展AI工程的能力邊界。
LangChain的BaseMemory
接口提供了Memory的統(tǒng)一抽象(截至v0.1.12還是Beta版本),提供了多種類型的Memory組件的實(shí)現(xiàn),我們選用最簡單的ConversationBufferMemory
實(shí)現(xiàn)類型。
需要注意的是,要將Memory組件應(yīng)用到Chain上,需要使用子類LLMChain
進(jìn)行創(chuàng)建Chain。
代碼示例參考:
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, \
HumanMessagePromptTemplate
from langchain_openai import ChatOpenAI
# 創(chuàng)建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創(chuàng)建Prompt
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name='chat_history'),
HumanMessagePromptTemplate.from_template('{question}')
])
# 創(chuàng)建Memory
memory = ConversationBufferMemory(memory_key='chat_history',
return_messages=True)
# 創(chuàng)建LLMChain
llm_chain = LLMChain(llm=llm, memory=memory, prompt=prompt)
# 調(diào)用LLMChain
print(llm_chain.predict(question='什么是圖計(jì)算?'))
print(llm_chain.predict(question='剛才我問了什么問題?'))
代碼示例輸出:
圖計(jì)算是一種計(jì)算類型,主要處理的數(shù)據(jù)結(jié)構(gòu)是圖。圖是由節(jié)點(diǎn)(或頂點(diǎn))和邊組成的,節(jié)點(diǎn)代表實(shí)體,邊代表實(shí)體之間的關(guān)系。在圖計(jì)算中,主要解決的問題是如何在圖的結(jié)構(gòu)上進(jìn)行有效的計(jì)算和分析。 你問的問題是:“什么是圖計(jì)算?”
這里可以看到,創(chuàng)建帶Memory功能的Chain,并不能使用統(tǒng)一的LCEL語法。調(diào)用LLMChain
使用的是predict而非invoke方法,直接調(diào)用invoke會(huì)返回一個(gè)LLMResult
類型的結(jié)果。因此,LLMChain
也不能使用管道運(yùn)算符接StrOutputParser
。這些設(shè)計(jì)上的問題,個(gè)人推測(cè)也是目前Memory模塊還是Beta版本的原因之一吧。
但是,LangChain提供了工具類RunnableWithMessageHistory
,支持了為Chain追加History的能力,從某種程度上緩解了上述問題。不過需要指定Lambda函數(shù)get_session_history以區(qū)分不同的會(huì)話,并需要在調(diào)用時(shí)通過config參數(shù)指定具體的會(huì)話ID。
SessionHistory必須是History接口類型BaseChatMessageHistory
,用戶可以根據(jù)需要選擇不同的存儲(chǔ)實(shí)現(xiàn)。這里為了簡化,全局只用了一份內(nèi)存類型的ChatMessageHistory
。
代碼示例參考:
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, \
HumanMessagePromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
# 創(chuàng)建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創(chuàng)建輸出解析器
output_parser = StrOutputParser()
# 創(chuàng)建Prompt
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name="chat_history"),
HumanMessagePromptTemplate.from_template("{question}")
])
# 創(chuàng)建Chain
chain = prompt | llm | output_parser
# 添加History
history = ChatMessageHistory()
chain_with_history = RunnableWithMessageHistory(
chain,
lambda session_id: history,
input_messages_key="question",
history_messages_key="chat_history",
)
# 調(diào)用Chain
print(chain_with_history.invoke({'question': '什么是圖計(jì)算?'},
config={"configurable": {"session_id": None}}))
print(chain_with_history.invoke({'question': '剛才我問了什么問題?'},
config={"configurable": {"session_id": None}}))
調(diào)用形式看起來是復(fù)雜了一些,不過代碼結(jié)構(gòu)相比Memory組件更清晰一些,聊勝于無……
擁有記憶后,確實(shí)擴(kuò)展了AI工程的應(yīng)用場(chǎng)景。但是在專有領(lǐng)域,LLM無法學(xué)習(xí)到所有的專業(yè)知識(shí)細(xì)節(jié),因此在面向?qū)I(yè)領(lǐng)域知識(shí)的提問時(shí),無法給出可靠準(zhǔn)確的回答,甚至?xí)昂詠y語”,這種現(xiàn)象稱之為LLM的“幻覺”。
檢索增強(qiáng)生成(RAG)把信息檢索技術(shù)和大模型結(jié)合起來,將檢索出來的文檔和提示詞一起提供給大模型服務(wù),從而生成更可靠的答案,有效的緩解大模型推理的“幻覺”問題。
如果說LangChain相當(dāng)于給LLM這個(gè)“大腦”安裝了“四肢和軀干”,RAG則是為LLM提供了接入“人類知識(shí)圖書館”的能力。
相比提示詞工程,RAG有更豐富的上下文和數(shù)據(jù)樣本,可以不需要用戶提供過多的背景描述,即能生成比較符合用戶預(yù)期的答案。相比于模型微調(diào),RAG可以提升問答內(nèi)容的時(shí)效性和可靠性,同時(shí)在一定程度上保護(hù)了業(yè)務(wù)數(shù)據(jù)的隱私性。
但由于每次問答都涉及外部系統(tǒng)數(shù)據(jù)檢索,因此RAG的響應(yīng)時(shí)延相對(duì)較高。另外,引用的外部知識(shí)數(shù)據(jù)會(huì)消耗大量的模型Token資源。因此,用戶需要結(jié)合自身的實(shí)際應(yīng)用場(chǎng)景做合適的技術(shù)選型。
借助LCEL提供的RunnableParallel
可以清晰描述RAG的計(jì)算圖結(jié)構(gòu),其中最關(guān)鍵的部分是通過context鍵注入向量存儲(chǔ)(Vector Store)的查詢器(Retriever)。
代碼示例參考:
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores.faiss import FAISS
from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
# 創(chuàng)建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創(chuàng)建Prompt
prompt = ChatPromptTemplate.from_template('基于上下文:{context}\n回答:{input}')
# 創(chuàng)建輸出解析器
output_parser = StrOutputParser()
# 模擬文檔
docs = [Document(page_content="TuGraph是螞蟻開源的圖數(shù)據(jù)庫產(chǎn)品")]
# 文檔嵌入
splits = RecursiveCharacterTextSplitter().split_documents(docs)
vector_store = FAISS.from_documents(splits, OpenAIEmbeddings())
retriever = vector_store.as_retriever()
# 創(chuàng)建Chain
chain_no_context = RunnablePassthrough() | llm | output_parser
chain = (
{"context": retriever, "input": RunnablePassthrough()}
| prompt | llm | output_parser
)
# 調(diào)用Chain
print(chain_no_context.invoke('螞蟻圖數(shù)據(jù)庫開源了嗎?'))
print(chain.invoke('螞蟻圖數(shù)據(jù)庫開源了嗎?'))
代碼示例輸出:
螞蟻圖數(shù)據(jù)庫目前沒有公開信息表明已經(jīng)開源。開源狀態(tài)可能會(huì)隨時(shí)間和公司政策變化,建議直接查閱螞蟻集團(tuán)或相關(guān)開源平臺(tái)的官方信息獲取最新和準(zhǔn)確的消息。 是的,螞蟻的圖數(shù)據(jù)庫產(chǎn)品TuGraph是開源的。
結(jié)合示例和向量數(shù)據(jù)庫的存取過程,我們簡單理解一下RAG中關(guān)鍵組件。
? DocumentLoader:從外部系統(tǒng)檢索文檔數(shù)據(jù)。簡單起見,示例中直接構(gòu)造了測(cè)試文檔對(duì)象。實(shí)際上LangChain提供了文檔加載器BaseLoader
的接口抽象和大量實(shí)現(xiàn),具體可根據(jù)自身需要選擇使用。
? TextSplitter:將文檔分割成塊,以適應(yīng)大模型上下文窗口。示例中采用了常用的RecursiveCharacterTextSplitter
,其他參考LangChain的TextSplitter
接口和實(shí)現(xiàn)。
? EmbeddingsModel:文本嵌入模型,提供將文本編碼為向量的能力。文檔寫入和查詢匹配前都會(huì)先執(zhí)行文本嵌入編碼。示例采用了OpenAI的文本嵌入模型服務(wù)[13],其他參考LangChain的Embeddings
接口和實(shí)現(xiàn)。
? VectorStore:向量存儲(chǔ),提供向量存儲(chǔ)和相似性檢索(ANN算法)能力。LangChain支持的向量存儲(chǔ)參考VectorStore
接口和實(shí)現(xiàn)。示例采用了Meta的Faiss[14]向量數(shù)據(jù)庫,本地安裝方式:pip install faiss-cpu
。需要額外提及的是,對(duì)于圖數(shù)據(jù)庫,可以將相似性搜索問題轉(zhuǎn)化為圖遍歷問題,并具備更強(qiáng)的知識(shí)可解釋性。螞蟻開源的TuGraph數(shù)據(jù)庫[15]目前正在做類似的技術(shù)探索。
? Retriever:向量存儲(chǔ)的查詢器。一般和VectorStore配套實(shí)現(xiàn),通過as_retriever方法獲取,LangChain提供的Retriever抽象接口是BaseRetriever
。
“會(huì)使用工具”是人類和動(dòng)物的根本區(qū)別。
要構(gòu)建更強(qiáng)大的AI工程應(yīng)用,只有生成文本這樣的“紙上談兵”能力自然是不夠的。工具不僅僅是“肢體”的延伸,更是為“大腦”插上了想象力的“翅膀”。借助工具,才能讓AI應(yīng)用的能力真正具備無限的可能,才能從“認(rèn)識(shí)世界”走向“改變世界”。
這里不得不提到OpenAI的Chat Completion API提供的函數(shù)調(diào)用[16]能力(注意這里不是Assistant的函數(shù)調(diào)用[17]),通過在對(duì)話請(qǐng)求內(nèi)附加tools參數(shù)描述工具的定義格式(原先的functions參數(shù)已過期),LLM會(huì)根據(jù)提示詞推斷出需要調(diào)用哪些工具,并提供具體的調(diào)用參數(shù)信息。用戶需要根據(jù)返回的工具調(diào)用信息,自行觸發(fā)相關(guān)工具的回調(diào)。下一章內(nèi)容我們可以看到工具的調(diào)用動(dòng)作可以通過Agent自主接管。
為了簡化代碼實(shí)現(xiàn),我們用LangChain的注解@tool[18]定義了一個(gè)測(cè)試用的“獲取指定城市的當(dāng)前氣溫”的工具函數(shù)。然后通過bind_tools
方法綁定到LLM對(duì)象即可。需要注意的是這里需要用JsonOutputToolsParser
解析結(jié)果輸出。
代碼示例參考:
import random
from langchain_core.output_parsers.openai_tools import JsonOutputToolsParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
# 定義Tool
@tool
def get_temperature(city: str) -> int:
"""獲取指定城市的當(dāng)前氣溫"""
return random.randint(-20, 50)
# 創(chuàng)建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創(chuàng)建JSON輸出解析器
output_parser = JsonOutputToolsParser()
# 創(chuàng)建Chain
chain = (
RunnablePassthrough()
| llm.bind_tools(tools=[get_temperature])
| output_parser
)
# 調(diào)用Chain
print(chain.invoke('杭州今天多少度?'))
代碼示例輸出:
[{‘type’: ‘get_temperature’, ‘args’: {‘city’: ‘杭州’}}]
實(shí)際上LangChain提供了大量的內(nèi)置工具和工具庫的支持。@tool只是提供了簡潔的工具創(chuàng)建的支持,要定制復(fù)雜的工具行為需要自行實(shí)現(xiàn)BaseTool
工具接口。同時(shí)工具庫接口BaseToolkit
下也有大量的實(shí)現(xiàn),如向量存儲(chǔ)、SQL數(shù)據(jù)庫、GitHub等等。用戶可以根據(jù)自身需求選用或自行擴(kuò)展。
通用人工智能(AGI)將是AI的終極形態(tài),幾乎已成為業(yè)界共識(shí)。類比之,構(gòu)建智能體(Agent)則是AI工程應(yīng)用當(dāng)下的“終極形態(tài)”。
引用LangChain中Agent的定義,可以一窺Agent與Chain的區(qū)別。
Agent的核心思想是使用大型語言模型(LLM)來選擇要采取的行動(dòng)序列。在Chain中行動(dòng)序列是硬編碼的,而Agent則采用語言模型作為推理引擎來確定以什么樣的順序采取什么樣的行動(dòng)。
Agent相比Chain最典型的特點(diǎn)是“自治”,它可以通過借助LLM專長的推理能力,自動(dòng)化地決策獲取什么樣的知識(shí),采取什么樣的行動(dòng),直到完成用戶設(shè)定的最終目標(biāo)。
因此,作為一個(gè)智能體,需要具備以下核心能力:
? 規(guī)劃:借助于LLM強(qiáng)大的推理能力,實(shí)現(xiàn)任務(wù)目標(biāo)的規(guī)劃拆解和自我反思。
? 記憶:具備短期記憶(上下文)和長期記憶(向量存儲(chǔ)),以及快速的知識(shí)檢索能力。
? 行動(dòng):根據(jù)拆解的任務(wù)需求正確地調(diào)用工具以達(dá)到任務(wù)的目的。
? 協(xié)作:通過與其他智能體交互合作,完成更復(fù)雜的任務(wù)目標(biāo)。
我們使用Agent繼續(xù)完成前邊Tool部分沒有完成的例子。這里使用create_openai_tools_agent方法創(chuàng)建一個(gè)簡單的OpenAI工具Agent,AgentExecutor會(huì)自動(dòng)接管工具調(diào)用的動(dòng)作。如果希望給Agent添加記憶能力,依然可以采用前邊Memory章節(jié)提過的RunnableWithMessageHistory的方案[19]。
代碼示例參考:
import random
from langchain.agents import create_openai_tools_agent, \
AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, \
HumanMessagePromptTemplate, SystemMessagePromptTemplate
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
# 創(chuàng)建LLM
llm = ChatOpenAI()
# 定義Tool
@tool
def get_temperature(city: str) -> int:
"""獲取指定城市的當(dāng)前氣溫"""
return random.randint(-20, 50)
# 創(chuàng)建Agent提示詞模板
prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template('You are a helpful assistant'),
MessagesPlaceholder(variable_name='chat_history', optional=True),
HumanMessagePromptTemplate.from_template('{input}'),
MessagesPlaceholder(variable_name='agent_scratchpad')
])
# 創(chuàng)建Agent
tools = [get_temperature]
agent = create_openai_tools_agent(llm, tools, prompt=prompt)
# 執(zhí)行Agent
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
print(agent_executor.invoke({'input': '今天杭州多少度?'})['output'])
代碼示例輸出:
> Entering new AgentExecutor chain… Invoking:
get_temperature
with{'city': 'Hangzhou'}
16 今天杭州的氣溫是16度。> Finished chain. 今天杭州的氣溫是16度。
需要補(bǔ)充說明的是,LangChain提供了Hub[20]功能,幫助大家管理共享Agent的提示詞模板。上述示例代碼的Agent提示詞模板和hwchase17/openai-tools-agent[21]的定義等價(jià)。
通過代碼prompt = hub.pull("hwchase17/openai-tools-agent")
可以直接引用創(chuàng)建prompt。
從API到Agent,我們“腦暴”了一個(gè)完整AI工程應(yīng)用的發(fā)展軌跡,借此我相信大家對(duì)LangChain的設(shè)計(jì)理念應(yīng)該有了進(jìn)一步的理解。
最后,我們?cè)倏匆谎跮angChain的產(chǎn)品架構(gòu)。除了本文未介紹的LangServe——將Chain部署為RESTful服務(wù),其他不再贅述。
可能會(huì)有小伙伴疑問,為啥我一個(gè)搞圖計(jì)算的,在這搗鼓起AI工具來了。拋開“擁抱行業(yè),跟進(jìn)趨勢(shì)”這樣的大口號(hào)不談,單純從工作需要角度,使用AI工具加速內(nèi)容的產(chǎn)出,本身就可以幫助我節(jié)省大量的時(shí)間和精力,空出的“閑暇”時(shí)間去帶娃也是極好的……
線上關(guān)于LangChain的文章,雖不說汗牛充棟,但也能隨手拈來。作為“后入場(chǎng)”者,我過去從事了多年的數(shù)倉、中臺(tái)、云產(chǎn)品的架構(gòu)和研發(fā),最近兩三年一直在搞圖計(jì)算這樣的基礎(chǔ)軟件,就想試試結(jié)合自身多面的工程經(jīng)驗(yàn)去理解LangChain背后的設(shè)計(jì)理念,給大家一個(gè)別樣的視角去觀察、體驗(yàn)和思考AI工程。這兩天被“AI程序員”的新聞刷屏了,其中引用的Adam Rackis的一條Twitter令我感受頗深:“做好磨練和深入學(xué)習(xí)的準(zhǔn)備,熟練地引導(dǎo)人工智能取得好的結(jié)果可能才是未來程序員能體現(xiàn)出來的價(jià)值。”,或許這正是AI工程的用武之地吧。
[1]
https://www.python.org/downloads: https://www.python.org/downloads/[2]
Chat Completion API: https://platform.openai.com/docs/api-reference/chat[3]
Completion API: https://platform.openai.com/docs/api-reference/completions[4]
OpenAI開發(fā)文檔: https://platform.openai.com/docs[5]
Prompt: https://python.langchain.com/docs/modules/model_io/prompts/[6]
OutputParser: https://python.langchain.com/docs/modules/model_io/output_parsers/[7]
LCEL: https://python.langchain.com/docs/expression_language/[8]
AK: https://smith.langchain.com/settings[9]
LangGraph: https://github.com/langchain-ai/langgraph[10]
LangGraph文檔: https://python.langchain.com/docs/langgraph[11]
TuGraph Analytics: https://github.com/TuGraph-family/tugraph-analytics[12]
Memory組件: https://python.langchain.com/docs/modules/memory/[13]
文本嵌入模型服務(wù): https://platform.openai.com/docs/guides/embeddings[14]
Faiss: https://github.com/facebookresearch/faiss[15]
TuGraph數(shù)據(jù)庫: https://github.com/TuGraph-family/tugraph-db[16]
函數(shù)調(diào)用: https://platform.openai.com/docs/guides/function-calling[17]
Assistant的函數(shù)調(diào)用: https://platform.openai.com/docs/assistants/tools/function-calling[18]
@tool: https://python.langchain.com/docs/modules/agents/tools/custom_tools#tool-decorator[19]
RunnableWithMessageHistory的方案: https://python.langchain.com/docs/modules/agents/quick_start#adding-in-memory[20]
Hub: https://smith.langchain.com/hub/[21]
hwchase17/openai-tools-agent: https://smith.langchain.com/hub/hwchase17/openai-tools-agent
文章轉(zhuǎn)自微信公眾號(hào)@TuGraph
GraphRAG:基于PolarDB+通義千問api+LangChain的知識(shí)圖譜定制實(shí)踐
使用Node.js、Express和MySQL構(gòu)建REST API
天氣API推薦:精準(zhǔn)獲取氣象數(shù)據(jù)的首選
基于自定義數(shù)據(jù)集的微調(diào):Alpaca與LLaMA模型的訓(xùn)練
OAuth和OpenID Connect圖解指南
有哪些新聞媒體提供Open API?
現(xiàn)在做大模型,還有靠譜且免費(fèi)的API接口嗎?
如何運(yùn)用AI提高自己的工作效率?
區(qū)塊鏈API推薦,快速開發(fā)去中心化應(yīng)用
對(duì)比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力
一鍵對(duì)比試用API 限時(shí)免費(fèi)