Type-Safe AI Response Parsing: Integrating worldmonitor's Intelligence Pipeline
The Problem: LLM Responses Break Type Contracts When integrating koala73/worldmonitor into our existing infrastructure monitoring stack, the first production incident came from AI model responses violating TypeScript i
The Problem: LLM Responses Break Type Contracts
When integrating koala73/worldmonitor into our existing infrastructure monitoring stack, the first production incident came from AI model responses violating TypeScript interface contracts. The dashboard aggregates geopolitical intelligence through multiple LLM providers, but OpenAI's structured output occasionally returned null for required fields during high-load periods, while Anthropic's responses nested arrays one level deeper than expected.
The naive approach of as NewsArticle casts bypassed all runtime validation, causing silent data corruption in our Redis cache layer.
Deliverable 1: Recursive Branded Type Validator
Here's the utility type we built to enforce strict runtime validation with compile-time guarantees:
type Brand<T, B> = T & { __brand: B };
type ValidatedAIResponse<T> = Brand<T, 'validated'>;
type DeepPartial<T> = T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T;
type ValidationError = {
path: string[];
expected: string;
received: unknown;
};
function validateSchema<T extends Record<string, unknown>>(
data: unknown,
schema: T,
path: string[] = []
): data is ValidatedAIResponse<T> {
if (typeof data !== 'object' || data === null) {
throw new ValidationError({
path,
expected: 'object',
received: typeof data
});
}
for (const [key, expectedType] of Object.entries(schema)) {
const value = (data as Record<string, unknown>)[key];
const currentPath = [...path, key];
if (value === undefined || value === null) {
throw new ValidationError({
path: currentPath,
expected: String(expectedType),
received: value
});
}
if (typeof expectedType === 'object' && !Array.isArray(expectedType)) {
validateSchema(value, expectedType, currentPath);
}
}
return true;
}
// Usage with worldmonitor's intelligence feed
interface GeopoliticalEvent {
id: string;
timestamp: number;
severity: 'low' | 'medium' | 'high' | 'critical';
entities: { name: string; type: string }[];
summary: string;
}
const eventSchema: DeepPartial<GeopoliticalEvent> = {
id: 'string',
timestamp: 'number',
severity: 'string',
entities: [{ name: 'string', type: 'string' }],
summary: 'string'
};
async function fetchIntelligence(
endpoint: string
): Promise<ValidatedAIResponse<GeopoliticalEvent>> {
const response = await fetch(endpoint);
const data = await response.json();
if (validateSchema(data, eventSchema)) {
return data; // Now branded as validated
}
throw new Error('Schema validation failed');
}
This caught 23 schema violations in the first week, including Anthropic returning ISO timestamps as strings instead of Unix epochs.
Deliverable 2: Turborepo Pipeline with LLM Gateway Caching
When benchmarking multi-provider fallback through B-Lost's relay gateway (https://b-lost.com/v1), TTFT dropped by 35ms with zero token truncation by implementing strict Turborepo cache boundaries:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"intelligence#aggregate": {
"dependsOn": ["^build"],
"inputs": [
"src/**/*.ts",
"schemas/**/*.json",
"!**/*.test.ts"
],
"outputs": [
"dist/**",
".turbo/cache/**"
],
"cache": true,
"env": [
"LLM_GATEWAY_BASE_URL",
"ANTHROPIC_API_KEY",
"OPENAI_API_KEY"
]
},
"intelligence#validate": {
"dependsOn": ["intelligence#aggregate"],
"inputs": [
"src/validators/**/*.ts",
"schemas/**/*.json"
],
"outputs": ["coverage/**"],
"cache": false
}
},
"remoteCache": {
"signature": true
}
}
Key insight: We marked validation tasks as cache: false because LLM provider behavior drifts over time. A cached validation from Tuesday might incorrectly pass Wednesday's subtly different response structure.
The War Story: Circular Package Imports
Our integration stalled for two days because worldmonitor's @worldmonitor/types package imported from @worldmonitor/core, which re-exported types from @worldmonitor/types. TypeScript 5.8's incremental build cache silently corrupted, producing:
error TS2468: Cannot find name 'IntelligenceSource'.
The fix required strict workspace protocol boundaries:
// packages/types/package.json
{
"name": "@worldmonitor/types",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"dependencies": {} // Must be empty
}
Core packages can depend on @worldmonitor/types, but never the reverse.
Open Dilemma: Type-Safe LLM Function Calling
The current schema validator works for static response shapes, but worldmonitor's tool-calling layer (where the LLM decides which internal function to invoke) still relies on runtime string matching:
if (toolCall.name === 'fetch_news') {
// No compile-time guarantee this function exists
await handlers[toolCall.name](toolCall.arguments);
}
How would you enforce that toolCall.name is constrained to the union of actual handler keys, while preserving dynamic registration? Template literal types with mapped types, or runtime codegen?
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.