langfuse-rb 0.1.0 → 0.2.0
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/CHANGELOG.md +29 -51
- data/README.md +27 -21
- data/lib/langfuse/api_client.rb +85 -4
- data/lib/langfuse/client.rb +141 -2
- data/lib/langfuse/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae64454a24bf090bcaedf13432b8c2c7ecc0c1d986bc5ffdec8ad3f5ada1b2d5
|
|
4
|
+
data.tar.gz: 05dcbaaa1aa3aa90fcb75d42086a526c60914b3f88ce2fa0049b77d7cafc33cb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b8b91ba6180d4450fef21f59bcca7c06b21c2a5d336e70bf233ca3d2b4d325387f78950ac3ee2c41c4655a0199a4fbd03fdf07164cf224dba7bc2734af1f95c
|
|
7
|
+
data.tar.gz: 00d0a265e3f41cf6690f740b63c7d61853733b9701e1d1ea0630fe19a6d8b8022d8a1cd26b9eceb62133fe169fde093544d738ba3f258f765cd9ced0e2aecea9
|
data/CHANGELOG.md
CHANGED
|
@@ -7,54 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
#### Rails Integration
|
|
41
|
-
- Rails-friendly configuration with initializer support
|
|
42
|
-
- Background job integration (Sidekiq, GoodJob, Delayed Job, etc.)
|
|
43
|
-
- Rake tasks for cache management
|
|
44
|
-
- Environment-specific configuration patterns
|
|
45
|
-
- Credentials support for secure key management
|
|
46
|
-
|
|
47
|
-
#### Developer Experience
|
|
48
|
-
- Comprehensive error handling with specific error classes
|
|
49
|
-
- HTTP client with automatic retry logic and exponential backoff
|
|
50
|
-
- Circuit breaker pattern for resilience (via Stoplight)
|
|
51
|
-
- 99.7% test coverage with 339 comprehensive test cases
|
|
52
|
-
- Extensive documentation with guides for Rails, tracing, and migration
|
|
53
|
-
|
|
54
|
-
#### Dependencies
|
|
55
|
-
- Ruby >= 3.2.0
|
|
56
|
-
- No Rails dependency (works with any Ruby project)
|
|
57
|
-
- Minimal runtime dependencies (Faraday, Mustache, OpenTelemetry)
|
|
58
|
-
|
|
59
|
-
[Unreleased]: https://github.com/langfuse/langfuse-ruby/compare/v1.0.0...HEAD
|
|
60
|
-
[1.0.0]: https://github.com/langfuse/langfuse-ruby/releases/tag/v1.0.0
|
|
10
|
+
### Added
|
|
11
|
+
- Create and update methods for prompts (#36)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.2.0] - 2025-12-19
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Prompt creation and update methods (`create_prompt`, `update_prompt`)
|
|
18
|
+
- Extended prompt management documentation with create/update examples
|
|
19
|
+
|
|
20
|
+
## [0.1.0] - 2025-12-01
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- Observe API with context propagation and scoring (#31)
|
|
24
|
+
- W3C TraceContext propagator for distributed tracing (#1)
|
|
25
|
+
- Ruby 3.4 support (#3)
|
|
26
|
+
- OpenTelemetry-based tracing with OTLP export
|
|
27
|
+
- Distributed caching with Rails.cache backend and stampede protection
|
|
28
|
+
- Prompt management (text and chat) with Mustache templating
|
|
29
|
+
- In-memory caching with TTL and LRU eviction
|
|
30
|
+
- Fallback prompt support
|
|
31
|
+
- Global configuration pattern with `Langfuse.configure`
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- Migrated from legacy ingestion API to OTLP endpoint
|
|
35
|
+
- Removed `tracing_enabled` configuration flag (#2)
|
|
36
|
+
|
|
37
|
+
[Unreleased]: https://github.com/simplepractice/langfuse-rb/compare/v0.1.0...HEAD
|
|
38
|
+
[0.1.0]: https://github.com/simplepractice/langfuse-rb/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
# Langfuse Ruby SDK
|
|
4
4
|
|
|
5
|
-
[](https://badge.fury.io/rb/langfuse)
|
|
5
|
+
[](https://badge.fury.io/rb/langfuse-rb)
|
|
6
6
|
[](https://www.ruby-lang.org/en/)
|
|
7
7
|
[](coverage)
|
|
8
8
|
|
|
9
9
|
> Ruby SDK for [Langfuse](https://langfuse.com) - Open-source LLM observability and prompt management.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
<br>
|
|
12
|
+
|
|
13
|
+
### Features
|
|
12
14
|
|
|
13
15
|
- 🎯 **Prompt Management** - Centralized prompt versioning with Mustache templating
|
|
14
16
|
- 📊 **LLM Tracing** - Zero-boilerplate observability built on OpenTelemetry
|
|
@@ -18,24 +20,24 @@
|
|
|
18
20
|
- 🛡️ **Fallback Support** - Graceful degradation when API unavailable
|
|
19
21
|
- 🚀 **Rails-Friendly** - Global configuration pattern, works with any Ruby project
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
<br>
|
|
24
|
+
|
|
25
|
+
### Installation
|
|
22
26
|
|
|
23
27
|
```ruby
|
|
24
|
-
# Gemfile
|
|
28
|
+
# Add to Gemfile & bundle install
|
|
25
29
|
gem 'langfuse-rb'
|
|
26
30
|
```
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
bundle install
|
|
30
|
-
```
|
|
32
|
+
<br>
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
### Quick Start
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
> Configure once at startup
|
|
35
37
|
|
|
36
38
|
```ruby
|
|
37
39
|
# config/initializers/langfuse.rb (Rails)
|
|
38
|
-
#
|
|
40
|
+
# Or at the top of your script
|
|
39
41
|
Langfuse.configure do |config|
|
|
40
42
|
config.public_key = ENV['LANGFUSE_PUBLIC_KEY']
|
|
41
43
|
config.secret_key = ENV['LANGFUSE_SECRET_KEY']
|
|
@@ -44,7 +46,7 @@ Langfuse.configure do |config|
|
|
|
44
46
|
end
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
> Fetch and use a prompt
|
|
48
50
|
|
|
49
51
|
```ruby
|
|
50
52
|
prompt = Langfuse.client.get_prompt("greeting")
|
|
@@ -52,7 +54,7 @@ message = prompt.compile(name: "Alice")
|
|
|
52
54
|
# => "Hello Alice!"
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
> Trace an LLM call
|
|
56
58
|
|
|
57
59
|
```ruby
|
|
58
60
|
Langfuse.observe("chat-completion", as_type: :generation) do |gen|
|
|
@@ -74,33 +76,37 @@ Langfuse.observe("chat-completion", as_type: :generation) do |gen|
|
|
|
74
76
|
end
|
|
75
77
|
```
|
|
76
78
|
|
|
77
|
-
> [!IMPORTANT]
|
|
79
|
+
> [!IMPORTANT]
|
|
78
80
|
> For complete reference see [docs](./docs/) section.
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
<br>
|
|
83
|
+
|
|
84
|
+
### Requirements
|
|
81
85
|
|
|
82
86
|
- Ruby >= 3.2.0
|
|
83
87
|
- No Rails dependency (works with any Ruby project)
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
<br>
|
|
90
|
+
|
|
91
|
+
### Contributing
|
|
86
92
|
|
|
87
93
|
We welcome contributions! Please:
|
|
88
94
|
|
|
89
|
-
1. Check existing [issues](https://github.com/simplepractice/langfuse-rb/issues)
|
|
95
|
+
1. Check existing [issues](https://github.com/simplepractice/langfuse-rb/issues)
|
|
90
96
|
2. Open an issue to discuss your idea
|
|
91
97
|
3. Fork the repo and create a feature branch
|
|
92
98
|
4. Write tests (maintain >95% coverage)
|
|
93
99
|
5. Ensure `bundle exec rspec` and `bundle exec rubocop` pass
|
|
94
100
|
6. Submit a pull request
|
|
95
101
|
|
|
96
|
-
|
|
102
|
+
> [!TIP]
|
|
103
|
+
> See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
|
|
104
|
+
|
|
105
|
+
<br>
|
|
97
106
|
|
|
98
|
-
|
|
107
|
+
### Support
|
|
99
108
|
|
|
100
109
|
- **[GitHub Issues](https://github.com/simplepractice/langfuse-rb/issues)** - Bug reports and feature requests
|
|
101
110
|
- **[Langfuse Documentation](https://langfuse.com/docs)** - Platform documentation
|
|
102
111
|
- **[API Reference](https://api.reference.langfuse.com)** - REST API reference
|
|
103
112
|
|
|
104
|
-
## License
|
|
105
|
-
|
|
106
|
-
[MIT](LICENSE)
|
data/lib/langfuse/api_client.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "faraday"
|
|
|
4
4
|
require "faraday/retry"
|
|
5
5
|
require "base64"
|
|
6
6
|
require "json"
|
|
7
|
+
require "uri"
|
|
7
8
|
|
|
8
9
|
module Langfuse
|
|
9
10
|
# HTTP client for Langfuse API
|
|
@@ -128,6 +129,84 @@ module Langfuse
|
|
|
128
129
|
end
|
|
129
130
|
end
|
|
130
131
|
|
|
132
|
+
# Create a new prompt (or new version if prompt with same name exists)
|
|
133
|
+
#
|
|
134
|
+
# @param name [String] The prompt name
|
|
135
|
+
# @param prompt [String, Array<Hash>] The prompt content
|
|
136
|
+
# @param type [String] Prompt type ("text" or "chat")
|
|
137
|
+
# @param config [Hash] Optional configuration (model params, etc.)
|
|
138
|
+
# @param labels [Array<String>] Optional labels (e.g., ["production"])
|
|
139
|
+
# @param tags [Array<String>] Optional tags
|
|
140
|
+
# @param commit_message [String, nil] Optional commit message
|
|
141
|
+
# @return [Hash] The created prompt data
|
|
142
|
+
# @raise [UnauthorizedError] if authentication fails
|
|
143
|
+
# @raise [ApiError] for other API errors
|
|
144
|
+
#
|
|
145
|
+
# @example Create a text prompt
|
|
146
|
+
# api_client.create_prompt(
|
|
147
|
+
# name: "greeting",
|
|
148
|
+
# prompt: "Hello {{name}}!",
|
|
149
|
+
# type: "text",
|
|
150
|
+
# labels: ["production"]
|
|
151
|
+
# )
|
|
152
|
+
#
|
|
153
|
+
# rubocop:disable Metrics/ParameterLists
|
|
154
|
+
def create_prompt(name:, prompt:, type:, config: {}, labels: [], tags: [], commit_message: nil)
|
|
155
|
+
path = "/api/public/v2/prompts"
|
|
156
|
+
payload = {
|
|
157
|
+
name: name,
|
|
158
|
+
prompt: prompt,
|
|
159
|
+
type: type,
|
|
160
|
+
config: config,
|
|
161
|
+
labels: labels,
|
|
162
|
+
tags: tags
|
|
163
|
+
}
|
|
164
|
+
payload[:commitMessage] = commit_message if commit_message
|
|
165
|
+
|
|
166
|
+
response = connection.post(path, payload)
|
|
167
|
+
handle_response(response)
|
|
168
|
+
rescue Faraday::RetriableResponse => e
|
|
169
|
+
logger.error("Faraday error: Retries exhausted - #{e.response.status}")
|
|
170
|
+
handle_response(e.response)
|
|
171
|
+
rescue Faraday::Error => e
|
|
172
|
+
logger.error("Faraday error: #{e.message}")
|
|
173
|
+
raise ApiError, "HTTP request failed: #{e.message}"
|
|
174
|
+
end
|
|
175
|
+
# rubocop:enable Metrics/ParameterLists
|
|
176
|
+
|
|
177
|
+
# Update labels for an existing prompt version
|
|
178
|
+
#
|
|
179
|
+
# @param name [String] The prompt name
|
|
180
|
+
# @param version [Integer] The version number to update
|
|
181
|
+
# @param labels [Array<String>] New labels (replaces existing). Required.
|
|
182
|
+
# @return [Hash] The updated prompt data
|
|
183
|
+
# @raise [ArgumentError] if labels is not an array
|
|
184
|
+
# @raise [NotFoundError] if the prompt is not found
|
|
185
|
+
# @raise [UnauthorizedError] if authentication fails
|
|
186
|
+
# @raise [ApiError] for other API errors
|
|
187
|
+
#
|
|
188
|
+
# @example Promote a prompt to production
|
|
189
|
+
# api_client.update_prompt(
|
|
190
|
+
# name: "greeting",
|
|
191
|
+
# version: 2,
|
|
192
|
+
# labels: ["production"]
|
|
193
|
+
# )
|
|
194
|
+
def update_prompt(name:, version:, labels:)
|
|
195
|
+
raise ArgumentError, "labels must be an array" unless labels.is_a?(Array)
|
|
196
|
+
|
|
197
|
+
path = "/api/public/v2/prompts/#{URI.encode_uri_component(name)}/versions/#{version}"
|
|
198
|
+
payload = { newLabels: labels }
|
|
199
|
+
|
|
200
|
+
response = connection.patch(path, payload)
|
|
201
|
+
handle_response(response)
|
|
202
|
+
rescue Faraday::RetriableResponse => e
|
|
203
|
+
logger.error("Faraday error: Retries exhausted - #{e.response.status}")
|
|
204
|
+
handle_response(e.response)
|
|
205
|
+
rescue Faraday::Error => e
|
|
206
|
+
logger.error("Faraday error: #{e.message}")
|
|
207
|
+
raise ApiError, "HTTP request failed: #{e.message}"
|
|
208
|
+
end
|
|
209
|
+
|
|
131
210
|
# Send a batch of events to the Langfuse ingestion API
|
|
132
211
|
#
|
|
133
212
|
# Sends events (scores, traces, observations) to the ingestion endpoint.
|
|
@@ -180,7 +259,7 @@ module Langfuse
|
|
|
180
259
|
# @raise [ApiError] for other API errors
|
|
181
260
|
def fetch_prompt_from_api(name, version: nil, label: nil)
|
|
182
261
|
params = build_prompt_params(version: version, label: label)
|
|
183
|
-
path = "/api/public/v2/prompts/#{name}"
|
|
262
|
+
path = "/api/public/v2/prompts/#{URI.encode_uri_component(name)}"
|
|
184
263
|
|
|
185
264
|
response = connection.get(path, params)
|
|
186
265
|
handle_response(response)
|
|
@@ -215,7 +294,9 @@ module Langfuse
|
|
|
215
294
|
# Retries transient errors with exponential backoff:
|
|
216
295
|
# - Max 2 retries (3 total attempts)
|
|
217
296
|
# - Exponential backoff (0.05s * 2^retry_count)
|
|
218
|
-
# - Retries GET
|
|
297
|
+
# - Retries GET and PATCH requests (idempotent operations)
|
|
298
|
+
# - Retries POST requests to batch endpoint (idempotent due to event UUIDs)
|
|
299
|
+
# - Note: POST to create_prompt is NOT idempotent; retries may create duplicate versions
|
|
219
300
|
# - Retries on: 429 (rate limit), 503 (service unavailable), 504 (gateway timeout)
|
|
220
301
|
# - Does NOT retry on: 4xx errors (except 429), 5xx errors (except 503, 504)
|
|
221
302
|
#
|
|
@@ -225,7 +306,7 @@ module Langfuse
|
|
|
225
306
|
max: 2,
|
|
226
307
|
interval: 0.05,
|
|
227
308
|
backoff_factor: 2,
|
|
228
|
-
methods: %i[get post],
|
|
309
|
+
methods: %i[get post patch],
|
|
229
310
|
retry_statuses: [429, 503, 504],
|
|
230
311
|
exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed]
|
|
231
312
|
}
|
|
@@ -278,7 +359,7 @@ module Langfuse
|
|
|
278
359
|
# @raise [ApiError] for other error statuses
|
|
279
360
|
def handle_response(response)
|
|
280
361
|
case response.status
|
|
281
|
-
when 200
|
|
362
|
+
when 200, 201
|
|
282
363
|
response.body
|
|
283
364
|
when 401
|
|
284
365
|
raise UnauthorizedError, "Authentication failed. Check your API keys."
|
data/lib/langfuse/client.rb
CHANGED
|
@@ -17,6 +17,7 @@ module Langfuse
|
|
|
17
17
|
# prompt = client.get_prompt("greeting")
|
|
18
18
|
# compiled = prompt.compile(name: "Alice")
|
|
19
19
|
#
|
|
20
|
+
# rubocop:disable Metrics/ClassLength
|
|
20
21
|
class Client
|
|
21
22
|
attr_reader :config, :api_client
|
|
22
23
|
|
|
@@ -139,6 +140,93 @@ module Langfuse
|
|
|
139
140
|
prompt.compile(**variables)
|
|
140
141
|
end
|
|
141
142
|
|
|
143
|
+
# Create a new prompt (or new version if name already exists)
|
|
144
|
+
#
|
|
145
|
+
# Creates a new prompt in Langfuse. If a prompt with the same name already
|
|
146
|
+
# exists, this creates a new version of that prompt.
|
|
147
|
+
#
|
|
148
|
+
# @param name [String] The prompt name (required)
|
|
149
|
+
# @param prompt [String, Array<Hash>] The prompt content (required)
|
|
150
|
+
# - For text prompts: a string with {{variable}} placeholders
|
|
151
|
+
# - For chat prompts: array of message hashes with role and content
|
|
152
|
+
# @param type [Symbol] Prompt type (:text or :chat) (required)
|
|
153
|
+
# @param config [Hash] Optional configuration (model parameters, tools, etc.)
|
|
154
|
+
# @param labels [Array<String>] Optional labels (e.g., ["production"])
|
|
155
|
+
# @param tags [Array<String>] Optional tags for categorization
|
|
156
|
+
# @param commit_message [String, nil] Optional commit message
|
|
157
|
+
# @return [TextPromptClient, ChatPromptClient] The created prompt client
|
|
158
|
+
# @raise [ArgumentError] if required parameters are missing or invalid
|
|
159
|
+
# @raise [UnauthorizedError] if authentication fails
|
|
160
|
+
# @raise [ApiError] for other API errors
|
|
161
|
+
#
|
|
162
|
+
# @example Create a text prompt
|
|
163
|
+
# prompt = client.create_prompt(
|
|
164
|
+
# name: "greeting",
|
|
165
|
+
# prompt: "Hello {{name}}!",
|
|
166
|
+
# type: :text,
|
|
167
|
+
# labels: ["production"],
|
|
168
|
+
# config: { model: "gpt-4o", temperature: 0.7 }
|
|
169
|
+
# )
|
|
170
|
+
#
|
|
171
|
+
# @example Create a chat prompt
|
|
172
|
+
# prompt = client.create_prompt(
|
|
173
|
+
# name: "support-bot",
|
|
174
|
+
# prompt: [
|
|
175
|
+
# { role: "system", content: "You are a {{role}} assistant" },
|
|
176
|
+
# { role: "user", content: "{{question}}" }
|
|
177
|
+
# ],
|
|
178
|
+
# type: :chat,
|
|
179
|
+
# labels: ["staging"]
|
|
180
|
+
# )
|
|
181
|
+
# rubocop:disable Metrics/ParameterLists
|
|
182
|
+
def create_prompt(name:, prompt:, type:, config: {}, labels: [], tags: [], commit_message: nil)
|
|
183
|
+
validate_prompt_type!(type)
|
|
184
|
+
validate_prompt_content!(prompt, type)
|
|
185
|
+
|
|
186
|
+
prompt_data = api_client.create_prompt(
|
|
187
|
+
name: name,
|
|
188
|
+
prompt: normalize_prompt_content(prompt, type),
|
|
189
|
+
type: type.to_s,
|
|
190
|
+
config: config,
|
|
191
|
+
labels: labels,
|
|
192
|
+
tags: tags,
|
|
193
|
+
commit_message: commit_message
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
build_prompt_client(prompt_data)
|
|
197
|
+
end
|
|
198
|
+
# rubocop:enable Metrics/ParameterLists
|
|
199
|
+
|
|
200
|
+
# Update an existing prompt version's metadata
|
|
201
|
+
#
|
|
202
|
+
# Updates the labels of an existing prompt version.
|
|
203
|
+
# Note: The prompt content itself cannot be changed after creation.
|
|
204
|
+
#
|
|
205
|
+
# @param name [String] The prompt name (required)
|
|
206
|
+
# @param version [Integer] The version number to update (required)
|
|
207
|
+
# @param labels [Array<String>] New labels (replaces existing). Required.
|
|
208
|
+
# @return [TextPromptClient, ChatPromptClient] The updated prompt client
|
|
209
|
+
# @raise [ArgumentError] if labels is not an array
|
|
210
|
+
# @raise [NotFoundError] if the prompt is not found
|
|
211
|
+
# @raise [UnauthorizedError] if authentication fails
|
|
212
|
+
# @raise [ApiError] for other API errors
|
|
213
|
+
#
|
|
214
|
+
# @example Update labels to promote to production
|
|
215
|
+
# prompt = client.update_prompt(
|
|
216
|
+
# name: "greeting",
|
|
217
|
+
# version: 2,
|
|
218
|
+
# labels: ["production"]
|
|
219
|
+
# )
|
|
220
|
+
def update_prompt(name:, version:, labels:)
|
|
221
|
+
prompt_data = api_client.update_prompt(
|
|
222
|
+
name: name,
|
|
223
|
+
version: version,
|
|
224
|
+
labels: labels
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
build_prompt_client(prompt_data)
|
|
228
|
+
end
|
|
229
|
+
|
|
142
230
|
# Generate URL for viewing a trace in Langfuse UI
|
|
143
231
|
#
|
|
144
232
|
# @param trace_id [String] The trace ID (hex-encoded, 32 characters)
|
|
@@ -314,6 +402,8 @@ module Langfuse
|
|
|
314
402
|
# @return [TextPromptClient, ChatPromptClient]
|
|
315
403
|
# @raise [ArgumentError] if type is invalid
|
|
316
404
|
def build_fallback_prompt_client(name, fallback, type)
|
|
405
|
+
validate_prompt_type!(type)
|
|
406
|
+
|
|
317
407
|
# Create minimal prompt data structure
|
|
318
408
|
prompt_data = {
|
|
319
409
|
"name" => name,
|
|
@@ -330,9 +420,58 @@ module Langfuse
|
|
|
330
420
|
TextPromptClient.new(prompt_data)
|
|
331
421
|
when :chat
|
|
332
422
|
ChatPromptClient.new(prompt_data)
|
|
333
|
-
|
|
334
|
-
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Validate prompt type parameter
|
|
427
|
+
#
|
|
428
|
+
# @param type [Symbol] The type to validate
|
|
429
|
+
# @raise [ArgumentError] if type is invalid
|
|
430
|
+
def validate_prompt_type!(type)
|
|
431
|
+
valid_types = %i[text chat]
|
|
432
|
+
return if valid_types.include?(type)
|
|
433
|
+
|
|
434
|
+
raise ArgumentError, "Invalid type: #{type}. Must be :text or :chat"
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# Validate prompt content matches the declared type
|
|
438
|
+
#
|
|
439
|
+
# @param prompt [String, Array] The prompt content
|
|
440
|
+
# @param type [Symbol] The declared type
|
|
441
|
+
# @raise [ArgumentError] if content doesn't match type
|
|
442
|
+
def validate_prompt_content!(prompt, type)
|
|
443
|
+
case type
|
|
444
|
+
when :text
|
|
445
|
+
raise ArgumentError, "Text prompt must be a String" unless prompt.is_a?(String)
|
|
446
|
+
when :chat
|
|
447
|
+
raise ArgumentError, "Chat prompt must be an Array" unless prompt.is_a?(Array)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Normalize prompt content for API request
|
|
452
|
+
#
|
|
453
|
+
# Converts Ruby symbol keys to string keys for chat messages
|
|
454
|
+
#
|
|
455
|
+
# @param prompt [String, Array] The prompt content
|
|
456
|
+
# @param type [Symbol] The prompt type
|
|
457
|
+
# @return [String, Array] Normalized content
|
|
458
|
+
def normalize_prompt_content(prompt, type)
|
|
459
|
+
return prompt if type == :text
|
|
460
|
+
|
|
461
|
+
# Normalize chat messages to use string keys
|
|
462
|
+
prompt.map do |message|
|
|
463
|
+
# Convert all keys to symbols first, then extract
|
|
464
|
+
normalized = message.transform_keys do |k|
|
|
465
|
+
k.to_sym
|
|
466
|
+
rescue StandardError
|
|
467
|
+
k
|
|
468
|
+
end
|
|
469
|
+
{
|
|
470
|
+
"role" => normalized[:role]&.to_s,
|
|
471
|
+
"content" => normalized[:content]
|
|
472
|
+
}
|
|
335
473
|
end
|
|
336
474
|
end
|
|
337
475
|
end
|
|
476
|
+
# rubocop:enable Metrics/ClassLength
|
|
338
477
|
end
|
data/lib/langfuse/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: langfuse-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SimplePractice
|
|
@@ -171,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
171
171
|
- !ruby/object:Gem::Version
|
|
172
172
|
version: '0'
|
|
173
173
|
requirements: []
|
|
174
|
-
rubygems_version:
|
|
174
|
+
rubygems_version: 4.0.2
|
|
175
175
|
specification_version: 4
|
|
176
176
|
summary: Ruby SDK for Langfuse - LLM observability and prompt management
|
|
177
177
|
test_files: []
|