đ¯ What You'll Learn Today
LangGraph Tutorial: Tool Execution with Configuration Management - Unit 2.1 Exercise 3
Try It Yourself
đĸ Joint Initiative
This tutorial is part of a collaboration between
AI Product Engineer
and
Nebius Academy
.
This tutorial demonstrates how to implement tool execution in LangGraph with proper configuration management and API key handling. We combine robust tool execution with secure configuration management.
Key Concepts Covered
- Configuration Management
- Tool Execution Flow
- Error Handling
- State Management
import json
import os
from typing import Annotated, Any, TypedDict
!pip install langchain-core
!pip install langgraph
!pip install pydantic-settings
from langchain_community.tools import TavilySearchResults
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages
from pydantic_settings import BaseSettingsStep 1: Configuration Setup
We establish our configuration management using Pydantic.
Why This Matters
Proper configuration management is crucial because
- Ensures secure API key handling
- Provides consistent tool setup
- Supports different environments
- Enables easy testing
Debug Tips
- Configuration Issues:
- Verify environment variables
- Check settings initialization
- Monitor API key handling
- Test fallback behavior
MOCK_TAVILY_KEY = "mock_tavily_api_key_12345"
class Settings(BaseSettings):
"""Configuration management for tool execution.
This class manages tool configuration with:
1. API key handling
2. Environment variable support
3. Default value management
Attributes:
tavily_api_key: API key for Tavily integration
"""
tavily_api_key: str = MOCK_TAVILY_KEYStep 2: State Definition
We define our state structure for tool execution.
Why This Matters
Proper state structure is crucial because
- Maintains execution context
- Tracks tool operations
- Supports error recovery
- Enables debugging
class State(TypedDict):
"""State container for tool execution.
This state implementation tracks:
1. Message history with special handling
2. Tool call specifications
3. Tool execution results
Attributes:
messages: Conversation history with proper annotation
tool_calls: Tool call specifications
tool_outputs: Results from tool executions
"""
messages: Annotated[list[BaseMessage], add_messages]
tool_calls: list[dict]
tool_outputs: list[Any]Step 3: Tool Executor Implementation
We implement the tool execution logic with proper configuration handling.
Why This Matters
Robust tool execution is crucial because
- Ensures reliable operations
- Handles errors gracefully
- Maintains consistent output
- Supports debugging
def setup_tool_environment():
"""Initialize the tool environment with configuration.
Returns:
tuple: Settings and tool instance
"""
settings = Settings()
os.environ["TAVILY_API_KEY"] = settings.tavily_api_key
tavily_tool = TavilySearchResults()
return settings, tavily_toolsettings = Settings()
os.environ["TAVILY_API_KEY"] = settings.tavily_api_key
tavily_tool = TavilySearchResults()
return settings, tavily_tool
def tool_executor(state: State) -> State:
"""Execute tools with proper configuration and error handling.
Args:
state: Current system state with tool calls
Returns:
Updated state with tool outputs
"""
if not state.get("tool_calls"):
return {"tool_outputs": []}
# Setup tool environment
_, tavily_tool = setup_tool_environment()
tool_call = state["tool_calls"][-1]
try:
if tool_call["tool_name"] == "TavilySearchResults":
output = tavily_tool.invoke(tool_call["args"])
return {"tool_outputs": [json.dumps(output)]}
except Exception as e:
return {"tool_outputs": [json.dumps({"error": str(e)})]}
return {"tool_outputs": []}Step 4: Usage Demonstration
Example showing the complete tool execution flow.
Debug Tips
- Testing Flow:
- Verify configuration loading
- Check tool initialization
- Monitor execution results
- Validate error handling
def demonstrate_tool_execution():
"""Demonstrates the complete tool execution workflow."""
# Initialize test state
initial_state = {
"tool_calls": [
{"tool_name": "TavilySearchResults", "args": {"query": "capital of France"}}
],
"messages": [],
"tool_outputs": [],
}
# Execute tool
result = tool_executor(initial_state)Display results
print("Tool Execution Results:")
print(f"Tool output received: {bool(result['tool_outputs'])}")
if result["tool_outputs"]:
print("Output structure:", json.dumps(result["tool_outputs"][0], indent=2))
initial_state = {
"tool_calls": [
{"tool_name": "TavilySearchResults", "args": {"query": "capital of France"}}
],
"messages": [],
"tool_outputs": [],
}Execute tool
result = tool_executor(initial_state)Display results
print("Tool Execution Results:")
print(f"Tool output received: {bool(result['tool_outputs'])}")
if result["tool_outputs"]:
print("Output structure:", json.dumps(result["tool_outputs"][0], indent=2))
if __name__ == "__main__":
demonstrate_tool_execution()Common Pitfalls
- Missing API key configuration
- Improper environment setup
- Incorrect error handling
- Poor state management
Key Takeaways
- Configuration: Proper settings management is essential
- Tool Setup: Clean initialization process
- Execution: Robust error handling
- State: Consistent state management
Next Steps
- Implement real API key management
- Add comprehensive error handling
- Enhance configuration options
- Add execution logging
- Implement security measures
Expected Output
Tool Execution Results
Tool output received: True
Output structure: {
"query": "capital of France",
"results": [...]
}

