Skip to content

feat(generations): add cost_details and usage_details support#61

Merged
kxzk merged 7 commits intomainfrom
feature/add-cost-details-and-usage-details-support-on-generations
Mar 6, 2026
Merged

feat(generations): add cost_details and usage_details support#61
kxzk merged 7 commits intomainfrom
feature/add-cost-details-and-usage-details-support-on-generations

Conversation

@kxzk
Copy link
Collaborator

@kxzk kxzk commented Feb 23, 2026

Supersedes #55 after branch rename to conventional format.

TL;DR

Add usage_details= and cost_details= setters to Generation and Embedding, route all model-related setters through update_observation_attributes, and extract shared setters into a ModelSetters module.

Why

The existing usage= setter on Generation did inline camelCase key remapping and wrote to a legacy langfuse.observation.usage OTel attribute. This diverged from the JS/Python SDKs which pass usage payload shape as-is via usage_details. The cost_details setter was missing entirely.

Changes

  • usage_details= on Generation and Embedding — preserves key shape as provided, writes to langfuse.observation.usage_details
  • cost_details= on Generation — writes to langfuse.observation.cost_details
  • usage= kept as a compatibility alias that forwards to usage_details= (no deprecation warning)
  • model= and model_parameters= refactored to route through update_observation_attributes instead of direct @otel_span.set_attribute calls
  • ModelSetters module extracts the 4 shared setters (usage=, usage_details=, model=, model_parameters=) included by both Generation and Embedding
  • Removed legacy camelCase key remapping from the setter path
  • Updated docs across 7 files to use usage_details in all examples
  • YARD docs on cost_details updated to recommend :input, :output, :total keys

Test plan

  • bundle exec rspec — 1198 examples, 0 failures, 98.06% coverage
  • bundle exec rubocop — no offenses
  • usage_details= tested on both Generation and Embedding
  • cost_details= tested on Generation
  • usage= alias verified to write to usage_details attribute (not legacy usage)
  • model= and model_parameters= verified against new OTel attribute keys

Checklist

  • Tests added for new behavior
  • Docs updated

Copilot AI review requested due to automatic review settings February 23, 2026 20:17
@kxzk kxzk added the enhancement New feature or request label Feb 23, 2026
@greptile-apps
Copy link

greptile-apps bot commented Feb 23, 2026

Greptile Summary

Adds Generation#usage_details= and Generation#cost_details= setters for generation observations. The existing Generation#usage= is preserved as a compatibility alias with a deprecation warning that forwards to usage_details=. All documentation has been updated to use usage_details consistently across examples.

Key changes:

  • New usage_details= and cost_details= methods delegate through the unified update_observation_attributes path
  • Deprecated usage= now logs a one-time warning per instance and forwards to usage_details=
  • Removed legacy camelCase remapping from the generation setter (preserves key shape as provided)
  • Comprehensive test coverage including deprecation warning verification
  • Documentation updated across 7 files (API_REFERENCE, ARCHITECTURE, GETTING_STARTED, PROMPTS, RAILS, SCORING, TRACING)

Note: The Embedding class still has usage= without deprecation, which is intentional based on the PR scope focused on generations.

Confidence Score: 5/5

  • Safe to merge - well-tested deprecation path with comprehensive documentation updates
  • All changes are backward compatible with proper deprecation warnings. The implementation follows the existing pattern of delegating through update_observation_attributes, comprehensive test coverage validates both new and deprecated behavior, and extensive documentation ensures users understand the migration path.
  • No files require special attention

Important Files Changed

Filename Overview
lib/langfuse/observations.rb Added usage_details= and cost_details= setters, deprecated usage= with proper forwarding and warning
spec/langfuse/base_observation_spec.rb Added comprehensive tests for usage_details=, cost_details=, and deprecated usage= behavior with logger verification
docs/API_REFERENCE.md Updated all examples to use usage_details and added deprecation notice for usage=

Last reviewed commit: eeba1bb

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for usage_details and cost_details setters to the Generation observation class, deprecating the legacy usage= setter. The change moves away from the old camelCase attribute conversion behavior to a simpler approach that preserves the original key format. All generation-focused documentation has been updated to use the new usage_details API.

Changes:

  • Added Generation#usage_details= and Generation#cost_details= setters that delegate through the unified observation update path
  • Deprecated Generation#usage= with a deprecation warning that forwards to usage_details=
  • Updated all documentation examples to use usage_details instead of usage

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/langfuse/observations.rb Added new usage_details= and cost_details= setters, deprecated usage= with warning mechanism
spec/langfuse/base_observation_spec.rb Added comprehensive tests for new setters and deprecation behavior
docs/TRACING.md Updated examples to use usage_details instead of usage
docs/SCORING.md Updated examples to use usage_details instead of usage
docs/RAILS.md Updated examples to use usage_details instead of usage
docs/PROMPTS.md Updated examples to use usage_details instead of usage
docs/GETTING_STARTED.md Updated examples to use usage_details instead of usage
docs/ARCHITECTURE.md Updated examples to use usage_details instead of usage
docs/API_REFERENCE.md Added documentation for new setters and deprecation notice

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +335 to +338
def usage=(value)
return unless @otel_span.recording?
warn_usage_deprecation
self.usage_details = value
end
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Embedding class has a usage= setter (line 681) that silently forwards to usage_details without any deprecation warning, while Generation#usage= logs a deprecation warning. For consistency, the Embedding class should either also have a deprecation warning for its usage= setter, or should have a corresponding usage_details= setter added. This inconsistency could confuse users about which API to use.

