> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hydradb.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> AGENTS.md is HydraDB's maintained agent guide. Read https://docs.hydradb.com/AGENTS.md once, then use its content. /llms.txt and /llms-full.txt redirect to the same guide; they are not separate documentation indexes. If the guide is already loaded, continue with it instead of fetching either alias or reloading the guide.

# Build your own customer support agent

> Support agents that answer from one shared help center and remember each customer's history.

A good support answer needs two things at once: what the help center says, and what has already happened with this customer. This cookbook keeps the help center in one shared collection and each customer's history in their own, then queries both together. Triage, billing, and technical agents all read the same context.

| Benchmark | Result | What it shows here |
| - | - | - |
| [LongMemEval-S](https://benchmarks.hydradb.com/longmemeval) | 90.79% overall | A customer's facts and preferences kept current across many sessions |
| [BEAM 1M](https://benchmarks.hydradb.com/beam-1m) | 82% overall | History that stays useful over months |
| [KyroBench](https://benchmarks.hydradb.com/kyrobench) | 0.776 nDCG | One customer's context kept out of another's results |

## Prerequisites

* A HydraDB API key from [dashboard.hydradb.com](https://dashboard.hydradb.com), set as `HYDRADB_API_KEY`.
* An OpenAI API key, set as `OPENAI_API_KEY`. HydraDB returns context, not answers, so any model works; swap the `answer` function below for your own.
* Python 3.10 or later, with the SDKs: `pip install "hydradb-sdk>=2.1.7,<3" openai`.

Save this as `hydra.py`. Every cookbook imports it. It holds what every HydraDB application does around its own logic: wait for the database, check what an ingest accepted, wait for indexing, and pass query results to a model.

```python hydra.py theme={"dark"}
import os
import time

from hydra_db import HydraDB
from hydra_db.errors import ConflictError
from hydra_db.helpers import build_string
from openai import OpenAI

client = HydraDB(token=os.environ["HYDRADB_API_KEY"])
llm = OpenAI()


def create_database(database, schema=None):
    """Create the database if it is new, then wait until it accepts context."""
    try:
        if schema:
            client.databases.create(database=database, database_metadata_schema=schema)
        else:
            client.databases.create(database=database)
    except ConflictError:
        pass  # The database already exists.
    while not client.databases.status(database=database).data.infra.ready_for_ingestion:
        time.sleep(5)


def source_ids(result):
    """Return the IDs an ingest accepted. Raise if it rejected any item."""
    for item in result.data.results:
        if item.error_code:
            raise RuntimeError(f"{item.filename or item.id}: {item.error_code} {item.error}")
    return [item.id for item in result.data.results]


def wait_indexed(database, collection, ids):
    """Block until every source has finished indexing. Indexing runs in the background."""
    while True:
        statuses = client.context.status(
            database=database, collection=collection, ids=ids
        ).data.statuses
        for status in statuses:
            if status.indexing_status == "errored":
                raise RuntimeError(f"{status.error_code}: {status.error_message}")
        if all(s.indexing_status == "completed" for s in statuses):
            return
        time.sleep(2)


def answer(question, result, instructions=""):
    """Send a query result to the model and return its answer."""
    completion = llm.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": "Answer using only the context provided. If the answer "
                "is not in the context, say you don't know. " + instructions,
            },
            {
                "role": "user",
                "content": f"Context:\n{build_string(result)}\n\nQuestion: {question}",
            },
        ],
    )
    return completion.choices[0].message.content
```

## 1. Create a database with a shared collection

```python support.py theme={"dark"}
import json

from hydra import answer, client, create_database, source_ids, wait_indexed

DATABASE = "acme_support"
HELP_CENTER = "help_center"

create_database(DATABASE)


def customer_collection(customer_id):
    return f"customer_{customer_id}"
```

## 2. Upload the help center

Upload help articles as files. Give each a stable `id` so that publishing a new version replaces the old one.

```python support.py theme={"dark"}
articles = {
    "refunds.md": "# Refunds\n\nRefunds are available within 30 days of purchase. "
                  "Annual plans are refunded pro rata.\n",
    "sso.md": "# Single sign-on\n\nSSO is available on the Business plan. "
              "Set it up under Settings, then Security.\n",
}
for name, body in articles.items():
    with open(name, "w") as f:
        f.write(body)

handles = [open(name, "rb") for name in articles]
result = client.context.ingest(
    type="knowledge",
    database=DATABASE,
    collection=HELP_CENTER,
    documents=[(name, h, "text/markdown") for name, h in zip(articles, handles)],
    document_metadata=json.dumps([{"id": name.removesuffix(".md")} for name in articles]),
)
for h in handles:
    h.close()
wait_indexed(DATABASE, HELP_CENTER, source_ids(result))
```

## 3. Remember each conversation

When a conversation ends, save it as a [memory](https://docs.hydradb.com/essentials/v2/context.md#2-save-memories) in that customer's collection. With `infer: true`, HydraDB extracts what matters: the customer's plan, what they asked for, what they were told.

```python support.py theme={"dark"}
def remember(customer_id, turns):
    """turns is a list of (customer message, agent reply) pairs."""
    collection = customer_collection(customer_id)
    result = client.context.ingest(
        type="memory",
        database=DATABASE,
        collection=collection,
        memories=json.dumps([{
            "user_assistant_pairs": [{"user": u, "assistant": a} for u, a in turns],
            "infer": True,
        }]),
    )
    wait_indexed(DATABASE, collection, source_ids(result))


remember("4821", [
    ("We're on the annual Business plan. I asked for a refund on September 30.",
     "I've logged the request. Finance will confirm once the billing cycle closes."),
])
```

## 4. Answer from both collections

Name both collections and set `type: "all"`. The query reads this customer's memories and the shared help center, and nothing else. Take `customer_id` from the authenticated session, never from the message: the collection name is what keeps one customer's history out of another's answers.

```python support.py theme={"dark"}
ROLES = {
    "triage": "Decide whether this is a billing or a technical question, and say why.",
    "billing": "Answer billing questions. State the policy, then what applies to this customer.",
    "technical": "Answer setup questions with numbered steps.",
}


def support_agent(role, customer_id, question):
    result = client.query(
        database=DATABASE,
        collections=[HELP_CENTER, customer_collection(customer_id)],
        query=question,
        type="all",
        mode="thinking",
    )
    return answer(question, result, ROLES[role])


question = "Any update on my refund, and how much will I get back?"
print(support_agent("triage", "4821", question))
print(support_agent("billing", "4821", question))
```

## Run it

```bash theme={"dark"}
python support.py
```

The billing answer should combine both sources: the refund was requested on September 30 and is waiting on the billing cycle, and annual plans are refunded pro rata.

## Use real data

Sync the public help center into the shared collection with a [connector](https://docs.hydradb.com/essentials/v2/connectors.md). Put only public content there: every customer's query reads that collection, and it is not filtered by `acl`. A customer's past tickets are private, so ingest each one into that customer's collection as a `ticket` [app source](https://docs.hydradb.com/essentials/v2/context.md#send-app-sources). Keep step 3 in your application, called when a conversation closes. If each of your customers is a company with its own private knowledge, give each company its own database. See [Databases and Collections](https://docs.hydradb.com/essentials/v2/databases-and-collections.md).

## Common mistakes

* **Saving customer memories or tickets in the shared collection:** every customer's query then reads every other customer's history.
* **Using `collection` with one name:** the query then misses either the help center or the customer. Use `collections` with both.
* **Querying a customer with no history:** every collection named in `collections` must exist, and a collection is created by its first write. For a new customer, query the help center alone.
* **Building the collection name from user input:** derive it from the session's customer ID on your server.

## Next steps

* [Context](https://docs.hydradb.com/essentials/v2/context.md#2-save-memories): `infer` and conversation input.
* [Databases and Collections](https://docs.hydradb.com/essentials/v2/databases-and-collections.md): scoping patterns for B2C and B2B.
* [Query](https://docs.hydradb.com/essentials/v2/query.md#7-use-the-results): combining knowledge and memories in a prompt.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.