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 the 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

Content remains scoped by namespace, each agent receives only the required search contract, and every retrieval call stays visible in its trace.

Try Connic free
  1. index approved content in a namespace

    Open the 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 leaves 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. uploading and inspecting retrieval content.

  2. expose a purpose-specific search tool

    Custom tools are plain typed Python functions under tools/. No decorator is required. Our platform 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 the product will display. Connic returns the evidence; the 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. custom retrieval wrapper reference.

  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.

  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 to detect indexing or relevance changes as well. Tool-call assertion reference.

Production checks before release

Test the corpus before the agent
Query the namespace with real support questions. Check that the useful passage clears the chosen 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.

The retrieval query and storage reference covers the APIs, and the quickstart walks through deployment.

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

Expose the Invoice Processor Template as an MCP Tool

Deploy Connic's invoice template as an MCP tool, discover it from Python, send invoice text, and check the extracted fields with a complete client example.

September 11, 202610 min read
Tutorial

How to Deploy a Python AI Agent Without Kubernetes

A Python AI agent deployed 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 Small Engineering Teams Can Add an AI Agent to SaaS

A practical, step-by-step path to shipping a first production AI agent with a small team: scope one job, define it in config, connect it to 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 defined criteria. Track score trends and alert on regressions.

March 29, 202610 min read
Tutorial

Migrate from LangChain to Production AI Agents

A working LangChain prototype still needs to handle real traffic. Migrate existing agent code to a production-grade platform without rewriting from scratch.

March 23, 202611 min read
Tutorial

Database vs Retrieval vs Sessions: Agent Memory Compared

A comparison of Connic's Database, Retrieval, and persistent sessions, including identity, TTL, sanitized events, and runs matched to that identity.

March 4, 202612 min read
Tutorial

AI Agents: From Prototype to Production

A demo works great until 1,000 concurrent users arrive. 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

AI Agents in SaaS Without an ML Team

Customers expect AI features even when a product team has no ML engineers. See how teams ship AI agents using skills they already have.

December 5, 20258 min read