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

Commit cc4e9e0

Browse files
authored
FIX: normalize keys in structured output (#1468)
* FIX: normalize keys in structured output Previously we did not validate the hash passed in to structured outputs which could either be string based or symbol base Specifically this broke structured outputs for Gemini in some specific cases. * comment out flake
1 parent 73768ce commit cc4e9e0

File tree

3 files changed

+46
-2
lines changed

3 files changed

+46
-2
lines changed

lib/completions/llm.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def presets
8080
tokens: 800_000,
8181
endpoint:
8282
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash",
83-
display_name: "Gemini 2.5 Pro",
83+
display_name: "Gemini 2.5 Flash",
8484
input_cost: 0.30,
8585
output_cost: 2.50,
8686
},
@@ -379,6 +379,12 @@ def generate(
379379

380380
model_params[:temperature] = temperature if temperature
381381
model_params[:top_p] = top_p if top_p
382+
383+
# internals expect symbolized keys, so we normalize here
384+
response_format =
385+
JSON.parse(response_format.to_json, symbolize_names: true) if response_format &&
386+
response_format.is_a?(Hash)
387+
382388
model_params[:response_format] = response_format if response_format
383389
model_params.merge!(extra_model_params) if extra_model_params
384390

spec/lib/completions/endpoints/gemini_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,7 @@ def tool_response
612612
).to_return(status: 200, body: response)
613613

614614
structured_response = nil
615+
615616
llm.generate("Hello", response_format: schema, user: user) do |partial|
616617
structured_response = partial
617618
end
@@ -626,6 +627,23 @@ def tool_response
626627
schema.dig(:json_schema, :schema).except(:additionalProperties),
627628
)
628629
expect(parsed.dig(:generationConfig, :responseMimeType)).to eq("application/json")
630+
631+
structured_response = nil
632+
# once more but this time lets have the schema as string keys
633+
llm.generate("Hello", response_format: schema.as_json, user: user) do |partial|
634+
structured_response = partial
635+
end
636+
637+
expect(structured_response.read_buffered_property(:key)).to eq("Hello!\n there")
638+
expect(structured_response.read_buffered_property(:num)).to eq(42)
639+
640+
parsed = JSON.parse(req_body, symbolize_names: true)
641+
642+
# Verify that schema is passed following Gemini API specs.
643+
expect(parsed.dig(:generationConfig, :responseSchema)).to eq(
644+
schema.dig(:json_schema, :schema).except(:additionalProperties),
645+
)
646+
expect(parsed.dig(:generationConfig, :responseMimeType)).to eq("application/json")
629647
end
630648
end
631649

spec/system/ai_helper/ai_composer_helper_spec.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,27 @@ def trigger_composer_helper(content)
8080
expect(ai_helper_menu).to have_custom_prompt_button_enabled
8181
end
8282

83-
it "replaces the composed message with AI generated content" do
83+
xit "replaces the composed message with AI generated content" do
84+
# TODO: @keegan - this is a flake
85+
# Failure/Error: super
86+
87+
# Playwright::TimeoutError:
88+
# Timeout 11000ms exceeded.
89+
# Call log:
90+
# - attempting click action
91+
# - 2 × waiting for element to be visible, enabled and stable
92+
# - - element is not enabled
93+
# - - retrying click action
94+
# - - waiting 20ms
95+
# - 2 × waiting for element to be visible, enabled and stable
96+
# - - element is not enabled
97+
# - - retrying click action
98+
# - - waiting 100ms
99+
# - 21 × waiting for element to be visible, enabled and stable
100+
# - - element is not enabled
101+
# - - retrying click action
102+
# - - waiting 500ms
103+
84104
trigger_composer_helper(input)
85105
ai_helper_menu.fill_custom_prompt(custom_prompt_input)
86106

0 commit comments

Comments
 (0)