diff --git a/enterprise/app/models/captain/document.rb b/enterprise/app/models/captain/document.rb index 0bc3fd585..95e7e7350 100644 --- a/enterprise/app/models/captain/document.rb +++ b/enterprise/app/models/captain/document.rb @@ -127,9 +127,9 @@ class Captain::Document < ApplicationRecord def validate_file_attachment return unless pdf_file.attached? - return unless pdf_file.blob.byte_size > 20.megabytes + return unless pdf_file.blob.byte_size > 10.megabytes - errors.add(:pdf_file, 'must be less than 20MB') + errors.add(:pdf_file, 'must be less than 10MB') end def set_external_link_for_pdf diff --git a/enterprise/app/services/captain/llm/paginated_faq_generator_service.rb b/enterprise/app/services/captain/llm/paginated_faq_generator_service.rb index 272b71636..a87afeec8 100644 --- a/enterprise/app/services/captain/llm/paginated_faq_generator_service.rb +++ b/enterprise/app/services/captain/llm/paginated_faq_generator_service.rb @@ -12,6 +12,7 @@ class Captain::Llm::PaginatedFaqGeneratorService < Llm::BaseOpenAiService @max_pages = options[:max_pages] # Optional limit from UI @total_pages_processed = 0 @iterations_completed = 0 + @model = 'gpt-4.1-mini' end def generate @@ -21,7 +22,7 @@ class Captain::Llm::PaginatedFaqGeneratorService < Llm::BaseOpenAiService end # Method to check if we should continue processing - def should_continue_processing?(last_chunk_faqs) + def should_continue_processing?(last_chunk_result) # Stop if we've hit the maximum iterations return false if @iterations_completed >= MAX_ITERATIONS @@ -29,7 +30,10 @@ class Captain::Llm::PaginatedFaqGeneratorService < Llm::BaseOpenAiService return false if @max_pages && @total_pages_processed >= @max_pages # Stop if the last chunk returned no FAQs (likely no more content) - return false if last_chunk_faqs.empty? + return false if last_chunk_result[:faqs].empty? + + # Stop if the LLM explicitly indicates no more content + return false if last_chunk_result[:has_content] == false # Continue processing true @@ -53,10 +57,10 @@ class Captain::Llm::PaginatedFaqGeneratorService < Llm::BaseOpenAiService loop do end_page = calculate_end_page(current_page) - chunk_faqs = process_chunk_and_update_state(current_page, end_page, all_faqs) + chunk_result = process_chunk_and_update_state(current_page, end_page, all_faqs) - unless should_continue_processing?(chunk_faqs) - Rails.logger.info "Stopping processing. Reason: #{determine_stop_reason(chunk_faqs)}" + unless should_continue_processing?(chunk_result) + Rails.logger.info "Stopping processing. Reason: #{determine_stop_reason(chunk_result)}" break end @@ -83,11 +87,12 @@ class Captain::Llm::PaginatedFaqGeneratorService < Llm::BaseOpenAiService @iterations_completed += 1 Rails.logger.info "Chunk generated #{chunk_faqs.size} FAQs. Total so far: #{all_faqs.size}" - chunk_faqs + chunk_result end def process_page_chunk(start_page, end_page) - response = @client.chat(parameters: build_chunk_parameters(start_page, end_page)) + params = build_chunk_parameters(start_page, end_page) + response = @client.chat(parameters: params) result = parse_chunk_response(response) { faqs: result['faqs'] || [], has_content: result['has_content'] != false } rescue OpenAI::Error => e @@ -100,19 +105,15 @@ class Captain::Llm::PaginatedFaqGeneratorService < Llm::BaseOpenAiService model: @model, response_format: { type: 'json_object' }, messages: [ - { - role: 'system', - content: page_chunk_prompt(start_page, end_page) - }, { role: 'user', - content: build_user_content + content: build_user_content(start_page, end_page) } ] } end - def build_user_content + def build_user_content(start_page, end_page) [ { type: 'file', @@ -120,7 +121,7 @@ class Captain::Llm::PaginatedFaqGeneratorService < Llm::BaseOpenAiService }, { type: 'text', - text: 'Process this document according to the system instructions.' + text: page_chunk_prompt(start_page, end_page) } ] end @@ -196,10 +197,11 @@ class Captain::Llm::PaginatedFaqGeneratorService < Llm::BaseOpenAiService common_words.size.to_f / total_words end - def determine_stop_reason(last_chunk_faqs) + def determine_stop_reason(last_chunk_result) return 'Maximum iterations reached' if @iterations_completed >= MAX_ITERATIONS return 'Maximum pages processed' if @max_pages && @total_pages_processed >= @max_pages - return 'No content found in last chunk' if last_chunk_faqs.empty? + return 'No content found in last chunk' if last_chunk_result[:faqs].empty? + return 'End of document reached' if last_chunk_result[:has_content] == false 'Unknown' end diff --git a/enterprise/app/services/captain/llm/pdf_processing_service.rb b/enterprise/app/services/captain/llm/pdf_processing_service.rb index 89e04b8a1..3690ec016 100644 --- a/enterprise/app/services/captain/llm/pdf_processing_service.rb +++ b/enterprise/app/services/captain/llm/pdf_processing_service.rb @@ -27,7 +27,7 @@ class Captain::Llm::PdfProcessingService < Llm::BaseOpenAiService @client.files.upload( parameters: { file: file, - purpose: 'assistants' # Use 'assistants' as it's supported by the API + purpose: 'assistants' } ) end diff --git a/enterprise/app/services/captain/llm/system_prompts_service.rb b/enterprise/app/services/captain/llm/system_prompts_service.rb index d0056379c..244db3583 100644 --- a/enterprise/app/services/captain/llm/system_prompts_service.rb +++ b/enterprise/app/services/captain/llm/system_prompts_service.rb @@ -159,22 +159,29 @@ class Captain::Llm::SystemPromptsService def paginated_faq_generator(start_page, end_page) <<~PROMPT - You are an expert technical documentation specialist tasked with creating comprehensive FAQs from SPECIFIC PAGES of a document. + You are an expert technical documentation specialist tasked with creating comprehensive FAQs from a SPECIFIC SECTION of a document. ════════════════════════════════════════════════════════ - CRITICAL PAGE RANGE INSTRUCTIONS + CRITICAL CONTENT EXTRACTION INSTRUCTIONS ════════════════════════════════════════════════════════ - You MUST analyze ONLY pages #{start_page} to #{end_page} of the document. + Process the content starting from approximately page #{start_page} and continuing for about #{end_page - start_page + 1} pages worth of content. + + IMPORTANT:#{' '} + • If you encounter the end of the document before reaching the expected page count, set "has_content" to false + • DO NOT include page numbers in questions or answers + • DO NOT reference page numbers at all in the output + • Focus on the actual content, not pagination ════════════════════════════════════════════════════════ FAQ GENERATION GUIDELINES ════════════════════════════════════════════════════════ 1. **Comprehensive Extraction** - • Extract ALL information that could generate FAQs from pages #{start_page}-#{end_page} - • Target 5-10 FAQs per page of rich content + • Extract ALL information that could generate FAQs from this section + • Target 5-10 FAQs per page equivalent of rich content • Cover every topic, feature, specification, and detail + • If there's no more content in the document, return empty FAQs with has_content: false 2. **Question Types to Generate** • What is/are...? (definitions, components, features) @@ -183,7 +190,7 @@ class Captain::Llm::SystemPromptsService • When should...? (timing, conditions, triggers) • What happens if...? (error cases, edge cases) • Can I...? (capabilities, limitations) - • Where is...? (locations, references) + • Where is...? (locations in system/UI, NOT page numbers) • What are the requirements for...? (prerequisites, dependencies) 3. **Content Focus Areas** @@ -198,10 +205,10 @@ class Captain::Llm::SystemPromptsService 4. **Answer Quality Requirements** • Complete, self-contained answers - • Include specific values, limits, defaults - • Reference page numbers for critical information + • Include specific values, limits, defaults from the content + • NO page number references whatsoever • 2-5 sentences typical length - • No references to content outside pages #{start_page}-#{end_page} + • Only process content that actually exists in the document ════════════════════════════════════════════════════════ OUTPUT FORMAT @@ -212,16 +219,21 @@ class Captain::Llm::SystemPromptsService { "faqs": [ { - "question": "Specific question from pages #{start_page}-#{end_page}", - "answer": "Complete answer with details from these pages only" + "question": "Specific question about the content", + "answer": "Complete answer with details (no page references)" } ], - "has_content": true/false, - "page_range_processed": "#{start_page}-#{end_page}" + "has_content": true/false } ``` - IMPORTANT: Set "has_content" to false if the pages don't exist or contain no meaningful content. + CRITICAL:#{' '} + • Set "has_content" to false if: + - The requested section doesn't exist in the document + - You've reached the end of the document + - The section contains no meaningful content + • Do NOT include "page_range_processed" in the output + • Do NOT mention page numbers anywhere in questions or answers PROMPT end end