Retrieval

Filters, scoping, streaming and handling citations well.

The simplest useful call

curl https://api.uni-flow.ai/api/v1/memory/search \
  -H "x-api-key: $CHORDIAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"query": "What is blocking the Acme deal?"}'

Ask questions, not keywords

The query is classified before retrieval, and the classification determines how much weight goes to vectors, the graph and temporal recall. A question carries that signal; a bag of keywords does not.

BetterWorse
"What is blocking the Acme deal?""acme blocker"
"Who at Acme has decision authority?""acme contacts"
"What changed on the Nexio contract last month?""nexio contract"

Filters

querystringrequired

The question.

memory_idstring

Defaults to the active workspace's primary memory.

vault_idstring

Restrict to a department.

space_idstring

Restrict to a space.

sourcesstring[]

Restrict to specific connected sources.

top_ninteger

Candidates to consider before reranking. Default 10.

Discover valid sources values from GET/api/v1/memory/sources — it returns only sources that actually have indexed content, so a filter built from it cannot produce an empty result by accident.

Streaming

Use it for anything a person watches.

event: sources     data: { "citations": [...] }
event: token       data: { "text": "The Acme" }
event: token       data: { "text": " expansion deal" }
event: done        data: { "answer": "...", "suggested_followups": [...] }

Sources arrive first — guaranteed, not incidental. Render them immediately: the reader sees the grounding while the answer is still being written, which both lowers perceived latency and makes an ungrounded claim obvious.

const res = await fetch(url, { method: 'POST', headers, body });
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();

let buffer = '';
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += value;

  const frames = buffer.split('\n\n');
  buffer = frames.pop() ?? '';

  for (const frame of frames) {
    const event = /event: (.+)/.exec(frame)?.[1];
    const data = /data: (.+)/.exec(frame)?.[1];
    if (!event || !data) continue;

    if (event === 'sources') renderCitations(JSON.parse(data).citations);
    if (event === 'token') appendToken(JSON.parse(data).text);
  }
}

Citations

{
  "answer": "Blocked on BAA approval and EU hosting confirmation [1]. Deal value is EUR 240k [2].",
  "citations": [
    { "index": 1, "title": "Acme — Security Review notes", "source": "notion", "document_id": "ing_9f2c" },
    { "index": 2, "title": "Acme — proposal", "source": "gdrive", "document_id": "ing_7a1b" }
  ]
}

Render [n] as a link to citations[n-1]. Two things to get right:

Show the source system. "From Notion" and "from an email" carry different weight to a reader deciding whether to trust a claim.

Treat an unmarked claim as unsupported. If the model asserts something with no citation, the retrieval did not support it. Surfacing that distinction is what makes the citations useful rather than decorative.

Caching

Repeat queries are served from cache. New content in the same scope invalidates it automatically, so re-asking after an ingest returns fresh results.

You do not need to bust the cache manually, and you should not try — the corpus version counter already handles the case that matters.

When answers are wrong

Do not delete the document. Correct it:

{
  "learn_type": "correction",
  "trigger": { "source_document_id": "ing_9f2c" },
  "learning": { "correction": "The Acme deal closed on 14 September." }
}

The correction is stored and retrievable, and the stale source is deprecated. Deleting removes the error and teaches nothing.

Was this page helpful?
Report an issue

On this page