Implementation:Langfuse Langfuse NormalizeInputOutput
| Knowledge Sources | |
|---|---|
| Domains | Data Normalization, AI Frameworks, LLM Messaging |
| Last Updated | 2026-02-14 00:00 GMT |
Overview
Concrete tool for normalizing diverse LLM provider message formats into unified ChatML arrays provided by Langfuse.
Description
The normalizeInput() and normalizeOutput() functions are the public entry points for ChatML normalization. They share the same architecture:
- Build adapter context: Merge the provided
NormalizerContextwith defaults, settingmetadatato the input data itself if not explicitly provided and attaching the raw data. - Select adapter: Call
selectAdapter(ctx)which evaluates the ordered adapter registry (langgraph > aisdk > openai > gemini > microsoftAgent > pydanticAI > semanticKernel > generic) and returns the first adapter whosedetect(ctx)returns true. - Preprocess: Call
adapter.preprocess(data, direction, ctx)where direction is "input" or "output". The adapter transforms the provider-specific format toward ChatML compatibility. - Validate: For input, call
mapToChatMl(preprocessed)which validates againstChatMlArraySchemawith fallback patterns. For output, callmapOutputToChatMl(preprocessed)which wraps single messages in arrays and handles themessageskey extraction.
The functions return a Zod SafeParseReturnType -- either { success: true, data: ChatMlMessage[] } or { success: false, error: ZodError }.
Supporting functions in the same module:
mapToChatMl(input): Validates input directly, then tries[[msgs]] -> [msgs]unwrapping, then tries{ messages: [...] }extraction.mapOutputToChatMl(output): Handles{ messages: [...] }extraction (LangGraph format), then wraps non-array output in an array.cleanLegacyOutput(output, fallback?): Converts legacy{ completion: "..." }format.extractAdditionalInput(input): Extracts non-messages keys from input objects.combineInputOutputMessages(inputResult, outputResult, cleanOutput): Merges input and output messages into a single thread, defaulting output role to "assistant".
Usage
These functions are used in the Langfuse web frontend to normalize observation input/output for display in the conversation view, and can be used in evaluation pipelines to ensure consistent message structures regardless of the originating provider.
Code Reference
Source Location
- Repository: langfuse
- File: packages/shared/src/utils/chatml/core.ts
- Lines: 130-158 (normalizeInput and normalizeOutput)
Signature
function normalizeInput(
input: unknown,
ctx?: NormalizerContext,
): {
success: boolean;
data?: ChatMlMessage[];
error?: ZodError;
}
function normalizeOutput(
output: unknown,
ctx?: NormalizerContext,
): {
success: boolean;
data?: ChatMlMessage[];
error?: ZodError;
}
Import
import {
normalizeInput,
normalizeOutput,
mapToChatMl,
mapOutputToChatMl,
cleanLegacyOutput,
extractAdditionalInput,
combineInputOutputMessages,
} from "@langfuse/shared/src/utils/chatml/core";
import type { NormalizerContext } from "@langfuse/shared/src/utils/chatml/adapters";
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| input (for normalizeInput) | unknown | Yes | Raw input data in any supported provider format. Can be a string, array of message objects, nested object with a messages key, or provider-specific structure.
|
| output (for normalizeOutput) | unknown | Yes | Raw output data in any supported provider format. Can be a single message object, string, array, or object with a messages key.
|
| ctx | NormalizerContext | No | Optional context for adapter selection. Includes: framework?: string (explicit adapter override), metadata?: unknown (observation metadata for detection), scopeName?: string (instrumentation scope), and other context fields.
|
Outputs
| Name | Type | Description |
|---|---|---|
| success | boolean | Whether the normalization and validation succeeded |
| data | ChatMlMessage[] (when success=true) | Array of normalized ChatML messages. Each message has at minimum a role field and typically content, with optional tool_calls, tool_call_id, json, and other fields.
|
| error | ZodError (when success=false) | Zod validation error describing why the data could not be normalized to ChatML format |
Usage Examples
Normalizing OpenAI-Format Input
import { normalizeInput } from "@langfuse/shared/src/utils/chatml/core";
const result = normalizeInput([
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What is the capital of France?" },
]);
// result.success === true
// result.data === [
// { role: "system", content: "You are a helpful assistant." },
// { role: "user", content: "What is the capital of France?" },
// ]
Normalizing Gemini-Format Input with Explicit Adapter
import { normalizeInput } from "@langfuse/shared/src/utils/chatml/core";
const result = normalizeInput(
[{ parts: [{ text: "Hello world" }], role: "user" }],
{ framework: "gemini" },
);
// result.success === true
// result.data === [{ role: "user", content: "Hello world" }]
Normalizing Output with Messages Key (LangGraph)
import { normalizeOutput } from "@langfuse/shared/src/utils/chatml/core";
const result = normalizeOutput({
messages: [
{ role: "assistant", content: "The capital of France is Paris." },
],
});
// result.success === true
// result.data === [
// { role: "assistant", content: "The capital of France is Paris." },
// ]
Combining Input and Output Messages
import {
normalizeInput,
normalizeOutput,
cleanLegacyOutput,
combineInputOutputMessages,
} from "@langfuse/shared/src/utils/chatml/core";
const inputResult = normalizeInput([
{ role: "user", content: "Tell me a joke" },
]);
const rawOutput = "Why did the chicken cross the road?";
const cleanOutput = cleanLegacyOutput(rawOutput, rawOutput);
const outputResult = normalizeOutput(rawOutput);
const combined = combineInputOutputMessages(inputResult, outputResult, cleanOutput);
// combined === [
// { role: "user", content: "Tell me a joke" },
// { role: "assistant", content: "Why did the chicken cross the road?" },
// ]
Handling Normalization Failure
import { normalizeInput } from "@langfuse/shared/src/utils/chatml/core";
const result = normalizeInput(42); // Not a valid message format
// result.success === false
// result.error contains ZodError describing the validation failure