@datar-platform/knowledge-kit
v0.1.0-alpha.3
Published
Drop-in Documents, Knowledge Base and Ask surfaces for the Datar platform — upload, indexing, cited answers, React hooks and Next.js route handlers on top of @datar-platform/sdk.
Maintainers
Readme
@datar-platform/knowledge-kit
Documents, Knowledge Base and Ask, on top of @datar-platform/sdk.
Upload a file, watch it index, and answer questions from it with citations that point at the exact indexed passage. The SDK gives you the calls; this gives you the parts every front end would otherwise rewrite: upload-and-wait, status that tells the truth, answer rendering with clickable citations, source cards, a reasoning trace, React hooks and Next.js route handlers.
Ships behaviour, not design. There is no CSS here and no component library.
pnpm add @datar-platform/knowledge-kitSetup
Two environment variables, server-side only:
DATAR_URL=https://develop-af.datar.co.za # gateway origin
DATAR_TOKEN=dt_... # org-scoped API tokenDATAR_DEV_SESSION_TOKEN is accepted as a fallback name.
The credential
Use an organisation-scoped API token, not a personal session — a session
expires in hours, a token does not. Mint one with v2.auth.apiToken.create,
passing the organisation and the three scopes this kit needs:
{
"name": "my-app service token",
"organizationId": "<your org id>",
"expiresInDays": 90,
"scopes": ["api.ai.kb.read", "api.ai.kb.ingest", "api.ai.invoke"]
}Scopes are enforced: a token without api.ai.kb.ingest can read and ask but
cannot upload or re-index, which is the honest way to give a front end read-only
access. A token with no scopes is refused everywhere.
Next.js in five files
// lib/knowledge.ts
import { createKnowledgeClientFromEnv } from "@datar-platform/knowledge-kit";
export const knowledge = createKnowledgeClientFromEnv();// app/api/documents/route.ts
import { createDocumentsRoute } from "@datar-platform/knowledge-kit/next";
import { knowledge } from "~/lib/knowledge";
export const { GET, POST, dynamic } = createDocumentsRoute(knowledge);// app/api/documents/[fileId]/index/route.ts → createDocumentIndexRoute
// app/api/documents/[fileId]/chunks/route.ts → createDocumentChunksRoute
// app/api/ask/route.ts → createAskRoute (also export maxDuration)
// app/api/threads/route.ts → createThreadsRoute
// app/api/threads/[sessionId]/route.ts → createThreadRouteThen in a client component:
"use client";
import { useAsk, useDocuments } from "@datar-platform/knowledge-kit/react";
export function Hub() {
const { documents, upload, uploading } = useDocuments();
const { messages, ask, busy } = useAsk();
// documents[n].kbStatus is "complete" once it is citable;
// messages carry rendered html, named sources and the trace.
}Every route answers { ok: true, data } or { ok: false, error: { message, code } },
and the hooks throw KnowledgeFetchError carrying that message and code — so an
expired credential is distinguishable from a failed upload without parsing strings.
Without a framework
import { createKnowledgeClient } from "@datar-platform/knowledge-kit";
const knowledge = createKnowledgeClient({ token, baseUrl });
const { fileId } = await knowledge.uploadDocument({ fileName, mimeType, body });
await knowledge.waitForIndexed(fileId);
const answer = await knowledge.ask({ question: "What is the tenant concentration?" });
answer.html; // escaped HTML, [n] rendered as <sup class="cite" data-cite="n">
answer.sources; // one card per citation, named after its document
answer.reasoning; // trace built only from values the platform returnedRender citations by listening for clicks on [data-cite] and showing
answer.sources[n - 1].
Entry points
| Import | Contents | Runs |
| --- | --- | --- |
| @datar-platform/knowledge-kit | createKnowledgeClient, documents, ask, threads | Server only — holds the credential |
| .../format | answerToHtml, citationsToSources, buildTrace, labels | Anywhere; pure, no network |
| .../react | useDocuments, useAsk, useThreads, kitFetch | Browser |
| .../next | The six route-handler factories | Server |
Safety
Model output is escaped before it is turned into HTML, and a [n] marker
becomes a citation badge only when citation n exists. An answer cannot inject
markup or fabricate a source number.
Platform notes
No workarounds: every call goes through a real SDK method, and the behaviours that used to need patching around are fixed at their source.
- Index status completes server-side — the gateway reconciles a pending job
before it answers a listing (#274).
listDocuments({ reconcile: true })additionally asks for reconciliation, and the polling paths use it so the kit stays correct against an older gateway. - Citations carry their own file name and type
(#278). If a
citation arrives unnamed — an older gateway —
askfetches the document list once to label it, and otherwise never makes that call. syncStatusandgetFileChunksare SDK methods as of0.3.0-alpha.9(#277).
Still open, and worth knowing before you design around them:
| Gap | Issue | Effect |
| --- | --- | --- |
| ~100-token chunks split structured records | #283 | An attribute can be attributed to a neighbouring record in a long table or register. The most important one to follow. |
| query takes no answer instructions | #280 | Every product inherits the same generic answer voice. |
| No streaming here | — | queryStream needs AppSync. Expect 15 to 30 seconds per answer and design the waiting state for it. |
Converting an existing front end
RUNBOOK.md walks through it: mint a credential, mount the routes, swap mock arrays for the hooks, and the end-to-end checks worth doing by hand the first time.
Tests
pnpm -F @datar-platform/knowledge-kit test # unit + contract, no network
DATAR_E2E=1 pnpm -F @datar-platform/knowledge-kit test:e2eThe contract tests pin every call to its exact tRPC path and verb, so drift between this kit and the router fails here rather than in a running app. The live suite uploads a fixture, waits for indexing, asks a question and asserts that each citation quotes text actually stored in the document — then asks something the document cannot answer and checks it refuses.
Follow-up: wiring these into .github/workflows/ci.yml (alongside the
existing pnpm -F @datar-platform/sdk test step) needs a token with the
workflow scope, so it has not been done here.
