Add custom spans using convenience wrappers or withSpan for fine-grained tracing control.
npm install @arizeai/phoenix-otel @arizeai/openinference-coreimport { register } from "@arizeai/phoenix-otel";
register({ projectName: "my-app" });| Span Kind | Method | Use Case |
|---|---|---|
| CHAIN | traceChain |
Workflows, pipelines, orchestration |
| AGENT | traceAgent |
Multi-step reasoning, planning |
| TOOL | traceTool |
External APIs, function calls |
| RETRIEVER | withSpan |
Vector search, document retrieval |
| LLM | withSpan |
LLM API calls (prefer auto-instrumentation) |
| EMBEDDING | withSpan |
Embedding generation |
| RERANKER | withSpan |
Document re-ranking |
| GUARDRAIL | withSpan |
Safety checks, content moderation |
| EVALUATOR | withSpan |
LLM evaluation |
import { traceChain, traceAgent, traceTool } from "@arizeai/openinference-core";
// CHAIN - workflows
const pipeline = traceChain(
async (query: string) => {
const docs = await retrieve(query);
return await generate(docs, query);
},
{ name: "rag-pipeline" }
);
// AGENT - reasoning
const agent = traceAgent(
async (question: string) => {
const thought = await llm.generate(`Think: ${question}`);
return await processThought(thought);
},
{ name: "my-agent" }
);
// TOOL - function calls
const getWeather = traceTool(
async (city: string) => fetch(`/api/weather/${city}`).then(r => r.json()),
{ name: "get-weather" }
);import { withSpan, getInputAttributes, getRetrieverAttributes } from "@arizeai/openinference-core";
// RETRIEVER with custom attributes
const retrieve = withSpan(
async (query: string) => {
const results = await vectorDb.search(query, { topK: 5 });
return results.map(doc => ({ content: doc.text, score: doc.score }));
},
{
kind: "RETRIEVER",
name: "vector-search",
processInput: (query) => getInputAttributes(query),
processOutput: (docs) => getRetrieverAttributes({ documents: docs })
}
);Options:
withSpan(fn, {
kind: "RETRIEVER", // OpenInference span kind
name: "span-name", // Span name (defaults to function name)
processInput: (args) => {}, // Transform input to attributes
processOutput: (result) => {}, // Transform output to attributes
attributes: { key: "value" } // Static attributes
});Always capture I/O for evaluation-ready spans. Use getInputAttributes and getOutputAttributes helpers for automatic MIME type detection:
import {
getInputAttributes,
getOutputAttributes,
withSpan,
} from "@arizeai/openinference-core";
const handleQuery = withSpan(
async (userInput: string) => {
const result = await agent.generate({ prompt: userInput });
return result;
},
{
name: "query.handler",
kind: "CHAIN",
// Use helpers - automatic MIME type detection
processInput: (input) => getInputAttributes(input),
processOutput: (result) => getOutputAttributes(result.text),
}
);
await handleQuery("What is 2+2?");What gets captured:
{
"input.value": "What is 2+2?",
"input.mime_type": "text/plain",
"output.value": "2+2 equals 4.",
"output.mime_type": "text/plain"
}Helper behavior:
- Strings →
text/plain - Objects/Arrays →
application/json(automatically serialized) undefined/null→ No attributes set
Why this matters:
- Phoenix evaluators require
input.valueandoutput.value - Phoenix UI displays I/O prominently for debugging
- Enables exporting data for fine-tuning datasets
Add custom metadata alongside standard I/O attributes:
const processWithMetadata = withSpan(
async (query: string) => {
const result = await llm.generate(query);
return result;
},
{
name: "query.process",
kind: "CHAIN",
processInput: (query) => ({
"input.value": query,
"input.mime_type": "text/plain",
"input.length": query.length, // Custom attribute
}),
processOutput: (result) => ({
"output.value": result.text,
"output.mime_type": "text/plain",
"output.tokens": result.usage?.totalTokens, // Custom attribute
}),
}
);- Span attributes:
span-chain.md,span-retriever.md,span-tool.md, etc. - Attribute helpers: https://docs.arize.com/phoenix/tracing/manual-instrumentation-typescript#attribute-helpers
- Auto-instrumentation:
instrumentation-auto-typescript.mdfor framework integrations