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 +4 -4
- data/.github/workflows/ci.yml +8 -8
- data/CHANGELOG.md +21 -0
- data/Gemfile.lock +5 -1
- data/README.md +6 -1
- data/docs/URL_ENCODING_FIX.md +164 -0
- data/examples/basic_tracing.rb +1 -1
- data/examples/url_encoding_demo.rb +57 -0
- data/langfuse-ruby.gemspec +5 -2
- data/lib/langfuse/client.rb +157 -2
- data/lib/langfuse/event.rb +21 -5
- data/lib/langfuse/generation.rb +155 -5
- data/lib/langfuse/observation_types.rb +61 -0
- data/lib/langfuse/span.rb +162 -7
- data/lib/langfuse/trace.rb +150 -1
- data/lib/langfuse/utils.rb +5 -0
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +1 -0
- data/test_offline.rb +1 -1
- metadata +20 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35c5fecfa9511702e3119b58f09de37402f8fdefa118a8f848b4e64a439a383c
|
|
4
|
+
data.tar.gz: 0057c42d68a7f170f5118efc9378b751d627ef51a11fc87ec965d60be049645b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee91c309e962d21351e33a3683602b873d167038d90488e0460677959d7c32ad3f715bd77672d3fd6f7f0e21601370fc75fbd6aa2562add32e54b7ae14e79912
|
|
7
|
+
data.tar.gz: f336046b47e535cf36c0f562bb012e419801300aff98acc49e9ee13aebb037d65785c21491a41f6f0cc3cda92ba839bebfa9cbf248e83fa4451939573ca139ec
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -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: ['
|
|
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
|
+
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
|
-
|
|
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
|
data/examples/basic_tracing.rb
CHANGED
|
@@ -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
|
data/langfuse-ruby.gemspec
CHANGED
|
@@ -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 = '>=
|
|
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
|
|
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
|
data/lib/langfuse/client.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
data/lib/langfuse/event.rb
CHANGED
|
@@ -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
|
-
}
|
|
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
|
-
}
|
|
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
|