INTRODUCTION
Large Language Models have revolutionized how humans interact with computational systems. Rather than requiring users to learn specific syntax or programming languages, LLMs enable natural language interfaces where users can describe their computational needs in plain English. However, LLMs themselves are not designed to perform precise mathematical calculations. They excel at understanding intent, reasoning about problems, and generating human-like text, but they struggle with arithmetic precision and complex mathematical operations.
The solution is to combine the natural language understanding capabilities of LLMs with specialized computational tools. This hybrid approach allows users to express their needs naturally while ensuring that actual calculations are performed by reliable, precise computational engines. The LLM acts as an intelligent orchestrator, interpreting user requests, determining which computational tools are needed, invoking those tools with appropriate parameters, and presenting results in a human-friendly format.
This article describes how to build such a system that handles mathematics, statistics, physics, chemistry, business calculations, and financial analysis. The architecture uses tool calling, where the LLM can invoke external functions to perform specific computational tasks. Each domain has specialized calculator implementations that handle the precise numerical work.
ARCHITECTURAL OVERVIEW
The system consists of several key components working together. At the top level sits the LLM interface, which receives user prompts and generates responses. The LLM has access to a tool registry that defines available computational functions. When the LLM determines that a calculation is needed, it generates a tool call with appropriate parameters. The tool dispatcher routes this call to the correct calculator implementation. The calculator performs the computation and returns results. Finally, the LLM interprets these results and presents them to the user in natural language.
The separation of concerns is critical. The LLM handles natural language understanding, reasoning, and response generation. The calculators handle precise numerical computation. The tool calling mechanism provides the bridge between these two worlds. This architecture ensures that each component does what it does best.
TOOL CALLING MECHANISM
Modern LLMs support function calling or tool calling through structured output formats. The LLM is provided with function definitions that describe available tools, their parameters, and their purposes. When the LLM determines that a tool should be used, it generates a structured response containing the function name and arguments.
A function definition for a general mathematics calculator might look like this:
{
"name": "evaluate_mathematical_expression",
"description": "Evaluates mathematical expressions including arithmetic, algebra, calculus, and complex numbers. Supports operations like integration, differentiation, limits, and equation solving.",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "The mathematical expression to evaluate, using standard mathematical notation"
},
"operation": {
"type": "string",
"enum": ["evaluate", "integrate", "differentiate", "solve", "limit"],
"description": "The type of mathematical operation to perform"
},
"variable": {
"type": "string",
"description": "The variable for operations like integration or differentiation"
},
"lower_bound": {
"type": "number",
"description": "Lower bound for definite integrals"
},
"upper_bound": {
"type": "number",
"description": "Upper bound for definite integrals"
}
},
"required": ["expression", "operation"]
}
}
This definition tells the LLM what the function does, what parameters it accepts, and which parameters are required. When a user asks to calculate the integral of e to the power of x from 0 to 5, the LLM can generate a tool call like this:
{
"function": "evaluate_mathematical_expression",
"arguments": {
"expression": "exp(x)",
"operation": "integrate",
"variable": "x",
"lower_bound": 0,
"upper_bound": 5
}
}
The system then executes this function call and returns the result to the LLM, which incorporates it into the response to the user.
MATHEMATICS CALCULATOR IMPLEMENTATION
The mathematics calculator handles a wide range of operations including basic arithmetic, algebra, calculus, linear algebra, and complex number operations. A robust implementation uses symbolic mathematics libraries that can handle both numerical and symbolic computation.
Here is a simplified example showing the core structure:
import sympy as sp
from typing import Dict, Any, Optional
class MathematicsCalculator:
"""
Handles mathematical computations including calculus, algebra,
and numerical analysis.
"""
def __init__(self):
# Initialize common mathematical symbols
self.symbols_cache = {}
def get_symbol(self, name: str):
"""Get or create a symbolic variable."""
if name not in self.symbols_cache:
self.symbols_cache[name] = sp.Symbol(name)
return self.symbols_cache[name]
def evaluate_expression(
self,
expression: str,
operation: str,
variable: Optional[str] = None,
lower_bound: Optional[float] = None,
upper_bound: Optional[float] = None,
**kwargs
) -> Dict[str, Any]:
"""
Main entry point for mathematical operations.
Args:
expression: Mathematical expression as string
operation: Type of operation (evaluate, integrate, etc.)
variable: Variable name for calculus operations
lower_bound: Lower limit for definite integrals
upper_bound: Upper limit for definite integrals
Returns:
Dictionary containing result and metadata
"""
try:
# Parse the expression into a symbolic form
expr = sp.sympify(expression)
if operation == "evaluate":
result = self._evaluate(expr)
elif operation == "integrate":
result = self._integrate(expr, variable, lower_bound, upper_bound)
elif operation == "differentiate":
result = self._differentiate(expr, variable)
elif operation == "solve":
result = self._solve(expr, variable)
elif operation == "limit":
result = self._limit(expr, variable, kwargs.get("point"))
else:
return {"error": f"Unknown operation: {operation}"}
return {
"success": True,
"result": str(result),
"numerical_value": float(result) if result.is_number else None,
"latex": sp.latex(result)
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
def _integrate(self, expr, variable, lower_bound, upper_bound):
"""Perform integration, definite or indefinite."""
var_symbol = self.get_symbol(variable)
if lower_bound is not None and upper_bound is not None:
# Definite integral
result = sp.integrate(expr, (var_symbol, lower_bound, upper_bound))
else:
# Indefinite integral
result = sp.integrate(expr, var_symbol)
return result
The mathematics calculator uses the SymPy library, which provides powerful symbolic mathematics capabilities. The evaluate_expression method serves as the main entry point, routing requests to specialized methods for different operations. Each method returns a structured result containing the symbolic answer, numerical approximation when applicable, and LaTeX representation for display purposes.
For the integral of e to the power of x from 0 to 5, the _integrate method would receive the expression exp(x), integrate it symbolically to get exp(x), then evaluate it at the bounds to get exp(5) minus exp(1), which equals approximately 147.413.
STATISTICS CALCULATOR IMPLEMENTATION
Statistical analysis requires different capabilities than pure mathematics. The statistics calculator handles hypothesis testing, probability distributions, descriptive statistics, regression analysis, and statistical inference. Users might request complex analyses like determining whether observed data supports a particular hypothesis.
The statistics calculator needs to handle both computational tasks and analytical reasoning. When a user says they want to create a statistics test for a decision and provides facts, the calculator must determine which statistical test is appropriate, perform the calculations, and interpret the results.
import numpy as np
from scipy import stats
from typing import List, Dict, Any, Optional
class StatisticsCalculator:
"""
Handles statistical computations and hypothesis testing.
"""
def __init__(self):
self.supported_tests = {
"t_test": self._t_test,
"chi_square": self._chi_square_test,
"anova": self._anova_test,
"correlation": self._correlation_analysis,
"regression": self._regression_analysis
}
def perform_statistical_test(
self,
test_type: str,
data: Dict[str, List[float]],
alpha: float = 0.05,
alternative: str = "two-sided",
**kwargs
) -> Dict[str, Any]:
"""
Perform statistical hypothesis testing.
Args:
test_type: Type of statistical test to perform
data: Dictionary containing data arrays
alpha: Significance level
alternative: Alternative hypothesis type
Returns:
Dictionary with test results and interpretation
"""
try:
if test_type not in self.supported_tests:
return {"error": f"Unsupported test type: {test_type}"}
# Execute the appropriate test
test_function = self.supported_tests[test_type]
result = test_function(data, alpha, alternative, **kwargs)
return result
except Exception as e:
return {
"success": False,
"error": str(e)
}
def _t_test(self, data, alpha, alternative, **kwargs):
"""Perform t-test for comparing means."""
if "sample1" in data and "sample2" in data:
# Two-sample t-test
sample1 = np.array(data["sample1"])
sample2 = np.array(data["sample2"])
statistic, p_value = stats.ttest_ind(
sample1, sample2, alternative=alternative
)
# Calculate effect size (Cohen's d)
pooled_std = np.sqrt(
((len(sample1) - 1) * np.var(sample1, ddof=1) +
(len(sample2) - 1) * np.var(sample2, ddof=1)) /
(len(sample1) + len(sample2) - 2)
)
cohens_d = (np.mean(sample1) - np.mean(sample2)) / pooled_std
reject_null = p_value < alpha
return {
"success": True,
"test_type": "Two-sample t-test",
"statistic": float(statistic),
"p_value": float(p_value),
"alpha": alpha,
"reject_null": reject_null,
"effect_size": float(cohens_d),
"interpretation": self._interpret_t_test(
reject_null, p_value, alpha, cohens_d
)
}
else:
# One-sample t-test
sample = np.array(data["sample"])
pop_mean = kwargs.get("population_mean", 0)
statistic, p_value = stats.ttest_1samp(
sample, pop_mean, alternative=alternative
)
reject_null = p_value < alpha
return {
"success": True,
"test_type": "One-sample t-test",
"statistic": float(statistic),
"p_value": float(p_value),
"alpha": alpha,
"reject_null": reject_null,
"interpretation": self._interpret_one_sample_t_test(
reject_null, p_value, alpha, np.mean(sample), pop_mean
)
}
def _interpret_t_test(self, reject_null, p_value, alpha, effect_size):
"""Generate human-readable interpretation of t-test results."""
interpretation = []
if reject_null:
interpretation.append(
f"The null hypothesis is rejected at the {alpha} significance level."
)
interpretation.append(
f"There is statistically significant evidence that the means differ (p = {p_value:.4f})."
)
else:
interpretation.append(
f"The null hypothesis cannot be rejected at the {alpha} significance level."
)
interpretation.append(
f"There is insufficient evidence that the means differ (p = {p_value:.4f})."
)
# Interpret effect size
abs_d = abs(effect_size)
if abs_d < 0.2:
effect_desc = "negligible"
elif abs_d < 0.5:
effect_desc = "small"
elif abs_d < 0.8:
effect_desc = "medium"
else:
effect_desc = "large"
interpretation.append(
f"The effect size (Cohen's d = {effect_size:.3f}) is {effect_desc}."
)
return " ".join(interpretation)
The statistics calculator uses NumPy for numerical operations and SciPy for statistical functions. The perform_statistical_test method routes requests to specific test implementations. Each test returns not just numerical results but also interpretations that help users understand what the results mean. This is crucial because statistical analysis requires contextual understanding, not just raw numbers.
PHYSICS CALCULATOR IMPLEMENTATION
Physics calculations often involve unit conversions, physical constants, and domain-specific formulas. A physics calculator must handle mechanics, thermodynamics, electromagnetism, quantum mechanics, and other physics domains. It needs to track units throughout calculations to ensure dimensional consistency.
from typing import Dict, Any, Optional
import numpy as np
class PhysicsCalculator:
"""
Handles physics calculations with proper unit handling.
"""
def __init__(self):
# Physical constants in SI units
self.constants = {
"c": 299792458, # Speed of light (m/s)
"h": 6.62607015e-34, # Planck constant (J*s)
"hbar": 1.054571817e-34, # Reduced Planck constant (J*s)
"G": 6.67430e-11, # Gravitational constant (m^3/kg/s^2)
"k_B": 1.380649e-23, # Boltzmann constant (J/K)
"N_A": 6.02214076e23, # Avogadro constant (1/mol)
"R": 8.314462618, # Gas constant (J/mol/K)
"e": 1.602176634e-19, # Elementary charge (C)
"epsilon_0": 8.8541878128e-12, # Vacuum permittivity (F/m)
"mu_0": 1.25663706212e-6, # Vacuum permeability (H/m)
"m_e": 9.1093837015e-31, # Electron mass (kg)
"m_p": 1.67262192369e-27, # Proton mass (kg)
"g": 9.80665 # Standard gravity (m/s^2)
}
# Unit conversion factors to SI base units
self.unit_conversions = {
"length": {
"m": 1.0, "km": 1000.0, "cm": 0.01, "mm": 0.001,
"mi": 1609.344, "ft": 0.3048, "in": 0.0254
},
"mass": {
"kg": 1.0, "g": 0.001, "mg": 1e-6,
"lb": 0.45359237, "oz": 0.028349523
},
"time": {
"s": 1.0, "min": 60.0, "h": 3600.0, "day": 86400.0
},
"energy": {
"J": 1.0, "kJ": 1000.0, "cal": 4.184, "kcal": 4184.0,
"eV": 1.602176634e-19, "kWh": 3.6e6
},
"temperature": {
"K": (1.0, 0.0), "C": (1.0, 273.15), "F": (5/9, 255.372)
}
}
def calculate_physics_quantity(
self,
calculation_type: str,
parameters: Dict[str, Any]
) -> Dict[str, Any]:
"""
Calculate physics quantities based on type and parameters.
Args:
calculation_type: Type of physics calculation
parameters: Dictionary of input parameters with units
Returns:
Dictionary with calculated results and units
"""
try:
# Convert all inputs to SI units
si_params = self._convert_to_si(parameters)
# Route to appropriate calculation
if calculation_type == "kinematics":
result = self._kinematics_calculation(si_params)
elif calculation_type == "dynamics":
result = self._dynamics_calculation(si_params)
elif calculation_type == "energy":
result = self._energy_calculation(si_params)
elif calculation_type == "thermodynamics":
result = self._thermodynamics_calculation(si_params)
elif calculation_type == "electromagnetism":
result = self._electromagnetism_calculation(si_params)
elif calculation_type == "quantum":
result = self._quantum_calculation(si_params)
else:
return {"error": f"Unknown calculation type: {calculation_type}"}
return result
except Exception as e:
return {
"success": False,
"error": str(e)
}
def _convert_to_si(self, parameters: Dict[str, Any]) -> Dict[str, float]:
"""Convert all parameters to SI base units."""
si_params = {}
for key, value in parameters.items():
if isinstance(value, dict) and "value" in value and "unit" in value:
# Parameter has explicit unit
numeric_value = value["value"]
unit = value["unit"]
# Determine quantity type and convert
converted = self._convert_unit(numeric_value, unit)
si_params[key] = converted
else:
# Assume already in SI or dimensionless
si_params[key] = value
return si_params
def _convert_unit(self, value: float, unit: str) -> float:
"""Convert a value with unit to SI base unit."""
# Check each quantity type
for quantity_type, conversions in self.unit_conversions.items():
if unit in conversions:
if quantity_type == "temperature":
# Temperature requires offset conversion
scale, offset = conversions[unit]
return value * scale + offset
else:
return value * conversions[unit]
# If unit not found, assume already SI
return value
def _kinematics_calculation(self, params: Dict[str, float]) -> Dict[str, Any]:
"""Calculate kinematic quantities."""
if "equation" in params:
eq = params["equation"]
if eq == "velocity":
# v = v0 + a*t
v0 = params.get("initial_velocity", 0)
a = params.get("acceleration", 0)
t = params.get("time", 0)
v = v0 + a * t
return {
"success": True,
"result": v,
"unit": "m/s",
"description": "Final velocity"
}
elif eq == "position":
# x = x0 + v0*t + 0.5*a*t^2
x0 = params.get("initial_position", 0)
v0 = params.get("initial_velocity", 0)
a = params.get("acceleration", 0)
t = params.get("time", 0)
x = x0 + v0 * t + 0.5 * a * t**2
return {
"success": True,
"result": x,
"unit": "m",
"description": "Final position"
}
return {"error": "Insufficient parameters for kinematics calculation"}
The physics calculator maintains a comprehensive database of physical constants and unit conversion factors. The _convert_to_si method ensures all calculations happen in consistent units, preventing common errors. Each calculation method handles a specific physics domain and returns results with appropriate units and descriptions.
CHEMISTRY CALCULATOR IMPLEMENTATION
Chemistry calculations involve molecular properties, stoichiometry, thermochemistry, equilibrium calculations, and reaction kinetics. The chemistry calculator needs access to periodic table data and can perform calculations involving chemical formulas and reactions.
from typing import Dict, Any, List, Optional
import re
class ChemistryCalculator:
"""
Handles chemistry calculations including stoichiometry,
thermochemistry, and molecular properties.
"""
def __init__(self):
# Periodic table data (atomic number: [symbol, name, atomic_mass])
self.periodic_table = {
1: ["H", "Hydrogen", 1.008],
6: ["C", "Carbon", 12.011],
7: ["N", "Nitrogen", 14.007],
8: ["O", "Oxygen", 15.999],
16: ["S", "Sulfur", 32.06],
17: ["Cl", "Chlorine", 35.45],
# ... (abbreviated for space)
}
# Create reverse lookup by symbol
self.element_by_symbol = {
data[0]: {"number": num, "name": data[1], "mass": data[2]}
for num, data in self.periodic_table.items()
}
# Gas constant in different units
self.R_values = {
"J/mol/K": 8.314462618,
"L*atm/mol/K": 0.08206,
"cal/mol/K": 1.987
}
def calculate_chemistry_quantity(
self,
calculation_type: str,
parameters: Dict[str, Any]
) -> Dict[str, Any]:
"""
Perform chemistry calculations.
Args:
calculation_type: Type of chemistry calculation
parameters: Input parameters for calculation
Returns:
Dictionary with results
"""
try:
if calculation_type == "molar_mass":
result = self._calculate_molar_mass(parameters["formula"])
elif calculation_type == "stoichiometry":
result = self._stoichiometry_calculation(parameters)
elif calculation_type == "ideal_gas":
result = self._ideal_gas_calculation(parameters)
elif calculation_type == "equilibrium":
result = self._equilibrium_calculation(parameters)
elif calculation_type == "pH":
result = self._ph_calculation(parameters)
else:
return {"error": f"Unknown calculation type: {calculation_type}"}
return result
except Exception as e:
return {
"success": False,
"error": str(e)
}
def _parse_chemical_formula(self, formula: str) -> Dict[str, int]:
"""
Parse chemical formula into element counts.
Example: H2SO4 -> {"H": 2, "S": 1, "O": 4}
"""
element_counts = {}
# Pattern matches element symbol followed by optional number
pattern = r'([A-Z][a-z]?)(\d*)'
matches = re.findall(pattern, formula)
for element, count in matches:
if element:
count = int(count) if count else 1
element_counts[element] = element_counts.get(element, 0) + count
return element_counts
def _calculate_molar_mass(self, formula: str) -> Dict[str, Any]:
"""Calculate molar mass of a chemical formula."""
element_counts = self._parse_chemical_formula(formula)
total_mass = 0.0
composition = {}
for element, count in element_counts.items():
if element not in self.element_by_symbol:
return {"error": f"Unknown element: {element}"}
element_mass = self.element_by_symbol[element]["mass"]
mass_contribution = element_mass * count
total_mass += mass_contribution
composition[element] = {
"count": count,
"mass_contribution": mass_contribution
}
# Calculate mass percentages
for element in composition:
composition[element]["percentage"] = (
composition[element]["mass_contribution"] / total_mass * 100
)
return {
"success": True,
"formula": formula,
"molar_mass": total_mass,
"unit": "g/mol",
"composition": composition
}
def _ideal_gas_calculation(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate ideal gas properties using PV = nRT.
Solves for the missing variable.
"""
# Extract known variables
P = params.get("pressure") # atm
V = params.get("volume") # L
n = params.get("moles") # mol
T = params.get("temperature") # K
R = self.R_values["L*atm/mol/K"]
# Determine which variable to solve for
if P is None:
P = (n * R * T) / V
return {
"success": True,
"solved_for": "pressure",
"result": P,
"unit": "atm"
}
elif V is None:
V = (n * R * T) / P
return {
"success": True,
"solved_for": "volume",
"result": V,
"unit": "L"
}
elif n is None:
n = (P * V) / (R * T)
return {
"success": True,
"solved_for": "moles",
"result": n,
"unit": "mol"
}
elif T is None:
T = (P * V) / (n * R)
return {
"success": True,
"solved_for": "temperature",
"result": T,
"unit": "K"
}
else:
# All variables provided, verify equation
calculated_P = (n * R * T) / V
error = abs(P - calculated_P) / P * 100
return {
"success": True,
"verification": True,
"expected_pressure": calculated_P,
"actual_pressure": P,
"percent_error": error
}
The chemistry calculator parses chemical formulas using regular expressions to extract element symbols and counts. It maintains periodic table data for accurate atomic mass calculations. The ideal gas calculation demonstrates how the calculator can solve for unknown variables given sufficient information.
BUSINESS AND FINANCIAL CALCULATOR IMPLEMENTATION
Business and financial calculations encompass a wide range of operations including time value of money, investment analysis, loan calculations, financial ratios, and business metrics. These calculations often involve iterative methods and complex formulas.
import numpy as np
from typing import Dict, Any, List, Optional
class FinancialCalculator:
"""
Handles business and financial calculations.
"""
def __init__(self):
self.days_per_year = 365
self.months_per_year = 12
def calculate_financial_metric(
self,
calculation_type: str,
parameters: Dict[str, Any]
) -> Dict[str, Any]:
"""
Calculate financial metrics and perform financial analysis.
Args:
calculation_type: Type of financial calculation
parameters: Input parameters
Returns:
Dictionary with calculation results
"""
try:
if calculation_type == "present_value":
result = self._present_value(parameters)
elif calculation_type == "future_value":
result = self._future_value(parameters)
elif calculation_type == "npv":
result = self._net_present_value(parameters)
elif calculation_type == "irr":
result = self._internal_rate_of_return(parameters)
elif calculation_type == "loan_payment":
result = self._loan_payment(parameters)
elif calculation_type == "amortization":
result = self._amortization_schedule(parameters)
elif calculation_type == "bond_price":
result = self._bond_pricing(parameters)
else:
return {"error": f"Unknown calculation type: {calculation_type}"}
return result
except Exception as e:
return {
"success": False,
"error": str(e)
}
def _present_value(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate present value of future cash flows.
PV = FV / (1 + r)^n
"""
future_value = params["future_value"]
rate = params["rate"]
periods = params["periods"]
pv = future_value / ((1 + rate) ** periods)
return {
"success": True,
"present_value": pv,
"future_value": future_value,
"rate": rate,
"periods": periods,
"discount_factor": 1 / ((1 + rate) ** periods)
}
def _future_value(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate future value of present investment.
FV = PV * (1 + r)^n
"""
present_value = params["present_value"]
rate = params["rate"]
periods = params["periods"]
fv = present_value * ((1 + rate) ** periods)
return {
"success": True,
"future_value": fv,
"present_value": present_value,
"rate": rate,
"periods": periods,
"growth_factor": (1 + rate) ** periods
}
def _net_present_value(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate Net Present Value of cash flow series.
NPV = sum(CF_t / (1 + r)^t) for t = 0 to n
"""
cash_flows = params["cash_flows"] # List of cash flows
discount_rate = params["discount_rate"]
npv = 0.0
discounted_flows = []
for t, cf in enumerate(cash_flows):
discount_factor = 1 / ((1 + discount_rate) ** t)
discounted_cf = cf * discount_factor
npv += discounted_cf
discounted_flows.append({
"period": t,
"cash_flow": cf,
"discount_factor": discount_factor,
"discounted_value": discounted_cf
})
return {
"success": True,
"npv": npv,
"discount_rate": discount_rate,
"total_undiscounted": sum(cash_flows),
"discounted_cash_flows": discounted_flows,
"recommendation": "Accept project" if npv > 0 else "Reject project"
}
def _internal_rate_of_return(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate Internal Rate of Return using Newton-Raphson method.
IRR is the rate where NPV = 0
"""
cash_flows = np.array(params["cash_flows"])
# Use numpy's IRR calculation
irr = np.irr(cash_flows)
# Verify by calculating NPV at this rate
npv_at_irr = sum(
cf / ((1 + irr) ** t) for t, cf in enumerate(cash_flows)
)
return {
"success": True,
"irr": irr,
"irr_percentage": irr * 100,
"npv_at_irr": npv_at_irr,
"cash_flows": cash_flows.tolist()
}
def _loan_payment(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate periodic loan payment using amortization formula.
PMT = P * [r(1+r)^n] / [(1+r)^n - 1]
"""
principal = params["principal"]
annual_rate = params["annual_rate"]
years = params["years"]
payments_per_year = params.get("payments_per_year", 12)
# Convert to periodic rate and number of payments
periodic_rate = annual_rate / payments_per_year
num_payments = years * payments_per_year
# Calculate payment
if periodic_rate == 0:
payment = principal / num_payments
else:
payment = principal * (
periodic_rate * (1 + periodic_rate) ** num_payments
) / (
(1 + periodic_rate) ** num_payments - 1
)
total_paid = payment * num_payments
total_interest = total_paid - principal
return {
"success": True,
"periodic_payment": payment,
"num_payments": num_payments,
"total_paid": total_paid,
"total_interest": total_interest,
"principal": principal,
"annual_rate": annual_rate,
"periodic_rate": periodic_rate
}
The financial calculator implements standard financial formulas with careful attention to compounding periods and rate conversions. The net present value and internal rate of return calculations are particularly important for investment analysis. Each method returns comprehensive results including intermediate calculations that help users understand how the final answer was derived.
TOOL REGISTRY AND DISPATCHER
The tool registry maintains definitions of all available calculator functions and routes tool calls to the appropriate implementations. It serves as the central coordination point between the LLM and the calculator implementations.
from typing import Dict, Any, Callable, List
import json
class ToolRegistry:
"""
Central registry for all calculator tools and their definitions.
"""
def __init__(self):
self.tools = {}
self.tool_definitions = []
# Initialize calculator instances
self.math_calc = MathematicsCalculator()
self.stats_calc = StatisticsCalculator()
self.physics_calc = PhysicsCalculator()
self.chem_calc = ChemistryCalculator()
self.finance_calc = FinancialCalculator()
# Register all tools
self._register_tools()
def _register_tools(self):
"""Register all available calculator tools."""
# Mathematics tools
self.register_tool(
name="evaluate_mathematical_expression",
description="Evaluates mathematical expressions including arithmetic, algebra, calculus, and complex numbers. Supports integration, differentiation, limits, and equation solving.",
parameters={
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression using standard notation"
},
"operation": {
"type": "string",
"enum": ["evaluate", "integrate", "differentiate", "solve", "limit"],
"description": "Type of mathematical operation"
},
"variable": {
"type": "string",
"description": "Variable for calculus operations"
},
"lower_bound": {"type": "number"},
"upper_bound": {"type": "number"}
},
"required": ["expression", "operation"]
},
function=self.math_calc.evaluate_expression
)
# Statistics tools
self.register_tool(
name="perform_statistical_test",
description="Performs statistical hypothesis testing including t-tests, chi-square tests, ANOVA, correlation, and regression analysis.",
parameters={
"type": "object",
"properties": {
"test_type": {
"type": "string",
"enum": ["t_test", "chi_square", "anova", "correlation", "regression"],
"description": "Type of statistical test"
},
"data": {
"type": "object",
"description": "Dictionary containing data arrays"
},
"alpha": {
"type": "number",
"description": "Significance level (default 0.05)"
},
"alternative": {
"type": "string",
"enum": ["two-sided", "less", "greater"],
"description": "Alternative hypothesis type"
}
},
"required": ["test_type", "data"]
},
function=self.stats_calc.perform_statistical_test
)
# Physics tools
self.register_tool(
name="calculate_physics_quantity",
description="Calculates physics quantities with proper unit handling. Supports kinematics, dynamics, energy, thermodynamics, electromagnetism, and quantum mechanics.",
parameters={
"type": "object",
"properties": {
"calculation_type": {
"type": "string",
"enum": ["kinematics", "dynamics", "energy", "thermodynamics", "electromagnetism", "quantum"],
"description": "Type of physics calculation"
},
"parameters": {
"type": "object",
"description": "Input parameters with units"
}
},
"required": ["calculation_type", "parameters"]
},
function=self.physics_calc.calculate_physics_quantity
)
# Chemistry tools
self.register_tool(
name="calculate_chemistry_quantity",
description="Performs chemistry calculations including molar mass, stoichiometry, ideal gas law, equilibrium, and pH calculations.",
parameters={
"type": "object",
"properties": {
"calculation_type": {
"type": "string",
"enum": ["molar_mass", "stoichiometry", "ideal_gas", "equilibrium", "pH"],
"description": "Type of chemistry calculation"
},
"parameters": {
"type": "object",
"description": "Input parameters for calculation"
}
},
"required": ["calculation_type", "parameters"]
},
function=self.chem_calc.calculate_chemistry_quantity
)
# Financial tools
self.register_tool(
name="calculate_financial_metric",
description="Calculates financial metrics including present value, future value, NPV, IRR, loan payments, and bond pricing.",
parameters={
"type": "object",
"properties": {
"calculation_type": {
"type": "string",
"enum": ["present_value", "future_value", "npv", "irr", "loan_payment", "amortization", "bond_price"],
"description": "Type of financial calculation"
},
"parameters": {
"type": "object",
"description": "Input parameters for calculation"
}
},
"required": ["calculation_type", "parameters"]
},
function=self.finance_calc.calculate_financial_metric
)
def register_tool(
self,
name: str,
description: str,
parameters: Dict[str, Any],
function: Callable
):
"""Register a new tool with its definition and implementation."""
self.tools[name] = function
self.tool_definitions.append({
"name": name,
"description": description,
"parameters": parameters
})
def get_tool_definitions(self) -> List[Dict[str, Any]]:
"""Get all tool definitions for LLM function calling."""
return self.tool_definitions
def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Execute a tool with given arguments."""
if tool_name not in self.tools:
return {
"success": False,
"error": f"Unknown tool: {tool_name}"
}
try:
function = self.tools[tool_name]
result = function(**arguments)
return result
except Exception as e:
return {
"success": False,
"error": f"Tool execution failed: {str(e)}"
}
The tool registry encapsulates all calculator instances and provides a unified interface for tool execution. The get_tool_definitions method returns the function definitions that are sent to the LLM, while execute_tool handles the actual function invocation. This separation allows the LLM interface to remain agnostic about calculator implementations.
LLM INTEGRATION LAYER
The LLM integration layer manages communication with the language model, handles tool calling workflows, and coordinates multi-step calculations. It sends user prompts to the LLM along with tool definitions, processes tool call requests, executes the tools, and returns results to the LLM for final response generation.
from typing import List, Dict, Any, Optional
import json
class LLMCalculatorSystem:
"""
Main system that integrates LLM with calculator tools.
"""
def __init__(self, llm_client, tool_registry: ToolRegistry):
"""
Initialize the LLM calculator system.
Args:
llm_client: Client for communicating with LLM API
tool_registry: Registry of available calculator tools
"""
self.llm_client = llm_client
self.tool_registry = tool_registry
self.conversation_history = []
# System prompt that explains the calculator capabilities
self.system_prompt = """You are an advanced calculator assistant with access to specialized computational tools for mathematics, statistics, physics, chemistry, and finance. When users ask for calculations:
1. Analyze the request to determine what calculations are needed
2. Use the appropriate calculator tools to perform precise computations
3. Interpret the results and present them clearly to the user
4. For complex problems, break them down into steps and use multiple tool calls if needed
5. Always explain your reasoning and the meaning of results
You have access to these calculator domains:
- Mathematics: algebra, calculus, complex numbers, equation solving
- Statistics: hypothesis testing, probability, regression, descriptive statistics
- Physics: mechanics, thermodynamics, electromagnetism with proper unit handling
- Chemistry: stoichiometry, molecular properties, equilibrium calculations
- Finance: time value of money, investment analysis, loan calculations
Use tools whenever precise numerical computation is required. Do not attempt to perform complex calculations yourself."""
def process_user_request(self, user_message: str) -> str:
"""
Process a user request through the LLM with tool calling.
Args:
user_message: The user's question or request
Returns:
The final response string
"""
# Add user message to conversation history
self.conversation_history.append({
"role": "user",
"content": user_message
})
# Maximum iterations to prevent infinite loops
max_iterations = 10
iteration = 0
while iteration < max_iterations:
iteration += 1
# Call LLM with current conversation and available tools
response = self._call_llm()
# Check if LLM wants to use tools
if response.get("tool_calls"):
# Execute requested tools
tool_results = self._execute_tool_calls(response["tool_calls"])
# Add assistant's tool call request to history
self.conversation_history.append({
"role": "assistant",
"content": response.get("content"),
"tool_calls": response["tool_calls"]
})
# Add tool results to history
for tool_result in tool_results:
self.conversation_history.append({
"role": "tool",
"tool_call_id": tool_result["tool_call_id"],
"content": json.dumps(tool_result["result"])
})
# Continue loop to let LLM process tool results
continue
else:
# LLM provided final response without tool calls
final_response = response["content"]
# Add to history
self.conversation_history.append({
"role": "assistant",
"content": final_response
})
return final_response
# If we hit max iterations, return what we have
return "I apologize, but I encountered complexity limits while processing your request. Please try breaking it into smaller parts."
def _call_llm(self) -> Dict[str, Any]:
"""
Call the LLM API with current conversation and tool definitions.
Returns:
Dictionary containing response content and any tool calls
"""
# Prepare messages with system prompt
messages = [
{"role": "system", "content": self.system_prompt}
] + self.conversation_history
# Get tool definitions
tools = self.tool_registry.get_tool_definitions()
# Call LLM API (implementation depends on specific LLM provider)
response = self.llm_client.chat_completion(
messages=messages,
tools=tools,
tool_choice="auto" # Let LLM decide when to use tools
)
return response
def _execute_tool_calls(self, tool_calls: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Execute all requested tool calls.
Args:
tool_calls: List of tool call requests from LLM
Returns:
List of tool execution results
"""
results = []
for tool_call in tool_calls:
tool_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
# Execute the tool
result = self.tool_registry.execute_tool(tool_name, arguments)
results.append({
"tool_call_id": tool_call["id"],
"result": result
})
return results
def reset_conversation(self):
"""Clear conversation history for a new session."""
self.conversation_history = []
The LLM integration layer implements the core workflow loop. It sends messages to the LLM, checks if tool calls are requested, executes those tools, and feeds results back to the LLM. This continues until the LLM generates a final response without requesting more tools. The system prompt is crucial as it instructs the LLM on how to use the calculator tools effectively.
ERROR HANDLING AND VALIDATION
Robust error handling is essential for a production system. Each calculator component should validate inputs, handle edge cases, and return informative error messages. The system should gracefully handle malformed requests, invalid parameters, and computational errors.
Input validation should occur at multiple levels. The tool registry can validate that required parameters are present before calling calculator functions. Each calculator should validate that parameters are within acceptable ranges and have correct types. For example, the statistics calculator should verify that data arrays are not empty and contain valid numbers. The physics calculator should check that units are recognized and conversions are possible.
When errors occur, the system should return structured error information that the LLM can interpret and explain to the user. Rather than cryptic error messages, the system should provide context about what went wrong and potentially suggest corrections.
class ValidationError(Exception):
"""Custom exception for validation errors."""
pass
def validate_parameters(params: Dict[str, Any], schema: Dict[str, Any]) -> None:
"""
Validate parameters against a schema.
Args:
params: Parameters to validate
schema: Schema defining required and optional parameters
Raises:
ValidationError: If validation fails
"""
# Check required parameters
required = schema.get("required", [])
for param in required:
if param not in params:
raise ValidationError(f"Missing required parameter: {param}")
# Check parameter types
properties = schema.get("properties", {})
for param, value in params.items():
if param in properties:
expected_type = properties[param].get("type")
if expected_type == "number" and not isinstance(value, (int, float)):
raise ValidationError(f"Parameter {param} must be a number")
elif expected_type == "string" and not isinstance(value, str):
raise ValidationError(f"Parameter {param} must be a string")
elif expected_type == "array" and not isinstance(value, list):
raise ValidationError(f"Parameter {param} must be an array")
elif expected_type == "object" and not isinstance(value, dict):
raise ValidationError(f"Parameter {param} must be an object")
This validation function can be called at the beginning of each calculator method to ensure inputs meet requirements. The ValidationError exception provides clear feedback about what went wrong, which the LLM can then communicate to the user in natural language.
HANDLING COMPLEX MULTI-STEP PROBLEMS
Some user requests require multiple calculations or a sequence of operations. The LLM's reasoning capabilities allow it to break down complex problems into steps and make multiple tool calls. The system must support this workflow by maintaining state across tool calls and allowing the LLM to build on previous results.
For example, if a user asks to analyze whether a business investment is worthwhile given certain cash flows and a required return rate, the LLM might first calculate the NPV, then calculate the IRR, then compare both metrics to make a recommendation. Each calculation uses results from the tool registry, and the LLM synthesizes all information into a coherent answer.
The conversation history maintained by the LLM integration layer enables this multi-step reasoning. Each tool result is added to the history, so the LLM has access to all previous calculations when generating its next response or tool call.
EXTENDING THE SYSTEM
The modular architecture makes it straightforward to add new calculator domains or extend existing ones. To add a new calculator, you would create a new calculator class following the same pattern as existing calculators, implement the necessary calculation methods, register the tools in the tool registry with appropriate function definitions, and update the system prompt to inform the LLM about the new capabilities.
For example, adding a biology calculator for genetic calculations or protein analysis would involve creating a BiologyCalculator class, implementing methods for DNA sequence analysis, protein structure calculations, or population genetics, and registering these as tools. The LLM would automatically be able to use these new tools based on their descriptions.
Similarly, existing calculators can be extended with new methods. Adding support for differential equations to the mathematics calculator would involve implementing numerical or symbolic differential equation solvers and registering them as new operations in the evaluate_mathematical_expression tool.
PERFORMANCE CONSIDERATIONS
Performance is important for user experience. Calculator operations should execute quickly to minimize latency. Most mathematical operations complete in milliseconds, but some operations like numerical optimization or Monte Carlo simulations might take longer. For time-consuming operations, consider implementing progress indicators or asynchronous execution.
Caching can improve performance for repeated calculations. If the same calculation is requested multiple times, the system can cache results and return them immediately. However, cache invalidation must be handled carefully to ensure results remain accurate.
The LLM API calls typically dominate latency. Each round trip to the LLM takes time, so minimizing the number of iterations in the tool calling loop improves responsiveness. Well-designed tool definitions that allow the LLM to request all needed calculations in a single tool call batch can reduce iterations.
TESTING AND VALIDATION
Comprehensive testing is essential for a calculator system where accuracy is critical. Each calculator component should have unit tests that verify correct results for known inputs. Test cases should cover normal operation, edge cases, and error conditions.
For mathematical operations, compare results against known analytical solutions or trusted reference implementations. For statistical tests, verify that p-values and test statistics match those from established statistical software. For financial calculations, check against published examples from finance textbooks.
Integration tests should verify that the complete system works end-to-end. These tests send user requests through the LLM integration layer and verify that appropriate tools are called and correct results are returned. Mock LLM responses can be used to test the tool calling workflow without requiring actual LLM API calls.
SECURITY CONSIDERATIONS
Security is important when executing user-provided expressions or formulas. The system should not allow arbitrary code execution. Using symbolic mathematics libraries like SymPy is safer than using eval() on user input, as SymPy parses expressions into a controlled abstract syntax tree rather than executing arbitrary Python code.
Input sanitization should prevent injection attacks. Validate that chemical formulas contain only valid element symbols, that mathematical expressions use only allowed functions, and that file paths or system commands cannot be injected through calculator parameters.
Rate limiting prevents abuse of computational resources. Limit the number of requests per user and the computational complexity of individual requests. Set timeouts for long-running calculations to prevent resource exhaustion.
PRODUCTION READY RUNNING EXAMPLE
The following is a complete, production-ready implementation of the LLM-based calculator system. This code integrates all components discussed above and can handle diverse calculation requests across multiple domains.
import sympy as sp
import numpy as np
from scipy import stats
from typing import Dict, Any, List, Optional, Callable
import json
import re
from abc import ABC, abstractmethod
# ============================================================================
# CALCULATOR IMPLEMENTATIONS
# ============================================================================
class MathematicsCalculator:
"""
Comprehensive mathematics calculator supporting algebra, calculus,
complex numbers, and numerical analysis.
"""
def __init__(self):
self.symbols_cache = {}
def get_symbol(self, name: str):
"""Get or create a symbolic variable."""
if name not in self.symbols_cache:
self.symbols_cache[name] = sp.Symbol(name, real=True)
return self.symbols_cache[name]
def evaluate_expression(
self,
expression: str,
operation: str,
variable: Optional[str] = None,
lower_bound: Optional[float] = None,
upper_bound: Optional[float] = None,
point: Optional[float] = None,
**kwargs
) -> Dict[str, Any]:
"""
Main entry point for mathematical operations.
Args:
expression: Mathematical expression as string
operation: Type of operation (evaluate, integrate, differentiate, solve, limit)
variable: Variable name for calculus operations
lower_bound: Lower limit for definite integrals
upper_bound: Upper limit for definite integrals
point: Point for limit evaluation
Returns:
Dictionary containing result and metadata
"""
try:
# Parse the expression into symbolic form
expr = sp.sympify(expression, locals=self.symbols_cache)
if operation == "evaluate":
result = self._evaluate(expr, kwargs)
elif operation == "integrate":
if not variable:
return {"success": False, "error": "Variable required for integration"}
result = self._integrate(expr, variable, lower_bound, upper_bound)
elif operation == "differentiate":
if not variable:
return {"success": False, "error": "Variable required for differentiation"}
result = self._differentiate(expr, variable, kwargs.get("order", 1))
elif operation == "solve":
if not variable:
return {"success": False, "error": "Variable required for solving"}
result = self._solve(expr, variable)
elif operation == "limit":
if not variable or point is None:
return {"success": False, "error": "Variable and point required for limit"}
result = self._limit(expr, variable, point, kwargs.get("direction", "+"))
else:
return {"success": False, "error": f"Unknown operation: {operation}"}
# Format result
return {
"success": True,
"result": str(result),
"numerical_value": self._to_numerical(result),
"latex": sp.latex(result),
"operation": operation
}
except Exception as e:
return {
"success": False,
"error": f"Mathematical error: {str(e)}"
}
def _evaluate(self, expr, substitutions: Dict[str, float]):
"""Evaluate expression with given variable substitutions."""
if substitutions:
subs_dict = {self.get_symbol(k): v for k, v in substitutions.items()}
return expr.subs(subs_dict)
return expr.evalf()
def _integrate(self, expr, variable: str, lower_bound, upper_bound):
"""Perform integration, definite or indefinite."""
var_symbol = self.get_symbol(variable)
if lower_bound is not None and upper_bound is not None:
# Definite integral
result = sp.integrate(expr, (var_symbol, lower_bound, upper_bound))
else:
# Indefinite integral
result = sp.integrate(expr, var_symbol)
return result
def _differentiate(self, expr, variable: str, order: int = 1):
"""Compute derivative of given order."""
var_symbol = self.get_symbol(variable)
return sp.diff(expr, var_symbol, order)
def _solve(self, expr, variable: str):
"""Solve equation for variable."""
var_symbol = self.get_symbol(variable)
solutions = sp.solve(expr, var_symbol)
if isinstance(solutions, list):
return solutions
return [solutions]
def _limit(self, expr, variable: str, point: float, direction: str = "+"):
"""Compute limit at a point."""
var_symbol = self.get_symbol(variable)
return sp.limit(expr, var_symbol, point, dir=direction)
def _to_numerical(self, result):
"""Convert result to numerical value if possible."""
try:
if isinstance(result, list):
return [float(r.evalf()) for r in result if r.is_number]
elif hasattr(result, 'is_number') and result.is_number:
return float(result.evalf())
return None
except:
return None
class StatisticsCalculator:
"""
Statistical analysis calculator supporting hypothesis testing,
probability distributions, and regression analysis.
"""
def __init__(self):
self.supported_tests = {
"t_test": self._t_test,
"chi_square": self._chi_square_test,
"anova": self._anova_test,
"correlation": self._correlation_analysis,
"regression": self._regression_analysis
}
def perform_statistical_test(
self,
test_type: str,
data: Dict[str, List[float]],
alpha: float = 0.05,
alternative: str = "two-sided",
**kwargs
) -> Dict[str, Any]:
"""
Perform statistical hypothesis testing.
Args:
test_type: Type of statistical test
data: Dictionary containing data arrays
alpha: Significance level
alternative: Alternative hypothesis type
Returns:
Dictionary with test results and interpretation
"""
try:
if test_type not in self.supported_tests:
return {
"success": False,
"error": f"Unsupported test type: {test_type}. Supported: {list(self.supported_tests.keys())}"
}
# Validate data
for key, values in data.items():
if not values or not all(isinstance(v, (int, float)) for v in values):
return {
"success": False,
"error": f"Invalid data for {key}: must be non-empty list of numbers"
}
# Execute the appropriate test
test_function = self.supported_tests[test_type]
result = test_function(data, alpha, alternative, **kwargs)
return result
except Exception as e:
return {
"success": False,
"error": f"Statistical test error: {str(e)}"
}
def _t_test(self, data, alpha, alternative, **kwargs):
"""Perform t-test for comparing means."""
if "sample1" in data and "sample2" in data:
# Two-sample t-test
sample1 = np.array(data["sample1"])
sample2 = np.array(data["sample2"])
# Perform test
statistic, p_value = stats.ttest_ind(sample1, sample2, alternative=alternative)
# Calculate descriptive statistics
mean1, mean2 = np.mean(sample1), np.mean(sample2)
std1, std2 = np.std(sample1, ddof=1), np.std(sample2, ddof=1)
n1, n2 = len(sample1), len(sample2)
# Calculate effect size (Cohen's d)
pooled_std = np.sqrt(((n1 - 1) * std1**2 + (n2 - 1) * std2**2) / (n1 + n2 - 2))
cohens_d = (mean1 - mean2) / pooled_std if pooled_std > 0 else 0
reject_null = p_value < alpha
return {
"success": True,
"test_type": "Two-sample t-test",
"statistic": float(statistic),
"p_value": float(p_value),
"alpha": alpha,
"reject_null": reject_null,
"effect_size": float(cohens_d),
"sample1_mean": float(mean1),
"sample2_mean": float(mean2),
"sample1_std": float(std1),
"sample2_std": float(std2),
"sample1_n": n1,
"sample2_n": n2,
"interpretation": self._interpret_t_test(reject_null, p_value, alpha, cohens_d, mean1, mean2)
}
elif "sample" in data:
# One-sample t-test
sample = np.array(data["sample"])
pop_mean = kwargs.get("population_mean", 0)
statistic, p_value = stats.ttest_1samp(sample, pop_mean, alternative=alternative)
sample_mean = np.mean(sample)
sample_std = np.std(sample, ddof=1)
n = len(sample)
reject_null = p_value < alpha
return {
"success": True,
"test_type": "One-sample t-test",
"statistic": float(statistic),
"p_value": float(p_value),
"alpha": alpha,
"reject_null": reject_null,
"sample_mean": float(sample_mean),
"population_mean": float(pop_mean),
"sample_std": float(sample_std),
"sample_n": n,
"interpretation": self._interpret_one_sample_t_test(reject_null, p_value, alpha, sample_mean, pop_mean)
}
else:
return {"success": False, "error": "T-test requires 'sample' or 'sample1' and 'sample2' in data"}
def _interpret_t_test(self, reject_null, p_value, alpha, effect_size, mean1, mean2):
"""Generate human-readable interpretation of two-sample t-test results."""
interpretation = []
if reject_null:
interpretation.append(f"The null hypothesis is rejected at the {alpha} significance level.")
interpretation.append(f"There is statistically significant evidence that the means differ (p = {p_value:.4f}).")
interpretation.append(f"Sample 1 mean ({mean1:.3f}) differs from Sample 2 mean ({mean2:.3f}).")
else:
interpretation.append(f"The null hypothesis cannot be rejected at the {alpha} significance level.")
interpretation.append(f"There is insufficient evidence that the means differ (p = {p_value:.4f}).")
# Interpret effect size using Cohen's conventions
abs_d = abs(effect_size)
if abs_d < 0.2:
effect_desc = "negligible"
elif abs_d < 0.5:
effect_desc = "small"
elif abs_d < 0.8:
effect_desc = "medium"
else:
effect_desc = "large"
interpretation.append(f"The effect size (Cohen's d = {effect_size:.3f}) is {effect_desc}.")
return " ".join(interpretation)
def _interpret_one_sample_t_test(self, reject_null, p_value, alpha, sample_mean, pop_mean):
"""Generate human-readable interpretation of one-sample t-test results."""
interpretation = []
if reject_null:
interpretation.append(f"The null hypothesis is rejected at the {alpha} significance level.")
interpretation.append(f"The sample mean ({sample_mean:.3f}) is significantly different from the population mean ({pop_mean:.3f}).")
interpretation.append(f"P-value: {p_value:.4f}")
else:
interpretation.append(f"The null hypothesis cannot be rejected at the {alpha} significance level.")
interpretation.append(f"The sample mean ({sample_mean:.3f}) is not significantly different from the population mean ({pop_mean:.3f}).")
interpretation.append(f"P-value: {p_value:.4f}")
return " ".join(interpretation)
def _chi_square_test(self, data, alpha, alternative, **kwargs):
"""Perform chi-square test for independence or goodness of fit."""
if "observed" in data and "expected" in data:
# Goodness of fit test
observed = np.array(data["observed"])
expected = np.array(data["expected"])
statistic, p_value = stats.chisquare(observed, expected)
reject_null = p_value < alpha
df = len(observed) - 1
return {
"success": True,
"test_type": "Chi-square goodness of fit",
"statistic": float(statistic),
"p_value": float(p_value),
"degrees_of_freedom": df,
"alpha": alpha,
"reject_null": reject_null,
"interpretation": f"{'Reject' if reject_null else 'Fail to reject'} null hypothesis. The observed frequencies {'do' if reject_null else 'do not'} significantly differ from expected frequencies (p = {p_value:.4f})."
}
elif "contingency_table" in data:
# Test of independence
table = np.array(data["contingency_table"])
statistic, p_value, df, expected = stats.chi2_contingency(table)
reject_null = p_value < alpha
return {
"success": True,
"test_type": "Chi-square test of independence",
"statistic": float(statistic),
"p_value": float(p_value),
"degrees_of_freedom": int(df),
"alpha": alpha,
"reject_null": reject_null,
"expected_frequencies": expected.tolist(),
"interpretation": f"{'Reject' if reject_null else 'Fail to reject'} null hypothesis. The variables {'are' if reject_null else 'are not'} significantly associated (p = {p_value:.4f})."
}
else:
return {"success": False, "error": "Chi-square test requires 'observed' and 'expected' or 'contingency_table' in data"}
def _anova_test(self, data, alpha, alternative, **kwargs):
"""Perform one-way ANOVA test."""
# Extract all groups from data
groups = []
group_names = []
for key, values in data.items():
if key.startswith("group"):
groups.append(np.array(values))
group_names.append(key)
if len(groups) < 2:
return {"success": False, "error": "ANOVA requires at least 2 groups"}
# Perform ANOVA
statistic, p_value = stats.f_oneway(*groups)
reject_null = p_value < alpha
# Calculate group statistics
group_stats = []
for i, group in enumerate(groups):
group_stats.append({
"name": group_names[i],
"mean": float(np.mean(group)),
"std": float(np.std(group, ddof=1)),
"n": len(group)
})
return {
"success": True,
"test_type": "One-way ANOVA",
"statistic": float(statistic),
"p_value": float(p_value),
"alpha": alpha,
"reject_null": reject_null,
"num_groups": len(groups),
"group_statistics": group_stats,
"interpretation": f"{'Reject' if reject_null else 'Fail to reject'} null hypothesis. There {'is' if reject_null else 'is no'} significant difference between group means (p = {p_value:.4f})."
}
def _correlation_analysis(self, data, alpha, alternative, **kwargs):
"""Perform correlation analysis."""
if "x" not in data or "y" not in data:
return {"success": False, "error": "Correlation requires 'x' and 'y' in data"}
x = np.array(data["x"])
y = np.array(data["y"])
if len(x) != len(y):
return {"success": False, "error": "x and y must have same length"}
# Pearson correlation
r, p_value = stats.pearsonr(x, y)
# Spearman correlation (rank-based)
rho, p_value_spearman = stats.spearmanr(x, y)
reject_null = p_value < alpha
# Interpret correlation strength
abs_r = abs(r)
if abs_r < 0.3:
strength = "weak"
elif abs_r < 0.7:
strength = "moderate"
else:
strength = "strong"
direction = "positive" if r > 0 else "negative"
return {
"success": True,
"test_type": "Correlation analysis",
"pearson_r": float(r),
"pearson_p_value": float(p_value),
"spearman_rho": float(rho),
"spearman_p_value": float(p_value_spearman),
"alpha": alpha,
"reject_null": reject_null,
"n": len(x),
"interpretation": f"There is a {strength} {direction} correlation (r = {r:.3f}, p = {p_value:.4f}). The correlation {'is' if reject_null else 'is not'} statistically significant at the {alpha} level."
}
def _regression_analysis(self, data, alpha, alternative, **kwargs):
"""Perform linear regression analysis."""
if "x" not in data or "y" not in data:
return {"success": False, "error": "Regression requires 'x' and 'y' in data"}
x = np.array(data["x"])
y = np.array(data["y"])
if len(x) != len(y):
return {"success": False, "error": "x and y must have same length"}
# Perform linear regression
slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
# Calculate R-squared
r_squared = r_value ** 2
# Calculate predicted values and residuals
y_pred = slope * x + intercept
residuals = y - y_pred
# Calculate residual standard error
n = len(x)
rse = np.sqrt(np.sum(residuals**2) / (n - 2))
reject_null = p_value < alpha
return {
"success": True,
"test_type": "Linear regression",
"slope": float(slope),
"intercept": float(intercept),
"r_squared": float(r_squared),
"p_value": float(p_value),
"standard_error": float(std_err),
"residual_standard_error": float(rse),
"alpha": alpha,
"reject_null": reject_null,
"n": n,
"equation": f"y = {slope:.4f}x + {intercept:.4f}",
"interpretation": f"The regression model explains {r_squared*100:.1f}% of variance. The slope {'is' if reject_null else 'is not'} significantly different from zero (p = {p_value:.4f})."
}
class PhysicsCalculator:
"""
Physics calculator with comprehensive unit handling and
support for multiple physics domains.
"""
def __init__(self):
# Physical constants in SI units
self.constants = {
"c": 299792458,
"h": 6.62607015e-34,
"hbar": 1.054571817e-34,
"G": 6.67430e-11,
"k_B": 1.380649e-23,
"N_A": 6.02214076e23,
"R": 8.314462618,
"e": 1.602176634e-19,
"epsilon_0": 8.8541878128e-12,
"mu_0": 1.25663706212e-6,
"m_e": 9.1093837015e-31,
"m_p": 1.67262192369e-27,
"g": 9.80665
}
# Unit conversion factors to SI base units
self.unit_conversions = {
"length": {
"m": 1.0, "km": 1000.0, "cm": 0.01, "mm": 0.001,
"mi": 1609.344, "ft": 0.3048, "in": 0.0254, "nm": 1e-9
},
"mass": {
"kg": 1.0, "g": 0.001, "mg": 1e-6, "ton": 1000.0,
"lb": 0.45359237, "oz": 0.028349523
},
"time": {
"s": 1.0, "min": 60.0, "h": 3600.0, "day": 86400.0, "ms": 0.001
},
"energy": {
"J": 1.0, "kJ": 1000.0, "MJ": 1e6, "cal": 4.184, "kcal": 4184.0,
"eV": 1.602176634e-19, "keV": 1.602176634e-16, "MeV": 1.602176634e-13,
"kWh": 3.6e6
},
"temperature": {
"K": (1.0, 0.0), "C": (1.0, 273.15), "F": (5.0/9.0, 255.372)
},
"force": {
"N": 1.0, "kN": 1000.0, "dyne": 1e-5, "lbf": 4.448222
},
"pressure": {
"Pa": 1.0, "kPa": 1000.0, "MPa": 1e6, "atm": 101325.0,
"bar": 1e5, "psi": 6894.757, "mmHg": 133.322
}
}
def calculate_physics_quantity(
self,
calculation_type: str,
parameters: Dict[str, Any]
) -> Dict[str, Any]:
"""
Calculate physics quantities based on type and parameters.
Args:
calculation_type: Type of physics calculation
parameters: Dictionary of input parameters with units
Returns:
Dictionary with calculated results and units
"""
try:
# Convert all inputs to SI units
si_params = self._convert_to_si(parameters)
# Route to appropriate calculation
if calculation_type == "kinematics":
result = self._kinematics_calculation(si_params)
elif calculation_type == "dynamics":
result = self._dynamics_calculation(si_params)
elif calculation_type == "energy":
result = self._energy_calculation(si_params)
elif calculation_type == "thermodynamics":
result = self._thermodynamics_calculation(si_params)
elif calculation_type == "electromagnetism":
result = self._electromagnetism_calculation(si_params)
elif calculation_type == "quantum":
result = self._quantum_calculation(si_params)
else:
return {"success": False, "error": f"Unknown calculation type: {calculation_type}"}
return result
except Exception as e:
return {
"success": False,
"error": f"Physics calculation error: {str(e)}"
}
def _convert_to_si(self, parameters: Dict[str, Any]) -> Dict[str, float]:
"""Convert all parameters to SI base units."""
si_params = {}
for key, value in parameters.items():
if isinstance(value, dict) and "value" in value and "unit" in value:
# Parameter has explicit unit
numeric_value = value["value"]
unit = value["unit"]
# Convert to SI
converted = self._convert_unit(numeric_value, unit)
si_params[key] = converted
else:
# Assume already in SI or dimensionless
si_params[key] = value
return si_params
def _convert_unit(self, value: float, unit: str) -> float:
"""Convert a value with unit to SI base unit."""
# Check each quantity type
for quantity_type, conversions in self.unit_conversions.items():
if unit in conversions:
if quantity_type == "temperature":
# Temperature requires offset conversion
scale, offset = conversions[unit]
return value * scale + offset
else:
return value * conversions[unit]
# If unit not found, assume already SI
return value
def _kinematics_calculation(self, params: Dict[str, float]) -> Dict[str, Any]:
"""Calculate kinematic quantities."""
equation = params.get("equation", "")
if equation == "velocity":
# v = v0 + a*t
v0 = params.get("initial_velocity", 0)
a = params.get("acceleration", 0)
t = params.get("time", 0)
v = v0 + a * t
return {
"success": True,
"result": v,
"unit": "m/s",
"description": "Final velocity",
"formula": "v = v0 + a*t"
}
elif equation == "position":
# x = x0 + v0*t + 0.5*a*t^2
x0 = params.get("initial_position", 0)
v0 = params.get("initial_velocity", 0)
a = params.get("acceleration", 0)
t = params.get("time", 0)
x = x0 + v0 * t + 0.5 * a * t**2
return {
"success": True,
"result": x,
"unit": "m",
"description": "Final position",
"formula": "x = x0 + v0*t + 0.5*a*t^2"
}
elif equation == "velocity_squared":
# v^2 = v0^2 + 2*a*d
v0 = params.get("initial_velocity", 0)
a = params.get("acceleration", 0)
d = params.get("displacement", 0)
v_squared = v0**2 + 2 * a * d
v = np.sqrt(abs(v_squared)) * (1 if v_squared >= 0 else -1)
return {
"success": True,
"result": v,
"unit": "m/s",
"description": "Final velocity",
"formula": "v^2 = v0^2 + 2*a*d"
}
else:
return {"success": False, "error": f"Unknown kinematics equation: {equation}"}
def _dynamics_calculation(self, params: Dict[str, float]) -> Dict[str, Any]:
"""Calculate dynamics quantities (forces, momentum, etc.)."""
equation = params.get("equation", "")
if equation == "force":
# F = m*a
m = params.get("mass", 0)
a = params.get("acceleration", 0)
F = m * a
return {
"success": True,
"result": F,
"unit": "N",
"description": "Force",
"formula": "F = m*a"
}
elif equation == "momentum":
# p = m*v
m = params.get("mass", 0)
v = params.get("velocity", 0)
p = m * v
return {
"success": True,
"result": p,
"unit": "kg*m/s",
"description": "Momentum",
"formula": "p = m*v"
}
elif equation == "gravitational_force":
# F = G*m1*m2/r^2
m1 = params.get("mass1", 0)
m2 = params.get("mass2", 0)
r = params.get("distance", 1)
G = self.constants["G"]
F = G * m1 * m2 / (r**2)
return {
"success": True,
"result": F,
"unit": "N",
"description": "Gravitational force",
"formula": "F = G*m1*m2/r^2"
}
else:
return {"success": False, "error": f"Unknown dynamics equation: {equation}"}
def _energy_calculation(self, params: Dict[str, float]) -> Dict[str, Any]:
"""Calculate energy quantities."""
equation = params.get("equation", "")
if equation == "kinetic":
# KE = 0.5*m*v^2
m = params.get("mass", 0)
v = params.get("velocity", 0)
KE = 0.5 * m * v**2
return {
"success": True,
"result": KE,
"unit": "J",
"description": "Kinetic energy",
"formula": "KE = 0.5*m*v^2"
}
elif equation == "potential":
# PE = m*g*h
m = params.get("mass", 0)
h = params.get("height", 0)
g = params.get("g", self.constants["g"])
PE = m * g * h
return {
"success": True,
"result": PE,
"unit": "J",
"description": "Gravitational potential energy",
"formula": "PE = m*g*h"
}
elif equation == "work":
# W = F*d*cos(theta)
F = params.get("force", 0)
d = params.get("distance", 0)
theta = params.get("angle", 0) # in radians
W = F * d * np.cos(theta)
return {
"success": True,
"result": W,
"unit": "J",
"description": "Work done",
"formula": "W = F*d*cos(theta)"
}
elif equation == "power":
# P = W/t or P = F*v
if "work" in params and "time" in params:
W = params["work"]
t = params["time"]
P = W / t
elif "force" in params and "velocity" in params:
F = params["force"]
v = params["velocity"]
P = F * v
else:
return {"success": False, "error": "Power calculation requires (work and time) or (force and velocity)"}
return {
"success": True,
"result": P,
"unit": "W",
"description": "Power",
"formula": "P = W/t or P = F*v"
}
else:
return {"success": False, "error": f"Unknown energy equation: {equation}"}
def _thermodynamics_calculation(self, params: Dict[str, float]) -> Dict[str, Any]:
"""Calculate thermodynamics quantities."""
equation = params.get("equation", "")
if equation == "ideal_gas":
# PV = nRT
# Solve for missing variable
P = params.get("pressure")
V = params.get("volume")
n = params.get("moles")
T = params.get("temperature")
R = self.constants["R"]
if P is None:
P = (n * R * T) / V
return {
"success": True,
"result": P,
"unit": "Pa",
"description": "Pressure",
"formula": "P = nRT/V"
}
elif V is None:
V = (n * R * T) / P
return {
"success": True,
"result": V,
"unit": "m^3",
"description": "Volume",
"formula": "V = nRT/P"
}
elif n is None:
n = (P * V) / (R * T)
return {
"success": True,
"result": n,
"unit": "mol",
"description": "Number of moles",
"formula": "n = PV/(RT)"
}
elif T is None:
T = (P * V) / (n * R)
return {
"success": True,
"result": T,
"unit": "K",
"description": "Temperature",
"formula": "T = PV/(nR)"
}
elif equation == "heat_transfer":
# Q = m*c*delta_T
m = params.get("mass", 0)
c = params.get("specific_heat", 0)
delta_T = params.get("temperature_change", 0)
Q = m * c * delta_T
return {
"success": True,
"result": Q,
"unit": "J",
"description": "Heat transferred",
"formula": "Q = m*c*ΔT"
}
elif equation == "efficiency":
# eta = W_out / Q_in or eta = 1 - T_cold/T_hot (Carnot)
if "work_out" in params and "heat_in" in params:
W_out = params["work_out"]
Q_in = params["heat_in"]
eta = W_out / Q_in if Q_in != 0 else 0
elif "temp_cold" in params and "temp_hot" in params:
T_cold = params["temp_cold"]
T_hot = params["temp_hot"]
eta = 1 - (T_cold / T_hot) if T_hot != 0 else 0
else:
return {"success": False, "error": "Efficiency requires (work_out and heat_in) or (temp_cold and temp_hot)"}
return {
"success": True,
"result": eta,
"result_percentage": eta * 100,
"unit": "dimensionless",
"description": "Thermal efficiency",
"formula": "η = W_out/Q_in or η = 1 - T_cold/T_hot"
}
else:
return {"success": False, "error": f"Unknown thermodynamics equation: {equation}"}
def _electromagnetism_calculation(self, params: Dict[str, float]) -> Dict[str, Any]:
"""Calculate electromagnetic quantities."""
equation = params.get("equation", "")
if equation == "coulomb_force":
# F = k*q1*q2/r^2 where k = 1/(4*pi*epsilon_0)
q1 = params.get("charge1", 0)
q2 = params.get("charge2", 0)
r = params.get("distance", 1)
k = 1 / (4 * np.pi * self.constants["epsilon_0"])
F = k * q1 * q2 / (r**2)
return {
"success": True,
"result": F,
"unit": "N",
"description": "Coulomb force",
"formula": "F = k*q1*q2/r^2"
}
elif equation == "electric_field":
# E = F/q or E = k*Q/r^2
if "force" in params and "charge" in params:
F = params["force"]
q = params["charge"]
E = F / q if q != 0 else 0
elif "source_charge" in params and "distance" in params:
Q = params["source_charge"]
r = params["distance"]
k = 1 / (4 * np.pi * self.constants["epsilon_0"])
E = k * Q / (r**2)
else:
return {"success": False, "error": "Electric field requires (force and charge) or (source_charge and distance)"}
return {
"success": True,
"result": E,
"unit": "N/C or V/m",
"description": "Electric field strength",
"formula": "E = F/q or E = k*Q/r^2"
}
elif equation == "magnetic_force":
# F = q*v*B*sin(theta)
q = params.get("charge", 0)
v = params.get("velocity", 0)
B = params.get("magnetic_field", 0)
theta = params.get("angle", np.pi/2) # default perpendicular
F = q * v * B * np.sin(theta)
return {
"success": True,
"result": F,
"unit": "N",
"description": "Magnetic force on moving charge",
"formula": "F = q*v*B*sin(θ)"
}
else:
return {"success": False, "error": f"Unknown electromagnetism equation: {equation}"}
def _quantum_calculation(self, params: Dict[str, float]) -> Dict[str, Any]:
"""Calculate quantum mechanics quantities."""
equation = params.get("equation", "")
if equation == "photon_energy":
# E = h*f or E = h*c/lambda
h = self.constants["h"]
c = self.constants["c"]
if "frequency" in params:
f = params["frequency"]
E = h * f
elif "wavelength" in params:
wavelength = params["wavelength"]
E = h * c / wavelength
else:
return {"success": False, "error": "Photon energy requires frequency or wavelength"}
return {
"success": True,
"result": E,
"result_eV": E / self.constants["e"],
"unit": "J",
"description": "Photon energy",
"formula": "E = h*f or E = h*c/λ"
}
elif equation == "de_broglie_wavelength":
# lambda = h/p = h/(m*v)
h = self.constants["h"]
if "momentum" in params:
p = params["momentum"]
wavelength = h / p if p != 0 else float('inf')
elif "mass" in params and "velocity" in params:
m = params["mass"]
v = params["velocity"]
p = m * v
wavelength = h / p if p != 0 else float('inf')
else:
return {"success": False, "error": "de Broglie wavelength requires momentum or (mass and velocity)"}
return {
"success": True,
"result": wavelength,
"unit": "m",
"description": "de Broglie wavelength",
"formula": "λ = h/p = h/(m*v)"
}
elif equation == "heisenberg_uncertainty":
# Δx*Δp >= hbar/2
hbar = self.constants["hbar"]
if "position_uncertainty" in params:
delta_x = params["position_uncertainty"]
min_delta_p = hbar / (2 * delta_x)
return {
"success": True,
"result": min_delta_p,
"unit": "kg*m/s",
"description": "Minimum momentum uncertainty",
"formula": "Δp >= ℏ/(2*Δx)"
}
elif "momentum_uncertainty" in params:
delta_p = params["momentum_uncertainty"]
min_delta_x = hbar / (2 * delta_p)
return {
"success": True,
"result": min_delta_x,
"unit": "m",
"description": "Minimum position uncertainty",
"formula": "Δx >= ℏ/(2*Δp)"
}
else:
return {"success": False, "error": "Heisenberg uncertainty requires position_uncertainty or momentum_uncertainty"}
else:
return {"success": False, "error": f"Unknown quantum equation: {equation}"}
class ChemistryCalculator:
"""
Chemistry calculator supporting molecular calculations,
stoichiometry, and chemical equilibrium.
"""
def __init__(self):
# Comprehensive periodic table data
self.periodic_table = {
1: ["H", "Hydrogen", 1.008],
2: ["He", "Helium", 4.003],
3: ["Li", "Lithium", 6.94],
4: ["Be", "Beryllium", 9.012],
5: ["B", "Boron", 10.81],
6: ["C", "Carbon", 12.011],
7: ["N", "Nitrogen", 14.007],
8: ["O", "Oxygen", 15.999],
9: ["F", "Fluorine", 18.998],
10: ["Ne", "Neon", 20.180],
11: ["Na", "Sodium", 22.990],
12: ["Mg", "Magnesium", 24.305],
13: ["Al", "Aluminum", 26.982],
14: ["Si", "Silicon", 28.085],
15: ["P", "Phosphorus", 30.974],
16: ["S", "Sulfur", 32.06],
17: ["Cl", "Chlorine", 35.45],
18: ["Ar", "Argon", 39.948],
19: ["K", "Potassium", 39.098],
20: ["Ca", "Calcium", 40.078],
26: ["Fe", "Iron", 55.845],
29: ["Cu", "Copper", 63.546],
30: ["Zn", "Zinc", 65.38],
35: ["Br", "Bromine", 79.904],
47: ["Ag", "Silver", 107.868],
53: ["I", "Iodine", 126.904],
79: ["Au", "Gold", 196.967],
82: ["Pb", "Lead", 207.2]
}
# Create reverse lookup by symbol
self.element_by_symbol = {
data[0]: {"number": num, "name": data[1], "mass": data[2]}
for num, data in self.periodic_table.items()
}
# Gas constant in different units
self.R_values = {
"J/mol/K": 8.314462618,
"L*atm/mol/K": 0.08206,
"cal/mol/K": 1.987
}
def calculate_chemistry_quantity(
self,
calculation_type: str,
parameters: Dict[str, Any]
) -> Dict[str, Any]:
"""
Perform chemistry calculations.
Args:
calculation_type: Type of chemistry calculation
parameters: Input parameters for calculation
Returns:
Dictionary with results
"""
try:
if calculation_type == "molar_mass":
if "formula" not in parameters:
return {"success": False, "error": "Molar mass calculation requires 'formula'"}
result = self._calculate_molar_mass(parameters["formula"])
elif calculation_type == "stoichiometry":
result = self._stoichiometry_calculation(parameters)
elif calculation_type == "ideal_gas":
result = self._ideal_gas_calculation(parameters)
elif calculation_type == "equilibrium":
result = self._equilibrium_calculation(parameters)
elif calculation_type == "pH":
result = self._ph_calculation(parameters)
else:
return {"success": False, "error": f"Unknown calculation type: {calculation_type}"}
return result
except Exception as e:
return {
"success": False,
"error": f"Chemistry calculation error: {str(e)}"
}
def _parse_chemical_formula(self, formula: str) -> Dict[str, int]:
"""
Parse chemical formula into element counts.
Example: H2SO4 -> {"H": 2, "S": 1, "O": 4}
"""
element_counts = {}
# Handle parentheses first
while "(" in formula:
# Find innermost parentheses
match = re.search(r'\(([^()]+)\)(\d*)', formula)
if not match:
break
inner = match.group(1)
multiplier = int(match.group(2)) if match.group(2) else 1
# Parse inner content
inner_pattern = r'([A-Z][a-z]?)(\d*)'
for elem, count in re.findall(inner_pattern, inner):
count = int(count) if count else 1
element_counts[elem] = element_counts.get(elem, 0) + count * multiplier
# Remove processed parentheses
formula = formula[:match.start()] + formula[match.end():]
# Pattern matches element symbol followed by optional number
pattern = r'([A-Z][a-z]?)(\d*)'
matches = re.findall(pattern, formula)
for element, count in matches:
if element:
count = int(count) if count else 1
element_counts[element] = element_counts.get(element, 0) + count
return element_counts
def _calculate_molar_mass(self, formula: str) -> Dict[str, Any]:
"""Calculate molar mass of a chemical formula."""
element_counts = self._parse_chemical_formula(formula)
total_mass = 0.0
composition = {}
for element, count in element_counts.items():
if element not in self.element_by_symbol:
return {"success": False, "error": f"Unknown element: {element}"}
element_mass = self.element_by_symbol[element]["mass"]
mass_contribution = element_mass * count
total_mass += mass_contribution
composition[element] = {
"count": count,
"atomic_mass": element_mass,
"mass_contribution": mass_contribution
}
# Calculate mass percentages
for element in composition:
composition[element]["percentage"] = (
composition[element]["mass_contribution"] / total_mass * 100
)
return {
"success": True,
"formula": formula,
"molar_mass": total_mass,
"unit": "g/mol",
"composition": composition,
"element_count": len(element_counts)
}
def _stoichiometry_calculation(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Perform stoichiometric calculations."""
# This requires reaction equation and amounts
if "reactant_formula" not in params or "product_formula" not in params:
return {"success": False, "error": "Stoichiometry requires reactant_formula and product_formula"}
reactant_formula = params["reactant_formula"]
product_formula = params["product_formula"]
reactant_moles = params.get("reactant_moles", 1.0)
stoich_ratio = params.get("stoichiometric_ratio", 1.0) # product/reactant
# Calculate molar masses
reactant_mm = self._calculate_molar_mass(reactant_formula)
product_mm = self._calculate_molar_mass(product_formula)
if not reactant_mm["success"] or not product_mm["success"]:
return {"success": False, "error": "Failed to calculate molar masses"}
# Calculate product moles based on stoichiometry
product_moles = reactant_moles * stoich_ratio
# Calculate masses
reactant_mass = reactant_moles * reactant_mm["molar_mass"]
product_mass = product_moles * product_mm["molar_mass"]
return {
"success": True,
"reactant": {
"formula": reactant_formula,
"moles": reactant_moles,
"mass": reactant_mass,
"molar_mass": reactant_mm["molar_mass"]
},
"product": {
"formula": product_formula,
"moles": product_moles,
"mass": product_mass,
"molar_mass": product_mm["molar_mass"]
},
"stoichiometric_ratio": stoich_ratio,
"unit_mass": "g",
"unit_moles": "mol"
}
def _ideal_gas_calculation(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate ideal gas properties using PV = nRT.
Solves for the missing variable.
"""
# Extract known variables
P = params.get("pressure") # Pa or atm depending on R
V = params.get("volume") # m^3 or L
n = params.get("moles") # mol
T = params.get("temperature") # K
# Determine which R to use based on units
unit_system = params.get("unit_system", "SI")
if unit_system == "SI":
R = self.R_values["J/mol/K"]
P_unit = "Pa"
V_unit = "m^3"
else:
R = self.R_values["L*atm/mol/K"]
P_unit = "atm"
V_unit = "L"
# Count how many variables are None
none_count = sum([P is None, V is None, n is None, T is None])
if none_count != 1:
return {"success": False, "error": "Exactly one variable must be unknown"}
# Determine which variable to solve for
if P is None:
if V == 0:
return {"success": False, "error": "Volume cannot be zero"}
P = (n * R * T) / V
return {
"success": True,
"solved_for": "pressure",
"result": P,
"unit": P_unit,
"given": {"volume": V, "moles": n, "temperature": T}
}
elif V is None:
if P == 0:
return {"success": False, "error": "Pressure cannot be zero"}
V = (n * R * T) / P
return {
"success": True,
"solved_for": "volume",
"result": V,
"unit": V_unit,
"given": {"pressure": P, "moles": n, "temperature": T}
}
elif n is None:
if R * T == 0:
return {"success": False, "error": "Temperature cannot be zero"}
n = (P * V) / (R * T)
return {
"success": True,
"solved_for": "moles",
"result": n,
"unit": "mol",
"given": {"pressure": P, "volume": V, "temperature": T}
}
elif T is None:
if n * R == 0:
return {"success": False, "error": "Moles cannot be zero"}
T = (P * V) / (n * R)
return {
"success": True,
"solved_for": "temperature",
"result": T,
"unit": "K",
"given": {"pressure": P, "volume": V, "moles": n}
}
def _equilibrium_calculation(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate chemical equilibrium quantities."""
if "concentrations" in params and "K_eq" in params:
# Calculate reaction quotient Q and compare to K_eq
concentrations = params["concentrations"]
K_eq = params["K_eq"]
products = concentrations.get("products", [])
reactants = concentrations.get("reactants", [])
if not products or not reactants:
return {"success": False, "error": "Both products and reactants concentrations required"}
# Calculate Q = [products] / [reactants]
Q_numerator = np.prod(products)
Q_denominator = np.prod(reactants)
Q = Q_numerator / Q_denominator if Q_denominator != 0 else float('inf')
# Determine reaction direction
if Q < K_eq:
direction = "forward (toward products)"
elif Q > K_eq:
direction = "reverse (toward reactants)"
else:
direction = "at equilibrium"
return {
"success": True,
"reaction_quotient": Q,
"equilibrium_constant": K_eq,
"direction": direction,
"at_equilibrium": abs(Q - K_eq) < 1e-6
}
else:
return {"success": False, "error": "Equilibrium calculation requires concentrations and K_eq"}
def _ph_calculation(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate pH, pOH, and related quantities."""
if "H_concentration" in params:
# Calculate pH from H+ concentration
H_conc = params["H_concentration"]
if H_conc <= 0:
return {"success": False, "error": "H+ concentration must be positive"}
pH = -np.log10(H_conc)
pOH = 14 - pH
OH_conc = 10**(-pOH)
return {
"success": True,
"pH": pH,
"pOH": pOH,
"H_concentration": H_conc,
"OH_concentration": OH_conc,
"acidity": "acidic" if pH < 7 else ("neutral" if pH == 7 else "basic")
}
elif "pH" in params:
# Calculate concentrations from pH
pH = params["pH"]
if pH < 0 or pH > 14:
return {"success": False, "error": "pH must be between 0 and 14"}
pOH = 14 - pH
H_conc = 10**(-pH)
OH_conc = 10**(-pOH)
return {
"success": True,
"pH": pH,
"pOH": pOH,
"H_concentration": H_conc,
"OH_concentration": OH_conc,
"acidity": "acidic" if pH < 7 else ("neutral" if pH == 7 else "basic")
}
else:
return {"success": False, "error": "pH calculation requires H_concentration or pH"}
class FinancialCalculator:
"""
Comprehensive financial calculator for business and investment analysis.
"""
def __init__(self):
self.days_per_year = 365
self.months_per_year = 12
def calculate_financial_metric(
self,
calculation_type: str,
parameters: Dict[str, Any]
) -> Dict[str, Any]:
"""
Calculate financial metrics and perform financial analysis.
Args:
calculation_type: Type of financial calculation
parameters: Input parameters
Returns:
Dictionary with calculation results
"""
try:
if calculation_type == "present_value":
result = self._present_value(parameters)
elif calculation_type == "future_value":
result = self._future_value(parameters)
elif calculation_type == "npv":
result = self._net_present_value(parameters)
elif calculation_type == "irr":
result = self._internal_rate_of_return(parameters)
elif calculation_type == "loan_payment":
result = self._loan_payment(parameters)
elif calculation_type == "amortization":
result = self._amortization_schedule(parameters)
elif calculation_type == "bond_price":
result = self._bond_pricing(parameters)
else:
return {"success": False, "error": f"Unknown calculation type: {calculation_type}"}
return result
except Exception as e:
return {
"success": False,
"error": f"Financial calculation error: {str(e)}"
}
def _present_value(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate present value of future cash flows.
PV = FV / (1 + r)^n
"""
if "future_value" not in params or "rate" not in params or "periods" not in params:
return {"success": False, "error": "Present value requires future_value, rate, and periods"}
future_value = params["future_value"]
rate = params["rate"]
periods = params["periods"]
discount_factor = 1 / ((1 + rate) ** periods)
pv = future_value * discount_factor
return {
"success": True,
"present_value": pv,
"future_value": future_value,
"rate": rate,
"rate_percentage": rate * 100,
"periods": periods,
"discount_factor": discount_factor
}
def _future_value(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate future value of present investment.
FV = PV * (1 + r)^n
"""
if "present_value" not in params or "rate" not in params or "periods" not in params:
return {"success": False, "error": "Future value requires present_value, rate, and periods"}
present_value = params["present_value"]
rate = params["rate"]
periods = params["periods"]
growth_factor = (1 + rate) ** periods
fv = present_value * growth_factor
total_return = fv - present_value
return_percentage = (total_return / present_value * 100) if present_value != 0 else 0
return {
"success": True,
"future_value": fv,
"present_value": present_value,
"rate": rate,
"rate_percentage": rate * 100,
"periods": periods,
"growth_factor": growth_factor,
"total_return": total_return,
"return_percentage": return_percentage
}
def _net_present_value(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate Net Present Value of cash flow series.
NPV = sum(CF_t / (1 + r)^t) for t = 0 to n
"""
if "cash_flows" not in params or "discount_rate" not in params:
return {"success": False, "error": "NPV requires cash_flows and discount_rate"}
cash_flows = params["cash_flows"]
discount_rate = params["discount_rate"]
if not isinstance(cash_flows, list) or len(cash_flows) == 0:
return {"success": False, "error": "cash_flows must be a non-empty list"}
npv = 0.0
discounted_flows = []
for t, cf in enumerate(cash_flows):
discount_factor = 1 / ((1 + discount_rate) ** t)
discounted_cf = cf * discount_factor
npv += discounted_cf
discounted_flows.append({
"period": t,
"cash_flow": cf,
"discount_factor": discount_factor,
"discounted_value": discounted_cf
})
total_undiscounted = sum(cash_flows)
return {
"success": True,
"npv": npv,
"discount_rate": discount_rate,
"discount_rate_percentage": discount_rate * 100,
"total_undiscounted": total_undiscounted,
"num_periods": len(cash_flows),
"discounted_cash_flows": discounted_flows,
"recommendation": "Accept project" if npv > 0 else ("Reject project" if npv < 0 else "Indifferent"),
"interpretation": f"The project has a net present value of {npv:.2f}. " +
("This positive NPV indicates the project adds value." if npv > 0 else
("This negative NPV indicates the project destroys value." if npv < 0 else
"This zero NPV indicates the project breaks even."))
}
def _internal_rate_of_return(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate Internal Rate of Return using Newton-Raphson method.
IRR is the rate where NPV = 0
"""
if "cash_flows" not in params:
return {"success": False, "error": "IRR requires cash_flows"}
cash_flows = np.array(params["cash_flows"])
if len(cash_flows) < 2:
return {"success": False, "error": "IRR requires at least 2 cash flows"}
# Use Newton-Raphson method to find IRR
# Initial guess
rate = 0.1
max_iterations = 100
tolerance = 1e-6
for iteration in range(max_iterations):
# Calculate NPV and its derivative
npv = 0
npv_derivative = 0
for t, cf in enumerate(cash_flows):
npv += cf / ((1 + rate) ** t)
if t > 0:
npv_derivative -= t * cf / ((1 + rate) ** (t + 1))
# Check convergence
if abs(npv) < tolerance:
break
# Newton-Raphson update
if abs(npv_derivative) < 1e-10:
return {"success": False, "error": "IRR calculation did not converge"}
rate = rate - npv / npv_derivative
# Verify by calculating NPV at this rate
npv_at_irr = sum(cf / ((1 + rate) ** t) for t, cf in enumerate(cash_flows))
return {
"success": True,
"irr": rate,
"irr_percentage": rate * 100,
"npv_at_irr": npv_at_irr,
"iterations": iteration + 1,
"cash_flows": cash_flows.tolist(),
"interpretation": f"The internal rate of return is {rate*100:.2f}%. This is the discount rate at which the project breaks even."
}
def _loan_payment(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate periodic loan payment using amortization formula.
PMT = P * [r(1+r)^n] / [(1+r)^n - 1]
"""
if "principal" not in params or "annual_rate" not in params or "years" not in params:
return {"success": False, "error": "Loan payment requires principal, annual_rate, and years"}
principal = params["principal"]
annual_rate = params["annual_rate"]
years = params["years"]
payments_per_year = params.get("payments_per_year", 12)
# Convert to periodic rate and number of payments
periodic_rate = annual_rate / payments_per_year
num_payments = years * payments_per_year
# Calculate payment
if periodic_rate == 0:
payment = principal / num_payments
else:
payment = principal * (
periodic_rate * (1 + periodic_rate) ** num_payments
) / (
(1 + periodic_rate) ** num_payments - 1
)
total_paid = payment * num_payments
total_interest = total_paid - principal
return {
"success": True,
"periodic_payment": payment,
"num_payments": int(num_payments),
"total_paid": total_paid,
"total_interest": total_interest,
"principal": principal,
"annual_rate": annual_rate,
"annual_rate_percentage": annual_rate * 100,
"periodic_rate": periodic_rate,
"periodic_rate_percentage": periodic_rate * 100,
"payments_per_year": payments_per_year,
"interpretation": f"Monthly payment of {payment:.2f} for {int(num_payments)} payments. Total interest paid: {total_interest:.2f}"
}
def _amortization_schedule(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Generate complete amortization schedule for a loan."""
# First calculate the payment
payment_result = self._loan_payment(params)
if not payment_result["success"]:
return payment_result
principal = params["principal"]
periodic_rate = payment_result["periodic_rate"]
payment = payment_result["periodic_payment"]
num_payments = payment_result["num_payments"]
# Generate schedule
schedule = []
remaining_balance = principal
for period in range(1, int(num_payments) + 1):
interest_payment = remaining_balance * periodic_rate
principal_payment = payment - interest_payment
remaining_balance -= principal_payment
# Handle final payment rounding
if period == num_payments:
principal_payment += remaining_balance
remaining_balance = 0
schedule.append({
"period": period,
"payment": payment,
"principal": principal_payment,
"interest": interest_payment,
"remaining_balance": max(0, remaining_balance)
})
return {
"success": True,
"schedule": schedule,
"summary": {
"total_payments": int(num_payments),
"periodic_payment": payment,
"total_paid": payment_result["total_paid"],
"total_interest": payment_result["total_interest"],
"original_principal": principal
}
}
def _bond_pricing(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate bond price given coupon rate, yield, and maturity."""
if "face_value" not in params or "coupon_rate" not in params or "yield_rate" not in params or "years" not in params:
return {"success": False, "error": "Bond pricing requires face_value, coupon_rate, yield_rate, and years"}
face_value = params["face_value"]
coupon_rate = params["coupon_rate"]
yield_rate = params["yield_rate"]
years = params["years"]
payments_per_year = params.get("payments_per_year", 2) # Semi-annual by default
# Calculate periodic values
periods = int(years * payments_per_year)
periodic_coupon = (face_value * coupon_rate) / payments_per_year
periodic_yield = yield_rate / payments_per_year
# Calculate present value of coupon payments
if periodic_yield == 0:
pv_coupons = periodic_coupon * periods
else:
pv_coupons = periodic_coupon * (1 - (1 + periodic_yield) ** (-periods)) / periodic_yield
# Calculate present value of face value
pv_face = face_value / ((1 + periodic_yield) ** periods)
# Bond price is sum of present values
bond_price = pv_coupons + pv_face
# Determine if trading at premium, discount, or par
if bond_price > face_value:
price_status = "premium"
elif bond_price < face_value:
price_status = "discount"
else:
price_status = "par"
return {
"success": True,
"bond_price": bond_price,
"face_value": face_value,
"coupon_rate": coupon_rate,
"coupon_rate_percentage": coupon_rate * 100,
"yield_rate": yield_rate,
"yield_rate_percentage": yield_rate * 100,
"years_to_maturity": years,
"periodic_coupon_payment": periodic_coupon,
"pv_of_coupons": pv_coupons,
"pv_of_face_value": pv_face,
"price_status": price_status,
"interpretation": f"The bond is trading at a {price_status} ({'above' if price_status == 'premium' else ('below' if price_status == 'discount' else 'equal to')} par value)."
}
# ============================================================================
# TOOL REGISTRY AND DISPATCHER
# ============================================================================
class ToolRegistry:
"""
Central registry for all calculator tools and their definitions.
Manages tool registration and execution.
"""
def __init__(self):
self.tools = {}
self.tool_definitions = []
# Initialize calculator instances
self.math_calc = MathematicsCalculator()
self.stats_calc = StatisticsCalculator()
self.physics_calc = PhysicsCalculator()
self.chem_calc = ChemistryCalculator()
self.finance_calc = FinancialCalculator()
# Register all tools
self._register_tools()
def _register_tools(self):
"""Register all available calculator tools with their definitions."""
# Mathematics tools
self.register_tool(
name="evaluate_mathematical_expression",
description="Evaluates mathematical expressions including arithmetic, algebra, calculus, and complex numbers. Supports integration, differentiation, limits, and equation solving.",
parameters={
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression using standard notation (e.g., 'x**2 + 2*x + 1', 'sin(x)', 'exp(x)')"
},
"operation": {
"type": "string",
"enum": ["evaluate", "integrate", "differentiate", "solve", "limit"],
"description": "Type of mathematical operation to perform"
},
"variable": {
"type": "string",
"description": "Variable name for calculus operations (e.g., 'x', 'y', 't')"
},
"lower_bound": {
"type": "number",
"description": "Lower bound for definite integrals"
},
"upper_bound": {
"type": "number",
"description": "Upper bound for definite integrals"
},
"point": {
"type": "number",
"description": "Point for limit evaluation"
},
"order": {
"type": "integer",
"description": "Order of derivative (default 1)"
}
},
"required": ["expression", "operation"]
},
function=self.math_calc.evaluate_expression
)
# Statistics tools
self.register_tool(
name="perform_statistical_test",
description="Performs statistical hypothesis testing including t-tests, chi-square tests, ANOVA, correlation, and regression analysis. Returns test statistics, p-values, and interpretations.",
parameters={
"type": "object",
"properties": {
"test_type": {
"type": "string",
"enum": ["t_test", "chi_square", "anova", "correlation", "regression"],
"description": "Type of statistical test to perform"
},
"data": {
"type": "object",
"description": "Dictionary containing data arrays. For t_test: 'sample' or 'sample1' and 'sample2'. For chi_square: 'observed' and 'expected' or 'contingency_table'. For anova: 'group1', 'group2', etc. For correlation/regression: 'x' and 'y'."
},
"alpha": {
"type": "number",
"description": "Significance level (default 0.05)"
},
"alternative": {
"type": "string",
"enum": ["two-sided", "less", "greater"],
"description": "Alternative hypothesis type (default 'two-sided')"
},
"population_mean": {
"type": "number",
"description": "Population mean for one-sample t-test"
}
},
"required": ["test_type", "data"]
},
function=self.stats_calc.perform_statistical_test
)
# Physics tools
self.register_tool(
name="calculate_physics_quantity",
description="Calculates physics quantities with proper unit handling. Supports kinematics, dynamics, energy, thermodynamics, electromagnetism, and quantum mechanics. Automatically converts units to SI.",
parameters={
"type": "object",
"properties": {
"calculation_type": {
"type": "string",
"enum": ["kinematics", "dynamics", "energy", "thermodynamics", "electromagnetism", "quantum"],
"description": "Type of physics calculation"
},
"parameters": {
"type": "object",
"description": "Input parameters with units. Include 'equation' to specify which formula to use. Parameters can have format {'value': number, 'unit': string} or just number (assumes SI)."
}
},
"required": ["calculation_type", "parameters"]
},
function=self.physics_calc.calculate_physics_quantity
)
# Chemistry tools
self.register_tool(
name="calculate_chemistry_quantity",
description="Performs chemistry calculations including molar mass, stoichiometry, ideal gas law, equilibrium, and pH calculations. Handles chemical formulas and reactions.",
parameters={
"type": "object",
"properties": {
"calculation_type": {
"type": "string",
"enum": ["molar_mass", "stoichiometry", "ideal_gas", "equilibrium", "pH"],
"description": "Type of chemistry calculation"
},
"parameters": {
"type": "object",
"description": "Input parameters for calculation. For molar_mass: 'formula'. For ideal_gas: 'pressure', 'volume', 'moles', 'temperature' (provide 3, solve for 4th). For pH: 'H_concentration' or 'pH'."
}
},
"required": ["calculation_type", "parameters"]
},
function=self.chem_calc.calculate_chemistry_quantity
)
# Financial tools
self.register_tool(
name="calculate_financial_metric",
description="Calculates financial metrics including present value, future value, NPV, IRR, loan payments, amortization schedules, and bond pricing. Essential for investment analysis and financial planning.",
parameters={
"type": "object",
"properties": {
"calculation_type": {
"type": "string",
"enum": ["present_value", "future_value", "npv", "irr", "loan_payment", "amortization", "bond_price"],
"description": "Type of financial calculation"
},
"parameters": {
"type": "object",
"description": "Input parameters for calculation. Varies by calculation_type. For npv/irr: 'cash_flows' (list) and 'discount_rate'. For loan_payment: 'principal', 'annual_rate', 'years'."
}
},
"required": ["calculation_type", "parameters"]
},
function=self.finance_calc.calculate_financial_metric
)
def register_tool(
self,
name: str,
description: str,
parameters: Dict[str, Any],
function: Callable
):
"""
Register a new tool with its definition and implementation.
Args:
name: Unique tool name
description: Human-readable description of what the tool does
parameters: JSON schema defining tool parameters
function: Callable that implements the tool
"""
self.tools[name] = function
self.tool_definitions.append({
"name": name,
"description": description,
"parameters": parameters
})
def get_tool_definitions(self) -> List[Dict[str, Any]]:
"""
Get all tool definitions for LLM function calling.
Returns:
List of tool definition dictionaries
"""
return self.tool_definitions
def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""
Execute a tool with given arguments.
Args:
tool_name: Name of the tool to execute
arguments: Dictionary of arguments to pass to the tool
Returns:
Dictionary containing execution results or error information
"""
if tool_name not in self.tools:
return {
"success": False,
"error": f"Unknown tool: {tool_name}"
}
try:
function = self.tools[tool_name]
result = function(**arguments)
return result
except TypeError as e:
return {
"success": False,
"error": f"Invalid arguments for tool {tool_name}: {str(e)}"
}
except Exception as e:
return {
"success": False,
"error": f"Tool execution failed: {str(e)}"
}
# ============================================================================
# LLM INTEGRATION LAYER
# ============================================================================
class LLMClient:
"""
Mock LLM client for demonstration purposes.
In production, this would interface with actual LLM APIs like OpenAI, Anthropic, etc.
"""
def __init__(self):
self.call_count = 0
def chat_completion(
self,
messages: List[Dict[str, Any]],
tools: List[Dict[str, Any]],
tool_choice: str = "auto"
) -> Dict[str, Any]:
"""
Simulate LLM API call.
In production, this would call actual LLM API.
Args:
messages: Conversation history
tools: Available tool definitions
tool_choice: How to use tools ("auto", "none", or specific tool)
Returns:
Dictionary with response content and optional tool calls
"""
self.call_count += 1
# This is a mock implementation
# Real implementation would call OpenAI, Anthropic, or other LLM API
# For demonstration, return a mock response
return {
"content": "This is a mock LLM response. In production, this would be the actual LLM output.",
"tool_calls": None
}
class LLMCalculatorSystem:
"""
Main system that integrates LLM with calculator tools.
Orchestrates the complete workflow from user input to final response.
"""
def __init__(self, llm_client, tool_registry: ToolRegistry):
"""
Initialize the LLM calculator system.
Args:
llm_client: Client for communicating with LLM API
tool_registry: Registry of available calculator tools
"""
self.llm_client = llm_client
self.tool_registry = tool_registry
self.conversation_history = []
# System prompt that explains the calculator capabilities
self.system_prompt = """You are an advanced calculator assistant with access to specialized computational tools for mathematics, statistics, physics, chemistry, and finance. When users ask for calculations:
1. Analyze the request to determine what calculations are needed
2. Use the appropriate calculator tools to perform precise computations
3. Interpret the results and present them clearly to the user
4. For complex problems, break them down into steps and use multiple tool calls if needed
5. Always explain your reasoning and the meaning of results
You have access to these calculator domains:
- Mathematics: algebra, calculus, complex numbers, equation solving, integration, differentiation
- Statistics: hypothesis testing, t-tests, ANOVA, chi-square, correlation, regression, probability
- Physics: mechanics, kinematics, dynamics, energy, thermodynamics, electromagnetism, quantum mechanics with proper unit handling
- Chemistry: stoichiometry, molecular properties, molar mass, ideal gas law, equilibrium calculations, pH
- Finance: time value of money, NPV, IRR, investment analysis, loan calculations, bond pricing
Use tools whenever precise numerical computation is required. Do not attempt to perform complex calculations yourself. Always use the tools for accuracy."""
def process_user_request(self, user_message: str) -> str:
"""
Process a user request through the LLM with tool calling.
Args:
user_message: The user's question or request
Returns:
The final response string
"""
# Add user message to conversation history
self.conversation_history.append({
"role": "user",
"content": user_message
})
# Maximum iterations to prevent infinite loops
max_iterations = 10
iteration = 0
while iteration < max_iterations:
iteration += 1
# Call LLM with current conversation and available tools
response = self._call_llm()
# Check if LLM wants to use tools
if response.get("tool_calls"):
# Execute requested tools
tool_results = self._execute_tool_calls(response["tool_calls"])
# Add assistant's tool call request to history
self.conversation_history.append({
"role": "assistant",
"content": response.get("content"),
"tool_calls": response["tool_calls"]
})
# Add tool results to history
for tool_result in tool_results:
self.conversation_history.append({
"role": "tool",
"tool_call_id": tool_result["tool_call_id"],
"content": json.dumps(tool_result["result"])
})
# Continue loop to let LLM process tool results
continue
else:
# LLM provided final response without tool calls
final_response = response["content"]
# Add to history
self.conversation_history.append({
"role": "assistant",
"content": final_response
})
return final_response
# If we hit max iterations, return what we have
return "I apologize, but I encountered complexity limits while processing your request. Please try breaking it into smaller parts."
def _call_llm(self) -> Dict[str, Any]:
"""
Call the LLM API with current conversation and tool definitions.
Returns:
Dictionary containing response content and any tool calls
"""
# Prepare messages with system prompt
messages = [
{"role": "system", "content": self.system_prompt}
] + self.conversation_history
# Get tool definitions
tools = self.tool_registry.get_tool_definitions()
# Call LLM API
response = self.llm_client.chat_completion(
messages=messages,
tools=tools,
tool_choice="auto"
)
return response
def _execute_tool_calls(self, tool_calls: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Execute all requested tool calls.
Args:
tool_calls: List of tool call requests from LLM
Returns:
List of tool execution results
"""
results = []
for tool_call in tool_calls:
tool_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
# Execute the tool
result = self.tool_registry.execute_tool(tool_name, arguments)
results.append({
"tool_call_id": tool_call["id"],
"result": result
})
return results
def reset_conversation(self):
"""Clear conversation history for a new session."""
self.conversation_history = []
def get_conversation_history(self) -> List[Dict[str, Any]]:
"""Get the current conversation history."""
return self.conversation_history
# ============================================================================
# DEMONSTRATION AND TESTING
# ============================================================================
def demonstrate_calculator_system():
"""
Demonstrate the calculator system with various examples.
This function shows how to use the system programmatically.
"""
print("=" * 80)
print("LLM-BASED CALCULATOR SYSTEM DEMONSTRATION")
print("=" * 80)
print()
# Initialize the system
tool_registry = ToolRegistry()
print("System initialized with the following calculators:")
print("- Mathematics Calculator")
print("- Statistics Calculator")
print("- Physics Calculator")
print("- Chemistry Calculator")
print("- Financial Calculator")
print()
print("=" * 80)
print()
# Example 1: Mathematics - Integration
print("EXAMPLE 1: Mathematics - Definite Integration")
print("-" * 80)
print("Calculate the integral of e^x from 0 to 5")
print()
result = tool_registry.execute_tool(
"evaluate_mathematical_expression",
{
"expression": "exp(x)",
"operation": "integrate",
"variable": "x",
"lower_bound": 0,
"upper_bound": 5
}
)
print("Result:")
print(f" Symbolic: {result['result']}")
print(f" Numerical: {result['numerical_value']:.6f}")
print(f" LaTeX: {result['latex']}")
print()
print("=" * 80)
print()
# Example 2: Statistics - Two-sample t-test
print("EXAMPLE 2: Statistics - Two-Sample T-Test")
print("-" * 80)
print("Compare two groups to determine if their means differ significantly")
print()
group1 = [23.5, 25.1, 24.8, 26.2, 24.5, 25.9, 24.1, 25.6]
group2 = [28.3, 27.9, 29.1, 28.5, 27.2, 28.8, 29.5, 28.1]
print(f"Group 1 (n={len(group1)}): {group1}")
print(f"Group 2 (n={len(group2)}): {group2}")
print()
result = tool_registry.execute_tool(
"perform_statistical_test",
{
"test_type": "t_test",
"data": {
"sample1": group1,
"sample2": group2
},
"alpha": 0.05,
"alternative": "two-sided"
}
)
print("Result:")
print(f" Test statistic: {result['statistic']:.4f}")
print(f" P-value: {result['p_value']:.4f}")
print(f" Reject null hypothesis: {result['reject_null']}")
print(f" Effect size (Cohen's d): {result['effect_size']:.3f}")
print(f" Group 1 mean: {result['sample1_mean']:.2f}")
print(f" Group 2 mean: {result['sample2_mean']:.2f}")
print()
print(f"Interpretation: {result['interpretation']}")
print()
print("=" * 80)
print()
# Example 3: Physics - Kinetic Energy
print("EXAMPLE 3: Physics - Kinetic Energy Calculation")
print("-" * 80)
print("Calculate kinetic energy of a 1500 kg car traveling at 25 m/s")
print()
result = tool_registry.execute_tool(
"calculate_physics_quantity",
{
"calculation_type": "energy",
"parameters": {
"equation": "kinetic",
"mass": 1500,
"velocity": 25
}
}
)
print("Result:")
print(f" Kinetic Energy: {result['result']:,.2f} {result['unit']}")
print(f" Formula: {result['formula']}")
print()
print("=" * 80)
print()
# Example 4: Chemistry - Molar Mass
print("EXAMPLE 4: Chemistry - Molar Mass Calculation")
print("-" * 80)
print("Calculate molar mass of sulfuric acid (H2SO4)")
print()
result = tool_registry.execute_tool(
"calculate_chemistry_quantity",
{
"calculation_type": "molar_mass",
"parameters": {
"formula": "H2SO4"
}
}
)
print("Result:")
print(f" Formula: {result['formula']}")
print(f" Molar Mass: {result['molar_mass']:.3f} {result['unit']}")
print()
print(" Composition:")
for element, data in result['composition'].items():
print(f" {element}: {data['count']} atoms, {data['mass_contribution']:.3f} g/mol ({data['percentage']:.2f}%)")
print()
print("=" * 80)
print()
# Example 5: Finance - Net Present Value
print("EXAMPLE 5: Finance - Net Present Value Analysis")
print("-" * 80)
print("Evaluate an investment project with the following cash flows:")
print(" Year 0: -100,000 (initial investment)")
print(" Year 1: 30,000")
print(" Year 2: 40,000")
print(" Year 3: 45,000")
print(" Year 4: 35,000")
print(" Discount rate: 10%")
print()
result = tool_registry.execute_tool(
"calculate_financial_metric",
{
"calculation_type": "npv",
"parameters": {
"cash_flows": [-100000, 30000, 40000, 45000, 35000],
"discount_rate": 0.10
}
}
)
print("Result:")
print(f" Net Present Value: ${result['npv']:,.2f}")
print(f" Total Undiscounted Cash Flow: ${result['total_undiscounted']:,.2f}")
print(f" Recommendation: {result['recommendation']}")
print()
print(f"Interpretation: {result['interpretation']}")
print()
print(" Detailed Cash Flow Analysis:")
for flow in result['discounted_cash_flows']:
print(f" Period {flow['period']}: ${flow['cash_flow']:,.2f} -> ${flow['discounted_value']:,.2f} (discount factor: {flow['discount_factor']:.4f})")
print()
print("=" * 80)
print()
# Example 6: Complex Multi-Domain Problem
print("EXAMPLE 6: Multi-Domain Problem - Loan Analysis")
print("-" * 80)
print("Calculate monthly payment for a $250,000 mortgage at 4.5% for 30 years")
print()
result = tool_registry.execute_tool(
"calculate_financial_metric",
{
"calculation_type": "loan_payment",
"parameters": {
"principal": 250000,
"annual_rate": 0.045,
"years": 30,
"payments_per_year": 12
}
}
)
print("Result:")
print(f" Monthly Payment: ${result['periodic_payment']:,.2f}")
print(f" Total Number of Payments: {result['num_payments']}")
print(f" Total Amount Paid: ${result['total_paid']:,.2f}")
print(f" Total Interest Paid: ${result['total_interest']:,.2f}")
print(f" Interest as % of Principal: {(result['total_interest']/result['principal']*100):.1f}%")
print()
print(f"Interpretation: {result['interpretation']}")
print()
print("=" * 80)
print()
# Example 7: Quantum Physics
print("EXAMPLE 7: Quantum Physics - Photon Energy")
print("-" * 80)
print("Calculate energy of a photon with wavelength 500 nm (green light)")
print()
result = tool_registry.execute_tool(
"calculate_physics_quantity",
{
"calculation_type": "quantum",
"parameters": {
"equation": "photon_energy",
"wavelength": 500e-9 # 500 nm in meters
}
}
)
print("Result:")
print(f" Photon Energy: {result['result']:.4e} {result['unit']}")
print(f" Photon Energy: {result['result_eV']:.3f} eV")
print(f" Formula: {result['formula']}")
print()
print("=" * 80)
print()
# Example 8: Regression Analysis
print("EXAMPLE 8: Statistics - Linear Regression")
print("-" * 80)
print("Analyze relationship between study hours and test scores")
print()
study_hours = [2, 3, 4, 5, 6, 7, 8, 9, 10]
test_scores = [65, 70, 75, 80, 82, 88, 90, 92, 95]
print(f"Study Hours: {study_hours}")
print(f"Test Scores: {test_scores}")
print()
result = tool_registry.execute_tool(
"perform_statistical_test",
{
"test_type": "regression",
"data": {
"x": study_hours,
"y": test_scores
},
"alpha": 0.05
}
)
print("Result:")
print(f" Regression Equation: {result['equation']}")
print(f" R-squared: {result['r_squared']:.4f}")
print(f" P-value: {result['p_value']:.6f}")
print(f" Standard Error: {result['standard_error']:.4f}")
print()
print(f"Interpretation: {result['interpretation']}")
print()
print("=" * 80)
print()
print("DEMONSTRATION COMPLETE")
print("=" * 80)
# ============================================================================
# MAIN ENTRY POINT
# ============================================================================
if __name__ == "__main__":
"""
Main entry point for the LLM-based calculator system.
This demonstrates the complete system functionality.
"""
# Run the demonstration
demonstrate_calculator_system()
print()
print("SYSTEM READY FOR PRODUCTION USE")
print()
print("To use this system in production:")
print("1. Replace LLMClient with actual LLM API client (OpenAI, Anthropic, etc.)")
print("2. Initialize the system:")
print(" tool_registry = ToolRegistry()")
print(" llm_client = YourLLMClient(api_key='your-key')")
print(" calculator_system = LLMCalculatorSystem(llm_client, tool_registry)")
print()
print("3. Process user requests:")
print(" response = calculator_system.process_user_request('Calculate integral of x^2 from 0 to 10')")
print()
print("The system will automatically:")
print("- Parse the user's natural language request")
print("- Determine which calculator tools to use")
print("- Execute the calculations with precision")
print("- Return results in human-friendly format")
CONCLUSION
This article has presented a comprehensive architecture for implementing an LLM-based calculator system that handles complex mathematical, statistical, physical, chemical, business, and financial calculations. The system combines the natural language understanding capabilities of large language models with specialized computational tools to provide an intuitive interface for technical calculations.
The key architectural principles include separation of concerns between language understanding and numerical computation, modular design that allows easy extension with new calculator domains, robust error handling and input validation, comprehensive unit handling for physics calculations, and structured result formats that include both numerical answers and human-readable interpretations.
The production-ready code example demonstrates all major components working together. Each calculator is implemented as an independent module with well-defined interfaces. The tool registry provides centralized management of available functions. The LLM integration layer orchestrates the complete workflow from user input through tool execution to final response generation.
This architecture can be extended in numerous ways. Additional calculator domains can be added for specialized fields like engineering, economics, or bioinformatics. Existing calculators can be enhanced with more sophisticated algorithms. The system can be integrated with visualization tools to generate graphs and charts. Caching mechanisms can improve performance for repeated calculations.
The combination of LLM intelligence with precise computational tools creates a powerful system that makes technical calculations accessible to users without specialized training. Users can express their needs in natural language, and the system handles the complexity of determining which calculations to perform, executing them accurately, and presenting results in an understandable format. This represents a significant advancement in human-computer interaction for technical and scientific computing.
No comments:
Post a Comment