Skip to main content
Connic
Back to BlogTutorial

AI Agent RAG Tutorial: Retrieval With Citations

Build a production RAG agent with scoped retrieval namespaces, read-only permissions, source citations, custom tool wrappers, and regression tests.

November 15, 2025(last updated: August 17, 2026)9 min readAuthor: Connic Engineering

Production RAG gives an AI agent a narrow search capability. In Connic, put trusted content in a named namespace, wrap semantic search in a purpose-specific Python tool, enforce read-only access in the agent YAML, and test both tool selection and citation output before deployment.

Formerly Knowledge Base

What a production RAG boundary controls

Retrieval-augmented generation adds relevant passages to an agent run before the model answers. Semantic search finds passages by meaning, but it does not decide which corpus is approved, whether the agent may write to it, or how the answer cites its evidence. Those decisions belong in your tool and agent configuration.

A fixed search scope
The custom tool owns the namespace, score threshold, and result limit. Storage routing stays in code; the model supplies only the question.
Runtime access controls
The agent YAML allowlists namespaces and can block writes and deletes. The same checks apply when a custom tool calls a predefined retrieval function.
A citation contract
The wrapper turns source fields into a stable shape. The system prompt tells the model when to cite that evidence and what to do when no passage supports an answer.
A regression contract
Tests assert that policy questions call the search tool, unrelated prompts do not, and grounded answers include the expected source reference.
Build grounded agents on managed retrieval

Index content by namespace, expose only the search contract each agent needs, and inspect every retrieval call in its trace.

Try Connic free

Step 1: index approved content in a namespace

Open your project's Retrieval page and upload the documents the agent may use. Assign a namespace such as support.approved and a descriptive entry ID such as refund-policy-2026. A query against a parent namespace also searches its child namespaces, so a stable hierarchy gives you room to split policies, product docs, and runbooks later.

Connic Retrieval showing policy and runbook documents organized in a namespace tree, with chunk counts and content types
The Retrieval page groups indexed entries by namespace and shows the content available to semantic search.

Uploads are indexed asynchronously. Wait until the ingestion job completes, then use the dashboard query to check representative questions before wiring the index into an agent. Read how to upload and inspect retrieval content.

Step 2: expose a purpose-specific search tool

Custom tools are plain typed Python functions under tools/. No decorator is required. Connic uses the function signature and docstring to build the schema the model sees. This wrapper fixes the namespace and returns only the passage and citation fields the support agent needs:

tools/support_knowledge.py
from connic.tools import retrieval_query

async def search_support_docs(question: str) -> list[dict]:
    """Find approved support passages for a customer question.

    Args:
        question: The customer's policy or product question.
    """
    result = await retrieval_query(
        query=question,
        namespace="support.approved",
        min_score=0.35,
        max_results=5,
    )

    matches = []
    for item in result.get("results", []):
        source = item["entry_id"]
        page = item.get("page_number")
        citation = f"{source}, p. {page}" if page else source
        matches.append({
            "passage": item["content"],
            "citation": citation,
        })
    return matches

The predefined retrieval_query function returns ranked chunks with source fields including entry_id, namespace, and page_number when the source has pages. The wrapper converts those fields into the citation format your product will display. Connic returns the evidence; your tool and prompt define how the answer cites it.

Directly attaching retrieval_query is useful for a prototype or a general internal operator. A production support agent should not choose arbitrary namespaces or tune search parameters at run time. It also should not receive retrieval_store unless writing indexed content is part of its job. Keep ingestion in the dashboard, a retrieval source, or a controlled API pipeline for reader agents. See the custom retrieval wrapper reference.

Step 3: enforce the same boundary in agent YAML

Attach the wrapper by its module and function name. The retrieval block is a second, runtime-enforced boundary: this agent can reach only support.approved and its child namespaces, and it cannot store or delete content there.

agents/support-agent.yaml
version: "1.0"

name: support-agent
type: llm
model: connic/gpt-5.6-terra
description: "Answers support questions from approved documentation"
system_prompt: |
  Answer policy and product questions only from passages returned by
  search_support_docs. Cite every supported claim in square brackets
  using the citation value from the tool result.

  If the search returns no relevant passage, say that the approved
  documentation does not answer the question. Do not guess.

tools:
  - support_knowledge.search_support_docs

retrieval:
  namespaces:
    support.approved:
      prevent_write: true
      prevent_delete: true

The wrapper improves the tool interface; the YAML access block limits what the underlying retrieval call can do. Keep both. A later refactor that changes the wrapper's namespace still hits the agent-level allowlist instead of silently widening access.

Step 4: test retrieval use and citation output

Mock the custom wrapper for deterministic agent tests. The mock below returns one known passage and citation without depending on the current index contents. Mocked custom tool calls still appear in the trace and count toward tool-call assertions.

