The Adaline SDK is the recommended way to integrate your AI application with Adaline. Beyond sending traces and spans, the SDK gives you deployment management with automatic caching, smart buffering with batched flushes, built-in retries with exponential backoff, and health monitoring — all production-ready out of the box.
What the SDK gives you
Capability Description Deployment management Fetch deployed prompts by ID or environment, with automatic background refresh and local caching. Structured observability Log traces and spans with 8 content types covering LLM calls, tool executions, retrieval, embeddings, guardrails, and custom logic. Smart buffering Traces and spans are buffered in memory and flushed in configurable batches — no request-per-span overhead. Automatic retries Failed API calls are retried with exponential backoff (5xx and network errors). 4xx errors fail immediately. Health monitoring Inspect flush status and background refresh health at runtime to detect issues before they become incidents. Nested spans Model complex workflows with parent-child span hierarchies — agents calling tools calling sub-agents. Session tracking Group related traces by session ID to follow multi-turn conversations or multi-request workflows. Evaluation variables Attach variable values to spans so they flow into continuous evaluations and datasets automatically.
Install
npm install @adaline/client
Initialize the client
import { Adaline } from "@adaline/client" ;
const adaline = new Adaline ({ apiKey: "your-api-key" });
from adaline import Adaline
adaline = Adaline( api_key = "your-api-key" )
Set the ADALINE_API_KEY environment variable and omit the apiKey parameter to avoid hardcoding secrets. The SDK reads from this variable automatically.
Manage deployments
The SDK can fetch deployed prompt configurations — including the model, provider settings, messages, tools, and variables — so your application always uses the latest version without redeploying code.
Fetch a specific deployment
const deployment = await adaline . getDeployment ({
promptId: "your-prompt-id" ,
deploymentId: "your-deployment-id" ,
});
const config = deployment . prompt . config ;
console . log ( config . providerName ); // "openai"
console . log ( config . model ); // "gpt-4"
console . log ( config . settings ); // { temperature: 0.7, maxTokens: 1000 }
const messages = deployment . prompt . messages ;
const tools = deployment . prompt . tools ;
const variables = deployment . prompt . variables ;
deployment = await adaline.get_deployment(
prompt_id = "your-prompt-id" ,
deployment_id = "your-deployment-id" ,
)
config = deployment.prompt.config
print (config.provider_name) # "openai"
print (config.model) # "gpt-4"
print (config.settings) # {"temperature": 0.7, "maxTokens": 1000}
messages = deployment.prompt.messages
tools = deployment.prompt.tools
variables = deployment.prompt.variables
Fetch the latest deployment for an environment
const deployment = await adaline . getLatestDeployment ({
promptId: "your-prompt-id" ,
deploymentEnvironmentId: "your-environment-id" ,
});
deployment = await adaline.get_latest_deployment(
prompt_id = "your-prompt-id" ,
deployment_environment_id = "your-environment-id" ,
)
Auto-refresh deployments in production
For long-running services, use initLatestDeployment to set up a cached deployment that refreshes automatically (default every 60 seconds) in the background. When you deploy a new prompt version in Adaline, your application picks it up without a restart.
const controller = await adaline . initLatestDeployment ({
promptId: "your-prompt-id" ,
deploymentEnvironmentId: "your-environment-id" ,
});
// Use the cached deployment in request handlers
const deployment = await controller . get ();
// Force a fresh fetch (bypasses cache)
const fresh = await controller . get ( true );
// Check background refresh health
const status = controller . backgroundStatus ();
// { stopped: false, consecutiveFailures: 0, lastError: null, lastRefreshed: Date }
// Stop the background refresh when shutting down
controller . stop ();
controller = await adaline.init_latest_deployment(
prompt_id = "your-prompt-id" ,
environment_id = "your-environment-id" ,
)
deployment = await controller.get()
fresh = await controller.get( force_refresh = True )
status = controller.get_background_status()
# {"stopped": False, "consecutive_failures": 0, "last_error": None, "last_refreshed": datetime}
await controller.stop()
Initialize the monitor
The monitor manages the lifecycle of traces and spans — buffering them in memory, batching them together, and flushing them to the Adaline API on a timer or when the buffer fills up.
const monitor = adaline . initMonitor ({ projectId: "your-project-id" });
monitor = adaline.init_monitor( project_id = "your-project-id" )
Create a trace
A trace represents a single end-to-end request flow — for example, one user message that triggers an LLM call, a tool execution, and a final response.
const trace = monitor . logTrace ({
name: "user-request" ,
status: "unknown" ,
sessionId: "session-abc123" ,
referenceId: "custom-uuid" ,
tags: [ "production" , "v2.1" ],
attributes: {
userId: "user-456" ,
region: "us-east-1" ,
featureFlag: "new-prompt-v2" ,
},
});
trace = monitor.log_trace(
name = "user-request" ,
status = "unknown" ,
session_id = "session-abc123" ,
reference_id = "custom-uuid" ,
tags = [ "production" , "v2.1" ],
attributes = {
"userId" : "user-456" ,
"region" : "us-east-1" ,
"featureFlag" : "new-prompt-v2" ,
},
)
Add spans
Each operation inside a trace is a span. Spans carry a content type that tells Adaline what kind of operation it represents — an LLM call, a tool execution, a vector retrieval, and more.
Create a span
const span = trace . logSpan ({
name: "chat-completion" ,
promptId: "your-prompt-id" ,
deploymentId: "your-deployment-id" ,
runEvaluation: true ,
tags: [ "v2.1" , "production" ],
attributes: { region: "us-east-1" , customerId: "dd2-cew3d2-saf32" },
});
span = trace.log_span(
name = "chat-completion" ,
prompt_id = "your-prompt-id" ,
deployment_id = "your-deployment-id" ,
run_evaluation = True ,
tags = [ "v2.1" , "production" ],
attributes = { "region" : "us-east-1" , "customerId" : "dd2-cew3d2-saf32" },
)
Update and end a span
After performing the operation, update the span with the result and end it:
const response = await openai . chat . completions . create ({ ... });
span . update ({
status: "success" ,
content: {
type: "Model" ,
provider: "openai" ,
model: "gpt-4" ,
input: JSON . stringify ({ messages }),
output: JSON . stringify ( response ),
},
});
span . end ();
response = openai.chat.completions.create( ... )
span.update(
status = "success" ,
content = {
"type" : "Model" ,
"provider" : "openai" ,
"model" : "gpt-4" ,
"input" : json.dumps({ "messages" : messages}),
"output" : json.dumps(response.model_dump()),
},
)
span.end()
Nested spans
Spans can contain child spans to model hierarchical workflows — an agent span containing tool call spans, or a RAG span containing embedding and retrieval sub-spans:
const agentSpan = trace . logSpan ({ name: "agent-orchestrator" });
const toolSpan = agentSpan . logSpan ({ name: "search-tool" });
// ... execute tool ...
toolSpan . update ({ status: "success" , content: { type: "Tool" , ... } });
toolSpan . end ();
const llmSpan = agentSpan . logSpan ({ name: "final-response" });
// ... generate response ...
llmSpan . update ({ status: "success" , content: { type: "Model" , ... } });
llmSpan . end ();
agentSpan . end ();
Span content types
The content.type field tells Adaline what kind of operation a span represents. Each type carries input and output as JSON strings, plus type-specific fields.
Model
LLM chat completions and text generation. Captures the provider, model, cost, and optionally expected output for evaluation.
For the best experience, stringify the exact request payload you send to your AI provider as input and the full response as output. When you use a supported provider, Adaline automatically extracts token usage, calculates cost, and surfaces model metadata. See Span content: input and output for full details and examples. You can also use Adaline’s own content schema for input and output, although this is more advanced and requires custom transformations.
span . update ({
status: "success" ,
content: {
type: "Model" ,
provider: "openai" ,
model: "gpt-4" ,
input: JSON . stringify ( params ),
output: JSON . stringify ( response ),
cost: 0.0032 , // optional, override the cost calculated by Adaline
},
});
ModelStream
Streaming LLM responses. Captures both the raw stream chunks and an aggregated output.
span . update ({
status: "success" ,
content: {
type: "ModelStream" ,
provider: "openai" ,
model: "gpt-4" ,
input: JSON . stringify ({ messages }),
output: rawStreamChunks ,
aggregateOutput: JSON . stringify ( aggregatedResponse ),
cost: 0.0028 , // optional, override the cost calculated by Adaline
},
});
Function or tool call execution.
span . update ({
status: "success" ,
content: {
type: "Tool" ,
input: JSON . stringify ({ index: 0 , id: "tool-123" , name: "get-weather" , arguments: JSON . stringify ({ city: "San Francisco" }) }),
output: JSON . stringify ({ index: 0 , id: "tool-123" , name: "get-weather" , data: JSON . stringify ({ temperature: 62 , condition: "foggy" }) }),
},
});
Retrieval
Vector search, document retrieval, or any RAG retrieval step.
span . update ({
status: "success" ,
content: {
type: "Retrieval" ,
input: JSON . stringify ({ query: "password reset steps" }),
output: JSON . stringify ({ documents: [
{ id: "doc1" , score: 0.95 , text: "..." },
{ id: "doc2" , score: 0.89 , text: "..." }
] }),
},
});
Embeddings
Embedding generation.
span . update ({
status: "success" ,
content: {
type: "Embeddings" ,
input: JSON . stringify ({ texts: [ "How do I reset my password?" ] }),
output: JSON . stringify ({ embeddings: [[ 0.012 , - 0.034 , ... ]] }),
},
});
Function
Custom business logic, data transforms, or any application-specific operation.
span . update ({
status: "success" ,
content: {
type: "Function" ,
input: JSON . stringify ({ operation: "get-user-plan" , userId: "user-456" }),
output: JSON . stringify ({ operation: "get-user-plan" , userId: "user-456" , plan: "enterprise" , features: [ ... ] }),
},
});
Guardrail
Safety checks, content filters, PII detection, or compliance rules.
span . update ({
status: "success" ,
content: {
type: "Guardrail" ,
input: JSON . stringify ({ text: userMessage , checks: [ "toxicity" , "pii" ] }),
output: JSON . stringify ({ text: userMessage , checks: [ "toxicity" , "pii" ], passed: true , flags: [] }),
},
});
Other
Any operation that doesn’t fit the types above.
span . update ({
status: "success" ,
content: {
type: "Other" ,
input: JSON . stringify ({ custom: "input" }),
output: JSON . stringify ({ custom: "output" }),
},
});
All content types share type, input (JSON string), and output (JSON string).
End the trace and flush
Always end the trace and flush remaining data. Calling end() on a trace recursively ends all child spans that haven’t been ended yet.
trace . update ({ status: "success" });
trace . end ();
await monitor . flush ();
trace.update( status = "success" )
trace.end()
await monitor.flush()
Traces and spans that are never ended will never be flushed. Always call end() — use try/finally blocks to guarantee it runs even when errors occur.
Attach variables for evaluation
Attach variable values to spans so they flow into continuous evaluations and can be captured into datasets :
const span = trace . logSpan ({
name: "chat-completion" ,
variables: {
user_question: { modality: "text" , value: userInput },
context: { modality: "text" , value: retrievedContext },
product_image: {
modality: "image" ,
detail: "auto" ,
value: { type: "url" , url: "https://example.com/product.jpg" },
},
},
});
span = trace.log_span(
name = "chat-completion" ,
variables = {
"user_question" : { "modality" : "text" , "value" : user_input},
"context" : { "modality" : "text" , "value" : retrieved_context},
"product_image" : {
"modality" : "image" ,
"detail" : "auto" ,
"value" : { "type" : "url" , "url" : "https://example.com/product.jpg" },
},
},
)
Variables support text, image, and pdf modalities. See Log attachments for full details on attaching variables, attributes, and tags.
Buffering, batching, and retries
The SDK handles reliability so you don’t have to.
How the buffer works
When you call logTrace() or logSpan(), entries are added to an in-memory buffer.
A background timer flushes the buffer every flushInterval seconds.
If the buffer reaches maxBufferSize, it flushes immediately.
Each flush sends a batch of entries in a single API call.
Retry behavior
Scenario Behavior 5xx server errors Retried with exponential backoff (1s initial, 10s max, 20s total budget). Network errors Retried with exponential backoff (same as 5xx). 4xx client errors Fail immediately — no retry. Check your API key and request payload. Consecutive failures After maxContinuousFlushFailures consecutive failures, the background timer stops.
Health monitoring
Inspect the monitor’s flush status at runtime to detect issues:
const status = monitor . flushStatus ;
// {
// stopped: false,
// consecutiveFailures: 0,
// lastError: null,
// lastFlushed: Date
// }
const buffer = monitor . buffer ;
const failedEntries = monitor . failedFlushEntries ;
status = monitor.flush_status
# {"stopped": False, "consecutive_failures": 0, "last_error": None, "last_flushed": datetime}
buffer = monitor.buffer
failed_entries = monitor.failed_flush_entries
Graceful shutdown
In production, handle process signals to flush remaining data before the process exits:
Serverless environments (AWS Lambda, Vercel Functions, Cloudflare Workers, etc.): The SDK flushes buffered traces and spans on a background interval, but serverless functions can exit before the next flush fires. Always call await monitor.flush() (TypeScript) or await monitor.flush() (Python) explicitly before your handler returns to ensure nothing is lost.
process . on ( "SIGTERM" , async () => {
await monitor . flush ();
monitor . stop ();
controller . stop ();
process . exit ( 0 );
});
import signal
async def shutdown ( sig , loop ):
await monitor.flush()
monitor.stop()
await controller.stop()
loop = asyncio.get_event_loop()
loop.add_signal_handler(signal. SIGTERM , lambda : asyncio.create_task(shutdown(signal. SIGTERM , loop)))
Next steps
Advanced Tracing Patterns Multi-step workflows, tool-calling agents, session tracking, and error handling patterns.
Log User Feedback Attach thumbs up/down, ratings, and comments to traces.
Log Attachments Attach attributes, tags, variables, and metadata to traces and spans.
SDK Reference Complete class and type reference for the TypeScript and Python SDKs.