ai_ml / transformers llm / 10_hallucinations_and_tools.md

Hallucinations, Function Calling, and Tool Use

9 interview angles 10 min read source

Hallucinations, Function Calling, and Tool Use

LLMs produce plausible-but-wrong outputs (hallucinations) and can connect to external tools (function calling). Both come up in nearly every Gen AI interview because they’re at the heart of building reliable systems.

For prompt engineering see 09_prompt_engineering.md. For RAG (the main mitigation for knowledge hallucination) see ../09_rag_embeddings/. For agentic patterns see ../10_agents_orchestration/.

Hallucinations — what and why

A hallucination is when the model produces a confident-sounding output that’s false, fabricated, or unsupported.

Q: Who wrote "The Anatomy of a Database System"?
A: "The Anatomy of a Database System" was written by Edgar Codd in 1970.
   (FALSE — Codd wrote on relational model; the named paper is by Hellerstein/Stonebraker/Hamilton, 2007)

Why LLMs hallucinate:

  1. They’re next-token predictors, not retrievers. They generate the most likely continuation; “most likely” doesn’t mean “true.”
  2. Training data has errors and contradictions. They average them.
  3. Information was rare or absent in training data. Model fills in plausible-sounding bits.
  4. Long-tail facts get lower fidelity than common ones. “Capital of France” — solid. “Tax code section 411(c)(2)” — risky.
  5. Reasoning chains amplify. One wrong step in a chain compounds.
  6. Prompts pressure the model to answer. “Don’t say you don’t know” leads to confabulation.

Types of hallucinations

Type Example
Factual wrong dates, names, citations, statistics
Logical invalid reasoning that sounds OK
Contextual / unfaithful answer doesn’t follow from provided context (RAG fails)
Confabulated tool calls model invents API endpoints, function names, parameter names
Fabricated citations references to papers/URLs that don’t exist
Self-contradiction model contradicts itself within one response

Hallucinations vary by domain. Code generation is relatively reliable (code is structured, the language is constrained). Specific factual recall (medical doses, legal precedents) is least reliable.

Mitigation strategies

Provide context (RAG)

If facts come from a knowledge base, retrieve relevant chunks and instruct the model to answer only from them.

Use ONLY the provided context to answer. If the context does not contain the answer,
respond "I don't have that information."

Context:
<retrieved chunks>

Question: <user question>

The single biggest reduction in hallucinations for factual Q&A.

Explicit “I don’t know” license

If you are uncertain, say "I'm not sure" rather than guessing.

Without this, models default to confident answers even when uncertain. With it, they more often abstain.

Citation requirements

For each claim in your answer, cite the source chunk by ID. If you cannot cite, do not include the claim.

Forces the model to ground answers; citations make it auditable.

Lower temperature

Temperature 0 (greedy decoding) produces the most likely token at each step. Less variation = less risk of veering into fabricated paths. For factual tasks, set temperature to 0.

Structured output

Free-form text is easy to hallucinate in. JSON with a schema is harder.

class Answer(BaseModel):
    response: str
    sources: list[int]      # chunk IDs
    confidence: Literal["high", "medium", "low"]

Model commits to a structure; “sources” must be filled with chunks that actually exist.

Verification step

Run a second LLM call to fact-check the first:

def verify(question, answer, context):
    return llm.generate(
        f"""Given the context:
{context}

Question: {question}
Answer: {answer}

Is the answer fully supported by the context? Respond yes/no with reasoning."""
    )

Adds latency and cost; catches a fraction of hallucinations. Worth it for high-stakes outputs.

Self-consistency

Generate multiple answers at temperature > 0; if they agree, more confidence. If they disagree, the model is uncertain.

answers = [llm.generate(prompt, temperature=0.7) for _ in range(5)]
if all_agree(answers):
    return answers[0]
return "I'm not sure — got conflicting answers"

Constrained generation

Force the model to choose from a fixed set:

# Classification — model can't hallucinate a new category
class Category(str, Enum):
    BILLING = "billing"
    TECHNICAL = "technical"
    SALES = "sales"

class Classification(BaseModel):
    category: Category

Where the answer space is bounded, constrain it.

Smaller scope, simpler prompts

Long prompts with many instructions invite drift. Break complex tasks into smaller prompts.

