import json import logging import os import asyncio import tempfile from typing import List, Dict, Optional, Any import openai from core.config import settings from core.prompts import get_report_prompt, get_report_suggestion_prompt from services.s3_service import s3_service logger = logging.getLogger(__name__) class ReportService: def __init__(self): self.openai_client = openai.OpenAI(api_key=settings.OPENAI_API_KEY) async def generate_format_suggestions( self, file_key: Optional[str] = None, text_input: Optional[str] = None, language: str = "Japanese" ) -> List[Dict[str, str]]: """ Generates 4 AI-suggested report formats based on the content. Uses asyncio.to_thread for all blocking I/O operations. """ try: system_prompt = get_report_suggestion_prompt(language) if file_key: # Download PDF from S3 (non-blocking) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") tmp_path = tmp.name tmp.close() try: await asyncio.to_thread( s3_service.s3_client.download_file, settings.AWS_S3_BUCKET, file_key, tmp_path ) # Upload to OpenAI (non-blocking) def upload_to_openai(): with open(tmp_path, "rb") as f: return self.openai_client.files.create( file=f, purpose="assistants" ) uploaded_file = await asyncio.to_thread(upload_to_openai) messages = [ {"role": "system", "content": system_prompt}, { "role": "user", "content": [ { "type": "file", "file": {"file_id": uploaded_file.id} } ] } ] # Call OpenAI (non-blocking) response = await asyncio.to_thread( self.openai_client.chat.completions.create, model="gpt-4o-mini", messages=messages, response_format={"type": "json_object"}, temperature=0.7 ) # Clean up OpenAI file (non-blocking) await asyncio.to_thread( self.openai_client.files.delete, uploaded_file.id ) raw_content = response.choices[0].message.content finally: if os.path.exists(tmp_path): await asyncio.to_thread(os.remove, tmp_path) elif text_input: messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Analyze this content:\n\n{text_input}"} ] # Call OpenAI (non-blocking) response = await asyncio.to_thread( self.openai_client.chat.completions.create, model="gpt-4o-mini", messages=messages, response_format={"type": "json_object"}, temperature=0.7 ) raw_content = response.choices[0].message.content else: raise ValueError("Either file_key or text_input must be provided") data = json.loads(raw_content) return data.get("suggestions", []) except Exception as e: logger.error(f"Format suggestion failed: {e}") return [] async def generate_report( self, file_key: Optional[str] = None, text_input: Optional[str] = None, format_key: str = "briefing_doc", custom_prompt: Optional[str] = None, language: str = "Japanese" ) -> str: """ Generates a full report based on the selected format. Uses asyncio.to_thread for all blocking I/O operations. """ try: base_prompt = get_report_prompt(format_key, custom_prompt or "", language) # Language styling instruction if language == "Japanese": system_prompt = ( "あなたは日本語でレポートを作成するAIアシスタントです。すべての回答は日本語で書いてください。\n\n" f"{base_prompt}\n\n" "重要: レポート全体を日本語で書いてください。回答はマークダウン形式で、適切な見出し、箇跨書き、構造を使用して読みやすくフォーマットしてください。" ) else: system_prompt = ( "You are an AI assistant that creates reports in English. Write all responses in English.\n\n" f"{base_prompt}\n\n" "IMPORTANT: Write the entire report in English. Please format your response in markdown with proper headings, bullet points, and structure for easy reading." ) if file_key: # Download PDF from S3 (non-blocking) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") tmp_path = tmp.name tmp.close() try: await asyncio.to_thread( s3_service.s3_client.download_file, settings.AWS_S3_BUCKET, file_key, tmp_path ) # Upload to OpenAI (non-blocking) def upload_to_openai(): with open(tmp_path, "rb") as f: return self.openai_client.files.create( file=f, purpose="assistants" ) uploaded_file = await asyncio.to_thread(upload_to_openai) messages = [ {"role": "system", "content": system_prompt}, { "role": "user", "content": [ { "type": "file", "file": {"file_id": uploaded_file.id} } ] } ] # Call OpenAI (non-blocking) response = await asyncio.to_thread( self.openai_client.chat.completions.create, model="gpt-4o-mini", messages=messages, temperature=0.7 ) # Clean up OpenAI (non-blocking) await asyncio.to_thread( self.openai_client.files.delete, uploaded_file.id ) return response.choices[0].message.content finally: if os.path.exists(tmp_path): await asyncio.to_thread(os.remove, tmp_path) elif text_input: messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Please analyze the following content and generate a report based on it:\n\n{text_input}"} ] # Call OpenAI (non-blocking) response = await asyncio.to_thread( self.openai_client.chat.completions.create, model="gpt-4o-mini", messages=messages, temperature=0.7 ) return response.choices[0].message.content else: raise ValueError("Either file_key or text_input must be provided") except Exception as e: logger.error(f"Report generation failed: {e}") raise report_service = ReportService()