sct-langchain
v0.1.0
Published
LangChain/LangGraph integration for SCT (Secure Compact Tokenization) — reversible, keyed PII pseudonymization (DSGVO/BaFin-grade) plus deterministic tool-output compression and a metered OpenAI-compatible chat model.
Maintainers
Readme
sct-langchain
Reversible, keyed PII pseudonymization for LangChain / LangGraph (v1) — DSGVO/BaFin-grade. PII is tokenized before the model sees it and the real values are restored in the answer via SCT (Secure Compact Tokenization).
Warum SCT statt der eingebauten PIIMiddleware?
LangChains eigene PIIMiddleware kennt nur redact / mask / hash — alle
einwegig. Die Antwort des Agenten enthält [REDACTED_email] statt des
echten Namens oder der IBAN. SCT ist ein echtes Round-Trip:
| | eingebaute PIIMiddleware | sct-langchain |
| --- | --- | --- |
| Vor dem Modell | redigiert / maskiert | tokenisiert (reversibel) |
| Nach dem Modell | keine Wiederherstellung | re-identifiziert die echten Werte |
| Schlüssel-Verwahrung | — | AES-256-GCM / FF3-1, tenant-isoliert |
| Erkennung | 5 US-Regex-Typen | DE/EU NER + Regex (Name, IBAN, Steuer-ID …) |
Installation
npm install sct-langchain
# peer deps
npm install langchain @langchain/core zod
# nur für ChatSCT (sct-langchain/chat) — optionaler Peer-Dep:
npm install @langchain/openaiMiddleware (Flagship)
import { createAgent } from 'langchain';
import { sctPseudonymizationMiddleware } from 'sct-langchain';
const agent = createAgent({
model: 'openai:gpt-4o',
middleware: [
sctPseudonymizationMiddleware({ apiKey: process.env.SCT_API_KEY! }),
],
});
// PII in the prompt never reaches the LLM; the answer comes back re-identified.
const res = await agent.invoke({
messages: [{ role: 'user', content: 'Mail an Max Mustermann: [email protected]' }],
});Optionen: applyToInput (default true), applyToOutput (default true),
applyToToolResults (default false), autoDetectPii (default true). Ein
vorgefertigter Client geht via client, sonst apiKey / baseUrl /
timeoutMs.
Design-Hinweis (verifiziert gegen [email protected])
Die Re-Identifikation läuft als node-style afterModel-Hook, nicht als
wrapModelCall. Grund: LangChain.js-Bug
#9418 bricht
responseFormat / Structured-Output mit jedem wrapModelCall-Middleware, und
wrapModelCall kann keine State-Updates zurückgeben. beforeModel +
afterModel sind die stabile, structured-output-sichere Naht. Der
Verschlüsselungs-Schlüssel wird pro Lauf einmal erzeugt und über das
Middleware-State (stateSchema) von beforeModel zu afterModel getragen.
Kompression: sctCompressionMiddleware
Der zweite Differenzierer — Token-Kosten. Werkzeug-Ausgaben (Log-Dumps,
Test-Reports, Such-Treffer) fließen oft ungefiltert zurück in den Kontext und
treiben die Kosten. sctCompressionMiddleware komprimiert sie im
wrapToolCall-Seam über SCTs /tokenizer/compress — bevor sie das Modell
wieder erreichen:
import { createAgent } from 'langchain';
import { sctCompressionMiddleware, sctPseudonymizationMiddleware } from 'sct-langchain';
const agent = createAgent({
model: 'openai:gpt-4o',
tools: [searchDocs],
middleware: [
// Erst pseudonymisieren, dann komprimieren — PII wird tokenisiert,
// bevor sie in die Kompression läuft.
sctPseudonymizationMiddleware({ apiKey: process.env.SCT_API_KEY! }),
sctCompressionMiddleware({ apiKey: process.env.SCT_API_KEY! }),
],
});Was LangChain sonst bietet — und warum SCT besser passt:
| | eingebaute Optionen | sctCompressionMiddleware |
| --- | --- | --- |
| Kürzen | blindes Truncation (schneidet Datensätze mitten durch) | format-bewusst (pytest, jest, eslint, git-diff …) |
| Zusammenfassen | Summarizer-LLM (extra Call, nicht-deterministisch) | deterministisch, kein extra LLM-Call |
| Garantie | keine | tiktoken-geprüft neverWorse — nie mehr Tokens als das Original |
| Finance/Legal/Health | riskant (LLM halluziniert Zusammenfassung) | reproduzierbar, auditierbar |
Optionen: format (Parser-Hinweis), model, verbosity
(compact | verbose | ultra), minChars (default 200 — überspringt
winzige Ausgaben), toolNames (Whitelist), onCompressed(result, toolName)
(z. B. um savings_pct als Billing-Envelope zu loggen). Non-ToolMessage- und
Nicht-Text-Ergebnisse laufen unverändert durch.
ChatSCT: gemetertes OpenAI-kompatibles Chat-Modell
ChatSCT ist ein dünner ChatOpenAI-Subclass, der auf SCTs
OpenAI-kompatibles api-gateway gepinnt ist — Tenant-/API-Key-Header und
Token-Metering inklusive. Import über den chat-Subpath (der optionale
Peer-Dep @langchain/openai wird nur hier benötigt):
import { ChatSCT } from 'sct-langchain/chat';
const model = new ChatSCT({
model: 'gpt-4o',
apiKey: process.env.SCT_API_KEY!, // sct_… Bearer-Token
tenantId: 'acme-gmbh', // -> X-Tenant-Id Header
});
const res = await model.invoke('Hallo!');Ein-Zeilen-Alternative ohne diesen Subpath: new ChatOpenAI({ apiKey,
configuration: { baseURL: 'https://sct.simosphereai.com/v1' } }). ChatSCT
bündelt Base-URL, Header und die SCTUsageCallback-Meterung, damit das nicht
pro Call-Site wiederholt wird.
Streaming-Hinweis: Eine eigene
base_urldeaktiviert OpenAIs gestreamte Token-Nutzung. Die angehängteSCTUsageCallbackliest das normalisierteusage_metadataaus der Antwort, sodass die Meterung in beiden Fällen korrekt bleibt.metering: falseschaltet sie ab.
Runnables (jede LCEL-Kette, nicht nur Agenten)
import { withSctPseudonymization } from 'sct-langchain';
// Beliebige string-in/string-out Runnable schützen:
const safe = withSctPseudonymization(prompt.pipe(llm), {
apiKey: process.env.SCT_API_KEY!,
});
const answer = await safe.invoke('Fasse zusammen: Max Mustermann schuldet 500 EUR');Für manuelle Komposition (Fork des Schlüssels um den Modell-Aufruf herum):
import { sctPseudonymize, sctReidentify } from 'sct-langchain';
const pseud = sctPseudonymize({ apiKey: KEY });
const reident = sctReidentify({ apiKey: KEY });
const { text, encryptionKey } = await pseud.invoke('Max Mustermann, [email protected]');
const modelOut = await llm.invoke(text); // sieht nur Tokens
const restored = await reident.invoke({ text: String(modelOut.content), encryptionKey });DSGVO-Nuance
Beide Middleware adressieren getrennte DSGVO-Hebel. sctPseudonymizationMiddleware
setzt Datenminimierung / Pseudonymisierung (Art. 5, Art. 32 DSGVO) durch: der
LLM-Prozessor sieht nie Klartext-PII, die Schlüsselverwahrung bleibt
tenant-isoliert und auditierbar server-seitig — anders als redigierende Guards
verliert der Nutzer aber keine Antwortqualität, weil re-identifiziert wird.
sctCompressionMiddleware reduziert die an den Prozessor übermittelte Datenmenge
zusätzlich, ohne Inhalte an ein zweites LLM (Summarizer) weiterzugeben — relevant
für die Vermeidung zusätzlicher Auftragsverarbeiter.
Exports
Root (sct-langchain):
sctPseudonymizationMiddleware(options)/ AliasSCTPseudonymizationMiddlewaresctCompressionMiddleware(options)/ AliasSCTCompressionMiddlewarewithSctPseudonymization(inner, options)sctPseudonymize(options)·sctReidentify(options)SCTUsageCallback(Token-Metering-Callback)SCTClient·resolveSctClient(source)
Subpath (sct-langchain/chat, benötigt @langchain/openai):
ChatSCT(gemetertes, gateway-gepinntesChatOpenAI)
Entwicklung
npm install
npm run build # tsup -> dist/ (ESM + d.ts)
npm test # vitest (gemockter SCT-Client, Golden-Round-Trip)
npm run typecheckMIT © SIMO GmbH
