AI Connect/How to create/Ein Multi-Tenant-KI-SaaS
Ein Multi-Tenant-KI-SaaS
Liefern Sie KI mit Connectoren an jeden Kunden Ihrer B2B-Plattform: begrenzen Sie jede external_id auf Tenant und Benutzer, halten Sie jede Organisation mit einem einzigen Application-Key isoliert und fügen Sie Steuerung pro Tenant hinzu, ohne jemals ein Credential über die Grenze preiszugeben. Ihre Kunden bekommen eine Konnektivitätsplattform; das Plumbing überlassen Sie ihnen nie.
Liefern Sie das KI-SaaS aus, auf das Ihre Kategorie gewartet hat: Jeder Kunde (Tenant) erhält ein Team von Benutzern, jeder Benutzer erhält seine KI, die auf sein GitHub, Jira und Slack zugreift, jeder Tenant isoliert auf Ihrem einen Application Key, und jeder Benutzer jedes Tenants gestützt auf Tausende KI-Verbindungen ab Tag eins. Keine von Ihnen gebaute Integration, kein von Ihnen gespeicherter Token, keine Identität, die Ihre Datenbank verlässt.
Jede Integrationsplattform auf dem Markt wird Ihnen sagen, dass dieses Problem in einem Vertrag pro Tenant, einer Rechnung pro Tenant oder Monaten Ihrer Ingenieure endet, die die Isolation von Hand bauen. Die Antwort des AI Connect SDK ist die, die kein Wettbewerber kopieren kann, ohne sein Produkt neu zu architecten: Liefern Sie KI-Funktionen, keine Integrationsprojekte. Dieser Leitfaden zeigt Ihnen, wie ein ganzer Marktplatz aus Tenants sich einen einzigen Application-Key teilt, ohne jemals eine Grenze zu überschreiten.
Hier ist der „Benutzer" eine Person in Ihrem Kundenunternehmen, daher muss die external_id sowohl den Tenant als auch den Menschen tragen. Diese einzelne Entscheidung ist das Isolationsmodell. Wenn es funktioniert, tut Ihr Produkt das, wofür einer Plattformfirma normalerweise Jahre und ein Sicherheitsteam brauchen, um es zu versprechen: jede Organisation ist eine Insel, jeder Benutzer ist Bürger von genau einer Insel, und Sie verwalten das ganze Archipel von einem einzigen Schlüssel aus. Vinkius trifft Ihre echten Nutzer niemals. Ihre E-Mails, ihre Namen, ihre Profile verlassen nie Ihre Datenbank; die Plattform sieht nur die opake Id, die Ihr Backend ihr übergibt.
Der Isolationsvertrag
- Eine Application = Ihr Produkt. Alle Tenants teilen sich in der Regel Ihre einzelne
appId. - Die
external_idkodiert die Adresse.cus_<tenant>_u_<user>ist die Grenze; Fähigkeiten werden nur innerhalb davon aufgelöst. - Tenant-Überschreitung ist ein 404, kein 500. Eine offengelegte oder fehlgeleitete id kann die Verbindung eines anderen Tenants nicht lesen, Vinkius lehnt sie außerhalb des Scopes ab.
- Ihre Benutzer bleiben unter Ihrer Kontrolle. Weder E-Mail noch Profil erreichen Vinkius; Sie übergeben nur eine opake id. Ihre Kundenbeziehung bleibt unter Ihrer Kontrolle.
Die Isolation wird aus einer wohlgeformten external_id abgeleitet, behandeln Sie sie daher als sicherheitskritische Eingabe. Erstellen Sie sie immer aus authentifizierten Tenant- und Benutzer-Claims, niemals aus Rohdaten der Anfrage, und lassen Sie niemals zu, dass ein Tenant die id eines anderen liefert. Für das detaillierte Garantienmodell siehe Authentifizierung und Scope und Sicherheit.
1. Einen Benutzer innerhalb eines Tenants adressieren
Komponieren Sie eine deterministische, URL-sichere id aus den beiden ids, denen Ihre Authentifizierung bereits vertraut.
interface AuthedPrincipal { tenantId: string; userId: string } // aus Ihrem JWT/Sitzung
const externalIdFor = (p: AuthedPrincipal) =>
`cus_${p.tenantId}_u_${p.userId}`; // "cus_acme_u_9f2c"2. Ein gemeinsamer Client
// server/vinkius.ts
import { Vinkius } from '@vinkius/connect';
export const vinkius = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!,
maxRetries: 2,
});3. Den Akteur aus einer verifizierten Session auflösen
Leiten Sie den Principal aus Ihrem Token ab und übergeben Sie ihn an das SDK. Alles Nachgelagerte ist ebenfalls scoped, weil externalIdFor es ist.
import { vinkius } from './vinkius';
export async function actorFor(req: Request) {
const claims = await verifySession(req); // Ihre Authentifizierung/Autorisierung
if (!claims) throw new Error('unauthenticated');
return vinkius.user(externalIdFor(claims));
}4. Jeden Benutzer seine eigenen Tools verbinden lassen
Zwei Personen in zwei Unternehmen verbinden die gleiche GitHub-Integration und erhalten zwei völlig getrennte Verbindungen, Credentials und Fähigkeiten, automatisch.
// POST /connect { connector, values }
async function connectForUser(claims: AuthedPrincipal, connector: string, values: Record<string, string>) {
const handle = vinkius.user(externalIdFor(claims)).connector(connector);
await handle.connect();
await handle.credentials.set(values);
return handle.status();
}5. Tenant-spezifische Richtlinien auf einem gemeinsamen Key
In der Regel werden Sie unterschiedliche Connectoren pro Plan oder pro Tenant anbieten müssen. Weil der Akteur über einen Namespace verfügt, fügt sich die Konfiguration auf Tenant-Ebene ohne ein spezielles SDK-Konzept in die Verbindung auf Benutzer-Ebene ein: Halten Sie die Allowlist pro Tenant in Ihrer Datenbank und übergeben Sie sie als include.
// server/policy.ts
export async function allowedConnectors(tenantId: string): Promise<string[]> {
// z. B. erhalten Enterprise-Tenants 'salesforce' und 'snowflake'
return await billing.planAllows(tenantId);
}
async function capabilitiesFor(claims: AuthedPrincipal) {
const allowed = await allowedConnectors(claims.tenantId);
return vinkius
.user(externalIdFor(claims))
.capabilities({ include: allowed }); // beschränkt den Fan-out auf die erlaubten Connectoren
}include beschneidet den Fan-out vor jedem Runtime-Aufruf: Ein Connector, den der Plan des Tenants verbietet, wird nie abgefragt, sodass der Benutzer ihn schlicht niemals sieht, selbst wenn diese Verbindung existiert. Richtlinie und Konnektivität fügen sich sauber zusammen.
6. Der passende Adapter für heterogene Modell-Runtimes
Unterschiedliche Tenants (oder unterschiedliche Funktionen) können auf unterschiedlichen Modellen laufen. Da CapabilitySet framework-agnostisch ist, bedient ein einziger Code-Pfad sie alle: Konvertieren Sie in der letzten Zeile.
import { toOpenAITools } from '@vinkius/connect/openai';
import { toAnthropicTools } from '@vinkius/connect/anthropic';
import { toGeminiTools } from '@vinkius/connect/gemini';
const capabilities = await capabilitiesFor(claims);
const toolSpec =
model === 'openai' ? toOpenAITools(capabilities)
: model === 'anthropic' ? toAnthropicTools(capabilities)
: model === 'gemini' ? toGeminiTools(capabilities)
: capabilities; // behalten Sie die Rohmenge zur AusführungBehalten Sie das ursprüngliche capabilities für die Ausführung, nur die konvertierten Definitionen werden an das Modell übergeben. Die Dispatch-Helfer des Adapters routen das gewählte Tool zurück auf die richtige Benutzer-Verbindung.
7. Enterprise-Tenants, die eine eigene Anwendung verlangen
Manche Enterprise-Kunden verlangen einen dedizierten Tenant-Key, statt Ihren zu teilen. Das ist schlicht eine weitere Vinkius-Instanz, pro Anfrage ausgewählt, Ihre externalIdFor-Logik ändert sich nicht.
import { Vinkius } from '@vinkius/connect';
const shared = new Vinkius({
appId: process.env.VINKIUS_APP_ID!,
apiKey: process.env.VINKIUS_APP_KEY!,
});
const dedicated = new Map<string, Vinkius>(); // tenantId -> eigene App
function clientFor(tenantId: string): Vinkius {
return dedicated.get(tenantId) ?? shared;
}8. Den Fall „falscher Tenant" explizit behandeln
Verteidigung in die Tiefe: Verweist eine Anfrage auf eine id, die Sie nicht autorisieren können, behandeln Sie einen NotFoundError als Scope-Fehler, nicht als generischen 404.
import { NotFoundError, AuthError } from '@vinkius/connect';
try {
await capabilitiesFor(claims);
} catch (error) {
if (error instanceof AuthError) return respond(401);
if (error instanceof NotFoundError) return respond(403, 'out of scope'); // tenantübergreifend
throw error;
}Zwei Garantien, denen jeder Tenant vertraut
Ein offengelegter Key überschreitet keine Grenze
Ihre Plattform hält ein einziges vk_app_sk_*-Secret. Dessen Auswirkungsradius ist konstruktionsbedingt begrenzt: Ein offengelegter Key kompromittiert eine Anwendung, und jeder Versuch, auf eine Ressource außerhalb dieser Anwendung zuzugreifen, liefert 404, niemals Daten eines anderen Tenants, niemals ein 500, das die Existenz einer Ressource bestätigt. Eine fehlgeleitete external_id schlägt daher sicher fehl, genau das, was ein Enterprise-Sicherheitsreview erwartet.
Ein Namespace-Name, viele Modellregeln
Der Standard-Namespace connector__name ist nicht für jeden Runtime gültig, auf den Sie einen Tenant routen könnten. toGeminiTools lehnt Bindestriche sofort ab (ein Connector-Slug wie google-calendar verletzt die ^[a-zA-Z_][a-zA-Z0-9_]*$-Regel von Gemini), und toOpenAITools begrenzt Namen auf 64 Zeichen, beide werfen einen ConfigError zur Konvertierungszeit, nicht eine 400 des Providers zur Inferenzzeit. Normalisieren Sie einmal, damit ein Tenant auf Gemini genauso sauber läuft wie einer auf OpenAI:
new Vinkius({
appId,
apiKey,
// nur Unterstriche, längenbegrenzt: gültig für OpenAI, Anthropic und Gemini
namespaceCapability: (connector, name) =>
`${connector}_${name}`.replace(/[^a-zA-Z0-9_]/g, '_').slice(0, 64),
});Produktions-Checkliste
- [ ] Erstellen Sie die
external_idimmer aus authentifizierten Tenant- und Benutzer-Claims, niemals aus Rohdaten. - [ ] Behalten Sie einen Application-Key für die Plattform; fügen Sie dedizierte
Vinkius-Instanzen nur für Tenants hinzu, die sie verlangen. - [ ] Erzwingen Sie Tenant-Pläne mit
capabilities({ include }), gestützt auf Ihre Billing-Tabelle. - [ ] Verlassen Sie sich auf 404-als-außerhalb-des-Scopes: versuchen Sie niemals, Ressourcen eines anderen Tenants zu rekonstruieren.
- [ ] Konvertieren Sie mit Adaptern beim Modellauf; führen Sie auf dem behaltenen
CapabilitySetaus. - [ ] Verwenden Sie pro Operation einen
idempotencyKey, damit Tenant-Wiederholungen sich nie kreuzen oder duplizieren.
Sie betreiben nun eine einzige KI-Plattform, die jeden Kunden und jeden Benutzer in seinem Inneren bedient, jeder auf isolierten Verbindungen und Credentials, mit Ihrem eigenen Application-Key und einer Policy-Schicht pro Tenant, und die Identitäten Ihrer Kunden verlassen niemals Ihre Kontrolle. Wettbewerber müssen diese Fähigkeit kaufen oder jahrelang selbst bauen. Sie haben sie an dem Tag erhalten, an dem Sie Ihre Application erstellt haben, und dieser Vorsprung wächst mit jedem Tenant, den Sie gewinnen.
What you just got
Not a pitch: the properties this build inherits automatically.
Connections and capabilities resolve only inside one external_id. No cross-actor leakage is possible, and you wrote none of that enforcement.
Your server stores secrets and can read back which fields are configured, never the values. Not your code, the model, or a dashboard can exfiltrate them.
Every connection owns a vk_live_* token, so cost and revocation are per connection. One call to disconnect() is a complete, auditable stop.
One CapabilitySet converts to OpenAI, Anthropic, Gemini, Vercel AI SDK, LangChain, LlamaIndex, Workers AI or neutral JSON Schema. Only the last line changes.
idempotencyKey, timeoutMs and AbortSignal per call; automatic full-jitter retries on transient failures; typed VinkiusError branches. No bespoke harness.
One app key, every customer isolated by address; a cross-tenant attempt is a 404. A customer can even get their own Vinkius instance, same code.
Give it to your AI agent
An Agent Skill (SKILL.md) for this build. Preview the first lines below, then copy or download it into your repo under .claude/skills/: Claude Code, Cursor or any Agent-Skills-compatible agent follows it to implement this pattern correctly.
