Skip to content

Commit fa3851c

Browse files
committed
feat(agentic-server): make canonical package the OpenAI-compatible gateway
Consolidate the two divergent agentic-server implementations into one source of truth. The published agentic-server becomes the stateless OpenAI-compatible gateway (ported from constructive-db) with backend- agnostic metering via an injected InferenceSink — it no longer imports any concrete telemetry/billing backend. The legacy stateful threads router (createAgenticRouter, express-context auth) is relocated into graphql/server as server-internal code so the deployed /v1/threads and /v1/embed surface is preserved unchanged. - agentic-server: gateway router/providers/transforms/server + types; exports createAgenticServer, createRouter, InferenceSink/InferenceEntry - graphql/server: local src/agentic threads router; server mounts it - deps: drop agentic-server dep from graphql/server, add the threads router's deps (@agentic-kit/ollama, llm-env); gateway keeps only logger
1 parent 117303b commit fa3851c

15 files changed

Lines changed: 4277 additions & 7941 deletions

File tree

agentic/agentic-server/README.md

Lines changed: 41 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,27 @@
99
<img height="20" src="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml/badge.svg" />
1010
</a>
1111
<a href="https://github.com/constructive-io/constructive/blob/main/LICENSE"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
12-
<a href="https://www.npmjs.com/package/agentic-server"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/constructive?filename=packages%2Fagentic-server%2Fpackage.json"/></a>
12+
<a href="https://www.npmjs.com/package/agentic-server"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/constructive?filename=agentic%2Fagentic-server%2Fpackage.json"/></a>
1313
</p>
1414

15-
Standalone Express LLM serviceagent threads, chat streaming, billing metering, and inference logging via `@constructive-io/express-context`.
15+
Standalone, OpenAI-compatible LLM gatewaya stateless multi-provider proxy with pluggable inference metering.
1616

1717
## Overview
1818

19-
`agentic-server` is the Express-only equivalent of what `graphile-llm` does inside PostGraphile. It provides the same capabilities (agent threads, chat, embeddings, billing, inference logging) but as a standalone Express router that uses `@constructive-io/express-context` for tenant-scoped database access.
19+
`agentic-server` is a small Express app that accepts OpenAI-compatible requests, routes them to a configured LLM provider (OpenAI, Anthropic, or Ollama), normalizes the response back to the OpenAI shape, and records usage through an injected sink. It is **backend-agnostic**: the gateway never imports a concrete telemetry/billing implementation — callers inject an `InferenceSink`, so the same gateway can meter to `compute_log`, a billing service, or nothing at all.
2020

2121
## Usage
2222

2323
```typescript
24-
import express from 'express';
25-
import { createContextMiddleware } from '@constructive-io/express-context';
26-
import { createAgenticRouter } from 'agentic-server';
27-
28-
const app = express();
29-
30-
// Tenant context middleware (domain resolution, JWT, pgSettings, withPgClient)
31-
app.use(createContextMiddleware());
32-
33-
// Mount the agentic router
34-
app.use(createAgenticRouter());
24+
import { createAgenticServer } from 'agentic-server';
25+
26+
const app = createAgenticServer({
27+
providerType: 'ollama',
28+
providerBaseUrl: 'http://localhost:11434',
29+
defaultModel: 'llama3',
30+
// Optional: record usage. Fire-and-forget; implementations must never throw.
31+
inferenceSink: { logInference: (entry) => myBackend.record(entry) },
32+
});
3533

3634
app.listen(3001, () => {
3735
console.log('agentic-server running on :3001');
@@ -42,42 +40,37 @@ app.listen(3001, () => {
4240

4341
| Method | Path | Description |
4442
|--------|------|-------------|
45-
| POST | `/v1/threads` | Create a new conversation thread |
46-
| POST | `/v1/threads/:thread_id/messages` | Send messages + get AI response (streaming SSE) |
47-
| POST | `/v1/orgs/:entity_id/threads` | Create thread (entity-scoped) |
48-
| POST | `/v1/orgs/:entity_id/threads/:thread_id/messages` | Send message (entity-scoped) |
49-
| POST | `/v1/embed` | Generate embeddings |
50-
51-
## Environment Variables
52-
53-
| Variable | Default | Description |
54-
|----------|---------|-------------|
55-
| `CHAT_PROVIDER` | `ollama` | Chat LLM provider |
56-
| `CHAT_MODEL` | `llama3` | Default chat model |
57-
| `CHAT_BASE_URL` | `http://localhost:11434` | Chat provider URL |
58-
| `EMBEDDER_PROVIDER` | `ollama` | Embedding provider |
59-
| `EMBEDDER_MODEL` | `nomic-embed-text` | Default embedding model |
60-
| `EMBEDDER_BASE_URL` | `http://localhost:11434` | Embedding provider URL |
61-
62-
## Features
63-
64-
- **Thread management**: Create and manage conversation threads with system prompts
65-
- **Streaming chat**: SSE streaming responses (OpenAI-compatible format)
66-
- **Batch chat**: Non-streaming JSON responses
67-
- **Embeddings**: Generate vector embeddings via `/v1/embed`
68-
- **Billing integration**: Automatic quota checks and usage recording (when billing module is provisioned)
69-
- **Inference logging**: Automatic token usage logging (when inference_log module is provisioned)
70-
- **RLS enforcement**: All database operations run within tenant-scoped RLS transactions
43+
| POST | `/v1/chat/completions` | OpenAI-compatible chat completion (multi-provider) |
44+
| POST | `/v1/embeddings` | OpenAI-compatible embeddings (multi-provider) |
45+
| POST | `/v1/usage` | Submit an external usage report to the metering sink |
46+
| GET | `/v1/providers` | List configured providers |
47+
| GET | `/healthz` | Health check |
48+
49+
## Identity & tenancy
50+
51+
Requests carry tenant identity via headers: `X-Database-Id` (**required** — requests without it are rejected `400`), `X-Entity-Id`, and `X-Actor-Id`. Routing to a specific provider can be forced with `X-LLM-Provider`.
52+
53+
`isPublic` controls trust: when `false` (default) the server sits on a private network and trusts these headers directly; when `true` the identity headers are stripped from incoming requests (external clients cannot set tenant context), so such requests fail loud rather than being mis-attributed.
54+
55+
## Metering
56+
57+
Metering is injected, not built in:
58+
59+
```typescript
60+
interface InferenceSink {
61+
logInference(entry: InferenceEntry): void; // fire-and-forget; must not throw
62+
}
63+
```
64+
65+
The gateway calls `logInference` after each chat/embedding (and for `/v1/usage` reports). Consumers own the concrete implementation — e.g. `constructive-db` injects a `compute_log`-backed sink built on its `ModuleLoader`.
7166

7267
## Architecture
7368

7469
```
75-
Cloud Function → POST /v1/threads/:id/messages → agentic-server
76-
77-
┌───────────────────┼───────────────────┐
78-
79-
express-context OllamaAdapter Billing
80-
(tenant DB context) (LLM provider) (quota + usage)
70+
Client → POST /v1/chat/completions → agentic-server
71+
72+
┌──────────────────────────────────┐
73+
74+
resolveProvider LLM provider InferenceSink
75+
+ transforms (OpenAI/Ollama/…) (injected metering)
8176
```
82-
83-
The cloud function doesn't need to know about databases, billing, or LLM providers. It just POSTs `{ messages, model }` and gets back a streamed response.

0 commit comments

Comments
 (0)