nitro_intelligence 3.0.0 → 3.0.2
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: feb0fe8d0b4ae0d1da8564f048fa7130dc78318622a23a86163edc65d2f37dbf
|
|
4
|
+
data.tar.gz: 122277bfc6b70b0aeb67166624458f37f50b1ed2b1a77044ade87575ea101bb1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf69e4777017afa0be7457612c42aadf434921c083b3a199e13a0682959b6579494793069e2548c592dd022ac5a96d342155549cfa5d36fdf798bd0a05bd647f
|
|
7
|
+
data.tar.gz: 18854e89e04d994bddc0315296d5f21ad2551dd9b7d64d01efda6aadc25dbcd0858d1e8f24dacb2f2b261b2b65ab1bb3fe747956f5064ce6ef176b017e59b712
|
data/docs/README.md
CHANGED
|
@@ -424,7 +424,62 @@ trace_id = NitroIntelligence::Trace.create_id(seed: document_id)
|
|
|
424
424
|
reporter.score(name: "precision", value: 0.5, trace_id:)
|
|
425
425
|
```
|
|
426
426
|
|
|
427
|
-
###
|
|
427
|
+
### Prompts
|
|
428
|
+
|
|
429
|
+
#### Message Shape
|
|
430
|
+
|
|
431
|
+
A chat completion needs a user message in order for the model to answer. What you have to supply depends on the type of the prompt you name, and the two types behave differently.
|
|
432
|
+
|
|
433
|
+
##### Text prompts
|
|
434
|
+
|
|
435
|
+
A text prompt in Cerebro is equivalent to the system prompt. System prompts are used to pre-load initial context into the model needed for following call executions.
|
|
436
|
+
|
|
437
|
+
Pure text prompts require a user message to be supplied. For example:
|
|
438
|
+
|
|
439
|
+
```ruby
|
|
440
|
+
# Wrong - Raises ObservedChatPromptError - A prompt alone, provides a system message alone with nothing to answer
|
|
441
|
+
client.chat(parameters: { prompt_name: "Assistant" })
|
|
442
|
+
|
|
443
|
+
# Correct - The prompt supplies the system message, you supply the user message
|
|
444
|
+
client.chat(message: "Why is the sky blue?", parameters: { prompt_name: "Assistant" })
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
##### Chat prompts
|
|
448
|
+
|
|
449
|
+
A chat prompt in Cerebro holds a list of role-tagged messages rather than a single string, so it can carry its own user turn. Define one when the prompt structure should represent a conversation flow vs. a single block of static text, e.g. pre-modeling an interaction you want to continue:
|
|
450
|
+
|
|
451
|
+
```yaml
|
|
452
|
+
[
|
|
453
|
+
{
|
|
454
|
+
"role": "system",
|
|
455
|
+
"content": "You are a helpful support agent for an online bookstore. Be concise and friendly. Our return window is 30 days."
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
"role": "user",
|
|
459
|
+
"content": "Hi, do you ship to Canada?"
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"role": "assistant",
|
|
463
|
+
"content": "Yes! We ship to Canada. Delivery usually takes 5–8 business days."
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
"role": "user",
|
|
467
|
+
"content": "{{question}}"
|
|
468
|
+
}
|
|
469
|
+
]
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
That prompt is self-sufficient, so no additional `message:` is needed:
|
|
473
|
+
|
|
474
|
+
```ruby
|
|
475
|
+
client.chat(parameters: { prompt_name: "Appointment Extractor", prompt_variables: { document: text } })
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Any messages you do pass are appended after the prompt's own system message, so a chat prompt can also serve as a preamble to a live conversation.
|
|
479
|
+
|
|
480
|
+
A chat prompt containing only a system message has the same problem as a text prompt, and is refused in the same way. If you find yourself writing one, either add a user message to it in Cerebro or make it a text prompt and pass the turn from the caller via the `message` keyword.
|
|
481
|
+
|
|
482
|
+
#### Prompt Variables and Config
|
|
428
483
|
|
|
429
484
|
Prompts are often created with "variables". These variables can be supplied and compiled into the prompt. For example:
|
|
430
485
|
|
|
@@ -74,16 +74,25 @@ module NitroIntelligence
|
|
|
74
74
|
# (OpenAI::Models::Audio::Transcription), so neither is set again here.
|
|
75
75
|
trace_attributes = {
|
|
76
76
|
output: audio_transcription.text,
|
|
77
|
-
usage_details:
|
|
78
|
-
input_tokens: audio_transcription.usage.input_tokens,
|
|
79
|
-
output_tokens: audio_transcription.usage.output_tokens,
|
|
80
|
-
total_tokens: audio_transcription.usage.total_tokens,
|
|
81
|
-
},
|
|
77
|
+
usage_details: usage_details(audio_transcription.usage),
|
|
82
78
|
cost_details: @base_handler.cost_details(audio_transcription),
|
|
83
79
|
}
|
|
84
80
|
|
|
85
81
|
[audio_transcription, trace_attributes]
|
|
86
82
|
end
|
|
83
|
+
|
|
84
|
+
def usage_details(usage)
|
|
85
|
+
case usage
|
|
86
|
+
when OpenAI::Models::Audio::Transcription::Usage::Tokens
|
|
87
|
+
{
|
|
88
|
+
input_tokens: usage.input_tokens,
|
|
89
|
+
output_tokens: usage.output_tokens,
|
|
90
|
+
total_tokens: usage.total_tokens,
|
|
91
|
+
}
|
|
92
|
+
when OpenAI::Models::Audio::Transcription::Usage::Duration
|
|
93
|
+
{ input_audio_seconds: usage.seconds.ceil }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
87
96
|
end
|
|
88
97
|
end
|
|
89
98
|
end
|
|
@@ -5,6 +5,8 @@ module NitroIntelligence
|
|
|
5
5
|
module Handlers
|
|
6
6
|
module Observed
|
|
7
7
|
class ChatHandler
|
|
8
|
+
class ObservedChatPromptError < StandardError; end
|
|
9
|
+
|
|
8
10
|
def initialize(base_handler:, observer:)
|
|
9
11
|
@base_handler = base_handler
|
|
10
12
|
@observer = observer
|
|
@@ -14,6 +16,8 @@ module NitroIntelligence
|
|
|
14
16
|
@base_handler.validate_and_resolve!(parameters, message)
|
|
15
17
|
|
|
16
18
|
prompt = handle_prompt(parameters:)
|
|
19
|
+
validate_message_shape!(parameters:, prompt:)
|
|
20
|
+
|
|
17
21
|
trace_name = parameters[:trace_name] || prompt&.name || @observer.project_client.project.slug
|
|
18
22
|
|
|
19
23
|
@observer.observe(
|
|
@@ -30,6 +34,46 @@ module NitroIntelligence
|
|
|
30
34
|
|
|
31
35
|
private
|
|
32
36
|
|
|
37
|
+
# A chat completion needs a turn for the model to answer, and it is the model's own
|
|
38
|
+
# chat template that insists on one: Qwen's raises "No user query found in
|
|
39
|
+
# messages." Left to the gateway that costs a round trip and comes back as a 400
|
|
40
|
+
# whose body the caller cannot read, so it is caught here instead, while the prompt
|
|
41
|
+
# is still in hand and the advice can name what to do about it.
|
|
42
|
+
#
|
|
43
|
+
# Presence of the turn is what is checked, not its usefulness. A template may well
|
|
44
|
+
# accept an empty user message -- Qwen's does -- but a caller who sent one almost
|
|
45
|
+
# never meant to, so blank content is refused rather than forwarded. Content
|
|
46
|
+
# arriving as an array of parts is taken at face value, since a message carrying
|
|
47
|
+
# only an image is a legitimate turn.
|
|
48
|
+
def validate_message_shape!(parameters:, prompt:)
|
|
49
|
+
return if parameters[:messages].any? { |message| user_turn?(message) }
|
|
50
|
+
|
|
51
|
+
raise ObservedChatPromptError, missing_user_message_error(prompt)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def user_turn?(message)
|
|
55
|
+
role = message[:role] || message["role"]
|
|
56
|
+
return false unless role.to_s == "user"
|
|
57
|
+
|
|
58
|
+
(message[:content] || message["content"]).present?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def missing_user_message_error(prompt)
|
|
62
|
+
case prompt&.type
|
|
63
|
+
when "text"
|
|
64
|
+
"The prompt #{prompt.name.inspect} is a text prompt, so it contributes only a system message and " \
|
|
65
|
+
"this request carries no turn for the model to answer. Pass a `message:`, or define " \
|
|
66
|
+
"#{prompt.name.inspect} as a chat prompt in Cerebro so that it carries its own user message."
|
|
67
|
+
when "chat"
|
|
68
|
+
"The chat prompt #{prompt.name.inspect} contains no user message and none was supplied, so this " \
|
|
69
|
+
"request carries no turn for the model to answer. Add a user message to #{prompt.name.inspect} " \
|
|
70
|
+
"in Cerebro, or pass a `message:`."
|
|
71
|
+
else
|
|
72
|
+
"This request carries no user message, so there is no turn for the model to answer. Pass a " \
|
|
73
|
+
"`message:`, or supply `parameters[:messages]` including a message with the `user` role."
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
33
77
|
def handle_prompt(parameters:)
|
|
34
78
|
prompt = NitroIntelligence::Observability::PromptResolver.for(
|
|
35
79
|
store: @observer.project_client.project.prompt_store,
|