move system prompt to user prompt for better accuracy

This commit is contained in:
Tanmay Deep Sharma
2025-08-06 16:13:33 +05:30
parent a56c4a5f8c
commit 1b54513a03
4 changed files with 47 additions and 33 deletions
+2 -2
View File
@@ -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
@@ -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
@@ -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
@@ -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