1919 overload ,
2020)
2121
22- from opentelemetry import trace
2322from opentelemetry .util ._decorator import _AgnosticContextManager
2423from typing_extensions import ParamSpec
2524
26- from langfuse ._client .attributes import LangfuseOtelSpanAttributes
2725from langfuse ._client .environment_variables import (
2826 LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED ,
2927)
28+
29+ from langfuse ._client .constants import VALID_OBSERVATION_TYPES
30+ from langfuse ._client .get_client import _set_current_public_key , get_client
3031from langfuse ._client .span import LangfuseGeneration , LangfuseSpan
3132from langfuse .types import TraceContext
3233
@@ -67,7 +68,18 @@ def observe(
6768 * ,
6869 name : Optional [str ] = None ,
6970 as_type : Optional [Literal ["generation" ]] = None ,
70- type : Optional [Literal ["SPAN" , "EVENT" , "GENERATION" , "AGENT" , "TOOL" , "CHAIN" , "RETRIEVER" , "EMBEDDING" ]] = None ,
71+ type : Optional [
72+ Literal [
73+ "SPAN" ,
74+ "EVENT" ,
75+ "GENERATION" ,
76+ "AGENT" ,
77+ "TOOL" ,
78+ "CHAIN" ,
79+ "RETRIEVER" ,
80+ "EMBEDDING" ,
81+ ]
82+ ] = None ,
7183 capture_input : Optional [bool ] = None ,
7284 capture_output : Optional [bool ] = None ,
7385 transform_to_string : Optional [Callable [[Iterable ], str ]] = None ,
@@ -79,7 +91,18 @@ def observe(
7991 * ,
8092 name : Optional [str ] = None ,
8193 as_type : Optional [Literal ["generation" ]] = None ,
82- type : Optional [Literal ["SPAN" , "EVENT" , "GENERATION" , "AGENT" , "TOOL" , "CHAIN" , "RETRIEVER" , "EMBEDDING" ]] = None ,
94+ type : Optional [
95+ Literal [
96+ "SPAN" ,
97+ "EVENT" ,
98+ "GENERATION" ,
99+ "AGENT" ,
100+ "TOOL" ,
101+ "CHAIN" ,
102+ "RETRIEVER" ,
103+ "EMBEDDING" ,
104+ ]
105+ ] = None ,
83106 capture_input : Optional [bool ] = None ,
84107 capture_output : Optional [bool ] = None ,
85108 transform_to_string : Optional [Callable [[Iterable ], str ]] = None ,
@@ -98,8 +121,8 @@ def observe(
98121 name (Optional[str]): Custom name for the created trace or span. If not provided, the function name is used.
99122 as_type (Optional[Literal["generation"]]): Set to "generation" to create a specialized LLM generation span
100123 with model metrics support, suitable for tracking language model outputs.
101- type (Optional[Literal]): Set the observation type directly . Supported values: "SPAN", "EVENT",
102- "GENERATION", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EMBEDDING". When specified, creates spans with
124+ type (Optional[Literal]): Set the observation type for agentic workflows . Supported values: "SPAN", "EVENT",
125+ "GENERATION", "AGENT", "TOOL", "CHAIN", "RETRIEVER", "EMBEDDING". When specified, creates spans with
103126 the specified type for graph visualization and filtering in the Langfuse UI.
104127
105128 Returns:
@@ -130,14 +153,7 @@ async def generate_answer(query):
130153 ```python
131154 @observe(type="AGENT")
132155 def planning_agent():
133- # Creates a span with observation type "AGENT" for graph visualization
134156 return create_plan()
135-
136- @observe(type="AGENT")
137- def execution_agent():
138- # Creates a span with observation type "AGENT"
139- # Parent relationships inferred from OpenTelemetry span hierarchy
140- return execute_plan()
141157 ```
142158
143159 For trace context propagation between functions:
@@ -166,13 +182,19 @@ def sub_process():
166182 - For async functions, the decorator returns an async function wrapper.
167183 - For sync functions, the decorator returns a synchronous wrapper.
168184 """
169- # Validate type parameter if provided
170- if type is not None :
171- from langfuse ._client .constants import VALID_OBSERVATION_TYPES
172- if type not in VALID_OBSERVATION_TYPES :
173- raise ValueError (
174- f"Invalid observation type '{ type } '. Valid types are: { ', ' .join (sorted (VALID_OBSERVATION_TYPES ))} "
175- )
185+ # Validate parameters
186+ if type is not None and type not in VALID_OBSERVATION_TYPES :
187+ raise ValueError (
188+ f"Invalid observation type '{ type } '. Valid types are: { ', ' .join (sorted (VALID_OBSERVATION_TYPES ))} "
189+ )
190+ if as_type is not None and as_type .upper () not in VALID_OBSERVATION_TYPES :
191+ valid_values = sorted (
192+ list (VALID_OBSERVATION_TYPES )
193+ + [t .lower () for t in VALID_OBSERVATION_TYPES ]
194+ )
195+ raise ValueError (
196+ f"Invalid as_type '{ as_type } '. Valid values are: { ', ' .join (valid_values )} "
197+ )
176198
177199 function_io_capture_enabled = os .environ .get (
178200 LANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED , "True"
@@ -189,12 +211,14 @@ def sub_process():
189211 )
190212
191213 def decorator (func : F ) -> F :
214+ # Merge as_type and type parameters - type takes precedence for graph observations
215+ final_as_type = type or as_type
216+
192217 return (
193218 self ._async_observe (
194219 func ,
195220 name = name ,
196- as_type = as_type ,
197- observation_type = type ,
221+ as_type = final_as_type ,
198222 capture_input = should_capture_input ,
199223 capture_output = should_capture_output ,
200224 transform_to_string = transform_to_string ,
@@ -203,8 +227,7 @@ def decorator(func: F) -> F:
203227 else self ._sync_observe (
204228 func ,
205229 name = name ,
206- as_type = as_type ,
207- observation_type = type ,
230+ as_type = final_as_type ,
208231 capture_input = should_capture_input ,
209232 capture_output = should_capture_output ,
210233 transform_to_string = transform_to_string ,
@@ -231,8 +254,7 @@ def _async_observe(
231254 func : F ,
232255 * ,
233256 name : Optional [str ],
234- as_type : Optional [Literal ["generation" ]],
235- observation_type : Optional [str ],
257+ as_type : Optional [str ],
236258 capture_input : bool ,
237259 capture_output : bool ,
238260 transform_to_string : Optional [Callable [[Iterable ], str ]] = None ,
@@ -252,7 +274,6 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any:
252274 else None
253275 )
254276 final_name = name or func .__name__
255-
256277 input = (
257278 self ._get_input_from_func_args (
258279 is_method = self ._is_method (func ),
@@ -264,9 +285,9 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any:
264285 )
265286 public_key = cast (str , kwargs .pop ("langfuse_public_key" , None ))
266287 langfuse_client = get_client (public_key = public_key )
267-
268- # Determine final observation type and create appropriate span
269- final_obs_type = observation_type or as_type
288+
289+ # Use consolidated as_type parameter
290+ final_obs_type = as_type
270291
271292 context_manager : Optional [
272293 Union [
@@ -281,13 +302,13 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any:
281302 input = input ,
282303 end_on_exit = False , # when returning a generator, closing on exit would be to early
283304 )
284- if final_obs_type == "generation" or observation_type == "GENERATION"
305+ if final_obs_type in ( "generation" , "GENERATION" )
285306 else langfuse_client .start_as_current_span (
286307 name = final_name ,
287308 trace_context = trace_context ,
288309 input = input ,
289310 end_on_exit = False , # when returning a generator, closing on exit would be to early
290- observation_type = observation_type ,
311+ as_type = final_obs_type ,
291312 )
292313 )
293314 if langfuse_client
@@ -298,7 +319,6 @@ async def async_wrapper(*args: Tuple[Any], **kwargs: Dict[str, Any]) -> Any:
298319 return await func (* args , ** kwargs )
299320
300321 with context_manager as langfuse_span_or_generation :
301-
302322 is_return_type_generator = False
303323
304324 try :
@@ -343,8 +363,7 @@ def _sync_observe(
343363 func : F ,
344364 * ,
345365 name : Optional [str ],
346- as_type : Optional [Literal ["generation" ]],
347- observation_type : Optional [str ],
366+ as_type : Optional [str ],
348367 capture_input : bool ,
349368 capture_output : bool ,
350369 transform_to_string : Optional [Callable [[Iterable ], str ]] = None ,
@@ -362,7 +381,6 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
362381 else None
363382 )
364383 final_name = name or func .__name__
365-
366384 input = (
367385 self ._get_input_from_func_args (
368386 is_method = self ._is_method (func ),
@@ -374,9 +392,9 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
374392 )
375393 public_key = kwargs .pop ("langfuse_public_key" , None )
376394 langfuse_client = get_client (public_key = public_key )
377-
378- # Determine final observation type and create appropriate span
379- final_obs_type = observation_type or as_type
395+
396+ # Use consolidated as_type parameter
397+ final_obs_type = as_type
380398
381399 context_manager : Optional [
382400 Union [
@@ -391,13 +409,13 @@ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
391409 input = input ,
392410 end_on_exit = False , # when returning a generator, closing on exit would be to early
393411 )
394- if final_obs_type == "generation" or observation_type == "GENERATION"
412+ if final_obs_type in ( "generation" , "GENERATION" )
395413 else langfuse_client .start_as_current_span (
396414 name = final_name ,
397415 trace_context = trace_context ,
398416 input = input ,
399417 end_on_exit = False , # when returning a generator, closing on exit would be to early
400- observation_type = observation_type ,
418+ as_type = final_obs_type ,
401419 )
402420 )
403421 if langfuse_client
@@ -454,7 +472,6 @@ def _is_method(func: Callable) -> bool:
454472 or "cls" in inspect .signature (func ).parameters
455473 )
456474
457-
458475 def _get_input_from_func_args (
459476 self ,
460477 * ,
0 commit comments