2024-06-17 | Cobus Greyling
介紹
為什麼我們要回到狀態和狀態轉換控制?我以為隨著自主AI代理的出現,我們已經超越了狀態控制和轉換的概念?
其實,如果你觀察自主代理的輸出,你會注意到代理會即時創建一系列的行動,並依照這些行動執行。
LangGraph 的目的是在執行自主AI代理時提供一定程度的控制。
抱歉這篇文章有點長,但我真的想深入了解圖作為數據類型的基本原則。
現狀
考慮下方的圖片,這是我們大多數人所熟悉的用於創建對話和流程流的設計與開發用戶界面。
設計的啟發可以分為兩個主要類別:節點和邊。
節點是構成界面的區塊,有時被稱為資產。在下方的圖片中,設計畫布上有五個節點。每個節點之間都有連結,這些連結稱為邊。邊表示可能的目標節點或多個目標節點。

提示連結
隨著大型語言模型的出現,提示鏈出現了…

提示鏈接是一種在使用語言模型時的技術,透過將多個提示(節點)依序連接(透過邊)在一起,引導生成應用程式完成一系列相關的任務或步驟。
此方法透過將大型任務分解為較小且易於管理的部分,以實現更複雜和細緻的輸出。
以下是提示鏈接如何運作的簡單說明:
- 分解任務(節點):複雜的任務被分成更小的、連續的步驟。每個步驟都是為了實現總體目標的特定部分。
- 為每個步驟建立提示(邊緣):對於每個步驟,都會設計一個特定的提示來指導語言模型產生所需的輸出。這些提示旨在明確並專注於各自的子任務。
- 順序執行:語言模型處理第一個提示並產生輸出。然後,該輸出將用作序列中下一個提示的輸入的一部分。
需要注意的是,提示鏈接基於與聊天機器人流程構建相同的原理。因此,我們面臨與之前相同的問題,即僵化的狀態機。
確實,提示鏈接的某些方面在每個節點的輸入上引入了一定程度的靈活性,並在輸出上提供了動態變化。然而,整體的序列仍然是固定且僵化的。
挑戰
這種方法需要精心手工製作流程,並且需要定義每個決策點;因此,這本質上是一個狀態機,對話樹是由不同的狀態(節點)和對話需要沿邊移動的決策點定義的。
邊緣也可以被視為可用於對話狀態/對話轉向的選項。
這給我們帶來了對話設計的挑戰之一;狀態和流量控制。
這就帶出了這種方法過於僵化的挑戰,通常我們希望減少這種僵化性,並引入更多的靈活性。
我們所知的傳統聊天機器人存在著結構過多且過於僵化的問題。
輸入代理
自主代理最近被引入,其自主程度令人驚嘆。代理具有一定的自主性,能夠即時創建一系列事件的鏈或序列,並遵循其創建的臨時鏈直到達成問題的最終答案。
您可以將這視為一次性鏈。

