diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 62b490f33..1d2bfd294 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -21,6 +21,13 @@ poetry install --all-extras
poetry run pre-commit install
```
+### Type Checking
+
+To run type checking on the langfuse package, run:
+```sh
+poetry run mypy langfuse --no-error-summary
+```
+
### Tests
#### Setup
diff --git a/langfuse/__init__.py b/langfuse/__init__.py
index 5d8da4cc2..3449e851f 100644
--- a/langfuse/__init__.py
+++ b/langfuse/__init__.py
@@ -2,9 +2,21 @@
from ._client import client as _client_module
from ._client.attributes import LangfuseOtelSpanAttributes
+from ._client.constants import ObservationTypeLiteral
from ._client.get_client import get_client
from ._client.observe import observe
-from ._client.span import LangfuseEvent, LangfuseGeneration, LangfuseSpan
+from ._client.span import (
+ LangfuseEvent,
+ LangfuseGeneration,
+ LangfuseSpan,
+ LangfuseAgent,
+ LangfuseTool,
+ LangfuseChain,
+ LangfuseEmbedding,
+ LangfuseEvaluator,
+ LangfuseRetriever,
+ LangfuseGuardrail,
+)
Langfuse = _client_module.Langfuse
@@ -12,8 +24,16 @@
"Langfuse",
"get_client",
"observe",
+ "ObservationTypeLiteral",
"LangfuseSpan",
"LangfuseGeneration",
"LangfuseEvent",
"LangfuseOtelSpanAttributes",
+ "LangfuseAgent",
+ "LangfuseTool",
+ "LangfuseChain",
+ "LangfuseEmbedding",
+ "LangfuseEvaluator",
+ "LangfuseRetriever",
+ "LangfuseGuardrail",
]
diff --git a/langfuse/_client/attributes.py b/langfuse/_client/attributes.py
index 0438b959a..5ae81000c 100644
--- a/langfuse/_client/attributes.py
+++ b/langfuse/_client/attributes.py
@@ -14,6 +14,11 @@
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional, Union
+from langfuse._client.constants import (
+ ObservationTypeGenerationLike,
+ ObservationTypeSpanLike,
+)
+
from langfuse._utils.serializer import EventSerializer
from langfuse.model import PromptClient
from langfuse.types import MapValue, SpanLevel
@@ -93,9 +98,12 @@ def create_span_attributes(
level: Optional[SpanLevel] = None,
status_message: Optional[str] = None,
version: Optional[str] = None,
+ observation_type: Optional[
+ Union[ObservationTypeSpanLike, Literal["event"]]
+ ] = "span",
) -> dict:
attributes = {
- LangfuseOtelSpanAttributes.OBSERVATION_TYPE: "span",
+ LangfuseOtelSpanAttributes.OBSERVATION_TYPE: observation_type,
LangfuseOtelSpanAttributes.OBSERVATION_LEVEL: level,
LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE: status_message,
LangfuseOtelSpanAttributes.VERSION: version,
@@ -122,9 +130,10 @@ def create_generation_attributes(
usage_details: Optional[Dict[str, int]] = None,
cost_details: Optional[Dict[str, float]] = None,
prompt: Optional[PromptClient] = None,
+ observation_type: Optional[ObservationTypeGenerationLike] = "generation",
) -> dict:
attributes = {
- LangfuseOtelSpanAttributes.OBSERVATION_TYPE: "generation",
+ LangfuseOtelSpanAttributes.OBSERVATION_TYPE: observation_type,
LangfuseOtelSpanAttributes.OBSERVATION_LEVEL: level,
LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE: status_message,
LangfuseOtelSpanAttributes.VERSION: version,
diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py
index 6aba529fc..18eca4bfd 100644
--- a/langfuse/_client/client.py
+++ b/langfuse/_client/client.py
@@ -5,12 +5,23 @@
import logging
import os
+import warnings
import re
import urllib.parse
from datetime import datetime
from hashlib import sha256
from time import time_ns
-from typing import Any, Dict, List, Literal, Optional, Union, cast, overload
+from typing import (
+ Any,
+ Dict,
+ List,
+ Literal,
+ Optional,
+ Union,
+ Type,
+ cast,
+ overload,
+)
import backoff
import httpx
@@ -36,11 +47,25 @@
LANGFUSE_TRACING_ENABLED,
LANGFUSE_TRACING_ENVIRONMENT,
)
+from langfuse._client.constants import (
+ ObservationTypeLiteral,
+ ObservationTypeLiteralNoEvent,
+ ObservationTypeGenerationLike,
+ ObservationTypeSpanLike,
+ get_observation_types_list,
+)
from langfuse._client.resource_manager import LangfuseResourceManager
from langfuse._client.span import (
LangfuseEvent,
LangfuseGeneration,
LangfuseSpan,
+ LangfuseAgent,
+ LangfuseTool,
+ LangfuseChain,
+ LangfuseRetriever,
+ LangfuseEvaluator,
+ LangfuseEmbedding,
+ LangfuseGuardrail,
)
from langfuse._utils import _get_timestamp
from langfuse._utils.parse_error import handle_fern_exception
@@ -297,39 +322,10 @@ def start_span(
span.end()
```
"""
- if trace_context:
- trace_id = trace_context.get("trace_id", None)
- parent_span_id = trace_context.get("parent_span_id", None)
-
- if trace_id:
- remote_parent_span = self._create_remote_parent_span(
- trace_id=trace_id, parent_span_id=parent_span_id
- )
-
- with otel_trace_api.use_span(
- cast(otel_trace_api.Span, remote_parent_span)
- ):
- otel_span = self._otel_tracer.start_span(name=name)
- otel_span.set_attribute(LangfuseOtelSpanAttributes.AS_ROOT, True)
-
- return LangfuseSpan(
- otel_span=otel_span,
- langfuse_client=self,
- environment=self._environment,
- input=input,
- output=output,
- metadata=metadata,
- version=version,
- level=level,
- status_message=status_message,
- )
-
- otel_span = self._otel_tracer.start_span(name=name)
-
- return LangfuseSpan(
- otel_span=otel_span,
- langfuse_client=self,
- environment=self._environment,
+ return self.start_observation(
+ trace_context=trace_context,
+ name=name,
+ as_type="span",
input=input,
output=output,
metadata=metadata,
@@ -386,52 +382,137 @@ def start_as_current_span(
child_span.update(output="sub-result")
```
"""
- if trace_context:
- trace_id = trace_context.get("trace_id", None)
- parent_span_id = trace_context.get("parent_span_id", None)
+ return self.start_as_current_observation(
+ trace_context=trace_context,
+ name=name,
+ as_type="span",
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ end_on_exit=end_on_exit,
+ )
- if trace_id:
- remote_parent_span = self._create_remote_parent_span(
- trace_id=trace_id, parent_span_id=parent_span_id
- )
+ @overload
+ def start_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["generation"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ ) -> LangfuseGeneration: ...
- return cast(
- _AgnosticContextManager[LangfuseSpan],
- self._create_span_with_parent_context(
- as_type="span",
- name=name,
- remote_parent_span=remote_parent_span,
- parent=None,
- end_on_exit=end_on_exit,
- input=input,
- output=output,
- metadata=metadata,
- version=version,
- level=level,
- status_message=status_message,
- ),
- )
+ @overload
+ def start_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["span"] = "span",
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> LangfuseSpan: ...
- return cast(
- _AgnosticContextManager[LangfuseSpan],
- self._start_as_current_otel_span_with_processed_media(
- as_type="span",
- name=name,
- end_on_exit=end_on_exit,
- input=input,
- output=output,
- metadata=metadata,
- version=version,
- level=level,
- status_message=status_message,
- ),
- )
+ @overload
+ def start_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["agent"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> LangfuseAgent: ...
- def start_generation(
+ @overload
+ def start_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["tool"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> LangfuseTool: ...
+
+ @overload
+ def start_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["chain"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> LangfuseChain: ...
+
+ @overload
+ def start_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["retriever"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> LangfuseRetriever: ...
+
+ @overload
+ def start_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["evaluator"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> LangfuseEvaluator: ...
+
+ @overload
+ def start_observation(
self,
*,
trace_context: Optional[TraceContext] = None,
name: str,
+ as_type: Literal["embedding"],
input: Optional[Any] = None,
output: Optional[Any] = None,
metadata: Optional[Any] = None,
@@ -444,56 +525,76 @@ def start_generation(
usage_details: Optional[Dict[str, int]] = None,
cost_details: Optional[Dict[str, float]] = None,
prompt: Optional[PromptClient] = None,
- ) -> LangfuseGeneration:
- """Create a new generation span for model generations.
+ ) -> LangfuseEmbedding: ...
- This method creates a specialized span for tracking model generations.
- It includes additional fields specific to model generations such as model name,
- token usage, and cost details.
+ @overload
+ def start_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["guardrail"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> LangfuseGuardrail: ...
- The created generation span will be the child of the current span in the context.
+ def start_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: ObservationTypeLiteralNoEvent = "span",
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ ) -> Union[
+ LangfuseSpan,
+ LangfuseGeneration,
+ LangfuseAgent,
+ LangfuseTool,
+ LangfuseChain,
+ LangfuseRetriever,
+ LangfuseEvaluator,
+ LangfuseEmbedding,
+ LangfuseGuardrail,
+ ]:
+ """Create a new observation of the specified type.
+
+ This method creates a new observation but does not set it as the current span in the
+ context. To create and use an observation within a context, use start_as_current_observation().
Args:
trace_context: Optional context for connecting to an existing trace
- name: Name of the generation operation
- input: Input data for the model (e.g., prompts)
- output: Output from the model (e.g., completions)
- metadata: Additional metadata to associate with the generation
- version: Version identifier for the model or component
- level: Importance level of the generation (info, warning, error)
- status_message: Optional status message for the generation
- completion_start_time: When the model started generating the response
- model: Name/identifier of the AI model used (e.g., "gpt-4")
- model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
- usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
- cost_details: Cost information for the model call
- prompt: Associated prompt template from Langfuse prompt management
+ name: Name of the observation
+ as_type: Type of observation to create (defaults to "span")
+ input: Input data for the operation
+ output: Output data from the operation
+ metadata: Additional metadata to associate with the observation
+ version: Version identifier for the code or component
+ level: Importance level of the observation
+ status_message: Optional status message for the observation
+ completion_start_time: When the model started generating (for generation types)
+ model: Name/identifier of the AI model used (for generation types)
+ model_parameters: Parameters used for the model (for generation types)
+ usage_details: Token usage information (for generation types)
+ cost_details: Cost information (for generation types)
+ prompt: Associated prompt template (for generation types)
Returns:
- A LangfuseGeneration object that must be ended with .end() when complete
-
- Example:
- ```python
- generation = langfuse.start_generation(
- name="answer-generation",
- model="gpt-4",
- input={"prompt": "Explain quantum computing"},
- model_parameters={"temperature": 0.7}
- )
- try:
- # Call model API
- response = llm.generate(...)
-
- generation.update(
- output=response.text,
- usage_details={
- "prompt_tokens": response.usage.prompt_tokens,
- "completion_tokens": response.usage.completion_tokens
- }
- )
- finally:
- generation.end()
- ```
+ An observation object of the appropriate type that must be ended with .end()
"""
if trace_context:
trace_id = trace_context.get("trace_id", None)
@@ -510,9 +611,9 @@ def start_generation(
otel_span = self._otel_tracer.start_span(name=name)
otel_span.set_attribute(LangfuseOtelSpanAttributes.AS_ROOT, True)
- return LangfuseGeneration(
+ return self._create_observation_from_otel_span(
otel_span=otel_span,
- langfuse_client=self,
+ as_type=as_type,
input=input,
output=output,
metadata=metadata,
@@ -529,9 +630,9 @@ def start_generation(
otel_span = self._otel_tracer.start_span(name=name)
- return LangfuseGeneration(
+ return self._create_observation_from_otel_span(
otel_span=otel_span,
- langfuse_client=self,
+ as_type=as_type,
input=input,
output=output,
metadata=metadata,
@@ -546,11 +647,11 @@ def start_generation(
prompt=prompt,
)
- def start_as_current_generation(
+ def _create_observation_from_otel_span(
self,
*,
- trace_context: Optional[TraceContext] = None,
- name: str,
+ otel_span: otel_trace_api.Span,
+ as_type: ObservationTypeLiteralNoEvent,
input: Optional[Any] = None,
output: Optional[Any] = None,
metadata: Optional[Any] = None,
@@ -563,34 +664,202 @@ def start_as_current_generation(
usage_details: Optional[Dict[str, int]] = None,
cost_details: Optional[Dict[str, float]] = None,
prompt: Optional[PromptClient] = None,
- end_on_exit: Optional[bool] = None,
- ) -> _AgnosticContextManager[LangfuseGeneration]:
- """Create a new generation span and set it as the current span in a context manager.
-
- This method creates a specialized span for model generations and sets it as the
- current span within a context manager. Use this method with a 'with' statement to
- automatically handle the generation span lifecycle within a code block.
-
- The created generation span will be the child of the current span in the context.
-
- Args:
- trace_context: Optional context for connecting to an existing trace
- name: Name of the generation operation
- input: Input data for the model (e.g., prompts)
- output: Output from the model (e.g., completions)
- metadata: Additional metadata to associate with the generation
- version: Version identifier for the model or component
- level: Importance level of the generation (info, warning, error)
- status_message: Optional status message for the generation
- completion_start_time: When the model started generating the response
- model: Name/identifier of the AI model used (e.g., "gpt-4")
- model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
- usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
- cost_details: Cost information for the model call
- prompt: Associated prompt template from Langfuse prompt management
- end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks.
-
- Returns:
+ ) -> Union[
+ LangfuseSpan,
+ LangfuseGeneration,
+ LangfuseAgent,
+ LangfuseTool,
+ LangfuseChain,
+ LangfuseRetriever,
+ LangfuseEvaluator,
+ LangfuseEmbedding,
+ LangfuseGuardrail,
+ ]:
+ """Create the appropriate observation type from an OTEL span."""
+ if as_type in get_observation_types_list(ObservationTypeGenerationLike):
+ observation_class = self._get_span_class(as_type)
+ # Type ignore to prevent overloads of internal _get_span_class function,
+ # issue is that LangfuseEvent could be returned and that classes have diff. args
+ return observation_class( # type: ignore[return-value,call-arg]
+ otel_span=otel_span,
+ langfuse_client=self,
+ environment=self._environment,
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ completion_start_time=completion_start_time,
+ model=model,
+ model_parameters=model_parameters,
+ usage_details=usage_details,
+ cost_details=cost_details,
+ prompt=prompt,
+ )
+ else:
+ # For other types (e.g. span, guardrail), create appropriate class without generation properties
+ observation_class = self._get_span_class(as_type)
+ # Type ignore to prevent overloads of internal _get_span_class function,
+ # issue is that LangfuseEvent could be returned and that classes have diff. args
+ return observation_class( # type: ignore[return-value,call-arg]
+ otel_span=otel_span,
+ langfuse_client=self,
+ environment=self._environment,
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ )
+ # span._observation_type = as_type
+ # span._otel_span.set_attribute("langfuse.observation.type", as_type)
+ # return span
+
+ def start_generation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ ) -> LangfuseGeneration:
+ """[DEPRECATED] Create a new generation span for model generations.
+
+ DEPRECATED: This method is deprecated and will be removed in a future version.
+ Use start_observation(as_type='generation') instead.
+
+ This method creates a specialized span for tracking model generations.
+ It includes additional fields specific to model generations such as model name,
+ token usage, and cost details.
+
+ The created generation span will be the child of the current span in the context.
+
+ Args:
+ trace_context: Optional context for connecting to an existing trace
+ name: Name of the generation operation
+ input: Input data for the model (e.g., prompts)
+ output: Output from the model (e.g., completions)
+ metadata: Additional metadata to associate with the generation
+ version: Version identifier for the model or component
+ level: Importance level of the generation (info, warning, error)
+ status_message: Optional status message for the generation
+ completion_start_time: When the model started generating the response
+ model: Name/identifier of the AI model used (e.g., "gpt-4")
+ model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
+ usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
+ cost_details: Cost information for the model call
+ prompt: Associated prompt template from Langfuse prompt management
+
+ Returns:
+ A LangfuseGeneration object that must be ended with .end() when complete
+
+ Example:
+ ```python
+ generation = langfuse.start_generation(
+ name="answer-generation",
+ model="gpt-4",
+ input={"prompt": "Explain quantum computing"},
+ model_parameters={"temperature": 0.7}
+ )
+ try:
+ # Call model API
+ response = llm.generate(...)
+
+ generation.update(
+ output=response.text,
+ usage_details={
+ "prompt_tokens": response.usage.prompt_tokens,
+ "completion_tokens": response.usage.completion_tokens
+ }
+ )
+ finally:
+ generation.end()
+ ```
+ """
+ warnings.warn(
+ "start_generation is deprecated and will be removed in a future version. "
+ "Use start_observation(as_type='generation') instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.start_observation(
+ trace_context=trace_context,
+ name=name,
+ as_type="generation",
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ completion_start_time=completion_start_time,
+ model=model,
+ model_parameters=model_parameters,
+ usage_details=usage_details,
+ cost_details=cost_details,
+ prompt=prompt,
+ )
+
+ def start_as_current_generation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseGeneration]:
+ """[DEPRECATED] Create a new generation span and set it as the current span in a context manager.
+
+ DEPRECATED: This method is deprecated and will be removed in a future version.
+ Use start_as_current_observation(as_type='generation') instead.
+
+ This method creates a specialized span for model generations and sets it as the
+ current span within a context manager. Use this method with a 'with' statement to
+ automatically handle the generation span lifecycle within a code block.
+
+ The created generation span will be the child of the current span in the context.
+
+ Args:
+ trace_context: Optional context for connecting to an existing trace
+ name: Name of the generation operation
+ input: Input data for the model (e.g., prompts)
+ output: Output from the model (e.g., completions)
+ metadata: Additional metadata to associate with the generation
+ version: Version identifier for the model or component
+ level: Importance level of the generation (info, warning, error)
+ status_message: Optional status message for the generation
+ completion_start_time: When the model started generating the response
+ model: Name/identifier of the AI model used (e.g., "gpt-4")
+ model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
+ usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
+ cost_details: Cost information for the model call
+ prompt: Associated prompt template from Langfuse prompt management
+ end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks.
+
+ Returns:
A context manager that yields a LangfuseGeneration
Example:
@@ -613,58 +882,452 @@ def start_as_current_generation(
)
```
"""
- if trace_context:
- trace_id = trace_context.get("trace_id", None)
- parent_span_id = trace_context.get("parent_span_id", None)
+ warnings.warn(
+ "start_as_current_generation is deprecated and will be removed in a future version. "
+ "Use start_as_current_observation(as_type='generation') instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.start_as_current_observation(
+ trace_context=trace_context,
+ name=name,
+ as_type="generation",
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ completion_start_time=completion_start_time,
+ model=model,
+ model_parameters=model_parameters,
+ usage_details=usage_details,
+ cost_details=cost_details,
+ prompt=prompt,
+ end_on_exit=end_on_exit,
+ )
- if trace_id:
- remote_parent_span = self._create_remote_parent_span(
- trace_id=trace_id, parent_span_id=parent_span_id
- )
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["generation"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseGeneration]: ...
- return cast(
- _AgnosticContextManager[LangfuseGeneration],
- self._create_span_with_parent_context(
- as_type="generation",
- name=name,
- remote_parent_span=remote_parent_span,
- parent=None,
- end_on_exit=end_on_exit,
- input=input,
- output=output,
- metadata=metadata,
- version=version,
- level=level,
- status_message=status_message,
- completion_start_time=completion_start_time,
- model=model,
- model_parameters=model_parameters,
- usage_details=usage_details,
- cost_details=cost_details,
- prompt=prompt,
- ),
- )
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["span"] = "span",
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseSpan]: ...
- return cast(
- _AgnosticContextManager[LangfuseGeneration],
- self._start_as_current_otel_span_with_processed_media(
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["agent"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseAgent]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["tool"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseTool]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["chain"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseChain]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["retriever"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseRetriever]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["evaluator"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseEvaluator]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["embedding"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseEmbedding]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: Literal["guardrail"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> _AgnosticContextManager[LangfuseGuardrail]: ...
+
+ def start_as_current_observation(
+ self,
+ *,
+ trace_context: Optional[TraceContext] = None,
+ name: str,
+ as_type: ObservationTypeLiteralNoEvent = "span",
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ end_on_exit: Optional[bool] = None,
+ ) -> Union[
+ _AgnosticContextManager[LangfuseGeneration],
+ _AgnosticContextManager[LangfuseSpan],
+ _AgnosticContextManager[LangfuseAgent],
+ _AgnosticContextManager[LangfuseTool],
+ _AgnosticContextManager[LangfuseChain],
+ _AgnosticContextManager[LangfuseRetriever],
+ _AgnosticContextManager[LangfuseEvaluator],
+ _AgnosticContextManager[LangfuseEmbedding],
+ _AgnosticContextManager[LangfuseGuardrail],
+ ]:
+ """Create a new observation and set it as the current span in a context manager.
+
+ This method creates a new observation of the specified type and sets it as the
+ current span within a context manager. Use this method with a 'with' statement to
+ automatically handle the observation lifecycle within a code block.
+
+ The created observation will be the child of the current span in the context.
+
+ Args:
+ trace_context: Optional context for connecting to an existing trace
+ name: Name of the observation (e.g., function or operation name)
+ as_type: Type of observation to create (defaults to "span")
+ input: Input data for the operation (can be any JSON-serializable object)
+ output: Output data from the operation (can be any JSON-serializable object)
+ metadata: Additional metadata to associate with the observation
+ version: Version identifier for the code or component
+ level: Importance level of the observation (info, warning, error)
+ status_message: Optional status message for the observation
+ end_on_exit (default: True): Whether to end the span automatically when leaving the context manager. If False, the span must be manually ended to avoid memory leaks.
+
+ The following parameters are available when as_type is: "generation" or "embedding".
+ completion_start_time: When the model started generating the response
+ model: Name/identifier of the AI model used (e.g., "gpt-4")
+ model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
+ usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
+ cost_details: Cost information for the model call
+ prompt: Associated prompt template from Langfuse prompt management
+
+ Returns:
+ A context manager that yields the appropriate observation type based on as_type
+
+ Example:
+ ```python
+ # Create a span
+ with langfuse.start_as_current_observation(name="process-query", as_type="span") as span:
+ # Do work
+ result = process_data()
+ span.update(output=result)
+
+ # Create a child span automatically
+ with span.start_as_current_span(name="sub-operation") as child_span:
+ # Do sub-operation work
+ child_span.update(output="sub-result")
+
+ # Create a tool observation
+ with langfuse.start_as_current_observation(name="web-search", as_type="tool") as tool:
+ # Do tool work
+ results = search_web(query)
+ tool.update(output=results)
+
+ # Create a generation observation
+ with langfuse.start_as_current_observation(
+ name="answer-generation",
as_type="generation",
- name=name,
- end_on_exit=end_on_exit,
- input=input,
- output=output,
- metadata=metadata,
- version=version,
- level=level,
- status_message=status_message,
- completion_start_time=completion_start_time,
- model=model,
- model_parameters=model_parameters,
- usage_details=usage_details,
- cost_details=cost_details,
- prompt=prompt,
- ),
+ model="gpt-4"
+ ) as generation:
+ # Generate answer
+ response = llm.generate(...)
+ generation.update(output=response)
+ ```
+ """
+ if as_type in get_observation_types_list(ObservationTypeGenerationLike):
+ if trace_context:
+ trace_id = trace_context.get("trace_id", None)
+ parent_span_id = trace_context.get("parent_span_id", None)
+
+ if trace_id:
+ remote_parent_span = self._create_remote_parent_span(
+ trace_id=trace_id, parent_span_id=parent_span_id
+ )
+
+ return cast(
+ Union[
+ _AgnosticContextManager[LangfuseGeneration],
+ _AgnosticContextManager[LangfuseEmbedding],
+ ],
+ self._create_span_with_parent_context(
+ as_type=as_type,
+ name=name,
+ remote_parent_span=remote_parent_span,
+ parent=None,
+ end_on_exit=end_on_exit,
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ completion_start_time=completion_start_time,
+ model=model,
+ model_parameters=model_parameters,
+ usage_details=usage_details,
+ cost_details=cost_details,
+ prompt=prompt,
+ ),
+ )
+
+ return cast(
+ Union[
+ _AgnosticContextManager[LangfuseGeneration],
+ _AgnosticContextManager[LangfuseEmbedding],
+ ],
+ self._start_as_current_otel_span_with_processed_media(
+ as_type=as_type,
+ name=name,
+ end_on_exit=end_on_exit,
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ completion_start_time=completion_start_time,
+ model=model,
+ model_parameters=model_parameters,
+ usage_details=usage_details,
+ cost_details=cost_details,
+ prompt=prompt,
+ ),
+ )
+
+ if as_type in get_observation_types_list(ObservationTypeSpanLike):
+ if trace_context:
+ trace_id = trace_context.get("trace_id", None)
+ parent_span_id = trace_context.get("parent_span_id", None)
+
+ if trace_id:
+ remote_parent_span = self._create_remote_parent_span(
+ trace_id=trace_id, parent_span_id=parent_span_id
+ )
+
+ return cast(
+ Union[
+ _AgnosticContextManager[LangfuseSpan],
+ _AgnosticContextManager[LangfuseAgent],
+ _AgnosticContextManager[LangfuseTool],
+ _AgnosticContextManager[LangfuseChain],
+ _AgnosticContextManager[LangfuseRetriever],
+ _AgnosticContextManager[LangfuseEvaluator],
+ _AgnosticContextManager[LangfuseGuardrail],
+ ],
+ self._create_span_with_parent_context(
+ as_type=as_type,
+ name=name,
+ remote_parent_span=remote_parent_span,
+ parent=None,
+ end_on_exit=end_on_exit,
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ ),
+ )
+
+ return cast(
+ Union[
+ _AgnosticContextManager[LangfuseSpan],
+ _AgnosticContextManager[LangfuseAgent],
+ _AgnosticContextManager[LangfuseTool],
+ _AgnosticContextManager[LangfuseChain],
+ _AgnosticContextManager[LangfuseRetriever],
+ _AgnosticContextManager[LangfuseEvaluator],
+ _AgnosticContextManager[LangfuseGuardrail],
+ ],
+ self._start_as_current_otel_span_with_processed_media(
+ as_type=as_type,
+ name=name,
+ end_on_exit=end_on_exit,
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ ),
+ )
+
+ # This should never be reached since all valid types are handled above
+ langfuse_logger.warning(
+ f"Unknown observation type: {as_type}, falling back to span"
)
+ return self._start_as_current_otel_span_with_processed_media(
+ as_type="span",
+ name=name,
+ end_on_exit=end_on_exit,
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ )
+
+ def _get_span_class(
+ self,
+ as_type: ObservationTypeLiteral,
+ ) -> Union[
+ Type[LangfuseAgent],
+ Type[LangfuseTool],
+ Type[LangfuseChain],
+ Type[LangfuseRetriever],
+ Type[LangfuseEvaluator],
+ Type[LangfuseEmbedding],
+ Type[LangfuseGuardrail],
+ Type[LangfuseGeneration],
+ Type[LangfuseEvent],
+ Type[LangfuseSpan],
+ ]:
+ """Get the appropriate span class based on as_type."""
+ normalized_type = as_type.lower()
+
+ if normalized_type == "agent":
+ return LangfuseAgent
+ elif normalized_type == "tool":
+ return LangfuseTool
+ elif normalized_type == "chain":
+ return LangfuseChain
+ elif normalized_type == "retriever":
+ return LangfuseRetriever
+ elif normalized_type == "evaluator":
+ return LangfuseEvaluator
+ elif normalized_type == "embedding":
+ return LangfuseEmbedding
+ elif normalized_type == "guardrail":
+ return LangfuseGuardrail
+ elif normalized_type == "generation":
+ return LangfuseGeneration
+ elif normalized_type == "event":
+ return LangfuseEvent
+ elif normalized_type == "span":
+ return LangfuseSpan
+ else:
+ return LangfuseSpan
@_agnosticcontextmanager
def _create_span_with_parent_context(
@@ -673,7 +1336,7 @@ def _create_span_with_parent_context(
name: str,
parent: Optional[otel_trace_api.Span] = None,
remote_parent_span: Optional[otel_trace_api.Span] = None,
- as_type: Literal["generation", "span"],
+ as_type: ObservationTypeLiteralNoEvent,
end_on_exit: Optional[bool] = None,
input: Optional[Any] = None,
output: Optional[Any] = None,
@@ -720,7 +1383,7 @@ def _start_as_current_otel_span_with_processed_media(
self,
*,
name: str,
- as_type: Optional[Literal["generation", "span"]] = None,
+ as_type: Optional[ObservationTypeLiteralNoEvent] = None,
end_on_exit: Optional[bool] = None,
input: Optional[Any] = None,
output: Optional[Any] = None,
@@ -739,37 +1402,38 @@ 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:
- yield (
- LangfuseSpan(
- otel_span=otel_span,
- langfuse_client=self,
- environment=self._environment,
- input=input,
- output=output,
- metadata=metadata,
- version=version,
- level=level,
- status_message=status_message,
- )
- if as_type == "span"
- else LangfuseGeneration(
- otel_span=otel_span,
- langfuse_client=self,
- environment=self._environment,
- input=input,
- output=output,
- metadata=metadata,
- version=version,
- level=level,
- status_message=status_message,
- completion_start_time=completion_start_time,
- model=model,
- model_parameters=model_parameters,
- usage_details=usage_details,
- cost_details=cost_details,
- prompt=prompt,
+ 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,
+ "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]
def _get_current_otel_span(self) -> Optional[otel_trace_api.Span]:
current_span = otel_trace_api.get_current_span()
@@ -996,7 +1660,12 @@ def update_current_trace(
current_otel_span = self._get_current_otel_span()
if current_otel_span is not None:
- span = LangfuseSpan(
+ existing_observation_type = current_otel_span.attributes.get( # type: ignore[attr-defined]
+ LangfuseOtelSpanAttributes.OBSERVATION_TYPE, "span"
+ )
+ # We need to preserve the class to keep the corret observation type
+ span_class = self._get_span_class(existing_observation_type)
+ span = span_class(
otel_span=current_otel_span,
langfuse_client=self,
environment=self._environment,
diff --git a/langfuse/_client/constants.py b/langfuse/_client/constants.py
index 1c805ddc3..b699480c0 100644
--- a/langfuse/_client/constants.py
+++ b/langfuse/_client/constants.py
@@ -3,4 +3,61 @@
This module defines constants used throughout the Langfuse OpenTelemetry integration.
"""
+from typing import Literal, List, get_args, Union, Any
+from typing_extensions import TypeAlias
+
LANGFUSE_TRACER_NAME = "langfuse-sdk"
+
+
+"""Note: this type is used with .__args__ / get_args in some cases and therefore must remain flat"""
+ObservationTypeGenerationLike: TypeAlias = Literal[
+ "generation",
+ "embedding",
+]
+
+ObservationTypeSpanLike: TypeAlias = Literal[
+ "span",
+ "agent",
+ "tool",
+ "chain",
+ "retriever",
+ "evaluator",
+ "guardrail",
+]
+
+ObservationTypeLiteralNoEvent: TypeAlias = Union[
+ ObservationTypeGenerationLike,
+ ObservationTypeSpanLike,
+]
+
+"""Enumeration of valid observation types for Langfuse tracing.
+
+This Literal defines all available observation types that can be used with the @observe
+decorator and other Langfuse SDK methods.
+"""
+ObservationTypeLiteral: TypeAlias = Union[
+ ObservationTypeLiteralNoEvent, Literal["event"]
+]
+
+
+def get_observation_types_list(
+ literal_type: Any,
+) -> List[str]:
+ """Flattens the Literal type to provide a list of strings.
+
+ Args:
+ literal_type: A Literal type, TypeAlias, or union of Literals to flatten
+
+ Returns:
+ Flat list of all string values contained in the Literal type
+ """
+ result = []
+ args = get_args(literal_type)
+
+ for arg in args:
+ if hasattr(arg, "__args__"):
+ result.extend(get_observation_types_list(arg))
+ else:
+ result.append(arg)
+
+ return result
diff --git a/langfuse/_client/environment_variables.py b/langfuse/_client/environment_variables.py
index b868b1e24..4394d2077 100644
--- a/langfuse/_client/environment_variables.py
+++ b/langfuse/_client/environment_variables.py
@@ -76,7 +76,7 @@
.. envvar:: LANGFUSE_FLUSH_AT
Max batch size until a new ingestion batch is sent to the API.
-**Default value:** ``15``
+**Default value:** same as OTEL ``OTEL_BSP_MAX_EXPORT_BATCH_SIZE``
"""
LANGFUSE_FLUSH_INTERVAL = "LANGFUSE_FLUSH_INTERVAL"
@@ -84,7 +84,7 @@
.. envvar:: LANGFUSE_FLUSH_INTERVAL
Max delay in seconds until a new ingestion batch is sent to the API.
-**Default value:** ``1``
+**Default value:** same as OTEL ``OTEL_BSP_SCHEDULE_DELAY``
"""
LANGFUSE_SAMPLE_RATE = "LANGFUSE_SAMPLE_RATE"
diff --git a/langfuse/_client/observe.py b/langfuse/_client/observe.py
index 0fef2b5dd..ce848e04a 100644
--- a/langfuse/_client/observe.py
+++ b/langfuse/_client/observe.py
@@ -10,7 +10,6 @@
Dict,
Generator,
Iterable,
- Literal,
Optional,
Tuple,
TypeVar,
@@ -25,8 +24,23 @@
from langfuse._client.environment_variables import (
LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED,
)
+
+from langfuse._client.constants import (
+ ObservationTypeLiteralNoEvent,
+ get_observation_types_list,
+)
from langfuse._client.get_client import _set_current_public_key, get_client
-from langfuse._client.span import LangfuseGeneration, LangfuseSpan
+from langfuse._client.span import (
+ LangfuseGeneration,
+ LangfuseSpan,
+ LangfuseAgent,
+ LangfuseTool,
+ LangfuseChain,
+ LangfuseRetriever,
+ LangfuseEvaluator,
+ LangfuseEmbedding,
+ LangfuseGuardrail,
+)
from langfuse.types import TraceContext
F = TypeVar("F", bound=Callable[..., Any])
@@ -65,7 +79,7 @@ def observe(
func: None = None,
*,
name: Optional[str] = None,
- as_type: Optional[Literal["generation"]] = None,
+ as_type: Optional[ObservationTypeLiteralNoEvent] = None,
capture_input: Optional[bool] = None,
capture_output: Optional[bool] = None,
transform_to_string: Optional[Callable[[Iterable], str]] = None,
@@ -76,7 +90,7 @@ def observe(
func: Optional[F] = None,
*,
name: Optional[str] = None,
- as_type: Optional[Literal["generation"]] = None,
+ as_type: Optional[ObservationTypeLiteralNoEvent] = None,
capture_input: Optional[bool] = None,
capture_output: Optional[bool] = None,
transform_to_string: Optional[Callable[[Iterable], str]] = None,
@@ -93,8 +107,11 @@ def observe(
Args:
func (Optional[Callable]): The function to decorate. When used with parentheses @observe(), this will be None.
name (Optional[str]): Custom name for the created trace or span. If not provided, the function name is used.
- as_type (Optional[Literal["generation"]]): Set to "generation" to create a specialized LLM generation span
- with model metrics support, suitable for tracking language model outputs.
+ as_type (Optional[Literal]): Set the observation type. Supported values:
+ "generation", "span", "agent", "tool", "chain", "retriever", "embedding", "evaluator", "guardrail".
+ Observation types are highlighted in the Langfuse UI for filtering and visualization.
+ The types "generation" and "embedding" create a span on which additional attributes such as model metrics
+ can be set.
Returns:
Callable: A wrapped version of the original function that automatically creates and manages Langfuse spans.
@@ -146,6 +163,13 @@ def sub_process():
- For async functions, the decorator returns an async function wrapper.
- For sync functions, the decorator returns a synchronous wrapper.
"""
+ valid_types = set(get_observation_types_list(ObservationTypeLiteralNoEvent))
+ if as_type is not None and as_type not in valid_types:
+ self._log.warning(
+ f"Invalid as_type '{as_type}'. Valid types are: {', '.join(sorted(valid_types))}. Defaulting to 'span'."
+ )
+ as_type = "span"
+
function_io_capture_enabled = os.environ.get(
LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED, "True"
).lower() not in ("false", "0")
@@ -182,13 +206,13 @@ def decorator(func: F) -> F:
)
"""Handle decorator with or without parentheses.
-
+
This logic enables the decorator to work both with and without parentheses:
- @observe - Python passes the function directly to the decorator
- @observe() - Python calls the decorator first, which must return a function decorator
-
+
When called without arguments (@observe), the func parameter contains the function to decorate,
- so we directly apply the decorator to it. When called with parentheses (@observe()),
+ so we directly apply the decorator to it. When called with parentheses (@observe()),
func is None, so we return the decorator function itself for Python to apply in the next step.
"""
if func is None:
@@ -201,7 +225,7 @@ def _async_observe(
func: F,
*,
name: Optional[str],
- as_type: Optional[Literal["generation"]],
+ as_type: Optional[ObservationTypeLiteralNoEvent],
capture_input: bool,
capture_output: bool,
transform_to_string: Optional[Callable[[Iterable], str]] = None,
@@ -239,22 +263,21 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any:
Union[
_AgnosticContextManager[LangfuseGeneration],
_AgnosticContextManager[LangfuseSpan],
+ _AgnosticContextManager[LangfuseAgent],
+ _AgnosticContextManager[LangfuseTool],
+ _AgnosticContextManager[LangfuseChain],
+ _AgnosticContextManager[LangfuseRetriever],
+ _AgnosticContextManager[LangfuseEvaluator],
+ _AgnosticContextManager[LangfuseEmbedding],
+ _AgnosticContextManager[LangfuseGuardrail],
]
] = (
- (
- langfuse_client.start_as_current_generation(
- name=final_name,
- trace_context=trace_context,
- input=input,
- end_on_exit=False, # when returning a generator, closing on exit would be to early
- )
- if as_type == "generation"
- else langfuse_client.start_as_current_span(
- name=final_name,
- trace_context=trace_context,
- input=input,
- end_on_exit=False, # when returning a generator, closing on exit would be to early
- )
+ langfuse_client.start_as_current_observation(
+ name=final_name,
+ as_type=as_type or "span",
+ trace_context=trace_context,
+ input=input,
+ end_on_exit=False, # when returning a generator, closing on exit would be to early
)
if langfuse_client
else None
@@ -308,7 +331,7 @@ def _sync_observe(
func: F,
*,
name: Optional[str],
- as_type: Optional[Literal["generation"]],
+ as_type: Optional[ObservationTypeLiteralNoEvent],
capture_input: bool,
capture_output: bool,
transform_to_string: Optional[Callable[[Iterable], str]] = None,
@@ -344,22 +367,21 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
Union[
_AgnosticContextManager[LangfuseGeneration],
_AgnosticContextManager[LangfuseSpan],
+ _AgnosticContextManager[LangfuseAgent],
+ _AgnosticContextManager[LangfuseTool],
+ _AgnosticContextManager[LangfuseChain],
+ _AgnosticContextManager[LangfuseRetriever],
+ _AgnosticContextManager[LangfuseEvaluator],
+ _AgnosticContextManager[LangfuseEmbedding],
+ _AgnosticContextManager[LangfuseGuardrail],
]
] = (
- (
- langfuse_client.start_as_current_generation(
- name=final_name,
- trace_context=trace_context,
- input=input,
- end_on_exit=False, # when returning a generator, closing on exit would be to early
- )
- if as_type == "generation"
- else langfuse_client.start_as_current_span(
- name=final_name,
- trace_context=trace_context,
- input=input,
- end_on_exit=False, # when returning a generator, closing on exit would be to early
- )
+ langfuse_client.start_as_current_observation(
+ name=final_name,
+ as_type=as_type or "span",
+ trace_context=trace_context,
+ input=input,
+ end_on_exit=False, # when returning a generator, closing on exit would be to early
)
if langfuse_client
else None
@@ -432,7 +454,17 @@ def _get_input_from_func_args(
def _wrap_sync_generator_result(
self,
- langfuse_span_or_generation: Union[LangfuseSpan, LangfuseGeneration],
+ langfuse_span_or_generation: Union[
+ LangfuseSpan,
+ LangfuseGeneration,
+ LangfuseAgent,
+ LangfuseTool,
+ LangfuseChain,
+ LangfuseRetriever,
+ LangfuseEvaluator,
+ LangfuseEmbedding,
+ LangfuseGuardrail,
+ ],
generator: Generator,
transform_to_string: Optional[Callable[[Iterable], str]] = None,
) -> Any:
@@ -458,7 +490,17 @@ def _wrap_sync_generator_result(
async def _wrap_async_generator_result(
self,
- langfuse_span_or_generation: Union[LangfuseSpan, LangfuseGeneration],
+ langfuse_span_or_generation: Union[
+ LangfuseSpan,
+ LangfuseGeneration,
+ LangfuseAgent,
+ LangfuseTool,
+ LangfuseChain,
+ LangfuseRetriever,
+ LangfuseEvaluator,
+ LangfuseEmbedding,
+ LangfuseGuardrail,
+ ],
generator: AsyncGenerator,
transform_to_string: Optional[Callable[[Iterable], str]] = None,
) -> AsyncGenerator:
diff --git a/langfuse/_client/span.py b/langfuse/_client/span.py
index 34aa4f0d1..003c58706 100644
--- a/langfuse/_client/span.py
+++ b/langfuse/_client/span.py
@@ -5,7 +5,7 @@
creating, updating, and scoring various types of spans used in AI application tracing.
Classes:
-- LangfuseSpanWrapper: Abstract base class for all Langfuse spans
+- LangfuseObservationWrapper: Abstract base class for all Langfuse spans
- LangfuseSpan: Implementation for general-purpose spans
- LangfuseGeneration: Specialized span implementation for LLM generations
@@ -15,6 +15,7 @@
from datetime import datetime
from time import time_ns
+import warnings
from typing import (
TYPE_CHECKING,
Any,
@@ -22,6 +23,7 @@
List,
Literal,
Optional,
+ Type,
Union,
cast,
overload,
@@ -41,11 +43,23 @@
create_span_attributes,
create_trace_attributes,
)
+from langfuse._client.constants import (
+ ObservationTypeLiteral,
+ ObservationTypeGenerationLike,
+ ObservationTypeSpanLike,
+ ObservationTypeLiteralNoEvent,
+ get_observation_types_list,
+)
from langfuse.logger import langfuse_logger
from langfuse.types import MapValue, ScoreDataType, SpanLevel
+# Factory mapping for observation classes
+# Note: "event" is handled separately due to special instantiation logic
+# Populated after class definitions
+_OBSERVATION_CLASS_MAP: Dict[str, Type["LangfuseObservationWrapper"]] = {}
+
-class LangfuseSpanWrapper:
+class LangfuseObservationWrapper:
"""Abstract base class for all Langfuse span types.
This class provides common functionality for all Langfuse span types, including
@@ -64,7 +78,7 @@ def __init__(
*,
otel_span: otel_trace_api.Span,
langfuse_client: "Langfuse",
- as_type: Literal["span", "generation", "event"],
+ as_type: ObservationTypeLiteral,
input: Optional[Any] = None,
output: Optional[Any] = None,
metadata: Optional[Any] = None,
@@ -104,6 +118,7 @@ def __init__(
LangfuseOtelSpanAttributes.OBSERVATION_TYPE, as_type
)
self._langfuse_client = langfuse_client
+ self._observation_type = as_type
self.trace_id = self._langfuse_client._get_otel_trace_id(otel_span)
self.id = self._langfuse_client._get_otel_span_id(otel_span)
@@ -128,7 +143,7 @@ def __init__(
attributes = {}
- if as_type == "generation":
+ if as_type in get_observation_types_list(ObservationTypeGenerationLike):
attributes = create_generation_attributes(
input=media_processed_input,
output=media_processed_output,
@@ -142,9 +157,14 @@ def __init__(
usage_details=usage_details,
cost_details=cost_details,
prompt=prompt,
+ observation_type=cast(
+ ObservationTypeGenerationLike,
+ as_type,
+ ),
)
else:
+ # For span-like types and events
attributes = create_span_attributes(
input=media_processed_input,
output=media_processed_output,
@@ -152,15 +172,24 @@ def __init__(
version=version,
level=level,
status_message=status_message,
+ observation_type=cast(
+ Optional[Union[ObservationTypeSpanLike, Literal["event"]]],
+ as_type
+ if as_type
+ in get_observation_types_list(ObservationTypeSpanLike)
+ or as_type == "event"
+ else None,
+ ),
)
+ # We don't want to overwrite the observation type, and already set it
attributes.pop(LangfuseOtelSpanAttributes.OBSERVATION_TYPE, None)
self._otel_span.set_attributes(
{k: v for k, v in attributes.items() if v is not None}
)
- def end(self, *, end_time: Optional[int] = None) -> "LangfuseSpanWrapper":
+ def end(self, *, end_time: Optional[int] = None) -> "LangfuseObservationWrapper":
"""End the span, marking it as completed.
This method ends the wrapped OpenTelemetry span, marking the end of the
@@ -186,7 +215,7 @@ def update_trace(
metadata: Optional[Any] = None,
tags: Optional[List[str]] = None,
public: Optional[bool] = None,
- ) -> "LangfuseSpanWrapper":
+ ) -> "LangfuseObservationWrapper":
"""Update the trace that this span belongs to.
This method updates trace-level attributes of the trace that this span
@@ -383,7 +412,7 @@ def _set_processed_span_attributes(
self,
*,
span: otel_trace_api.Span,
- as_type: Optional[Literal["span", "generation", "event"]] = None,
+ as_type: Optional[ObservationTypeLiteral] = None,
input: Optional[Any] = None,
output: Optional[Any] = None,
metadata: Optional[Any] = None,
@@ -511,55 +540,6 @@ def _process_media_in_attribute(
return data
-
-class LangfuseSpan(LangfuseSpanWrapper):
- """Standard span implementation for general operations in Langfuse.
-
- This class represents a general-purpose span that can be used to trace
- any operation in your application. It extends the base LangfuseSpanWrapper
- with specific methods for creating child spans, generations, and updating
- span-specific attributes.
- """
-
- def __init__(
- self,
- *,
- otel_span: otel_trace_api.Span,
- langfuse_client: "Langfuse",
- input: Optional[Any] = None,
- output: Optional[Any] = None,
- metadata: Optional[Any] = None,
- environment: Optional[str] = None,
- version: Optional[str] = None,
- level: Optional[SpanLevel] = None,
- status_message: Optional[str] = None,
- ):
- """Initialize a new LangfuseSpan.
-
- Args:
- otel_span: The OpenTelemetry span to wrap
- langfuse_client: Reference to the parent Langfuse client
- input: Input data for the span (any JSON-serializable object)
- output: Output data from the span (any JSON-serializable object)
- metadata: Additional metadata to associate with the span
- environment: The tracing environment
- version: Version identifier for the code or component
- level: Importance level of the span (info, warning, error)
- status_message: Optional status message for the span
- """
- super().__init__(
- otel_span=otel_span,
- as_type="span",
- langfuse_client=langfuse_client,
- input=input,
- output=output,
- metadata=metadata,
- environment=environment,
- version=version,
- level=level,
- status_message=status_message,
- )
-
def update(
self,
*,
@@ -570,33 +550,34 @@ def update(
version: Optional[str] = None,
level: Optional[SpanLevel] = None,
status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
**kwargs: Any,
- ) -> "LangfuseSpan":
- """Update this span with new information.
+ ) -> "LangfuseObservationWrapper":
+ """Update this observation with new information.
- This method updates the span with new information that becomes available
+ This method updates the observation with new information that becomes available
during execution, such as outputs, metadata, or status changes.
Args:
- name: Span name
+ name: Observation name
input: Updated input data for the operation
output: Output data from the operation
- metadata: Additional metadata to associate with the span
+ metadata: Additional metadata to associate with the observation
version: Version identifier for the code or component
- level: Importance level of the span (info, warning, error)
- status_message: Optional status message for the span
+ level: Importance level of the observation (info, warning, error)
+ status_message: Optional status message for the observation
+ completion_start_time: When the generation started (for generation types)
+ model: Model identifier used (for generation types)
+ model_parameters: Parameters passed to the model (for generation types)
+ usage_details: Token or other usage statistics (for generation types)
+ cost_details: Cost breakdown for the operation (for generation types)
+ prompt: Reference to the prompt used (for generation types)
**kwargs: Additional keyword arguments (ignored)
-
- Example:
- ```python
- span = langfuse.start_span(name="process-data")
- try:
- # Do work
- result = process_data()
- span.update(output=result, metadata={"processing_time": 350})
- finally:
- span.end()
- ```
"""
if not self._otel_span.is_recording():
return self
@@ -614,147 +595,160 @@ def update(
if name:
self._otel_span.update_name(name)
- attributes = create_span_attributes(
- input=processed_input,
- output=processed_output,
- metadata=processed_metadata,
- version=version,
- level=level,
- status_message=status_message,
- )
+ if self._observation_type in get_observation_types_list(
+ ObservationTypeGenerationLike
+ ):
+ attributes = create_generation_attributes(
+ input=processed_input,
+ output=processed_output,
+ metadata=processed_metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ observation_type=cast(
+ ObservationTypeGenerationLike,
+ self._observation_type,
+ ),
+ completion_start_time=completion_start_time,
+ model=model,
+ model_parameters=model_parameters,
+ usage_details=usage_details,
+ cost_details=cost_details,
+ prompt=prompt,
+ )
+ else:
+ # For span-like types and events
+ attributes = create_span_attributes(
+ input=processed_input,
+ output=processed_output,
+ metadata=processed_metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ observation_type=cast(
+ Optional[Union[ObservationTypeSpanLike, Literal["event"]]],
+ self._observation_type
+ if self._observation_type
+ in get_observation_types_list(ObservationTypeSpanLike)
+ or self._observation_type == "event"
+ else None,
+ ),
+ )
self._otel_span.set_attributes(attributes=attributes)
return self
- def start_span(
+ @overload
+ def start_observation(
self,
+ *,
name: str,
+ as_type: Literal["span"],
input: Optional[Any] = None,
output: Optional[Any] = None,
metadata: Optional[Any] = None,
version: Optional[str] = None,
level: Optional[SpanLevel] = None,
status_message: Optional[str] = None,
- ) -> "LangfuseSpan":
- """Create a new child span.
-
- This method creates a new child span with this span as the parent.
- Unlike start_as_current_span(), this method does not set the new span
- as the current span in the context.
-
- Args:
- name: Name of the span (e.g., function or operation name)
- input: Input data for the operation
- output: Output data from the operation
- metadata: Additional metadata to associate with the span
- version: Version identifier for the code or component
- level: Importance level of the span (info, warning, error)
- status_message: Optional status message for the span
-
- Returns:
- A new LangfuseSpan that must be ended with .end() when complete
-
- Example:
- ```python
- parent_span = langfuse.start_span(name="process-request")
- try:
- # Create a child span
- child_span = parent_span.start_span(name="validate-input")
- try:
- # Do validation work
- validation_result = validate(request_data)
- child_span.update(output=validation_result)
- finally:
- child_span.end()
-
- # Continue with parent span
- result = process_validated_data(validation_result)
- parent_span.update(output=result)
- finally:
- parent_span.end()
- ```
- """
- with otel_trace_api.use_span(self._otel_span):
- new_otel_span = self._langfuse_client._otel_tracer.start_span(name=name)
-
- return LangfuseSpan(
- otel_span=new_otel_span,
- langfuse_client=self._langfuse_client,
- environment=self._environment,
- input=input,
- output=output,
- metadata=metadata,
- version=version,
- level=level,
- status_message=status_message,
- )
+ ) -> "LangfuseSpan": ...
- def start_as_current_span(
+ @overload
+ def start_observation(
self,
*,
name: str,
+ as_type: Literal["generation"],
input: Optional[Any] = None,
output: Optional[Any] = None,
metadata: Optional[Any] = None,
version: Optional[str] = None,
level: Optional[SpanLevel] = None,
status_message: Optional[str] = None,
- ) -> _AgnosticContextManager["LangfuseSpan"]:
- """Create a new child span and set it as the current span in a context manager.
-
- This method creates a new child span and sets it as the current span within
- a context manager. It should be used with a 'with' statement to automatically
- manage the span's lifecycle.
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ ) -> "LangfuseGeneration": ...
- Args:
- name: Name of the span (e.g., function or operation name)
- input: Input data for the operation
- output: Output data from the operation
- metadata: Additional metadata to associate with the span
- version: Version identifier for the code or component
- level: Importance level of the span (info, warning, error)
- status_message: Optional status message for the span
+ @overload
+ def start_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["agent"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> "LangfuseAgent": ...
- Returns:
- A context manager that yields a new LangfuseSpan
+ @overload
+ def start_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["tool"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> "LangfuseTool": ...
- Example:
- ```python
- with langfuse.start_as_current_span(name="process-request") as parent_span:
- # Parent span is active here
+ @overload
+ def start_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["chain"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> "LangfuseChain": ...
- # Create a child span with context management
- with parent_span.start_as_current_span(name="validate-input") as child_span:
- # Child span is active here
- validation_result = validate(request_data)
- child_span.update(output=validation_result)
+ @overload
+ def start_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["retriever"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> "LangfuseRetriever": ...
- # Back to parent span context
- result = process_validated_data(validation_result)
- parent_span.update(output=result)
- ```
- """
- return cast(
- _AgnosticContextManager["LangfuseSpan"],
- self._langfuse_client._create_span_with_parent_context(
- name=name,
- as_type="span",
- remote_parent_span=None,
- parent=self._otel_span,
- input=input,
- output=output,
- metadata=metadata,
- version=version,
- level=level,
- status_message=status_message,
- ),
- )
+ @overload
+ def start_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["evaluator"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> "LangfuseEvaluator": ...
- def start_generation(
+ @overload
+ def start_observation(
self,
*,
name: str,
+ as_type: Literal["embedding"],
input: Optional[Any] = None,
output: Optional[Any] = None,
metadata: Optional[Any] = None,
@@ -767,15 +761,560 @@ def start_generation(
usage_details: Optional[Dict[str, int]] = None,
cost_details: Optional[Dict[str, float]] = None,
prompt: Optional[PromptClient] = None,
- ) -> "LangfuseGeneration":
- """Create a new child generation span.
+ ) -> "LangfuseEmbedding": ...
- This method creates a new child generation span with this span as the parent.
- Generation spans are specialized for AI/LLM operations and include additional
- fields for model information, usage stats, and costs.
+ @overload
+ def start_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["guardrail"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> "LangfuseGuardrail": ...
- Unlike start_as_current_generation(), this method does not set the new span
- as the current span in the context.
+ @overload
+ def start_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["event"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> "LangfuseEvent": ...
+
+ def start_observation(
+ self,
+ *,
+ name: str,
+ as_type: ObservationTypeLiteral,
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ ) -> Union[
+ "LangfuseSpan",
+ "LangfuseGeneration",
+ "LangfuseAgent",
+ "LangfuseTool",
+ "LangfuseChain",
+ "LangfuseRetriever",
+ "LangfuseEvaluator",
+ "LangfuseEmbedding",
+ "LangfuseGuardrail",
+ "LangfuseEvent",
+ ]:
+ """Create a new child observation of the specified type.
+
+ This is the generic method for creating any type of child observation.
+ Unlike start_as_current_observation(), this method does not set the new
+ observation as the current observation in the context.
+
+ Args:
+ name: Name of the observation
+ as_type: Type of observation to create
+ input: Input data for the operation
+ output: Output data from the operation
+ metadata: Additional metadata to associate with the observation
+ version: Version identifier for the code or component
+ level: Importance level of the observation (info, warning, error)
+ status_message: Optional status message for the observation
+ completion_start_time: When the model started generating (for generation types)
+ model: Name/identifier of the AI model used (for generation types)
+ model_parameters: Parameters used for the model (for generation types)
+ usage_details: Token usage information (for generation types)
+ cost_details: Cost information (for generation types)
+ prompt: Associated prompt template (for generation types)
+
+ Returns:
+ A new observation of the specified type that must be ended with .end()
+ """
+ if as_type == "event":
+ timestamp = time_ns()
+ event_span = self._langfuse_client._otel_tracer.start_span(
+ name=name, start_time=timestamp
+ )
+ return cast(
+ LangfuseEvent,
+ LangfuseEvent(
+ otel_span=event_span,
+ langfuse_client=self._langfuse_client,
+ input=input,
+ output=output,
+ metadata=metadata,
+ environment=self._environment,
+ version=version,
+ level=level,
+ status_message=status_message,
+ ).end(end_time=timestamp),
+ )
+
+ observation_class = _OBSERVATION_CLASS_MAP.get(as_type)
+ if not observation_class:
+ langfuse_logger.warning(
+ f"Unknown observation type: {as_type}, falling back to LangfuseSpan"
+ )
+ observation_class = LangfuseSpan
+
+ with otel_trace_api.use_span(self._otel_span):
+ new_otel_span = self._langfuse_client._otel_tracer.start_span(name=name)
+
+ common_args = {
+ "otel_span": new_otel_span,
+ "langfuse_client": self._langfuse_client,
+ "environment": self._environment,
+ "input": input,
+ "output": output,
+ "metadata": metadata,
+ "version": version,
+ "level": level,
+ "status_message": status_message,
+ }
+
+ if as_type in get_observation_types_list(ObservationTypeGenerationLike):
+ 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,
+ }
+ )
+
+ return observation_class(**common_args) # type: ignore[no-any-return,return-value,arg-type]
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["span"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> _AgnosticContextManager["LangfuseSpan"]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["generation"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ ) -> _AgnosticContextManager["LangfuseGeneration"]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["embedding"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ ) -> _AgnosticContextManager["LangfuseEmbedding"]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["agent"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> _AgnosticContextManager["LangfuseAgent"]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["tool"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> _AgnosticContextManager["LangfuseTool"]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["chain"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> _AgnosticContextManager["LangfuseChain"]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["retriever"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> _AgnosticContextManager["LangfuseRetriever"]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["evaluator"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> _AgnosticContextManager["LangfuseEvaluator"]: ...
+
+ @overload
+ def start_as_current_observation(
+ self,
+ *,
+ name: str,
+ as_type: Literal["guardrail"],
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> _AgnosticContextManager["LangfuseGuardrail"]: ...
+
+ def start_as_current_observation( # type: ignore[misc]
+ self,
+ *,
+ name: str,
+ as_type: ObservationTypeLiteralNoEvent,
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ # TODO: or union of context managers?
+ ) -> _AgnosticContextManager[
+ Union[
+ "LangfuseSpan",
+ "LangfuseGeneration",
+ "LangfuseAgent",
+ "LangfuseTool",
+ "LangfuseChain",
+ "LangfuseRetriever",
+ "LangfuseEvaluator",
+ "LangfuseEmbedding",
+ "LangfuseGuardrail",
+ ]
+ ]:
+ """Create a new child observation and set it as the current observation in a context manager.
+
+ This is the generic method for creating any type of child observation with
+ context management. It delegates to the client's _create_span_with_parent_context method.
+
+ Args:
+ name: Name of the observation
+ as_type: Type of observation to create
+ input: Input data for the operation
+ output: Output data from the operation
+ metadata: Additional metadata to associate with the observation
+ version: Version identifier for the code or component
+ level: Importance level of the observation (info, warning, error)
+ status_message: Optional status message for the observation
+ completion_start_time: When the model started generating (for generation types)
+ model: Name/identifier of the AI model used (for generation types)
+ model_parameters: Parameters used for the model (for generation types)
+ usage_details: Token usage information (for generation types)
+ cost_details: Cost information (for generation types)
+ prompt: Associated prompt template (for generation types)
+
+ Returns:
+ A context manager that yields a new observation of the specified type
+ """
+ return self._langfuse_client._create_span_with_parent_context(
+ name=name,
+ as_type=as_type,
+ remote_parent_span=None,
+ parent=self._otel_span,
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ completion_start_time=completion_start_time,
+ model=model,
+ model_parameters=model_parameters,
+ usage_details=usage_details,
+ cost_details=cost_details,
+ prompt=prompt,
+ )
+
+
+class LangfuseSpan(LangfuseObservationWrapper):
+ """Standard span implementation for general operations in Langfuse.
+
+ This class represents a general-purpose span that can be used to trace
+ any operation in your application. It extends the base LangfuseObservationWrapper
+ with specific methods for creating child spans, generations, and updating
+ span-specific attributes. If possible, use a more specific type for
+ better observability and insights.
+ """
+
+ def __init__(
+ self,
+ *,
+ otel_span: otel_trace_api.Span,
+ langfuse_client: "Langfuse",
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ environment: Optional[str] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ):
+ """Initialize a new LangfuseSpan.
+
+ Args:
+ otel_span: The OpenTelemetry span to wrap
+ langfuse_client: Reference to the parent Langfuse client
+ input: Input data for the span (any JSON-serializable object)
+ output: Output data from the span (any JSON-serializable object)
+ metadata: Additional metadata to associate with the span
+ environment: The tracing environment
+ version: Version identifier for the code or component
+ level: Importance level of the span (info, warning, error)
+ status_message: Optional status message for the span
+ """
+ super().__init__(
+ otel_span=otel_span,
+ as_type="span",
+ langfuse_client=langfuse_client,
+ input=input,
+ output=output,
+ metadata=metadata,
+ environment=environment,
+ version=version,
+ level=level,
+ status_message=status_message,
+ )
+
+ def start_span(
+ self,
+ name: str,
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> "LangfuseSpan":
+ """Create a new child span.
+
+ This method creates a new child span with this span as the parent.
+ Unlike start_as_current_span(), this method does not set the new span
+ as the current span in the context.
+
+ Args:
+ name: Name of the span (e.g., function or operation name)
+ input: Input data for the operation
+ output: Output data from the operation
+ metadata: Additional metadata to associate with the span
+ version: Version identifier for the code or component
+ level: Importance level of the span (info, warning, error)
+ status_message: Optional status message for the span
+
+ Returns:
+ A new LangfuseSpan that must be ended with .end() when complete
+
+ Example:
+ ```python
+ parent_span = langfuse.start_span(name="process-request")
+ try:
+ # Create a child span
+ child_span = parent_span.start_span(name="validate-input")
+ try:
+ # Do validation work
+ validation_result = validate(request_data)
+ child_span.update(output=validation_result)
+ finally:
+ child_span.end()
+
+ # Continue with parent span
+ result = process_validated_data(validation_result)
+ parent_span.update(output=result)
+ finally:
+ parent_span.end()
+ ```
+ """
+ return self.start_observation(
+ name=name,
+ as_type="span",
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ )
+
+ def start_as_current_span(
+ self,
+ *,
+ name: str,
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ ) -> _AgnosticContextManager["LangfuseSpan"]:
+ """[DEPRECATED] Create a new child span and set it as the current span in a context manager.
+
+ DEPRECATED: This method is deprecated and will be removed in a future version.
+ Use start_as_current_observation(as_type='span') instead.
+
+ This method creates a new child span and sets it as the current span within
+ a context manager. It should be used with a 'with' statement to automatically
+ manage the span's lifecycle.
+
+ Args:
+ name: Name of the span (e.g., function or operation name)
+ input: Input data for the operation
+ output: Output data from the operation
+ metadata: Additional metadata to associate with the span
+ version: Version identifier for the code or component
+ level: Importance level of the span (info, warning, error)
+ status_message: Optional status message for the span
+
+ Returns:
+ A context manager that yields a new LangfuseSpan
+
+ Example:
+ ```python
+ with langfuse.start_as_current_span(name="process-request") as parent_span:
+ # Parent span is active here
+
+ # Create a child span with context management
+ with parent_span.start_as_current_span(name="validate-input") as child_span:
+ # Child span is active here
+ validation_result = validate(request_data)
+ child_span.update(output=validation_result)
+
+ # Back to parent span context
+ result = process_validated_data(validation_result)
+ parent_span.update(output=result)
+ ```
+ """
+ warnings.warn(
+ "start_as_current_span is deprecated and will be removed in a future version. "
+ "Use start_as_current_observation(as_type='span') instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.start_as_current_observation(
+ name=name,
+ as_type="span",
+ input=input,
+ output=output,
+ metadata=metadata,
+ version=version,
+ level=level,
+ status_message=status_message,
+ )
+
+ def start_generation(
+ self,
+ *,
+ name: str,
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ ) -> "LangfuseGeneration":
+ """[DEPRECATED] Create a new child generation span.
+
+ DEPRECATED: This method is deprecated and will be removed in a future version.
+ Use start_observation(as_type='generation') instead.
+
+ This method creates a new child generation span with this span as the parent.
+ Generation spans are specialized for AI/LLM operations and include additional
+ fields for model information, usage stats, and costs.
+
+ Unlike start_as_current_generation(), this method does not set the new span
+ as the current span in the context.
Args:
name: Name of the generation operation
@@ -825,13 +1364,15 @@ def start_generation(
span.end()
```
"""
- with otel_trace_api.use_span(self._otel_span):
- new_otel_span = self._langfuse_client._otel_tracer.start_span(name=name)
-
- return LangfuseGeneration(
- otel_span=new_otel_span,
- langfuse_client=self._langfuse_client,
- environment=self._environment,
+ warnings.warn(
+ "start_generation is deprecated and will be removed in a future version. "
+ "Use start_observation(as_type='generation') instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.start_observation(
+ name=name,
+ as_type="generation",
input=input,
output=output,
metadata=metadata,
@@ -863,7 +1404,10 @@ def start_as_current_generation(
cost_details: Optional[Dict[str, float]] = None,
prompt: Optional[PromptClient] = None,
) -> _AgnosticContextManager["LangfuseGeneration"]:
- """Create a new child generation span and set it as the current span in a context manager.
+ """[DEPRECATED] Create a new child generation span and set it as the current span in a context manager.
+
+ DEPRECATED: This method is deprecated and will be removed in a future version.
+ Use start_as_current_observation(as_type='generation') instead.
This method creates a new child generation span and sets it as the current span
within a context manager. Generation spans are specialized for AI/LLM operations
@@ -915,13 +1459,15 @@ def start_as_current_generation(
span.update(output={"answer": response.text, "source": "gpt-4"})
```
"""
- return cast(
- _AgnosticContextManager["LangfuseGeneration"],
- self._langfuse_client._create_span_with_parent_context(
- name=name,
- as_type="generation",
- remote_parent_span=None,
- parent=self._otel_span,
+ warnings.warn(
+ "start_as_current_generation is deprecated and will be removed in a future version. "
+ "Use start_as_current_observation(as_type='generation') instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return self.start_as_current_observation(
+ name=name,
+ as_type="generation",
input=input,
output=output,
metadata=metadata,
@@ -934,8 +1480,7 @@ def start_as_current_generation(
usage_details=usage_details,
cost_details=cost_details,
prompt=prompt,
- ),
- )
+ )
def create_event(
self,
@@ -990,11 +1535,11 @@ def create_event(
)
-class LangfuseGeneration(LangfuseSpanWrapper):
+class LangfuseGeneration(LangfuseObservationWrapper):
"""Specialized span implementation for AI model generations in Langfuse.
This class represents a generation span specifically designed for tracking
- AI/LLM operations. It extends the base LangfuseSpanWrapper with specialized
+ AI/LLM operations. It extends the base LangfuseObservationWrapper with specialized
attributes for model details, token usage, and costs.
"""
@@ -1037,8 +1582,8 @@ def __init__(
prompt: Associated prompt template from Langfuse prompt management
"""
super().__init__(
- otel_span=otel_span,
as_type="generation",
+ otel_span=otel_span,
langfuse_client=langfuse_client,
input=input,
output=output,
@@ -1055,110 +1600,8 @@ def __init__(
prompt=prompt,
)
- def update(
- self,
- *,
- name: Optional[str] = None,
- input: Optional[Any] = None,
- output: Optional[Any] = None,
- metadata: Optional[Any] = None,
- version: Optional[str] = None,
- level: Optional[SpanLevel] = None,
- status_message: Optional[str] = None,
- completion_start_time: Optional[datetime] = None,
- model: Optional[str] = None,
- model_parameters: Optional[Dict[str, MapValue]] = None,
- usage_details: Optional[Dict[str, int]] = None,
- cost_details: Optional[Dict[str, float]] = None,
- prompt: Optional[PromptClient] = None,
- **kwargs: Dict[str, Any],
- ) -> "LangfuseGeneration":
- """Update this generation span with new information.
-
- This method updates the generation span with new information that becomes
- available during or after the model generation, such as model outputs,
- token usage statistics, or cost details.
-
- Args:
- name: The generation name
- input: Updated input data for the model
- output: Output from the model (e.g., completions)
- metadata: Additional metadata to associate with the generation
- version: Version identifier for the model or component
- level: Importance level of the generation (info, warning, error)
- status_message: Optional status message for the generation
- completion_start_time: When the model started generating the response
- model: Name/identifier of the AI model used (e.g., "gpt-4")
- model_parameters: Parameters used for the model (e.g., temperature, max_tokens)
- usage_details: Token usage information (e.g., prompt_tokens, completion_tokens)
- cost_details: Cost information for the model call
- prompt: Associated prompt template from Langfuse prompt management
- **kwargs: Additional keyword arguments (ignored)
-
- Example:
- ```python
- generation = langfuse.start_generation(
- name="answer-generation",
- model="gpt-4",
- input={"prompt": "Explain quantum computing"}
- )
- try:
- # Call model API
- response = llm.generate(...)
-
- # Update with results
- generation.update(
- output=response.text,
- usage_details={
- "prompt_tokens": response.usage.prompt_tokens,
- "completion_tokens": response.usage.completion_tokens,
- "total_tokens": response.usage.total_tokens
- },
- cost_details={
- "total_cost": 0.0035
- }
- )
- finally:
- generation.end()
- ```
- """
- if not self._otel_span.is_recording():
- return self
-
- processed_input = self._process_media_and_apply_mask(
- data=input, field="input", span=self._otel_span
- )
- processed_output = self._process_media_and_apply_mask(
- data=output, field="output", span=self._otel_span
- )
- processed_metadata = self._process_media_and_apply_mask(
- data=metadata, field="metadata", span=self._otel_span
- )
-
- if name:
- self._otel_span.update_name(name)
-
- attributes = create_generation_attributes(
- input=processed_input,
- output=processed_output,
- metadata=processed_metadata,
- version=version,
- level=level,
- status_message=status_message,
- completion_start_time=completion_start_time,
- model=model,
- model_parameters=model_parameters,
- usage_details=usage_details,
- cost_details=cost_details,
- prompt=prompt,
- )
-
- self._otel_span.set_attributes(attributes=attributes)
-
- return self
-
-class LangfuseEvent(LangfuseSpanWrapper):
+class LangfuseEvent(LangfuseObservationWrapper):
"""Specialized span implementation for Langfuse Events."""
def __init__(
@@ -1199,3 +1642,111 @@ def __init__(
level=level,
status_message=status_message,
)
+
+ def update(
+ self,
+ *,
+ name: Optional[str] = None,
+ input: Optional[Any] = None,
+ output: Optional[Any] = None,
+ metadata: Optional[Any] = None,
+ version: Optional[str] = None,
+ level: Optional[SpanLevel] = None,
+ status_message: Optional[str] = None,
+ completion_start_time: Optional[datetime] = None,
+ model: Optional[str] = None,
+ model_parameters: Optional[Dict[str, MapValue]] = None,
+ usage_details: Optional[Dict[str, int]] = None,
+ cost_details: Optional[Dict[str, float]] = None,
+ prompt: Optional[PromptClient] = None,
+ **kwargs: Any,
+ ) -> "LangfuseEvent":
+ """Update is not allowed for LangfuseEvent because events cannot be updated.
+
+ This method logs a warning and returns self without making changes.
+
+ Returns:
+ self: Returns the unchanged LangfuseEvent instance
+ """
+ langfuse_logger.warning(
+ "Attempted to update LangfuseEvent observation. Events cannot be updated after creation."
+ )
+ return self
+
+
+class LangfuseAgent(LangfuseObservationWrapper):
+ """Agent observation for reasoning blocks that act on tools using LLM guidance."""
+
+ def __init__(self, **kwargs: Any) -> None:
+ """Initialize a new LangfuseAgent span."""
+ kwargs["as_type"] = "agent"
+ super().__init__(**kwargs)
+
+
+class LangfuseTool(LangfuseObservationWrapper):
+ """Tool observation representing external tool calls, e.g., calling a weather API."""
+
+ def __init__(self, **kwargs: Any) -> None:
+ """Initialize a new LangfuseTool span."""
+ kwargs["as_type"] = "tool"
+ super().__init__(**kwargs)
+
+
+class LangfuseChain(LangfuseObservationWrapper):
+ """Chain observation for connecting LLM application steps, e.g. passing context from retriever to LLM."""
+
+ def __init__(self, **kwargs: Any) -> None:
+ """Initialize a new LangfuseChain span."""
+ kwargs["as_type"] = "chain"
+ super().__init__(**kwargs)
+
+
+class LangfuseRetriever(LangfuseObservationWrapper):
+ """Retriever observation for data retrieval steps, e.g. vector store or database queries."""
+
+ def __init__(self, **kwargs: Any) -> None:
+ """Initialize a new LangfuseRetriever span."""
+ kwargs["as_type"] = "retriever"
+ super().__init__(**kwargs)
+
+
+class LangfuseEmbedding(LangfuseObservationWrapper):
+ """Embedding observation for LLM embedding calls, typically used before retrieval."""
+
+ def __init__(self, **kwargs: Any) -> None:
+ """Initialize a new LangfuseEmbedding span."""
+ kwargs["as_type"] = "embedding"
+ super().__init__(**kwargs)
+
+
+class LangfuseEvaluator(LangfuseObservationWrapper):
+ """Evaluator observation for assessing relevance, correctness, or helpfulness of LLM outputs."""
+
+ def __init__(self, **kwargs: Any) -> None:
+ """Initialize a new LangfuseEvaluator span."""
+ kwargs["as_type"] = "evaluator"
+ super().__init__(**kwargs)
+
+
+class LangfuseGuardrail(LangfuseObservationWrapper):
+ """Guardrail observation for protection e.g. against jailbreaks or offensive content."""
+
+ def __init__(self, **kwargs: Any) -> None:
+ """Initialize a new LangfuseGuardrail span."""
+ kwargs["as_type"] = "guardrail"
+ super().__init__(**kwargs)
+
+
+_OBSERVATION_CLASS_MAP.update(
+ {
+ "span": LangfuseSpan,
+ "generation": LangfuseGeneration,
+ "agent": LangfuseAgent,
+ "tool": LangfuseTool,
+ "chain": LangfuseChain,
+ "retriever": LangfuseRetriever,
+ "evaluator": LangfuseEvaluator,
+ "embedding": LangfuseEmbedding,
+ "guardrail": LangfuseGuardrail,
+ }
+)
diff --git a/langfuse/api/README.md b/langfuse/api/README.md
index feb6512ef..d7fa24a33 100644
--- a/langfuse/api/README.md
+++ b/langfuse/api/README.md
@@ -16,7 +16,7 @@ pip install langfuse
Instantiate and use the client with the following:
```python
-from langfuse import AnnotationQueueObjectType, CreateAnnotationQueueItemRequest
+from langfuse import CreateAnnotationQueueRequest
from langfuse.client import FernLangfuse
client = FernLangfuse(
@@ -27,11 +27,10 @@ client = FernLangfuse(
password="YOUR_PASSWORD",
base_url="https://yourhost.com/path/to/api",
)
-client.annotation_queues.create_queue_item(
- queue_id="queueId",
- request=CreateAnnotationQueueItemRequest(
- object_id="objectId",
- object_type=AnnotationQueueObjectType.TRACE,
+client.annotation_queues.create_queue(
+ request=CreateAnnotationQueueRequest(
+ name="name",
+ score_config_ids=["scoreConfigIds", "scoreConfigIds"],
),
)
```
@@ -43,7 +42,7 @@ The SDK also exports an `async` client so that you can make non-blocking calls t
```python
import asyncio
-from langfuse import AnnotationQueueObjectType, CreateAnnotationQueueItemRequest
+from langfuse import CreateAnnotationQueueRequest
from langfuse.client import AsyncFernLangfuse
client = AsyncFernLangfuse(
@@ -57,11 +56,10 @@ client = AsyncFernLangfuse(
async def main() -> None:
- await client.annotation_queues.create_queue_item(
- queue_id="queueId",
- request=CreateAnnotationQueueItemRequest(
- object_id="objectId",
- object_type=AnnotationQueueObjectType.TRACE,
+ await client.annotation_queues.create_queue(
+ request=CreateAnnotationQueueRequest(
+ name="name",
+ score_config_ids=["scoreConfigIds", "scoreConfigIds"],
),
)
@@ -78,7 +76,7 @@ will be thrown.
from .api_error import ApiError
try:
- client.annotation_queues.create_queue_item(...)
+ client.annotation_queues.create_queue(...)
except ApiError as e:
print(e.status_code)
print(e.body)
@@ -101,7 +99,7 @@ A request is deemed retriable when any of the following HTTP status codes is ret
Use the `max_retries` request option to configure this behavior.
```python
-client.annotation_queues.create_queue_item(...,{
+client.annotation_queues.create_queue(...,{
max_retries=1
})
```
@@ -118,7 +116,7 @@ client = FernLangfuse(..., { timeout=20.0 }, )
# Override timeout for a specific method
-client.annotation_queues.create_queue_item(...,{
+client.annotation_queues.create_queue(...,{
timeout_in_seconds=1
})
```
diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py
index 2a274a811..4f43e45f1 100644
--- a/langfuse/api/__init__.py
+++ b/langfuse/api/__init__.py
@@ -3,6 +3,7 @@
from .resources import (
AccessDeniedError,
AnnotationQueue,
+ AnnotationQueueAssignmentRequest,
AnnotationQueueItem,
AnnotationQueueObjectType,
AnnotationQueueStatus,
@@ -28,7 +29,9 @@
Comment,
CommentObjectType,
ConfigCategory,
+ CreateAnnotationQueueAssignmentResponse,
CreateAnnotationQueueItemRequest,
+ CreateAnnotationQueueRequest,
CreateChatPromptRequest,
CreateCommentRequest,
CreateCommentResponse,
@@ -57,6 +60,7 @@
DatasetRunItem,
DatasetRunWithItems,
DatasetStatus,
+ DeleteAnnotationQueueAssignmentResponse,
DeleteAnnotationQueueItemResponse,
DeleteDatasetItemResponse,
DeleteDatasetRunResponse,
@@ -93,6 +97,8 @@
IngestionResponse,
IngestionSuccess,
IngestionUsage,
+ LlmAdapter,
+ LlmConnection,
MapValue,
MediaContentType,
MembershipRequest,
@@ -126,6 +132,7 @@
PaginatedDatasetRunItems,
PaginatedDatasetRuns,
PaginatedDatasets,
+ PaginatedLlmConnections,
PaginatedModels,
PaginatedSessions,
PatchMediaBody,
@@ -185,6 +192,7 @@
UpdateObservationEvent,
UpdateSpanBody,
UpdateSpanEvent,
+ UpsertLlmConnectionRequest,
Usage,
UsageDetails,
UserMeta,
@@ -196,6 +204,7 @@
datasets,
health,
ingestion,
+ llm_connections,
media,
metrics,
models,
@@ -216,6 +225,7 @@
__all__ = [
"AccessDeniedError",
"AnnotationQueue",
+ "AnnotationQueueAssignmentRequest",
"AnnotationQueueItem",
"AnnotationQueueObjectType",
"AnnotationQueueStatus",
@@ -241,7 +251,9 @@
"Comment",
"CommentObjectType",
"ConfigCategory",
+ "CreateAnnotationQueueAssignmentResponse",
"CreateAnnotationQueueItemRequest",
+ "CreateAnnotationQueueRequest",
"CreateChatPromptRequest",
"CreateCommentRequest",
"CreateCommentResponse",
@@ -270,6 +282,7 @@
"DatasetRunItem",
"DatasetRunWithItems",
"DatasetStatus",
+ "DeleteAnnotationQueueAssignmentResponse",
"DeleteAnnotationQueueItemResponse",
"DeleteDatasetItemResponse",
"DeleteDatasetRunResponse",
@@ -306,6 +319,8 @@
"IngestionResponse",
"IngestionSuccess",
"IngestionUsage",
+ "LlmAdapter",
+ "LlmConnection",
"MapValue",
"MediaContentType",
"MembershipRequest",
@@ -339,6 +354,7 @@
"PaginatedDatasetRunItems",
"PaginatedDatasetRuns",
"PaginatedDatasets",
+ "PaginatedLlmConnections",
"PaginatedModels",
"PaginatedSessions",
"PatchMediaBody",
@@ -398,6 +414,7 @@
"UpdateObservationEvent",
"UpdateSpanBody",
"UpdateSpanEvent",
+ "UpsertLlmConnectionRequest",
"Usage",
"UsageDetails",
"UserMeta",
@@ -409,6 +426,7 @@
"datasets",
"health",
"ingestion",
+ "llm_connections",
"media",
"metrics",
"models",
diff --git a/langfuse/api/client.py b/langfuse/api/client.py
index 87b46c2f8..f18caba1c 100644
--- a/langfuse/api/client.py
+++ b/langfuse/api/client.py
@@ -18,6 +18,10 @@
from .resources.datasets.client import AsyncDatasetsClient, DatasetsClient
from .resources.health.client import AsyncHealthClient, HealthClient
from .resources.ingestion.client import AsyncIngestionClient, IngestionClient
+from .resources.llm_connections.client import (
+ AsyncLlmConnectionsClient,
+ LlmConnectionsClient,
+)
from .resources.media.client import AsyncMediaClient, MediaClient
from .resources.metrics.client import AsyncMetricsClient, MetricsClient
from .resources.models.client import AsyncModelsClient, ModelsClient
@@ -120,6 +124,7 @@ def __init__(
self.datasets = DatasetsClient(client_wrapper=self._client_wrapper)
self.health = HealthClient(client_wrapper=self._client_wrapper)
self.ingestion = IngestionClient(client_wrapper=self._client_wrapper)
+ self.llm_connections = LlmConnectionsClient(client_wrapper=self._client_wrapper)
self.media = MediaClient(client_wrapper=self._client_wrapper)
self.metrics = MetricsClient(client_wrapper=self._client_wrapper)
self.models = ModelsClient(client_wrapper=self._client_wrapper)
@@ -218,6 +223,9 @@ def __init__(
self.datasets = AsyncDatasetsClient(client_wrapper=self._client_wrapper)
self.health = AsyncHealthClient(client_wrapper=self._client_wrapper)
self.ingestion = AsyncIngestionClient(client_wrapper=self._client_wrapper)
+ self.llm_connections = AsyncLlmConnectionsClient(
+ client_wrapper=self._client_wrapper
+ )
self.media = AsyncMediaClient(client_wrapper=self._client_wrapper)
self.metrics = AsyncMetricsClient(client_wrapper=self._client_wrapper)
self.models = AsyncModelsClient(client_wrapper=self._client_wrapper)
diff --git a/langfuse/api/reference.md b/langfuse/api/reference.md
index 29a7db88b..ce4c4ecd8 100644
--- a/langfuse/api/reference.md
+++ b/langfuse/api/reference.md
@@ -77,6 +77,85 @@ client.annotation_queues.list_queues()
+
+
+
+
+
+client.annotation_queues.create_queue(...)
+
+
+
+#### 🔌 Usage
+
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+
+
+
+
+
+
+
+
+
+
+client.annotation_queues.create_queue_assignment(...)
+
+
+
+
+#### 🔌 Usage
+
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+client.annotation_queues.delete_queue_assignment(...)
+
+
+
+#### 🔌 Usage
+
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+client.llm_connections.list(...)
+
+
+
+
+#### 🔌 Usage
+
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+client.llm_connections.upsert(...)
+
+
+
+#### 🔌 Usage
+
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+
+
+
+
+
+
+
+
+
+