- Match field names: id, status, confidence, model, task_type, latency_ms, tokens, output - Default URL now https://llm-gateway.context-x.org (public endpoint) - ShieldX client uses 'shieldx' caller (not 'internal') - tokens.in/tokens.out instead of token_count.input/output
255 lines
7.8 KiB
TypeScript
255 lines
7.8 KiB
TypeScript
/**
|
||
* @llm-gateway/client
|
||
*
|
||
* TypeScript client library for the LLM Gateway.
|
||
* Used by all Context X projects: TIP, EO Global Pulse, SwitchBlade,
|
||
* PeerCortex, NOGnet, ShieldX, and CtxEvent.
|
||
*
|
||
* Usage:
|
||
* import { LLMGatewayClient, createTIPClient } from '@llm-gateway/client';
|
||
* const client = createTIPClient();
|
||
* const result = await client.completion({ task_type: 'summarize', input: '...' });
|
||
*/
|
||
|
||
// ============================================================
|
||
// Request / Response types
|
||
// ============================================================
|
||
|
||
export interface CompletionRequest {
|
||
/** Identifies which project/service is calling (e.g. 'tip-scraper', 'eo-global-pulse') */
|
||
caller: string;
|
||
/** Task type that maps to a prompt template (e.g. 'summarize', 'classify', 'translate') */
|
||
task_type: string;
|
||
/** The raw input text to process */
|
||
input: string;
|
||
/** Preferred output language */
|
||
language?: 'de' | 'en';
|
||
/** Additional context passed to the prompt template */
|
||
context?: Record<string, unknown>;
|
||
/** Per-request model / behavior overrides */
|
||
options?: {
|
||
/** Override the model (e.g. 'qwen2.5:32b'). Gateway picks a sensible default. */
|
||
model?: string;
|
||
/** Sampling temperature 0–1 */
|
||
temperature?: number;
|
||
/** Max output tokens */
|
||
max_tokens?: number;
|
||
/** Include full validation details in the response */
|
||
return_validation_details?: boolean;
|
||
};
|
||
}
|
||
|
||
export interface CompletionResponse {
|
||
/** Request ID for tracing */
|
||
id: string;
|
||
/** Overall status of the response */
|
||
status: 'approved' | 'warning' | 'pending_review' | 'rejected';
|
||
/** Model confidence score 0–10 */
|
||
confidence: number;
|
||
/** Ollama model that produced the output */
|
||
model: string;
|
||
/** Task type that was processed */
|
||
task_type: string;
|
||
/** End-to-end latency in milliseconds */
|
||
latency_ms: number;
|
||
/** Token usage */
|
||
tokens: { in: number; out: number };
|
||
/** The LLM output text */
|
||
output: string;
|
||
/** Validation details (present when return_validation_details=true) */
|
||
validation?: {
|
||
passed: boolean;
|
||
score: number;
|
||
validators: Record<string, unknown>;
|
||
};
|
||
}
|
||
|
||
export interface ClassifyResponse {
|
||
task_type: string;
|
||
content_type: string;
|
||
language: string;
|
||
complexity: 'low' | 'medium' | 'high';
|
||
requires_facts: boolean;
|
||
suggested_task_types: string[];
|
||
}
|
||
|
||
export interface BatchResponse {
|
||
batch_id: string;
|
||
}
|
||
|
||
export interface HealthResponse {
|
||
status: 'ok' | 'degraded' | 'down';
|
||
ollama: unknown;
|
||
queue: unknown;
|
||
}
|
||
|
||
// ============================================================
|
||
// Gateway client
|
||
// ============================================================
|
||
|
||
export class LLMGatewayClient {
|
||
private readonly baseUrl: string;
|
||
private readonly caller: string;
|
||
private readonly timeout: number;
|
||
|
||
constructor(config: {
|
||
baseUrl?: string;
|
||
caller: string;
|
||
/** Request timeout in ms (default: 30 000) */
|
||
timeout?: number;
|
||
}) {
|
||
this.baseUrl = config.baseUrl
|
||
?? process.env['LLM_GATEWAY_URL']
|
||
?? 'https://llm-gateway.context-x.org';
|
||
this.caller = config.caller;
|
||
this.timeout = config.timeout ?? 30_000;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Core: completion
|
||
// ----------------------------------------------------------
|
||
|
||
async completion(
|
||
params: Omit<CompletionRequest, 'caller'>,
|
||
): Promise<CompletionResponse> {
|
||
const body: CompletionRequest = { ...params, caller: this.caller };
|
||
return this.post<CompletionResponse>('/v1/completion', body);
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Classify input before routing
|
||
// ----------------------------------------------------------
|
||
|
||
async classify(input: string): Promise<ClassifyResponse> {
|
||
return this.post<ClassifyResponse>('/v1/classify', {
|
||
caller: this.caller,
|
||
input,
|
||
});
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Batch: submit multiple tasks, results delivered via webhook
|
||
// ----------------------------------------------------------
|
||
|
||
async batch(
|
||
tasks: Array<Omit<CompletionRequest, 'caller'>>,
|
||
webhookUrl: string,
|
||
): Promise<BatchResponse> {
|
||
return this.post<BatchResponse>('/v1/batch', {
|
||
caller: this.caller,
|
||
tasks,
|
||
webhook_url: webhookUrl,
|
||
});
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Health
|
||
// ----------------------------------------------------------
|
||
|
||
async health(): Promise<HealthResponse> {
|
||
const res = await this.fetchWithTimeout(`${this.baseUrl}/health`);
|
||
if (!res.ok) {
|
||
throw new Error(`Health check failed: ${res.status}`);
|
||
}
|
||
return res.json() as Promise<HealthResponse>;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Graceful degradation — returns null when gateway is unavailable
|
||
// ----------------------------------------------------------
|
||
|
||
async safeCompletion(
|
||
params: Omit<CompletionRequest, 'caller'>,
|
||
): Promise<CompletionResponse | null> {
|
||
try {
|
||
return await this.completion(params);
|
||
} catch {
|
||
// Gateway is down or timed out — caller handles degraded mode
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Internal helpers
|
||
// ----------------------------------------------------------
|
||
|
||
private async post<T>(path: string, body: unknown): Promise<T> {
|
||
const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body),
|
||
});
|
||
|
||
if (!res.ok) {
|
||
const text = await res.text().catch(() => '');
|
||
throw new Error(`Gateway error ${res.status} on ${path}: ${text}`);
|
||
}
|
||
|
||
return res.json() as Promise<T>;
|
||
}
|
||
|
||
private fetchWithTimeout(url: string, init?: RequestInit): Promise<Response> {
|
||
const controller = new AbortController();
|
||
const timer = setTimeout(() => controller.abort(), this.timeout);
|
||
|
||
return fetch(url, { ...init, signal: controller.signal }).finally(() =>
|
||
clearTimeout(timer),
|
||
);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// Project-specific pre-configured factory functions
|
||
// ============================================================
|
||
|
||
/**
|
||
* TIP (Transceiver Intelligence Platform)
|
||
* Long timeout because scraping + AI analysis can take time.
|
||
*/
|
||
export function createTIPClient(baseUrl?: string): LLMGatewayClient {
|
||
return new LLMGatewayClient({ caller: 'tip-scraper', baseUrl, timeout: 60_000 });
|
||
}
|
||
|
||
/**
|
||
* EO Global Pulse — team collaboration & CRM intelligence
|
||
*/
|
||
export function createEOPulseClient(baseUrl?: string): LLMGatewayClient {
|
||
return new LLMGatewayClient({ caller: 'eo-global-pulse', baseUrl, timeout: 30_000 });
|
||
}
|
||
|
||
/**
|
||
* SwitchBlade — infrastructure management platform
|
||
*/
|
||
export function createSwitchBladeClient(baseUrl?: string): LLMGatewayClient {
|
||
return new LLMGatewayClient({ caller: 'switchblade', baseUrl, timeout: 15_000 });
|
||
}
|
||
|
||
/**
|
||
* PeerCortex — BGP/RPKI network intelligence
|
||
* Short timeout: results must be near-real-time for network monitoring.
|
||
*/
|
||
export function createPeerCortexClient(baseUrl?: string): LLMGatewayClient {
|
||
return new LLMGatewayClient({ caller: 'peercortex', baseUrl, timeout: 8_000 });
|
||
}
|
||
|
||
/**
|
||
* NOGnet — NOG Support Program & event management
|
||
*/
|
||
export function createNOGnetClient(baseUrl?: string): LLMGatewayClient {
|
||
return new LLMGatewayClient({ caller: 'nognet', baseUrl, timeout: 30_000 });
|
||
}
|
||
|
||
/**
|
||
* ShieldX — LLM prompt injection defense
|
||
*/
|
||
export function createShieldXClient(baseUrl?: string): LLMGatewayClient {
|
||
return new LLMGatewayClient({ caller: 'shieldx', baseUrl, timeout: 10_000 });
|
||
}
|
||
|
||
/**
|
||
* CtxEvent — event management platform
|
||
*/
|
||
export function createCtxEventClient(baseUrl?: string): LLMGatewayClient {
|
||
return new LLMGatewayClient({ caller: 'ctxevent', baseUrl, timeout: 20_000 });
|
||
}
|