langfuse-ruby 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0bb59924d3a9dcb1cb99841c6d07dd1277838ffee389d13ada1c8b03cafeb524
4
- data.tar.gz: c5ba1846cd51fc3dc82fb43dd335d88394549652531a5243101ccd43ef71b3fa
3
+ metadata.gz: 35c5fecfa9511702e3119b58f09de37402f8fdefa118a8f848b4e64a439a383c
4
+ data.tar.gz: 0057c42d68a7f170f5118efc9378b751d627ef51a11fc87ec965d60be049645b
5
5
  SHA512:
6
- metadata.gz: 9e448848c65cb8cd6f7ab7e4e566787bcab4aa8c43f20da7cc7a7a39e24e888c734c23644682f080635a0288b40fc4e3e169d8576445fdd07dde8d676faaccca
7
- data.tar.gz: 9d9d1e88bf2e1161bd1d7f316e17600904169344b679839992a227eb35ac48e7edbe188ce782e3757f9f03a8a39104eac8c171a23f7bdc0c54b707754dfd87e8
6
+ metadata.gz: ee91c309e962d21351e33a3683602b873d167038d90488e0460677959d7c32ad3f715bd77672d3fd6f7f0e21601370fc75fbd6aa2562add32e54b7ae14e79912
7
+ data.tar.gz: f336046b47e535cf36c0f562bb012e419801300aff98acc49e9ee13aebb037d65785c21491a41f6f0cc3cda92ba839bebfa9cbf248e83fa4451939573ca139ec
@@ -9,26 +9,26 @@ on:
9
9
  jobs:
10
10
  test:
11
11
  runs-on: ubuntu-latest
12
-
12
+
13
13
  strategy:
14
14
  matrix:
15
- ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3']
16
-
15
+ ruby-version: ['3.1', '3.2', '3.3']
16
+
17
17
  steps:
18
18
  - uses: actions/checkout@v4
19
-
19
+
20
20
  - name: Set up Ruby ${{ matrix.ruby-version }}
21
21
  uses: ruby/setup-ruby@v1
22
22
  with:
23
23
  ruby-version: ${{ matrix.ruby-version }}
24
24
  bundler-cache: true
25
-
25
+
26
26
  - name: Run tests
27
27
  run: bundle exec rspec
28
-
28
+
29
29
  - name: Run offline tests
30
- run: ruby test_offline.rb
31
-
30
+ run: bundle exec ruby test_offline.rb
31
+
32
32
  - name: Run RuboCop
33
33
  run: bundle exec rubocop
34
34
  continue-on-error: true
data/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.5] - 2025-12-26
11
+
12
+ ### Added
13
+ - Support for all enhanced observation types: `agent`, `tool`, `chain`, `retriever`, `embedding`, `evaluator`, `guardrail`
14
+ - New `ObservationType` module with constants for all observation types
15
+ - Convenience methods on `Client` and `Trace` for creating enhanced observations
16
+ - New `as_type` parameter on `span()` method for specifying observation type
17
+ - Comprehensive test coverage for enhanced observation types
18
+
19
+ ### Fixed
20
+ - Fixed URL encoding for prompt names containing special characters (/, spaces, etc.) in `get_prompt` method
21
+ - Prompt names are now automatically URL-encoded before being interpolated into API paths
22
+
23
+ ### Changed
24
+ - Updated Ruby version requirement to >= 3.1.0
25
+ - Environment variables moved from metadata to top-level trace attributes
26
+
27
+ ### Internal
28
+ - Added `Utils.url_encode` helper method for consistent URL encoding across the SDK
29
+ - CI improvements for offline test execution
30
+
10
31
  ## [0.1.4] - 2025-07-29
11
32
 
12
33
  ### Added
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- langfuse-ruby (0.1.4)
4
+ langfuse-ruby (0.1.5)
5
5
  concurrent-ruby (~> 1.0)
6
6
  faraday (>= 1.8, < 3.0)
7
+ faraday-multipart (~> 1.0)
7
8
  faraday-net_http (>= 1.0, < 4.0)
8
9
  json (~> 2.0)
9
10
 
@@ -24,6 +25,8 @@ GEM
24
25
  faraday-net_http (>= 2.0, < 3.5)
25
26
  json
26
27
  logger
