Zucchetti LLM Course — Lecture 7
When the LLM becomes the decision-maker
Kamyar Zeinalipour
Section 1 — Why RAG Needs Agency
Section 2 — Function Calling & Tool Use
Section 3 — The ReAct Paradigm
β Break
Section 4 — Agentic RAG Architectures
Section 5 — Multi-Agent Systems
Section 1 of 5
From pipeline to decision-maker
Some tasks can’t be hardcoded — you don’t know the steps in advance
“Book me a flight to Rome under €200 that doesn’t conflict with my meetings.”
Each lecture adds a layer of intelligence to your retrieval system
L7 Decide WHAT to retrieve — the LLM controls the pipeline
L6 Retrieve connected → Knowledge Graphs
L5 Retrieve smarter → HyDE, Routing, Re-ranking
L4 Retrieve similar → Vector Search
Four capabilities that separate agents from pipelines
| Capability | What It Does | Example |
|---|---|---|
| Perception | Understand the query intent | “This needs a calendar check + flight search” |
| Planning | Decompose into ordered steps | “First check calendar, then search flights” |
| Action | Use tools to execute steps | Call search_flights(), check_calendar() |
| Reflection | Check own work, retry if needed | “No flights under €200 — try flexible dates” |
Section 2 of 5
Teaching LLMs to act on the world
The LLM does NOT execute code. It writes a request. Your code executes it.
The LLM “calls an API” or “runs a function”
The LLM generates a JSON request. Your backend executes it and returns the result.
Analogy: The LLM is a doctor writing a prescription. The pharmacy (your code) fills it.
Five steps — the LLM never touches the internet
π§ LLM World
Understands query, picks tool, generates JSON, formats answer
βοΈ Your Code
Receives JSON, calls real API, returns data
You describe your tools in JSON Schema — the LLM reads this to decide what to call
{
"name": "get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, e.g. 'Rome'"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"],
"description": "Temperature units"}
},
"required": ["city"]
}
}
From natural language to structured JSON — automatically
“What’s the weather like in Rome?”
{
"name": "get_weather",
"arguments": {"city": "Rome", "units": "celsius"}
}
get_weather) AND extracted the arguments (Rome, celsius) from the user’s natural language. No parsing, no regex — the model did it.Your code runs the real API — the LLM never touched the internet
get_weather("Rome"){"temp":28, "condition":"sunny"}“It’s 28Β°C and sunny in Rome right now! Great weather for sightseeing.”
The “intern test”: if an intern can’t use your tool from the description, the LLM can’t either
| β Bad | β Good | |
|---|---|---|
| Name | process | search_flights |
| Description | “Handles requests” | “Search available flights between two cities on a given date. Returns a list of flights with prices.” |
| Parameters | data: string | origin: string, destination: string, date: string (YYYY-MM-DD), max_price: number |
| Constraints | None | enum: ["economy","business"] |
The LLM generates multiple tool calls at once — when they’re independent
“Is it warmer in Rome or Paris right now?”
β Parallel (correct)
[
{"name":"get_weather", "args":{"city":"Rome"}},
{"name":"get_weather", "args":{"city":"Paris"}}
]
Both run simultaneously
β Sequential (wasteful)
Call Rome → wait → call Paris → wait
2× latency for no reason
The LLM chains tools automatically — each result shapes the next decision
get_employee("Mario")get_policy("Engineering","remote")LLMs can learn to decide when to call tools — not just how
Insert API call into text → if it improves next-token prediction, keep it. Self-supervised: no human labels needed.
“The number in the next term is 18 + 12 × 3 = [Calculator(18 + 12 * 3)] 54.”
The model inserts a calculator call because it knows it’s bad at arithmetic. It discovered this on its own.
Schick et al. (2023) — “Toolformer: Language Models Can Teach Themselves to Use Tools” — NeurIPS 2023 (Oral)
Five failure modes you’ll hit in production
| Pitfall | Example | Fix |
|---|---|---|
| Hallucinated tool | Calls search_database() — doesn’t exist | Strict validation: reject unknown tools |
| Wrong types | Sends "28" (string) instead of 28 (number) | Schema validation + type coercion |
| Ignoring result | Calls API, gets data, generates answer from memory | Prompt: “use ONLY the tool result” |
| Over-calling | Calls calculator for “2 + 2” | Prompt: “only use tools when needed” |
| Under-calling | Answers from memory when it should search | Prompt: “always verify with tools” |
LLM generates JSON requests. Your code executes them.
The LLM never runs code, calls APIs, or accesses the internet.
Query → JSON → Execute → Result → Answer
Section 3 of 5
Reasoning + Acting = Grounded Intelligence
Same pattern, fundamentally different control
YOU write 3 prompts
YOU decide the steps
YOU control the flow
Steps are known before execution
DEVELOPER = PILOT
LLM writes its own reasoning
LLM decides the next action
LLM controls the flow
Steps discovered at runtime
DEVELOPER = AIR TRAFFIC CONTROL
Both break queries into steps — but when?
Plan ALL sub-queries upfront
All steps known before retrieval
One LLM call to decompose, then execute all
Like a battle plan written before the war
Discover steps one at a time
Step 2 depends on step 1’s result
Each step = Thought + Action + Observation
Like improvisation on the battlefield
CoT reasons but can’t verify — it thinks, it doesn’t know
“Who directed Inception?”
CoT: “I think Inception was directed by Christopher Nolan. He’s known for films like Interstellar and The Dark Knight…”
β οΈ It thinks it’s Nolan. What if it’s wrong? No way to verify.
“Let me search for the director of Inception” → search("director Inception") → verified fact
Thought → Action → Observation — repeat until done
π Thought
What should I do next?
β‘ Action
Call a tool
ποΈ Observation
Read the result
The LLM generates Thought and Action. The Observation comes from the tool. The loop continues until a finish() action.
Yao et al. (2023) — “ReAct: Synergizing Reasoning and Acting in Language Models” — ICLR 2023
“What awards has the director of Inception won?”
search("director of Inception film")
search("Christopher Nolan awards won")
continued on next slide →
finish("Christopher Nolan directed Inception. He has won the Academy Award for Best Director for Oppenheimer (2024), along with BAFTA and Golden Globe awards.")
Same question, different approaches
“Inception was directed by Christopher Nolan” β
“He won the Oscar for Interstellar” β Wrong!
“He also won for Dunkirk” β Wrong!
Sounds plausible. Confident. Incorrect.
Search: “director Inception” → Nolan β
Search: “Nolan awards” → Oppenheimer 2024 β
Every fact verified by real tool call β
Slower. More calls. Correct.
“Is Mario eligible for the senior developer promotion?”
get_employee("Mario Rossi") → Role: Developer, Hired: 2019search_policy("senior developer promotion criteria") → Requires: 3+ years, ≥4.0 review avgget_reviews("Mario Rossi") → Avg: 4.3/5.0finish("Yes — Mario meets both criteria: 5+ years tenure and 4.3 avg review.")Four failure modes and their fixes
| Failure | What Happens | Fix |
|---|---|---|
| Infinite loops | Keeps searching, never stops | max_iterations = 10 |
| Wrong tool | Searches docs when it should query SQL | Better tool descriptions |
| Early stopping | Stops after 1 hop when 3 are needed | Prompt: “keep going until complete” |
| Context overflow | Too many observations fill the window | Summarize past observations |
5 observations × 2K tokens each = 10K tokens of context consumed. With a 32K window, that’s 31% gone just on observations. Solution: summarize older observations to ~200 tokens each.
Reflection tokens let the model critique its own generation
| Token | Question It Asks | Values |
|---|---|---|
[Retrieve] | “Should I retrieve here?” | yes / no / continue |
[IsRel] | “Is the retrieved passage relevant?” | relevant / irrelevant |
[IsSup] | “Is my answer supported by evidence?” | fully / partially / no |
[IsUse] | “Is the overall response useful?” | 1–5 scale |
Asai et al. (2024) — “Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection” — ICLR 2024 (Oral, top 1%)
The model retrieves only when it needs to
[Retrieve] = no[Retrieve] = yes
[IsRel] = relevant → generates answer
[IsSup] = fully → answer is supported by evidence → output
When to use which — they’re complementary, not competing
| Function Calling | ReAct | |
|---|---|---|
| Best for | Deterministic, known workflows | Exploratory, open-ended tasks |
| Steps | Predefined or few | Discovered at runtime |
| Latency | Low (1–2 calls) | Higher (3–10 calls) |
| Cost | Low | Higher |
| Control | High — you define tools | Lower — LLM improvises |
| Production | β Most systems use this | Research tasks, complex queries |
When we come back: Agentic RAG Architectures
So far: tools (Section 2) + reasoning (Section 3).
Next: put them together into a self-correcting RAG system.
Section 4 of 5
RAG systems that think
Fixed. One shot. No quality check.
Dynamic. Self-correcting. Re-retrieves if needed.
Full architecture — color-coded by origin
If Retrieval Grader says β → rewrite query → loop back. If Answer Grader says β → retry.
L5 routing always retrieves. The Query Analyzer adds a new option: don’t retrieve at all.
| Route | When | Example | Origin |
|---|---|---|---|
| β Answer directly | LLM already knows | “What is Python?” | NEW |
| β‘ Search docs | Company-specific | “What is our leave policy?” | L5 |
| β’ Query graph | Relationship query | “Who reports to Mario?” | L6 |
| β£ Search web | Real-time info | “What is EUR/USD today?” | L5 |
L5 gave you the tools. This agent orchestrates them.
| Step | What | L5 Tool Used |
|---|---|---|
| 1. Pick index | Vector? Graph? SQL? | Semantic routing |
| 2. Formulate query | Optimize the search query | HyDE / query rewriting |
| 3. Decide k | How many results to fetch | Adaptive top-k |
| 4. Parallelize | Call multiple sources at once | Parallel FC (Section 2) |
| 5. Re-rank | Score results by relevance | Cross-encoder (L5) |
After retrieval: is this context actually relevant?
β Relevant
Use these docs for generation
β οΈ Partial
Retrieve more from different source
β Irrelevant
Reformulate query → retrieve again
LLM-as-Judge prompt:
“Given the query and retrieved documents, rate relevance as: RELEVANT, PARTIAL, or IRRELEVANT. Explain why.”
After generation: is my answer actually good?
“Does my answer address the question?”
Catches: off-topic answers, tangential information
“Is my answer supported by the retrieved context?”
Catches: hallucination, information not in context
If either check fails → retry with different retrieval or query reformulation
Route by query complexity — save cost on simple questions
Level A
Simple
No retrieval needed
Cost: $0.001
Level B
Moderate
Single retrieval
Cost: $0.005
Level C
Complex
Iterative retrieval (ReAct)
Cost: $0.02
A small classifier routes before the expensive pipeline starts.
Jeong et al. (2024) — “Adaptive-RAG: Learning to Adapt Retrieval-Augmented LLMs through Question Complexity” — NAACL 2024
What happens when retrieval returns garbage?
“What is Zucchetti’s current remote work policy?”
Three-way grading: Correct, Ambiguous, or Incorrect
β Correct
Retrieved docs are relevant and current
→ Use docs directly
β οΈ Ambiguous
Partially relevant or potentially outdated
→ Supplement with web search
β Incorrect
Docs are irrelevant or wrong
→ Discard, fall back to web only
Yan et al. (2024) — “Corrective Retrieval Augmented Generation” — arXiv 2024
“What is Zucchetti’s remote work policy?”
How do you implement an agentic RAG system?
Explicit nodes and edges
Predictable execution path
Easy to debug and test
You define every transition
β Start here
LLM decides the next step
Flexible, handles unexpected cases
Harder to debug
Requires guardrails
β οΈ Graduate to this
You’re not starting from scratch — you’re adding intelligence to what you already built
π New in L7
Query Analyzer
Retrieval Grader
Answer Grader
Adaptive routing
Self-correcting loop
π§ From L5
HyDE
Re-ranking
Semantic routing
Query decomposition
πΈοΈ From L6
Knowledge graphs
Graph traversal
Entity extraction
Graph + vector hybrid
Section 5 of 5
When one agent isn’t enough
Three reasons to split work across agents
One agent can’t hold 50 documents + conversation history + tool results
A “researcher” agent + “writer” agent > one generalist
Multiple agents work simultaneously on different sub-tasks
Agents pass work linearly — each has focused context
When to use: well-defined workflows with clear stages. Each agent has its own system prompt, tools, and output schema.
A manager agent routes sub-tasks to specialists
π― Supervisor
Sees the big picture, delegates tasks
π Data Agent
SQL queries
π Writer Agent
Drafts text
π Reviewer Agent
Checks facts
When to use: complex tasks needing dynamic decomposition. The supervisor decides which worker to call based on the current state.
“Write a report on our Q3 sales performance”
SELECT region, SUM(revenue) FROM sales WHERE quarter='Q3' → returns table“Agents without guardrails are interns with root access”
| Guardrail | What It Prevents | Implementation |
|---|---|---|
| π Tool authorization | Agent calls unauthorized API | Whitelist of allowed tools per agent |
| π‘οΈ PII filtering | Agent exposes personal data | Regex + NER scan on outputs |
| π€ Human-in-the-loop | High-stakes actions without approval | Pause before delete, purchase, send |
| π° Cost budget | Runaway agent burns $1000 | Max tokens / max $ per query |
| β±οΈ Timeout | Infinite loop agent | Max 30 seconds per query |
Four pillars for production-grade agents
| Pillar | Why | Tools |
|---|---|---|
| Observability | Trace every tool call, reasoning step | LangSmith, Phoenix, Arize |
| Cost Control | Budget per query, cache results | Token counting, result caching |
| Latency | Minimize agent hops, parallelize | Parallel FC, async execution |
| Error Handling | Graceful degradation | Fallback to simpler pipeline |
| Framework | Approach | Best For |
|---|---|---|
| Custom Python | Function calling + your code | Learning, max flexibility |
| LangGraph | State machines — explicit nodes/edges | Production systems, max control |
| CrewAI | Role-based agents with goals | Quick prototyping, team metaphor |
| MS Agent Framework | Enterprise-grade, .NET + Python | Azure / Microsoft ecosystem |
Don’t use a chainsaw to cut butter
| Query Type | Right Tool | NOT Agents |
|---|---|---|
| Simple factual | RAG (L4) | Overkill — one retrieval is enough |
| Vocabulary mismatch | HyDE (L5) | Agents won’t help with embeddings |
| Multi-source | Routing (L5) | Static routing is cheaper |
| Relationship queries | Graph RAG (L6) | Graph traversal is deterministic |
| Multi-step + dynamic + tools | Agentic RAG (L7) β | This is the sweet spot |
Approximate figures — vary by model, provider, and implementation
| Approach | LLM Calls | Cost/Query | Latency | When |
|---|---|---|---|---|
| Naive RAG (L4) | 1 | $0.002 | 0.3s | Simple queries |
| Advanced RAG (L5) | 2–3 | $0.005 | 0.8s | Complex retrieval |
| Agentic RAG (L7) | 3–10 | $0.02 | 2–5s | Dynamic multi-step |
| Multi-Agent (L7) | 5–20 | $0.05 | 5–15s | Complex workflows |
1. Function calling = LLM generates JSON, your code executes. The LLM never touches the internet.
2. ReAct = reasoning + acting. Each fact is grounded by a real tool call — hallucination dramatically reduced.
3. Agentic RAG = a self-correcting pipeline. Grading (retrieval + answer) is what makes it “agentic.”
4. You’re not starting from scratch. L7 wraps L4–L6 components in an intelligent controller.
5. Multi-agent = specialization when one context window isn’t enough.
6. Start simple. Add agency only when the query genuinely needs dynamic reasoning.
Four levels of retrieval sophistication — you now have all of them
L7 Autonomous RAG — LLM decides what, when, and how to retrieve
L6 Graph RAG — follow relationships between entities
L5 Smart RAG — re-rank, rewrite, route, decompose
L4 Basic RAG — embed, search, generate
A decision flowchart for your next RAG project
| Question | Answer | Use |
|---|---|---|
| Simple factual query? | Yes | L4 — Basic RAG |
| Vocabulary mismatch or multi-source? | Yes | L5 — HyDE, routing |
| Needs to follow relationships? | Yes | L6 — Graph RAG |
| Multi-step, dynamic, needs tools? | Yes | L7 — Agentic RAG |
| Too complex for one agent? | Yes | L7 — Multi-Agent |
Next → Lecture 8: RAG Evaluation & Observability
You’ve built increasingly powerful systems. But how do you know they’re actually working?
Metrics, tracing, and evaluation frameworks.
Thank you! See you next session.
Kamyar Zeinalipour — Zucchetti LLM Course