diff --git a/lib/langfuse.rb b/lib/langfuse.rb index ddc5fa4..6697dc8 100644 --- a/lib/langfuse.rb +++ b/lib/langfuse.rb @@ -20,10 +20,19 @@ # prompt = client.get_prompt("greeting") # module Langfuse + # Base error class for all Langfuse SDK errors class Error < StandardError; end + + # Raised when Langfuse configuration is invalid or incomplete class ConfigurationError < Error; end + + # Raised when a Langfuse API request fails class ApiError < Error; end + + # Raised when a requested resource is not found (HTTP 404) class NotFoundError < ApiError; end + + # Raised when API authentication fails (HTTP 401) class UnauthorizedError < ApiError; end # Default timeout (in seconds) for flushing traces during experiment runs. @@ -58,6 +67,7 @@ class UnauthorizedError < ApiError; end module Langfuse # rubocop:disable Metrics/ClassLength class << self + # @param configuration [Config] the global configuration object attr_writer :configuration # Returns the global configuration object @@ -302,6 +312,7 @@ def reset! # @param start_time [Time, Integer, nil] Optional start time (Time object or Unix timestamp in nanoseconds) # @param skip_validation [Boolean] Skip validation (for internal use). Defaults to false. # @return [BaseObservation] The observation wrapper (Span, Generation, or Event) + # @raise [ArgumentError] if an invalid observation type is provided # # @example Create root span # span = Langfuse.start_observation("root-operation", { input: {...} }) @@ -348,6 +359,7 @@ def start_observation(name, attrs = {}, as_type: :span, parent_span_context: nil # @param name [String] Descriptive name for the observation # @param attrs [Hash] Observation attributes (optional positional or keyword) # @param as_type [Symbol, String] Observation type (:span, :generation, :event, etc.) + # @param kwargs [Hash] Additional keyword arguments merged into observation attributes (e.g., input:, output:, metadata:) # @yield [observation] Optional block that receives the observation object # @yieldparam observation [BaseObservation] The observation object # @return [BaseObservation, Object] The observation (or block return value if block given) diff --git a/lib/langfuse/api_client.rb b/lib/langfuse/api_client.rb index 153d7de..dbac268 100644 --- a/lib/langfuse/api_client.rb +++ b/lib/langfuse/api_client.rb @@ -22,7 +22,23 @@ module Langfuse # ) # class ApiClient # rubocop:disable Metrics/ClassLength - attr_reader :public_key, :secret_key, :base_url, :timeout, :logger, :cache + # @return [String] Langfuse public API key + attr_reader :public_key + + # @return [String] Langfuse secret API key + attr_reader :secret_key + + # @return [String] Base URL for Langfuse API + attr_reader :base_url + + # @return [Integer] HTTP request timeout in seconds + attr_reader :timeout + + # @return [Logger] Logger instance for debugging + attr_reader :logger + + # @return [PromptCache, RailsCacheAdapter, nil] Optional cache for prompt responses + attr_reader :cache # Initialize a new API client # @@ -31,7 +47,8 @@ class ApiClient # rubocop:disable Metrics/ClassLength # @param base_url [String] Base URL for Langfuse API # @param timeout [Integer] HTTP request timeout in seconds # @param logger [Logger] Logger instance for debugging - # @param cache [PromptCache, nil] Optional cache for prompt responses + # @param cache [PromptCache, RailsCacheAdapter, nil] Optional cache for prompt responses + # @return [ApiClient] def initialize(public_key:, secret_key:, base_url:, timeout: 5, logger: nil, cache: nil) @public_key = public_key @secret_key = secret_key @@ -195,6 +212,7 @@ def update_prompt(name:, version:, labels:) # # @param events [Array] Array of event hashes to send # @return [void] + # @raise [ArgumentError] if events is not an Array or is empty # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors after retries exhausted # @@ -237,6 +255,9 @@ def send_batch(events) # @return [Hash] The created dataset run item data # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # api_client.create_dataset_run_item(dataset_item_id: "item-123", run_name: "eval-v1", trace_id: "trace-abc") def create_dataset_run_item(dataset_item_id:, run_name:, trace_id: nil, observation_id: nil, metadata: nil, run_description: nil) payload = { datasetItemId: dataset_item_id, runName: run_name } @@ -255,6 +276,11 @@ def create_dataset_run_item(dataset_item_id:, run_name:, trace_id: nil, raise ApiError, "HTTP request failed: #{e.message}" end + # Shut down the API client and release resources + # + # Shuts down the cache if it supports shutdown (e.g., SWR thread pool). + # + # @return [void] def shutdown cache.shutdown if cache.respond_to?(:shutdown) end @@ -266,6 +292,9 @@ def shutdown # @return [Array] Array of dataset metadata hashes # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # datasets = api_client.list_datasets(page: 1, limit: 10) def list_datasets(page: nil, limit: nil) params = { page: page, limit: limit }.compact @@ -287,6 +316,9 @@ def list_datasets(page: nil, limit: nil) # @raise [NotFoundError] if the dataset is not found # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # data = api_client.get_dataset("my-dataset") def get_dataset(name) encoded_name = URI.encode_uri_component(name) response = connection.get("/api/public/v2/datasets/#{encoded_name}") @@ -307,6 +339,9 @@ def get_dataset(name) # @return [Hash] The created dataset data # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # data = api_client.create_dataset(name: "my-dataset", description: "QA evaluation set") def create_dataset(name:, description: nil, metadata: nil) payload = { name: name, description: description, metadata: metadata }.compact @@ -333,6 +368,13 @@ def create_dataset(name:, description: nil, metadata: nil) # @return [Hash] The created dataset item data # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # data = api_client.create_dataset_item( + # dataset_name: "my-dataset", + # input: { query: "What is Ruby?" }, + # expected_output: { answer: "A programming language" } + # ) # rubocop:disable Metrics/ParameterLists def create_dataset_item(dataset_name:, input: nil, expected_output: nil, metadata: nil, id: nil, source_trace_id: nil, @@ -361,6 +403,9 @@ def create_dataset_item(dataset_name:, input: nil, expected_output: nil, # @raise [NotFoundError] if the item is not found # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # data = api_client.get_dataset_item("item-uuid-123") def get_dataset_item(id) encoded_id = URI.encode_uri_component(id) response = connection.get("/api/public/dataset-items/#{encoded_id}") @@ -383,6 +428,9 @@ def get_dataset_item(id) # @return [Array] Array of dataset item hashes # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # items = api_client.list_dataset_items(dataset_name: "my-dataset", limit: 50) def list_dataset_items(dataset_name:, page: nil, limit: nil, source_trace_id: nil, source_observation_id: nil) result = list_dataset_items_paginated( @@ -420,6 +468,9 @@ def list_dataset_items_paginated(dataset_name:, page: nil, limit: nil, # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors # @note 404 responses are treated as success to keep DELETE idempotent across retries + # + # @example + # api_client.delete_dataset_item("item-uuid-123") def delete_dataset_item(id) encoded_id = URI.encode_uri_component(id) response = connection.delete("/api/public/dataset-items/#{encoded_id}") diff --git a/lib/langfuse/cache_warmer.rb b/lib/langfuse/cache_warmer.rb index 8d3ba5c..32cb556 100644 --- a/lib/langfuse/cache_warmer.rb +++ b/lib/langfuse/cache_warmer.rb @@ -20,6 +20,7 @@ module Langfuse # end # class CacheWarmer + # @return [Client] Langfuse client used for fetching prompts attr_reader :client # Initialize a new cache warmer @@ -35,8 +36,8 @@ def initialize(client: nil) # safe to call multiple times. # # @param prompt_names [Array] List of prompt names to cache - # @param versions [Hash, nil] Optional version numbers per prompt - # @param labels [Hash, nil] Optional labels per prompt + # @param versions [Hash] Optional version numbers per prompt + # @param labels [Hash] Optional labels per prompt # @return [Hash] Results with :success and :failed arrays # # @example Basic warming @@ -73,8 +74,8 @@ def warm(prompt_names, versions: {}, labels: {}) # are cached without manually specifying them. # # @param default_label [String, nil] Label to use for all prompts (default: "production") - # @param versions [Hash, nil] Optional version numbers per prompt - # @param labels [Hash, nil] Optional labels per specific prompts (overrides default_label) + # @param versions [Hash] Optional version numbers per prompt + # @param labels [Hash] Optional labels per specific prompts (overrides default_label) # @return [Hash] Results with :success and :failed arrays # # @example Auto-discover and warm all prompts with "production" label @@ -119,8 +120,8 @@ def warm_all(default_label: "production", versions: {}, labels: {}) # Useful when you want to abort deployment if cache warming fails. # # @param prompt_names [Array] List of prompt names to cache - # @param versions [Hash, nil] Optional version numbers per prompt - # @param labels [Hash, nil] Optional labels per prompt + # @param versions [Hash] Optional version numbers per prompt + # @param labels [Hash] Optional labels per prompt # @return [Hash] Results with :success array # @raise [CacheWarmingError] if any prompts fail to cache # diff --git a/lib/langfuse/chat_prompt_client.rb b/lib/langfuse/chat_prompt_client.rb index 1494359..6075414 100644 --- a/lib/langfuse/chat_prompt_client.rb +++ b/lib/langfuse/chat_prompt_client.rb @@ -20,7 +20,23 @@ module Langfuse # chat_prompt.labels # => ["production"] # class ChatPromptClient - attr_reader :name, :version, :labels, :tags, :config, :prompt + # @return [String] Prompt name + attr_reader :name + + # @return [Integer] Prompt version number + attr_reader :version + + # @return [Array] Labels assigned to this prompt + attr_reader :labels + + # @return [Array] Tags assigned to this prompt + attr_reader :tags + + # @return [Hash] Prompt configuration + attr_reader :config + + # @return [Array] Array of message hashes with role and content + attr_reader :prompt # Initialize a new chat prompt client # diff --git a/lib/langfuse/client.rb b/lib/langfuse/client.rb index 1756e70..4503a40 100644 --- a/lib/langfuse/client.rb +++ b/lib/langfuse/client.rb @@ -19,13 +19,19 @@ module Langfuse # # rubocop:disable Metrics/ClassLength class Client + # @return [Integer] Default page size when fetching all dataset items DATASET_ITEMS_PAGE_SIZE = 50 - attr_reader :config, :api_client + # @return [Config] The client configuration + attr_reader :config + + # @return [ApiClient] The underlying API client + attr_reader :api_client # Initialize a new Langfuse client # # @param config [Config] Configuration object + # @return [Client] def initialize(config) @config = config @config.validate! @@ -362,6 +368,9 @@ def shutdown # @return [DatasetClient] The created dataset client # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # dataset = client.create_dataset(name: "my-dataset", description: "QA evaluation set") def create_dataset(name:, description: nil, metadata: nil) data = api_client.create_dataset(name: name, description: description, metadata: metadata) DatasetClient.new(data, client: self) @@ -374,6 +383,9 @@ def create_dataset(name:, description: nil, metadata: nil) # @raise [NotFoundError] if the dataset is not found # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # dataset = client.get_dataset("my-dataset") def get_dataset(name) data = api_client.get_dataset(name) DatasetClient.new(data, client: self) @@ -386,6 +398,9 @@ def get_dataset(name) # @return [Array] Array of dataset metadata hashes # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # datasets = client.list_datasets(page: 1, limit: 10) def list_datasets(page: nil, limit: nil) api_client.list_datasets(page: page, limit: limit) end @@ -403,6 +418,13 @@ def list_datasets(page: nil, limit: nil) # @return [DatasetItemClient] The created dataset item client # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # item = client.create_dataset_item( + # dataset_name: "my-dataset", + # input: { query: "What is Ruby?" }, + # expected_output: { answer: "A programming language" } + # ) # rubocop:disable Metrics/ParameterLists def create_dataset_item(dataset_name:, input: nil, expected_output: nil, metadata: nil, id: nil, source_trace_id: nil, @@ -423,6 +445,9 @@ def create_dataset_item(dataset_name:, input: nil, expected_output: nil, # @raise [NotFoundError] if the item is not found # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # item = client.get_dataset_item("item-uuid-123") def get_dataset_item(id) data = api_client.get_dataset_item(id) DatasetItemClient.new(data, client: self) @@ -441,6 +466,9 @@ def get_dataset_item(id) # @return [Array] Array of dataset item clients # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors + # + # @example + # items = client.list_dataset_items(dataset_name: "my-dataset", limit: 50) def list_dataset_items(dataset_name:, page: nil, limit: nil, source_trace_id: nil, source_observation_id: nil) filters = { dataset_name: dataset_name, source_trace_id: source_trace_id, @@ -462,6 +490,9 @@ def list_dataset_items(dataset_name:, page: nil, limit: nil, # @raise [UnauthorizedError] if authentication fails # @raise [ApiError] for other API errors # @note 404 responses are treated as success to keep DELETE idempotent across retries + # + # @example + # client.delete_dataset_item("item-uuid-123") def delete_dataset_item(id) api_client.delete_dataset_item(id) nil diff --git a/lib/langfuse/config.rb b/lib/langfuse/config.rb index bdc9993..734e66b 100644 --- a/lib/langfuse/config.rb +++ b/lib/langfuse/config.rb @@ -68,27 +68,50 @@ class Config # @return [Symbol] ActiveJob queue name for async processing attr_accessor :job_queue - # Default values + # @return [String] Default Langfuse API base URL DEFAULT_BASE_URL = "https://cloud.langfuse.com" + + # @return [Integer] Default HTTP request timeout in seconds DEFAULT_TIMEOUT = 5 + + # @return [Integer] Default cache TTL in seconds DEFAULT_CACHE_TTL = 60 + + # @return [Integer] Default maximum number of cached items DEFAULT_CACHE_MAX_SIZE = 1000 + + # @return [Symbol] Default cache backend DEFAULT_CACHE_BACKEND = :memory + + # @return [Integer] Default lock timeout in seconds for cache stampede protection DEFAULT_CACHE_LOCK_TIMEOUT = 10 + + # @return [Boolean] Default stale-while-revalidate setting DEFAULT_CACHE_STALE_WHILE_REVALIDATE = false + + # @return [Integer] Default number of background threads for cache refresh DEFAULT_CACHE_REFRESH_THREADS = 5 + + # @return [Boolean] Default async processing setting DEFAULT_TRACING_ASYNC = true + + # @return [Integer] Default number of events to batch before sending DEFAULT_BATCH_SIZE = 50 + + # @return [Integer] Default flush interval in seconds DEFAULT_FLUSH_INTERVAL = 10 + + # @return [Symbol] Default ActiveJob queue name DEFAULT_JOB_QUEUE = :default - # Number of seconds representing indefinite cache duration (~1000 years) + # @return [Integer] Number of seconds representing indefinite cache duration (~1000 years) INDEFINITE_SECONDS = 1000 * 365 * 24 * 60 * 60 # Initialize a new Config object # # @yield [config] Optional block for configuration # @yieldparam config [Config] The config instance + # @return [Config] a new Config instance def initialize @public_key = ENV.fetch("LANGFUSE_PUBLIC_KEY", nil) @secret_key = ENV.fetch("LANGFUSE_SECRET_KEY", nil) diff --git a/lib/langfuse/dataset_client.rb b/lib/langfuse/dataset_client.rb index 5c3b718..dfc358f 100644 --- a/lib/langfuse/dataset_client.rb +++ b/lib/langfuse/dataset_client.rb @@ -13,18 +13,31 @@ module Langfuse # # @example Running an experiment # dataset.run_experiment(name: "v1", task: ->(item) { llm_call(item.input) }) + # class DatasetClient include TimestampParser - # @return [String] dataset ID - # @return [String] dataset name - # @return [String, nil] optional description - # @return [Hash] metadata hash - # @return [Time, nil] creation timestamp - # @return [Time, nil] last update timestamp - attr_reader :id, :name, :description, :metadata, :created_at, :updated_at + # @return [String] Unique identifier for the dataset + attr_reader :id + + # @return [String] Human-readable name of the dataset + attr_reader :name + + # @return [String, nil] Optional description of the dataset + attr_reader :description + + # @return [Hash] Additional metadata as key-value pairs + attr_reader :metadata - # @param dataset_data [Hash] raw dataset hash from the API (string keys) + # @return [Time, nil] Timestamp when the dataset was created + attr_reader :created_at + + # @return [Time, nil] Timestamp when the dataset was last updated + attr_reader :updated_at + + # Initialize a new dataset client from API response data + # + # @param dataset_data [Hash] Raw dataset data from the API (string keys) # @param client [Client, nil] Langfuse client for API operations # @raise [ArgumentError] if dataset_data is not a Hash or missing required fields def initialize(dataset_data, client: nil) @@ -39,7 +52,9 @@ def initialize(dataset_data, client: nil) @client = client end - # @return [Array] + # Lazily-parsed dataset items + # + # @return [Array] Items belonging to this dataset def items @items ||= if @raw_items.empty? && @client @client.list_dataset_items(dataset_name: @name) diff --git a/lib/langfuse/dataset_item_client.rb b/lib/langfuse/dataset_item_client.rb index 4924929..c8df97a 100644 --- a/lib/langfuse/dataset_item_client.rb +++ b/lib/langfuse/dataset_item_client.rb @@ -14,24 +14,43 @@ module Langfuse # item.run(run_name: "eval-v1") do |span| # my_llm_call(item.input) # end + # class DatasetItemClient include TimestampParser - # @return [String] item ID - # @return [String] parent dataset ID - # @return [Object, nil] input data - # @return [Object, nil] expected output for evaluation - # @return [Hash] metadata hash - # @return [String, nil] source trace ID - # @return [String, nil] source observation ID - # @return [String] item status ("ACTIVE" or "ARCHIVED") - # @return [Time, nil] creation timestamp - # @return [Time, nil] last update timestamp - attr_reader :id, :dataset_id, :input, :expected_output, :metadata, - :source_trace_id, :source_observation_id, :status, - :created_at, :updated_at - - # @param item_data [Hash] raw item hash from the API (string keys) + # @return [String] Unique identifier for the dataset item + attr_reader :id + + # @return [String] Identifier of the parent dataset + attr_reader :dataset_id + + # @return [Object, nil] Input data for the dataset item + attr_reader :input + + # @return [Object, nil] Expected output for evaluation + attr_reader :expected_output + + # @return [Hash] Additional metadata as key-value pairs + attr_reader :metadata + + # @return [String, nil] Trace ID that produced this item + attr_reader :source_trace_id + + # @return [String, nil] Observation ID that produced this item + attr_reader :source_observation_id + + # @return [String] Item status (ACTIVE or ARCHIVED) + attr_reader :status + + # @return [Time, nil] Timestamp when the item was created + attr_reader :created_at + + # @return [Time, nil] Timestamp when the item was last updated + attr_reader :updated_at + + # Initialize a new dataset item client from API response data + # + # @param item_data [Hash] Raw item data from the API (string keys) # @param client [Client, nil] Langfuse client for API operations # @raise [ArgumentError] if item_data is not a Hash or missing required fields def initialize(item_data, client: nil) diff --git a/lib/langfuse/observations.rb b/lib/langfuse/observations.rb index 6e3210b..e8a412c 100644 --- a/lib/langfuse/observations.rb +++ b/lib/langfuse/observations.rb @@ -58,7 +58,14 @@ module Langfuse # # @abstract Subclass and pass type: to super to create concrete observation types class BaseObservation - attr_reader :otel_span, :otel_tracer, :type + # @return [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + attr_reader :otel_span + + # @return [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + attr_reader :otel_tracer + + # @return [String] Observation type (e.g., "span", "generation", "event") + attr_reader :type # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer @@ -95,7 +102,10 @@ def trace_url Langfuse.client.trace_url(trace_id) end + # Ends the observation span. + # # @param end_time [Time, Integer, nil] Optional end time (Time object or Unix timestamp in nanoseconds) + # @return [void] def end(end_time: nil) @otel_span.finish(end_timestamp: end_time) end @@ -154,6 +164,7 @@ def start_observation(name, attrs = {}, as_type: :span, &block) # Sets observation-level input attributes. # # @param value [Object] Input value (will be JSON-encoded) + # @return [void] def input=(value) update_observation_attributes(input: value) end @@ -161,24 +172,29 @@ def input=(value) # Sets observation-level output attributes. # # @param value [Object] Output value (will be JSON-encoded) + # @return [void] def output=(value) update_observation_attributes(output: value) end # @param value [Hash] Metadata hash (expanded into individual langfuse.observation.metadata.* attributes) + # @return [void] def metadata=(value) update_observation_attributes(metadata: value) end # @param value [String] Level (DEBUG, DEFAULT, WARNING, ERROR) + # @return [void] def level=(value) update_observation_attributes(level: value) end + # Adds an event to this observation's span. + # # @param name [String] Event name # @param input [Object, nil] Optional event data # @param level [String] Log level (debug, default, warning, error) - # + # @return [void] def event(name:, input: nil, level: "default") attributes = { "langfuse.observation.input" => input&.to_json, @@ -260,6 +276,9 @@ def normalize_prompt(prompt) # span.end # class Span < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::SpanAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:span]) end @@ -296,6 +315,9 @@ def update(attrs) # gen.end # class Generation < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::GenerationAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:generation]) end @@ -308,6 +330,7 @@ def update(attrs) end # @param value [Hash] Usage hash with token counts (:prompt_tokens, :completion_tokens, :total_tokens) + # @return [void] def usage=(value) return unless @otel_span.recording? @@ -323,6 +346,7 @@ def usage=(value) end # @param value [String] Model name (e.g., "gpt-4", "claude-3-opus") + # @return [void] def model=(value) return unless @otel_span.recording? @@ -330,6 +354,7 @@ def model=(value) end # @param value [Hash] Model parameters (temperature, max_tokens, etc.) + # @return [void] def model_parameters=(value) return unless @otel_span.recording? @@ -361,6 +386,9 @@ def model_parameters=(value) # # Event is automatically ended # class Event < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::SpanAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:event]) end @@ -395,6 +423,9 @@ def update(attrs) # agent.end # class Agent < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::SpanAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:agent]) end @@ -425,6 +456,9 @@ def update(attrs) # tool.end # class Tool < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::SpanAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:tool]) end @@ -464,6 +498,9 @@ def update(attrs) # chain.end # class Chain < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::SpanAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:chain]) end @@ -497,6 +534,9 @@ def update(attrs) # retriever.end # class Retriever < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::SpanAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:retriever]) end @@ -530,6 +570,9 @@ def update(attrs) # evaluator.end # class Evaluator < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::SpanAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:evaluator]) end @@ -563,6 +606,9 @@ def update(attrs) # guardrail.end # class Guardrail < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::SpanAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:guardrail]) end @@ -601,6 +647,9 @@ def update(attrs) # embedding.end # class Embedding < BaseObservation + # @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span + # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer + # @param attributes [Hash, Types::EmbeddingAttributes, nil] Optional initial attributes def initialize(otel_span, otel_tracer, attributes: nil) super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:embedding]) end @@ -613,16 +662,19 @@ def update(attrs) end # @param value [Hash] Usage hash with token counts (:prompt_tokens, :total_tokens) + # @return [void] def usage=(value) update_observation_attributes(usage_details: value) end # @param value [String] Model name (e.g., "text-embedding-ada-002") + # @return [void] def model=(value) update_observation_attributes(model: value) end # @param value [Hash] Model parameters (temperature, max_tokens, etc.) + # @return [void] def model_parameters=(value) update_observation_attributes(model_parameters: value) end diff --git a/lib/langfuse/otel_setup.rb b/lib/langfuse/otel_setup.rb index 7f7a074..846c66d 100644 --- a/lib/langfuse/otel_setup.rb +++ b/lib/langfuse/otel_setup.rb @@ -13,6 +13,7 @@ module Langfuse # module OtelSetup class << self + # @return [OpenTelemetry::SDK::Trace::TracerProvider, nil] The configured tracer provider attr_reader :tracer_provider # Initialize OpenTelemetry with Langfuse OTLP exporter diff --git a/lib/langfuse/prompt_cache.rb b/lib/langfuse/prompt_cache.rb index 2f0925a..e3b629c 100644 --- a/lib/langfuse/prompt_cache.rb +++ b/lib/langfuse/prompt_cache.rb @@ -53,7 +53,17 @@ def expired? end end - attr_reader :ttl, :max_size, :stale_ttl, :logger + # @return [Integer] Time-to-live in seconds + attr_reader :ttl + + # @return [Integer] Maximum number of cache entries + attr_reader :max_size + + # @return [Integer] Stale TTL for SWR in seconds + attr_reader :stale_ttl + + # @return [Logger] Logger instance for error reporting + attr_reader :logger # Initialize a new cache # diff --git a/lib/langfuse/propagation.rb b/lib/langfuse/propagation.rb index 07f92b0..735621a 100644 --- a/lib/langfuse/propagation.rb +++ b/lib/langfuse/propagation.rb @@ -60,6 +60,7 @@ module Propagation # @param as_baggage [Boolean] If true, propagates via OpenTelemetry baggage for cross-service propagation # @yield Block within which attributes are propagated # @return [Object] The result of the block + # @raise [ArgumentError] if no block is given # # @example Basic usage # Langfuse.propagate_attributes(user_id: "user_123", session_id: "session_abc") do @@ -329,6 +330,7 @@ def self._validate_string_value(value, key) # # @param key [String] Attribute key (user_id, session_id, etc.) # @return [OpenTelemetry::Context::Key] Context key object + # @raise [ArgumentError] if key is not a known propagated attribute # # @api private def self._get_propagated_context_key(key) diff --git a/lib/langfuse/rails_cache_adapter.rb b/lib/langfuse/rails_cache_adapter.rb index 99edb16..319b24c 100644 --- a/lib/langfuse/rails_cache_adapter.rb +++ b/lib/langfuse/rails_cache_adapter.rb @@ -17,7 +17,23 @@ module Langfuse class RailsCacheAdapter include StaleWhileRevalidate - attr_reader :ttl, :namespace, :lock_timeout, :stale_ttl, :thread_pool, :logger + # @return [Integer] Time-to-live in seconds + attr_reader :ttl + + # @return [String] Cache key namespace + attr_reader :namespace + + # @return [Integer] Lock timeout in seconds for stampede protection + attr_reader :lock_timeout + + # @return [Integer] Stale TTL for SWR in seconds + attr_reader :stale_ttl + + # @return [Concurrent::CachedThreadPool, nil] Thread pool for background refreshes + attr_reader :thread_pool + + # @return [Logger] Logger instance for error reporting + attr_reader :logger # Initialize a new Rails.cache adapter # diff --git a/lib/langfuse/score_client.rb b/lib/langfuse/score_client.rb index db6b61f..b69fee9 100644 --- a/lib/langfuse/score_client.rb +++ b/lib/langfuse/score_client.rb @@ -22,7 +22,14 @@ module Langfuse # @api private # rubocop:disable Metrics/ClassLength class ScoreClient - attr_reader :api_client, :config, :logger + # @return [ApiClient] The API client for sending batches + attr_reader :api_client + + # @return [Config] Configuration object + attr_reader :config + + # @return [Logger] Logger instance + attr_reader :logger # Initialize a new ScoreClient # diff --git a/lib/langfuse/text_prompt_client.rb b/lib/langfuse/text_prompt_client.rb index 20f7eaa..d559ba5 100644 --- a/lib/langfuse/text_prompt_client.rb +++ b/lib/langfuse/text_prompt_client.rb @@ -20,7 +20,23 @@ module Langfuse # text_prompt.labels # => ["production"] # class TextPromptClient - attr_reader :name, :version, :labels, :tags, :config, :prompt + # @return [String] Prompt name + attr_reader :name + + # @return [Integer] Prompt version number + attr_reader :version + + # @return [Array] Labels assigned to this prompt + attr_reader :labels + + # @return [Array] Tags assigned to this prompt + attr_reader :tags + + # @return [Hash] Prompt configuration + attr_reader :config + + # @return [String] Raw prompt template string + attr_reader :prompt # Initialize a new text prompt client #