考慮下面的範例,代理可能會被問到非常模糊的問題,例如:Who is regarded as the father of the iPhone and what is the square root of his year of birth?”
或者
What is the square root of the year of birth, of the man who is generally regarded as the father of the iPhone?
如下所示,智能體即時創造一條鏈,反思問題,並經歷行動、觀察、思考、行動、觀察、 思考的過程…直到得出最終答案。
> Entering new AgentExecutor chain...
I need to find out who is regarded as the father of the iPhone and his year of birth. Then, I will calculate the square root of his year of birth.
Action: Search
Action Input: father of the iPhone year of birth
Observation: Family. Steven Paul Jobs was born in San Francisco, California, on February 24, 1955, to Joanne Carole Schieble and Abdulfattah "John" Jandali (Arabic: عبد الف ...
Thought:I found that Steve Jobs is regarded as the father of the iPhone and he was born in 1955. Now I will calculate the square root of his year of birth.
Action: Calculator
Action Input: sqrt(1955)
Observation: Answer: 44.21538193886829
Thought:I now know the final answer.
Final Answer: Steve Jobs is regarded as the father of the iPhone, and the square root of his year of birth (1955) is approximately 44.22.
> Finished chain.
'Steve Jobs is regarded as the father of the iPhone, and the square root of his year of birth (1955) is approximately 44.22.
挑戰
代理的挑戰,以及我最常聽到的批評點,是代理擁有過高的自主性。
開發者希望對代理有一定程度的控制。
因此,代理的引入使我們從過度的控制和僵化轉向了更大的靈活性,但也帶來了缺乏控制的問題。
從LangChain進入LangGraph
但首先,什麼是 Graph 作為一種資料型態?
什麼是圖(抽象資料型別)
圖數據的概念一開始可能看起來有些晦澀,但我會在此嘗試將其分解說明。
確實,圖是一種抽象數據類型…
抽象數據類型是一種數據類型的數學模型,從使用者的角度來看,它是由其行為(語義)定義的。
抽象數據類型與數據結構形成了鮮明對比,數據結構是數據的具體表示,是實現者的觀點,而非使用者的觀點。這種數據結構較不晦澀且容易理解。
有向圖
有向圖(或稱為有向圖)是一種由一組節點和由定向邊連接的這些節點組成的圖。
在圖數據結構中,有限的節點集與這些節點的無序對構成了無向圖。
在下方的圖表示中,顯示了節點、邊及其邊的選項。

LangGraph
再次考慮下方的圖片,左側顯示了一段 LangGraph 的 Python 代碼片段,右側則是相對應的圖形。您可以在代碼中看到節點的定義部分,即 builder.add_node
,並使用 ReturnNodeValue
。對於每個節點,邊的定義則使用 builder.add_edge
。
同時,您可以看到節點 a
被設置為 entry_point
(入口點),而節點 d
則被設置為 finish_point
(結束點)。

LangGraph 是建立在 LangChain 之上的模組,旨在更好地支持循環圖的創建,這在代理執行時常常是必需的。
LangChain 的一個主要優勢是其能夠輕鬆創建自定義鏈,這也稱為流程工程。將 LangGraph 與 LangChain 的代理結合,代理可以是既有方向性也可包含循環的圖形。
**有向無環圖(DAG)**是一種類型的圖,在計算機科學和數學中被廣泛應用。以下是其簡單解釋:
- 有向:每個節點(或頂點)之間的連接(或邊)具有方向性,類似於單行道,表示從一個節點到另一個節點的方向。
- 無環:圖中沒有任何環路。這意味著如果從某個節點出發並按照方向行進,將無法回到同一節點。沒有進入循環的可能。
可以將其想像為家譜或流程圖,其中只能向前移動,無法回到起始點。
在開發更複雜的 LLM 應用程式時,一個常見的模式是將循環引入運行時中,這些循環通常使用 LLM 來決定流程中的下一步。
LLM 的一大優勢是其執行推理任務的能力,基本上可以像在 for 迴圈中一樣工作。採用此方法的系統通常被稱為代理。
Agents & Control
然而,循環代理通常需要在各個階段進行細緻的控制。
開發者可能需要確保代理首先調用特定工具,或者希望對工具的使用方式有更多控制。
此外,他們可能希望根據代理當前的狀態使用不同的提示。
公開窄接口
LangGraph 的核心提供了一個建構在 LangChain 之上的簡化介面。
為什麼選擇 LangGraph?
LangGraph 與框架無關,每個節點都充當常規 Python 函數。
它擴展了核心 Runnable API(用於串流、非同步和批次呼叫的共享介面)以促進:
- 跨多個對話回合或工具所使用的無縫狀態管理。
- 基於動態標準的節點間靈活路由
- 法學碩士和人工幹預之間的平穩過渡
- 長期運行的多會話應用程式的持久性
LangGraph 聊天機器人
下面是一個基於 Anthropic 模型的正在運行的 LangChain 聊天機器人。基本程式碼是從 LangChain 的食譜中的範例程式碼複製而來的。
%%capture --no-stderr
%pip install -U langgraph langsmith
# Used for this tutorial; not a requirement for LangGraph
%pip install -U langchain_anthropic
#################################
import getpass
import os
def _set_env(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"{var}: ")
_set_env("ANTHROPIC_API_KEY")
#################################
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.graph.message import add_messages
class State(TypedDict):
# Messages have the type "list". The `add_messages` function
# in the annotation defines how this state key should be updated
# (in this case, it appends messages to the list, rather than overwriting them)
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
#################################
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-3-haiku-20240307")
def chatbot(state: State):
return {"messages": [llm.invoke(state["messages"])]}
# The first argument is the unique node name
# The second argument is the function or object that will be called whenever
# the node is used.
graph_builder.add_node("chatbot", chatbot)
#################################
graph_builder.set_entry_point("chatbot")
#################################
graph_builder.set_finish_point("chatbot")
#################################
graph = graph_builder.compile()
#################################
from IPython.display import Image, display
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
# This requires some extra dependencies and is optional
pass
#################################
while True:
user_input = input("User: ")
if user_input.lower() in ["quit", "exit", "q"]:
print("Goodbye!")
break
for event in graph.stream({"messages": ("user", user_input)}):
for value in event.values():
print("Assistant:", value["messages"][-1].content)
#################################
下面的截圖顯示了圖形如何渲染流程。

最後
圖數據類型是一種強大的工具,用於呈現數據的視覺化表示。不僅是視覺化表示,不同節點之間的關係也非常適合創建數據節點的空間表示。
從數據使用者的角度來看,圖數據類型在語義行為上也非常理想,能夠直觀地展現數據之間的關聯和結構。
資料來源: https://cobusgreyling.medium.com/langgraph-from-langchain-explained-in-simple-terms-f7cd0c12cdbf