diff --git a/lib/ruby_llm/providers/vertexai/chat.rb b/lib/ruby_llm/providers/vertexai/chat.rb index c059d39e6..012ce557b 100644 --- a/lib/ruby_llm/providers/vertexai/chat.rb +++ b/lib/ruby_llm/providers/vertexai/chat.rb @@ -5,9 +5,31 @@ module Providers class VertexAI # Chat methods for the Vertex AI implementation module Chat + def render_payload(...) + payload = super + payload[:contents] = normalize_tool_response_roles(payload[:contents]) + payload + end + def completion_url "projects/#{@config.vertexai_project_id}/locations/#{@config.vertexai_location}/publishers/google/models/#{@model}:generateContent" # rubocop:disable Layout/LineLength end + + private + + def normalize_tool_response_roles(contents) + Array(contents).map do |content| + next content unless tool_response_content?(content) + + content.merge(role: 'user') + end + end + + def tool_response_content?(content) + content[:role] == 'function' && Array(content[:parts]).any? do |part| + part[:functionResponse] + end + end end end end diff --git a/spec/ruby_llm/providers/vertex_ai_spec.rb b/spec/ruby_llm/providers/vertex_ai_spec.rb index 22e82749f..b52199235 100644 --- a/spec/ruby_llm/providers/vertex_ai_spec.rb +++ b/spec/ruby_llm/providers/vertex_ai_spec.rb @@ -36,4 +36,28 @@ end end end + + describe '#render_payload' do + let(:location) { 'us-central1' } + let(:model) { instance_double(RubyLLM::Model::Info, id: 'gemini-3.1-flash-lite-preview') } + + it 'normalizes tool response roles to user for Vertex AI' do + messages = [ + RubyLLM::Message.new( + role: :assistant, + content: '', + tool_calls: { + 'call_1' => RubyLLM::ToolCall.new(id: 'call_1', name: 'weather', arguments: {}) + } + ), + RubyLLM::Message.new(role: :tool, content: 'Sunny', tool_call_id: 'call_1') + ] + + payload = provider.send(:render_payload, messages, tools: [], temperature: nil, model: model) + + expect(payload[:contents].last[:role]).to eq('user') + expect(payload[:contents].last[:parts][0][:functionResponse][:name]).to eq('weather') + expect(payload[:contents].last[:parts][0][:functionResponse][:response]).to include(name: 'weather') + end + end end