Support agents built on LLMs routinely invent refund windows, misstate plan limits, and confabulate order statuses when they lack grounding. To reduce hallucinations support agent output at scale, you need a pipeline that retrieves verified context, forces the model to cite it, and validates the result before it reaches a customer. The following steps take you from a naive prompt to a measurable, reliable system.
Step 1: Retrieve and constrain context
A support reply is only as good as the knowledge you feed it. Stand up a vector index over your help center, past tickets, and policy docs. At request time, pull the top-k chunks and inject them as the only permitted source.
def retrieve(query: str, k: int = 4) -> list[dict]:
# Pseudocode for a vector search; swap in Pinecone, pgvector, etc.
hits = vector_store.similarity_search(query, k)
return [{"id": h.id, "text": h.payload["text"]} for h in hits]
context = retrieve("Can I get a refund after 30 days?")
Then build the prompt with an explicit constraint: answer solely from the supplied excerpts. This single rule cuts a large class of fabrications.
system = """You are a support agent. Use ONLY the provided context to answer.
If the context does not contain the answer, return {"answer": null}.
Do not use prior knowledge."""
user = "Context:\n" + "\n".join(f"[{c['id']}] {c['text']}" for c in context)
Step 2: Force citations with structured outputs
Free-text answers hide hallucinations. Demand a structured response that links every claim to a source id. OpenAI-compatible APIs support JSON mode or function calling; use it.
from openai import OpenAI
client = OpenAI() # or your gateway
resp = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": system},
{"role": "user", "content": user + "\nQuestion: Can I get a refund after 30 days?"}
],
temperature=0.0,
)
import json
data = json.loads(resp.choices[0].message.content)
# Expected: {"answer": "Refunds are allowed within 30 days.", "source": "doc_123"}
If source points to a retrieved id, you have a traceable claim. If the model returns answer: null, surface a fallback to a human.
Step 3: Add a refusal and uncertainty path
Models default to guessing when uncertain. Encode a hard refusal in the schema and treat null as a first-class outcome.
{
"type": "object",
"properties": {
"answer": {"type": ["string", "null"]},
"source": {"type": "string"},
"confidence": {"type": "number", "minimum": 0, "maximum": 1}
},
"required": ["answer", "source", "confidence"]
}
Set the system prompt to require confidence < 0.5 when the context is partial, and answer: null when absent. This trains the agent to stay silent rather than invent.
Step 4: Validate replies with a discriminator
Even cited text can be misrepresented. Run a second pass: a smaller model checks whether the answer is faithful to the cited chunk.
def verify(answer: str, source_text: str) -> bool:
check = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Does the ANSWER strictly follow from SOURCE? Reply 'yes' or 'no'."},
{"role": "user", "content": f"SOURCE: {source_text}\nANSWER: {answer}"}
],
temperature=0.0,
)
return check.choices[0].message.content.strip().lower() == "yes"
assert verify(data["answer"], next(c["text"] for c in context if c["id"] == data["source"]))
A reply that fails verification gets routed to a human queue. This layer is what lets you reduce hallucinations support agent deployments without trusting the generator blindly.
Step 5: Route to stable models with fallback
Model degradation spikes hallucination rates. If your primary provider throws 429s or returns truncated completions, the agent will guess. Use an inference gateway that keeps a healthy model in front of the request. n4n.ai exposes one OpenAI-compatible endpoint that addresses 240+ models and applies automatic fallback when a provider is rate-limited or degraded, so the generation step stays consistent under load.
from openai import OpenAI
import os
client = OpenAI(
base_url="https://api.n4n.ai/v1",
api_key=os.environ["N4N_API_KEY"],
)
# Same chat call as before; fallback happens server-side.
Honoring client routing directives also lets you pin a known-good model for high-risk intents (e.g., billing) while using cheaper models for FAQ triage.
Step 6: Measure with a golden eval set
You cannot improve what you do not measure. Assemble 50–100 real support questions with ground-truth answers and expected source ids. Run the pipeline nightly.
def eval_run(questions):
hits = 0
for q in questions:
out = pipeline(q["query"])
if out["answer"] and verify(out["answer"], q["source_text"]):
hits += 1
return hits / len(questions)
Track the faithfulness score and the null-rate. A rising null-rate means the retriever is missing docs; a falling faithfulness score means the generator or discriminator needs tuning. This loop is how teams reduce hallucinations support agent replies from sporadic fires to a monitored SLO.
How to verify success
Success means three things in production: (1) every non-null answer carries a valid source id from the retrieved set, (2) the discriminator passes on a baseline you establish for your own data (do not chase arbitrary thresholds like 95% without evidence), and (3) the weekly eval score holds flat or improves. Wire these checks into your CI and alert when the null-rate exceeds a threshold that indicates retrieval gaps. Only then can you trust the agent to autonomous customer-facing replies.