LangChain的Agent使用介紹

GaoYanbing發表於2024-03-13

LangChain 介紹

隨著各種開源大模型的釋出,越來越多的人開始嘗試接觸和使用大模型。在感嘆大模型帶來的驚人表現的同時,也發現一些問題,比如沒法查詢到最新的資訊,有時候問一些數學問題時候,會出現錯誤答案,還有一些專業領域類問題甚至編造回答等等。有沒有什麼辦法能解決這些問題呢?答案就是LangChain。

LangChain 是一個開源的語言模型整合框架,旨在簡化使用大型語言模型(LLM)建立應用程式的過程。利用它可以讓開發者使用語言模型來實現各種複雜的任務,例如文字到影像的生成、文件問答、聊天機器人、呼叫特定的SaaS服務等等。隨著ChatGPT、midjourney等AI技術的爆火,LangChain也是在短時間內得到6w+的star數,版本迭代也是異常的快,社群十分活躍。

LangChain 在沒有任何收入也沒有任何明顯的創收計劃的情況下,獲得了 1000 萬美元的種子輪融資和 2000-2500 萬美元的 A 輪融資,估值達到 2 億美元左右。

LangChain的Agent使用介紹
LangChain架構圖

上面是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內部具體拆解:


LangChain的Agent使用介紹
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-6BBaichuan-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)即可,下面是一個詳細的呼叫日誌輸出:

LangChain的Agent使用介紹
執行結果

日誌已經完整的體現出了整個流程,大模型的確每次都匹配到了正確的tool。如果還覺得日誌不詳細,可以設定langchain.debug = True,這樣會列印更詳細日誌。

總結

可以這麼理解Agent,它讓大模型變成了一個決策者。使用者的問題首先由它去理解和拆分,它來從工具列表中找到覺得合適的工具,然後將使用者的提問資訊轉化成結構化的資料,當成引數傳遞給工具函式。工具函式返回結果又交還給了大模型去觀察分析,如果它覺得不是正確答案,那麼繼續這個迴圈直到得出它認為的正確答案。

它就像是一個優秀的專案經理,分解使用者的問題,可能他不擅長完成某一項任務,但是他能找到合適專業的外部的人去完成子任務,最後他再彙總任務結果交付給使用者。

優點

  • 框架層上來說,對大模型的有更系統化的干預機制,方便整合。
  • 擴充了大模型更多的能力,而且是不需要經過複雜且昂貴的訓練過程。
  • 不用再去寫那些匹配場景的規則了,大模型已經幫你做了,前提是這個模型引數要夠大,能理解使用者的意思。
  • 整個流程都有詳細的記錄日誌,方便除錯。

不足

  • 大模型會被多次呼叫,響應使用者的時間可能會比較久,因此相應產品也就會限制在一些特定領域。
  • 雖然不用寫工具匹配規則,但是這也讓這一塊邏輯變成一個黑盒了,很難去精準的匹配或者除錯。
  • 對大模型本身能力要求很高,如果使用低引數大模型,很有可能無法識別問題並正確的分發給對應工具。

當然還是有最佳化的方向的:比如可以考慮去使用語料專門往解析action方面訓練,讓模型能更好的解析出action。

引用連結:

  1. Introduction | 🦜️🔗 Langchain
  2. 【LangChain】模組架構解析:一圖帶你瞭解 LangChain 的內部結構!
  3. Prophet-Andrew-Ng/langchain/李魯魯學LangChain 11.md
  4. 面向開發者的 LLM 入門教程
  5. 我為什麼放棄了 LangChain? - 知乎 (zhihu.com)


作者:雨田君的記事本
連結:https://www.jianshu.com/p/f58ddfb88f95
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

相關文章