Accueil / Articles

Blocs de construction de LangGraph : état, nœuds, arêtes et invocation

Vocabulaire de LangGraph avant sa création : état saisi, nœuds de petite taille, arêtes conditionnelles, appels d’outils et messages en tant qu’état, illustré avec un premier agent simple.

Éditeur : REACTAPP.TOP9 min de lecture2150 mots

Sources: The Building Blocks of LangGraph (Part 0)

Au fur et à mesure que les modèles ont acquis la capacité d’utiliser des outils et de raisonner en plusieurs étapes, les boucles ad hoc sont devenues difficiles à tester et à reprendre. LangGraph rend le flux de contrôle explicite : état, nœuds, arêtes et appels — ce qui permet de considérer les agents comme des graphes que l’on peut examiner, et non comme un enchevêtrement de commandes.

Le graphique le plus simple

Un flux d’agent minimal se compose de : début → nœud(s) de raisonnement/action → fin, avec un état partagé transmis entre les étapes.

Créer un premier agent simple

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

État

L’état défini manuellement (souvent un TypedDict ou des reducers) contient les messages, les flags et les résultats intermédiaires. Choisissez les reducers en fonction de vos besoins : utilisez l’opération append pour les listes de messages et replace pour les valeurs scalaires.

Nœuds

Les nœuds sont des fonctions : elles lisent l’état, effectuent des opérations (LLM, outils, Python pur) et retournent une mise à jour partielle. Gardez-les simples et testables.

Arêtes

Les arêtes statiques fixent l’ordre ; les arêtes conditionnelles créent des branches en fonction de l’état. Les graphes deviennent utiles lorsque des branches et des boucles apparaissent.

Construction du graphique

Enregistrez les nœuds, connectez les arêtes, et compilez avec un point de contrôle optionnel pour assurer la durabilité.

Appel du graphique

Appelez-le avec un état d’entrée et des paramètres de configuration (identifiant de thread pour le point de contrôle). Diffusez des événements afin d’améliorer l’expérience utilisateur.

Chaîne vs graphique

Les chaînes linéaires permettent encore un traitement en une seule passe. Les graphiques permettent des pauses/reprises, des branches et un état nommé partagé.

Messages et modèles de chat

Les listes de messages représentent l’état conversationnel habituel. Associez les modèles de chat à l’intérieur des nœuds plutôt que de cacher les opérations d’entrée/sortie dans la logique des arêtes.

Outils

Faites exposer des outils aux nœuds d’appel de modèles ; analysez les appels aux outils ; écrivez les observations à nouveau dans l’état.

Utilisation des messages comme état

Lorsque les messages sont l’état, les réducteurs doivent s’ajouter de manière sécurisée et le troncage/la synthèse deviennent un nœud explicite — et non le résultat d’un hasard.

Conclusion

La partie 0 porte sur le vocabulaire : état, nœuds, arêtes, compilation, invocation. Les parties suivantes ajoutent des threads durables, des interruptions HITL et des modèles multi-agents sur cette même base.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphes lisibles sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphique compilé avec un état de référence et vérifie quels nœuds ont été exécutés. La fonction compile() seule ne prouve pas la complétude du processus métier.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphes lisibles sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphes lisibles sont des graphiques opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphiques lisibles sont des graphiques opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Donnez des noms aux nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Les graphes lisibles sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphique compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple appel à compile() ne prouve pas la complétude du processus métier.

Donnez des noms aux nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Les graphes lisibles sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphique compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple appel à compile() ne prouve pas la complétude du processus métier.

Donnez des noms aux nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Les graphes lisibles sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphes lisibles sont des graphiques opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphiques lisibles sont des graphiques opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Donnez des noms aux nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Les graphes lisible sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphique compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple appel à compile() ne prouve pas la complétude du processus métier.

Donnez des noms aux nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Les graphes lisible sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphique compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple appel à compile() ne prouve pas la complétude du processus métier.

Donnez des noms aux nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Les graphes lisible sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphes lisibles sont des graphiques opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphiques lisibles sont des graphiques opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Donnez des noms aux nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Les graphes lisible sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphique compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple appel à compile() ne prouve pas la complétude du processus métier.

Donnez des noms aux nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Les graphes lisible sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphique compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple appel à compile() ne prouve pas la complétude du processus métier.

Donnez des noms aux nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Les graphes lisible sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphes lisibles sont des graphiques opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Nommez les nœuds en fonction des étapes métier (classify, retrieve, draft) plutôt que node1. Des graphiques lisibles sont des graphiques opérationnels.

Ajoutez un test unitaire qui invoque le graphe compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple fait d’appeler compile() ne prouve pas la complétude du processus métier.

Nommez les nœuds d’après les étapes métier (classify, retrieve, draft) plutôt que node1. Des graphes lisibles sont des graphes opérationnels.

Ajoutez un test unitaire qui invoque le graphique compilé avec un état de fixture et vérifie quels nœuds ont été exécutés. Le simple appel à compile() ne prouve pas la complétude métier.

Préférez des nœuds déterministes pour les tests de routage : injectez un LLM fictif qui renvoie des appels d’outils fixes afin que le CI n’ait pas besoin d’accès réseau.

Dokumentez tôt la règle d’autorisation thread_id : localiser un point de contrôle ne signifie pas avoir le droit de le lire.

Maintenez les outils ayant des effets secondaires derrière des arêtes d’approbation, même dans les prototypes de la Partie 0 — les habitudes se forment rapidement.

Préférez des nœuds déterministes pour les tests de routage : injectez un LLM fictif qui renvoie des appels d’outils fixes afin que le CI n’ait pas besoin d’accès réseau.

Dokumentez tôt la règle d’autorisation thread_id : localiser un point de contrôle ne signifie pas avoir le droit de le lire.

Gardez les outils ayant des effets secondaires derrière les étapes d’approbation, même dans les prototypes de la Partie 0 — les habitudes se forment rapidement.

Préférez des nœuds déterministes pour les tests de routage : injectez un LLM fictif qui renvoie des appels d’outils fixes afin que le CI n’ait pas besoin d’accès au réseau.

Dokumentez tôt la règle d’autorisation thread_id : localiser un point de contrôle ne signifie pas avoir le droit de le lire.

Gardez les outils ayant des effets secondaires derrière les étapes d’approbation, même dans les prototypes de la Partie 0 — les habitudes se forment rapidement.

Préférez des nœuds déterministes pour les tests de routage : injectez un LLM fictif qui renvoie des appels d’outils fixes afin que le CI n’ait pas besoin d’accès au réseau.