Introduction
Large Language Models (LLMs) are exceptional at processing natural language, but they are inherently disconnected from the real world. They cannot check a database, send an email, or calculate a complex financial derivative on their own. Function Calling (also known as Tool Use) is the architectural pattern that bridges this gap. It allows an LLM to recognize when a task requires external action, structure a request for that action in a standardized format (usually JSON), and then integrate the result back into its reasoning process.
In enterprise environments, function calling is not just about convenience; it is about reliability and integration. A naive implementation where an LLM "guesses" how to call an API is prone to errors and security risks. Instead, enterprises require a structured, stateful approach where function schemas are strictly defined, inputs are validated, and execution is orchestrated within a secure workflow. This article explores function calling through the lens of an Enterprise Inventory Management System. We will build a multi-agent Proof of Concept (POC) using LangGraph for orchestration, Pydantic for strict schema validation, Graph RAG for context-aware tool selection, and a FastAPI/React stack for deployment. This architecture ensures that every function call is intentional, validated, and auditable.
Real-Time Use Case: Intelligent Inventory Assistant
Consider a global retail company with a distributed inventory system. Warehouse managers need to query stock levels, reserve items for high-priority orders, and trigger restocking alerts.
A manager might ask: "Check the stock for 'Wireless Headphones' in the New York warehouse. If there are fewer than 50 units, reserve 20 for Order #9901 and alert the procurement team."
This single request requires multiple function calls:
check_stock(item, location)reserve_inventory(order_id, item, quantity)(Conditional on step 1)send_alert(team, message)(Conditional on step 1)
Our system will use LangGraph to manage this multi-step logic, ensuring that the second and third functions only execute if the first returns a specific result.
Architecture Overview
Schema Registry: Uses Pydantic models to define strict function signatures, ensuring type safety.
Graph RAG (Neo4j): Maps business entities (like warehouses and products) to available functions, helping the agent choose the right tool for the context.
LangGraph Orchestrator: Manages the "Reason-Act-Observation" loop, maintaining state across multiple function calls.
Executor Node: Safely executes the Python functions based on the LLM's structured output.
FastAPI & React: Provides the interface for managers to interact with the system and view the execution trace.
Step-by-Step Implementation
Step 1: Environment Setup
# requirements.txt
langgraph==0.2.0
langchain-community==0.3.0
neo4j==5.14.0
fastapi==0.109.0
uvicorn==0.27.0
pydantic==2.6.0
openai==1.12.0
Step 2: Defining Function Schemas with Pydantic
We define our tools as Pydantic models. This allows us to use FastAPI's dependency injection and validation features later.
from pydantic import BaseModel, Field
from typing import Optional
class CheckStockInput(BaseModel):
item_name: str = Field(..., description="Name of the product")
warehouse_location: str = Field(..., description="City or ID of the warehouse")
class ReserveInventoryInput(BaseModel):
order_id: str = Field(..., description="Unique order identifier")
item_name: str = Field(..., description="Product to reserve")
quantity: int = Field(..., gt=0, description="Number of units to reserve")
class SendAlertInput(BaseModel):
team: str = Field(..., description="Target team (e.g., 'procurement')")
message: str = Field(..., description="Alert content")
Step 3: The Function Executor
These are the actual business logic functions that the LLM will trigger.
def check_stock(item_name: str, warehouse_location: str) -> dict:
"""Simulates a database lookup"""
# In production, this would query SQL/NoSQL
mock_db = {"Wireless Headphones": {"New York": 45, "London": 120}}
qty = mock_db.get(item_name, {}).get(warehouse_location, 0)
return {"item": item_name, "location": warehouse_location, "quantity": qty}
def reserve_inventory(order_id: str, item_name: str, quantity: int) -> dict:
"""Simulates reserving stock"""
return {"status": "success", "order_id": order_id, "reserved": quantity, "item": item_name}
def send_alert(team: str, message: str) -> dict:
"""Simulates sending a notification"""
return {"status": "sent", "team": team, "message_preview": message[:50]}
Step 4: Graph RAG for Contextual Tool Selection
We use Neo4j to store which functions are relevant to which business domains.
from neo4j import GraphDatabase
class ToolContextRetriever:
def __init__(self):
self.driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))
def get_relevant_tools(self, domain: str) -> list[str]:
"""Retrieve tool names associated with a business domain"""
with self.driver.session() as session:
result = session.run("""
MATCH (d:Domain {name: $domain})-[:USES_TOOL]->(t:Tool)
RETURN t.name
""", domain=domain)
return [record['t.name'] for record in result]
Step 5: Building the LangGraph Multi-Agent Workflow
The core of our system is a stateful graph that handles the reasoning and execution loop.
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from typing import TypedDict, List, Annotated
import operator
import json
llm = ChatOpenAI(model="gpt-4-turbo", temperature=0)
class AgentState(TypedDict):
messages: Annotated[List[dict], operator.add]
current_step: str
last_function_result: Optional[dict]
execution_trace: List[str]
def reason_node(state: AgentState) -> AgentState:
"""LLM decides which function to call next"""
# In a real app, we would bind tools to the LLM here
# For this POC, we simulate the LLM's decision based on state
if not state['last_function_result']:
# First step: Check stock
func_call = {"name": "check_stock", "arguments": {"item_name": "Wireless Headphones", "warehouse_location": "New York"}}
elif state['last_function_result'].get('quantity', 100) < 50:
# Condition met: Reserve and Alert
func_call = {"name": "reserve_inventory", "arguments": {"order_id": "9901", "item_name": "Wireless Headphones", "quantity": 20}}
else:
func_call = None
if func_call:
state['messages'].append({"role": "assistant", "content": f"Calling {func_call['name']}"})
state['current_step'] = func_call['name']
else:
state['current_step'] = "complete"
return state
def execute_node(state: AgentState) -> AgentState:
"""Executes the function and updates state"""
step = state['current_step']
result = None
if step == "check_stock":
result = check_stock("Wireless Headphones", "New York")
elif step == "reserve_inventory":
result = reserve_inventory("9901", "Wireless Headphones", 20)
if result:
state['last_function_result'] = result
state['execution_trace'].append(f"Executed {step}: {result}")
state['messages'].append({"role": "function", "content": json.dumps(result)})
return state
# Build Graph
workflow = StateGraph(AgentState)
workflow.add_node("reason", reason_node)
workflow.add_node("execute", execute_node)
workflow.set_entry_point("reason")
workflow.add_conditional_edges("reason",
lambda s: "execute" if s['current_step'] != "complete" else END)
workflow.add_edge("execute", "reason")
app = workflow.compile()
Step 6: FastAPI Backend Integration
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
api_app = FastAPI(title="Inventory Function Caller")
api_app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"])
@api_app.post("/manage-inventory")
async def manage_inventory(query: str):
initial_state = AgentState(
messages=[{"role": "user", "content": query}],
current_step="",
last_function_result=None,
execution_trace=[]
)
result = await app.ainvoke(initial_state)
return {
"final_status": result['current_step'],
"trace": result['execution_trace'],
"last_result": result['last_function_result']
}
Step 7: React Frontend Component
import React, { useState } from 'react';
import axios from 'axios';
const InventoryAgent = () => {
const [query, setQuery] = useState('');
const [trace, setTrace] = useState([]);
const runAgent = async () => {
const res = await axios.post('http://localhost:8000/manage-inventory', {
query: query || "Check stock and reserve if low"
});
setTrace(res.data.trace);
};
return (
<div className="p-4 max-w-lg mx-auto">
<h2 className="text-xl font-bold">Inventory Management Agent</h2>
<button onClick={runAgent} className="bg-indigo-600 text-white p-2 rounded mt-2">
Run Automated Workflow
</button>
<div className="mt-4">
<h3 className="font-semibold">Execution Trace:</h3>
<ul className="list-disc pl-5">
{trace.map((t, i) => <li key={i} className="text-sm">{t}</li>)}
</ul>
</div>
</div>
);
};
export default InventoryAgent;
Conclusion
Function calling transforms LLMs from passive knowledge bases into active components of enterprise software. By combining strict Pydantic schemas with the orchestration power of LangGraph, we ensure that these calls are reliable, secure, and context-aware. The addition of Graph RAG allows the system to intelligently select tools based on business topology, while the stateful nature of the graph enables complex, multi-step workflows like conditional inventory management. This architecture provides the robustness required for mission-critical business operations, paving the way for truly autonomous enterprise agents.

Join the conversation! Your thoughts help the community grow.