LangChain 介紹
隨著各種開源大模型的發(fā)布崖媚,越來越多的人開始嘗試接觸和使用大模型痒玩。在感嘆大模型帶來的驚人表現(xiàn)的同時愤估,也發(fā)現(xiàn)一些問題嵌牺,比如沒法查詢到最新的信息打洼,有時候問一些數(shù)學問題時候,會出現(xiàn)錯誤答案逆粹,還有一些專業(yè)領(lǐng)域類問題甚至編造回答等等募疮。有沒有什么辦法能解決這些問題呢?答案就是LangChain枯饿。
LangChain 是一個開源的語言模型集成框架酝锅,旨在簡化使用大型語言模型(LLM)創(chuàng)建應(yīng)用程序的過程。利用它可以讓開發(fā)者使用語言模型來實現(xiàn)各種復(fù)雜的任務(wù)奢方,例如文本到圖像的生成搔扁、文檔問答、聊天機器人蟋字、調(diào)用特定的SaaS服務(wù)等等稿蹲。隨著ChatGPT、midjourney等AI技術(shù)的爆火鹊奖,LangChain也是在短時間內(nèi)得到6w+的star數(shù)苛聘,版本迭代也是異常的快,社區(qū)十分活躍忠聚。
LangChain 在沒有任何收入也沒有任何明顯的創(chuàng)收計劃的情況下设哗,獲得了 1000 萬美元的種子輪融資和 2000-2500 萬美元的 A 輪融資,估值達到 2 億美元左右两蟀。
上面是LangChain的核心架構(gòu)圖网梢,可以看到LangChain主要包含如下模塊:
- Model I/O:大模型的輸入輸出,包含提示詞赂毯、任何大模型战虏、結(jié)果解析器拣宰。
- Retrieval:涉及到數(shù)據(jù)集相關(guān),主要包含文檔提取器烦感、文檔轉(zhuǎn)換器巡社、向量數(shù)據(jù)庫等。
- Chains:允許將多個不同組件組合在一起使用手趣,形成鏈條式調(diào)用晌该。
- Memory:在大模型調(diào)用期間提供存儲能力。
- Agents:鏈式調(diào)用是硬編碼的回懦,而代理是由大模型根據(jù)實時情況來決定如何調(diào)用工具气笙。
- Callbacks:大模型各個階段的的回調(diào)系統(tǒng),對于日志記錄怯晕、監(jiān)控潜圃、流傳輸和其他任務(wù)非常有用。
Agent
大模型一般只擁有他們被訓練的知識舟茶,這種知識可能很快就會過時了谭期,所以在推理的時候大模型與外界是處于“斷開”狀態(tài)。為了克服這一限制吧凉,LangChain在Yao等人在2022年11月提出的推理和行動(ReAct)框架上提出了“代理”(Agent)的解決方案隧出。此方案可以獲取最新的數(shù)據(jù),并將其作為上下文插入到提示中阀捅。Agent也可以用來采取行動(例如胀瞪,運行代碼,修改文件等)饲鄙,然后該行動的結(jié)果可以被LLM觀察到凄诞,并被納入他們關(guān)于下一步行動的決定。
運行大體流程: 1用戶給出一個任務(wù)(Prompt) -> 2思考(Thought) -> 3行動(Action) -> 4觀察(Observation)
忍级,
然后循環(huán)執(zhí)行上述 2-4
的流程帆谍,直到大模型認為找到最終答案為止。
Agent內(nèi)部具體拆解:
使用Agent有兩個必備條件:相關(guān)能力工具和對這些工具的正確描述轴咱。
定義工具
工具的定義只需要集成BaseTool
類汛蝙,然后在_run
方法中編寫你的邏輯就行,大模型會把合適的參數(shù)傳進來朴肺。
需要定義類變量有:
- name: 工具名稱窖剑,很重要,大模型內(nèi)部會使用到
- description:工具描述戈稿,很重要苛吱,告知大模型在什么情況下來使用這個工具
- return_direct:這個字段默認為false,如果設(shè)置為true器瘪,工具返回結(jié)果后翠储,大模型就不再循環(huán)思考了會直接將這個結(jié)果當做答案。
LangChain 已經(jīng)內(nèi)置了 duckduckgo
搜索引擎橡疼,pip install duckduckgo-search
安裝一下依賴包即可使用援所,只是需要科學上網(wǎng)才能調(diào)通。
下面是我定義的兩個工具欣除,一個用于電影搜索住拭,一個用于數(shù)學計算:
from langchain.tools import BaseTool, DuckDuckGoSearchRun
# 搜索工具
class SearchTool(BaseTool):
name = "Search"
description = "當問電影相關(guān)問題時候,使用這個工具"
return_direct = False # 直接返回結(jié)果
def _run(self, query: str) -> str:
print("\n正在調(diào)用搜索引擎執(zhí)行查詢: " + query)
search = DuckDuckGoSearchRun()
return search.run(query)
# 計算工具
class CalculatorTool(BaseTool):
name = "Calculator"
description = "如果問數(shù)學相關(guān)問題時历帚,使用這個工具"
return_direct = False # 直接返回結(jié)果
def _run(self, query: str) -> str:
return eval(query)
定義結(jié)果解析類
每次大模型輸出之后滔岳,都會對結(jié)果進行解析,如果找到action就會去調(diào)用挽牢。但是默認的解析類我測試的時候總報錯谱煤,所以我改寫了一下:
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,同時需要科學上網(wǎng)睹栖。也可以配置一些本地的開源大模型硫惕,比如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, # 是否打印調(diào)試日志搪锣,方便查看每個環(huán)節(jié)執(zhí)行情況
output_parser=output_parser #
)
agent = AgentExecutor.from_agent_and_tools(
agent=chat_agent, tools=tools, memory=memory, verbose=True,
max_iterations=3 # 設(shè)置大模型循環(huán)最大次數(shù)秋忙,防止無限循環(huán)
)
調(diào)用Agent
調(diào)用就很簡單了,執(zhí)行agent.run(prompt)
即可构舟,下面是一個詳細的調(diào)用日志輸出:
日志已經(jīng)完整的體現(xiàn)出了整個流程灰追,大模型的確每次都匹配到了正確的tool。如果還覺得日志不詳細狗超,可以設(shè)置langchain.debug = True
弹澎,這樣會打印更詳細日志。
總結(jié)
可以這么理解Agent努咐,它讓大模型變成了一個決策者苦蒿。用戶的問題首先由它去理解和拆分,它來從工具列表中找到覺得合適的工具渗稍,然后將用戶的提問信息轉(zhuǎn)化成結(jié)構(gòu)化的數(shù)據(jù)佩迟,當成參數(shù)傳遞給工具函數(shù)团滥。工具函數(shù)返回結(jié)果又交還給了大模型去觀察分析,如果它覺得不是正確答案报强,那么繼續(xù)這個循環(huán)直到得出它認為的正確答案灸姊。
它就像是一個優(yōu)秀的項目經(jīng)理,分解用戶的問題秉溉,可能他不擅長完成某一項任務(wù)力惯,但是他能找到合適專業(yè)的外部的人去完成子任務(wù),最后他再匯總?cè)蝿?wù)結(jié)果交付給用戶召嘶。
優(yōu)點
- 框架層上來說父晶,對大模型的有更系統(tǒng)化的干預(yù)機制,方便集成弄跌。
- 拓展了大模型更多的能力甲喝,而且是不需要經(jīng)過復(fù)雜且昂貴的訓練過程。
- 不用再去寫那些匹配場景的規(guī)則了碟绑,大模型已經(jīng)幫你做了俺猿,前提是這個模型參數(shù)要夠大,能理解用戶的意思格仲。
- 整個流程都有詳細的記錄日志押袍,方便調(diào)試。
不足
- 大模型會被多次調(diào)用凯肋,響應(yīng)用戶的時間可能會比較久谊惭,因此相應(yīng)產(chǎn)品也就會限制在一些特定領(lǐng)域。
- 雖然不用寫工具匹配規(guī)則侮东,但是這也讓這一塊邏輯變成一個黑盒了圈盔,很難去精準的匹配或者調(diào)試。
- 對大模型本身能力要求很高悄雅,如果使用低參數(shù)大模型驱敲,很有可能無法識別問題并正確的分發(fā)給對應(yīng)工具。
當然還是有優(yōu)化的方向的:比如可以考慮去使用語料專門往解析action方面訓練宽闲,讓模型能更好的解析出action众眨。