Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 1eb1993

Browse files
committed
Handle malformed gemini replies
1 parent 41a002b commit 1eb1993

File tree

3 files changed

+50
-20
lines changed

3 files changed

+50
-20
lines changed

lib/ai_bot/tools/create_artifact.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ def self.signature
2121
required: true,
2222
},
2323
{
24-
name: "html_content",
25-
description: "The HTML content for the artifact",
24+
name: "html_body",
25+
description: "The HTML content for the BODY tag (do not include the BODY tag)",
2626
type: "string",
2727
required: true,
2828
},
@@ -43,7 +43,7 @@ def invoke
4343
post = Post.find_by(id: context[:post_id])
4444
return error_response("No post context found") unless post
4545

46-
html = parameters[:html_content].to_s
46+
html = parameters[:html_body].to_s
4747
css = parameters[:css].to_s
4848
js = parameters[:js].to_s
4949

lib/completions/endpoints/gemini.rb

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -173,25 +173,24 @@ def decode_chunk(chunk)
173173
.decode(chunk)
174174
.map do |parsed|
175175
update_usage(parsed)
176-
parsed
177-
.dig(:candidates, 0, :content, :parts)
178-
.map do |part|
179-
if part[:text]
180-
part = part[:text]
181-
if part != ""
182-
part
183-
else
184-
nil
185-
end
186-
elsif part[:functionCall]
187-
@tool_index += 1
188-
ToolCall.new(
189-
id: "tool_#{@tool_index}",
190-
name: part[:functionCall][:name],
191-
parameters: part[:functionCall][:args],
192-
)
176+
parts = parsed.dig(:candidates, 0, :content, :parts)
177+
parts&.map do |part|
178+
if part[:text]
179+
part = part[:text]
180+
if part != ""
181+
part
182+
else
183+
nil
193184
end
185+
elsif part[:functionCall]
186+
@tool_index += 1
187+
ToolCall.new(
188+
id: "tool_#{@tool_index}",
189+
name: part[:functionCall][:name],
190+
parameters: part[:functionCall][:args],
191+
)
194192
end
193+
end
195194
end
196195
.flatten
197196
.compact

spec/lib/completions/endpoints/gemini_spec.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,37 @@ def tool_response
324324
expect(log.response_tokens).to eq(4)
325325
end
326326

327+
it "Can correctly handle malformed responses" do
328+
response = <<~TEXT
329+
data: {"candidates": [{"content": {"parts": [{"text": "Certainly"}],"role": "model"}}],"usageMetadata": {"promptTokenCount": 399,"totalTokenCount": 399},"modelVersion": "gemini-1.5-pro-002"}
330+
331+
data: {"candidates": [{"content": {"parts": [{"text": "! I'll create a simple \\"Hello, World!\\" page where each letter"}],"role": "model"},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"}]}],"usageMetadata": {"promptTokenCount": 399,"totalTokenCount": 399},"modelVersion": "gemini-1.5-pro-002"}
332+
333+
data: {"candidates": [{"content": {"parts": [{"text": " has a different color using inline styles for simplicity. Each letter will be wrapped"}],"role": "model"},"safetyRatings": [{"category": "HARM_CATEGORY_HATE_SPEECH","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_DANGEROUS_CONTENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_HARASSMENT","probability": "NEGLIGIBLE"},{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT","probability": "NEGLIGIBLE"}]}],"usageMetadata": {"promptTokenCount": 399,"totalTokenCount": 399},"modelVersion": "gemini-1.5-pro-002"}
334+
335+
data: {"candidates": [{"content": {"parts": [{"text": ""}],"role": "model"},"finishReason": "STOP"}],"usageMetadata": {"promptTokenCount": 399,"candidatesTokenCount": 191,"totalTokenCount": 590},"modelVersion": "gemini-1.5-pro-002"}
336+
337+
data: {"candidates": [{"finishReason": "MALFORMED_FUNCTION_CALL"}],"usageMetadata": {"promptTokenCount": 399,"candidatesTokenCount": 191,"totalTokenCount": 590},"modelVersion": "gemini-1.5-pro-002"}
338+
339+
TEXT
340+
341+
llm = DiscourseAi::Completions::Llm.proxy("custom:#{model.id}")
342+
url = "#{model.url}:streamGenerateContent?alt=sse&key=123"
343+
344+
output = []
345+
346+
stub_request(:post, url).to_return(status: 200, body: response)
347+
llm.generate("Hello", user: user) { |partial| output << partial }
348+
349+
expect(output).to eq(
350+
[
351+
"Certainly",
352+
"! I'll create a simple \"Hello, World!\" page where each letter",
353+
" has a different color using inline styles for simplicity. Each letter will be wrapped",
354+
],
355+
)
356+
end
357+
327358
it "Can correctly handle streamed responses even if they are chunked badly" do
328359
data = +""
329360
data << "da|ta: |"

0 commit comments

Comments
 (0)