Copilot uses AI. Check for mistakes.
Comment on lines 342 to 350
def usage_details=(value)
update_observation_attributes(usage_details: value)
end

usage_json = usage_hash.to_json
@otel_span.set_attribute("langfuse.observation.usage", usage_json)
# @param value [Hash] Cost details hash (e.g., total_cost and provider-specific fields)
# @return [void]
def cost_details=(value)
update_observation_attributes(cost_details: value)
end
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The model= and model_parameters= setters directly call @otel_span.set_attribute() and check @otel_span.recording?, while the new usage_details= and cost_details= setters use update_observation_attributes() which internally handles the recording check. This inconsistency in implementation patterns within the same class may lead to maintenance issues. Consider refactoring model= and model_parameters= to also use update_observation_attributes() for consistency, or document why they need different implementation patterns.

Copilot uses AI. Check for mistakes.
Comment on lines +382 to +384
Langfuse.configuration.logger&.warn(
"Langfuse::Generation#usage= is deprecated; use #usage_details= instead."
)
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecation warning uses Langfuse.configuration.logger&.warn(...) with the safe navigation operator. This means if the logger is nil, the warning will be silently suppressed. While this is likely intentional for cases where logging isn't configured, consider whether this is the desired behavior. If deprecation warnings should always be visible regardless of logger configuration, you might want to fall back to warn or $stderr.puts when the logger is nil.

Suggested change
Langfuse.configuration.logger&.warn(
"Langfuse::Generation#usage= is deprecated; use #usage_details= instead."
)
message = "Langfuse::Generation#usage= is deprecated; use #usage_details= instead."
logger = Langfuse.configuration.logger
if logger
logger.warn(message)
else
warn(message)
end

Copilot uses AI. Check for mistakes.
"Langfuse::Generation#usage= is deprecated; use #usage_details= instead."
)
end

Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test verifies that the deprecation warning is logged when usage= is called, but it doesn't test that the warning is only logged once per instance even if usage= is called multiple times. Consider adding a test case that calls usage= twice and verifies the warning is logged exactly once, to ensure the @usage_deprecation_logged flag works as intended.

Suggested change
it "logs usage deprecation warning only once per instance" do
allow(Langfuse.configuration.logger).to receive(:warn)
generation.usage = { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
generation.usage = { prompt_tokens: 20, completion_tokens: 10, total_tokens: 30 }
span_data = generation.otel_span.to_span_data
usage_attr_value = span_data.attributes["langfuse.observation.usage_details"]
expect(usage_attr_value).not_to be_nil
usage_attr = JSON.parse(usage_attr_value)
expect(usage_attr["prompt_tokens"]).to eq(20)
expect(usage_attr["completion_tokens"]).to eq(10)
expect(usage_attr["total_tokens"]).to eq(30)
expect(span_data.attributes).not_to have_key("langfuse.observation.usage")
expect(Langfuse.configuration.logger).to have_received(:warn).once.with(
"Langfuse::Generation#usage= is deprecated; use #usage_details= instead."
)
end

Copilot uses AI. Check for mistakes.
kxzk added 2 commits February 28, 2026 05:49
- Delegate Generation#model= and #model_parameters= to
  update_observation_attributes, matching the pattern used by
  usage_details=, cost_details=, and the Embedding class. This also
  fixes a pre-existing bug where these setters used wrong OTel attribute
  names (langfuse.observation.model vs langfuse.observation.model.name).
- Simplify warn_usage_deprecation by removing unnecessary defined? guard.
- Update tests to expect correct OTel attribute names.
- Fall back to Kernel#warn when logger is nil in Generation and
  Embedding deprecation warnings, ensuring deprecation notices are
  always visible regardless of logger configuration.
- Add deprecation warning to Embedding#usage= for consistency with
  Generation#usage=, including usage_details= setter and once-per-
  instance warning guard.
- Add test coverage for once-only deprecation warning behavior on
  both Generation and Embedding classes.
- Update Embedding doc example to use usage_details instead of usage.
@kxzk kxzk self-assigned this Mar 6, 2026
…ils-and-usage-details-support-on-generations
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional findings.

Open in Devin Review

kxzk added 3 commits March 6, 2026 01:17
…d Embedding

Deduplicates usage=, usage_details=, model=, and model_parameters= setters
that were identical across both classes. cost_details= remains on Generation.
@kxzk kxzk merged commit 27e7bec into main Mar 6, 2026
10 checks passed
@kxzk kxzk deleted the feature/add-cost-details-and-usage-details-support-on-generations branch March 6, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants