快速了解 LangGraph

2024年2月6日 35点热度 0人点赞

摘要 Introduction

LangGraph是在LangChain之上構建的模塊,它主要是用於更好地創建循環圖,這種圖經常需要用在代理運行時中。LangGraph主要通過引入一種創建循環圖的簡單方式來增加新的價值。我們將首先介紹LangGraph的動機,然後探討它提供的基本功能。之後,我們將重點介紹我們已經實現的兩個代理運行時,然後我們將突出幾種我們聽到的對這些運行時的常見修改要求,以及實現這些要求的例子。最後,將展示將要發佈的預覽。

動機 Motivation

  • LangChain的一大優勢是可以輕松創建自定義鏈。然而,卻一直缺乏一個能方便添加循環到這些鏈的方法。
  • 一種常見的模式是人們在創建更復雜的LLM應用程序時引入運行時的循環。這些循環當LLM在循環中決定下一步要做什麼時候,常常會利用LLM進行推理。這可以被理解為在for循環中運行LLM。這種類型的系統通常被稱為代理。

在考慮典型的檢索增強生成(RAG)應用程序時,我們可以找到一些強大的示例,展示了為什麼這種代理行為如此有效。在典型的RAG應用程序中,首先調用檢索器以獲取一些文檔。然後,這些文檔傳遞給LLM(語言模型)以生成最終答案。盡管這通常是有效的,但如果第一個檢索步驟未能返回有用的結果,整個過程可能會失敗。在這種情況下,如果LLM能夠推斷檢索器返回的結果不佳,並且可能需要發出第二個(更精細的)查詢,然後使用這些結果,那將是理想的。從本質上講,通過在循環中運行LLM,我們可以創建更靈活的應用程序,以完成可能未預定義的更模糊的用例。

這些類型的應用程序通常被稱為“代理”。其中最簡單但也最雄心勃勃的形式是一個循環,它基本上包含兩個步驟:

  1. 調用LLM以確定要執行的操作或要向用戶提供的響應。
  2. 執行給定的操作,然後返回到步驟1。

重復這些步驟,直到生成最終響應。這個循環本質上是我們核心AgentExecutor的驅動力,與導致AutoGPT等項目聲名鵲起的邏輯相似。雖然這個循環很簡單,但它也是最雄心勃勃的,因為它將幾乎所有的決策和推理能力都交給了LLM。

在實際應用中,我們與社區和公司合作,將代理投入生產。我們發現,通常需要更多的控制。您可能希望始終強制代理首先調用特定工具,或者更好地控制工具的調用方式。您還可能希望根據代理所處的狀態為其提供不同的提示。

在討論這些更受控制的流程時,我們在內部將其稱為“狀態機”。您可以在我們關於認知架構的博客中找到下圖。

這些狀態機具有循環的能力,允許處理比簡單鏈更模糊的輸入。然而,就如何構建該循環而言,仍然存在人類指導的元素。

LangGraph 是一種通過將狀態機指定為圖形來創建這些狀態機的方法。

功能性 Functionality

從本質上講,LangGraph 在 LangChain 之上暴露了一個非常狹窄的接口。

StateGraph

StateGraph 是一個表示圖形的類。通過傳入定義來初始化這個類。這個狀態定義代表著隨時間更新的核心狀態對象。這個狀態會被圖中的節點更新,這些節點會返回操作的屬性(以鍵值對的形式)。 state 可以通過兩種方式來更新這個狀態的屬性。首先,可以完全覆蓋屬性。如果您希望節點返回屬性的新值,這將非常有用。其次,可以通過增加屬性的值來更新屬性。如果屬性是所執行操作(或類似操作)的列表,並且您希望節點返回已執行的新操作(並將這些操作自動添加到屬性中),這將非常有用。

在創建初始狀態定義時,您可以指定是應該覆蓋屬性還是添加屬性。以下是一個偽代碼示例:

from langgraph.graph import StateGraph
from typing import TypedDict, List, Annotated
import Operator
class State(TypedDict):
		input: str 
    all_actions: Annotated[List[str], operator.add]
graph = StateGraph(State)

節點 Nodes

在創建狀態機之後,我們可以使用語法來添加節點。這些節點的參數應該是字符串,我們將使用它們來在添加邊時引用這些節點。參數應該是將要調用的函數或可運行的LCEL(語言模型)。這些函數或LCEL應該接受與對象形式相同的字典作為輸入,並輸出帶有要更新的對象鍵的字典。