28
+ faraday-multipart (1.1.1)
29
+ multipart-post (~> 2.0)
27
30
  faraday-net_http (3.4.1)
28
31
  net-http (>= 0.5.0)
29
32
  hashdiff (1.2.0)
@@ -31,6 +34,7 @@ GEM
31
34
  language_server-protocol (3.17.0.5)
32
35
  lint_roller (1.1.0)
33
36
  logger (1.7.0)
37
+ multipart-post (2.4.1)
34
38
  net-http (0.6.0)
35
39
  uri
36
40
  parallel (1.27.0)
data/README.md CHANGED
@@ -69,7 +69,7 @@ trace = client.trace(
69
69
  name: "chat-completion",
70
70
  user_id: "user123",
71
71
  session_id: "session456",
72
- metadata: { environment: "production" }
72
+ environment: "production"
73
73
  )
74
74
 
75
75
  # Add a generation (LLM call)
@@ -173,6 +173,9 @@ event = client.event(
173
173
  # Get a prompt
174
174
  prompt = client.get_prompt("chat-prompt", version: 1)
175
175
 
176
+ # Prompt names with special characters are automatically URL-encoded
177
+ prompt = client.get_prompt("EXEMPLE/my-prompt") # Works correctly!
178
+
176
179
  # Compile prompt with variables
177
180
  compiled = prompt.compile(
178
181
  user_name: "Alice",
@@ -183,6 +186,8 @@ puts compiled
183
186
  # Output: "Hello Alice! Let's discuss machine learning today."
184
187
  ```
185
188
 
189
+ > **Note**: Prompt names containing special characters (like `/`, spaces, `?`, etc.) are automatically URL-encoded. You don't need to manually encode them.
190
+
186
191
  ### Create Prompts
187
192
 
188
193
  ```ruby
@@ -0,0 +1,164 @@
1
+ # URL Encoding Fix for Prompt Names
2
+
3
+ ## Issue Summary
4
+
5
+ **Issue ID**: AIF-2
6
+ **Title**: Prompt names with `/` are not URL-encoded
7
+ **Status**: Fixed
8
+
9
+ ## Problem Description
10
+
11
+ Prompt names containing special characters (particularly `/`) were not being URL-encoded before being interpolated into the API path when using `get_prompt`. This caused the server to misinterpret the prompt name as a nested path structure, resulting in 404 errors.
12
+
13
+ ### Example
14
+
15
+ ```ruby
16
+ # Before fix
17
+ client.get_prompt("EXEMPLE/my-prompt")
18
+
19
+ # Generated URL (broken):
20
+ # /api/public/v2/prompts/EXEMPLE/my-prompt
21
+ # Server interprets this as: /api/public/v2/prompts/EXEMPLE/<nested-path>/my-prompt
22
+ # Result: 404 Not Found
23
+ ```
24
+
25
+ ### Expected Behavior
26
+
27
+ ```ruby
28
+ # After fix
29
+ client.get_prompt("EXEMPLE/my-prompt")
30
+
31
+ # Generated URL (correct):
32
+ # /api/public/v2/prompts/EXEMPLE%2Fmy-prompt
33
+ # Server correctly interprets this as a single prompt name
34
+ # Result: Prompt found successfully
35
+ ```
36
+
37
+ ## Solution
38
+
39
+ ### Changes Made
40
+
41
+ 1. **Added URL encoding utility** (`lib/langfuse/utils.rb`):
42
+ - Added `Utils.url_encode` method using Ruby's `ERB::Util.url_encode`
43
+ - Provides consistent URL encoding across the SDK
44
+
45
+ 2. **Updated `get_prompt` method** (`lib/langfuse/client.rb`):
46
+ - Automatically URL-encodes prompt names before constructing API paths
47
+ - No breaking changes - existing code continues to work
48
+
49
+ 3. **Added comprehensive tests** (`spec/langfuse/client_spec.rb`):
50
+ - Tests for forward slashes in prompt names
51
+ - Tests for spaces in prompt names
52
+ - Tests for multiple special characters
53
+ - Tests for simple names (no encoding needed)
54
+
55
+ 4. **Updated documentation**:
56
+ - Added note in README about automatic URL encoding
57
+ - Added example demonstrating the feature
58
+ - Updated CHANGELOG with fix details
59
+
60
+ ### Code Changes
61
+
62
+ #### lib/langfuse/utils.rb
63
+ ```ruby
64
+ def url_encode(string)
65
+ ERB::Util.url_encode(string.to_s)
66
+ end
67
+ ```
68
+
69
+ #### lib/langfuse/client.rb
70
+ ```ruby
71
+ def get_prompt(name, version: nil, label: nil, cache_ttl_seconds: 60)
72
+ # ... existing code ...
73
+
74
+ encoded_name = Utils.url_encode(name)
75
+ path = "/api/public/v2/prompts/#{encoded_name}"
76
+
77
+ # ... rest of method ...
78
+ end
79
+ ```
80
+
81
+ ## Testing
82
+
83
+ ### Test Coverage
84
+
85
+ All test cases pass successfully:
86
+
87
+ ```bash
88
+ $ bundle exec rspec spec/langfuse/client_spec.rb -e "get_prompt"
89
+
90
+ Langfuse::Client
91
+ #get_prompt
92
+ URL-encodes prompt names with special characters ✓
93
+ URL-encodes prompt names with spaces ✓
94
+ URL-encodes prompt names with multiple special characters ✓
95
+ handles simple prompt names without special characters ✓
96
+
97
+ 4 examples, 0 failures
98
+ ```
99
+
100
+ ### Test Cases
101
+
102
+ 1. **Forward slash**: `EXEMPLE/my-prompt` → `EXEMPLE%2Fmy-prompt`
103
+ 2. **Spaces**: `my prompt` → `my%20prompt`
104
+ 3. **Multiple special chars**: `test/prompt name?query` → `test%2Fprompt%20name%3Fquery`
105
+ 4. **Simple names**: `simple-prompt` → `simple-prompt` (no encoding needed)
106
+
107
+ ## Usage
108
+
109
+ ### Before Fix (Workaround)
110
+
111
+ Users had to manually encode prompt names:
112
+
113
+ ```ruby
114
+ encoded_name = ERB::Util.url_encode("EXEMPLE/my-prompt")
115
+ client.get_prompt(encoded_name)
116
+ ```
117
+
118
+ ### After Fix (Automatic)
119
+
120
+ Users can now use prompt names directly:
121
+
122
+ ```ruby
123
+ # Works automatically!
124
+ client.get_prompt("EXEMPLE/my-prompt")
125
+ client.get_prompt("my prompt name")
126
+ client.get_prompt("test/prompt?query")
127
+ ```
128
+
129
+ ## Backward Compatibility
130
+
131
+ This fix is **100% backward compatible**:
132
+
133
+ - Existing code continues to work without changes
134
+ - Simple prompt names (without special characters) work exactly as before
135
+ - If users were manually encoding names, double-encoding is prevented by the URL encoding algorithm
136
+
137
+ ## Special Characters Supported
138
+
139
+ The following special characters are now properly encoded:
140
+
141
+ - `/` (forward slash) → `%2F`
142
+ - ` ` (space) → `%20`
143
+ - `?` (question mark) → `%3F`
144
+ - `#` (hash) → `%23`
145
+ - `@` (at sign) → `%40`
146
+ - `&` (ampersand) → `%26`
147
+ - `=` (equals) → `%3D`
148
+ - `+` (plus) → `%2B`
149
+ - And all other URL-unsafe characters
150
+
151
+ ## Related Files
152
+
153
+ - `lib/langfuse/utils.rb` - URL encoding utility
154
+ - `lib/langfuse/client.rb` - Updated `get_prompt` method
155
+ - `spec/langfuse/client_spec.rb` - Test coverage
156
+ - `examples/url_encoding_demo.rb` - Usage examples
157
+ - `CHANGELOG.md` - Change documentation
158
+ - `README.md` - Updated documentation
159
+
160
+ ## References
161
+
162
+ - Linear Issue: AIF-2
163
+ - Ruby ERB::Util documentation: https://ruby-doc.org/stdlib-3.0.0/libdoc/erb/rdoc/ERB/Util.html
164
+ - URL encoding standard: RFC 3986
@@ -19,8 +19,8 @@ trace = client.trace(
19
19
  user_id: 'user-123',
20
20
  session_id: 'session-456',
21
21
  input: { message: 'Hello, how are you?' },
22
+ environment: 'development',
22
23
  metadata: {
23
- environment: 'development',
24
24
  version: '1.0.0'
25
25
  }
26
26
  )
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # This example demonstrates URL encoding for prompt names with special characters
5
+ # such as forward slashes, spaces, and other URL-unsafe characters.
6
+
7
+ require_relative '../lib/langfuse'
8
+
9
+ # Initialize the Langfuse client
10
+ client = Langfuse::Client.new(
11
+ public_key: ENV['LANGFUSE_PUBLIC_KEY'] || 'your-public-key',
12
+ secret_key: ENV['LANGFUSE_SECRET_KEY'] || 'your-secret-key',
13
+ host: ENV['LANGFUSE_HOST'] || 'https://cloud.langfuse.com'
14
+ )
15
+
16
+ # Example 1: Prompt name with forward slash
17
+ # Before fix: Would result in 404 error
18
+ # After fix: Automatically URL-encoded to EXEMPLE%2Fmy-prompt
19
+ puts 'Example 1: Fetching prompt with forward slash in name'
20
+ begin
21
+ prompt = client.get_prompt('EXEMPLE/my-prompt')
22
+ puts "✓ Successfully fetched prompt: #{prompt.name}"
23
+ rescue Langfuse::ValidationError => e
24
+ puts "✗ Error: #{e.message}"
25
+ end
26
+
27
+ # Example 2: Prompt name with spaces
28
+ puts "\nExample 2: Fetching prompt with spaces in name"
29
+ begin
30
+ prompt = client.get_prompt('my prompt name')
31
+ puts "✓ Successfully fetched prompt: #{prompt.name}"
32
+ rescue Langfuse::ValidationError => e
33
+ puts "✗ Error: #{e.message}"
34
+ end
35
+
36
+ # Example 3: Prompt name with multiple special characters
37
+ puts "\nExample 3: Fetching prompt with multiple special characters"
38
+ begin
39
+ prompt = client.get_prompt('test/prompt name?query')
40
+ puts "✓ Successfully fetched prompt: #{prompt.name}"
41
+ rescue Langfuse::ValidationError => e
42
+ puts "✗ Error: #{e.message}"
43
+ end
44
+
45
+ # Example 4: Simple prompt name (no special characters)
46
+ puts "\nExample 4: Fetching prompt with simple name"
47
+ begin
48
+ prompt = client.get_prompt('simple-prompt')
49
+ puts "✓ Successfully fetched prompt: #{prompt.name}"
50
+ rescue Langfuse::ValidationError => e
51
+ puts "✗ Error: #{e.message}"
52
+ end
53
+
54
+ puts "\n" + '=' * 60
55
+ puts 'Note: The client now automatically URL-encodes prompt names.'
56
+ puts 'You no longer need to manually encode them!'
57
+ puts '=' * 60
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  'and evaluation capabilities for LLM applications'
13
13
  spec.homepage = 'https://langfuse.com'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = '>= 2.7.0'
15
+ spec.required_ruby_version = '>= 3.1.0'
16
16
 
17
17
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
18
  spec.metadata['homepage_uri'] = 'https://langfuse.com/docs/sdk/ruby'
@@ -24,7 +24,9 @@ Gem::Specification.new do |spec|
24
24
  # Specify which files should be added to the gem when it is released.
25
25
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
26
  if File.exist?('.git')
27
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ `git ls-files -z`.split("\x0").reject do |f|
28
+ (f == 'ISSUE_AIF-2_RESOLUTION.md') || f.match(%r{\A(?:test|spec|features)/})
29
+ end
28
30
  else
29
31
  Dir.glob('**/*').reject do |f|
30
32
  File.directory?(f) ||
@@ -43,6 +45,7 @@ Gem::Specification.new do |spec|
43
45
  spec.add_dependency 'concurrent-ruby', '~> 1.0'
44
46
  spec.add_dependency 'faraday', '>= 1.8', '< 3.0'
45
47
  spec.add_dependency 'faraday-net_http', '>= 1.0', '< 4.0'
48
+ spec.add_dependency 'faraday-multipart', '~> 1.0'
46
49
  spec.add_dependency 'json', '~> 2.0'
47
50
 
48
51
  # Development dependencies
@@ -1,5 +1,6 @@
1
1
  require 'faraday'
2
2
  require 'faraday/net_http'
3
+ require 'faraday/multipart'
3
4
  require 'json'
4
5
  require 'base64'
5
6
  require 'concurrent'
@@ -54,7 +55,7 @@ module Langfuse
54
55
  # Span operations
55
56
  def span(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
56
57
  metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
57
- version: nil, **kwargs)
58
+ version: nil, as_type: nil, **kwargs)
58
59
  Span.new(
59
60
  client: self,
60
61
  trace_id: trace_id,
@@ -68,6 +69,159 @@ module Langfuse
68
69
  status_message: status_message,
69
70
  parent_observation_id: parent_observation_id,
70
71
  version: version,
72
+ as_type: as_type,
73
+ **kwargs
74
+ )
75
+ end
76
+
77
+ # Convenience methods for enhanced observation types
78
+
79
+ # Create an agent observation (wrapper around span with as_type: 'agent')
80
+ def agent(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
81
+ metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
82
+ version: nil, **kwargs)
83
+ span(
84
+ trace_id: trace_id,
85
+ name: name,
86
+ start_time: start_time,
87
+ end_time: end_time,
88
+ input: input,
89
+ output: output,
90
+ metadata: metadata,
91
+ level: level,
92
+ status_message: status_message,
93
+ parent_observation_id: parent_observation_id,
94
+ version: version,
95
+ as_type: ObservationType::AGENT,
96
+ **kwargs
97
+ )
98
+ end
99
+
100
+ # Create a tool observation (wrapper around span with as_type: 'tool')
101
+ def tool(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
102
+ metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
103
+ version: nil, **kwargs)
104
+ span(
105
+ trace_id: trace_id,
106
+ name: name,
107
+ start_time: start_time,
108
+ end_time: end_time,
109
+ input: input,
110
+ output: output,
111
+ metadata: metadata,
112
+ level: level,
113
+ status_message: status_message,
114
+ parent_observation_id: parent_observation_id,
115
+ version: version,
116
+ as_type: ObservationType::TOOL,
117
+ **kwargs
118
+ )
119
+ end
120
+
121
+ # Create a chain observation (wrapper around span with as_type: 'chain')
122
+ def chain(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
123
+ metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
124
+ version: nil, **kwargs)
125
+ span(
126
+ trace_id: trace_id,
127
+ name: name,
128
+ start_time: start_time,
129
+ end_time: end_time,
130
+ input: input,
131
+ output: output,
132
+ metadata: metadata,
133
+ level: level,
134
+ status_message: status_message,
135
+ parent_observation_id: parent_observation_id,
136
+ version: version,
137
+ as_type: ObservationType::CHAIN,
138
+ **kwargs
139
+ )
140
+ end
141
+
142
+ # Create a retriever observation (wrapper around span with as_type: 'retriever')
143
+ def retriever(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
144
+ metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
145
+ version: nil, **kwargs)
146
+ span(
147
+ trace_id: trace_id,
148
+ name: name,
149
+ start_time: start_time,
150
+ end_time: end_time,
151
+ input: input,
152
+ output: output,
153
+ metadata: metadata,
154
+ level: level,
155
+ status_message: status_message,
156
+ parent_observation_id: parent_observation_id,
157
+ version: version,
158
+ as_type: ObservationType::RETRIEVER,
159
+ **kwargs
160
+ )
161
+ end
162
+
163
+ # Create an embedding observation (wrapper around span with as_type: 'embedding')
164
+ def embedding(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
165
+ model: nil, usage: nil, metadata: nil, level: nil, status_message: nil,
166
+ parent_observation_id: nil, version: nil, **kwargs)
167
+ merged_metadata = (metadata || {}).merge(
168
+ { model: model, usage: usage }.compact
169
+ )
170
+ span(
171
+ trace_id: trace_id,
172
+ name: name,
173
+ start_time: start_time,
174
+ end_time: end_time,
175
+ input: input,
176
+ output: output,
177
+ metadata: merged_metadata.empty? ? nil : merged_metadata,
178
+ level: level,
179
+ status_message: status_message,
180
+ parent_observation_id: parent_observation_id,
181
+ version: version,
182
+ as_type: ObservationType::EMBEDDING,
183
+ **kwargs
184
+ )
185
+ end
186
+
187
+ # Create an evaluator observation (wrapper around span with as_type: 'evaluator')
188
+ def evaluator_obs(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
189
+ metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
190
+ version: nil, **kwargs)
191
+ span(
192
+ trace_id: trace_id,
193
+ name: name,
194
+ start_time: start_time,
195
+ end_time: end_time,
196
+ input: input,
197
+ output: output,
198
+ metadata: metadata,
199
+ level: level,
200
+ status_message: status_message,
201
+ parent_observation_id: parent_observation_id,
202
+ version: version,
203
+ as_type: ObservationType::EVALUATOR,
204
+ **kwargs
205
+ )
206
+ end
207
+
208
+ # Create a guardrail observation (wrapper around span with as_type: 'guardrail')
209
+ def guardrail(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
210
+ metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
211
+ version: nil, **kwargs)
212
+ span(
213
+ trace_id: trace_id,
214
+ name: name,
215
+ start_time: start_time,
216
+ end_time: end_time,
217
+ input: input,
218
+ output: output,
219
+ metadata: metadata,
220
+ level: level,
221
+ status_message: status_message,
222
+ parent_observation_id: parent_observation_id,
223
+ version: version,
224
+ as_type: ObservationType::GUARDRAIL,
71
225
  **kwargs
72
226
  )
73
227
  end
@@ -125,7 +279,8 @@ module Langfuse
125
279
  return cached_prompt[:prompt]
126
280
  end
127
281
 
128
- path = "/api/public/v2/prompts/#{name}"
282
+ encoded_name = Utils.url_encode(name)
283
+ path = "/api/public/v2/prompts/#{encoded_name}"
129
284
  params = {}
130
285
  params[:version] = version if version
131
286
  params[:label] = label if label
@@ -1,11 +1,11 @@
1
1
  module Langfuse
2
2
  class Event
3
3
  attr_reader :id, :trace_id, :name, :start_time, :input, :output, :metadata,
4
- :level, :status_message, :parent_observation_id, :version, :client
4
+ :level, :status_message, :parent_observation_id, :version, :as_type, :client
5
5
 
6
6
  def initialize(client:, trace_id:, name:, id: nil, start_time: nil, input: nil,
7
7
  output: nil, metadata: nil, level: nil, status_message: nil,
8
- parent_observation_id: nil, version: nil, **kwargs)
8
+ parent_observation_id: nil, version: nil, as_type: nil, **kwargs)
9
9
  @client = client
