"""
Pattern 93: Function Calling Pattern
Agents directly call Python functions based on natural language requests.
Uses OpenAI's function calling API to map queries to function invocations.
"""
from typing import Dict, Any, List, Optional, Callable
from dataclasses import dataclass, field
from datetime import datetime
import json
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.utils.function_calling import convert_to_openai_function
@dataclass
class FunctionDefinition:
"""Definition of callable function."""
name: str
description: str
function: Callable
parameters: Dict[str, Any]
returns: str
@dataclass
class FunctionCall:
"""Record of function call."""
call_id: str
function_name: str
arguments: Dict[str, Any]
result: Any
success: bool
timestamp: datetime = field(default_factory=datetime.now)
error: Optional[str] = None
# Example functions that agents can call
def calculate_discount(price: float, discount_percent: float) -> float:
"""Calculate final price after discount.
Args:
price: Original price
discount_percent: Discount percentage (0-100)
Returns:
Final price after discount
"""
return price * (1 - discount_percent / 100)
def schedule_meeting(date: str, time: str, attendees: List[str], topic: str) -> str:
"""Schedule a meeting.
Args:
date: Meeting date (YYYY-MM-DD)
time: Meeting time (HH:MM)
attendees: List of attendee names
topic: Meeting topic
Returns:
Confirmation message
"""
return f"Meeting scheduled for {date} at {time} with {len(attendees)} attendees. Topic: {topic}"
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email.
Args:
to: Recipient email address
subject: Email subject
body: Email body
Returns:
Confirmation message
"""
return f"Email sent to {to} with subject '{subject}'"
def query_database(table: str, filters: Dict[str, Any]) -> List[Dict]:
"""Query database table.
Args:
table: Table name
filters: Dictionary of filter conditions
Returns:
List of matching records
"""
# Simulated database query
return [
{"id": 1, "name": "Record 1", "value": 100},
{"id": 2, "name": "Record 2", "value": 200}
]
def create_report(report_type: str, data_source: str, format: str = "pdf") -> str:
"""Generate a report.
Args:
report_type: Type of report (sales, analytics, etc.)
data_source: Data source name
format: Output format (pdf, excel, html)
Returns:
Report generation confirmation
"""
return f"Generated {report_type} report from {data_source} in {format} format"
class FunctionCallingAgent:
"""Agent that can call functions based on natural language."""
def __init__(self, name: str, model_name: str = "gpt-4"):
self.name = name
self.llm = ChatOpenAI(model=model_name, temperature=0)
self.functions: Dict[str, FunctionDefinition] = {}
self.call_history: List[FunctionCall] = []
def register_function(self, func_def: FunctionDefinition):
"""Register a function that can be called."""
self.functions[func_def.name] = func_def
def execute_query(self, query: str) -> Dict[str, Any]:
"""Execute natural language query by calling appropriate functions."""
# Prepare function descriptions for LLM
functions_for_llm = []
for func_def in self.functions.values():
functions_for_llm.append({
"name": func_def.name,
"description": func_def.description,
"parameters": func_def.parameters
})
# Create LLM with function calling
llm_with_functions = self.llm.bind(functions=functions_for_llm)
prompt = ChatPromptTemplate.from_messages([
("system", f"You are {self.name}, an AI assistant that can call functions to help users. "
f"Analyze the user's request and call the appropriate function."),
("human", "{query}")
])
chain = prompt | llm_with_functions
try:
response = chain.invoke({"query": query})
# Check if function was called
if hasattr(response, 'additional_kwargs') and 'function_call' in response.additional_kwargs:
function_call = response.additional_kwargs['function_call']
function_name = function_call['name']
arguments = json.loads(function_call['arguments'])
# Execute the function
if function_name in self.functions:
func_def = self.functions[function_name]
result = func_def.function(**arguments)
# Record call
call = FunctionCall(
call_id=f"call_{len(self.call_history)}",
function_name=function_name,
arguments=arguments,
result=result,
success=True
)
self.call_history.append(call)
return {
"success": True,
"function_called": function_name,
"arguments": arguments,
"result": result,
"agent": self.name
}
else:
return {
"success": False,
"error": f"Function {function_name} not registered"
}
else:
# No function called, just return text response
return {
"success": True,
"response": str(response.content),
"function_called": None
}
except Exception as e:
call = FunctionCall(
call_id=f"call_{len(self.call_history)}",
function_name="unknown",
arguments={},
result=None,
success=False,
error=str(e)
)
self.call_history.append(call)
return {
"success": False,
"error": str(e)
}
class FunctionCallingPattern:
"""
Function Calling Pattern - Natural language to function execution.
Agents interpret natural language requests and automatically call
appropriate Python functions with correct parameters.
"""
def __init__(self):
self.agents: Dict[str, FunctionCallingAgent] = {}
self.available_functions: Dict[str, FunctionDefinition] = {}
def register_function(self, func: Callable, description: str):
"""Register a function for agents to call."""
# Extract function signature
import inspect
sig = inspect.signature(func)
# Build parameter schema
parameters = {
"type": "object",
"properties": {},
"required": []
}
for param_name, param in sig.parameters.items():
if param_name != 'self':
param_type = "string" # Simplified
if param.annotation in [int, float]:
param_type = "number"
elif param.annotation == bool:
param_type = "boolean"
elif param.annotation == list:
param_type = "array"
parameters["properties"][param_name] = {
"type": param_type,
"description": f"Parameter {param_name}"
}
if param.default == inspect.Parameter.empty:
parameters["required"].append(param_name)
func_def = FunctionDefinition(
name=func.__name__,
description=description,
function=func,
parameters=parameters,
returns="Function result"
)
self.available_functions[func.__name__] = func_def
def create_agent(self, name: str, function_names: Optional[List[str]] = None) -> FunctionCallingAgent:
"""Create agent with access to specified functions."""
agent = FunctionCallingAgent(name)
# Register functions to agent
if function_names:
for func_name in function_names:
if func_name in self.available_functions:
agent.register_function(self.available_functions[func_name])
else:
# Register all functions
for func_def in self.available_functions.values():
agent.register_function(func_def)
self.agents[name] = agent
return agent
def execute_query(self, agent_name: str, query: str) -> Dict[str, Any]:
"""Execute query with specified agent."""
if agent_name not in self.agents:
return {"success": False, "error": "Agent not found"}
agent = self.agents[agent_name]
result = agent.execute_query(query)
if result.get("success"):
if result.get("function_called"):
print(f"\n ✓ {agent_name} called {result['function_called']}")
print(f" Arguments: {result['arguments']}")
print(f" Result: {result['result']}")
else:
print(f"\n ℹ️ {agent_name}: {result.get('response', 'No response')[:80]}")
else:
print(f"\n ✗ {agent_name}: {result.get('error', 'Unknown error')}")
return result
def run(self, scenario: str) -> Dict[str, Any]:
"""Execute function calling pattern."""
total_calls = sum(len(agent.call_history) for agent in self.agents.values())
successful_calls = sum(
sum(1 for call in agent.call_history if call.success)
for agent in self.agents.values()
)
return {
"pattern": "FunctionCalling",
"total_agents": len(self.agents),
"available_functions": len(self.available_functions),
"total_calls": total_calls,
"successful_calls": successful_calls,
"scenario": scenario
}
# Example usage
if __name__ == "__main__":
print("=== Function Calling Pattern Demo ===\n")
# Create pattern
pattern = FunctionCallingPattern()
# Register functions
print("1. Registering callable functions...")
pattern.register_function(calculate_discount, "Calculate final price after applying discount")
pattern.register_function(schedule_meeting, "Schedule a meeting with date, time, and attendees")
pattern.register_function(send_email, "Send an email to a recipient")
pattern.register_function(query_database, "Query database table with filters")
pattern.register_function(create_report, "Generate a report from data source")
print(f" Registered {len(pattern.available_functions)} functions")
# Create agents
print("\n2. Creating function-calling agents...")
assistant = pattern.create_agent("VirtualAssistant")
print(f" VirtualAssistant: {len(assistant.functions)} functions available")
# Execute natural language queries
print("\n3. Executing natural language queries...")
queries = [
"Calculate the final price for an item that costs $100 with a 20% discount",
"Schedule a meeting for tomorrow at 2pm with Alice, Bob, and Carol to discuss Q4 planning",
"Send an email to john@example.com with subject 'Project Update' and body 'The project is on track'",
"Query the customers table and filter by status active",
"Create a sales report from the Q4 data in PDF format"
]
for query in queries:
print(f"\n Query: {query}")
pattern.execute_query("VirtualAssistant", query)
# Summary
print("\n4. Execution Summary:")
summary = pattern.run("Virtual assistant with function calling")
print(f" Total Functions: {summary['available_functions']}")
print(f" Function Calls: {summary['total_calls']}")
print(f" Successful: {summary['successful_calls']}")
print("\n5. Call History:")
for call in assistant.call_history:
status = "✓" if call.success else "✗"
print(f" {status} {call.function_name}: {call.arguments}")
print("\n✓ Function calling pattern executed natural language to function calls!")
Agentic Architecture Pattern: Function Calling
Pattern 93: 93 Function Calling – Line-by-Line Explanation
Overview
Pattern 93: Function Calling Pattern
Agents directly call Python functions based on natural language requests. Uses OpenAI’s function calling API to map queries to function invocations.
Code Structure Analysis
File Information
- Pattern Number: 93
- Pattern Name: 93 Function Calling
- File Path:
patterns\tool_resource\93_function_calling.py - Total Lines: 368
Detailed Code Explanation
Module Docstring
"""
Pattern 93: Function Calling Pattern
Agents directly call Python functions based on natural language requests.
Uses OpenAI's function calling API to map queries to function invocations.
"""
Explanation: This module docstring describes the purpose and key features of the 93 Function Calling pattern.
Imports Section
from typing import Dict, Any, List, Optional, Callable
from dataclasses import dataclass, field
from datetime import datetime
import json
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.utils.function_calling import convert_to_openai_function
import inspect # First 20 imports shown
Explanation: The module imports necessary dependencies:
– from typing import Dict, Any, List, Optional, Callable: Type hints for better code documentation
– from dataclasses import dataclass, field: Data class decorators for clean data structures
– from datetime import datetime: Date and time utilities
– import json: JSON parsing and serialization
– from langchain_core.prompts import ChatPromptTemplate: LangChain framework integration
– from langchain_openai import ChatOpenAI: LangChain framework integration
– from langchain_core.utils.function_calling import convert_to_openai_function: LangChain framework integration
Classes
FunctionDefinition (Line 18)
Description: Definition of callable function.
FunctionCall (Line 28)
Description: Record of function call.
FunctionCallingAgent (Line 113)
Description: Agent that can call functions based on natural language.
Methods:
__init__ (Line 116)
def __init__(self, name, model_name):
"""No method docstring."""
Purpose: No method docstring.
Parameters:
– self: Instance reference
– name: Identifier or label
– model_name: LLM model to use (e.g., “gpt-4”)
register_function (Line 122)
def register_function(self, func_def):
"""Register a function that can be called."""
Purpose: Register a function that can be called.
Parameters:
– self: Instance reference
– func_def: Parameter for func_def
execute_query (Line 126)
def execute_query(self, query):
"""Execute natural language query by calling appropriate functions."""
Purpose: Execute natural language query by calling appropriate functions.
Parameters:
– self: Instance reference
– query: Parameter for query
FunctionCallingPattern (Line 210)
Description: Function Calling Pattern – Natural language to function execution.
Agents interpret natural language requests and automatically call appropriate Python functions with correct parameters.
Methods:
__init__ (Line 218)
def __init__(self):
"""No method docstring."""
Purpose: No method docstring.
Parameters:
– self: Instance reference
register_function (Line 222)
def register_function(self, func, description):
"""Register a function for agents to call."""
Purpose: Register a function for agents to call.
Parameters:
– self: Instance reference
– func: Parameter for func
– description: Parameter for description
create_agent (Line 263)
def create_agent(self, name, function_names):
"""Create agent with access to specified functions."""
Purpose: Create agent with access to specified functions.
Parameters:
– self: Instance reference
– name: Identifier or label
– function_names: Parameter for function_names
execute_query (Line 280)
def execute_query(self, agent_name, query):
"""Execute query with specified agent."""
Purpose: Execute query with specified agent.
Parameters:
– self: Instance reference
– agent_name: Parameter for agent_name
– query: Parameter for query
run (Line 300)
def run(self, scenario):
"""Execute function calling pattern."""
Purpose: Execute function calling pattern.
Parameters:
– self: Instance reference
– scenario: Parameter for scenario
Functions
calculate_discount (Line 40)
def calculate_discount(price, discount_percent):
"""Calculate final price after discount.
Args:
price: Original price
discount_percent: Discount percentage (0-100)
Returns:
Final price after discount"""
Purpose: Calculate final price after discount.
Args: price: Original price discount_percent: Discount percentage (0-100)
Returns: Final price after discount
schedule_meeting (Line 53)
def schedule_meeting(date, time, attendees, topic):
"""Schedule a meeting.
Args:
date: Meeting date (YYYY-MM-DD)
time: Meeting time (HH:MM)
attendees: List of attendee names
topic: Meeting topic
Returns:
Confirmation message"""
Purpose: Schedule a meeting.
Args: date: Meeting date (YYYY-MM-DD) time: Meeting time (HH:MM) attendees: List of attendee names topic: Meeting topic
Returns: Confirmation message
send_email (Line 68)
def send_email(to, subject, body):
"""Send an email.
Args:
to: Recipient email address
subject: Email subject
body: Email body
Returns:
Confirmation message"""
Purpose: Send an email.
Args: to: Recipient email address subject: Email subject body: Email body
Returns: Confirmation message
query_database (Line 82)
def query_database(table, filters):
"""Query database table.
Args:
table: Table name
filters: Dictionary of filter conditions
Returns:
List of matching records"""
Purpose: Query database table.
Args: table: Table name filters: Dictionary of filter conditions
Returns: List of matching records
create_report (Line 99)
def create_report(report_type, data_source, format):
"""Generate a report.
Args:
report_type: Type of report (sales, analytics, etc.)
data_source: Data source name
format: Output format (pdf, excel, html)
Returns:
Report generation confirmation"""
Purpose: Generate a report.
Args: report_type: Type of report (sales, analytics, etc.) data_source: Data source name format: Output format (pdf, excel, html)
Returns: Report generation confirmation
Design Patterns Used
- Strategy Pattern: Different execution strategies for various scenarios
- State Pattern: Managing different states in the workflow
- Chain of Responsibility: Sequential processing through workflow nodes
- Observer Pattern: Tracking and monitoring pattern execution
- Factory Pattern: Creating instances with proper configuration
Usage Example
# Initialize the pattern
pattern = FunctionDefinition(name="example", model_name="gpt-4")
# Configure and execute
result = pattern.execute(data={"key": "value"})
# Check results
print(result)
Key Concepts
- LLM Integration: Uses LangChain/OpenAI for intelligent decision-making
- Workflow Orchestration: LangGraph manages execution flow through nodes
- State Management: TypedDict defines workflow state structure
- Error Handling: Comprehensive error handling and fallback mechanisms
- Extensibility: Designed for easy customization and extension
Extension Points
This pattern can be extended by: 1. Adding custom workflow nodes 2. Implementing additional validation logic 3. Integrating with external services 4. Customizing LLM prompts and responses 5. Adding monitoring and logging capabilities
Leave a Reply