nitro_intelligence 2.1.0 → 2.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/docs/README.md +43 -0
- data/lib/nitro_intelligence/client/handlers/observed/audio_transcription_handler.rb +13 -17
- data/lib/nitro_intelligence/client/handlers/observed/chat_handler.rb +11 -14
- data/lib/nitro_intelligence/client/handlers/observed/image_handler.rb +10 -14
- data/lib/nitro_intelligence/client/handlers/observed/text_to_speech_handler.rb +14 -18
- data/lib/nitro_intelligence/observability/prompt_resolver.rb +69 -0
- data/lib/nitro_intelligence/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1181b17cc855ffa2f039b1401fd5dd2040c262d2878e6d265e65f9a581f6106e
|
|
4
|
+
data.tar.gz: 28b3a022b8d6d7e8e8472930fcf089fa5a3ead8a6c0d917e7587a73b69b8fba5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5fd5911aa6740d71f9dc5564766fe80137d7c2da6e7195ee406649387a22a8387bc02db78671e643d90445c6b8578e2b7b47d7573d4abdf677b35edcc41c6a3
|
|
7
|
+
data.tar.gz: d11585c4f9731911910660e67eb8702634475fd7bbf523b7313c81910d2775852f918a789a669719840196d64c474c1452842f6948e7618563ee1d5b2a1cd5e1
|
data/docs/README.md
CHANGED
|
@@ -254,6 +254,49 @@ client.chat(
|
|
|
254
254
|
|
|
255
255
|
If no `prompt_label` or `prompt_version` is provided, the 'production' prompt is used by default.
|
|
256
256
|
|
|
257
|
+
### Prompt Fallbacks
|
|
258
|
+
|
|
259
|
+
Pass `prompt_fallback_name` to name a second prompt to use when the one you asked for isn't there. The prompt in `prompt_name` is tried first; if it doesn't exist, or its lookup fails, the fallback is used instead:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
client = NitroIntelligence::Client.new(observability_project_slug: "fake-feature-project")
|
|
263
|
+
client.chat(
|
|
264
|
+
message: "Why is the sky blue?",
|
|
265
|
+
parameters: {
|
|
266
|
+
prompt_name: "My Prompt - Scheduling", # used when it exists
|
|
267
|
+
prompt_fallback_name: "My Prompt", # used when it doesn't
|
|
268
|
+
# prompt_fallback_label: "debug",
|
|
269
|
+
# prompt_fallback_version: "v2",
|
|
270
|
+
prompt_variables: {
|
|
271
|
+
appointment_id: "1234",
|
|
272
|
+
},
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
This is for shipping a variant of a prompt — per domain, per locale, per experiment — without having to create one for every caller. Build the specific name however your feature likes; the fallback is just the prompt you'd have asked for anyway.
|
|
278
|
+
|
|
279
|
+
In practice the caller composes the specific name from something it already knows. Summarising a call, for example: each queue holds a different kind of conversation, so a queue that needs its own wording can have its own prompt while the rest use the general one.
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
base = "call_summary"
|
|
283
|
+
queue = call.queue.slug # "sales", "support", "collections", ...
|
|
284
|
+
|
|
285
|
+
client.chat(
|
|
286
|
+
parameters: {
|
|
287
|
+
prompt_name: [base, queue].compact.join("."), # call_summary.collections
|
|
288
|
+
prompt_fallback_name: base,
|
|
289
|
+
prompt_variables: { transcript: call.transcript },
|
|
290
|
+
}
|
|
291
|
+
)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The fallback is looked up at its own `prompt_fallback_label` and `prompt_fallback_version`, and inherits neither from the prompt you asked for first. They describe a different prompt: a version number is minted per prompt, so version 3 of one name is an unrelated revision of another, and a label the first prompt carries need not exist on the fallback at all.
|
|
295
|
+
|
|
296
|
+
That means an omitted `prompt_fallback_label` resolves the fallback the same way any unlabelled lookup does — at the 'production' label. **If you want both prompts to come from the same label, name it for both.**
|
|
297
|
+
|
|
298
|
+
Whichever prompt is used is the one linked to the trace and merged into your request. If the fallback is missing too, the error is raised rather than swallowed — a request that asked for a prompt shouldn't quietly run without one.
|
|
299
|
+
|
|
257
300
|
### Custom Trace Names
|
|
258
301
|
|
|
259
302
|
To provide custom trace names to the observability platform, you can pass 'trace_name' in parameters. Example:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "base64"
|
|
2
2
|
require "nitro_intelligence/media/audio"
|
|
3
|
+
require "nitro_intelligence/observability/prompt_resolver"
|
|
3
4
|
|
|
4
5
|
module NitroIntelligence
|
|
5
6
|
module Client
|
|
@@ -34,26 +35,21 @@ module NitroIntelligence
|
|
|
34
35
|
private
|
|
35
36
|
|
|
36
37
|
def handle_prompt(message:, parameters:)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
prompt_name: parameters[:prompt_name],
|
|
41
|
-
prompt_label: parameters[:prompt_label],
|
|
42
|
-
prompt_version: parameters[:prompt_version]
|
|
38
|
+
prompt = NitroIntelligence::Observability::PromptResolver.for(
|
|
39
|
+
store: @observer.project_client.project.prompt_store,
|
|
40
|
+
parameters:
|
|
43
41
|
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if prompt.present?
|
|
47
|
-
# Prompts for audio transcriptions should only be text
|
|
48
|
-
if prompt.type != "text"
|
|
49
|
-
raise ObservedAudioTranscriptionPromptError,
|
|
50
|
-
"Prompt type for audio transcription must be text: #{prompt.name}"
|
|
51
|
-
end
|
|
52
|
-
interpolated_prompt = prompt.compile(**prompt_variables)
|
|
42
|
+
return nil if prompt.blank?
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
# Prompts for audio transcriptions should only be text
|
|
45
|
+
if prompt.type != "text"
|
|
46
|
+
raise ObservedAudioTranscriptionPromptError,
|
|
47
|
+
"Prompt type for audio transcription must be text: #{prompt.name}"
|
|
56
48
|
end
|
|
49
|
+
interpolated_prompt = prompt.compile(**(parameters[:prompt_variables] || {}))
|
|
50
|
+
|
|
51
|
+
message.prepend("#{interpolated_prompt} ").strip!
|
|
52
|
+
parameters.merge!(prompt.config) unless parameters[:prompt_config_disabled]
|
|
57
53
|
|
|
58
54
|
prompt
|
|
59
55
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "nitro_intelligence/observability/prompt_resolver"
|
|
2
|
+
|
|
1
3
|
module NitroIntelligence
|
|
2
4
|
module Client
|
|
3
5
|
module Handlers
|
|
@@ -28,23 +30,18 @@ module NitroIntelligence
|
|
|
28
30
|
private
|
|
29
31
|
|
|
30
32
|
def handle_prompt(parameters:)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
prompt_name: parameters[:prompt_name],
|
|
35
|
-
prompt_label: parameters[:prompt_label],
|
|
36
|
-
prompt_version: parameters[:prompt_version]
|
|
33
|
+
prompt = NitroIntelligence::Observability::PromptResolver.for(
|
|
34
|
+
store: @observer.project_client.project.prompt_store,
|
|
35
|
+
parameters:
|
|
37
36
|
)
|
|
38
|
-
|
|
37
|
+
return nil if prompt.blank?
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
parameters[:messages]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
39
|
+
parameters[:messages] = prompt.interpolate(
|
|
40
|
+
messages: parameters[:messages],
|
|
41
|
+
variables: parameters[:prompt_variables] || {}
|
|
42
|
+
)
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
end
|
|
44
|
+
parameters.merge!(prompt.config) unless parameters[:prompt_config_disabled]
|
|
48
45
|
|
|
49
46
|
prompt
|
|
50
47
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "nitro_intelligence/media/image_generation"
|
|
2
|
+
require "nitro_intelligence/observability/prompt_resolver"
|
|
2
3
|
|
|
3
4
|
module NitroIntelligence
|
|
4
5
|
module Client
|
|
@@ -58,23 +59,18 @@ module NitroIntelligence
|
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
def handle_prompt(parameters:)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
prompt_name: parameters[:prompt_name],
|
|
65
|
-
prompt_label: parameters[:prompt_label],
|
|
66
|
-
prompt_version: parameters[:prompt_version]
|
|
62
|
+
prompt = NitroIntelligence::Observability::PromptResolver.for(
|
|
63
|
+
store: @observer.project_client.project.prompt_store,
|
|
64
|
+
parameters:
|
|
67
65
|
)
|
|
68
|
-
|
|
66
|
+
return nil if prompt.blank?
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
parameters[:messages]
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
68
|
+
parameters[:messages] = prompt.interpolate(
|
|
69
|
+
messages: parameters[:messages],
|
|
70
|
+
variables: parameters[:prompt_variables] || {}
|
|
71
|
+
)
|
|
75
72
|
|
|
76
|
-
|
|
77
|
-
end
|
|
73
|
+
parameters.merge!(prompt.config) unless parameters[:prompt_config_disabled]
|
|
78
74
|
|
|
79
75
|
prompt
|
|
80
76
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "nitro_intelligence/media/audio"
|
|
2
|
+
require "nitro_intelligence/observability/prompt_resolver"
|
|
2
3
|
|
|
3
4
|
module NitroIntelligence
|
|
4
5
|
module Client
|
|
@@ -33,26 +34,21 @@ module NitroIntelligence
|
|
|
33
34
|
private
|
|
34
35
|
|
|
35
36
|
def handle_prompt(parameters:)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
prompt_name: parameters[:prompt_name],
|
|
40
|
-
prompt_label: parameters[:prompt_label],
|
|
41
|
-
prompt_version: parameters[:prompt_version]
|
|
37
|
+
prompt = NitroIntelligence::Observability::PromptResolver.for(
|
|
38
|
+
store: @observer.project_client.project.prompt_store,
|
|
39
|
+
parameters:
|
|
42
40
|
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"Prompt type for text-to-speech must be text: #{prompt.name}"
|
|
50
|
-
end
|
|
51
|
-
interpolated_prompt = prompt.compile(**prompt_variables)
|
|
52
|
-
|
|
53
|
-
parameters.merge!(prompt.config) unless parameters[:prompt_config_disabled]
|
|
54
|
-
parameters[:instructions] = interpolated_prompt
|
|
41
|
+
return nil if prompt.blank?
|
|
42
|
+
|
|
43
|
+
# Prompts for tts should only be text
|
|
44
|
+
if prompt.type != "text"
|
|
45
|
+
raise ObservedTextToSpeechPromptError,
|
|
46
|
+
"Prompt type for text-to-speech must be text: #{prompt.name}"
|
|
55
47
|
end
|
|
48
|
+
interpolated_prompt = prompt.compile(**(parameters[:prompt_variables] || {}))
|
|
49
|
+
|
|
50
|
+
parameters.merge!(prompt.config) unless parameters[:prompt_config_disabled]
|
|
51
|
+
parameters[:instructions] = interpolated_prompt
|
|
56
52
|
|
|
57
53
|
prompt
|
|
58
54
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require "nitro_intelligence/observability/prompt_store"
|
|
2
|
+
|
|
3
|
+
module NitroIntelligence
|
|
4
|
+
module Observability
|
|
5
|
+
# Resolves a managed prompt from the lookups a request asks for, most specific first. A lookup that is
|
|
6
|
+
# missing -- or that fails -- yields to the next; the last one's failure propagates.
|
|
7
|
+
class PromptResolver
|
|
8
|
+
# One prompt lookup: the name asked for, at a label or a version.
|
|
9
|
+
Lookup = Data.define(:name, :label, :version)
|
|
10
|
+
|
|
11
|
+
def self.for(store:, parameters:)
|
|
12
|
+
new(store: store, lookups: lookups_for(parameters)).prompt
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# The fallback names its own label and version and inherits neither: a version is minted per prompt,
|
|
16
|
+
# and a label the requested prompt carries need not exist on the fallback at all.
|
|
17
|
+
def self.lookups_for(parameters)
|
|
18
|
+
[
|
|
19
|
+
Lookup.new(
|
|
20
|
+
name: parameters[:prompt_name],
|
|
21
|
+
label: parameters[:prompt_label],
|
|
22
|
+
version: parameters[:prompt_version]
|
|
23
|
+
),
|
|
24
|
+
Lookup.new(
|
|
25
|
+
name: parameters[:prompt_fallback_name],
|
|
26
|
+
label: parameters[:prompt_fallback_label],
|
|
27
|
+
version: parameters[:prompt_fallback_version]
|
|
28
|
+
),
|
|
29
|
+
]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(store:, lookups:)
|
|
33
|
+
@store = store
|
|
34
|
+
@lookups = lookups.select { |lookup| lookup.name.present? }.uniq
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def prompt
|
|
38
|
+
*optional, final = @lookups
|
|
39
|
+
return nil if final.nil?
|
|
40
|
+
|
|
41
|
+
optional.each do |lookup|
|
|
42
|
+
resolved = fetch_optional(lookup)
|
|
43
|
+
return resolved if resolved
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
fetch(final)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def fetch(lookup)
|
|
52
|
+
@store.get_prompt(
|
|
53
|
+
prompt_name: lookup.name,
|
|
54
|
+
prompt_label: lookup.label,
|
|
55
|
+
prompt_version: lookup.version
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def fetch_optional(lookup)
|
|
60
|
+
fetch(lookup)
|
|
61
|
+
rescue => e
|
|
62
|
+
NitroIntelligence.logger.info(
|
|
63
|
+
"#{self.class} #{e} - Falling back to the next prompt name after a failed lookup: #{lookup.name}"
|
|
64
|
+
)
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nitro_intelligence
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Igor Artemenko
|
|
@@ -105,6 +105,7 @@ files:
|
|
|
105
105
|
- lib/nitro_intelligence/observability/project_client.rb
|
|
106
106
|
- lib/nitro_intelligence/observability/project_client_registry.rb
|
|
107
107
|
- lib/nitro_intelligence/observability/prompt.rb
|
|
108
|
+
- lib/nitro_intelligence/observability/prompt_resolver.rb
|
|
108
109
|
- lib/nitro_intelligence/observability/prompt_store.rb
|
|
109
110
|
- lib/nitro_intelligence/observability/upload_handler.rb
|
|
110
111
|
- lib/nitro_intelligence/reporter.rb
|