Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Currently inactive
# * @langfuse/maintainers

# Require maintainer review for GitHub configuration changes
.github/ @langfuse/maintainers
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
with:
persist-credentials: false
- name: Install uv and set Python version
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.2"
python-version: "3.13"
Expand All @@ -41,12 +41,12 @@ jobs:
with:
persist-credentials: false
- name: Install uv and set Python version
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.2"
python-version: "3.13"
enable-cache: true # zizmor: ignore[cache-poisoning] CI-only, no artifacts published
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 # zizmor: ignore[cache-poisoning]
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # zizmor: ignore[cache-poisoning]
name: Cache mypy cache
with:
path: ./.mypy_cache
Expand Down Expand Up @@ -82,7 +82,7 @@ jobs:
with:
persist-credentials: false
- name: Install uv and set Python version
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.2"
python-version: ${{ matrix.python-version }}
Expand Down Expand Up @@ -145,7 +145,7 @@ jobs:
with:
persist-credentials: false
- name: Install uv and set Python version
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.2"
python-version: "3.13"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
Expand Down Expand Up @@ -89,6 +89,6 @@ jobs:
exit 1

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
category: "/language:${{matrix.language}}"
2 changes: 1 addition & 1 deletion .github/workflows/dependabot-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3.0.0
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
persist-credentials: false

- name: Install uv and set Python version
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.2"
python-version: "3.12"
Expand Down Expand Up @@ -347,7 +347,7 @@ jobs:

- name: Notify Slack on success
if: success()
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
with:
webhook: ${{ secrets.SLACK_WEBHOOK_RELEASES }}
webhook-type: incoming-webhook
Expand Down Expand Up @@ -431,7 +431,7 @@ jobs:

- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
with:
webhook: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }}
webhook-type: incoming-webhook
Expand Down
1 change: 1 addition & 0 deletions langfuse/_client/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class LangfuseOtelSpanAttributes:

# Internal
AS_ROOT = "langfuse.internal.as_root"
IS_APP_ROOT = "langfuse.internal.is_app_root"

# Experiments
EXPERIMENT_ID = "langfuse.experiment.id"
Expand Down
74 changes: 46 additions & 28 deletions langfuse/_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import backoff
import httpx
from opentelemetry import context as otel_context_api
from opentelemetry import trace as otel_trace_api
from opentelemetry.sdk.trace import ReadableSpan, TracerProvider
from opentelemetry.sdk.trace.export import SpanExporter
Expand Down Expand Up @@ -66,7 +67,9 @@
)
from langfuse._client.propagation import (
PropagatedExperimentAttributes,
_detach_context_token_safely,
_propagate_attributes,
_set_langfuse_trace_id_in_baggage,
)
from langfuse._client.resource_manager import LangfuseResourceManager
from langfuse._client.span import (
Expand Down Expand Up @@ -1178,39 +1181,54 @@ def _start_as_current_otel_span_with_processed_media(
name=name,
end_on_exit=end_on_exit if end_on_exit is not None else True,
) as otel_span:
baggage_token = None

if otel_span.is_recording():
context_with_app_root_claim = _set_langfuse_trace_id_in_baggage(
trace_id=self._get_otel_trace_id(otel_span),
context=otel_context_api.get_current(),
)
baggage_token = otel_context_api.attach(context_with_app_root_claim)
Comment thread
hassiebp marked this conversation as resolved.
Comment thread
hassiebp marked this conversation as resolved.

span_class = self._get_span_class(
as_type or "generation"
) # default was "generation"
common_args = {
"otel_span": otel_span,
"langfuse_client": self,
"environment": self._environment,
"release": self._release,
"input": input,
"output": output,
"metadata": metadata,
"version": version,
"level": level,
"status_message": status_message,
}

if span_class in [
LangfuseGeneration,
LangfuseEmbedding,
]:
common_args.update(
{
"completion_start_time": completion_start_time,
"model": model,
"model_parameters": model_parameters,
"usage_details": usage_details,
"cost_details": cost_details,
"prompt": prompt,
}
)
# For span-like types (span, agent, tool, chain, retriever, evaluator, guardrail), no generation properties needed
try:
common_args = {
"otel_span": otel_span,
"langfuse_client": self,
"environment": self._environment,
"release": self._release,
"input": input,
"output": output,
"metadata": metadata,
"version": version,
"level": level,
"status_message": status_message,
}

if span_class in [
LangfuseGeneration,
LangfuseEmbedding,
]:
common_args.update(
{
"completion_start_time": completion_start_time,
"model": model,
"model_parameters": model_parameters,
"usage_details": usage_details,
"cost_details": cost_details,
"prompt": prompt,
}
)
# For span-like types (span, agent, tool, chain, retriever, evaluator, guardrail), no generation properties needed

yield span_class(**common_args) # type: ignore[arg-type]

yield span_class(**common_args) # type: ignore[arg-type]
finally:
if baggage_token is not None:
_detach_context_token_safely(baggage_token)

def _get_current_otel_span(self) -> Optional[otel_trace_api.Span]:
current_span = otel_trace_api.get_current_span()
Expand Down
35 changes: 35 additions & 0 deletions langfuse/_client/propagation.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ def _get_propagated_attributes_from_context(
# Handle baggage
baggage_entries = baggage.get_all(context=context)
for baggage_key, baggage_value in baggage_entries.items():
if baggage_key == LANGFUSE_TRACE_ID_BAGGAGE_KEY:
continue

if baggage_key.startswith(LANGFUSE_BAGGAGE_PREFIX):
span_key = _get_span_key_from_baggage_key(baggage_key)

Expand Down Expand Up @@ -471,12 +474,44 @@ def _get_propagated_context_key(key: str) -> str:


LANGFUSE_BAGGAGE_PREFIX = "langfuse_"
LANGFUSE_TRACE_ID_BAGGAGE_KEY = "langfuse_trace_id"


def _get_propagated_baggage_key(key: str) -> str:
return f"{LANGFUSE_BAGGAGE_PREFIX}{key}"


def _get_langfuse_trace_id_from_baggage(
context: otel_context_api.Context,
) -> Optional[str]:
value = otel_baggage_api.get_baggage(
name=LANGFUSE_TRACE_ID_BAGGAGE_KEY,
context=context,
)

if value is None:
return None

return str(value).lower()


def _set_langfuse_trace_id_in_baggage(
*,
trace_id: str,
context: otel_context_api.Context,
) -> otel_context_api.Context:
normalized_trace_id = trace_id.lower()

if _get_langfuse_trace_id_from_baggage(context) == normalized_trace_id:
return context

return otel_baggage_api.set_baggage(
name=LANGFUSE_TRACE_ID_BAGGAGE_KEY,
value=normalized_trace_id,
context=context,
)


def _get_span_key_from_baggage_key(key: str) -> Optional[str]:
if not key.startswith(LANGFUSE_BAGGAGE_PREFIX):
return None
Expand Down
Loading
Loading