從 API 到 Agent:萬字長文洞悉 LangChain 工程化設計
本文作者試着從工程角度去理解 LangChain 的設計和使用。大家可以將此文檔作爲 LangChain 的 “10 分鐘快速上手” 手冊,希望幫助需要的同學實現 AI 工程的 Bootstrap。
我想做一個嘗試,看看能不能用盡量清晰的邏輯,給 “AI 外行人士”(當然,我也是)引入一下 LangChain,試着從工程角度去理解 LangChain 的設計和使用。同時大家也可以將此文檔作爲 LangChain 的“10 分鐘快速上手” 手冊,本意是希望幫助需要的同學實現 AI 工程的 Bootstrap。
文中所有的示例代碼都已託管到 GitHub:https://github.com/TuGraph-contrib/langchain-demo,喜歡實操的小夥伴可以邊閱讀,邊復現。
一、引言
1.1 什麼是 LangChain?
正式開始前,還是有必要從定義(What)開始。LangChain 是 2022 年 10 月底,由哈佛大學的 Harrison Chase 發起的基於開源大語言模型的 AI 工程開發框架。當然也可以問一下 AI:
我:LangChain 是什麼?
GPT-4:LangChain 是一個開源的語言模型工具鏈框架,旨在使研究人員和開發人員能夠更輕鬆地構建、實驗和部署以自然語言處理(NLP)爲中心的應用程序。它提供了多種組件和工具,可幫助用戶利用最近的語言模型進展,如大型 Transformer 模型等,並且可以與 Hugging Face 等平臺集成。LangChain 的核心理念是將語言模型用作協作工具,通過它,開發者可以構建出處理複雜任務的系統,並且可以高效地對接不同的數據源和應用程序接口(APIs)。這個框架試圖簡化連接不同的輸入和輸出流,以及在流中實現語言模型的過程。
顧名思義,LangChain 中的 “Lang” 自然是指大語言模型,“Chain”即“鏈”,也就是將大模型與其他組件連接成鏈,藉此構建 AI 工程應用。那麼 LangChain 該如何(How)做到這一點的呢?解答這個問題之前,需要先回答什麼是工程?什麼是 AI 工程?
1.2 什麼是 AI 工程?
我們先 Review 一下 “工程” 的百科定義:
工程是指以某組設想的目標爲依據,應用有關的科學知識和技術手段,通過有組織的一羣人將某個(或某些)現有實體(自然的或人造的)轉化爲具有預期使用價值的人造產品過程。
其中,“目標”定義了要解決的問題域,決定了工程的頂層設計和能力邊界,同時也定義了 “產品” 的最終形態。提升 “人” 的 ROI 是工程設計的價值歸屬。“實體”是工程的生產材料輸入,“科學 | 技術”是工程有序運行的基礎,對它們的合理利用可以提升工程的整體產出效率。
“工程” 關鍵概念剖析
於是,我們可以這樣解讀 “AI 工程” 中的關鍵概念:
-
目標: 待解決的特定 AI 用戶需求。如內容創作、智能問答、文檔摘要、圖像識別等等。
-
人: 實施 AI 工程的具體角色。可以是程序員,或者 AI 應用的研發團隊、創業公司。
-
科學 | 技術: 顯然是大模型與相關工具服務,以及其後的計算科學理論。
-
實體: 已有的文檔、知識庫、業務數據等生產材料。
-
產品: 能滿足目標需求的具體產品。如聊天機器人、內容生成工具等。
1.3 如何設計 LangChain?
因此,如果我們是 LangChain 的設計者,希望構建通用的 AI 工程框架。需要回答如下問題:
-
【目標 | 產品】 LangChain 的設計目標是什麼,能解決哪些 AI 工程問題?
-
【人】 LangChain 的編程接口如何定義,才能提升 AI 工程師的研發效率?
-
【實體 | 科學 | 技術】 LangChain 的核心組件如何抽象,以提升框架的擴展能力?
當然,作爲 “事後諸葛”,這些問題目前有比較明確的答案:
-
作爲 AI 工程框架,LangChain 實際是對 LLM 能力的擴展和補充。如果把 LLM 比作人的大腦,LangChain 則是人的軀幹和四肢,協助 LLM 完成 “思考” 之外的“髒活累活”。它的能力邊界只取決於 LLM 的智力水平和 LangChain 能提供的工具集的豐富程度。
-
LangChain 提供了 LCEL(LangChain Expression Language)聲明式編程語言,降低 AI 工程師的研發成本。
-
LangChain 提供了 Models、Prompts、Indexes、Memory、Chains、Agents 六大核心抽象,用於構建複雜的 AI 應用,同時保持了良好的擴展能力。
很明顯,LLM 作爲 LangChain 能力的基礎,是瞭解 LangChain 工程化設計的前提。接下來我們就從最基礎的 LLM API 使用談起,一步步瞭解 LangChain 的工程化構建過程及其背後的設計理念。
二、環境準備
-
Python 環境: 建議 3.8 版本以上。
- 下載鏈接:https://www.python.org/downloads[1]
-
OpenAI SK: 自備。
-
申請地址:https://platform.openai.com/api-keys
-
環境變量:export OPENAI_API_KEY=""
-
-
安裝 LangChain:
- 執行命令:pip install langchain langchain-openai
三、設計推演
架構設計領域有個比較流行的術語——樂高架構,當然也可以叫可插拔架構。說白就是通過對系統基本組件的合理抽象,找到構造複雜系統的統一規律和可達路徑,從而實現在降低系統實現複雜度的同時,提升系統整體的擴展性。(非官方表達,大家能 Get 到我的意思就好……)
LangChain 實際上也遵循了樂高架構的思想。當然,作爲最關鍵的樂高組件之一,LLM 的能力自然是我們優先了解的對象,那我們就從 OpenAI 的 API 開始吧!
3.1 造夢基礎——API
文本生成模型服務是 OpenAI 提供的最核心的 API 服務,自 ChatGPT 發佈後經歷過幾次版本迭代。
3.1.1 Chat Completion API
當下最新的是 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}'
}
# 準備數據
data = {
'model': 'gpt-4',
'messages': [{'role': 'user', 'content': '什麼是圖計算?'}],
'temperature': 0.7
}
# 調用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)
代碼示例輸出:
圖計算是一種計算模型,用於處理大規模圖形結構的數據,並執行各種複雜的算法和計算。這種計算模型主要用於社交網絡分析、Web 搜索、生物信息學、網絡路由優化、數據挖掘等領域。圖計算模型的核心是將數據表示爲圖形結構(節點和邊),這樣可以更好地揭示數據之間的關係和互動。在圖計算中,算法通常以迭代的方式運行,每次迭代都會更新圖中節點的狀態,直到達到某種停止條件。
3.1.2 Completion API
早先的 Completion API[3] 已經在 2023 年 7 月後不再維護,和最新的 Chat Completion API 參數和結果格式有所不同,最明顯的是 Prompt 是以純文本方式傳遞,而非 Message 格式。
# 準備數據
data = {
'model': 'gpt-3.5-turbo-instruct',
'prompt': ['什麼是圖計算?'],
'max_tokens': 1024
}
# 調用API
url = 'https://api.openai.com/v1/completions'
response = requests.post(url, json=data, headers=headers)
answer = response.json()['choices'][0]['text']
print(answer)
除了文本生成服務,OpenAI 也提供了大量的 LLM 的周邊服務,以協助 AI 工程構建更復雜的應用能力。如:函數調用、嵌入、微調、多模態等,具體可參考 OpenAI 開發文檔 [4] 的內容。
3.2 智能開端——Chat
自 2022 年 11 月底 ChatGPT 發佈以來,AI 的大門才真正地向人類打開,其中給用戶留下最深印象的功能,自然是智能對話。OpenAI 的 Chat Completion API 參數支持傳入消息歷史,可以輕鬆地實現簡單的對話服務。
代碼示例參考:
# 對話歷史
messages = []
def chat_with_ai(message):
# 記錄歷史
messages.append({'role': 'user', 'content': message})
print(f'me: {message}')
# 對話請求
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())
# 多輪對話
chat_with_ai('什麼是圖計算?')
chat_with_ai('剛纔我問了什麼問題?')
代碼示例輸出:
me: 什麼是圖計算?ai: 圖計算是一種計算模型,用於處理大規模圖形結構數據的計算和分析。在這種計算模型中,數據被表示爲圖形,其中節點代表實體,邊代表實體之間的關係。圖計算可以用於解決許多實際問題,如社交網絡分析、網絡路由、生物信息學等。圖計算的主要挑戰是如何有效地處理大規模的圖形數據,並提供快速的計算和分析結果。me: 剛纔我問了什麼問題?ai: 你問的問題是:“什麼是圖計算?”
3.3 初步封裝——SDK
到目前爲止,我們還只是用 OpenAI 最原始的 RESTful API 構建 LLM 工程能力,甚至連 OpenAI 提供的 SDK 都未使用。顯然這不是一個高效的方式,使用前邊安裝的 LangChain-OpenAI 集成包 langchain-openai 可以大大降低代碼的開發成本。
代碼示例參考:
from langchain_openai import ChatOpenAI
# 調用Chat Completion API
llm = ChatOpenAI(model_name='gpt-4')
response = llm.invoke('什麼是圖計算?')
print(response)
代碼示例輸出:
content='圖計算是一種計算模型,主要用於處理圖形結構數據的計算和分析。圖計算的對象是圖,圖由節點和邊組成,節點代表實體對象,邊代表實體對象之間的關係。圖計算主要用於解決實體關係複雜、關係密集的問題,如社交網絡分析、網絡拓撲分析、推薦系統等。圖計算的主要任務是通過對圖中節點和邊的計算,發現和提取出圖中隱含的知識和信息。'
3.4 數據抽象——IO
對於文本生成模型服務來說,實際的輸入和輸出本質上都是字符串,因此直接裸調用 LLM 服務帶來的問題是要在輸入格式化和輸出結果解析上做大量的重複的文本處理工作。LangChain 當然考慮到這一點,提供了 Prompt[5] 和 OutputParser[6] 抽象,用戶可以根據自己的需要選擇具體的實現類型使用。
IO 數據抽象示意
代碼示例參考:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
# 創建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創建Prompt
prompt = ChatPromptTemplate.from_template("{question}")
# 創建輸出解析器
output_parser = StrOutputParser()
# 調用LLM
message = prompt.invoke({'question': '什麼是圖計算?'})
response = llm.invoke(message)
answer = output_parser.invoke(response)
print(answer)
3.5 組裝成鏈——Chain
模型的 IO 組件確實可以減少重複的文本處理工作,但形式上依然不夠清晰,這裏就引入了 LangChain 中的關鍵概念:鏈(Chain)。
3.5.1 HelloWorld
LangChain 的表達式語言(LCEL[7])通過重載__or__運算符的思路,構建了類似 Unix 管道運算符的設計,實現更簡潔的 LLM 調用形式。
代碼示例參考:
# 創建Chain
chain = prompt | llm | output_parser
# 調用Chain
answer = chain.invoke({'question': '什麼是圖計算?'})
print(answer)
至此,我們終於看到了 LangChain 版的 “HelloWorld”……
3.5.2 RunnablePassthrough
當然,爲了簡化 Chain 的參數調用格式,也可以藉助 RunnablePassthrough 透傳上游參數輸入。
代碼示例參考:
from langchain_core.runnables import RunnablePassthrough
# 創建Chain
chain = {"question": RunnablePassthrough()} | prompt | llm | output_parser
# 調用Chain
answer = chain.invoke('什麼是圖計算?')
print(answer)
3.5.3 DAG
另外,Chain 也可以分叉、合併,組合出更復雜的 DAG 計算圖結構。
代碼示例參考:
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
# 創建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創建輸出解析器
output_parser = StrOutputParser()
# 創建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", "生成最終結論"),
]
)
# 創建組合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
)
# 調用chain
answer = chain.invoke({"input": '常見水果'})
print(answer)
代碼示例輸出:
蘋果是一種營養豐富的水果,具有幫助消化、保護心臟、降低糖尿病風險、強化免疫系統、幫助減肥、保護視力、預防哮喘、抗癌和提升記憶力等多種好處。然而,過度食用或者不適當的食用方式也可能帶來一些不利影響,如引發過敏、導致腹瀉、對牙齒造成傷害、可能攜帶農藥殘留、影響正常飲食和鈣質吸收、增加蛀牙風險和引發胃痛等。因此,我們在享受蘋果帶來的好處的同時,也需要注意適量和正確的食用方式。
通過調用 chain.get_graph().print_ascii() 可以查看 Chain 的計算圖結構。當然,使用 LangSmith 能更清晰的跟蹤每一步的計算結果。
LangChain 的計算圖與 Trace
Tips:開啓 LangSmith 需要申請 LangChain 的 AK[8],並配置環境變量:
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY=""
3.5.4 LangGraph
基於 LCEL 確實能描述比較複雜的 LangChain 計算圖結構,但依然有 DAG 天然的設計限制,即不能支持 “循環”。於是 LangChain 社區推出了一個新的項目——LangGraph[9],期望基於 LangChain 構建支持循環和跨多鏈的計算圖結構,以描述更復雜的,甚至具備自動化屬性的 AI 工程應用邏輯,比如智能體應用。其具體使用方式可以參考 LangGraph 文檔 [10]。
LangGraph 聲稱其設計理念受 Pregel/Beam 的啓發,構建支持多步迭代的計算能力,這部分設計理念和我們設計的支持 “流 / 批 / 圖” 計算一體化的圖計算引擎 TuGraph 也十分相似,感興趣的朋友可以訪問 TuGraph Analytics[11]項目進行學習。
3.6 開啓記憶——Memory
通過 Chain,LangChain 相當於以 “工作流” 的形式,將 LLM 與 IO 組件進行了有秩序的連接,從而具備構建複雜 AI 工程流程的能力。而我們都知道 LLM 提供的文本生成服務本身不提供記憶功能,需要用戶自己管理對話歷史。因此引入 Memory 組件[12],可以很好地擴展 AI 工程的能力邊界。
帶記憶的問答處理
3.6.1 Memory 接口
LangChain 的 BaseMemory 接口提供了 Memory 的統一抽象(截至 v0.1.12 還是 Beta 版本),提供了多種類型的 Memory 組件的實現,我們選用最簡單的 ConversationBufferMemory 實現類型。
需要注意的是,要將 Memory 組件應用到 Chain 上,需要使用子類 LLMChain 進行創建 Chain。
代碼示例參考:
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, \
HumanMessagePromptTemplate
from langchain_openai import ChatOpenAI
# 創建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創建Prompt
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name='chat_history'),
HumanMessagePromptTemplate.from_template('{question}')
])
# 創建Memory
memory = ConversationBufferMemory(memory_key='chat_history',
return_messages=True)
# 創建LLMChain
llm_chain = LLMChain(llm=llm, memory=memory, prompt=prompt)
# 調用LLMChain
print(llm_chain.predict(question='什麼是圖計算?'))
print(llm_chain.predict(question='剛纔我問了什麼問題?'))
代碼示例輸出:
圖計算是一種計算類型,主要處理的數據結構是圖。圖是由節點(或頂點)和邊組成的,節點代表實體,邊代表實體之間的關係。在圖計算中,主要解決的問題是如何在圖的結構上進行有效的計算和分析。你問的問題是:“什麼是圖計算?”
這裏可以看到,創建帶 Memory 功能的 Chain,並不能使用統一的 LCEL 語法。調用 LLMChain 使用的是 predict 而非 invoke 方法,直接調用 invoke 會返回一個 LLMResult 類型的結果。因此,LLMChain 也不能使用管道運算符接 StrOutputParser。這些設計上的問題,個人推測也是目前 Memory 模塊還是 Beta 版本的原因之一吧。
3.6.2 History 接口
但是,LangChain 提供了工具類 RunnableWithMessageHistory,支持了爲 Chain 追加 History 的能力,從某種程度上緩解了上述問題。不過需要指定 Lambda 函數 get_session_history 以區分不同的會話,並需要在調用時通過 config 參數指定具體的會話 ID。
SessionHistory 必須是 History 接口類型 BaseChatMessageHistory,用戶可以根據需要選擇不同的存儲實現。這裏爲了簡化,全局只用了一份內存類型的 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
# 創建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創建輸出解析器
output_parser = StrOutputParser()
# 創建Prompt
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name="chat_history"),
HumanMessagePromptTemplate.from_template("{question}")
])
# 創建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",
)
# 調用Chain
print(chain_with_history.invoke({'question': '什麼是圖計算?'},
config={"configurable": {"session_id": None}}))
print(chain_with_history.invoke({'question': '剛纔我問了什麼問題?'},
config={"configurable": {"session_id": None}}))
調用形式看起來是複雜了一些,不過代碼結構相比 Memory 組件更清晰一些,聊勝於無……
3.7 消除幻覺——RAG
擁有記憶後,確實擴展了 AI 工程的應用場景。但是在專有領域,LLM 無法學習到所有的專業知識細節,因此在面向專業領域知識的提問時,無法給出可靠準確的回答,甚至會 “胡言亂語”,這種現象稱之爲 LLM 的 “幻覺”。
檢索增強生成(RAG)把信息檢索技術和大模型結合起來,將檢索出來的文檔和提示詞一起提供給大模型服務,從而生成更可靠的答案,有效的緩解大模型推理的 “幻覺” 問題。
如果說 LangChain 相當於給 LLM 這個 “大腦” 安裝了 “四肢和軀幹”,RAG 則是爲 LLM 提供了接入“人類知識圖書館” 的能力。
基於 RAG 的問答處理流程
相比提示詞工程,RAG 有更豐富的上下文和數據樣本,可以不需要用戶提供過多的背景描述,即能生成比較符合用戶預期的答案。相比於模型微調,RAG 可以提升問答內容的時效性和可靠性,同時在一定程度上保護了業務數據的隱私性。
但由於每次問答都涉及外部系統數據檢索,因此 RAG 的響應時延相對較高。另外,引用的外部知識數據會消耗大量的模型 Token 資源。因此,用戶需要結合自身的實際應用場景做合適的技術選型。
大語言模型優化技術
藉助 LCEL 提供的 RunnableParallel 可以清晰描述 RAG 的計算圖結構,其中最關鍵的部分是通過 context 鍵注入向量存儲(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
# 創建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創建Prompt
prompt = ChatPromptTemplate.from_template('基於上下文:{context}\n回答:{input}')
# 創建輸出解析器
output_parser = StrOutputParser()
# 模擬文檔
docs = [Document(page_content="TuGraph是螞蟻開源的圖數據庫產品")]
# 文檔嵌入
splits = RecursiveCharacterTextSplitter().split_documents(docs)
vector_store = FAISS.from_documents(splits, OpenAIEmbeddings())
retriever = vector_store.as_retriever()
# 創建Chain
chain_no_context = RunnablePassthrough() | llm | output_parser
chain = (
{"context": retriever, "input": RunnablePassthrough()}
| prompt | llm | output_parser
)
# 調用Chain
print(chain_no_context.invoke('螞蟻圖數據庫開源了嗎?'))
print(chain.invoke('螞蟻圖數據庫開源了嗎?'))
代碼示例輸出:
螞蟻圖數據庫目前沒有公開信息表明已經開源。開源狀態可能會隨時間和公司政策變化,建議直接查閱螞蟻集團或相關開源平臺的官方信息獲取最新和準確的消息。是的,螞蟻的圖數據庫產品 TuGraph 是開源的。
向量存儲的寫入與查詢
結合示例和向量數據庫的存取過程,我們簡單理解一下 RAG 中關鍵組件。
-
DocumentLoader: 從外部系統檢索文檔數據。簡單起見,示例中直接構造了測試文檔對象。實際上 LangChain 提供了文檔加載器 BaseLoader 的接口抽象和大量實現,具體可根據自身需要選擇使用。
-
TextSplitter: 將文檔分割成塊,以適應大模型上下文窗口。示例中採用了常用的 RecursiveCharacterTextSplitter,其他參考 LangChain 的 TextSplitter 接口和實現。
-
EmbeddingsModel: 文本嵌入模型,提供將文本編碼爲向量的能力。文檔寫入和查詢匹配前都會先執行文本嵌入編碼。示例採用了 OpenAI 的文本嵌入模型服務 [13],其他參考 LangChain 的 Embeddings 接口和實現。
-
VectorStore: 向量存儲,提供向量存儲和相似性檢索(ANN 算法)能力。LangChain 支持的向量存儲參考 VectorStore 接口和實現。示例採用了 Meta 的 Faiss[14] 向量數據庫,本地安裝方式:pip install faiss-cpu。需要額外提及的是,對於圖數據庫,可以將相似性搜索問題轉化爲圖遍歷問題,並具備更強的知識可解釋性。螞蟻開源的 TuGraph 數據庫 [15] 目前正在做類似的技術探索。
-
Retriever: 向量存儲的查詢器。一般和 VectorStore 配套實現,通過 as_retriever 方法獲取,LangChain 提供的 Retriever 抽象接口是 BaseRetriever。
3.8 使用工具——Tool
“會使用工具” 是人類和動物的根本區別。
要構建更強大的 AI 工程應用,只有生成文本這樣的 “紙上談兵” 能力自然是不夠的。工具不僅僅是 “肢體” 的延伸,更是爲 “大腦” 插上了想象力的 “翅膀”。藉助工具,才能讓 AI 應用的能力真正具備無限的可能,才能從“認識世界” 走向“改變世界”。
這裏不得不提到 OpenAI 的 Chat Completion API 提供的函數調用 [16] 能力(注意這裏不是 Assistant 的函數調用[17]),通過在對話請求內附加 tools 參數描述工具的定義格式(原先的 functions 參數已過期),LLM 會根據提示詞推斷出需要調用哪些工具,並提供具體的調用參數信息。用戶需要根據返回的工具調用信息,自行觸發相關工具的回調。下一章內容我們可以看到工具的調用動作可以通過 Agent 自主接管。
LLM Tools 執行流程示意
爲了簡化代碼實現,我們用 LangChain 的註解 @tool[18]定義了一個測試用的 “獲取指定城市的當前氣溫” 的工具函數。然後通過 bind_tools 方法綁定到 LLM 對象即可。需要注意的是這裏需要用 JsonOutputToolsParser 解析結果輸出。
代碼示例參考:
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:
"""獲取指定城市的當前氣溫"""
return random.randint(-20, 50)
# 創建LLM
llm = ChatOpenAI(model_name='gpt-4')
# 創建JSON輸出解析器
output_parser = JsonOutputToolsParser()
# 創建Chain
chain = (
RunnablePassthrough()
| llm.bind_tools(tools=[get_temperature])
| output_parser
)
# 調用Chain
print(chain.invoke('杭州今天多少度?'))
代碼示例輸出:
[{'type': 'get_temperature', 'args': {'city': '杭州'}}]
實際上 LangChain 提供了大量的內置工具和工具庫的支持。@tool 只是提供了簡潔的工具創建的支持,要定製複雜的工具行爲需要自行實現 BaseTool 工具接口。同時工具庫接口 BaseToolkit 下也有大量的實現,如向量存儲、SQL 數據庫、GitHub 等等。用戶可以根據自身需求選用或自行擴展。
3.9 走向智能——Agent
通用人工智能(AGI)將是 AI 的終極形態,幾乎已成爲業界共識。類比之,構建智能體(Agent)則是 AI 工程應用當下的 “終極形態”。
3.9.1 什麼是 Agent?
引用 LangChain 中 Agent 的定義,可以一窺 Agent 與 Chain 的區別。
Agent 的核心思想是使用大型語言模型(LLM)來選擇要採取的行動序列。在 Chain 中行動序列是硬編碼的,而 Agent 則採用語言模型作爲推理引擎來確定以什麼樣的順序採取什麼樣的行動。
Agent 相比 Chain 最典型的特點是 “自治”,它可以通過藉助 LLM 專長的推理能力,自動化地決策獲取什麼樣的知識,採取什麼樣的行動,直到完成用戶設定的最終目標。
LangChain Agent 工作流程示意圖
因此,作爲一個智能體,需要具備以下核心能力:
-
規劃: 藉助於 LLM 強大的推理能力,實現任務目標的規劃拆解和自我反思。
-
記憶: 具備短期記憶(上下文)和長期記憶(向量存儲),以及快速的知識檢索能力。
-
行動: 根據拆解的任務需求正確地調用工具以達到任務的目的。
-
協作: 通過與其他智能體交互合作,完成更復雜的任務目標。
Agent 的核心能力
3.9.2 構建智能體
我們使用 Agent 繼續完成前邊 Tool 部分沒有完成的例子。這裏使用 create_openai_tools_agent 方法創建一個簡單的 OpenAI 工具 Agent,AgentExecutor 會自動接管工具調用的動作。如果希望給 Agent 添加記憶能力,依然可以採用前邊 Memory 章節提過的 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
# 創建LLM
llm = ChatOpenAI()
# 定義Tool
@tool
def get_temperature(city: str) -> int:
"""獲取指定城市的當前氣溫"""
return random.randint(-20, 50)
# 創建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')
])
# 創建Agent
tools = [get_temperature]
agent = create_openai_tools_agent(llm, tools, prompt=prompt)
# 執行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 度。
需要補充說明的是,LangChain 提供了 Hub[20] 功能,幫助大家管理共享 Agent 的提示詞模板。上述示例代碼的 Agent 提示詞模板和 hwchase17/openai-tools-agent[21] 的定義等價。
通過代碼 prompt = hub.pull("hwchase17/openai-tools-agent") 可以直接引用創建 prompt。
hwchase17/openai-tools-agent 的定義
四、LangChain 架構
從 API 到 Agent,我們 “腦暴” 了一個完整 AI 工程應用的發展軌跡,藉此我相信大家對 LangChain 的設計理念應該有了進一步的理解。
最後,我們再看一眼 LangChain 的產品架構。除了本文未介紹的 LangServe——將 Chain 部署爲 RESTful 服務,其他不再贅述。
LangChain 產品架構
五、尾記
可能會有小夥伴疑問,爲啥我一個搞圖計算的,在這搗鼓起 AI 工具來了。拋開 “擁抱行業,跟進趨勢” 這樣的大口號不談,單純從工作需要角度,使用 AI 工具加速內容的產出,本身就可以幫助我節省大量的時間和精力,空出的 “閒暇” 時間去帶娃也是極好的……
線上關於 LangChain 的文章,雖不說汗牛充棟,但也能隨手拈來。作爲 “後入場” 者,我過去從事了多年的數倉、中臺、雲產品的架構和研發,最近兩三年一直在搞圖計算這樣的基礎軟件,就想試試結合自身多面的工程經驗去理解 LangChain 背後的設計理念,給大家一個別樣的視角去觀察、體驗和思考 AI 工程。這兩天被 “AI 程序員” 的新聞刷屏了,其中引用的 Adam Rackis 的一條 Twitter 令我感受頗深:“做好磨練和深入學習的準備,熟練地引導人工智能取得好的結果可能纔是未來程序員能體現出來的價值。”,或許這正是 AI 工程的用武之地吧。
參考資料:
-
Retrieval-Augmented Generation for Large Language Models: A Survey:https://arxiv.org/abs/2312.10997
-
Chain-of-Thought Prompting Elicits Reasoning in Large Language Models:https://arxiv.org/pdf/2201.11903.pdf
-
Tree of Thoughts: Deliberate Problem Solving with Large Language Models:https://arxiv.org/pdf/2305.10601.pdf
-
ReAct: Synergizing Reasoning and Acting in Language Models:https://arxiv.org/abs/2210.03629
-
API-Bank: A Comprehensive Benchmark for Tool-Augmented LLMs:https://arxiv.org/abs/2304.08244
-
LangChain Docs:https://python.langchain.com/
-
OpenAI Docs:https://platform.openai.com/docs
-
LangGraph Docs:https://python.langchain.com/docs/langgraph
-
LangChain:[https://github.com/langchain-ai/langchain]
-
LangGraph:[https://github.com/langchain-ai/langgraph]
-
TuGraph Analytics:https://github.com/TuGraph-family/tugraph-analytics
-
TuGraph DB:https://github.com/TuGraph-family/tugraph-db
-
Langchain-Chatchat:https://github.com/chatchat-space/Langchain-Chatchat
-
LLM Powered Autonomous Agents:https://lilianweng.github.io/posts/2023-06-23-agent/
-
Emerging Architectures for LLM Applications:https://a16z.com/emerging-architectures-for-llm-applications/
-
Prompt Engineering Guide:https://www.promptingguide.ai/
-
Bond Copilot: Unleashing Refinitiv Data Library Search API with AI (LLM) :https://developers.lseg.com/en/article-catalog/article/bond-copilot--unleashing-rd-lib-search-api-with-ai-llm-langchain
引用鏈接:
[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 開發文檔: 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] 文本嵌入模型服務: https://platform.openai.com/docs/guides/embeddings
[14] Faiss: https://github.com/facebookresearch/faiss
[15] TuGraph 數據庫: https://github.com/TuGraph-family/tugraph-db
[16] 函數調用: https://platform.openai.com/docs/guides/function-calling
[17] Assistant 的函數調用: 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
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/zGS9N92R6dsc9Jk57pmYSg