Skip to content

Runtime view — tool-calling path

Status: Implemented (v3)

This page describes how engineering calculation requests are handled, including validation errors and the retry loop.


Flow diagram

The router is not a LangGraph node — it is the conditional edge route_after_intent() evaluated after the intent node.

graph TD
    Start([User: calculation query]) --> Intent[Intent node]
    Intent -->|calculation| Tools[Tools node]

    Tools --> Guard{refuse_unsafe<br/>calculations?}
    Guard -->|stress without FoS context| Refuse[Append error, skip tool]
    Guard -->|ok| Select{LLM or regex<br/>tool selection}

    Select --> Validate{Pydantic<br/>validation}

    Validate -->|invalid args| Err[Append to state.errors]
    Err -->|retry if attempts < 3| Select

    Validate -->|valid| Exec[Execute pure Python tool]
    Exec --> Result[tool_results in state]
    Refuse --> Report[Report node]
    Result --> Report
    Report --> End([Answer to user])

    classDef error fill:#fdd,stroke:#333;
    class Err error;
    class Refuse error;

The loop from validation errors back to tool selection is the LangGraph conditional edge on the tools node: the LLM (or regex fallback) sees prior errors and can correct inputs on the next attempt.


Tool selection modes

Mode When Mechanism
LLM tool-calling CAEC_LLM_API_KEY set langchain bind_tools + ToolMessage loop
Regex dispatch No API key Pattern rules in nodes/tools.py
Input parsing Regex mode name=value pairs extracted from query

Max attempts: 3 (_MAX_TOOL_ATTEMPTS in nodes/tools.py).


Registered tools

Tool Purpose
estimate_particle_count Estimate particle count from geometry and packing density
convert_units Convert length/mass/time units (mm/m, g/kg, ms/s)
check_timestep_heuristic Heuristic DEM timestep check from particle size and density

All tools are pure functions — no I/O, no LLM calls inside the tool body.


Validation and self-correction

  1. LLM proposes tool name + JSON arguments.
  2. validate_tool_inputs() checks against the JSON Schema in tools/registry.py.
  3. On failure, error message is appended to state["errors"].
  4. Graph retries up to _MAX_TOOL_ATTEMPTS.
  5. On success, ToolResult is stored and the report node formats the answer.

Guardrails

When CAEC_REFUSE_UNSAFE_CALCULATIONS=true, the tools node refuses to run a calculation when the query mentions stress without safety-factor context or explicit yield_stress / max_stress parameters. The report node then formats the refusal message for the user.