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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ If you change CI bootstrap:
## Python-Specific Notes

- Exception messages should not inline f-string literals in the `raise` statement. Build the message in a variable first.
- Logging calls must not use f-strings; pass lazy `%`-style args (`langfuse_logger.debug("span=%s", name)`), enforced by ruff `G004`. Arguments are still evaluated eagerly, so guard an expensive one with `langfuse_logger.isEnabledFor(logging.DEBUG)`.
- Prefer ASCII-only edits unless the file already uses Unicode or Unicode is clearly required.

## Release And Docs
Expand Down
1 change: 1 addition & 0 deletions code_review.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Use this checklist for `/review`, PR review, or self-review before handoff.
## Python Style

- Exception messages should not inline f-string literals in `raise` statements; build the message in a variable first.
- Logging calls should use lazy `%`-style args, not f-strings (ruff `G004`). Flag any logging argument that is expensive to compute and is not behind an `isEnabledFor` guard.
- Keep edits ASCII-only unless the file already uses Unicode or Unicode is clearly required.
- Keep changes scoped; avoid opportunistic refactors.
- Never commit secrets or credentials.
93 changes: 60 additions & 33 deletions langfuse/_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,7 +1200,7 @@ def start_as_current_observation(

# 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"
"Unknown observation type: %s, falling back to span", as_type
)
return self._start_as_current_otel_span_with_processed_media(
as_type="span",
Expand Down Expand Up @@ -1734,12 +1734,16 @@ def _create_remote_parent_span(
) -> Any:
if not self._is_valid_trace_id(trace_id):
langfuse_logger.warning(
f"Passed trace ID '{trace_id}' is not a valid 32 lowercase hex char Langfuse trace id. Ignoring trace ID."
"Passed trace ID '%s' is not a valid 32 lowercase hex char Langfuse trace "
"id. Ignoring trace ID.",
trace_id,
)

if parent_span_id and not self._is_valid_span_id(parent_span_id):
langfuse_logger.warning(
f"Passed span ID '{parent_span_id}' is not a valid 16 lowercase hex char Langfuse span id. Ignoring parent span ID."
"Passed span ID '%s' is not a valid 16 lowercase hex char Langfuse span "
"id. Ignoring parent span ID.",
parent_span_id,
)

int_trace_id = int(trace_id, 16)
Expand Down Expand Up @@ -2051,7 +2055,11 @@ def create_score(

except Exception as e:
langfuse_logger.exception(
f"Error creating score: Failed to process score event for trace_id={trace_id}, name={name}. Error: {e}"
"Error creating score: Failed to process score event for trace_id=%s, "
"name=%s. Error: %s",
trace_id,
name,
e,
)

def _create_trace_tags_via_ingestion(
Expand Down Expand Up @@ -2084,7 +2092,10 @@ def _create_trace_tags_via_ingestion(
self._resources.add_trace_task(event)
except Exception as e:
langfuse_logger.exception(
f"Error updating trace tags: Failed to process trace update event for trace_id={trace_id}. Error: {e}"
"Error updating trace tags: Failed to process trace update event for "
"trace_id=%s. Error: %s",
trace_id,
e,
)

@overload
Expand Down Expand Up @@ -2167,7 +2178,12 @@ def score_current_span(
observation_id = self._get_otel_span_id(current_span)

langfuse_logger.info(
f"Score: Creating score name='{name}' value={value} for current span ({observation_id}) in trace {trace_id}"
"Score: Creating score name='%s' value=%s for current span (%s) in trace "
"%s",
name,
value,
observation_id,
trace_id,
)

self.create_score(
Expand Down Expand Up @@ -2265,7 +2281,10 @@ def score_current_trace(
trace_id = self._get_otel_trace_id(current_span)

langfuse_logger.info(
f"Score: Creating score name='{name}' value={value} for entire trace {trace_id}"
"Score: Creating score name='%s' value=%s for entire trace %s",
name,
value,
trace_id,
)

self.create_score(
Expand Down Expand Up @@ -2476,7 +2495,7 @@ def get_dataset(
DatasetClient: The dataset with the given name.
"""
try:
langfuse_logger.debug(f"Getting datasets {name}")
langfuse_logger.debug("Getting datasets %s", name)
dataset = self.api.datasets.get(dataset_name=self._url_encode(name))

dataset_items: List[DatasetItem] = []
Expand Down Expand Up @@ -2804,7 +2823,7 @@ async def _run_experiment_async(
dataset_version: Optional[datetime] = None,
) -> ExperimentResult:
langfuse_logger.debug(
f"Starting experiment '{name}' run '{run_name}' with {len(data)} items"
"Starting experiment '%s' run '%s' with %s items", name, run_name, len(data)
)

shared_fallback_experiment_id = self._create_observation_id()
Expand Down Expand Up @@ -2836,7 +2855,7 @@ async def process_item(item: ExperimentItem) -> ExperimentItemResult:
valid_results: List[ExperimentItemResult] = []
for i, result in enumerate(item_results):
if isinstance(result, Exception):
langfuse_logger.error(f"Item {i} failed: {result}")
langfuse_logger.error("Item %s failed: %s", i, result)
elif isinstance(result, ExperimentItemResult):
valid_results.append(result) # type: ignore

Expand All @@ -2849,7 +2868,7 @@ async def process_item(item: ExperimentItem) -> ExperimentItemResult:
)
run_evaluations.extend(evaluations)
except Exception as e:
langfuse_logger.error(f"Run evaluator failed: {e}")
langfuse_logger.error("Run evaluator failed: %s", e)

# Generate dataset run URL if applicable
dataset_run_id = next(
Expand Down Expand Up @@ -2894,7 +2913,7 @@ async def process_item(item: ExperimentItem) -> ExperimentItemResult:
)

except Exception as e:
langfuse_logger.error(f"Failed to store run evaluation: {e}")
langfuse_logger.error("Failed to store run evaluation: %s", e)

# Flush scores and traces
self.flush()
Expand Down Expand Up @@ -3016,7 +3035,7 @@ async def _process_experiment_item(

except Exception as e:
langfuse_logger.error(
f"Failed to create dataset run item: {e}"
"Failed to create dataset run item: %s", e
)

experiment_id = dataset_run_id or fallback_experiment_id
Expand Down Expand Up @@ -3115,7 +3134,7 @@ async def _process_experiment_item(
level="ERROR",
status_message=str(e),
)
langfuse_logger.error(f"Evaluator failed: {e}")
langfuse_logger.error("Evaluator failed: %s", e)
continue

evaluations.extend(eval_results)
Expand All @@ -3134,7 +3153,7 @@ async def _process_experiment_item(
)
except Exception as e:
langfuse_logger.error(
f"Failed to store evaluation: {e}"
"Failed to store evaluation: %s", e
)

if composite_evaluator and evaluations:
Expand Down Expand Up @@ -3182,7 +3201,7 @@ async def _process_experiment_item(
status_message=str(e),
)
langfuse_logger.error(
f"Composite evaluator failed: {e}"
"Composite evaluator failed: %s", e
)
composite_evals = []

Expand All @@ -3201,7 +3220,7 @@ async def _process_experiment_item(
)
except Exception as e:
langfuse_logger.error(
f"Failed to store composite evaluation: {e}"
"Failed to store composite evaluation: %s", e
)

evaluation_span.update(
Expand Down Expand Up @@ -3481,7 +3500,7 @@ def auth_check(self) -> bool:
try:
projects = self.api.projects.get()
langfuse_logger.debug(
f"Auth check successful, found {len(projects.data)} projects"
"Auth check successful, found %s projects", len(projects.data)
)
if len(projects.data) == 0:
raise Exception(
Expand All @@ -3491,7 +3510,7 @@ def auth_check(self) -> bool:

except AttributeError as e:
langfuse_logger.warning(
f"Auth check failed: Client not properly initialized. Error: {e}"
"Auth check failed: Client not properly initialized. Error: %s", e
)
return False

Expand Down Expand Up @@ -3521,7 +3540,7 @@ def create_dataset(
Dataset: The created dataset as returned by the Langfuse API.
"""
try:
langfuse_logger.debug(f"Creating datasets {name}")
langfuse_logger.debug("Creating datasets %s", name)

result = self.api.datasets.create(
name=name,
Expand Down Expand Up @@ -3582,7 +3601,7 @@ def create_dataset_item(
```
"""
try:
langfuse_logger.debug(f"Creating dataset item for dataset {dataset_name}")
langfuse_logger.debug("Creating dataset item for dataset %s", dataset_name)

# Media uploads must reference the (dataset, item) they belong to, and
# the item need not exist yet — so settle on the item id up front and
Expand Down Expand Up @@ -3757,7 +3776,8 @@ def _replace_json_path_value(
return json_path.set_value_at_path(value, path, replacement)
except Exception as e:
langfuse_logger.warning(
f"Failed to hydrate dataset media reference at JSONPath {path}",
"Failed to hydrate dataset media reference at JSONPath %s",
path,
exc_info=e,
)

Expand Down Expand Up @@ -3902,12 +3922,12 @@ def get_prompt(
max_retries, default_max_retries=2, max_retries_upper_bound=4
)

langfuse_logger.debug(f"Getting prompt '{cache_key}'")
langfuse_logger.debug("Getting prompt '%s'", cache_key)
cached_prompt = self._resources.prompt_cache.get(cache_key)

if cached_prompt is None or cache_ttl_seconds == 0:
langfuse_logger.debug(
f"Prompt '{cache_key}' not found in cache or caching disabled."
"Prompt '%s' not found in cache or caching disabled.", cache_key
)
try:
return self._fetch_prompt_and_update_cache(
Expand All @@ -3921,7 +3941,9 @@ def get_prompt(
except Exception as e:
if fallback:
langfuse_logger.warning(
f"Returning fallback prompt for '{cache_key}' due to fetch error: {e}"
"Returning fallback prompt for '%s' due to fetch error: %s",
cache_key,
e,
)

fallback_client_args: Dict[str, Any] = {
Expand Down Expand Up @@ -3949,10 +3971,12 @@ def get_prompt(
raise e

if cached_prompt.is_expired():
langfuse_logger.debug(f"Stale prompt '{cache_key}' found in cache.")
langfuse_logger.debug("Stale prompt '%s' found in cache.", cache_key)
try:
# refresh prompt in background thread, refresh_prompt deduplicates tasks
langfuse_logger.debug(f"Refreshing prompt '{cache_key}' in background.")
langfuse_logger.debug(
"Refreshing prompt '%s' in background.", cache_key
)

def refresh_task() -> None:
self._fetch_prompt_and_update_cache(
Expand All @@ -3970,14 +3994,17 @@ def refresh_task() -> None:
refresh_task,
)
langfuse_logger.debug(
f"Returning stale prompt '{cache_key}' from cache."
"Returning stale prompt '%s' from cache.", cache_key
)
# return stale prompt
return cached_prompt.value

except Exception as e:
langfuse_logger.warning(
f"Error when refreshing cached prompt '{cache_key}', returning cached version. Error: {e}"
"Error when refreshing cached prompt '%s', returning cached version. "
"Error: %s",
cache_key,
e,
)
# creation of refresh prompt task failed, return stale prompt
return cached_prompt.value
Expand All @@ -3995,7 +4022,7 @@ def _fetch_prompt_and_update_cache(
fetch_timeout_seconds: Optional[int],
) -> PromptClient:
cache_key = PromptCache.generate_cache_key(name, version=version, label=label)
langfuse_logger.debug(f"Fetching prompt '{cache_key}' from server...")
langfuse_logger.debug("Fetching prompt '%s' from server...", cache_key)

try:

Expand Down Expand Up @@ -4029,15 +4056,15 @@ def fetch_prompts() -> Any:

except NotFoundError as not_found_error:
langfuse_logger.warning(
f"Prompt '{cache_key}' not found during refresh, evicting from cache."
"Prompt '%s' not found during refresh, evicting from cache.", cache_key
)
if self._resources is not None:
self._resources.prompt_cache.delete(cache_key)
raise not_found_error

except Exception as e:
langfuse_logger.error(
f"Error while fetching prompt '{cache_key}': {str(e)}"
"Error while fetching prompt '%s': %s", cache_key, str(e)
)
raise e

Expand Down Expand Up @@ -4114,7 +4141,7 @@ def create_prompt(
ChatPromptClient: The prompt if type argument is 'chat'.
"""
try:
langfuse_logger.debug(f"Creating prompt {name=}, {labels=}")
langfuse_logger.debug("Creating prompt name=%r, labels=%r", name, labels)

if type == "chat":
if not isinstance(prompt, list):
Expand Down
4 changes: 3 additions & 1 deletion langfuse/_client/get_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ def get_client(*, public_key: Optional[str] = None) -> Langfuse:
if target_instance is None:
# No instance found with this key - client not initialized properly
langfuse_logger.warning(
f"No Langfuse client with public key {public_key} has been initialized. Skipping tracing for decorated function."
"No Langfuse client with public key %s has been initialized. Skipping "
"tracing for decorated function.",
public_key,
)
return Langfuse(
tracing_enabled=False, public_key="fake", secret_key="fake"
Expand Down
4 changes: 3 additions & 1 deletion langfuse/_client/observe.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ def sub_process():
valid_types = set(get_observation_types_list(ObservationTypeLiteralNoEvent))
if as_type is not None and as_type not in valid_types:
logger.warning(
f"Invalid as_type '{as_type}'. Valid types are: {', '.join(sorted(valid_types))}. Defaulting to 'span'."
"Invalid as_type '%s'. Valid types are: %s. Defaulting to 'span'.",
as_type,
", ".join(sorted(valid_types)),
)
as_type = "span"

Expand Down
Loading