GET STARTED
See an agent run, not another logging chore
Pass the provider response you already have. AgentPeek extracts supported output, model, and token fields, calculates a versioned catalog estimate when usage and billing context are known, redacts sensitive values, and attaches request context automatically.
Create a project, copy its one-time API key, and set AGENTPEEK_API_KEY in your backend environment.
1. Install
npm install https://agentpeek.62.238.63.198.sslip.io/sdk/agentpeek-sdk-0.2.1.tgz
pip install https://agentpeek.62.238.63.198.sslip.io/sdk/agentpeek_sdk-0.2.1-py3-none-any.whl
2. Track a provider response
import { AgentPeek } from "@agentpeek/sdk";
const peek = new AgentPeek({
apiKey: process.env.AGENTPEEK_API_KEY!,
});
const started = performance.now();
const response = await openai.responses.create({
model: "gpt-5-mini",
input: userMessage,
});
// model, typed output, reported token classes, and known catalog cost are extracted
peek.trackOpenAI(response, {
input: userMessage,
latencyMs: Math.round(performance.now() - started),
});track() and provider tracking methods return synchronously after queueing. Use await peek.flush() only during graceful shutdown or at the end of a short script.
NORMALIZATION
OpenAI, Anthropic, and Gemini adapters
The adapters accept raw OpenAI Responses and Chat Completions, Anthropic Messages, and Gemini GenerateContent and Interactions results. A single text-only result becomes a string. Multiple choices or candidates, Interaction steps, tool and function calls, thinking and signature parts, and multimodal content remain typed JSON.
const createResponse = peek.wrapProvider(
"openai",
{ input: ([request]) => request },
openai.responses.create.bind(openai.responses),
);
const message = peek.wrapProvider(
"anthropic",
{ input: ([request]) => request },
anthropic.messages.create.bind(anthropic.messages),
);
// Raw adapters are also exported for pipelines that only normalize.
import { fromOpenAI, fromAnthropic, fromGemini } from "@agentpeek/sdk";
// fromGemini accepts both GenerateContent and Interactions results.| Provider | Accepted usage | Preserved output |
|---|---|---|
| OpenAI | input/prompt, cached, cache-write, output/completion, reasoning | Responses items; Chat choices, messages, and tool calls |
| Anthropic | uncached input, cache read, 5m/1h cache write, output, thinking | text, thinking/signature, and tool_use blocks |
| Gemini | input/prompt, cached, output/candidate, thought | GenerateContent candidates/parts; Interaction steps and function calls |
AUTOMATIC COST
Versioned pricing and billing units
Known model aliases resolve against the built-in catalog dated 2026-08-30. Estimates select the applicable long-context tier, subtract cache reads and writes from regular input, distinguish Anthropic 5-minute and 1-hour cache writes, and bill output once—reasoning or thoughts remain an output subset.
Normalized billing context applies supported modifiers, including Anthropic US inference geography and Gemini Flex or supported Priority service tiers. An unrecognized model, missing usage, or unsupported service-tier modifier produces unknown cost instead of silently using the standard rate.
Built-in rates plus normalized provider usage and billing context.
cost_source: catalog · pricing_version: 2026-08-30Send a provider-reported or contract-specific USD cost.
cost_source: reported · pricing_version: nullFor a custom JavaScript catalog, pass both pricing and a stable pricingVersion provenance label; without a label, catalog-derived events use pricing_version: custom. The server authoritatively fills missing costs from its built-in catalog, so direct HTTP events receive the same built-in provenance.
PROPAGATION
Request and session context
Context uses AsyncLocalStorage in Node.js and contextvars in Python, so concurrent requests do not leak IDs or metadata. Inner context and explicit event values override outer defaults.
await peek.withContext(
{
userId: user.id,
sessionId: conversation.id,
metadata: { environment: "production" },
},
async () => {
// Every track(), stream(), and span below inherits this context.
await answerQuestion();
},
);Express middleware reads authenticated user fields, x-agentpeek-session-id, W3C traceparent, and request IDs. ASGI middleware offers the same scope for FastAPI and Starlette.
STREAMING
Pass chunks through, capture the result
Streaming helpers are lazy async iterables, and every chunk keeps its original identity and order. Successful exhaustion records success, a recognized provider terminal failure or thrown iterable records an error, and breaking consumption early records cancellation. In every case, any assembled typed output and observed time to first token are retained.
const stream = await openai.chat.completions.create({
model: "gpt-5-mini",
messages,
stream: true,
stream_options: { include_usage: true },
});
for await (const chunk of peek.stream("openai", stream, { input: messages })) {
yield chunk; // exact provider chunk; AgentPeek never changes it
}
// Output and TTFT are tracked; usage and cost require provider-reported usage.stream_options.include_usage; Responses reports usage on its terminal response, Anthropic in the final message_delta, GenerateContent in usageMetadata, and Gemini Interactions on interaction.completed. If that usage never arrives, token fields remain absent and catalog cost stays unknown—AgentPeek does not substitute zero.TRACES & SPANS
Nested spans for LLM calls, tools, and workflows
Use trace() for the root run and span() for agent, chain, workflow, LLM, tool, retriever, or custom work. Parent and trace IDs propagate automatically across async boundaries.
await peek.trace("support.answer", async () => {
const documents = await peek.span(
"knowledge.search",
{ kind: "retriever", input: { query } },
() => searchDocuments(query),
);
return peek.span("policy.check", { kind: "tool" }, () =>
checkPolicy(documents)
);
});Provider interactions created inside a span become trace leaves. Dashboard trace trees do not roll generic span durations or costs into interaction analytics twice.
FRAMEWORKS
Express, ASGI, LangChain, and LangGraph
// Express-compatible request context
app.use(peek.requestMiddleware());
// LangChain / LangGraph nested run callbacks
const callbacks = [peek.langchain()];
await chain.invoke({ question }, { callbacks });from agentpeek import AgentPeekASGIMiddleware
# FastAPI / Starlette / any ASGI application
app.add_middleware(AgentPeekASGIMiddleware, client=peek)
# LangChain / LangGraph
result = await chain.ainvoke(
{"question": question},
config={"callbacks": [peek.langchain()]},
)The callback handlers map framework run IDs and parent run IDs into AgentPeek spans without importing LangChain as a required dependency.
PYTHON
Sync and async are both first-class
from agentpeek import AgentPeek
peek = AgentPeek() # reads AGENTPEEK_API_KEY
response = client.messages.create(
model="claude-sonnet-5",
messages=messages,
)
peek.track_anthropic(response, input=messages, latency_ms=842)Async decorator, context, trace, and stream
@peek.track(model="gpt-5-mini", provider="openai")
async def answer(question: str):
return await run_agent(question)
with peek.context(user_id=user.id, session_id=session.id):
result = await peek.trace("support.answer", answer, question)
async for chunk in peek.astream("openai", provider_stream, input=question):
yield chunkDecorated functions preserve return values and re-raise the original exception object. stream() handles synchronous iterables; astream() handles provider async iterables.
PRIVACY
Redaction runs before the queue
Default hooks recursively mask authorization, cookies, passwords, API keys, access/refresh tokens, secrets, private keys, email addresses, payment-card-like values, and common provider key formats. Source request and response objects are never mutated.
const peek = new AgentPeek({
redaction: {
sensitiveKeys: ["ssn", /customer_secret/i],
patterns: [/acct_[a-z0-9]+/gi],
replacement: "[REDACTED]",
hook: (value, field) => tenantPolicy(value, field),
},
});Customize keys and patternsAdd strings or regular expressions for your domain.
Add policy hooksHooks receive the already-redacted JSON and its input/output/error/metadata field.
Fail closedIf a custom hook throws, built-in redaction still protects the queued value.
API REFERENCE
Direct HTTPS ingestion
Use direct HTTP when an SDK does not fit your runtime. Both endpoints use the same project key, rate limit, project isolation, and payload-size boundary.
https://agentpeek.62.238.63.198.sslip.io/v1/interactionshttps://agentpeek.62.238.63.198.sslip.io/v1/spanscurl -X POST https://agentpeek.62.238.63.198.sslip.io/v1/interactions \
-H "Authorization: Bearer $AGENTPEEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "evt_01J...",
"model": "gpt-5-mini",
"provider": "openai",
"input": "Where is my order?",
"output": "It arrives tomorrow.",
"input_tokens": 120,
"cached_input_tokens": 40,
"output_tokens": 28,
"latency_ms": 842,
"status": "success",
"user_id": "usr_123",
"session_id": "ses_abc",
"trace_id": "trace_01J...",
"span_id": "span_01J..."
}'Idempotency
Provide a stable id. Replaying the same project ID and identical payload returns duplicate: true without changing session or usage totals. Reusing it for a changed payload returns 409. The same event ID remains independent across projects.
HTTP/1.1 202 Accepted
{
"accepted": true,
"id": "server-uuid",
"external_id": "evt_01J...",
"duplicate": false
}API REFERENCE
Interaction event schema
| Field | Type | Description | |
|---|---|---|---|
model | string | required | Resolved model identifier |
input | JSON | required | Prompt, messages, or structured input |
output | JSON | optional | String for text-only; typed provider JSON otherwise |
latency_ms | integer | required | End-to-end milliseconds |
time_to_first_token_ms | integer | optional | Streaming TTFT |
status | success | error | cancelled | optional | Includes terminal failure and consumer cancellation |
provider | string | for pricing | openai, anthropic, gemini/google |
user_id / session_id | string | optional | Inherited request context |
input_tokens | integer | optional | Total input when provider usage is available |
cached_input_tokens | integer | optional | Subset of input |
cache_write_tokens | integer | optional | Subset of input |
output_tokens | integer | optional | Total output including reasoning |
reasoning_tokens | integer | optional | Subset of output |
cost | USD number | optional | Reported cost or versioned catalog estimate |
cost_source / pricing_version | string | optional | reported, catalog plus provenance, or unknown |
trace_id / span_id | string | optional | Nested execution identifiers |
error | JSON | for errors | Required for error status |
metadata | object | optional | Searchable redacted context |
Span-only fields
trace_id | Required trace identifier |
|---|---|
span_id | Required identifier unique inside the project |
parent_span_id | Optional parent, including out-of-order delivery |
name | Human-readable operation name |
kind | agent, chain, workflow, llm, tool, retriever, or custom |
OPERATIONS
Reliability, security, and limits
- Tracking queues locally and stays off the application response path.
- Network, 429, and 5xx failures retry with bounded backoff.
- Queues are bounded to protect memory; overflow reports through
onError. - API keys are hashed at rest and dashboard access is organization/project scoped.
- Retention deletes content in bounded batches while usage history remains available.
| Request body | 1 MB |
|---|---|
| Ingestion rate | 300 requests/minute per project key |
| IDs | 160 characters |
| Default retention | 30 days, configurable per project |
| Unknown price | null / unknown, never zero |
Create a project, set the key, and paste either provider example.