By Sagar Shankaran, Founder of CallSphere
Build an AI agent that performs structured legal reasoning — searching precedents, constructing multi-step arguments with evidence chains, generating counter-arguments, and producing balanced legal analysis.
Key takeaways
Legal reasoning is fundamentally different from factual Q&A. A lawyer does not just retrieve facts — they construct arguments. Each argument has a claim, supporting evidence, a legal basis (statutes or precedent), and must withstand counter-arguments. This multi-step, adversarial structure makes legal reasoning an excellent test case for advanced agent architectures.
This tutorial builds a legal reasoning agent that can analyze a legal question, search for relevant precedents, construct structured arguments, and generate counter-arguments — all while maintaining proper evidence chains.
Legal arguments have a recursive structure: claims are supported by evidence, which may themselves be claims requiring further support.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart LR
CALLER(["Prospective Client"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["Legal Intake AI Agent"]
STT["Streaming STT<br/>Deepgram or Whisper"]
NLU{"Intent and<br/>Entity Extraction"}
TOOLS["Tool Calls"]
TTS["Streaming TTS<br/>ElevenLabs or Rime"]
end
subgraph DATA["Live Data Plane"]
CRM[("CRM and Notes")]
CAL[("Calendar and<br/>Schedule")]
KB[("Knowledge Base<br/>and Policies")]
end
subgraph OUT["Outcomes"]
O1(["Consultation booked"])
O2(["Conflict check passed"])
O3(["Attorney callback queued"])
end
CALLER --> SIP --> STT --> NLU
NLU -->|Lookup| TOOLS
TOOLS <--> CRM
TOOLS <--> CAL
TOOLS <--> KB
NLU --> TTS --> SIP --> CALLER
NLU -->|Resolved| O1
NLU -->|Schedule| O2
NLU -->|Escalate| O3
style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
style O1 fill:#059669,stroke:#047857,color:#fff
style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
from pydantic import BaseModel
from enum import Enum
class EvidenceType(str, Enum):
STATUTE = "statute"
CASE_LAW = "case_law"
REGULATION = "regulation"
EXPERT_OPINION = "expert_opinion"
FACTUAL = "factual"
class Evidence(BaseModel):
source: str
content: str
evidence_type: EvidenceType
relevance_score: float # 0.0 to 1.0
citation: str
class LegalArgument(BaseModel):
claim: str
supporting_evidence: list[Evidence]
reasoning_chain: list[str] # step-by-step logic
strength: float # 0.0 to 1.0
counter_arguments: list["LegalArgument"] = []
class LegalAnalysis(BaseModel):
question: str
arguments_for: list[LegalArgument]
arguments_against: list[LegalArgument]
conclusion: str
confidence: float
The agent needs a way to find relevant legal precedents. In production this would hit a legal database API (Westlaw, LexisNexis). Here we simulate it with a structured retrieval pattern:
from openai import OpenAI
import json
client = OpenAI()
def search_precedents(legal_issue: str, jurisdiction: str = "US Federal") -> list[Evidence]:
"""Search for relevant legal precedents."""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": (
"You are a legal research assistant. Given a legal issue, "
"identify the most relevant cases, statutes, and regulations. "
"For each, provide the citation, key holding, and relevance. "
"Return JSON array of evidence objects."
)},
{"role": "user", "content": (
f"Legal issue: {legal_issue}\n"
f"Jurisdiction: {jurisdiction}\n"
"Find 3-5 most relevant precedents."
)},
],
response_format={"type": "json_object"},
)
data = json.loads(response.choices[0].message.content)
return [Evidence(**e) for e in data.get("evidence", [])]
The argument builder works in three phases: (1) identify possible claims, (2) gather evidence for each, (3) construct the reasoning chain connecting evidence to claim.
def construct_argument(
claim: str,
evidence: list[Evidence],
legal_question: str,
) -> LegalArgument:
"""Build a structured legal argument from claim and evidence."""
evidence_summary = "\n".join(
f"[{e.evidence_type.value}] {e.citation}: {e.content}"
for e in evidence
)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": """You are a legal reasoning agent.
Construct a rigorous legal argument by:
1. Stating the claim clearly
2. Building a step-by-step reasoning chain from evidence to claim
3. Each step must cite specific evidence
4. Assess the overall strength of the argument (0.0-1.0)
5. Identify the weakest link in the reasoning chain
Return JSON with: reasoning_chain (list of steps), strength (float)."""},
{"role": "user", "content": (
f"Legal question: {legal_question}\n"
f"Claim to support: {claim}\n"
f"Available evidence:\n{evidence_summary}"
)},
],
response_format={"type": "json_object"},
)
data = json.loads(response.choices[0].message.content)
return LegalArgument(
claim=claim,
supporting_evidence=evidence,
reasoning_chain=data["reasoning_chain"],
strength=data["strength"],
)
A good legal analysis must address opposing views. The counter-argument generator takes an existing argument and attacks it:
def generate_counter_arguments(
argument: LegalArgument,
legal_question: str,
) -> list[LegalArgument]:
"""Generate counter-arguments that challenge the given argument."""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": """You are an opposing counsel.
Your job is to find flaws in the given argument and construct counter-arguments.
Attack strategies:
- Distinguish cited cases on facts
- Challenge the reasoning chain logic
- Cite conflicting precedent
- Argue policy implications
Return 2-3 counter-arguments as JSON."""},
{"role": "user", "content": (
f"Question: {legal_question}\n"
f"Argument to counter:\n"
f"Claim: {argument.claim}\n"
f"Reasoning: {argument.reasoning_chain}"
)},
],
response_format={"type": "json_object"},
)
data = json.loads(response.choices[0].message.content)
counters = []
for c in data.get("counter_arguments", []):
counters.append(LegalArgument(
claim=c["claim"],
supporting_evidence=[],
reasoning_chain=c["reasoning_chain"],
strength=c["strength"],
))
return counters
def analyze_legal_question(question: str) -> LegalAnalysis:
# 1. Search for relevant precedents
evidence = search_precedents(question)
# 2. Identify claims for and against
claims = identify_claims(question, evidence)
# 3. Construct arguments for each side
args_for = [construct_argument(c, evidence, question) for c in claims["for"]]
args_against = [construct_argument(c, evidence, question) for c in claims["against"]]
# 4. Generate counter-arguments
for arg in args_for:
arg.counter_arguments = generate_counter_arguments(arg, question)
# 5. Synthesize conclusion
conclusion = synthesize_conclusion(question, args_for, args_against)
return LegalAnalysis(
question=question,
arguments_for=args_for,
arguments_against=args_against,
conclusion=conclusion,
confidence=0.7,
)
This agent is a reasoning tool, not a replacement for licensed attorneys. It cannot guarantee legal accuracy, may miss jurisdiction-specific nuances, and should never be the sole basis for legal decisions.
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.
In production, connect the precedent search to a real legal database API. When using LLM-generated citations, always flag them as "AI-generated — verify before citing" and implement a validation step against a case law database.
Yes, by parameterizing the precedent search with jurisdiction and instructing the reasoning agent to consider jurisdictional differences. Multi-jurisdiction analysis requires separate evidence gathering for each jurisdiction and explicit conflict-of-law analysis.
Use a separate evaluator agent that scores arguments on: logical validity (does the conclusion follow from the premises?), evidence quality (are sources authoritative and relevant?), and completeness (are there obvious gaps in the reasoning chain?).
#LegalAI #LegalReasoning #ArgumentConstruction #EvidenceChains #AgenticAI #PythonAI #AIForLaw #ReasoningAgents

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.
Eve Legal is the agentic platform plaintiff firms picked for mass-tort intake in 2026. Here's the architecture, the per-intake pricing, the ROI numbers.
Lexis+ AI shipped major 2026 updates with litigation drafting and Brief Analyzer. Here's what's new, what it costs per seat. The 30-day picture for buyers and operators.
Enterprise CIO Guide perspective on Harvey AI's enterprise rollout numbers show legal agents have moved past the pilot stage at AmLaw 100 firms.
Robin AI is the contract-review agent UK big law picked in 2026. We profile deployments at Linklaters, Allen & Overy, and Slaughter and May, the per-contract pricing.
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.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.