Knowledge Graph Platform for Document Processing and AI-powered Q&A created by OFFIS e.V.
Features • Quick Start • Production • Architecture • Library Usage • Configuration • Development • Documentation
- Document Processing – Upload and process PDFs, Office files, images, audio, video, HTML, email, calendars, contacts, CSV, JSON, XML, YAML, TOML, and Excel files
- Adaptive PDF OCR Rendering – Automatically switches to high-resolution tiled rendering, with optional panel splitting for large technical drawings such as A1 and A0 sheets
- Knowledge Graph Extraction – Uses AI to extract entities, relationships, and supporting evidence from uploaded documents
- Graph Storage – Stores entities and relationships in a queryable knowledge graph backed by PostgreSQL and pgvector
- Vector Search – Supports semantic retrieval over extracted document content
- Graph Exploration Tools – Provides AI-assisted traversal, multi-hop path finding, and source-grounded exploration of graph data
- Authentication & Authorization – Uses Better Auth for sessions, roles, LDAP, and email/password sign-in
- User Management – Includes an admin UI for managing users, roles, and bans
- Chat Interface – Lets users ask questions about their documents with streaming responses in both normal and agentic modes
- Multi-Model Support – Works with OpenAI-compatible providers, OpenAI, Azure, Anthropic, and local inference backends where available
When using OpenAI reasoning models, temperature is fixed to 1.0, as required by the provider.
| Layer | Technology |
|---|---|
| Frontend | Next.js 16, React 19, TanStack Query, Tailwind CSS, Bun |
| Backend | Bun, Elysia, Drizzle ORM |
| Auth | Better Auth with admin roles, LDAP, and email/password |
| Database | PostgreSQL + pgvector |
| Workflow | OpenWorkflow with PostgreSQL-backed durable workflow storage |
| Storage | RustFS (S3-compatible) |
| AI | Vercel AI SDK with OpenAI, Azure, Anthropic, and compatible APIs |
# Clone the repository
git clone https://github.com/OFFIS-RIT/kiwi.git
cd kiwi
# Configure environment
cp .env.sample .env
# Edit .env with your AI credentials and local settings
# Start development infrastructure
docker compose up -d
# Start frontend, API, and worker
bun run devThe application will be available at:
- Frontend: http://localhost:3000
- API: http://localhost:4321
- Auth Routes: http://localhost:4321/auth
Database migrations are run manually in local development after the infrastructure is up.
The rustfs-setup container automatically creates the S3 bucket on first start.
If you want to use a local OpenAI-compatible model backend such as Ollama, point the AI settings in .env to that endpoint.
For example, with Ollama:
docker exec -it ollama ollama pull <model-name>docker compose -f compose.prod.yml up -d # Start production
docker compose -f compose.prod.yml down # Stop productioncompose.prod.yml also runs the migrations startup container automatically,
which applies pending migrations before app services connect to the database.
Production uses Caddy as the edge proxy. The checked-in compose.prod.yml
mounts ./caddy/Caddyfile, reads APP_DOMAIN, and does not require
certificate files in ./certs.
| Service | Description |
|---|---|
| caddy | Edge proxy with HTTPS |
| frontend | Next.js frontend |
| server | Bun API server with /auth |
| worker | Durable workflow worker |
| migrations | Startup migration job |
| postgres | PostgreSQL + pgvector |
| bouncer | PostgreSQL connection pool |
| rustfs | S3-compatible storage |
The frontend runs as a Next.js standalone server on Node 20. Browser-facing
configuration such as API_URL and AUTH_URL is read from runtime environment
variables, so the same built image can be deployed with different API/auth
endpoints without rebuilding. Authentication mode is derived server-side from
non-sensitive LDAP presence flags.
┌──────────────┐ ┌──────────────┐ ┌─────────────┐
│ Frontend │────▶│ Caddy │────▶│ Server │
│ (Next.js) │ │ (prod) │ │ (Bun) │
└──────────────┘ └──────────────┘ └─────────────┘
│ │
▼ ▼
┌──────────────┐ ┌─────────────┐
│ Auth │ │ PostgreSQL │
│ (/auth) │ │ + pgvector │
└──────────────┘ └─────────────┘
▲
│
┌──────────────┐
│ Worker │
│ (workflow) │
└──────────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ RustFS │ │ Ollama/OpenAI│
│ (S3) │ │ (AI/LLM) │
└──────────┘ └──────────────┘
- User uploads files → API stores the originals in RustFS
- API enqueues durable workflow runs in PostgreSQL within the request transaction
- Worker claims pending runs from workflow storage and processes files (PDF, Office files, images, audio, video, HTML, email, calendar/contact files, CSV, JSON, XML, YAML, TOML, Excel)
- Worker extracts entities/relations via AI and stores graph data in PostgreSQL with embeddings
- When all file workflows in a batch finish, description workflows are enqueued and the project returns to
ready - User queries via chat → vector search, graph traversal, or agentic tool exploration + AI response
| Mode | API | Description |
|---|---|---|
| Normal | mode=normal |
Vector similarity search with path finding between relevant entities |
| Agentic | mode=agentic |
Agentic exploration using graph tools for autonomous knowledge discovery |
| Tool | Capability |
|---|---|
list_files |
List files in the current graph and return file IDs for follow-up tool calls |
search_entities |
Find entities by name, alias, type, or short topical query |
list_entities |
Scan entities broadly, optionally scoped to specific files |
search_relationships |
Search relationships relevant to a query and return relationship IDs |
get_relationships |
Retrieve relationships for known entity IDs |
get_entity_neighbours |
Traverse directly connected entities from a selected entity |
get_path_between_entities |
Find multi-hop paths between two entities |
get_sources |
Retrieve source evidence and citation IDs for entities or relationships |
ask_clarifying_questions |
Ask the user for missing information when the graph and prior context are insufficient |
KIWI is organized as a Bun workspace monorepo. The main applications and shared packages are:
| Package | Purpose |
|---|---|
apps/frontend |
Next.js frontend for document upload, graph browsing, admin flows, and chat |
apps/api |
Elysia API server, auth bridge, route handlers, and OpenWorkflow integration |
apps/worker |
Background worker that executes durable workflows for file processing |
packages/ai |
Shared AI adapters, prompt helpers, chat message types, and tool wiring |
packages/auth |
Better Auth server and client setup, permissions, and auth helpers |
packages/db |
Drizzle schema, tables, and database access |
packages/files |
Shared RustFS and S3 file utilities |
packages/graph |
Graph extraction, chunking, loaders, and processing logic |
packages/logger |
Shared logging and OpenTelemetry helpers |
This structure keeps frontend, API, worker, and shared business logic aligned while still allowing each app to ship independently.
Copy .env.sample to .env and configure:
Environment Variables
| Variable | Description |
|---|---|
LOG_LEVEL |
API log level |
APP_DOMAIN |
Public domain used by Caddy for HTTPS and /s3 proxying |
AUTH_SECRET |
Secret key for authentication |
AUTH_URL |
Public auth base URL |
TRUSTED_ORIGINS |
Comma-separated origins allowed to call auth/API with credentials |
AUTH_CROSS_SUBDOMAIN_COOKIES |
Enable Better Auth cross-subdomain cookies |
AUTH_COOKIE_DOMAIN |
Optional cookie domain for shared auth sessions across subdomains |
WORKER_CONCURRENCY |
Maximum number of workflow runs processed in parallel by the worker |
API_URL |
Frontend API base URL (runtime; no rebuild needed) |
INTERNAL_AUTH_URL |
Server-to-server auth URL inside Docker (defaults to AUTH_URL) |
BUILD_LABEL |
Frontend build label, set via CI build arg |
APPLE_CLIENT_ID |
Apple OAuth client ID |
APPLE_CLIENT_SECRET |
Apple OAuth client secret |
APPLE_BUNDLE_ID |
Apple bundle identifier (optional) |
GOOGLE_CLIENT_ID |
Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret |
MICROSOFT_CLIENT_ID |
Microsoft OAuth client ID |
MICROSOFT_CLIENT_SECRET |
Microsoft OAuth client secret |
MICROSOFT_TENANT_ID |
Microsoft tenant ID (optional) |
MICROSOFT_AUTHORITY_URL |
Microsoft authority URL (optional) |
LDAP_URL |
LDAP server URL |
LDAP_BIND_DN |
LDAP bind DN |
LDAP_PASSW |
LDAP bind password |
LDAP_BASE_DN |
LDAP base DN |
LDAP_SEARCH_ATTR |
LDAP search attribute |
LDAP_*_CONFIGURED |
Non-sensitive frontend LDAP presence flags, derived in production compose |
MASTER_USER_ID |
Master user ID (string) |
MASTER_USER_NAME |
Optional display name for the bootstrapped master user |
MASTER_USER_EMAIL |
Optional email for the bootstrapped master user |
MASTER_USER_PASSWORD |
Optional password for bootstrapping a credential login for master user |
MASTER_USER_API_KEY |
Optional Better Auth API key bound to the master user |
DATABASE_URL |
Host PgBouncer PostgreSQL connection string |
DATABASE_DIRECT_URL |
Host direct PostgreSQL connection string |
S3_REGION |
S3 region |
S3_ENDPOINT |
Host RustFS endpoint |
TLS_EMAIL |
Optional ACME contact email used by Caddy |
S3_ACCESS_KEY_ID |
S3 access key |
S3_SECRET_ACCESS_KEY |
S3 secret key |
S3_BUCKET |
S3 bucket name |
AI_TEXT_CONCURRENCY |
Worker-local maximum concurrent text requests |
AI_IMAGE_CONCURRENCY |
Worker-local maximum concurrent image requests |
AI_EMBEDDING_CONCURRENCY |
Worker-local maximum concurrent embedding requests |
AI_AUDIO_CONCURRENCY |
Worker-local maximum concurrent audio requests |
AI_VIDEO_CONCURRENCY |
Worker-local maximum concurrent video requests |
OTEL_EXPORTER_OTLP_ENDPOINT |
Optional OTLP endpoint fallback for log export |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT |
Optional OTLP logs endpoint |
OTEL_EXPORTER_OTLP_HEADERS |
Optional OTLP export headers |
AI model adapters, provider model names, and provider credentials are managed
per organization through the /models API. Model credentials are encrypted with
AUTH_SECRET before being stored in PostgreSQL. Existing AI_* model
environment variables are still read during startup as a bootstrap source: when
an organization has no row for a model type, Kiwi seeds one from those legacy
variables and then uses the database-backed configuration.
LDAP is auto-enabled when all LDAP env vars are present (LDAP_URL,
LDAP_BIND_DN, LDAP_PASSW, LDAP_BASE_DN, LDAP_SEARCH_ATTR). When LDAP is
active, email/password sign-up and sign-in are disabled automatically.
The API and frontend both derive the mode with the shared @kiwi/auth helper.
The API derives from actual LDAP values and logs a warning for partial
configuration. The frontend derives from non-sensitive LDAP_*_CONFIGURED
presence flags so LDAP bind credentials do not need to be present in the
frontend container.
To share auth sessions across subdomains, configure the backend with:
TRUSTED_ORIGINSset to a comma-separated list of frontend origins, for examplehttps://app.example.com,https://admin.example.comAUTH_CROSS_SUBDOMAIN_COOKIES=trueAUTH_COOKIE_DOMAIN=.example.com(or the most specific shared domain you need)
Example:
AUTH_URL=https://auth.example.com/auth
TRUSTED_ORIGINS=https://app.example.com,https://admin.example.com
AUTH_CROSS_SUBDOMAIN_COOKIES=true
AUTH_COOKIE_DOMAIN=.example.comWhen you deploy behind Caddy on one host, keep the frontend on relative paths:
API_URL=/apiAUTH_URL=/auth
Example:
AUTH_URL=https://kiwi.example.com/auth
TRUSTED_ORIGINS=https://kiwi.example.com
API_URL=/apiFor local Bun development without Caddy, point the frontend directly at the API/auth server:
AUTH_URL=http://localhost:4321/auth
API_URL=http://localhost:4321OpenWorkflow uses DATABASE_DIRECT_URL instead of a separate workflow-specific connection variable.
Use DATABASE_URL for pooled application queries and DATABASE_DIRECT_URL for migrations and workflow storage.
The frontend uses next-intl with cookie-based locale selection via
NEXT_LOCALE. Supported locales are de and en. A valid NEXT_LOCALE
cookie takes precedence. If the cookie is missing or invalid, the frontend
selects the preferred supported locale from Accept-Language, including region
variants such as en-US; if no supported locale is requested, it falls back to
en.
For production, set these variables to the container-network endpoints used inside the Compose stack:
DATABASE_URL=postgresql://kiwi:kiwi@bouncer:5432/kiwi?sslmode=disable
DATABASE_DIRECT_URL=postgresql://kiwi:kiwi@postgres:5432/kiwi?sslmode=disable
S3_ENDPOINT=http://rustfs:9000docker compose up -d # Start PostgreSQL, PgBouncer, and RustFS
bun run dev # Start frontend, API, and worker
docker compose down # Stop infrastructureArchive uploads in the Docker/production API image include extraction tools automatically.
Local Bun API development that handles archive uploads requires bsdtar from
libarchive-tools, plus gzip, bzip2, xz, zstd, and brotli:
sudo apt-get install -y --no-install-recommends brotli bzip2 gzip libarchive-tools xz-utils zstdRun database migrations manually after the infrastructure is up and before
starting the app processes. When MASTER_USER_ID is configured, the API also
ensures the matching user exists as an admin with the configured profile fields.
If MASTER_USER_API_KEY is also configured, the API bootstraps that value as a
managed Better Auth API key for the master user.
| Service | Port | Description |
|---|---|---|
| frontend | 3000 | Next.js dev server |
| server | 4321 | Bun API server with /auth |
| worker | - | Durable workflow worker |
| postgres | 5433 | Direct PostgreSQL connection |
| bouncer | 5432 | PgBouncer pooled connection |
| rustfs | 9000, 9001 | S3-compatible storage |
The background worker in apps/worker executes durable workflow runs stored in PostgreSQL.
- API requests enqueue
process,delete, anddescriptionworkflow runs transactionally. - The worker polls pending runs, claims a lease, heartbeats while work is in progress, and retries failures with backoff.
- File indexing fans out to one
processworkflow per file. Once all file workflows in a correlation finish, description workflows are enqueued automatically. - Delete operations use
deleteworkflows and refresh affected descriptions before the project returns toready.
# Start the full local stack
bun run dev
# Start only the worker
bun --cwd apps/worker run dev| Document | Description |
|---|---|
| AGENTS.md | Repo technologies, commands, and style |