10
10
  @id = id || Utils.generate_id
11
11
  @trace_id = trace_id
@@ -18,6 +18,7 @@ module Langfuse
18
18
  @status_message = status_message
19
19
  @parent_observation_id = parent_observation_id
20
20
  @version = version
21
+ @as_type = validate_as_type(as_type)
21
22
  @kwargs = kwargs
22
23
 
23
24
  # Create the event
@@ -25,7 +26,7 @@ module Langfuse
25
26
  end
26
27
 
27
28
  def to_dict
28
- {
29
+ data = {
29
30
  id: @id,
30
31
  trace_id: @trace_id,
31
32
  name: @name,
@@ -37,11 +38,24 @@ module Langfuse
37
38
  status_message: @status_message,
38
39
  parent_observation_id: @parent_observation_id,
39
40
  version: @version
40
- }.merge(@kwargs).compact
41
+ }
42
+ data[:type] = @as_type if @as_type
43
+ data.merge(@kwargs).compact
41
44
  end
42
45
 
43
46
  private
44
47
 
48
+ def validate_as_type(type)
49
+ return nil if type.nil?
50
+
51
+ type_str = type.to_s
52
+ unless ObservationType.valid?(type_str)
53
+ raise ValidationError, "Invalid observation type: #{type}. Valid types are: #{ObservationType::ALL.join(', ')}"
54
+ end
55
+
56
+ type_str
57
+ end
58
+
45
59
  def create_event
46
60
  data = {
47
61
  id: @id,
@@ -55,7 +69,9 @@ module Langfuse
55
69
  status_message: @status_message,
56
70
  parent_observation_id: @parent_observation_id,
57
71
  version: @version
58
- }.merge(@kwargs).compact
72
+ }
73
+ data[:type] = @as_type if @as_type
74
+ data = data.merge(@kwargs).compact
59
75
 
60
76
  @client.enqueue_event('event-create', data)
61
77
  end