From ai-engineer
Provides LangGraph v1.0 development principles: deployment-first patterns, prebuilt components, and project structure for building and exporting agents.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ai-engineer:langgraphThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
If you are coding with LangGraph, follow these principles and patterns.
If you are coding with LangGraph, follow these principles and patterns.
Before creating any files, always search the codebase for existing LangGraph-related files:
graph.py, main.py, app.py, agent.py, workflow.py.compile(), StateGraph, create_react_agent, app =, graph exportsIf any LangGraph files exist: Follow the existing structure exactly. Do not create new agent.py files.
Only create agent.py when: Building from completely empty directory with zero existing LangGraph files.
agent.py at project root with compiled graph exported as applanggraph.json configuration file in the same directory as the graphTypedDict or Pydantic BaseModelCRITICAL: All LangGraph agents should be written for DEPLOYMENT unless otherwise specified.
app# Don't do this unless asked!
from langgraph.checkpoint.memory import MemorySaver
graph = create_react_agent(model, tools, checkpointer=MemorySaver())
agent.py if graphs are already exported elsewhere./agent.py # Main agent file, exports: app
./langgraph.json # LangGraph configuration
from langgraph.graph import StateGraph, START, END
# ... your state and node definitions ...
# Build your graph
graph_builder = StateGraph(YourState)
# ... add nodes and edges ...
# Export as 'app' for new agents from scratch
graph = graph_builder.compile()
app = graph # Required for new LangGraph agents
Always use prebuilt components when possible - they are deployment-ready and well-tested.
from langgraph.prebuilt import create_react_agent
# Simple, deployment-ready agent
graph = create_react_agent(
model=model,
tools=tools,
prompt="Your agent instructions here"
)
app = graph
from langgraph_supervisor import create_supervisor
supervisor = create_supervisor(
agents=[agent1, agent2],
model=model,
prompt="You coordinate between agents..."
)
app = supervisor.compile()
Documentation: https://langchain-ai.github.io/langgraph/reference/supervisor/
from langgraph_swarm import create_swarm, create_handoff_tool
alice = create_react_agent(
model,
[tools, create_handoff_tool(agent_name="Bob")],
prompt="You are Alice.",
name="Alice",
)
workflow = create_swarm([alice, bob], default_active_agent="Alice")
app = workflow.compile()
Documentation: https://langchain-ai.github.io/langgraph/reference/swarm/
LLM MODEL PRIORITY (follow this order):
# 1. PREFER: Anthropic
from langchain_anthropic import ChatAnthropic
model = ChatAnthropic(model="claude-3-5-sonnet-20241022")
# 2. SECOND CHOICE: OpenAI
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-4o")
# 3. THIRD CHOICE: Google
from langchain_google_genai import ChatGoogleGenerativeAI
model = ChatGoogleGenerativeAI(model="gemini-1.5-pro")
NOTE: Assume API keys are available in environment. During development, ignore missing key errors.
# CORRECT: Extract message content properly
result = agent.invoke({"messages": state["messages"]})
if result.get("messages"):
final_message = result["messages"][-1] # This is a message object
content = final_message.content # This is the string content
# WRONG: Treating message objects as strings
content = result["messages"][-1] # This is an object, not a string!
if content.startswith("Error"): # Will fail - objects don't have startswith()
def my_node(state: State) -> Dict[str, Any]:
# Do work...
return {
"field_name": extracted_string, # Always return dict updates
"messages": updated_message_list # Not the raw messages
}
Interrupts only work with stream_mode="updates", not stream_mode="values"
In "updates" mode, events are structured as {node_name: node_data, ...}
Check for "__interrupt__" key directly in the event object
Iterate through event.items() to access individual node outputs
Interrupts appear as event["__interrupt__"] containing a tuple of Interrupt objects
Access interrupt data via interrupt_obj.value where interrupt_obj = event["__interrupt__"][0]
Documentation:
Use interrupt() when you need:
# CORRECT: interrupt() pauses execution for human input
interrupt("Please confirm action")
# Execution resumes after human provides input through platform
# AVOID: Treating interrupt() as synchronous
result = interrupt("Please confirm action") # Wrong - doesn't return values
if result == "yes": # This won't work
proceed()
interrupt() usage: It pauses execution, doesn't return valueslanggraph.json configurationstate.get("field", "")[-1].method() without verifying typesWhen building integrations, always start with debugging:
# Temporary debugging for new integrations
def my_integration_function(input_data, config):
print(f"=== DEBUG START ===")
print(f"Input type: {type(input_data)}")
print(f"Input data: {input_data}")
print(f"Config type: {type(config)}")
print(f"Config data: {config}")
# Process...
result = process(input_data, config)
print(f"Result type: {type(result)}")
print(f"Result data: {result}")
print(f"=== DEBUG END ===")
return result
Always verify the receiving end actually uses configuration:
# WRONG: Assuming config is used
def my_node(state: State) -> Dict[str, Any]:
response = llm.invoke(state["messages"])
return {"messages": [response]}
# CORRECT: Actually using config
def my_node(state: State, config: RunnableConfig) -> Dict[str, Any]:
# Extract configuration
configurable = config.get("configurable", {})
system_prompt = configurable.get("system_prompt", "Default prompt")
# Use configuration in messages
messages = [SystemMessage(content=system_prompt)] + state["messages"]
response = llm.invoke(messages)
return {"messages": [response]}
# AVOID: LLM call + tool execution in same node
def bad_node(state):
ai_response = model.invoke(state["messages"]) # LLM call
tool_result = tool_node.invoke({"messages": [ai_response]}) # Tool execution
return {"messages": [...]} # Mixed concerns!
# PREFER: Separate nodes for separate concerns
def llm_node(state):
return {"messages": [model.invoke(state["messages"])]}
def tool_node(state):
return ToolNode(tools).invoke(state)
# Connect with edges
workflow.add_edge("llm", "tools")
# AVOID: Unnecessary complexity
workflow = StateGraph(ComplexState)
workflow.add_node("agent", agent_node)
workflow.add_node("tools", tool_node)
# ... 20 lines of manual setup when create_react_agent would work
# AVOID: Too many state fields
class State(TypedDict):
messages: List[BaseMessage]
user_input: str
current_step: int
metadata: Dict[str, Any]
history: List[Dict]
# ... many more fields
# PREFER: Use MessagesState when sufficient
from langgraph.graph import MessagesState
# AVOID: Wrong variable names or missing export
compiled_graph = workflow.compile() # Wrong name
# Missing: app = compiled_graph
# AVOID: Treating interrupt() as synchronous
result = interrupt("Please confirm action") # Wrong - doesn't return values
if result == "yes": # This won't work
proceed()
# CORRECT: interrupt() pauses execution for human input
interrupt("Please confirm action")
# Execution resumes after human provides input through platform
Reference: https://langchain-ai.github.io/langgraph/concepts/streaming/#whats-possible-with-langgraph-streaming
When working with LangGraph nodes that involve LLM calls, always use structured output with Pydantic dataclasses:
with_structured_output() method for LLM calls that need specific response formatsExample: llm.with_structured_output(MyPydanticModel).invoke(messages) instead of raw string parsing
Always use documentation tools before implementing LangGraph code (the API evolves rapidly):
../, go one level up in the URL hierarchy../../, go two levels up, then append the relative pathhttps://langchain-ai.github.io/langgraph/tutorials/get-started/langgraph-platform/setup/ with link ../../langgraph-platform/local-server
https://langchain-ai.github.io/langgraph/tutorials/get-started/https://langchain-ai.github.io/langgraph/tutorials/get-started/langgraph-platform/local-servernpx claudepluginhub p/hanamizuki-ai-engineer-plugins-ai-engineerBuilds production-grade stateful AI agents with LangGraph, covering graph construction, state management, cycles, checkpointing, and human-in-the-loop patterns.
Provides expertise in LangGraph for building stateful multi-actor AI applications with graph construction, state management, cycles, persistence, human-in-the-loop patterns, and ReAct agents.
Provides LangGraph 1.x LTS patterns for state management, routing, parallel execution, supervisor-worker, tool calling, checkpointing, human-in-loop, streaming, subgraphs, and functional API. Use for LangGraph pipelines, multi-agent systems, AI workflows.