以下是一個偽代碼示例:

graph.add_node("model", model)
graph.add_node("tools", tool_executor)

還有一個特殊的節點,用於表示圖的末尾

from langgraph.graph import END

邊緣 Edges

在創建節點之後,您可以添加邊以構建圖形。有幾種類型的邊:

起始邊:將圖形的起點連接到特定節點。這使得該節點成為在輸入傳遞到圖形時首先被調用的節點。偽代碼如下:

graph.set_entry_point("model")

正常邊:在這些邊上,一個節點應始終在另一個節點之後被調用。例如,在基本代理運行時,我們通常希望在調用工具後再調用模型:

graph.add_edge("tools", "model")

條件邊:這些邊使用函數(通常由LLM提供支持)來確定首先轉到哪個節點。要創建這樣的邊,需要傳入三個參數:

  • 上遊節點:它查看此節點的輸出以確定下一步應該做什麼。
  • 一個函數:該函數將被調用以確定接下來要調用哪個節點。它應該返回一個字符串。
  • 映射:此映射將用於將第二個參數中函數的輸出映射到另一個節點。鍵應該是函數可能返回的可能值。如果返回了某個值,那麼這些值應該是要轉到的節點的名稱。

例如,在調用模型後,我們可以選擇要麼退出圖形並返回給用戶,要麼調用工具 - 這取決於用戶的決定!以下是偽代碼示例:

graph.add_conditional_edge("model", should_continue, {"end": END,"continue": "tools" })

編譯 Compile

在我們定義了狀態圖之後,我們可以將其編譯成可運行的代碼!這個過程簡單地將我們迄今為止創建的狀態圖定義轉換為可運行的代碼。這個可運行的代碼暴露了與LangChain可運行代碼相同的方法(例如,invokestreamastream_log等),因此可以以與鏈式調用相同的方式進行調用。

app = graph.compile()

代理執行器 Agent Executor

我們使用 LangGraph 重新創建了標準的 LangChain AgentExecutor。它允許您使用現有的 LangChain 代理,同時更容易地修改 AgentExecutor 的內部結構。默認情況下,此圖的狀態包含了一些您在使用 LangChain 代理時應該熟悉的概念:inputchat_historyintermediate_stepsagent_outcome

from typing import TypedDict, Annotated, List, Union
from langchain_core.agents import AgentAction, AgentFinish
from langchain_core.messages import BaseMessage
import operator
class AgentState(TypedDict):
   input: str
   chat_history: list[BaseMessage]
   agent_outcome: Union[AgentAction, AgentFinish, None]
   intermediate_steps: Annotated[list[tuple[AgentAction, str]], operator.add]

聊天代理執行器 Chat Agent Executor

我們註意到一個普遍趨勢,即越來越多的模型是基於消息列表進行操作的“聊天”模型。這些模型通常配備了諸如函數調用之類的功能,使得代理類似的體驗變得更加可行。在使用這些類型的模型時,將代理的狀態表示為消息列表通常是直觀的。

因此,我們創建了一個與此狀態一起工作的代理運行時。輸入是一個消息列表,節點隻需隨著時間的推移不斷添加到這個消息列表中。

from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]

修改 Modifications

LangGraph 的一個重要優勢在於以更自然且可修改的方式暴露了 AgentExecutor 的邏輯。我們提供了一些我們聽到過請求的修改示例:

  1. 強制調用工具:當您總是希望代理首先調用工具時使用。適用於 Agent Executor 和 Chat Agent Executor。
  2. 循環步驟中:如何在調用工具之前添加循環步驟。適用於 Agent Executor 和 Chat Agent Executor。
  3. 管理代理步驟:用於添加處理代理可能采取的中間步驟的自定義邏輯(在步驟較多時非常有用)。適用於 Agent Executor 和 Chat Agent Executor。
  4. 以特定格式返回輸出:如何使用函數調用使代理以特定格式返回輸出。僅適用於 Chat Agent Executor。
  5. 動態直接返回工具的輸出:有時您可能希望直接返回工具的輸出。我們在 LangChain 中提供了一種簡單的方法來實現這一點。但這會導致工具的輸出始終直接返回。有時,您可能希望讓 LLM 自行決定是否直接返回響應。僅適用於 Chat Agent Executor。