From 62ff1bd883bea5bcd1809c6917baacdd52f194a1 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 4 Apr 2025 13:33:36 +0530 Subject: [PATCH] test: add specs for chat helper --- .../llm/assistant_chat_service_spec.rb | 128 +++++++++++++----- 1 file changed, 97 insertions(+), 31 deletions(-) diff --git a/spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb b/spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb index 4520a30ba..195da219b 100644 --- a/spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb +++ b/spec/enterprise/services/captain/llm/assistant_chat_service_spec.rb @@ -4,6 +4,7 @@ RSpec.describe Captain::Llm::AssistantChatService do let(:captain_assistant) { create(:captain_assistant) } let(:service) { described_class.new(assistant: captain_assistant) } let(:client) { instance_double(OpenAI::Client) } + let(:input) { 'How can I help you?' } before do create(:installation_config, name: 'CAPTAIN_OPEN_AI_API_KEY', value: 'test-key') @@ -11,7 +12,6 @@ RSpec.describe Captain::Llm::AssistantChatService do end describe '#generate_response' do - let(:input) { 'How can I help you?' } let(:previous_messages) { [{ role: 'user', content: 'Previous message' }] } let(:openai_response) do { @@ -67,6 +67,33 @@ RSpec.describe Captain::Llm::AssistantChatService do expect(messages.first[:content]).to include(captain_assistant.config['product_name']) end end + + it 'includes documentation search tool in chat parameters' do + service.generate_response(input) + expect(client).to have_received(:chat) do |params| + tools = params[:parameters][:tools] + expect(tools).to contain_exactly( + hash_including( + type: 'function', + function: hash_including( + name: 'search_documentation', + parameters: hash_including( + properties: hash_including( + search_query: hash_including(type: 'string') + ) + ) + ) + ) + ) + end + end + + it 'includes json response format in chat parameters' do + service.generate_response(input) + expect(client).to have_received(:chat) do |params| + expect(params[:parameters][:response_format]).to eq({ type: 'json_object' }) + end + end end context 'when input is empty' do @@ -86,6 +113,9 @@ RSpec.describe Captain::Llm::AssistantChatService do end context 'with documentation search' do + let(:tool_call_id) { 'call_123' } + let(:search_query) { 'test query' } + let(:response_content) { 'Documentation content' } let(:openai_response_with_tool) do { 'choices' => [ @@ -93,10 +123,10 @@ RSpec.describe Captain::Llm::AssistantChatService do 'message' => { 'tool_calls' => [ { - 'id' => 'call_123', + 'id' => tool_call_id, 'function' => { 'name' => 'search_documentation', - 'arguments' => { 'search_query' => 'test query' }.to_json + 'arguments' => { 'search_query' => search_query }.to_json } } ] @@ -106,54 +136,90 @@ RSpec.describe Captain::Llm::AssistantChatService do } end - let(:search_results) { "Question: Test\nAnswer: Test answer" } - before do - allow(client).to receive(:chat).and_return(openai_response_with_tool, openai_response) - allow(service).to receive(:fetch_documentation).and_return(search_results) + allow(client).to receive(:chat) + .and_return(openai_response_with_tool, openai_response) + allow(captain_assistant.responses).to receive(:approved).and_return(captain_assistant.responses) + allow(captain_assistant.responses).to receive(:search).with(search_query) + .and_return([ + instance_double(Captain::AssistantResponse, + question: 'Test Q?', + answer: 'Test A', + documentable: nil) + ]) end - it 'handles documentation search tool calls' do + it 'processes tool calls and fetches documentation' do response = service.generate_response(input) + expect(client).to have_received(:chat).at_least(:once) expect(response).to eq({ 'reasoning' => 'This is a helpful response', 'response' => 'I can assist you with your questions.' }) - expect(client).to have_received(:chat).twice + end + + it 'appends tool calls and responses to messages' do + service.generate_response(input) + expect(client).to have_received(:chat).at_least(:once) do |params| + messages = params[:parameters][:messages] + assistant_message = messages.find { |m| m[:role] == 'assistant' } + expect(assistant_message).to include( + tool_calls: array_including( + hash_including( + 'id' => tool_call_id, + 'function' => hash_including( + 'name' => 'search_documentation', + 'arguments' => { 'search_query' => search_query }.to_json + ) + ) + ) + ) + end + end + + context 'with external link' do + before do + allow(captain_assistant.responses).to receive(:search).with(search_query) + .and_return([ + instance_double(Captain::AssistantResponse, + question: 'Test Q?', + answer: 'Test A', + documentable: instance_double(Captain::Document, + external_link: 'https://example.com')) + ]) + end + + it 'includes source in formatted response' do + service.generate_response(input) + expect(client).to have_received(:chat).at_least(:once) do |params| + messages = params[:parameters][:messages] + tool_response = messages.find { |m| m[:role] == 'tool' } + expect(tool_response[:content]).to include('Source: https://example.com') + end + end end end end describe '#chat_parameters' do before do - allow(client).to receive(:chat).and_return( - { - 'choices' => [ - { - 'message' => { - 'content' => { - reasoning: 'Test reasoning', - response: 'Test response' - }.to_json - } - } - ] - } - ) + allow(client).to receive(:chat).and_return({ + 'choices' => [ + { + 'message' => { + 'content' => { reasoning: '', response: '' }.to_json + } + } + ] + }) end it 'includes correct model and response format' do - service.generate_response('test') - expect(client).to have_received(:chat) do |params| + service.generate_response(input) + expect(client).to have_received(:chat).at_least(:once) do |params| parameters = params[:parameters] expect(parameters[:model]).to eq('gpt-4o-mini') expect(parameters[:response_format]).to eq({ type: 'json_object' }) - expect(parameters[:tools]).to include( - hash_including( - type: 'function', - function: hash_including(name: 'search_documentation') - ) - ) end end end