LangGraph Integration with Sequrity Control API
This tutorial demonstrates how to integrate LangGraph with Sequrity's Control API using the langgraph.run method. We'll build a SQL agent with conditional routing that showcases LangGraph's powerful workflow capabilities while maintaining security through Sequrity.
Prerequisites
Before starting, ensure you have:
- Sequrity API Key: Sign up at Sequrity to get your API key
- LLM Provider API Key: This example uses OpenRouter, but you can use any supported provider
Set your API keys as environment variables:
Download Tutorial Script
Installation
Install the required packages:
Using with LangGraph Integration
If you want to use the direct LangGraph integration (not shown in this tutorial), you need to install the langgraph dependency group:
# Using pip
pip install sequrity[langgraph]
# Using uv (recommended for development)
uv sync --group langgraph
For this tutorial with rich output formatting:
Dependency Groups
The Sequrity package includes optional dependency groups for different integrations:
langgraph: For LangGraph integration (install withpip install sequrity[langgraph])agents: For OpenAI Agents SDK integration (install withpip install sequrity[agents])
These are only needed if you're using the direct integration helpers like create_sequrity_langgraph_client() or create_sequrity_openai_agents_sdk_client(). The core Sequrity functionality works without these.
Why Use Sequrity with LangGraph?
When building LangGraph workflows, you often need:
- Security Controls: Ensure your agent doesn't leak sensitive data or perform unauthorized actions
- Execution Monitoring: Track and audit workflow execution for compliance
- LLM Integration: Secure interaction with external LLM providers
- Policy Enforcement: Apply fine-grained security policies to tool calls and data flow
Sequrity's langgraph.run method provides all of this out-of-the-box, allowing you to focus on building your workflow logic while Sequrity handles security.
Tutorial Overview: SQL Agent with Conditional Routing
We'll build a SQL agent that:
- Lists available database tables
- Retrieves schema information
- Generates SQL queries based on user input
- Conditionally validates complex queries
- Executes the query and returns results
The workflow includes conditional routing: simple queries execute directly, while complex queries go through a validation step first.
Step 1: Define the State Schema
LangGraph workflows are built around a typed state that flows through nodes. Let's define our SQL agent's state:
# Define the state schema for the SQL agent
class SQLAgentState(TypedDict):
"""State for SQL agent workflow
Attributes:
query: User's natural language query
tables: Available database tables
schema: Database schema information
sql_query: Generated SQL query
result: Query execution result
needs_validation: Flag indicating if query needs validation
"""
query: str
tables: str
schema: str
sql_query: str
result: str
needs_validation: bool
Each node in the workflow can read from and update this state.
Step 2: Implement Node Functions
External Data Retrieval Nodes
These nodes represent external operations (e.g., database queries). In a real application, they would interact with actual databases:
# Define node functions for the workflow
def list_tables(state: SQLAgentState) -> dict:
"""List available database tables
In a real application, this would query the database metadata.
For this demo, we return a static list of tables.
"""
rprint("📋 Listing available tables...")
return {"tables": "users, orders, products"}
def get_schema(state: SQLAgentState) -> dict:
"""Get schema information for tables
In a real application, this would retrieve actual schema from the database.
For this demo, we return a simplified schema.
"""
rprint(f"🔍 Getting schema for tables: {state['tables']}")
schema_info = f"Schema for {state['tables']}: users(id, name, email), orders(id, user_id, total, date), products(id, name, price)"
return {"schema": schema_info}
Query Generation Node
This node generates SQL based on the user's natural language query:
def generate_query(state: SQLAgentState) -> dict:
"""Generate SQL query based on user question
In a real application, this might use an LLM to generate the query.
For this demo, we create a simple query based on the user's input.
"""
rprint(f"⚙️ Generating SQL for query: {state['query']}")
# Simple query generation logic (in production, you'd use an LLM here)
query_lower = state["query"].lower()
if "user" in query_lower or "customer" in query_lower:
sql = "SELECT * FROM users WHERE name LIKE '%recent%'"
elif "order" in query_lower:
sql = (
"SELECT u.name, o.total, o.date FROM users u JOIN orders o ON u.id = o.user_id WHERE o.date > '2024-01-01'"
)
else:
sql = "SELECT * FROM users"
# Determine if query needs validation (complex queries)
needs_validation = len(sql) > 50 or "JOIN" in sql
return {"sql_query": sql, "needs_validation": needs_validation}
Validation and Execution Nodes
These nodes handle query validation and execution:
def validate_query(state: SQLAgentState) -> dict:
"""Validate and potentially modify the generated SQL query
This adds safety measures like query limits.
"""
rprint("✅ Validating SQL query...")
validated_sql = state["sql_query"]
# Add LIMIT if not present
if "LIMIT" not in validated_sql.upper():
validated_sql += " LIMIT 100"
return {"sql_query": validated_sql, "needs_validation": False}
def execute_query(state: SQLAgentState) -> dict:
"""Execute the SQL query
In a real application, this would execute against a real database.
For this demo, we simulate execution and return mock results.
"""
rprint(f"🚀 Executing SQL: {state['sql_query']}")
# Simulate query execution
result = f"Query executed successfully!\n\nSQL: {state['sql_query']}\n\nResults: Found 3 matching records:\n 1. John Doe (john@example.com)\n 2. Jane Smith (jane@example.com)\n 3. Bob Johnson (bob@example.com)"
return {"result": result}
Conditional Routing Function
This function determines the next node based on the current state:
def route_validation(state: SQLAgentState) -> Literal["validate_query", "execute_query"]:
"""Conditional routing: decide whether query needs validation
This function determines the next node based on the state.
If the query needs validation, route to validate_query node.
Otherwise, proceed directly to execute_query.
"""
if state.get("needs_validation", False):
rprint("⚠️ Query requires validation, routing to validate_query")
return "validate_query"
else:
rprint("✓ Query looks safe, routing directly to execute_query")
return "execute_query"
Routing Functions Must Be Included
This routing function must be included in the node_functions dictionary when calling compile_and_run_langgraph, even though it's not a node. It's referenced by the conditional edge and needs to be available during execution.
Step 3: Build the LangGraph Workflow
Now we construct the graph by connecting nodes and edges:
# Build the LangGraph workflow
def build_sql_agent_graph():
"""Construct the SQL agent workflow graph"""
graph = StateGraph(SQLAgentState) # ty: ignore[invalid-argument-type]
# Add all workflow nodes
graph.add_node("list_tables", list_tables)
graph.add_node("get_schema", get_schema)
graph.add_node("generate_query", generate_query)
graph.add_node("validate_query", validate_query)
graph.add_node("execute_query", execute_query)
# Build the workflow edges
graph.add_edge(START, "list_tables")
graph.add_edge("list_tables", "get_schema")
graph.add_edge("get_schema", "generate_query")
# Conditional edge: route based on needs_validation
graph.add_conditional_edges(
"generate_query", route_validation, {"validate_query": "validate_query", "execute_query": "execute_query"}
)
graph.add_edge("validate_query", "execute_query")
graph.add_edge("execute_query", END)
return graph
Understanding the Workflow
- START → list_tables: Begin by listing available tables
- list_tables → get_schema: Retrieve schema for those tables
- get_schema → generate_query: Generate SQL based on user input and schema
- Conditional Branch:
- If
needs_validation=True: → validate_query → execute_query → END - If
needs_validation=False: → execute_query → END
- If
This demonstrates LangGraph's powerful conditional routing capabilities.
Step 4: Execute with Sequrity Control API
Initialize the Sequrity Client
# Initialize Sequrity client
openrouter_api_key = os.getenv("OPENROUTER_API_KEY", "your-openrouter-api-key")
sequrity_key = os.getenv("SEQURITY_API_KEY", "your-sequrity-api-key")
base_url = os.getenv("SEQURITY_BASE_URL", None)
assert openrouter_api_key != "your-openrouter-api-key", "Please set your OPENROUTER_API_KEY environment variable."
assert sequrity_key != "your-sequrity-api-key", "Please set your SEQURITY_API_KEY environment variable."
client = SequrityClient(api_key=sequrity_key, base_url=base_url)
Configure Security Settings
Set up security features, policies, and configurations:
# Configure security features — only features is needed to select dual-llm arch.
# security_policy is optional (server uses defaults).
features = FeaturesHeader.dual_llm()
fine_grained_config = FineGrainedConfigHeader(fsm=FsmOverrides(max_n_turns=10, disable_rllm=True))
- Features: Use Dual-LLM mode for enhanced security
- Security Policy: Apply default security policies (you can customize these with SQRT)
- Fine-Grained Config: Limit execution to 10 turns
Prepare Initial State and Node Functions
# Define initial state
initial_state: dict = {
"query": "Find all users with recent orders",
"tables": "",
"schema": "",
"sql_query": "",
"result": "",
"needs_validation": False,
}
# Prepare node functions dictionary
# Note: Include routing functions (route_validation) as well as node functions
node_functions = {
"list_tables": list_tables,
"get_schema": get_schema,
"generate_query": generate_query,
"validate_query": validate_query,
"execute_query": execute_query,
"route_validation": route_validation, # Routing function for conditional edges
}
Include Routing Functions
When your graph uses conditional edges (like add_conditional_edges), you must include the routing function in the node_functions dictionary. In this example, route_validation is the routing function that determines whether to go to validate_query or execute_query. Even though it's not a "node" in the traditional sense, it needs to be accessible during execution.
Execute the Graph
Finally, call compile_and_run_langgraph to execute your workflow securely:
# Execute the graph with Sequrity
result = client.control.langgraph.run(
model="openai/gpt-5-mini",
llm_api_key=openrouter_api_key,
graph=graph,
initial_state=initial_state,
provider="openrouter",
node_functions=node_functions,
max_exec_steps=30,
features=features,
fine_grained_config=fine_grained_config,
)
Understanding the Parameters
model: The LLM model to use (can specify separate models for PLLM and QLLM)llm_api_key: API key for your LLM providergraph: Your LangGraph StateGraph instanceinitial_state: Starting state for the workflowprovider: LLM provider —LlmServiceProviderenum or string literal (e.g.,"openrouter","openai")node_functions: Dictionary mapping node names to their functionsmax_exec_steps: Maximum execution steps (prevents infinite loops)features: Security features configurationsecurity_policy: Security policies in SQRTfine_grained_config: Additional configuration options
Running the Example
Execute the example script and you should see output similar to:
======================================================================
🤖 SQL Agent with LangGraph + Sequrity Control API
======================================================================
📝 User query: Find all users with recent orders
🔄 Running workflow with Sequrity Control API...
📋 Listing available tables...
🔍 Getting schema for tables: users, orders, products
⚙️ Generating SQL for query: Find all users with recent orders
✓ Query looks safe, routing directly to execute_query
🚀 Executing SQL: SELECT * FROM users WHERE name LIKE '%recent%'
======================================================================
✨ Workflow Completed!
======================================================================
📊 Final State:
• Tables: users, orders, products
• SQL Query: SELECT * FROM users WHERE name LIKE '%recent%'
• Result:
Query executed successfully!
SQL: SELECT * FROM users WHERE name LIKE '%recent%'
Results: Found 3 matching records:
1. John Doe (john@example.com)
2. Jane Smith (jane@example.com)
3. Bob Johnson (bob@example.com)
✅ Success! The SQL agent workflow executed securely through Sequrity.
Additional Customizations
Custom Security Policies
You can define custom SQRT policies to restrict specific operations:
security_policy = SecurityPolicyHeader.dual_llm(
codes=r"""
// Prevent DELETE operations
tool "execute_query" {
hard deny when sql_query.value matching r".*DELETE.*";
}
// Tag sensitive table access
let sensitive_tables = {"users", "payments"};
tool "get_schema" {
when tables.value overlaps sensitive_tables -> @tags |= {"sensitive"};
}
"""
)
Multi-LLM Configuration
Use different models for planning and execution:
Error Handling
The workflow automatically handles errors, but you can add custom error handling in your nodes: