By Sagar Shankaran, Founder of CallSphere
Learn how to build AI agents that perform causal reasoning using causal graphs, interventions, and counterfactual analysis — moving beyond pattern matching to genuine understanding of cause and effect.
Key takeaways
Standard LLM agents excel at finding patterns and correlations in data. But correlation is not causation — and when an agent needs to make decisions, it needs to understand why things happen, not just that they tend to co-occur.
Consider an agent analyzing customer churn. It notices that customers who contact support more often have higher churn rates. A correlation-based agent might recommend reducing support contacts. A causal reasoning agent would recognize that dissatisfaction causes both support contacts and churn — and that reducing support access would actually increase churn.
Judea Pearl's causal hierarchy defines three levels of reasoning: seeing (correlation), doing (intervention), and imagining (counterfactual). Most AI agents operate at level one. This tutorial pushes them to levels two and three.
A causal graph (also called a DAG — directed acyclic graph) represents cause-and-effect relationships between variables:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart LR
REQ(["Request"])
BATCH["Continuous batching<br/>vLLM scheduler"]
PREF{"Prefill or<br/>decode?"}
PRE["Prefill phase<br/>parallel attention"]
DEC["Decode phase<br/>token by token"]
KV[("Paged KV cache")]
SAMP["Sampling<br/>top-p, temp"]
STREAM["Stream tokens<br/>to client"]
REQ --> BATCH --> PREF
PREF -->|First token| PRE --> KV
PREF -->|Next token| DEC
KV --> DEC --> SAMP --> STREAM
SAMP -->|EOS| DONE(["Response complete"])
style BATCH fill:#4f46e5,stroke:#4338ca,color:#fff
style KV fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style STREAM fill:#0ea5e9,stroke:#0369a1,color:#fff
style DONE fill:#059669,stroke:#047857,color:#fff
from dataclasses import dataclass, field
@dataclass
class CausalNode:
name: str
description: str
possible_values: list[str]
@dataclass
class CausalEdge:
cause: str
effect: str
mechanism: str # how the cause produces the effect
strength: str # "strong", "moderate", "weak"
@dataclass
class CausalGraph:
nodes: dict[str, CausalNode] = field(default_factory=dict)
edges: list[CausalEdge] = field(default_factory=list)
def add_node(self, node: CausalNode):
self.nodes[node.name] = node
def add_edge(self, edge: CausalEdge):
self.edges.append(edge)
def get_causes(self, effect: str) -> list[CausalEdge]:
return [e for e in self.edges if e.effect == effect]
def get_effects(self, cause: str) -> list[CausalEdge]:
return [e for e in self.edges if e.cause == cause]
def describe(self) -> str:
lines = ["Causal Graph:"]
for edge in self.edges:
lines.append(
f" {edge.cause} --({edge.strength})--> {edge.effect}"
f" [{edge.mechanism}]"
)
return "\n".join(lines)
The agent can construct causal graphs from domain knowledge:
from openai import OpenAI
import json
client = OpenAI()
def discover_causal_structure(domain: str, variables: list[str]) -> CausalGraph:
"""Use LLM domain knowledge to propose causal relationships."""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": """You are a causal reasoning expert.
Given a domain and variables, identify causal relationships.
For each relationship, specify:
- cause and effect variables
- the mechanism (HOW the cause produces the effect)
- strength (strong/moderate/weak)
- whether this is well-established or hypothetical
CRITICAL: Only include edges where there is a genuine causal mechanism.
Correlation without mechanism is NOT causation.
Return JSON with nodes and edges arrays."""},
{"role": "user", "content": (
f"Domain: {domain}\n"
f"Variables: {variables}\n"
"Identify the causal structure."
)},
],
response_format={"type": "json_object"},
)
data = json.loads(response.choices[0].message.content)
graph = CausalGraph()
for n in data["nodes"]:
graph.add_node(CausalNode(**n))
for e in data["edges"]:
graph.add_edge(CausalEdge(**e))
return graph
Pearl's do-operator asks: "What happens if we force variable X to a specific value?" This is different from observing X naturally. The agent simulates interventions by cutting incoming edges to the intervened variable:
def simulate_intervention(
graph: CausalGraph,
intervention: dict[str, str],
target: str,
) -> dict:
"""Simulate do(X=x) and predict the effect on target."""
# Build modified graph description (cut incoming edges to intervened vars)
modified_edges = [
e for e in graph.edges
if e.effect not in intervention # remove edges into intervened vars
]
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": """You are a causal inference engine.
An intervention has been applied: certain variables are forced to specific values.
Using the causal graph (with incoming edges to intervened variables removed),
predict the effect on the target variable.
Trace the causal path from intervention to target step by step.
Return JSON: {predicted_effect, confidence, reasoning_path}."""},
{"role": "user", "content": (
f"Causal graph edges: {modified_edges}\n"
f"Intervention: do({intervention})\n"
f"Target variable: {target}\n"
"Predict the causal effect."
)},
],
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)
Counterfactuals ask "What would have happened if...?" — the most powerful level of causal reasoning:
def counterfactual_analysis(
graph: CausalGraph,
actual_scenario: dict[str, str],
counterfactual_change: dict[str, str],
outcome_variable: str,
) -> dict:
"""Analyze: if X had been different, would the outcome change?"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": """You are a counterfactual reasoning engine.
Given what actually happened and a hypothetical change, determine:
1. Would the outcome have been different?
2. Through which causal path would the change propagate?
3. How confident are you in this counterfactual?
Use the causal graph to trace effects. Be explicit about assumptions."""},
{"role": "user", "content": (
f"Causal structure: {graph.describe()}\n"
f"What actually happened: {actual_scenario}\n"
f"Counterfactual: What if {counterfactual_change}?\n"
f"Would {outcome_variable} have been different?"
)},
],
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)
When an agent uses causal reasoning for decision-making, it follows this process: (1) build or retrieve the causal graph for the domain, (2) for each possible action, simulate the intervention, (3) compare predicted outcomes across actions, and (4) select the action with the best causal effect on the goal variable. This is fundamentally more robust than choosing actions based on observed correlations in historical data.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
LLMs have absorbed vast amounts of causal knowledge from scientific literature and common sense. They perform well on causal reasoning benchmarks when explicitly prompted to think causally. However, they can still confuse correlation with causation — the structured approach in this tutorial (explicit graphs, interventions, counterfactuals) guards against this.
Three approaches: (1) domain expert review, (2) statistical testing with observational data using tools like DoWhy or CausalML, and (3) A/B tests that directly test proposed causal relationships through real interventions.
Use causal reasoning when the agent needs to recommend actions (interventions), explain outcomes, or predict effects of changes. Use correlational reasoning for prediction tasks where the data distribution is stable and no interventions are planned.
#CausalReasoning #CausalInference #Counterfactuals #PearlsCausalHierarchy #AgenticAI #PythonAI #AIReasoning #DataScience

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
GPT Image 2.0 is the first image model with native reasoning. Turn on thinking mode and it can plan composition, search the web, self-check, and emit up to 8 consistent images per prompt.
Modal turns a Python function into autoscaling serverless compute with optional GPU. Deploy a LiveKit Agent with one command and get pay-per-second billing.
Pydantic AI's April release tightens the typed-agent loop and adds structured tool definitions. Why type-safe agents reduce production bugs and speed iteration.
Index a knowledge base with text-embedding-3-large into ChromaDB, expose a retrieve tool to your voice agent, and ground every answer in real documents — full Python tutorial.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.