LangChain 介紹
隨著各種開源大模型的釋出,越來越多的人開始嘗試接觸和使用大模型。在感嘆大模型帶來的驚人表現的同時,也發現一些問題,比如沒法查詢到最新的資訊,有時候問一些數學問題時候,會出現錯誤答案,還有一些專業領域類問題甚至編造回答等等。有沒有什麼辦法能解決這些問題呢?答案就是LangChain。
LangChain 是一個開源的語言模型整合框架,旨在簡化使用大型語言模型(LLM)建立應用程式的過程。利用它可以讓開發者使用語言模型來實現各種複雜的任務,例如文字到影像的生成、文件問答、聊天機器人、呼叫特定的SaaS服務等等。隨著ChatGPT、midjourney等AI技術的爆火,LangChain也是在短時間內得到6w+的star數,版本迭代也是異常的快,社群十分活躍。
LangChain 在沒有任何收入也沒有任何明顯的創收計劃的情況下,獲得了 1000 萬美元的種子輪融資和 2000-2500 萬美元的 A 輪融資,估值達到 2 億美元左右。
上面是LangChain的核心架構圖,可以看到LangChain主要包含如下模組:
- Model I/O:大模型的輸入輸出,包含提示詞、任何大模型、結果解析器。
- Retrieval:涉及到資料集相關,主要包含文件提取器、文件轉換器、向量資料庫等。
- Chains:允許將多個不同元件組合在一起使用,形成鏈條式呼叫。
- Memory:在大模型呼叫期間提供儲存能力。
- Agents:鏈式呼叫是硬編碼的,而代理是由大模型根據實時情況來決定如何呼叫工具。
- Callbacks:大模型各個階段的的回撥系統,對於日誌記錄、監控、流傳輸和其他任務非常有用。
Agent
大模型一般只擁有他們被訓練的知識,這種知識可能很快就會過時了,所以在推理的時候大模型與外界是處於“斷開”狀態。為了克服這一限制,LangChain在Yao等人在2022年11月提出的推理和行動(ReAct)框架上提出了“代理”(Agent)的解決方案。此方案可以獲取最新的資料,並將其作為上下文插入到提示中。Agent也可以用來採取行動(例如,執行程式碼,修改檔案等),然後該行動的結果可以被LLM觀察到,並被納入他們關於下一步行動的決定。
執行大體流程: 1使用者給出一個任務(Prompt) -> 2思考(Thought) -> 3行動(Action) -> 4觀察(Observation)
,
然後迴圈執行上述 2-4
的流程,直到大模型認為找到最終答案為止。
Agent內部具體拆解:
使用Agent有兩個必備條件:相關能力工具和對這些工具的正確描述。
定義工具
工具的定義只需要整合BaseTool
類,然後在_run
方法中編寫你的邏輯就行,大模型會把合適的引數傳進來。
需要定義類變數有:
- name: 工具名稱,很重要,大模型內部會使用到
- description:工具描述,很重要,告知大模型在什麼情況下來使用這個工具
- return_direct:這個欄位預設為false,如果設定為true,工具返回結果後,大模型就不再迴圈思考了會直接將這個結果當做答案。
LangChain 已經內建了 duckduckgo
搜尋引擎,pip install duckduckgo-search
安裝一下依賴包即可使用,只是需要kx上網才能調通。
下面是我定義的兩個工具,一個用於電影搜尋,一個用於數學計算:
from langchain.tools import BaseTool, DuckDuckGoSearchRun
# 搜尋工具
class SearchTool(BaseTool):
name = "Search"
description = "當問電影相關問題時候,使用這個工具"
return_direct = False # 直接返回結果
def _run(self, query: str) -> str:
print("\n正在呼叫搜尋引擎執行查詢: " + query)
search = DuckDuckGoSearchRun()
return search.run(query)
# 計算工具
class CalculatorTool(BaseTool):
name = "Calculator"
description = "如果問數學相關問題時,使用這個工具"
return_direct = False # 直接返回結果
def _run(self, query: str) -> str:
return eval(query)
定義結果解析類
每次大模型輸出之後,都會對結果進行解析,如果找到action就會去呼叫。但是預設的解析類我測試的時候總報錯,所以我改寫了一下:
from typing import Dict, Union, Any, List
from langchain.output_parsers.json import parse_json_markdown
from langchain.agents.conversational_chat.prompt import FORMAT_INSTRUCTIONS
from langchain.agents import AgentExecutor, AgentOutputParser
from langchain.schema import AgentAction, AgentFinish
# 自定義解析類
class CustomOutputParser(AgentOutputParser):
def get_format_instructions(self) -> str:
return FORMAT_INSTRUCTIONS
def parse(self, text: str) -> Union[AgentAction, AgentFinish]:
print(text)
cleaned_output = text.strip()
# 定義匹配正則
action_pattern = r'"action":\s*"([^"]*)"'
action_input_pattern = r'"action_input":\s*"([^"]*)"'
# 提取出匹配到的action值
action = re.search(action_pattern, cleaned_output)
action_input = re.search(action_input_pattern, cleaned_output)
if action:
action_value = action.group(1)
if action_input:
action_input_value = action_input.group(1)
# 如果遇到'Final Answer',則判斷為本次提問的最終答案了
if action_value and action_input_value:
if action_value == "Final Answer":
return AgentFinish({"output": action_input_value}, text)
else:
return AgentAction(action_value, action_input_value, text)
# 如果宣告的正則未匹配到,則用json格式進行匹配
response = parse_json_markdown(text)
action_value = response["action"]
action_input_value = response["action_input"]
if action_value == "Final Answer":
return AgentFinish({"output": action_input_value}, text)
else:
return AgentAction(action_value, action_input_value, text)
output_parser = CustomOutputParser()
初始化Agent
如果你使用ChatGPT
的話,這裡需要配置ChatGPT的api-key,同時需要kx上網。也可以配置一些本地的開源大模型,比如ChatGLM2-6B
、Baichuan-13B
等,但是效果確實要比ChatGPT差很多。
from langchain.memory import ConversationBufferMemory
from langchain.agents.conversational_chat.base import ConversationalChatAgent
from langchain.agents import AgentExecutor, AgentOutputParser
SYSTEM_MESSAGE_PREFIX = """儘可能用中文回答以下問題。您可以使用以下工具"""
# 初始化大模型例項,可以是本地部署的,也可是是ChatGPT
# llm = ChatGLM(endpoint_url="http://你本地的例項地址")
llm = ChatOpenAI(openai_api_key="sk-xxx", model_name='gpt-3.5-turbo', request_timeout=60)
# 初始化工具
tools = [CalculatorTool(), SearchTool()]
# 初始化對話儲存,儲存上下文
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# 配置agent
chat_agent = ConversationalChatAgent.from_llm_and_tools(
system_message=SYSTEM_MESSAGE_PREFIX, # 指定提示詞字首
llm=llm, tools=tools, memory=memory,
verbose=True, # 是否列印除錯日誌,方便檢視每個環節執行情況
output_parser=output_parser #
)
agent = AgentExecutor.from_agent_and_tools(
agent=chat_agent, tools=tools, memory=memory, verbose=True,
max_iterations=3 # 設定大模型迴圈最大次數,防止無限迴圈
)
呼叫Agent
呼叫就很簡單了,執行agent.run(prompt)
即可,下面是一個詳細的呼叫日誌輸出:
日誌已經完整的體現出了整個流程,大模型的確每次都匹配到了正確的tool。如果還覺得日誌不詳細,可以設定langchain.debug = True
,這樣會列印更詳細日誌。
總結
可以這麼理解Agent,它讓大模型變成了一個決策者。使用者的問題首先由它去理解和拆分,它來從工具列表中找到覺得合適的工具,然後將使用者的提問資訊轉化成結構化的資料,當成引數傳遞給工具函式。工具函式返回結果又交還給了大模型去觀察分析,如果它覺得不是正確答案,那麼繼續這個迴圈直到得出它認為的正確答案。
它就像是一個優秀的專案經理,分解使用者的問題,可能他不擅長完成某一項任務,但是他能找到合適專業的外部的人去完成子任務,最後他再彙總任務結果交付給使用者。
優點
- 框架層上來說,對大模型的有更系統化的干預機制,方便整合。
- 擴充了大模型更多的能力,而且是不需要經過複雜且昂貴的訓練過程。
- 不用再去寫那些匹配場景的規則了,大模型已經幫你做了,前提是這個模型引數要夠大,能理解使用者的意思。
- 整個流程都有詳細的記錄日誌,方便除錯。
不足
- 大模型會被多次呼叫,響應使用者的時間可能會比較久,因此相應產品也就會限制在一些特定領域。
- 雖然不用寫工具匹配規則,但是這也讓這一塊邏輯變成一個黑盒了,很難去精準的匹配或者除錯。
- 對大模型本身能力要求很高,如果使用低引數大模型,很有可能無法識別問題並正確的分發給對應工具。
當然還是有最佳化的方向的:比如可以考慮去使用語料專門往解析action方面訓練,讓模型能更好的解析出action。
引用連結:
- Introduction | 🦜️🔗 Langchain
- 【LangChain】模組架構解析:一圖帶你瞭解 LangChain 的內部結構!
- Prophet-Andrew-Ng/langchain/李魯魯學LangChain 11.md
- 面向開發者的 LLM 入門教程
- 我為什麼放棄了 LangChain? - 知乎 (zhihu.com)
作者:雨田君的記事本
連結:https://www.jianshu.com/p/f58ddfb88f95
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。