Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.trodo.ai/docs/llms.txt

Use this file to discover all available pages before exploring further.

Helpers — tool, llm, trace, retrieval

Thin wrappers around withSpan. Declare the span once at the function definition site instead of inlining withSpan(...) at every call site.
const fetchUser   = trodo.tool('fetch_user',  async (id)    => db.users.findById(id));
const searchKb    = trodo.retrieval('kb.search', async (q)  => kb.search(q, { k: 8 }));
const prepareCtx  = trodo.trace('prepare_ctx', async (u, t) => ({ history: await loadHistory(u), topic: t }));
const answer      = trodo.llm('answer', async (messages) => {
  return await client.chat.completions.create({ model: 'gpt-4o-mini', messages, temperature: 0.2 });
}, { model: 'gpt-4o-mini', provider: 'openai', temperature: 0.2 });
Every helper captures args as span input, return value as span output, and records thrown exceptions as status='error'. Don’t use llm for frameworks we already auto-instrument (OpenAI, Anthropic, etc.) — you’ll get duplicate spans.

extractUsage — custom token extraction

llm auto-extracts tokens from three response shapes: OpenAI (usage.prompt_tokens), Anthropic (usage.input_tokens), Google (usageMetadata.promptTokenCount). For anything else, pass your own:
const generate = trodo.llm('cohere.generate', callCohere, {
  model: 'command-r',
  provider: 'cohere',
  extractUsage: (r) => ({
    inputTokens: r?.meta?.tokens?.input_tokens,
    outputTokens: r?.meta?.tokens?.output_tokens,
  }),
});

trackLlmCall — imperative LLM span

Use when you’ve already made the LLM call and just want to record it, e.g. raw fetch to Ollama / vLLM / a self-hosted endpoint.
const resp = await fetch('http://ollama.internal/api/chat', {
  method: 'POST',
  body: JSON.stringify({ model: 'llama3.1:70b', messages }),
}).then((r) => r.json());

await trodo.trackLlmCall({
  model: resp.model,
  provider: 'ollama',
  inputTokens: resp.prompt_eval_count,
  outputTokens: resp.eval_count,
  prompt: messages,
  completion: resp,
  metadata: { endpoint: '/api/chat' },
});
trackLlmCall is a no-op outside wrapAgent. Pass cost explicitly to override the internal pricing table (negotiated rates, self-hosted zero-cost, etc.).

Feedback

Record a user’s reaction to a run — thumbs / rating / comment. One of satisfaction, rating, comment must be set.
const { result, runId } = await trodo.wrapAgent('support-bot', fn);
// … later, after the user clicks thumbs-up:
await trodo.feedback(runId, { satisfaction: 'positive', rating: 5, comment: 'looks right' });
FieldTypePurpose
satisfaction'positive' | 'negative'Thumbs-up / thumbs-down column + cluster satisfaction rollup
ratingnumberAny numeric scale (1–5, NPS, eval score)
commentstringFree-text note, searchable in the dashboard
metadataobjectFree-form JSON, e.g. { source: 'thumbs_widget' }
Each call appends a row — sending the same thumbs twice creates two rows. De-dupe on the client if you care.

Custom attributes

Attach arbitrary key/value pairs to any span. Visible in the span detail drawer, searchable in the dashboard.
await trodo.withSpan({ kind: 'tool', name: 'billing.lookup' }, async (span) => {
  span.setAttribute('customer_tier', 'enterprise');
  span.setAttribute('region', 'eu-west-1');
  span.setAttribute('request_id', req.id);
  return await billing.lookup(req.customerId);
});
Also available on runs via run.setMetadata({ ... }).

Errors

Two levels — they’re not mutually exclusive.
LevelTriggerSets
Span errorwithSpan / tool / llm callback throwsstatus='error', error_type, error_message on the span; increments run’s error_count
Run errorwrapAgent callback throws (or run.setErrorSummary(...))status='error', error_summary on the run
A throw inside a nested withSpan that bubbles all the way up sets both. wrapAgent always flushes on throw — you don’t need try/catch around it just to “rescue” metrics. For providers that return HTTP 200 with an error body, either throw after checking (makes the span an error), or setAttribute('llm_error', ...) and keep status='ok' (shows up in filters without inflating error_count).

Tokens and cost

Tokens come from the provider’s response; cost is computed at ingest time from the internal pricing table.
FrameworkToken sourceAuto-extracted?
OpenAI / Azure OpenAIresponse.usage.{prompt_tokens, completion_tokens}yes
Anthropicresponse.usage.{input_tokens, output_tokens}yes
Google Gemini / Vertexresponse.usageMetadata.{promptTokenCount, candidatesTokenCount}yes
Bedrock Converseresponse.usage.{inputTokens, outputTokens}yes
Cohereresponse.meta.tokens.{input_tokens, output_tokens}yes
Mistralresponse.usage.{prompt_tokens, completion_tokens}yes
Vercel AI SDKAccumulated across calls (requires experimental_telemetry)yes
LangChain / LlamaIndexFrom the underlying LLM wrapperyes if wrapper supports it
Raw HTTPuse trackLlmCall
Cost: (input_tokens × input_rate) + (output_tokens × output_rate) from the pricing table. Pass cost explicitly on setLlm or trackLlmCall to override — enterprise rates, cached tokens, self-hosted zero-cost.

Rollups

Run fieldSource
total_tokens_inSUM(span.input_tokens) across kind='llm' spans
total_tokens_outSUM(span.output_tokens) across kind='llm' spans
total_costSUM(span.cost) across kind='llm' spans
span_countCOUNT(span)
tool_countCOUNT(span WHERE kind='tool')
error_countCOUNT(span WHERE status='error')
Rollups only include spans that belong to this run — parentRunId does not cascade tokens from child runs. Use joinRun for unified rollups.