<:body>
+
{{i18n "discourse_ai.ai_bot.debug_ai_modal.request_tokens"}}
diff --git a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
index d60ff11c..7c36955d 100644
--- a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
+++ b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss
@@ -161,3 +161,9 @@ span.onebox-ai-llm-title {
.ai-debug-modal__tokens span {
display: block;
}
+
+.d-modal ul.ai-debug-modal__nav {
+ margin: 0 0 1em;
+ padding: 0;
+ border-bottom: none;
+}
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 8ad0bb17..1c3d9c0c 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -271,6 +271,8 @@ en:
copy_response: "Copy response"
request_tokens: "Request tokens:"
response_tokens: "Response tokens:"
+ request: "Request"
+ response: "Response"
share_full_topic_modal:
title: "Share Conversation Publicly"
diff --git a/lib/completions/endpoints/aws_bedrock.rb b/lib/completions/endpoints/aws_bedrock.rb
index b7f3e8bf..7163ff47 100644
--- a/lib/completions/endpoints/aws_bedrock.rb
+++ b/lib/completions/endpoints/aws_bedrock.rb
@@ -111,23 +111,30 @@ module DiscourseAi
end
def decode(chunk)
- parsed =
- Aws::EventStream::Decoder
- .new
- .decode_chunk(chunk)
- .first
- .payload
- .string
- .then { JSON.parse(_1) }
+ @decoder ||= Aws::EventStream::Decoder.new
- bytes = parsed.dig("bytes")
+ decoded, _done = @decoder.decode_chunk(chunk)
- if !bytes
- Rails.logger.error("#{self.class.name}: #{parsed.to_s[0..500]}")
- nil
- else
- Base64.decode64(parsed.dig("bytes"))
+ messages = []
+ return messages if !decoded
+
+ i = 0
+ while decoded
+ parsed = JSON.parse(decoded.payload.string)
+ messages << Base64.decode64(parsed["bytes"])
+
+ decoded, _done = @decoder.decode_chunk
+
+ i += 1
+ if i > 10_000
+ Rails.logger.error(
+ "DiscourseAI: Stream decoder looped too many times, logic error needs fixing",
+ )
+ break
+ end
end
+
+ messages
rescue JSON::ParserError,
Aws::EventStream::Errors::MessageChecksumError,
Aws::EventStream::Errors::PreludeChecksumError => e
@@ -161,8 +168,14 @@ module DiscourseAi
result
end
- def partials_from(decoded_chunk)
- [decoded_chunk]
+ def partials_from(decoded_chunks)
+ decoded_chunks
+ end
+
+ def chunk_to_string(chunk)
+ joined = +chunk.join("\n")
+ joined << "\n" if joined.length > 0
+ joined
end
end
end
diff --git a/lib/completions/endpoints/base.rb b/lib/completions/endpoints/base.rb
index 7a914a1e..9f34aa31 100644
--- a/lib/completions/endpoints/base.rb
+++ b/lib/completions/endpoints/base.rb
@@ -168,9 +168,15 @@ module DiscourseAi
if decoded_chunk.nil?
raise CompletionFailed, "#{self.class.name}: Failed to decode LLM completion"
end
- response_raw << decoded_chunk
+ response_raw << chunk_to_string(decoded_chunk)
- redo_chunk = leftover + decoded_chunk
+ if decoded_chunk.is_a?(String)
+ redo_chunk = leftover + decoded_chunk
+ else
+ # custom implementation for endpoint
+ # no implicit leftover support
+ redo_chunk = decoded_chunk
+ end
raw_partials = partials_from(redo_chunk)
@@ -347,6 +353,14 @@ module DiscourseAi
response.include?("")
end
+ def chunk_to_string(chunk)
+ if chunk.is_a?(String)
+ chunk
+ else
+ chunk.to_s
+ end
+ end
+
def add_to_function_buffer(function_buffer, partial: nil, payload: nil)
if payload&.include?("")
matches = payload.match(%r{.*}m)
diff --git a/lib/completions/endpoints/cohere.rb b/lib/completions/endpoints/cohere.rb
index 4903fe00..47190742 100644
--- a/lib/completions/endpoints/cohere.rb
+++ b/lib/completions/endpoints/cohere.rb
@@ -26,9 +26,7 @@ module DiscourseAi
def normalize_model_params(model_params)
model_params = model_params.dup
-
model_params[:p] = model_params.delete(:top_p) if model_params[:top_p]
-
model_params
end
diff --git a/lib/configuration/llm_enumerator.rb b/lib/configuration/llm_enumerator.rb
index ab9757bc..d7618795 100644
--- a/lib/configuration/llm_enumerator.rb
+++ b/lib/configuration/llm_enumerator.rb
@@ -10,15 +10,15 @@ module DiscourseAi
end
def self.values
- @values ||=
- DiscourseAi::Completions::Llm.models_by_provider.flat_map do |provider, models|
- endpoint =
- DiscourseAi::Completions::Endpoints::Base.endpoint_for(provider.to_s, models.first)
+ # do not cache cause settings can change this
+ DiscourseAi::Completions::Llm.models_by_provider.flat_map do |provider, models|
+ endpoint =
+ DiscourseAi::Completions::Endpoints::Base.endpoint_for(provider.to_s, models.first)
- models.map do |model_name|
- { name: endpoint.display_name(model_name), value: "#{provider}:#{model_name}" }
- end
+ models.map do |model_name|
+ { name: endpoint.display_name(model_name), value: "#{provider}:#{model_name}" }
end
+ end
end
end
end
diff --git a/spec/lib/completions/endpoints/aws_bedrock_spec.rb b/spec/lib/completions/endpoints/aws_bedrock_spec.rb
index 7d3554cd..5b87c095 100644
--- a/spec/lib/completions/endpoints/aws_bedrock_spec.rb
+++ b/spec/lib/completions/endpoints/aws_bedrock_spec.rb
@@ -86,6 +86,10 @@ RSpec.describe DiscourseAi::Completions::Endpoints::AwsBedrock do
Aws::EventStream::Encoder.new.encode(aws_message)
end
+ # stream 1 letter at a time
+ # cause we need to handle this case
+ messages = messages.join("").split
+
bedrock_mock.with_chunk_array_support do
stub_request(
:post,