Skip to content

Changelog

[Unreleased]

[0.11.0] - 2026-11-12

Every trace can now carry the release that produced it, so a production answer can be tied to its release: when a reviewed trace becomes a dataset item, when quality is compared per release, and when deciding on a rollback. The release can change while the process runs, as it does when a release service promotes new knowledge without a redeploy, and a trace already running keeps the release it started with.

Added

  • Release(id, instructions_version=None, knowledge_version=None) and set_release(release), plus setup_tracing(release=...). The release id goes to Langfuse's trace release field, which the trace list filters on. All three values go to trace metadata as release_id, instructions_version and knowledge_version: these are the names semantic_evaluation's run_config reserves for a release service. A reserved key passed in propagate(metadata=...) overrides the release for that request. LANGFUSE_RELEASE and the CI commit variables apply only while no release is set.

Changed

  • Observations recorded with resume_trace, record_trace_root and record_observation take the current release, and fall back to LANGFUSE_RELEASE only while none is set.
  • setup_tracing() always installs its filtering exporter, so the semantic_tracing.units log line appears on every flush, including when no filtering option is set.

[0.10.0] - 2026-11-05

Langfuse Cloud stops serving its deprecated public API on 16 November 2026, and this package read traces through it. 0.10.0 moves to Langfuse Python SDK 5, which no longer has those calls, and reads traces through Langfuse's v2 observations API. Upgrade before 16 November: on an older release, reading a trace back (fetch_trace, trace metrics in an offline evaluation, pricing unpriced and verify) stops working that day. Sending traces is unaffected either way.

Changed

  • Requires langfuse>=5.1.0,<6. Langfuse 5's LangChain integration needs LangChain 1.x (langchain-core>=1), so a project using get_langchain_callback() moves to LangChain 1.x with it.
  • fetch_trace() returns a LangfuseTrace, assembled from every observation of the trace: id, name, input, output, metadata and tags come from the root observation, latency runs from the first observation's start to the last one's end, and total_cost sums the observations' costs. observations are Langfuse ObservationV2 objects, with input and output JSON-decoded where they parse. Metadata values over 200 characters arrive truncated, as the v2 API returns them.
  • propagate(input=...) writes the input on the first span started inside the block, which is the trace's root when propagate wraps the turn. A span that sets its own input keeps it. Langfuse 5 has no trace-level input of its own: a trace's input is its root's.
  • Metadata written by resume_trace, record_trace_root and record_observation is JSON-encoded per key, which is how Langfuse 5 reads metadata, and a locally captured trace decodes it back, so get_local_trace() returns the values as they were set.
  • python -m semantic_tracing.pricing reads generations from the v2 observations API. Its commands and output are unchanged.

Added

  • semantic_tracing.trace_reader.read_trace(): one read of a trace, without polling, for a given or the default Langfuse client. select_root() picks a trace's root observation, and EXPERIMENT_ENVIRONMENT names the environment (sdk-experiment) of the span an offline evaluation adds to a finished trace to link it to an experiment. Root selection and latency ignore that span.

[0.9.0] - 2026-10-22

A project can now price its Azure model deployments in Langfuse at Azure's own meters, and keep those prices current. Langfuse puts a cost on a generation only through a model definition that matches it, and an Azure deployment usually has none, or matches Langfuse's definition at the provider's list price, which is too low for a Data Zone or regional deployment. Either way the cost a trace shows, and every evaluation run or experiment that reads it, was wrong without an error. Nothing changes for a project that does not run the command.

Added

  • python -m semantic_tracing.pricing, with six subcommands. unpriced lists recent generations no definition priced, the usage keys left at 0, and which definition priced the rest. azure-prices reads a deployment's type and region with az and lists the retail meters it is billed by, read from the Retail Prices API. plan builds the definition from Langfuse's own definition of the model, every tier priced from its Azure meter or, where Azure has none, scaled by the model's Azure-to-Langfuse ratio, and shows how it differs from the project's. apply writes it, replacing the project's definition when the price changed, and writes a pricing record for the project to commit. check re-plans everything in the record against today's prices and writes nothing. verify recomputes a trace's costs from its usage. The command calls Langfuse's public REST API with httpx, so it works across the langfuse releases this package allows.
  • The page 14-tracing-model-prices.md: how Langfuse prices a generation, where an Azure deployment's price comes from, which name to match, and the command's reference.

[0.8.0] - 2026-10-21

The wheel now carries this SDK's documentation, so an agent or a person in a project that installed it can read the docs of the installed version without a checkout of this repository or a network. A span dropped by name can also hand its attributes to its children, which makes a wrapper span such as Agent Framework's agent span safe to leave out of the trace, one billed observation fewer per model round. Everything is additive: a pod that sets nothing exports what 0.7.0 exported.

Added

  • The wheel carries the documentation and changelog under semantic_tracing/_docs/, as they were when the release was built. python -m semantic_tracing.docs reads them: list, show <page> and path. semantic-evaluation's eval-docs reads them too.
  • setup_tracing(carry_dropped_span_attributes=[...]): attribute keys copied from a span dropped by name or prefix onto each of its children as they start, so what only a wrapper span knew stays in the trace once the wrapper is left out. A child's own value wins, and it chains through consecutive drops. This makes Agent Framework's agent span, which wraps one model call per run, safe to drop with the run's round kept on the calls beneath it; the model call spans hold the usage Langfuse prices, so no tokens are lost or doubled. Nothing is carried unless the pod names a key.

Changed

  • The error setup_tracing(require_langfuse=True) raises on a failed Langfuse authentication names LANGFUSE_BASE_URL, the variable the Langfuse SDK reads first, instead of its deprecated fallback LANGFUSE_HOST. The docs name LANGFUSE_BASE_URL throughout.

[0.7.0] - 2026-10-20

A local trace now says when each span started and ended, so an evaluation run can measure how long a turn took from the local capture, without waiting for Langfuse to ingest the trace.

Added

  • LocalObservation.start_time and .end_time: the span's OTel timestamps, in nanoseconds since the epoch. Both travel through to_dict() and local_trace_from_dict(), so fetch_local_trace_via_http returns them; they are None on an observation served by a pod on an older release. semantic_evaluation 1.4.0 reads them for its wall_time_s trace metric.

[0.6.0] - 2026-10-05

Makes the package usable with current Microsoft Agent Framework releases, and adds a way to record one trace across several requests. On agent_framework 1.19, setup_tracing() broke every agent run: its token usage patch replaced an internal the framework now calls with a keyword the patch did not accept. The patch now forwards whatever it is given and writes token buckets Langfuse prices without counting anything twice. The new helpers cover a unit of work that is one trace but several requests, such as an agent turn that pauses at a tool its client executes and continues in the client's next request.

Added

  • resume_trace(key): every observation started in scope, including Agent Framework's own spans, joins the trace key names, under that trace's root.
  • record_trace_root(key, name, *, start_time, ...): records the root observation key names, with times the caller supplies. Recording it again under the same key updates that observation rather than adding a second root.
  • record_observation(name, *, start_time, end_time, ...): records work that already happened, such as a client's tool call, with its own times.
  • derived_ids(key): the trace id and root observation id a key resolves to.
  • New docs page: 13-tracing-across-requests.md. Token usage on Agent Framework spans is described in 05-tracing-frameworks.md.