Human-in-the-loop for high stakes

For medical, legal, financial advice: don’t auto-publish. Show the model’s draft to a human.

What doesn’t work

  • “Don’t hallucinate” in the prompt: surprisingly little effect.
  • Demanding citations without RAG: model fabricates plausible-looking citations.
  • Bigger models alone: smarter models hallucinate less in absolute terms but still hallucinate; capability ≠ reliability.
  • Higher temperature: makes it worse.

Function calling / tool use

Letting the LLM trigger external actions: query a DB, call an API, run code, send an email.

# OpenAI function calling
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get current weather for a location",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {"type": "string", "description": "City name"},
                "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
            },
            "required": ["location"]
        }
    }
}]

response = openai.chat.completions.create(
    model=MODEL,
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
    tools=tools,
)

# Model returns:
# tool_calls=[{
#   "id": "call_abc",
#   "function": {"name": "get_weather", "arguments": '{"location": "Tokyo", "unit": "celsius"}'}
# }]

The model emits a structured request to call your function. Your code executes it. Result feeds back to the model.

# Execute the tool
result = get_weather(location="Tokyo", unit="celsius")  # → "12°C, partly cloudy"

# Send back to the model
response = openai.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "user", "content": "What's the weather in Tokyo?"},
        {"role": "assistant", "tool_calls": [...]},
        {"role": "tool", "tool_call_id": "call_abc", "content": result},
    ],
    tools=tools,
)
# Final answer: "It's 12°C in Tokyo, partly cloudy."

When to use tools

  • The model needs real-time data (weather, stock prices, current news).
  • The model needs to act (send email, book meeting, write to DB).
  • The model needs precise computation (math, regex, code execution).
  • The model needs domain-specific lookups (your KB, your API).

Function-calling patterns

Single function call: model decides to call a tool once; result returned. Standard for “give me current X” queries.

ReAct loop (Reason + Act): model alternates between reasoning and tool calls. Used for complex multi-step tasks.

User: "What's the weather in Paris and the time difference from New York?"

Model: I need weather and time data.
Action: get_weather("Paris")
Observation: 12°C
Action: get_time_diff("Paris", "New York")
Observation: 6 hours
Final answer: It's 12°C in Paris, which is 6 hours ahead of New York.

Parallel function calls (newer): model emits multiple tool calls in one turn; they execute in parallel; results returned together. Faster than sequential.

Recursive / agentic: model orchestrates multiple tools across many turns. See ../10_agents_orchestration/.

Tool design principles

  • Descriptive names: search_customer_orders beats query1. The model picks tools by name + description.
  • Clear descriptions: explain what the tool does AND when to use it. The description is the model’s prompt for tool selection.
  • Strict parameter schemas: use enums for categorical fields; specify required vs optional; type properly.
  • Idempotent when possible: model might call the same tool multiple times. Idempotent tools are safer.
  • Side-effect-free for read tools: the model may exploratively call tools. Don’t let “list users” charge a fee.
  • Limit blast radius: dangerous tools (delete data, send money) should require human approval, not just LLM intention.

Function-calling pitfalls

  • Hallucinated function names: model invents tools you didn’t define. Use strict-mode features (tool_choice="required") or validate at runtime.
  • Wrong argument types: model passes strings where ints expected. Validate; retry with clarification.
  • Calling a tool when it shouldn’t: model invokes delete_user from an ambiguous prompt. Authorization at the tool layer; never trust the LLM.
  • Infinite loops: agent keeps calling tools without finishing. Cap iterations.
  • Token cost: each tool call adds to the context. Long agent runs are expensive.

Tool calling vs structured output

Both produce structured data from the model. Different intents:

Structured output Function calling
Purpose extract data request an action
Triggered by “give me JSON” “call this tool”
Result parsed by your code tool executed; result fed back
Example extracting entities from text querying a database

Newer models blur the line. Both are forms of “make the model emit a structured response.”

MCP (Model Context Protocol)

A 2024+ standard from Anthropic for connecting LLMs to tools and data sources via a protocol. Lets you:

  • Define tools once; clients (Claude, Cursor, custom apps) consume them.
  • Decouple tool implementation from the LLM client.
  • Standardize across tools.

If you’re building tool integrations from scratch in 2025+, consider MCP servers over hand-rolled function calling.

OpenAI / Anthropic / Google specifics

OpenAI Anthropic Claude Google Gemini
Function calling tools + tool_calls tools + content blocks function declarations
Parallel calls yes yes yes
Structured output JSON schema mode tool-call pattern + prompting structured output mode
Strict mode (no hallucinated functions) strict: true careful prompting similar

Specifics vary by SDK. The pattern is universal: declare tools + schema → model emits call → execute → feed back result.

Reducing tool-call errors

# Validate model output with Pydantic before executing
class WeatherArgs(BaseModel):
    location: str
    unit: Literal["celsius", "fahrenheit"] = "celsius"

def safe_call(model_args):
    try:
        validated = WeatherArgs.model_validate_json(model_args)
        return get_weather(**validated.model_dump())
    except ValidationError as e:
        # Send back to model with error so it retries
        return f"Tool error: {e}. Please fix and try again."

Validate at the boundary. Send errors back to the model for retry. Cap retries.

Common patterns

Tool routing

def route(question):
    response = llm.generate(
        question,
        tools=[search_kb, web_search, calculator, send_email],
    )
    for tool_call in response.tool_calls:
        result = execute_tool(tool_call)
        # ... feed back ...

Model decides which tool fits the question.

Tool chaining

def book_meeting(participants, date_str):
    times = get_availability(participants, date_str)
    slot = pick_best_slot(times)
    invite = send_calendar_invite(participants, slot)
    return invite

LLM calls one tool whose output drives the next call.

Guarded tools

def transfer_money(from_account, to_account, amount, _user_approved):
    if not _user_approved:
        return "User approval required for transfer"
    # actual transfer

High-risk tools require explicit user confirmation, not LLM judgment.

Common interview confusions

  • “Bigger models don’t hallucinate.” — they hallucinate less but still hallucinate. No model is fully reliable for arbitrary factual recall.
  • “RAG eliminates hallucinations.” — it eliminates knowledge hallucinations from your indexed data. The model can still reason incorrectly, fabricate citations, or contradict the context if not constrained.
  • “Function calling makes the model ‘truly intelligent’.” — it’s structured output for tool calls. The model still gets things wrong; tools have their own failure modes.

Interview angle

  • “Why do LLMs hallucinate?” — they’re next-token predictors; “most likely” doesn’t mean “true.” Long-tail facts have weaker signal in training; the model fills with plausible content. Reasoning chains compound errors. Prompts that demand answers pressure confabulation.
  • “How do you reduce hallucinations in production?” — provide retrieved context (RAG), instruct “say I don’t know if unsure,” require citations to context, lower temperature, use structured output / constrained generation, add a verification step, human-in-the-loop for high stakes.
  • “What’s function calling / tool use?” — letting the LLM emit a structured request to invoke external functions (DB query, API call, computation). Your code executes; result feeds back. Enables LLMs to act on real data and trigger real actions.
  • “How do you defend against the LLM hallucinating tool calls?” — strict schemas, validation before execution, retries with error feedback, authorization layer (never let LLM intention alone trigger destructive actions), capping iterations on agent loops.
  • “Function calling vs structured output?” — function calling is a special case of structured output for tools. Both produce structured data; one represents “an action to take,” the other “data to return.” Modern APIs unify them.
  • “What’s ReAct?” — Reason + Act loop: model alternates between reasoning (chain of thought) and tool calls. Used for multi-step tasks where intermediate results inform next steps. Pattern behind most “AI agents.”
  • “How do you handle the LLM calling a destructive tool by mistake?” — never trust LLM intention for irreversible actions. Require explicit user confirmation, apply the tool’s own authorization (the LLM acts as the user, not as root), make tools idempotent where possible, log everything for audit.
  • “What’s MCP (Model Context Protocol)?” — Anthropic’s 2024+ standard for connecting LLMs to tools/data via a protocol. Define tools once; multiple clients consume. Decouples tool implementation from LLM client. Growing ecosystem.
  • “Cost / latency of tool use?” — each tool call adds context to the next prompt; long agent runs are expensive. Mitigate with parallel tool calls (where supported), context compression, capping iterations, caching tool results for the conversation.