Inicio / Artículos

Bloques básicos de LangGraph: estado, nodos, aristas e invocación

Vocabulario de LangGraph antes de la construcción: estado escrito, nodos pequeños, aristas condicionales, llamadas a herramientas y mensajes como estado, mostrado con un primer agente simple.

Editor: REACTAPP.TOP9 min de lectura2150 palabras

Fuentes: The Building Blocks of LangGraph (Part 0)

A medida que los modelos adquirieron la capacidad de usar herramientas y razonar en múltiples pasos, los bucles ad hoc se volvieron difíciles de probar y reanudar. LangGraph hace que el flujo de control sea explícito: estado, nodos, aristas e invocaciones; así, los agentes son gráficos que se pueden inspeccionar, no secuencias de comandos desorganizadas.

El gráfico más simple

Un flujo de agente mínimo es: inicio → nodo(s) de razonamiento/acción → final, con un estado compartido que se transmite entre pasos.

Crea tu primer agente 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

Estado

El estado definido mediante tipos (a menudo un TypedDict o reducers) almacena mensajes, flags y resultados intermedios. Elija los reducers con cuidado: utilícelos para añadir elementos a listas de mensajes o para reemplazar valores escalares.

Nodos

Los nodos son funciones: leen el estado, realizan tareas (con LLM, herramientas o Python puro) y devuelven una actualización parcial. Manténgalos pequeños y fáciles de probar.

Aristas

Los bordes estáticos fijan el orden; los bordes condicionales crean ramas según el estado. Los grafos son útiles cuando aparecen ramificaciones y bucles.

Construcción de grafos

Registre los nodos, conecte los bordes y compile con un puntero de verificación opcional para garantizar la durabilidad.

Invocación de grafos

Invóquelos con un estado de entrada y configuración (identificador del hilo al crear puntos de control). Transmita eventos para mejorar la experiencia de usuario.

Cadenas vs grafos

Las cadenas lineales siguen permitiendo un procesamiento en una sola pasada. Los grafos permiten pausas/reanudaciones, ramificaciones y un estado compartido con nombre.

Mensajes y modelos de chat

Las listas de mensajes representan el estado habitual de la conversación. Asocie los modelos de chat dentro de los nodos en lugar de ocultar las operaciones de entrada/salida en la lógica de los bordes.

Herramientas

Exponga herramientas a los nodos que realizan llamadas al modelo; analice las llamadas a las herramientas; escriba las observaciones de vuelta al estado.

Uso de mensajes como estado

Cuando los mensajes sí constituyen el estado, los reducers deben añadirlos de forma segura y la truncación/resumen se convierte en un nodo explícito, no en algo accidental.

Cierre

La Parte 0 trata sobre vocabulario: estado, nodos, aristas, compilar e invocar. Las partes posteriores añaden hilos duraderos, interrupciones HITL y patrones multiagente sobre la misma base.

Nombrar los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregar una prueba unitaria que invoque el gráfico compilado con un estado de fixture y verifique qué nodos se ejecutaron. La función compile() por sí sola no demuestra la completitud del proceso de negocio.

Nombrar los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombré los nodos según las etapas comerciales (classify, retrieve, draft) en lugar de node1. Los grafos fáciles de leer son grafos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombré los nodos según las etapas comerciales (classify, retrieve, draft) en lugar de node1. Los grafos fáciles de leer son grafos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos de negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregar una prueba unitaria que invoque el gráfico compilado con un estado de fixture y verifique qué nodos se ejecutaron. El mero uso de compile() no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos de negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregar una prueba unitaria que invoque el gráfico compilado con un estado de fixture y verifique qué nodos se ejecutaron. El mero uso de compile() no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos de negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombré los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los grafos fáciles de leer son grafos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombré los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los grafos fáciles de leer son grafos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos de negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregar una prueba unitaria que invoque el gráfico compilado con un estado de fixture y verifique qué nodos se ejecutaron. El mero uso de compile() no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos de negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregar una prueba unitaria que invoque el gráfico compilado con un estado de fixture y verifique qué nodos se ejecutaron. El mero uso de compile() no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos de negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombré los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los grafos fáciles de leer son grafos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombré los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los grafos fáciles de leer son grafos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregar una prueba unitaria que invoque el gráfico compilado con un estado de fixture y verifique qué nodos se ejecutaron. El mero uso de compile() no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregar una prueba unitaria que invoque el gráfico compilado con un estado de fixture y verifique qué nodos se ejecutaron. El mero uso de compile() no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombré los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los grafos fáciles de leer son grafos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombré los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los grafos fáciles de leer son grafos operativos.

Agregue una prueba unitaria que invoque el grafo compilado con un estado de fixture y verifique qué nodos se ejecutaron. El método compile() por sí solo no demuestra la completitud del proceso empresarial.

Nombrar los nodos según los pasos del negocio (classify, retrieve, draft) en lugar de node1. Los gráficos legibles son gráficos operativos.

Agregar una prueba unitaria que invoque el gráfico compilado con un estado de fixture y verifique qué nodos se ejecutaron. La función compile() por sí sola no demuestra la completitud del proceso de negocio.

Preferir nodos deterministas para las pruebas de enrutamiento: insertar un LLM falso que devuelva llamadas a herramientas fijas para que el CI no necesite acceso a la red.

Documentar temprano la regla de autorización thread_id: encontrar un punto de control no implica permiso para leerlo.

Mantener las herramientas con efectos secundarios detrás de aristas de aprobación, incluso en prototipos de Parte 0: los hábitos se forman rápidamente.

Preferir nodos deterministas para las pruebas de enrutamiento: insertar un LLM falso que devuelva llamadas a herramientas fijas para que el CI no necesite acceso a la red.

Documente tempranamente la regla de autorización relacionada con thread_id: encontrar un punto de control no implica permiso para leerlo.

Mantenga las herramientas que generan efectos secundarios detrás de los puntos de aprobación, incluso en los prototipos de la Parte 0: los hábitos se forman rápidamente.

Prefiera nodos deterministas para las pruebas de enrutamiento: introduzca un LLM falso que devuelva llamadas a herramientas fijas para que el CI no necesite acceso a la red.

Documente tempranamente la regla de autorización relacionada con thread_id: encontrar un punto de control no implica permiso para leerlo.

Mantenga las herramientas que generan efectos secundarios detrás de los puntos de aprobación, incluso en los prototipos de la Parte 0: los hábitos se forman rápidamente.

Prefiera nodos deterministas para las pruebas de enrutamiento: introduzca un LLM falso que devuelva llamadas a herramientas fijas para que el CI no necesite acceso a la red.

Lecturas relacionadas