Changed

  • Model call spans from Agent Framework carry langfuse.observation.usage_details with input and output net of cached input and reasoning, and those two as buckets of their own. Langfuse prefers it over gen_ai.usage.*, so a cached token is no longer charged at the full input price as well. The usage keys of agent_framework 1.x are read alongside the older prompt/* and completion/* ones.
  • The agent span usage patch is skipped without a warning on framework releases that no longer roll model call usage up onto the agent span.
  • opentelemetry-sdk is accepted up to <2, so the package no longer holds Agent Framework's OpenTelemetry API back at 1.40. Tested with langfuse 4.17 and OpenTelemetry 1.45.

Fixed

  • setup_tracing() no longer makes every agent run fail on agent_framework 1.19 with TypeError: ... unexpected keyword argument 'capture_response_id'.

[0.5.0] - 2026-08-21

Makes drop_span_names usable on a span that has children. Until now a dropped span was refused at the sampler, so it was never created, yet the spans nested inside it had already been parented onto it. Those children still reached Langfuse, pointing at an observation that never arrived, and rendered detached from the rest of the trace. The result was that suppressing a framework's mid-tree bookkeeping span silently flattened the trace it was meant to tidy. Drops now happen on the way out, and every surviving span is reattached to the nearest ancestor that survived with it.

Changed

  • drop_span_names and drop_span_name_prefixes are applied when spans are exported rather than when they are created, and descendants of a dropped span are reattached to the nearest surviving ancestor. A pod can now suppress a span that has children, which is what these parameters are usually wanted for: Microsoft Agent Framework's workflow.run and executor.process <name> sit directly between an application's own spans.
  • Spans matching either list are excluded from the local trace cache as well, so a span named as noise no longer has to be filtered a second time when reading it back through get_local_trace().
  • Dropped spans are now created in-process before being discarded, where previously they were never allocated. No span reaches the network and no Langfuse units are consumed either way.

Fixed

  • drop_empty_observations is documented as not reattaching descendants, which it never did. Emptiness is only known once a span has ended, by which point its children may already have been exported, so there is no point at which the reattachment target is available. Use it for leaf noise and name a span explicitly in drop_span_names when it has children.

[0.4.0] - 2026-07-10

Adds an in-process alternative to polling Langfuse for evaluation reads. Pulling structured signals (tool calls, routing decisions, usage) out of a finished turn today means trace_reader.fetch_trace() — an async round trip against Langfuse's asynchronous ingestion pipeline, whose readiness check can accept a partially-ingested trace on slow, multi-step turns. Local trace capture mirrors the same spans in-process as they end, so a predictor reads a complete trace the instant the turn's OTel spans have all ended — no polling, no ingestion lag.

Added

  • capture_local_traces / local_trace_cache_size parameters on setup_tracing(). When enabled, mirrors every span (both semantic_tracing's own @step/span()/event() observations and any auto-instrumented framework spans, e.g. Microsoft Agent Framework) into a bounded, trace_id-keyed in-process store as each span ends. Default off — opt in per pod.
  • LocalTrace / LocalObservation — an in-process trace/observation pair shaped to match what find_observations(), as_dict(), and coerce_text() already read from a Langfuse-fetched trace, so existing pod-side extractors need no changes when fed a LocalTrace instead.
  • get_local_trace(trace_id) — retrieve a captured trace from the same process, or None if absent or capture was never enabled.
  • local_trace_from_dict(data) / LocalTrace.to_dict() — round-trip (de)serialization for reading a trace from a separate process.
  • fetch_local_trace_via_http(url, *, client=None) — convenience helper for a predictor process to GET a pod-supplied debug endpoint and deserialize the response.
  • New docs page: 12-tracing-local-capture.md.

[0.3.6] - 2026-07-06

Introduces TraceContext — a request-scoped dataclass populated automatically by propagate() that carries the Langfuse trace ID and App Insights operation/request-span IDs in one place. link_app_insights_to_langfuse() now reads both values from context when they are not passed explicitly, so pod code can cross-reference the two backends with a zero-argument call.

Added

  • TraceContext dataclass (langfuse_trace_id, app_insights_operation_id, app_insights_request_span_id).
  • TRACE_CONTEXT: ContextVar[TraceContext | None] — populated by propagate() when the current OTel span (the App Insights HTTP server span) is valid at entry time. Resets to None on scope exit.
  • get_trace_context() — convenience accessor for TRACE_CONTEXT.get().
  • All three symbols are exported from the top-level semantic_tracing package.

Changed

  • propagate() now captures the currently active OTel span at entry and stores it in a private ContextVar (_REQ_SPAN_CV). The best results come when propagate() is entered before span() so the App Insights HTTP server span is still current.
  • link_app_insights_to_langfuse() — request_span and chain_span are now optional (default: read from context). When request_span is omitted the function reads the span stored by propagate(); when chain_span is omitted it calls get_client().update_current_span(metadata=...) on the active Langfuse observation. All existing positional callers remain unaffected.

[0.3.5] - 2026-07-06

Eliminates the startup plumbing that pods previously needed to wire up the Langfuse trace-URL prefix. link_app_insights_to_langfuse now resolves and caches the prefix automatically on its first call, so pods no longer need to call resolve_langfuse_trace_url_prefix() at startup or store the result on app.state.

Changed

  • link_app_insights_to_langfuse(request_span, chain_span) — trace_url_prefix is now optional (default: auto-resolve). When omitted, the Langfuse trace-URL prefix is resolved from Langfuse and cached for the process lifetime via functools.lru_cache. Passing an explicit string or None continues to work unchanged, so existing callers need no modification until they are ready to simplify.

Deprecated

  • resolve_langfuse_trace_url_prefix() — no longer needed; retained for backward compatibility. Calling it emits a DeprecationWarning. Remove the startup call and the app.state.langfuse_trace_url_prefix plumbing from pods that use it.

[0.3.4] - 2026-07-03

Adds a one-click Log Analytics deep-link URL to every Langfuse chain observation, so engineers can jump from a Langfuse trace directly to the matching App Insights request row without manually constructing the KQL query.

Added

  • get_app_insights_query_url(kql, *, resource_id=None, tenant=None) -> str | None — builds an Azure Portal Log Analytics URL with the KQL query embedded as a gzip-compressed, base64-encoded payload. Reads APP_INSIGHTS_RESOURCE_ID and APP_INSIGHTS_TENANT_ID from the environment; explicit params override. Returns None when either value is absent.
  • link_app_insights_to_langfuse() now also writes app_insights.query_url to the Langfuse chain observation metadata when both env vars are configured. Pods that omit them see no change in behaviour.

[0.3.3] - 2026-07-03

Extracts the App Insights ↔ Langfuse cross-reference pattern into the SDK so any pod can wire bidirectional navigation between the two observability backends without reimplementing the logic.

Added

  • resolve_langfuse_trace_url_prefix() -> str | None — resolves the project-scoped Langfuse trace-URL prefix at pod startup by making a single API call. Pods cache the result and pass it to link_app_insights_to_langfuse per request. Returns None when Langfuse is unconfigured.
  • link_app_insights_to_langfuse(request_span, chain_span, trace_url_prefix) — stamps langfuse.trace_id and langfuse.trace_url on the App Insights request span (enabling App Insights → Langfuse navigation from the portal) and app_insights.request_span_id on the Langfuse chain observation (enabling Langfuse → App Insights navigation via requests | where id == '<span_id>').

[0.3.2] - 2026-07-03

Adds opt-in Langfuse trace URL logging to propagate(), and introduces the App Insights / Langfuse cross-reference reference documentation.

Added

  • propagate(log_trace_url=False) — when set to True, emits an INFO log line ("Langfuse trace: <url>") via the semantic_tracing logger when the propagate scope exits. The URL is resolved after the scope body completes, so the trace ID is always available. Useful for local development and server log correlation.

[0.3.1] - 2026-06-30

Two fixes for deployments running Microsoft Agent Framework with capture_sensitive_data=False.

Fixed

  • FilteringExporter._is_empty(): spans with gen_ai.* attributes (agent operation name, token counts, tool name/call-id, etc.) are no longer dropped by drop_empty_observations=True. Agent Framework and the OTel gen_ai semantic conventions use gen_ai.operation.name, gen_ai.agent.name, and token-count attributes rather than Langfuse-specific langfuse.input/langfuse.output, so these spans were previously classified as empty and silently dropped. The fix restores invoke_agent, LLM chat, and execute_tool observations in Langfuse traces when capture_sensitive_data=False.

Changed

  • opentelemetry-sdk dependency capped at <1.41 to avoid a breaking serialisation change introduced in 1.41 that causes span attribute encoding errors at export time.

[0.3.0] - 2026-06-30

Broadens Python compatibility to include Python 3.11. Callers on 3.11 can now install semantic-tracing without any application code changes.

Changed

  • requires-python lowered from >=3.12 to >=3.11. All source files already use from __future__ import annotations; no 3.12-specific syntax was in use, so the previous constraint was unnecessarily strict.

[0.2.2] - 2026-06-28

Fixes update_current_step() silently dropping generation fields (model, usage_details, completion_start_time, model_parameters) when called from any context. The function now dispatches to update_current_generation() when any generation-specific kwarg is present, routing through the correct Langfuse SDK v4 path. This unblocks record_first_token() and enables consistent use of update_current_step() from both @step and span() contexts.

Added

  • model_parameters parameter on update_current_step() — stores sampling parameters (temperature, reasoning_effort, etc.) in the Langfuse generation panel.

Fixed

  • update_current_step() dispatches to langfuse.update_current_generation() when any of model, usage_details, completion_start_time, or model_parameters are present. Previously all calls went to update_current_span(), which silently discarded generation-specific fields in Langfuse SDK v4 — causing record_first_token() to appear to succeed while TTFT was never recorded.

[0.2.1] - 2026-06-26

Fixes silent token-count drops when using span() or update_current_step() with Langfuse SDK v4, and corrects service.name propagation to OTel.

Added

  • _SpanWrapper — thin proxy returned by span() that translates update(usage={"input": N, "output": M}) into usage_details= before forwarding to the underlying Langfuse observation. Langfuse SDK v4 silently discards the old usage= kwarg; without this wrapper, token counts from span(as_type="generation") blocks were always zero.

Fixed

  • span() converted to a @contextmanager generator so the start_as_current_observation call happens inside the with block where the OTel context is active.
  • update_current_step() now merges usage={"input": N, "output": M} into usage_details= before calling langfuse.update_current_span(), aligning with the Langfuse SDK v4 contract. The public kwarg name is unchanged.
  • setup_tracing() now sets OTEL_SERVICE_NAME from service_name when the env var is absent, so the service label appears correctly in Langfuse instead of "unknown_service".

[0.2.0] - 2026-06-24

Unit consumption controls and session-based sampling for the Langfuse exporter pipeline.

Added

  • FilteringExporter — wraps the Langfuse BatchSpanProcessor's inner exporter; applies suppression (E), null-content filter (B), session-consistent sampling (A), and unit-count logging (D) before forwarding spans to Langfuse.
  • SessionIdStampingProcessor — stamps session.id onto spans via OTel on_start() from the _SESSION_ID context variable set by propagate().
  • SuppressionProcessor — stamps langfuse.suppress_export onto spans via on_start() when propagate(langfuse_export=False) is active.
  • _should_sample(key, rate) — deterministic, stateless, MD5-based sampling predicate; same algorithm as the LiteLLM callback so sampling is consistent across the proxy and the SDK layer.
  • setup_tracing() new parameters: sample_rate, always_send_errors, drop_empty_observations, clear_default_drop_prefixes.
  • propagate() new parameters: langfuse_export (bool, default True) and input (trace-level I/O via set_current_trace_io).
  • propagate() is now a true @contextmanager; existing with propagate(): usage is unchanged.
  • propagate() auto-derives trace_name from the currently active OTel span name when trace_name is omitted — useful in FastAPI routes.
  • semantic_tracing.units logger — emits "Langfuse flush: N spans" per FilteringExporter.export() call for monitoring and alerting.

Fixed

  • _wrap_langfuse_exporter: newer versions of the OTel SDK make BatchSpanProcessor.span_exporter a read-only @property backed by proc._batch_processor._exporter. The wrapper now writes directly to _batch_processor._exporter instead of assigning to the property, avoiding the AttributeError: property 'span_exporter' … has no setter that appeared on startup when sample_rate < 1.0 or drop_empty_observations=True.

[0.1.0] - 2026-06-23

First standalone release. semantic-tracing provides Langfuse tracing helpers as a lightweight package (~3 runtime dependencies) independent of the full evaluation stack. Pods that only need observability can install this package without pulling in evaluators, datasets, or orchestration.

Added

  • setup_tracing() — one-time OTel/Langfuse provider initialization; configures the global TracerProvider with the Langfuse OTLP exporter and optional span-name drop filters.
  • @step decorator and update_current_step() — function-level tracing with input filtering, as_type control, and generation metadata support (model, usage, completion_start_time, usage_details).
  • span(), event(), propagate() — context-manager tracing primitives for block-level spans, fire-and-forget markers, and sub-call context injection.
  • get_trace_id(), get_observation_id(), flush(), get_tracer() — trace lifecycle utilities.
  • get_langchain_callback() — lazy-loaded LangChain/LangGraph callback adapter.
  • InputFilter — global input-filtering configuration for controlling what content is captured in span inputs.
  • record_first_token() — TTFT timestamp helper (Level 2).
  • wrap_azure_stream() — async streaming wrapper for OpenAI/Azure with automatic TTFT recording and usage extraction (Level 3).
  • fetch_trace(), find_observations(), as_dict(), coerce_text() — read-side helpers in semantic_tracing.trace_reader for pulling structured data out of finished Langfuse traces.