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)andset_release(release), plussetup_tracing(release=...). The releaseidgoes to Langfuse's trace release field, which the trace list filters on. All three values go to trace metadata asrelease_id,instructions_versionandknowledge_version: these are the namessemantic_evaluation'srun_configreserves for a release service. A reserved key passed inpropagate(metadata=...)overrides the release for that request.LANGFUSE_RELEASEand the CI commit variables apply only while no release is set.
Changed
- Observations recorded with
resume_trace,record_trace_rootandrecord_observationtake the current release, and fall back toLANGFUSE_RELEASEonly while none is set. setup_tracing()always installs its filtering exporter, so thesemantic_tracing.unitslog 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 usingget_langchain_callback()moves to LangChain 1.x with it. fetch_trace()returns aLangfuseTrace, assembled from every observation of the trace:id,name,input,output,metadataandtagscome from the root observation,latencyruns from the first observation's start to the last one's end, andtotal_costsums the observations' costs.observationsare LangfuseObservationV2objects, 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 whenpropagatewraps 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_rootandrecord_observationis JSON-encoded per key, which is how Langfuse 5 reads metadata, and a locally captured trace decodes it back, soget_local_trace()returns the values as they were set. python -m semantic_tracing.pricingreads 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, andEXPERIMENT_ENVIRONMENTnames 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.unpricedlists recent generations no definition priced, the usage keys left at 0, and which definition priced the rest.azure-pricesreads a deployment's type and region withazand lists the retail meters it is billed by, read from the Retail Prices API.planbuilds 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.applywrites it, replacing the project's definition when the price changed, and writes a pricing record for the project to commit.checkre-plans everything in the record against today's prices and writes nothing.verifyrecomputes a trace's costs from its usage. The command calls Langfuse's public REST API withhttpx, so it works across thelangfusereleases 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.docsreads them:list,show <page>andpath.semantic-evaluation'seval-docsreads 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 namesLANGFUSE_BASE_URL, the variable the Langfuse SDK reads first, instead of its deprecated fallbackLANGFUSE_HOST. The docs nameLANGFUSE_BASE_URLthroughout.
[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_timeand.end_time: the span's OTel timestamps, in nanoseconds since the epoch. Both travel throughto_dict()andlocal_trace_from_dict(), sofetch_local_trace_via_httpreturns them; they areNoneon an observation served by a pod on an older release.semantic_evaluation1.4.0 reads them for itswall_time_strace 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 tracekeynames, under that trace's root.record_trace_root(key, name, *, start_time, ...): records the root observationkeynames, 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 in05-tracing-frameworks.md.
Changed
- Model call spans from Agent Framework carry
langfuse.observation.usage_detailswith input and output net of cached input and reasoning, and those two as buckets of their own. Langfuse prefers it overgen_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 olderprompt/*andcompletion/*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-sdkis 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 withTypeError: ... 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_namesanddrop_span_name_prefixesare 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'sworkflow.runandexecutor.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_observationsis 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 indrop_span_nameswhen 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_sizeparameters onsetup_tracing(). When enabled, mirrors every span (bothsemantic_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 whatfind_observations(),as_dict(), andcoerce_text()already read from a Langfuse-fetched trace, so existing pod-side extractors need no changes when fed aLocalTraceinstead.get_local_trace(trace_id)— retrieve a captured trace from the same process, orNoneif 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
TraceContextdataclass (langfuse_trace_id,app_insights_operation_id,app_insights_request_span_id).TRACE_CONTEXT: ContextVar[TraceContext | None]— populated bypropagate()when the current OTel span (the App Insights HTTP server span) is valid at entry time. Resets toNoneon scope exit.get_trace_context()— convenience accessor forTRACE_CONTEXT.get().- All three symbols are exported from the top-level
semantic_tracingpackage.
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 whenpropagate()is entered beforespan()so the App Insights HTTP server span is still current.link_app_insights_to_langfuse()—request_spanandchain_spanare now optional (default: read from context). Whenrequest_spanis omitted the function reads the span stored bypropagate(); whenchain_spanis omitted it callsget_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_prefixis now optional (default: auto-resolve). When omitted, the Langfuse trace-URL prefix is resolved from Langfuse and cached for the process lifetime viafunctools.lru_cache. Passing an explicit string orNonecontinues 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 aDeprecationWarning. Remove the startup call and theapp.state.langfuse_trace_url_prefixplumbing 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. ReadsAPP_INSIGHTS_RESOURCE_IDandAPP_INSIGHTS_TENANT_IDfrom the environment; explicit params override. ReturnsNonewhen either value is absent.link_app_insights_to_langfuse()now also writesapp_insights.query_urlto 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 tolink_app_insights_to_langfuseper request. ReturnsNonewhen Langfuse is unconfigured.link_app_insights_to_langfuse(request_span, chain_span, trace_url_prefix)— stampslangfuse.trace_idandlangfuse.trace_urlon the App Insights request span (enabling App Insights → Langfuse navigation from the portal) andapp_insights.request_span_idon the Langfuse chain observation (enabling Langfuse → App Insights navigation viarequests | 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 toTrue, emits an INFO log line ("Langfuse trace: <url>") via thesemantic_tracinglogger 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 withgen_ai.*attributes (agent operation name, token counts, tool name/call-id, etc.) are no longer dropped bydrop_empty_observations=True. Agent Framework and the OTel gen_ai semantic conventions usegen_ai.operation.name,gen_ai.agent.name, and token-count attributes rather than Langfuse-specificlangfuse.input/langfuse.output, so these spans were previously classified as empty and silently dropped. The fix restoresinvoke_agent, LLM chat, andexecute_toolobservations in Langfuse traces whencapture_sensitive_data=False.
Changed
opentelemetry-sdkdependency capped at<1.41to 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-pythonlowered from>=3.12to>=3.11. All source files already usefrom __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_parametersparameter onupdate_current_step()— stores sampling parameters (temperature, reasoning_effort, etc.) in the Langfuse generation panel.
Fixed
update_current_step()dispatches tolangfuse.update_current_generation()when any ofmodel,usage_details,completion_start_time, ormodel_parametersare present. Previously all calls went toupdate_current_span(), which silently discarded generation-specific fields in Langfuse SDK v4 — causingrecord_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 byspan()that translatesupdate(usage={"input": N, "output": M})intousage_details=before forwarding to the underlying Langfuse observation. Langfuse SDK v4 silently discards the oldusage=kwarg; without this wrapper, token counts fromspan(as_type="generation")blocks were always zero.
Fixed
span()converted to a@contextmanagergenerator so thestart_as_current_observationcall happens inside thewithblock where the OTel context is active.update_current_step()now mergesusage={"input": N, "output": M}intousage_details=before callinglangfuse.update_current_span(), aligning with the Langfuse SDK v4 contract. The public kwarg name is unchanged.setup_tracing()now setsOTEL_SERVICE_NAMEfromservice_namewhen 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 LangfuseBatchSpanProcessor'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— stampssession.idonto spans via OTelon_start()from the_SESSION_IDcontext variable set bypropagate().SuppressionProcessor— stampslangfuse.suppress_exportonto spans viaon_start()whenpropagate(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, defaultTrue) andinput(trace-level I/O viaset_current_trace_io).propagate()is now a true@contextmanager; existingwith propagate():usage is unchanged.propagate()auto-derivestrace_namefrom the currently active OTel span name whentrace_nameis omitted — useful in FastAPI routes.semantic_tracing.unitslogger — emits"Langfuse flush: N spans"perFilteringExporter.export()call for monitoring and alerting.
Fixed
_wrap_langfuse_exporter: newer versions of the OTel SDK makeBatchSpanProcessor.span_exportera read-only@propertybacked byproc._batch_processor._exporter. The wrapper now writes directly to_batch_processor._exporterinstead of assigning to the property, avoiding theAttributeError: property 'span_exporter' … has no setterthat appeared on startup whensample_rate < 1.0ordrop_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.@stepdecorator andupdate_current_step()— function-level tracing with input filtering,as_typecontrol, 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 insemantic_tracing.trace_readerfor pulling structured data out of finished Langfuse traces.