integrate-flowlines-sdk-js
npx skills add https://github.com/flowlines-ai/skills --skill integrate-flowlines-sdk-js
Agent 安装分布
Skill 文档
Flowlines SDK Integration Guide
This document helps AI coding agents integrate the @flowlines/sdk into a Node.js/TypeScript codebase.
What is the Flowlines SDK?
@flowlines/sdk is a TypeScript SDK that provides OpenTelemetry-based observability for AI/LLM applications. It automatically instruments 13+ AI libraries (OpenAI, Anthropic, Cohere, AWS Bedrock, Google Vertex AI, LangChain, etc.), filters to only export LLM-relevant spans, and sends them to the Flowlines backend.
Installation
npm install @flowlines/sdk
- Requires Node.js >= 20
- Requires ESM (
"type": "module"in package.json)
Public API
The SDK exports exactly 5 items:
import { Flowlines, FlowlinesExporter, withContext } from "@flowlines/sdk";
import type { FlowlinesConfig, FlowlinesContext } from "@flowlines/sdk";
| Export | Kind | Purpose |
|---|---|---|
Flowlines |
Class | Main entry point. Initializes instrumentation and manages lifecycle. |
withContext |
Function | Attaches user/session/agent metadata to all LLM spans within a callback. |
FlowlinesExporter |
Class | Custom span exporter for advanced OpenTelemetry composition. |
FlowlinesConfig |
Type | Configuration interface for Flowlines constructor. |
FlowlinesContext |
Type | Context object passed to withContext. |
Configuration
interface FlowlinesConfig {
apiKey: string; // Required. Flowlines API key.
endpoint?: string; // Default: "https://ingest.flowlines.ai"
hasExternalOtel?: boolean; // Default: false. Set true to compose with your own NodeSDK.
hasTraceloop?: boolean; // Default: false. Set true for existing Traceloop setups.
instrumentModules?: InstrumentModules; // Default: undefined (all libraries instrumented).
exportIntervalMs?: number; // Default: 5000
maxQueueSize?: number; // Default: 2048
maxExportBatchSize?: number; // Default: 512
debug?: boolean; // Default: false
}
Validation rules:
apiKeymust be a non-empty stringhasExternalOtelandhasTraceloopare mutually exclusiveendpointmust use HTTPS (HTTP is only allowed for localhost/127.0.0.1)
Three Integration Modes
1. Standalone Mode (recommended for most apps)
Flowlines creates and manages its own OpenTelemetry NodeSDK. This is the simplest setup.
import OpenAI from "openai";
import { Flowlines, withContext } from "@flowlines/sdk";
// CRITICAL: Initialize BEFORE creating any AI client instances.
// Use instrumentModules to pass the imported module for reliable ESM instrumentation.
const flowlines = new Flowlines({
apiKey: process.env.FLOWLINES_API_KEY,
instrumentModules: { openAI: OpenAI },
});
// Now create your AI clients â they will be automatically instrumented.
const openai = new OpenAI();
// Use withContext to attach user/session metadata to spans.
const response = await withContext({ userId: "user-123", sessionId: "sess-456" }, () =>
openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello" }],
})
);
// Shutdown gracefully before process exit.
await flowlines.shutdown();
ESM note: In ESM applications, all
importstatements are hoisted to the top of the module and executed before any other code. This means auto-instrumentation (withoutinstrumentModules) may not patch modules in time. Always useinstrumentModulesto pass the imported modules explicitly â this guarantees reliable instrumentation regardless of import order.
2. External OpenTelemetry Mode
Use when the app already has its own NodeSDK setup and you want to add Flowlines to the existing pipeline.
import { Flowlines, withContext } from "@flowlines/sdk";
import { NodeSDK } from "@opentelemetry/sdk-node";
const flowlines = new Flowlines({
apiKey: process.env.FLOWLINES_API_KEY,
hasExternalOtel: true,
});
const sdk = new NodeSDK({
spanProcessors: [flowlines.createSpanProcessor()],
instrumentations: flowlines.getInstrumentations(),
});
sdk.start();
// ... use withContext as usual ...
await flowlines.shutdown();
await sdk.shutdown();
3. Traceloop Mode
Use when the app already has Traceloop initialized.
import { Flowlines } from "@flowlines/sdk";
const flowlines = new Flowlines({
apiKey: process.env.FLOWLINES_API_KEY,
hasTraceloop: true,
});
// Add flowlines.createSpanProcessor() to your Traceloop configuration.
Using withContext()
withContext attaches flowlines.user_id, flowlines.session_id, and flowlines.agent_id attributes to all LLM spans created within its callback. This is how Flowlines associates traces with users, sessions, and agents.
interface FlowlinesContext {
userId: string;
sessionId?: string;
agentId?: string;
}
function withContext<T>(ctx: FlowlinesContext, fn: () => T): T;
userIdis required (string)sessionIdis optional (omit to skip)agentIdis optional (omit to skip)fncan be sync or async â the return value (or Promise) is forwarded- Supports nesting: inner calls override outer values
- Must wrap every AI/LLM call that should carry user context
// Async usage with session and agent
const result = await withContext(
{ userId: "user-123", sessionId: "sess-456", agentId: "agent-1" },
async () => {
return await openai.chat.completions.create({ /* ... */ });
}
);
// Without session or agent ID
await withContext({ userId: "user-123" }, async () => {
await anthropic.messages.create({ /* ... */ });
});
// Nested contexts (inner overrides outer)
await withContext({ userId: "user-A", sessionId: "sess-1" }, async () => {
// spans here have user_id="user-A", session_id="sess-1"
await withContext({ userId: "user-B", sessionId: "sess-2" }, async () => {
// spans here have user_id="user-B", session_id="sess-2"
});
});
Context integration guidance
When integrating withContext, you MUST wrap LLM calls with context. Follow these steps:
-
Identify existing data in the codebase that maps to
userId,sessionId, andagentId:userId: the end-user making the request (e.g., authenticated user ID, email, API key owner)sessionId: the conversation or session grouping multiple interactions (e.g., chat thread ID, session token, conversation UUID)agentId: the AI agent or assistant handling the request (e.g., agent name, bot identifier, assistant ID)
-
If obvious mappings exist, use them directly. For example, if the app has
req.user.idand athreadId, wire them in:await withContext({ userId: req.user.id, sessionId: threadId }, async () => { ... }); -
If mappings are unclear, ask the user which variables or fields should be used for
userId,sessionId, andagentId. -
If no data is available yet, propose using placeholder values with TODO comments so the integration is functional and easy to complete later:
await withContext( { userId: "anonymous", // TODO: replace with actual user identifier sessionId: `sess-${Date.now()}`, // TODO: replace with actual session/conversation ID agentId: "my-agent", // TODO: replace with actual agent identifier }, async () => { ... } );Only include fields that are relevant.
sessionIdandagentIdcan be omitted entirely if not applicable.
Selective Instrumentation with instrumentModules
By default, all 13+ supported libraries are instrumented. To instrument only specific libraries, pass instrumentModules with the imported modules. The required import style varies by library:
OpenAI â use the default import:
import OpenAI from "openai";
const flowlines = new Flowlines({
apiKey: process.env.FLOWLINES_API_KEY,
instrumentModules: { openAI: OpenAI },
});
Anthropic â use a namespace import:
import * as AnthropicModule from "@anthropic-ai/sdk";
const flowlines = new Flowlines({
apiKey: process.env.FLOWLINES_API_KEY,
instrumentModules: { anthropic: AnthropicModule },
});
Both together:
import OpenAI from "openai";
import * as AnthropicModule from "@anthropic-ai/sdk";
const flowlines = new Flowlines({
apiKey: process.env.FLOWLINES_API_KEY,
instrumentModules: {
openAI: OpenAI,
anthropic: AnthropicModule,
},
});
Supported keys: openAI, anthropic, cohere, bedrock, google_vertexai, google_aiplatform, google_generativeai, pinecone, together, chromadb, qdrant, langchain, llamaIndex, mcp.
Important: The import style required depends on the library. OpenAI requires the default import (the class itself). Anthropic and most other libraries require a namespace import (import * as X from "..."). Using the wrong import style will cause instrumentation to silently fail or throw errors.
Critical Integration Rules
-
Initialize Flowlines BEFORE creating AI client instances. The SDK uses monkey-patching via OpenTelemetry instrumentation. Clients created before
new Flowlines(...)will not be instrumented. -
Only one standalone Flowlines instance can exist at a time. Creating a second standalone instance throws an error. Call
shutdown()first, or usehasExternalOtel/hasTraceloopfor composition. -
Always call
shutdown()before process exit. This flushes any pending spans to the backend. Without it, the last batch of traces may be lost. -
Wrap LLM calls with
withContext()to associate traces with users. WithoutwithContext, spans are still exported but lack user/session/agent metadata. -
The SDK only exports LLM-related spans. It filters for spans with
gen_ai.*orai.*attribute prefixes. Non-LLM OpenTelemetry spans (HTTP, DB, etc.) are discarded by the Flowlines exporter.
Graceful Shutdown Pattern
const flowlines = new Flowlines({ apiKey: "..." });
// Handle process signals
process.on("SIGINT", async () => {
await flowlines.shutdown();
process.exit(0);
});
process.on("SIGTERM", async () => {
await flowlines.shutdown();
process.exit(0);
});
Known Limitations
- OpenAI
responses.createis not instrumented. The underlying OpenTelemetry instrumentation only patcheschat.completions.create. Calls to the newerresponses.createAPI will not produce traces. Usechat.completions.createfor traced interactions.
Common Errors
| Error | Cause | Fix |
|---|---|---|
Flowlines API key is required |
Empty or missing apiKey |
Provide a valid API key |
Cannot set both hasExternalOtel and hasTraceloop |
Both flags set to true | Use only one mode flag |
A standalone Flowlines instance is already active |
Creating a second standalone instance | Call shutdown() on the first, or use hasExternalOtel: true |
Invalid Flowlines endpoint URL |
Malformed URL in endpoint |
Provide a valid URL |
Insecure Flowlines endpoint |
HTTP endpoint that is not localhost | Use HTTPS, or use localhost for development |
Full Example: Anthropic Chat with Tools
import * as AnthropicModule from "@anthropic-ai/sdk";
import Anthropic from "@anthropic-ai/sdk";
import { Flowlines, withContext } from "@flowlines/sdk";
// 1. Init Flowlines FIRST
const flowlines = new Flowlines({
apiKey: process.env.FLOWLINES_API_KEY,
instrumentModules: { anthropic: AnthropicModule },
});
// 2. Then create the AI client
const client = new Anthropic();
// 3. Wrap calls with withContext
const sessionId = `sess-${Date.now()}`;
const reply = await withContext({ userId: "user-123", sessionId }, () =>
client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }],
})
);
// 4. Shutdown before exit
await flowlines.shutdown();