tests/mocks/support_knowledge.py
def mock_support_knowledge_search_support_docs(tool_name, params, context):
    return [{
        "passage": "Enterprise refunds are available within 60 days.",
        "citation": "refund-policy-2026, p. 12",
    }]
tests/support-agent.yaml
version: "1.0"

tests:
  - name: cites_the_refund_policy
    payload: "What is the enterprise refund window?"
    mocks: support_knowledge
    strict_mocks: true
    expected_tool_calls:
      - support_knowledge.search_support_docs: invocations == 1
    expected_result: '"[refund-policy-2026, p. 12]" in output'

  - name: skips_retrieval_for_a_greeting
    payload: "Say hello."
    mocks: support_knowledge
    strict_mocks: true
    expected_no_tool_calls:
      - support_knowledge.search_support_docs

The first case catches a model or prompt change that stops searching or drops the source reference. The second catches unnecessary retrieval on a prompt the model can answer without company knowledge. Run the suite with connic test; the same tests run as the deployment gate. Keep a separate non-mocked case in a test environment when you also need to detect indexing or relevance changes. Follow the tool-call assertion reference.

Production checks before you ship

Test the corpus before the agent
Query the namespace with real support questions. Check that the useful passage clears your threshold and that near-duplicate or obsolete documents do not dominate the results.
Separate readers from writers
Reader agents get purpose-specific query wrappers and read-only namespaces. If an agent must add content, give it a separate write wrapper and only the namespace it owns.
Treat citations as output behavior
Stable entry IDs make citations inspectable. Tests should fail when a grounded answer omits its source or when the agent answers a company-specific question without searching.
Inspect the trace after deployment
Confirm the production run called the wrapper once, used the expected question, and returned evidence that supports the final answer.

Review every retrieval query and storage parameter or deploy the example from the quickstart.

Frequently Asked Questions

A production RAG boundary fixes the searchable namespace, score threshold, and result limit in a purpose-specific tool. The agent gets read-only access to approved content, receives stable citation fields, and has tests that verify when retrieval runs and whether grounded answers cite the expected source.

Direct retrieval_query access is useful for prototypes and broad internal operators. For a production task, wrap it in a typed tool whose input is the business question and whose code owns the namespace, threshold, result limit, and response shape. The model should not choose the storage boundary at run time.

Give the agent a purpose-specific search wrapper, omit retrieval_store from its tools, and set prevent_write and prevent_delete for the allowed namespace in agent YAML. Keep uploads and source synchronization in the dashboard or a separate controlled ingestion path.

Mock the purpose-specific search tool with a known passage and citation. Assert that a policy question calls the tool once and that the final answer includes the expected citation. Add a second case that verifies unrelated prompts do not call retrieval, then keep a non-mocked test in a test environment for relevance and indexing changes.

More from the Blog

Tutorial

How to Deploy a Python AI Agent Without Kubernetes

Deploy a Python AI agent without Kubernetes using YAML, plain Python, deployment-gated tests, Git, and a managed EU runtime. Includes working code.

August 12, 202612 min read
Tutorial

How to Trigger AI Agents from Kafka Topics

Point a Connic Kafka inbound connector at a topic and every message starts an agent run. Configure the connector, link an agent, deploy, and watch runs.

July 12, 20268 min read
Tutorial

How to Add an AI Agent to Your SaaS Without a Large Engineering Team

A practical, step-by-step path to shipping your first production AI agent with a small team: scope one job, define it in config, connect it to your existing systems, and let a runtime handle the rest.

June 12, 20269 min read
Tutorial

Automated Agent Scoring: AI Agent Evaluation with LLM Judges

Automated agent scoring uses an LLM judge to grade sampled or every matching agent run against criteria you define. Track score trends and alert on regressions.

March 29, 202610 min read
Tutorial

Migrate from LangChain to Production AI Agents

Your LangChain prototype works. Now you need it to handle real traffic. Learn how to migrate existing agent code to a production-grade platform without rewriting from scratch.

March 23, 202611 min read
Tutorial

Database vs Retrieval vs Sessions: Choose and Debug Agent Memory

Compare Connic's database, Retrieval, and persistent sessions. Configure identity and TTL, then inspect sanitized events and runs matched to that identity.

March 4, 202612 min read
Tutorial

AI Agents: From Prototype to Production

Your demo works great until you have 1,000 concurrent users. A practical guide to the production requirements most teams find out about too late.

January 10, 202610 min read
Tutorial

Hidden Costs of Self-Hosting AI Agents

We'll just deploy it on Kubernetes. Famous last words. The true cost of self-hosting AI agents versus a managed platform.

December 18, 20257 min read
Tutorial

Add AI Agents to SaaS Without an ML Team

Your customers expect AI features, but you don't have ML engineers. Learn how teams ship AI agents using skills they already have.

December 5, 20258 min read