LLM-oriented observability SDK built on OpenTelemetry with cost/usage tracking
Project description
yuutrace
LLM-oriented observability SDK built on OpenTelemetry. Provides structured tracing for LLM agent workloads with first-class cost and token usage tracking.
What's in the box
| Deliverable | Registry | Description |
|---|---|---|
yuutrace |
PyPI | Python SDK for instrumentation + CLI (ytrace server / ytrace ui) |
@yuutrace/ui |
npm | React component library for trace visualization |
your-agent (Python)
│ import yuutrace
│
▼
ytrace server ──OTLP/HTTP JSON──▶ SQLite
│
▼
ytrace ui ──REST API──▶ Browser (@yuutrace/ui)
Key Concepts
Span Hierarchy
Every instrumented conversation produces a tree of OpenTelemetry spans:
conversation (root)
├── llm_gen # one LLM request
├── tools # a batch of tool calls
│ ├── tool:search
│ └── tool:calc
├── llm_gen
└── ...
The root conversation span carries metadata (conversation.id, agent, model, tags). Child spans are created automatically by the context managers.
Delta Semantics
All cost and usage data is recorded as increments (deltas). A single span can emit multiple cost/usage events. Aggregation happens at query time, not write time. This keeps the write path simple and concurrent-safe.
Event Types
| Event Name | Purpose | Key Attributes |
|---|---|---|
yuu.cost |
Cost increment | category, currency, amount, llm.model, tool.name |
yuu.llm.usage |
Token usage | provider, model, input_tokens, output_tokens, cache_read_tokens |
yuu.tool.usage |
Tool usage (optional) | name, unit, quantity |
Business code never writes these event names or attribute keys directly — the SDK wraps them in type-safe functions.
Fast Fail
current_span() raises NoActiveSpanError if called outside a span context. No implicit span creation, no silent data loss.
Installation
# Python SDK
pip install yuutrace
# React components (for embedding in your own dashboard)
npm install @yuutrace/ui
Python SDK Usage
import yuutrace as ytrace
from uuid import uuid4
# 1. Open a conversation span
with ytrace.conversation(id=uuid4(), agent="my-agent", model="gpt-4o") as chat:
chat.system(persona="You are helpful.", tools=tool_specs)
chat.user("What is Bitcoin price?")
# 2. LLM generation
with chat.llm_gen() as gen:
response = await llm.call(messages)
gen.log(response.items)
# Record token usage
ytrace.record_llm_usage(
provider="openai",
model="gpt-4o",
input_tokens=150,
output_tokens=42,
)
# Record cost
ytrace.record_cost(
category="llm",
currency="USD",
amount=0.0023,
)
# 3. Tool execution
with chat.tools() as t:
results = await t.gather([
{"tool_call_id": "call_1", "tool": search_fn, "params": {"q": "BTC"}},
])
SDK API Reference
Context managers:
conversation(*, id, agent, model, tags=None)— root spanConversationContext.llm_gen()— child span for LLM callConversationContext.tools()— child span for tool batch
Recording functions:
record_cost(*, category, currency, amount, ...)— cost deltarecord_cost_delta(cost: CostDelta)— cost delta from structrecord_llm_usage(*, provider, model, input_tokens, output_tokens, ...)— token usagerecord_tool_usage(usage: ToolUsageDelta)— tool usage
Types:
CostDelta,LlmUsageDelta,ToolUsageDelta— frozen msgspec structsCostCategory—"llm"|"tool"Currency—"USD"
CLI
Collector
Receives OTLP/HTTP JSON and stores to SQLite:
ytrace server --db ./traces.db --port 4318
Configure your OpenTelemetry SDK to export here:
export OTEL_EXPORTER_OTLP_PROTOCOL=http/json
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
Web UI
Serves a trace visualization dashboard:
ytrace ui --db ./traces.db --port 8080
REST API endpoints:
| Method | Path | Description |
|---|---|---|
| GET | /api/health |
Health check |
| GET | /api/conversations |
List conversations (?limit=50&offset=0&agent=...) |
| GET | /api/conversations/{id} |
Single conversation with all spans and events |
| GET | /api/spans/{id} |
Single span detail |
React Component Library
@yuutrace/ui exports pure presentation components. Data is injected via props — no built-in data fetching, no framework lock-in.
import {
ConversationList,
ConversationFlow,
CostSummary,
UsageSummary,
SpanTimeline,
parseConversation,
} from "@yuutrace/ui";
function MyDashboard({ conversation }) {
const { costs, usages } = parseConversation(conversation.spans);
return (
<>
<SpanTimeline spans={conversation.spans} />
<ConversationFlow spans={conversation.spans} />
<CostSummary costs={costs} />
<UsageSummary usages={usages} />
</>
);
}
Components
| Component | Props | Description |
|---|---|---|
ConversationList |
conversations, selectedId?, onSelect? |
Searchable conversation list |
ConversationFlow |
spans |
Waterfall of LLM/tool cards |
LlmCard |
span, usage?, cost? |
LLM call detail card |
ToolCard |
span, usage?, cost? |
Tool call detail card |
CostSummary |
costs |
Cost breakdown by category/model |
UsageSummary |
usages |
Token usage by model |
SpanTimeline |
spans |
Horizontal Gantt chart |
Utilities
parseConversation(spans)— extract typed cost/usage events from raw spansextractCostEvents(span)— cost events from a single spanextractLlmUsageEvents(span)— LLM usage from a single spanextractToolUsageEvents(span)— tool usage from a single span
Development
Prerequisites
- Python >= 3.14
- Node.js >= 20
- uv (Python package manager)
Setup
# Python
uv sync
# React UI
cd ui && npm install
Build the UI
# Build standalone app + copy to _static/ for ytrace ui
bash scripts/build_ui.sh
# Or build separately:
cd ui
npm run build:app # standalone page → dist/app/
npm run build:lib # npm library → dist/lib/
Project Structure
yuutrace/
├── src/yuutrace/
│ ├── __init__.py # public API
│ ├── types.py # CostDelta, LlmUsageDelta, ToolUsageDelta
│ ├── context.py # conversation(), llm_gen(), tools()
│ ├── cost.py # record_cost(), record_cost_delta()
│ ├── usage.py # record_llm_usage(), record_tool_usage()
│ ├── span.py # current_span(), add_event()
│ ├── otel.py # OTEL attribute keys + serialization
│ └── cli/
│ ├── main.py # ytrace CLI entry point
│ ├── server.py # OTLP collector (Starlette)
│ ├── ui.py # REST API + static serving (Starlette)
│ ├── db.py # SQLite persistence
│ └── _static/ # pre-built UI assets
├── ui/ # @yuutrace/ui React package
│ ├── src/
│ │ ├── components/ # ConversationList, LlmCard, etc.
│ │ ├── hooks/ # useTraceData (standalone only)
│ │ ├── pages/ # TracePage
│ │ ├── utils/ # parse.ts
│ │ ├── types.ts
│ │ └── index.ts # library exports
│ ├── vite.config.ts # app build
│ └── vite.config.lib.ts # library build
├── scripts/
│ └── build_ui.sh
└── pyproject.toml
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file yuutrace-0.1.0.tar.gz.
File metadata
- Download URL: yuutrace-0.1.0.tar.gz
- Upload date:
- Size: 81.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
154aad8c75a19a5be88e013d86d89d94b2fc14aa16d901796f6773d7c13ba6c1
|
|
| MD5 |
d6f4c4c0e5f94f95a37f3d1e4c83a0ef
|
|
| BLAKE2b-256 |
a922bbaf320940a5212ec3774f79791a86ba083499b6aaa3f3bdda425074d668
|
Provenance
The following attestation bundles were made for yuutrace-0.1.0.tar.gz:
Publisher:
publish-pypi.yml on yuulabs/yuutrace
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yuutrace-0.1.0.tar.gz -
Subject digest:
154aad8c75a19a5be88e013d86d89d94b2fc14aa16d901796f6773d7c13ba6c1 - Sigstore transparency entry: 927313504
- Sigstore integration time:
-
Permalink:
yuulabs/yuutrace@c06a3e6aad4fd114819fe988050d9175d20ca460 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/yuulabs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@c06a3e6aad4fd114819fe988050d9175d20ca460 -
Trigger Event:
push
-
Statement type:
File details
Details for the file yuutrace-0.1.0-py3-none-any.whl.
File metadata
- Download URL: yuutrace-0.1.0-py3-none-any.whl
- Upload date:
- Size: 87.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
33343a83fc172ee1577010929dd1132050f4fcf23a77e921dc849a2c11eb209a
|
|
| MD5 |
2db9dac52bfd0ccd70c72e062be33d64
|
|
| BLAKE2b-256 |
6bda190944202d0ad9a2f32f11ca389ae0572daf9b2e15e988df4a1005f2235d
|
Provenance
The following attestation bundles were made for yuutrace-0.1.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on yuulabs/yuutrace
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yuutrace-0.1.0-py3-none-any.whl -
Subject digest:
33343a83fc172ee1577010929dd1132050f4fcf23a77e921dc849a2c11eb209a - Sigstore transparency entry: 927313508
- Sigstore integration time:
-
Permalink:
yuulabs/yuutrace@c06a3e6aad4fd114819fe988050d9175d20ca460 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/yuulabs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@c06a3e6aad4fd114819fe988050d9175d20ca460 -
Trigger Event:
push
-
Statement type: