Blokowanie budulcowe LangGraph: stan, węzły, krawędzie i wywołanie
Słownictwo LangGraph przed jego utworzeniem: stan wpisany, małe węzły, krawędzie warunkowe, wywoływanie narzędzi oraz komunikaty jako stan, pokazane przy użyciu prostego agenta.
Gdy modele nabywały umiejętności używania narzędzi i rozumowania wieloetapowego, pętle ad-hoc stały się trudne do testowania i wznowienia. LangGraph sprawia, że przepływ sterowania jest jawny: stan, węzły, krawędzie oraz wywołania — dzięki temu agenci są grafami, które można przeanalizować, a nie chaotycznymi sekwencjami poleceń.
Najprostszy graf
Minimalny przepływ agenta wygląda tak: start → węzeł(ы) rozumowania/działania → koniec, przy czym stan jest udostępniany pomiędzy poszczególnymi etapami.
Zbuduj pierwszego prostego agenta
Receive Order
Prepare Food
Deliver Food
Receive Order
↓
Prepare Food
↓
Deliver Food
Pizza
Address: 123 Main Street
Customer: John
Receive Order
↓
Prepare Food
↓
Deliver Food
from typing_extensions import TypedDict
class State(TypedDict):
graph_state: strNodes
def node_1(state):
print("---Node 1---")
return {"graph_state": state['graph_state'] + "I am"}
def node_2(state):
print("---Node 2---")
return {"graph_state": state['graph_state'] + "happy!"}
def node_3(state):
print("---Node 3---")
return {"graph_state": state['graph_state'] + "sad!"}
import random
from typing import Literal
def decide_mood(state) -> Literal["node_2", "node_3"]:
# Often, we will use state to decide on the next node to visit
user_input = state['graph_state']
# Here, let's just do a 50 / 50 split between nodes 2, 3
if random.random() < 0.5
# 50% of the time, we return Node 2
return "node_2"
# 50% of the time, we return Node 3
return "node_3"
from IPython.display import Image, display
from langgraph.graph import StateGraph, START, END
#Build Graph
builder = StateGraph(state)
builder.add_node("node_1", node_1)
builder.add_node("node_2", node_2)
builder.add_node("node_3", node_3)
#Logic
builder.add_edge(START, "node_1")
builder.add_conditional_edges("node_1", decide_mood)
builder.add_edge("node_2", END)
builder.add_edge("node_3", END)
#Add
graph = builder.compile()
#View
display(Image(graph.get_graph().draw_mermaid_png()))
graph.invoke({"graph_state" : "Hi, this is Lance."})
###OUTPUT
---Node 1---
---Node 3---
{'graph_state': 'Hi, this is Lance. I am sad!'}
{'graph_state': 'Hi, this is Lance. I am sad!'}
%%capture --no-stderr
%pip install --quiet -U langchain_openai langchain_core langgraph
from pprint import pprint
from langchain_core.messages import AIMessage, HumanMessage
messages = [AIMessage(content=f"So you said you were researching ocean mammals?", name="Model")]
messages.append(HumanMessage(content=f"Yes, that's right.",name="Lance"))
messages.append(AIMessage(content=f"Great, what would you like to learn about.", name="Model"))
messages.append(HumanMessage(content=f"I want to learn about the best place to see Orcas in the US.", name="Lance"))
for m in messages:
m.pretty_print()
#OUTPUT
================================== Ai Message ==================================
Name: Model
So you said you were researching ocean mammals?
================================ Human Message =================================
Name: Lance
Yes, that's right.
================================== Ai Message ==================================
Name: Model
Great, what would you like to learn about.
================================ Human Message =================================
Name: Lance
I want to learn about the best place to see Orcas in the US.
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o")
result = llm.invoke(messages)
type(result)
result
#OUTPUT
langchain_core.messages.ai.AIMessage
result
AIMessage(content='One of the best places to see orcas in the United States is the Pacific Northwest, particularly around the San Juan Islands in Washington State. Here are some details:\n\n1. **San Juan Islands, Washington**: These islands are a renowned spot for whale watching, with orcas frequently spotted between late spring and early fall. The waters around the San Juan Islands are home to both resident and transient orca pods, making it an excellent location for sightings.\n\n2. **Puget Sound, Washington**: This area, including places like Seattle and the surrounding waters, offers additional opportunities to see orcas, particularly the Southern Resident killer whale population.\n\n3. **Olympic National Park, Washington**: The coastal areas of the park provide a stunning backdrop for spotting orcas, especially during their migration periods.\n\nWhen planning a trip for whale watching, consider peak seasons for orca activity and book tours with reputable operators who adhere to responsible wildlife viewing practices. Additionally, land-based spots like Lime Kiln Point State Park, also known as “Whale Watch Park,” on San Juan Island, offer great opportunities for orca watching from shore.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 228, 'prompt_tokens': 67, 'total_tokens': 295, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_50cad350e4', 'finish_reason': 'stop', 'logprobs': None}, id='run-57ed2891-c426-4452-b44b-15d0a5c3f225-0', usage_metadata={'input_tokens': 67, 'output_tokens': 228, 'total_tokens': 295, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})
def multiply(a:int, b:int)-> int:
return a*b
llm_with_tools = llm.bind_tools([multiply])
tool_call = llm_with_tools.invoke([HumanMessage(content=f"What is 2 multiplied by 3
", name="Lance")])
tool_call.tool_calls
#OUTPUT
[{'name': 'multiply',
'args': {'a': 2, 'b': 3},
'id': 'call_lBBBNo5oYpHGRqwxNaNRbsiT',
'type': 'tool_call'}]
from typing_extentions import TypedDict
from langchain_core.messges import AnyMessage
class MessagesState(TypedDict):
messages : list[AnyMessage]
from typing import Annotated
from langgraph.graph.message import add_messages
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
from langgraph.graph import MessagesState
class MessagesState(MessagesState):
# Add any keys needed beyond messages, which is pre-built
pass
# Initial state
initial_messages = [AIMessage(content="Hello! How can I assist you?", name="Model"),
HumanMessage(content="I'm looking for information on marine biology.", name="Lance")
]
# New message to add
new_message = AIMessage(content="Sure, I can help with that. What specifically are you interested in?", name="Model")
# Test
add_messages(initial_messages , new_message)
from IPython.display import Image, display
from langgraph.graph import StateGraph, START, END
# Node
def tool_calling_llm(state: MessagesState):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
# Build graph
builder = StateGraph(MessagesState)
builder.add_node("tool_calling_llm", tool_calling_llm)
builder.add_edge(START, "tool_calling_llm")
builder.add_edge("tool_calling_llm", END)
graph = builder.compile()
# View
display(Image(graph.get_graph().draw_mermaid_png()))
messages = graph.invoke({"messages": HumanMessage(content="Hello!")})
for m in messages['messages']:
m.pretty_print()
#OUTPUT
================================ Human Message =================================
Hello!
================================== Ai Message ==================================
Hi there! How can I assist you today?
messages = graph.invoke({"messages": HumanMessage(content="Multiply 2 and 3")})
for m in messages['messages']:
m.pretty_print()
OUTPUT
================================ Human Message =================================
Multiply 2 and 3!
================================== Ai Message ==================================
Tool Calls:
multiply (call_Er4gChFoSGzU7lsuaGzfSGTQ)
Call ID: call_Er4gChFoSGzU7lsuaGzfSGTQ
Args:
a: 2
b: 3
Stan
Typowany stan (często typu TypedDict lub reducers) przechowuje wiadomości, flagi oraz wyniki pośrednie. Wybieraj reduktory starannie – używaj append dla list wiadomości, a replace dla wartości skalarnych.
Węzły
Węzły to funkcje: odczytują stan, wykonywają zadania (LLM, narzędzia, czysty Python) i zwracają częściową aktualizację. Trzymaj je proste i poddawalne testom.
Krawędzie
Krawędzie statyczne ustalają kolejność; krawędzie warunkowe tworzą gałęzie w zależności od stanu. Grafy są przydatne, gdy pojawiają się gałęzie i pętle.
Budowa grafu
Zarejestruj węzły, połącz je krawędziami, skompiluj z opcjonalnym wskaźnikiem stanu dla trwałości.
Wywoływanie grafu
Wywołaj go z danymi wejściowymi i konfiguracją (identyfikator wątku podczas tworzenia punktu kontrolnego). Przesyłaj wydarzenia dla lepszej obsługi użytkownika.
Łańcuchy vs grafy
Liniowe łańcuchy nadal pozwalają na prace jednopassowe. Grafy umożliwiają pauzowanie/kontynuowanie, tworzenie gałęzi oraz współdzielony stan o nazwie.
Wiadomości i modele czatu
Listy wiadomości stanowią standardowy stan konwersacji. Zwiąż modele czatu wewnątrz węzłów, zamiast ukrywać operacje wejścia/wyjścia w logice krawędzi.
Narzędzia
Ujawnij narzędzia węzłom wywołującym modele; parsuj żądania narzędzi; zapisz obserwacje z powrotem do stanu.
Wykorzystywanie wiadomości jako stanu
Gdy wiadomości są stanem, reduktory muszą je bezpiecznie dodawać, a skracanie/sumaryzowanie staje się wyraźnym elementem struktury — a nie przypadkiem.
Zakończenie
Część 0 dotyczy słownictwa: stan, węzły, krawędzie, kompilacja, wywołanie. W kolejnych częściach dodawane są trwałe wątki, przerwy HITL oraz wzory wieloagentowe oparte na tej samej podstawie.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do obsługi.
Dodaj test jednostkowy, który wywoła zkompilowany graf przy użyciu ustalonego stanu i sprawdzi, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności rozwiązania biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do obsługi.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności procesu biznesowego.
Nazwij węzły od kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności procesu biznesowego.
Nazwij węzły od kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności procesu biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności rozwiązania biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności rozwiązania biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności procesu biznesowego.
Nazwij węzły od kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności procesu biznesowego.
Nazwij węzły od kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności procesu biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy czytelne to grafy użyteczne do działania.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie potwierdza kompletności rozwiązania biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy czytelne to grafy użyteczne do działania.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie potwierdza kompletności rozwiązania biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy czytelne to grafy użyteczne do działania.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności rozwiązania biznesowego.
Nazwij węzły od kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności rozwiązania biznesowego.
Nazwij węzły od kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności rozwiązania biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy czytelne to grafy użyteczne do działania.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie potwierdza kompletności rozwiązania biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy czytelne to grafy użyteczne do działania.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie potwierdza kompletności rozwiązania biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy czytelne to grafy użyteczne do działania.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności procesu biznesowego.
Nazwij węzły od kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności procesu biznesowego.
Nazwij węzły od kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy łatwe do odczytania to grafy nadające się do użytku.
Dodaj test jednostkowy, który wywołuje skompilowany graf w określonym stanie i sprawdza, które węzły zostały uruchomione. Sama funkcja compile() nie dowodzi kompletności procesu biznesowego.
Nazwij węzły według kroków biznesowych (classify, retrieve, draft) zamiast node1. Grafy czytelne to grafy użyteczne do działania.
Dodaj test jednostkowy, który uruchamia skompilowany graf w określonym stanie i sprawdza, które węzły zostały wykorzystane. Sama funkcja compile() nie gwarantuje kompletności realizacji procesu biznesowego.
Dla testów routingu preferuj węzły deterministyczne: wprowadź fałszywy model LLM, który zwraca ustalone wywołania narzędzi, dzięki czemu CI nie potrzebuje dostępu do sieci.
Jeszcze na wczesnym etapie udokumentuj zasadę autoryzacji thread_id: znajdowanie punktu kontrolnego nie oznacza uprawnień do jego odczytu.
Nawet w prototypach części 0 trzymaj narzędzia wywołujące efekty uboczne za krawędziami zatwierdzenia — nawyki szybko się kształtują.
Dla testów routingu preferuj węzły deterministyczne: wprowadź fałszywy model LLM, który zwraca ustalone wywołania narzędzi, dzięki czemu CI nie potrzebuje dostępu do sieci.
Należy jak najszybciej udokumentować zasadę autoryzacji thread_id: znajdowanie punktu kontrolnego nie oznacza uprawnień do jego odczytu.
Nawet w prototypach części 0 należy trzymać narzędzia wywołujące efekty uboczne poza obszarami zatwierdzeń — nawyki szybko się kształtują.
Dla testów routingu lepiej używać węzłów deterministycznych: należy wprowadzić fałszywy LLM, który zwraca ustalone wywołania narzędzi, dzięki czemu CI nie potrzebuje dostępu do sieci.
Należy jak najszybciej udokumentować zasadę autoryzacji thread_id: znajdowanie punktu kontrolnego nie oznacza uprawnień do jego odczytu.
Nawet w prototypach części 0 należy trzymać narzędzia wywołujące efekty uboczne poza obszarami zatwierdzeń — nawyki szybko się kształtują.
Dla testów routingu lepiej używać węzłów deterministycznych: należy wprowadzić fałszywy LLM, który zwraca ustalone wywołania narzędzi, dzięki czemu CI nie potrzebuje dostępu do sieci.
Literatura pokrewna
- Bloki budulcowe LangChain dla prawdziwych aplikacji AI — Modele, prompty, łańcuchy, narzędzia, agenci oraz RAG — jak LangChain organizuje aplikacje oparte na LLM poza ramami pojedynczego wywołania API dostawcy.
- Wprowadzenie do LangGraph: Węzły, krawędzie i stan wspólny bez zamglenia — Podręcznik dla początkujących dotyczący trzech elementów składowych LangGraph — pracowników, ścieżek i pamięci — wraz z modułowymi rolami, schematami oraz metodami debugowania typu podróż w czasie.