smart_prompt 0.4.4 → 0.5.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 +10 -10
- data/README.cn.md +307 -64
- data/README.md +311 -64
- data/Rakefile +10 -1
- data/config/anthropic_config.yml +151 -0
- data/config/image_generation_config.yml +22 -0
- data/config/multimodal_config.yml +85 -0
- data/config/sensenova_config.yml +63 -0
- data/config/zhipu_config.yml +73 -0
- data/examples/anthropic_basic_chat.rb +143 -0
- data/examples/anthropic_example.rb +232 -0
- data/examples/anthropic_multimodal.rb +212 -0
- data/examples/anthropic_streaming.rb +312 -0
- data/examples/anthropic_tool_calling.rb +393 -0
- data/examples/automatic_cleanup_example.rb +109 -0
- data/examples/history_management_examples.rb +522 -0
- data/examples/image_generation_example.rb +130 -0
- data/examples/monitoring_example.rb +121 -0
- data/examples/multimodal_example.rb +63 -0
- data/examples/relevance_based_strategy_example.rb +87 -0
- data/examples/sensenova_example.rb +129 -0
- data/examples/stt_example.rb +287 -0
- data/examples/tts_example.rb +244 -0
- data/examples/video_generation_example.rb +189 -0
- data/examples/zhipu_example.rb +151 -0
- data/lib/smart_prompt/anthropic_adapter.rb +363 -281
- data/lib/smart_prompt/compression_engine.rb +201 -0
- data/lib/smart_prompt/context_strategy.rb +22 -0
- data/lib/smart_prompt/conversation.rb +81 -191
- data/lib/smart_prompt/engine.rb +36 -19
- data/lib/smart_prompt/history_manager.rb +596 -0
- data/lib/smart_prompt/hybrid_strategy.rb +222 -0
- data/lib/smart_prompt/image_generation_adapter.rb +297 -0
- data/lib/smart_prompt/lru_cache.rb +133 -0
- data/lib/smart_prompt/message.rb +57 -0
- data/lib/smart_prompt/multimodal_adapter.rb +277 -0
- data/lib/smart_prompt/openai_adapter.rb +1 -25
- data/lib/smart_prompt/persistence_layer.rb +197 -0
- data/lib/smart_prompt/relevance_based_strategy.rb +221 -0
- data/lib/smart_prompt/sensenova_adapter.rb +410 -0
- data/lib/smart_prompt/session.rb +140 -0
- data/lib/smart_prompt/sliding_window_strategy.rb +100 -0
- data/lib/smart_prompt/stt_adapter.rb +381 -0
- data/lib/smart_prompt/summary_based_strategy.rb +152 -0
- data/lib/smart_prompt/token_counter.rb +74 -0
- data/lib/smart_prompt/tts_adapter.rb +403 -0
- data/lib/smart_prompt/version.rb +1 -1
- data/lib/smart_prompt/video_generation_adapter.rb +330 -0
- data/lib/smart_prompt/worker.rb +25 -3
- data/lib/smart_prompt/zhipu_adapter.rb +616 -0
- data/lib/smart_prompt.rb +22 -2
- data/workers/history_management_examples.rb +407 -0
- data/workers/image_generation_workers.rb +119 -0
- data/workers/multimodal_workers.rb +110 -0
- data/workers/sensenova_workers.rb +62 -0
- data/workers/stt_workers.rb +195 -0
- data/workers/tts_workers.rb +388 -0
- data/workers/video_generation_workers.rb +264 -0
- data/workers/zhipu_workers.rb +113 -0
- metadata +84 -8
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
module SmartPrompt
|
|
2
|
+
# CompressionEngine handles automatic compression of conversation history
|
|
3
|
+
# through summarization using an LLM adapter
|
|
4
|
+
#
|
|
5
|
+
# This engine:
|
|
6
|
+
# - Generates summaries of older messages to reduce token usage
|
|
7
|
+
# - Preserves key facts, decisions, and context in summaries
|
|
8
|
+
# - Falls back to truncation strategies when summarization fails
|
|
9
|
+
# - Tracks compression metrics for monitoring
|
|
10
|
+
class CompressionEngine
|
|
11
|
+
attr_reader :config
|
|
12
|
+
|
|
13
|
+
# Initialize the compression engine
|
|
14
|
+
# @param config [Hash] Configuration options
|
|
15
|
+
# @option config [LLMAdapter] :llm_adapter LLM adapter for generating summaries
|
|
16
|
+
# @option config [String] :prompt Custom summarization prompt template
|
|
17
|
+
# @option config [Float] :compression_ratio (0.5) Target compression ratio
|
|
18
|
+
# @option config [Integer] :min_messages_to_compress (5) Minimum messages needed for compression
|
|
19
|
+
def initialize(config = {})
|
|
20
|
+
@config = config
|
|
21
|
+
@llm_adapter = config[:llm_adapter]
|
|
22
|
+
@summarization_prompt = config[:prompt] || default_prompt
|
|
23
|
+
@compression_ratio = config[:compression_ratio] || 0.5
|
|
24
|
+
@min_messages_to_compress = config[:min_messages_to_compress] || 5
|
|
25
|
+
@token_counter = TokenCounter.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Summarize a collection of messages into a single summary message
|
|
29
|
+
# @param messages [Array<Message>] Messages to summarize
|
|
30
|
+
# @return [Message, nil] Summary message or nil if summarization fails
|
|
31
|
+
def summarize(messages)
|
|
32
|
+
return nil if messages.nil? || messages.empty?
|
|
33
|
+
return nil if messages.length < @min_messages_to_compress
|
|
34
|
+
|
|
35
|
+
# Build the content to summarize
|
|
36
|
+
content = messages.map { |msg| "#{msg.role}: #{msg.content}" }.join("\n")
|
|
37
|
+
|
|
38
|
+
# Create the summarization prompt
|
|
39
|
+
prompt = @summarization_prompt.gsub("{content}", content)
|
|
40
|
+
|
|
41
|
+
begin
|
|
42
|
+
# Call LLM to generate summary
|
|
43
|
+
summary_text = if @llm_adapter
|
|
44
|
+
@llm_adapter.send_request([
|
|
45
|
+
{ role: "user", content: prompt }
|
|
46
|
+
])
|
|
47
|
+
else
|
|
48
|
+
# If no LLM adapter, create a simple summary
|
|
49
|
+
create_fallback_summary(messages)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Calculate original token count
|
|
53
|
+
original_tokens = messages.sum { |msg| msg.token_count || @token_counter.count(msg.content) }
|
|
54
|
+
|
|
55
|
+
# Create summary message
|
|
56
|
+
summary_message = Message.new(
|
|
57
|
+
role: "system",
|
|
58
|
+
content: "[Summary of previous conversation]\n#{summary_text}",
|
|
59
|
+
is_summary: true,
|
|
60
|
+
metadata: {
|
|
61
|
+
original_count: messages.count,
|
|
62
|
+
original_tokens: original_tokens,
|
|
63
|
+
compressed_at: Time.now.iso8601
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Calculate tokens for the summary
|
|
68
|
+
summary_message.calculate_tokens(@token_counter)
|
|
69
|
+
|
|
70
|
+
SmartPrompt.logger.info "Compressed #{messages.count} messages (#{original_tokens} tokens) " \
|
|
71
|
+
"into summary (#{summary_message.token_count} tokens)"
|
|
72
|
+
|
|
73
|
+
summary_message
|
|
74
|
+
rescue => e
|
|
75
|
+
SmartPrompt.logger.error "Summarization failed: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Compress a session by identifying and summarizing compressible segments
|
|
81
|
+
# @param session [Session] The session to compress
|
|
82
|
+
# @return [Boolean] true if compression was successful
|
|
83
|
+
def compress(session)
|
|
84
|
+
return false if session.nil? || session.messages.empty?
|
|
85
|
+
|
|
86
|
+
begin
|
|
87
|
+
# Identify compressible message segments
|
|
88
|
+
compressible_segments = identify_compressible_segments(session.messages)
|
|
89
|
+
|
|
90
|
+
return false if compressible_segments.empty?
|
|
91
|
+
|
|
92
|
+
# Generate summaries for each segment
|
|
93
|
+
summaries = compressible_segments.map { |segment| summarize(segment) }.compact
|
|
94
|
+
|
|
95
|
+
return false if summaries.empty?
|
|
96
|
+
|
|
97
|
+
# Replace original messages with summaries
|
|
98
|
+
replace_with_summaries(session, compressible_segments, summaries)
|
|
99
|
+
|
|
100
|
+
SmartPrompt.logger.info "Session #{session.id} compressed: #{compressible_segments.flatten.count} " \
|
|
101
|
+
"messages replaced with #{summaries.count} summaries"
|
|
102
|
+
true
|
|
103
|
+
rescue => e
|
|
104
|
+
SmartPrompt.logger.error "Compression failed for session #{session.id}: #{e.message}"
|
|
105
|
+
|
|
106
|
+
# Fall back to truncation strategy
|
|
107
|
+
fallback_truncate(session)
|
|
108
|
+
false
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Check if a session should be compressed based on configuration
|
|
113
|
+
# @param session [Session] The session to evaluate
|
|
114
|
+
# @return [Boolean] true if compression is recommended
|
|
115
|
+
def should_compress?(session)
|
|
116
|
+
return false if session.nil?
|
|
117
|
+
|
|
118
|
+
# Check if session has enough messages to warrant compression
|
|
119
|
+
session.message_count > (@min_messages_to_compress * 2)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
# Default summarization prompt template
|
|
125
|
+
def default_prompt
|
|
126
|
+
"Please provide a concise summary of the following conversation, " \
|
|
127
|
+
"preserving key facts, decisions, and context. Focus on the most important " \
|
|
128
|
+
"information that would be needed to continue the conversation:\n\n{content}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Create a simple fallback summary when LLM is not available
|
|
132
|
+
# @param messages [Array<Message>] Messages to summarize
|
|
133
|
+
# @return [String] Simple summary text
|
|
134
|
+
def create_fallback_summary(messages)
|
|
135
|
+
"Previous conversation contained #{messages.count} messages covering various topics."
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Identify segments of messages that can be compressed
|
|
139
|
+
# Strategy: Keep recent messages, compress older ones
|
|
140
|
+
# @param messages [Array<Message>] All messages in the session
|
|
141
|
+
# @return [Array<Array<Message>>] Array of message segments to compress
|
|
142
|
+
def identify_compressible_segments(messages)
|
|
143
|
+
return [] if messages.length <= @min_messages_to_compress
|
|
144
|
+
|
|
145
|
+
# Keep the most recent 5 messages uncompressed
|
|
146
|
+
keep_recent = 5
|
|
147
|
+
|
|
148
|
+
# Separate system messages (never compress) from others
|
|
149
|
+
system_messages = messages.select(&:system_message?)
|
|
150
|
+
non_system_messages = messages.reject(&:system_message?)
|
|
151
|
+
|
|
152
|
+
# If we don't have enough non-system messages, don't compress
|
|
153
|
+
return [] if non_system_messages.length <= keep_recent
|
|
154
|
+
|
|
155
|
+
# Identify the older messages that can be compressed
|
|
156
|
+
compressible = non_system_messages[0...-keep_recent]
|
|
157
|
+
|
|
158
|
+
# Group into segments (for now, treat all compressible messages as one segment)
|
|
159
|
+
compressible.empty? ? [] : [compressible]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Replace original messages with summary messages in the session
|
|
163
|
+
# @param session [Session] The session to modify
|
|
164
|
+
# @param segments [Array<Array<Message>>] Original message segments
|
|
165
|
+
# @param summaries [Array<Message>] Summary messages
|
|
166
|
+
def replace_with_summaries(session, segments, summaries)
|
|
167
|
+
# Get all messages to compress (flatten segments)
|
|
168
|
+
messages_to_remove = segments.flatten
|
|
169
|
+
|
|
170
|
+
# Remove the original messages
|
|
171
|
+
session.messages.reject! { |msg| messages_to_remove.include?(msg) }
|
|
172
|
+
|
|
173
|
+
# Insert summaries at the beginning (after system messages)
|
|
174
|
+
system_messages = session.messages.select(&:system_message?)
|
|
175
|
+
other_messages = session.messages.reject(&:system_message?)
|
|
176
|
+
|
|
177
|
+
# Rebuild messages array: system messages + summaries + remaining messages
|
|
178
|
+
session.instance_variable_set(:@messages, system_messages + summaries + other_messages)
|
|
179
|
+
session.instance_variable_set(:@updated_at, Time.now)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Fallback truncation strategy when summarization fails
|
|
183
|
+
# Simply removes oldest non-system messages to reduce size
|
|
184
|
+
# @param session [Session] The session to truncate
|
|
185
|
+
def fallback_truncate(session)
|
|
186
|
+
SmartPrompt.logger.warn "Falling back to truncation for session #{session.id}"
|
|
187
|
+
|
|
188
|
+
# Keep system messages and recent messages
|
|
189
|
+
system_messages = session.messages.select(&:system_message?)
|
|
190
|
+
non_system_messages = session.messages.reject(&:system_message?)
|
|
191
|
+
|
|
192
|
+
# Keep only the most recent half of non-system messages
|
|
193
|
+
keep_count = (non_system_messages.length * 0.5).ceil
|
|
194
|
+
kept_messages = non_system_messages.last(keep_count)
|
|
195
|
+
|
|
196
|
+
# Update session messages
|
|
197
|
+
session.instance_variable_set(:@messages, system_messages + kept_messages)
|
|
198
|
+
session.instance_variable_set(:@updated_at, Time.now)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module SmartPrompt
|
|
2
|
+
# ContextStrategy defines the interface for context selection strategies
|
|
3
|
+
# Different strategies implement different algorithms for selecting which
|
|
4
|
+
# messages to include in the context window based on various criteria
|
|
5
|
+
module ContextStrategy
|
|
6
|
+
# Select messages from the session to include in context
|
|
7
|
+
# @param messages [Array<Message>] All messages in the session
|
|
8
|
+
# @param max_tokens [Integer, nil] Maximum token limit for selected messages
|
|
9
|
+
# @param current_message [Message, nil] The current message being processed (for relevance)
|
|
10
|
+
# @return [Array<Message>] Selected messages that fit within constraints
|
|
11
|
+
def select_messages(messages, max_tokens, current_message = nil)
|
|
12
|
+
raise NotImplementedError, "#{self.class} must implement #select_messages"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Determine if the session should be compressed
|
|
16
|
+
# @param session [Session] The session to evaluate
|
|
17
|
+
# @return [Boolean] true if compression is recommended
|
|
18
|
+
def should_compress?(session)
|
|
19
|
+
raise NotImplementedError, "#{self.class} must implement #should_compress?"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -1,97 +1,72 @@
|
|
|
1
1
|
require "yaml"
|
|
2
2
|
require "retriable"
|
|
3
3
|
require "numo/narray"
|
|
4
|
-
require "base64"
|
|
5
4
|
|
|
6
5
|
module SmartPrompt
|
|
7
6
|
class Conversation
|
|
8
7
|
include APIHandler
|
|
9
|
-
MODEL_REQUEST_OPTION_KEYS = %w[
|
|
10
|
-
max_tokens
|
|
11
|
-
max_completion_tokens
|
|
12
|
-
top_p
|
|
13
|
-
top_k
|
|
14
|
-
response_format
|
|
15
|
-
tool_choice
|
|
16
|
-
parallel_tool_calls
|
|
17
|
-
seed
|
|
18
|
-
stop
|
|
19
|
-
].freeze
|
|
20
|
-
|
|
21
8
|
attr_reader :messages, :last_response, :config_file
|
|
22
9
|
attr_reader :last_call_id
|
|
10
|
+
attr_reader :session_id
|
|
23
11
|
|
|
24
|
-
def initialize(engine, tools = nil)
|
|
12
|
+
def initialize(engine, tools = nil, session_id = nil)
|
|
25
13
|
SmartPrompt.logger.info "Create Conversation"
|
|
26
14
|
@messages = []
|
|
27
15
|
@engine = engine
|
|
28
16
|
@adapters = engine.adapters
|
|
29
17
|
@llms = engine.llms
|
|
30
|
-
@models = engine.models
|
|
31
18
|
@current_llm_name = nil
|
|
32
19
|
@templates = engine.templates
|
|
33
20
|
@temperature = 0.7
|
|
34
21
|
@current_adapter = engine.current_adapter
|
|
35
22
|
@last_response = nil
|
|
36
23
|
@tools = tools
|
|
37
|
-
@
|
|
38
|
-
@
|
|
39
|
-
@thinking_enabled = nil
|
|
24
|
+
@session_id = session_id
|
|
25
|
+
@use_history_manager = false
|
|
40
26
|
end
|
|
41
27
|
|
|
42
28
|
def use(llm_name)
|
|
43
|
-
llm_name
|
|
44
|
-
raise ConfigurationError, "LLM #{llm_name} not configured" unless @llms.key?(llm_name)
|
|
29
|
+
raise "LLM #{llm_name} not configured" unless @llms.key?(llm_name)
|
|
45
30
|
@current_llm = @llms[llm_name]
|
|
46
31
|
@current_llm_name = llm_name
|
|
47
32
|
self
|
|
48
33
|
end
|
|
49
34
|
|
|
50
|
-
def use_model(model_name)
|
|
51
|
-
model_name = model_name.to_s
|
|
52
|
-
model_config = @models[model_name] || @models[model_name.to_sym]
|
|
53
|
-
raise ConfigurationError, "Model #{model_name} not configured" unless model_config
|
|
54
|
-
|
|
55
|
-
llm_name = model_config["use"] || model_config[:use]
|
|
56
|
-
configured_model_name = model_config["model"] || model_config[:model]
|
|
57
|
-
raise ConfigurationError, "Model #{model_name} must define use" if llm_name.nil? || llm_name.empty?
|
|
58
|
-
raise ConfigurationError, "Model #{model_name} must define model" if configured_model_name.nil? || configured_model_name.empty?
|
|
59
|
-
|
|
60
|
-
use(llm_name)
|
|
61
|
-
model(configured_model_name)
|
|
62
|
-
merge_model_request_options(model_config)
|
|
63
|
-
self
|
|
64
|
-
end
|
|
65
|
-
|
|
66
35
|
def model(model_name)
|
|
67
36
|
@model_name = model_name
|
|
37
|
+
if @engine.config["better_prompt_db"]
|
|
38
|
+
BetterPrompt.add_model(@current_llm_name, @model_name)
|
|
39
|
+
end
|
|
68
40
|
end
|
|
69
41
|
|
|
70
42
|
def temperature(temperature)
|
|
71
43
|
@temperature = temperature
|
|
72
44
|
end
|
|
73
45
|
|
|
74
|
-
def request_options(options = {})
|
|
75
|
-
@request_options.merge!(options || {})
|
|
76
|
-
self
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def thinking(enabled = true)
|
|
80
|
-
@thinking_enabled = enabled
|
|
81
|
-
if @sys_msg
|
|
82
|
-
@sys_msg = thinking_system_message(@sys_msg)
|
|
83
|
-
refresh_system_message(@sys_msg)
|
|
84
|
-
end
|
|
85
|
-
self
|
|
86
|
-
end
|
|
87
|
-
|
|
88
46
|
def history_messages
|
|
89
|
-
|
|
47
|
+
# If using HistoryManager, get messages from session
|
|
48
|
+
if @use_history_manager && @engine.history_manager
|
|
49
|
+
session_messages = @engine.history_manager.get_context(@session_id)
|
|
50
|
+
# Convert Message objects to hash format for backward compatibility
|
|
51
|
+
session_messages.map(&:to_h)
|
|
52
|
+
else
|
|
53
|
+
# Fall back to old implementation
|
|
54
|
+
@engine.history_messages
|
|
55
|
+
end
|
|
90
56
|
end
|
|
91
57
|
|
|
92
58
|
def add_message(msg, with_history = false)
|
|
93
59
|
if with_history
|
|
94
|
-
|
|
60
|
+
# If HistoryManager is available, use it
|
|
61
|
+
if @engine.history_manager
|
|
62
|
+
@use_history_manager = true
|
|
63
|
+
# Ensure we have a session ID
|
|
64
|
+
@session_id ||= generate_default_session_id
|
|
65
|
+
@engine.history_manager.add_message(@session_id, msg)
|
|
66
|
+
else
|
|
67
|
+
# Fall back to old implementation
|
|
68
|
+
@engine.history_messages << msg
|
|
69
|
+
end
|
|
95
70
|
end
|
|
96
71
|
@messages << msg
|
|
97
72
|
end
|
|
@@ -102,59 +77,67 @@ module SmartPrompt
|
|
|
102
77
|
SmartPrompt.logger.info "Use template #{template_name}"
|
|
103
78
|
raise "Template #{template_name} not found" unless @templates.key?(template_name)
|
|
104
79
|
content = @templates[template_name].render(params)
|
|
105
|
-
|
|
80
|
+
add_message({ role: "user", content: content }, with_history)
|
|
81
|
+
if @engine.config["better_prompt_db"]
|
|
82
|
+
BetterPrompt.add_prompt(template_name, "user", content)
|
|
83
|
+
end
|
|
106
84
|
self
|
|
107
85
|
else
|
|
108
|
-
|
|
86
|
+
add_message({ role: "user", content: template_name }, with_history)
|
|
87
|
+
if @engine.config["better_prompt_db"]
|
|
88
|
+
BetterPrompt.add_prompt("NULL", "user", template_name)
|
|
89
|
+
end
|
|
109
90
|
self
|
|
110
91
|
end
|
|
111
92
|
end
|
|
112
93
|
|
|
113
|
-
def sys_msg(message, params)
|
|
114
|
-
@sys_msg =
|
|
115
|
-
add_message({ role: "system", content:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def multimodal_prompt(parts, with_history: false)
|
|
120
|
-
add_message({ role: "user", content: normalize_content_parts(parts) }, with_history)
|
|
121
|
-
self
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def image(source, token_budget: nil, **metadata)
|
|
125
|
-
@pending_content_parts << media_part("image", source, token_budget: token_budget, **metadata)
|
|
126
|
-
self
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def audio(source, **metadata)
|
|
130
|
-
@pending_content_parts << media_part("audio", source, **metadata)
|
|
131
|
-
self
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def video(source, fps: nil, max_seconds: nil, **metadata)
|
|
135
|
-
@pending_content_parts << media_part("video", source, fps: fps, max_seconds: max_seconds, **metadata)
|
|
94
|
+
def sys_msg(message, params = {})
|
|
95
|
+
@sys_msg = message
|
|
96
|
+
add_message({ role: "system", content: message }, params[:with_history])
|
|
97
|
+
if @engine.config["better_prompt_db"]
|
|
98
|
+
BetterPrompt.add_prompt("NULL", "system", message)
|
|
99
|
+
end
|
|
136
100
|
self
|
|
137
101
|
end
|
|
138
102
|
|
|
139
103
|
def send_msg_once
|
|
140
104
|
raise "No LLM selected" if @current_llm.nil?
|
|
141
|
-
@last_response =
|
|
105
|
+
@last_response = @current_llm.send_request(@messages, @model_name, @temperature)
|
|
142
106
|
@messages = []
|
|
143
107
|
@messages << { role: "system", content: @sys_msg }
|
|
144
108
|
@last_response
|
|
145
109
|
end
|
|
146
110
|
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def generate_default_session_id
|
|
114
|
+
# Generate a default session ID based on worker name or timestamp
|
|
115
|
+
"default_#{Time.now.to_i}_#{rand(1000)}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
public
|
|
119
|
+
|
|
147
120
|
def send_msg(params = {})
|
|
148
121
|
Retriable.retriable(RETRY_OPTIONS) do
|
|
149
122
|
raise ConfigurationError, "No LLM selected" if @current_llm.nil?
|
|
123
|
+
if @engine.config["better_prompt_db"]
|
|
124
|
+
if params[:with_history]
|
|
125
|
+
@last_call_id = BetterPrompt.add_model_call(@current_llm_name, @model_name, history_messages, false, @temperature, 0, 0.0, 0, @tools)
|
|
126
|
+
else
|
|
127
|
+
@last_call_id = BetterPrompt.add_model_call(@current_llm_name, @model_name, @messages, false, @temperature, 0, 0.0, 0, @tools)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
150
130
|
if params[:with_history]
|
|
151
|
-
@last_response =
|
|
131
|
+
@last_response = @current_llm.send_request(history_messages, @model_name, @temperature, @tools, nil)
|
|
152
132
|
else
|
|
153
|
-
@last_response =
|
|
133
|
+
@last_response = @current_llm.send_request(@messages, @model_name, @temperature, @tools, nil)
|
|
154
134
|
end
|
|
155
135
|
if @last_response == ""
|
|
156
136
|
@last_response = @current_llm.last_response
|
|
157
137
|
end
|
|
138
|
+
if @engine.config["better_prompt_db"]
|
|
139
|
+
BetterPrompt.add_response(@last_call_id, @last_response, false)
|
|
140
|
+
end
|
|
158
141
|
@messages = []
|
|
159
142
|
@messages << { role: "system", content: @sys_msg }
|
|
160
143
|
@last_response
|
|
@@ -166,10 +149,20 @@ module SmartPrompt
|
|
|
166
149
|
def send_msg_by_stream(params = {}, &proc)
|
|
167
150
|
Retriable.retriable(RETRY_OPTIONS) do
|
|
168
151
|
raise ConfigurationError, "No LLM selected" if @current_llm.nil?
|
|
152
|
+
if @engine.config["better_prompt_db"]
|
|
153
|
+
if params[:with_history]
|
|
154
|
+
@last_call_id = BetterPrompt.add_model_call(@current_llm_name, @model_name, history_messages, true, @temperature, 0, 0.0, 0, @tools)
|
|
155
|
+
else
|
|
156
|
+
@last_call_id = BetterPrompt.add_model_call(@current_llm_name, @model_name, @messages, true, @temperature, 0, 0.0, 0, @tools)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
169
159
|
if params[:with_history]
|
|
170
|
-
|
|
160
|
+
@current_llm.send_request(history_messages, @model_name, @temperature, @tools, proc)
|
|
171
161
|
else
|
|
172
|
-
|
|
162
|
+
@current_llm.send_request(@messages, @model_name, @temperature, @tools, proc)
|
|
163
|
+
end
|
|
164
|
+
if @engine.config["better_prompt_db"]
|
|
165
|
+
BetterPrompt.add_response(@last_call_id, @engine.stream_response, true)
|
|
173
166
|
end
|
|
174
167
|
@messages = []
|
|
175
168
|
@messages << { role: "system", content: @sys_msg }
|
|
@@ -204,119 +197,16 @@ module SmartPrompt
|
|
|
204
197
|
end
|
|
205
198
|
end
|
|
206
199
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def send_llm_request(messages, proc)
|
|
210
|
-
parameters = @current_llm.method(:send_request).parameters
|
|
211
|
-
if parameters.length >= 6
|
|
212
|
-
@current_llm.send_request(messages, @model_name, @temperature, @tools, proc, @request_options)
|
|
213
|
-
else
|
|
214
|
-
@current_llm.send_request(messages, @model_name, @temperature, @tools, proc)
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def merge_model_request_options(model_config)
|
|
219
|
-
explicit_options = model_config["request_options"] || model_config[:request_options] || {}
|
|
220
|
-
@request_options.merge!(explicit_options)
|
|
221
|
-
MODEL_REQUEST_OPTION_KEYS.each do |key|
|
|
222
|
-
value = model_config[key] || model_config[key.to_sym]
|
|
223
|
-
@request_options[key.to_sym] = value unless value.nil?
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
def add_user_content(content, with_history)
|
|
228
|
-
if @pending_content_parts.empty?
|
|
229
|
-
add_message({ role: "user", content: content }, with_history)
|
|
230
|
-
else
|
|
231
|
-
add_message({ role: "user", content: multimodal_content(content) }, with_history)
|
|
232
|
-
@pending_content_parts = []
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def multimodal_content(text)
|
|
237
|
-
parts = @pending_content_parts
|
|
238
|
-
images_and_videos = parts.select { |part| ["image_url", "image", "video_url", "video"].include?(part[:type] || part["type"]) }
|
|
239
|
-
audio_parts = parts.select { |part| ["input_audio", "audio"].include?(part[:type] || part["type"]) }
|
|
240
|
-
other_parts = parts - images_and_videos - audio_parts
|
|
241
|
-
normalize_content_parts(images_and_videos + other_parts + [{ type: "text", text: text.to_s }] + audio_parts)
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def normalize_content_parts(parts)
|
|
245
|
-
parts.map do |part|
|
|
246
|
-
normalized = part.transform_keys(&:to_s)
|
|
247
|
-
normalized["text"] = normalized.delete("content") if normalized["type"] == "text" && normalized.key?("content")
|
|
248
|
-
normalized
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
def media_part(type, source, **metadata)
|
|
253
|
-
case type
|
|
254
|
-
when "image"
|
|
255
|
-
mime_type = detect_image_mime(source)
|
|
256
|
-
data = File.binread(source)
|
|
257
|
-
base64_data = Base64.strict_encode64(data)
|
|
258
|
-
url = "data:#{mime_type};base64,#{base64_data}"
|
|
259
|
-
part = { type: "image_url", image_url: { url: url } }
|
|
260
|
-
when "audio"
|
|
261
|
-
format = detect_audio_format(source)
|
|
262
|
-
data = File.binread(source)
|
|
263
|
-
base64_data = Base64.strict_encode64(data)
|
|
264
|
-
part = { type: "input_audio", input_audio: { data: base64_data, format: format } }
|
|
265
|
-
when "video"
|
|
266
|
-
mime_type = detect_video_mime(source)
|
|
267
|
-
data = File.binread(source)
|
|
268
|
-
base64_data = Base64.strict_encode64(data)
|
|
269
|
-
url = "data:#{mime_type};base64,#{base64_data}"
|
|
270
|
-
part = { type: "video_url", video_url: { url: url } }
|
|
271
|
-
else
|
|
272
|
-
part = { type: type }
|
|
273
|
-
end
|
|
274
|
-
metadata.each do |key, value|
|
|
275
|
-
part[key] = value unless value.nil?
|
|
276
|
-
end
|
|
277
|
-
part
|
|
200
|
+
def generate_image(prompt, params = {})
|
|
201
|
+
@current_llm.generate_image(prompt, params)
|
|
278
202
|
end
|
|
279
203
|
|
|
280
|
-
def
|
|
281
|
-
|
|
282
|
-
case ext
|
|
283
|
-
when ".png" then "image/png"
|
|
284
|
-
when ".jpg", ".jpeg" then "image/jpeg"
|
|
285
|
-
when ".gif" then "image/gif"
|
|
286
|
-
when ".webp" then "image/webp"
|
|
287
|
-
when ".bmp" then "image/bmp"
|
|
288
|
-
when ".svg" then "image/svg+xml"
|
|
289
|
-
else "application/octet-stream"
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
def detect_audio_format(path)
|
|
294
|
-
ext = File.extname(path).downcase.delete_prefix(".")
|
|
295
|
-
%w[wav mp3 ogg flac aac m4a].include?(ext) ? ext : "wav"
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
def detect_video_mime(path)
|
|
299
|
-
ext = File.extname(path).downcase
|
|
300
|
-
case ext
|
|
301
|
-
when ".mp4" then "video/mp4"
|
|
302
|
-
when ".webm" then "video/webm"
|
|
303
|
-
when ".mov" then "video/quicktime"
|
|
304
|
-
when ".avi" then "video/x-msvideo"
|
|
305
|
-
else "application/octet-stream"
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
def thinking_system_message(message)
|
|
310
|
-
message = message.to_s.sub(/\A<\|think\|>\n?/, "")
|
|
311
|
-
return message if @thinking_enabled == false
|
|
312
|
-
return message unless @thinking_enabled == true
|
|
313
|
-
|
|
314
|
-
"<|think|>\n#{message}"
|
|
204
|
+
def edit_image(prompt, params = {})
|
|
205
|
+
@current_llm.edit_image(prompt, params)
|
|
315
206
|
end
|
|
316
207
|
|
|
317
|
-
def
|
|
318
|
-
|
|
319
|
-
system_message[:content] = message if system_message
|
|
208
|
+
def save_image(image_data, output_dir = "./output", filename_prefix = "generated_image")
|
|
209
|
+
@current_llm.save_image(image_data, output_dir, filename_prefix)
|
|
320
210
|
end
|
|
321
211
|
end
|
|
322
212
|
end
|