Lifecycle Middleware: onFinish, onError, and onChunk Hooks
You need visibility into AI request lifecycles without cluttering your business logic. NeuroLink's lifecycle middleware hooks provide exactly that—clean observation points for streaming chunks, completion events, and errors.
The Three Core Hooks
NeuroLink v9.30 introduces three callback mechanisms:
-
onChunk: Fires for every token in streaming responses -
onFinish: Fires when generation completes successfully -
onError: Fires when errors occur
Real-Time Token Processing with onChunk
Monitor streaming responses as they arrive:
import { NeuroLink } from "@juspay/neurolink";
const neurolink = new NeuroLink();
const result = await neurolink.stream({
input: { text: "Write a story about a robot learning to paint" },
middleware: [
{
name: "chunk-logger",
onChunk: (chunk, { sequenceNumber }) => {
// Track every token as it arrives
analytics.track("token.received", {
sequence: sequenceNumber,
timestamp: Date.now(),
});
},
},
],
});
for await (const chunk of result.stream) {
process.stdout.write(chunk.content || "");
}
The onChunk callback receives:
-
chunk: The actual chunk data (text delta, tool call, etc.) -
sequenceNumber: Position in the stream (0, 1, 2...) -
timestamp: When the chunk was received
Important: onChunk is fire-and-forget. If your callback returns a promise, errors are caught and logged—they don't break the stream.
Post-Generation Analytics with onFinish
Capture complete generation metrics:
const result = await neurolink.generate({
input: { text: "Analyze this code for bugs" },
middleware: [
{
name: "cost-tracker",
onFinish: (result, metadata) => {
const costEntry = {
timestamp: Date.now(),
provider: metadata.provider,
model: metadata.model,
tokens: result.usage,
cost: calculateCost(result.usage, metadata.model),
duration: metadata.duration,
};
costTracker.record(costEntry);
// Alert on budget thresholds
if (costTracker.total > ALERT_THRESHOLD) {
notifyTeam("Cost threshold exceeded");
}
},
},
],
});
onFinish receives:
-
result: The complete generation result (text, usage, finish reason) -
metadata: Provider, model, timestamps, duration
Graceful Degradation with onError
Observe failures without suppressing them:
const result = await neurolink.generate({
input: { text: "Process this transaction" },
middleware: [
{
name: "error-handler",
onError: (error, metadata) => {
// Log for debugging
logger.error("Generation failed", {
error: error.message,
provider: metadata.provider,
model: metadata.model,
recoverable: metadata.recoverable,
duration: metadata.duration,
});
// Track in metrics
metrics.increment("generation.errors", {
provider: metadata.provider,
type: error.name,
});
// Note: The original error is still thrown after this hook runs
},
},
],
});
The recoverable boolean indicates whether the error is transient (rate limit, timeout) or permanent (invalid API key, malformed request).
Complete Cost Tracking Example
Here's a production-ready cost tracking middleware:
interface CostRecord {
timestamp: number;
provider: string;
model: string;
promptTokens: number;
completionTokens: number;
cost: number;
duration: number;
success: boolean;
}
class CostTracker {
private records: CostRecord[] = [];
private totalCost = 0;
private alertThreshold: number;
private alertCallback?: (total: number, record: CostRecord) => void;
constructor(
alertThreshold = 100,
alertCallback?: (total: number, record: CostRecord) => void
) {
this.alertThreshold = alertThreshold;
this.alertCallback = alertCallback;
}
record(entry: CostRecord): void {
this.records.push(entry);
this.totalCost += entry.cost;
if (this.totalCost > this.alertThreshold && this.alertCallback) {
this.alertCallback(this.totalCost, entry);
}
}
getTotalCost(): number {
return this.totalCost;
}
getRecords(): CostRecord[] {
return [...this.records];
}
}
// Pricing per 1K tokens
const PRICING: Record<string, { input: number; output: number }> = {
"gpt-4o": { input: 0.005, output: 0.015 },
"gpt-4o-mini": { input: 0.00015, output: 0.0006 },
"claude-3-5-haiku": { input: 0.0008, output: 0.004 },
"claude-3-5-sonnet": { input: 0.003, output: 0.015 },
};
function calculateCost(
usage: { promptTokens: number; completionTokens: number },
model: string
): number {
const pricing = PRICING[model] || { input: 0.001, output: 0.002 };
return (
(usage.promptTokens / 1000) * pricing.input +
(usage.completionTokens / 1000) * pricing.output
);
}
Middleware Composition
Lifecycle hooks integrate naturally with other NeuroLink middleware:
const neurolink = new NeuroLink({
middleware: [
// Priority 120: Request transformation
{ name: "transform", priority: 120 },
// Priority 110: Lifecycle hooks (this article)
{ name: "lifecycle", priority: 110 },
// Priority 100: Analytics
{ name: "analytics", priority: 100 },
// Priority 90: Guardrails
{ name: "guardrails", priority: 90 },
],
});
Priority 110 ensures lifecycle middleware observes the full pipeline, including analytics overhead and guardrail processing.
Testing Lifecycle Hooks
Unit test without real API calls:
describe("CostTracker middleware", () => {
it("tracks costs correctly", async () => {
const tracker = new CostTracker();
const mockNeurolink = createMockNeurolink();
// Simulate successful generation
mockNeurolink.simulateGenerate({
text: "Test response",
usage: { promptTokens: 100, completionTokens: 50 },
provider: "openai",
model: "gpt-4o-mini",
});
const result = await mockNeurolink.generate({
input: { text: "Test" },
middleware: [createCostMiddleware(tracker)],
});
expect(tracker.getTotalCost()).toBeGreaterThan(0);
expect(tracker.getRecords()).toHaveLength(1);
});
});
Production Considerations
-
Error propagation: Provider errors are re-thrown after
onErrorfires - Hook errors: Callback errors are swallowed and logged—they don't break your pipeline
- Async patterns: All hooks support async callbacks
- Conditional execution: Use middleware conditions to restrict hooks to specific providers or models
NeuroLink — The Universal AI SDK for TypeScript
- GitHub: github.com/juspay/neurolink
- Install:
npm install @juspay/neurolink - Docs: docs.neurolink.ink
- Blog: blog.neurolink.ink — 150+ technical articles
Top comments (0)