| | """ |
| | Academic Paper Generator |
| | |
| | Generates academic papers in LaTeX format from structured JSON data using |
| | language models to create content for each section. |
| | """ |
| |
|
| | import json |
| | import subprocess |
| | import os |
| | import re |
| | from typing import Dict, List, Any, Optional |
| | from dataclasses import dataclass |
| |
|
| | |
| | from prompt.template import PAPER_CHAPTER_PROMPT, PAPER_CHAPTER_WITH_PRECEDING_PROMPT, PAPER_INFO_PROMPT, PAPER_NOTATION_PROMPT |
| | from llm.llm import LLM |
| | from utils.utils import parse_llm_output_to_json |
| |
|
| | |
| | |
| | |
| |
|
| | @dataclass |
| | class Chapter: |
| | """Represents a chapter in the paper with its hierarchical structure and content.""" |
| | path: List[str] |
| | content: str = "" |
| | title: str = "" |
| | is_generated: bool = False |
| | needs_content: bool = False |
| | |
| | @property |
| | def path_string(self) -> str: |
| | """Returns the full path as a string (e.g., 'Problem Analysis > Task 1 Analysis')""" |
| | return " > ".join(self.path) |
| | |
| | @property |
| | def depth(self) -> int: |
| | """Returns the heading level (depth in hierarchy)""" |
| | return len(self.path) |
| | |
| | @property |
| | def display_title(self) -> str: |
| | """Returns the chapter title to display (custom title or last path element)""" |
| | return self.title if self.title else self.path[-1] |
| |
|
| | |
| | |
| | |
| |
|
| | def escape_underscores_in_quotes(text): |
| | pattern = r'(".*?")|(\'.*?\')' |
| | def replace_underscores(match): |
| | content = match.group(0)[1:-1] |
| | escaped_content = content.replace('_', r'\_') |
| | return f'"{escaped_content}"' if match.group(0).startswith('"') else f"'{escaped_content}'" |
| | |
| | result = re.sub(pattern, replace_underscores, text, flags=re.DOTALL) |
| | return result |
| |
|
| |
|
| | class ContentGenerator: |
| | """Interface for generating content using language models""" |
| | |
| | def __init__(self, llm): |
| | self.llm = llm |
| | |
| | def generate_chapter_content(self, prompt: str) -> Dict[str, str]: |
| | """Generate chapter content using the language model""" |
| | response = self.llm.generate(prompt) |
| | response = escape_underscores_in_quotes(response) |
| | response = response.replace("```latex", "").replace("```", "") |
| | |
| | return response |
| | |
| | def _parse_latex_response(self, latex_string: str) -> Dict[str, str]: |
| | """Parse LLM response from LaTeX format""" |
| | pattern = r"```latex\s*\\chapter{\s*(.*?)\s*}\s*(.*)```" |
| | match = re.match(pattern, latex_string.strip(), re.DOTALL) |
| | |
| | if match: |
| | return { |
| | "title": match.group(1).strip(), |
| | "content": match.group(2).strip() |
| | } |
| | |
| | |
| | return { |
| | "title": "", |
| | "content": latex_string |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | class OutlineGenerator: |
| | """Creates the hierarchical structure of the paper""" |
| | |
| | def create_outline(self, task_count: int) -> List[Chapter]: |
| | """Create a complete chapter structure based on number of tasks""" |
| | print(f"Creating paper outline for {task_count} tasks") |
| | |
| | |
| | outline = self._create_base_outline(task_count) |
| | |
| | |
| | chapters = [] |
| | for path in outline: |
| | |
| | needs_content = not any(other[:len(path)] == path and len(other) > len(path) |
| | for other in outline) |
| | chapters.append(Chapter(path=path, needs_content=needs_content)) |
| | |
| | content_chapters = sum(1 for c in chapters if c.needs_content) |
| | print(f"Created {len(chapters)} sections, {content_chapters} require content generation") |
| | for chapter in chapters: |
| | print(chapter.path_string) |
| | return chapters |
| | |
| | def _create_base_outline(self, task_count: int) -> List[List[str]]: |
| | """Define the hierarchical structure of the paper""" |
| | |
| | outline = [ |
| | ["Problem Restatement", "Problem Background"], |
| | ["Problem Restatement", "Problem Statement"], |
| | ["Model Assumptions"], |
| | ["Explanation of Assumptions"], |
| | ["Problem Analysis"] |
| | ] |
| | |
| | |
| | for i in range(1, task_count + 1): |
| | outline.append(["Problem Analysis", f"Task {i} Analysis"]) |
| | |
| | outline.append(["Solution to the Problem"]) |
| | |
| | |
| | for i in range(1, task_count + 1): |
| | outline.append(["Solution to the Problem", f"Task {i} Solution", "Model Setup: Assumptions and Chain Models"]) |
| | outline.append(["Solution to the Problem", f"Task {i} Solution", "Model Calculation"]) |
| | |
| | |
| | outline.extend([ |
| | ["Model Conclusion", "Model Advantages"], |
| | ["Model Conclusion", "Model Limitations"], |
| | ["Notation and Explanations"] |
| | ]) |
| | |
| | return outline |
| |
|
| | def generate_chapter_relevance_map(self, task_count: int) -> Dict[str, List[str]]: |
| | """ |
| | Dynamically generate chapter relevance mapping based on the number of tasks. |
| | |
| | Args: |
| | task_count: Number of tasks in the paper |
| | |
| | Returns: |
| | Dictionary mapping chapter paths to lists of related chapter paths |
| | """ |
| | relevance_map = {} |
| |
|
| | for i in range(1, task_count + 1): |
| | setup_path = f"Solution to the Problem > Task {i} Solution > Model Setup: Assumptions and Chain Models" |
| | relevance_map[setup_path] = [f"Problem Analysis > Task {i} Analysis"] |
| |
|
| | for i in range(1, task_count + 1): |
| | calculation_path = f"Solution to the Problem > Task {i} Solution > Model Calculation" |
| | relevance_map[calculation_path] = [ |
| | f"Problem Analysis > Task {i} Analysis", |
| | f"Solution to the Problem > Task {i} Solution > Model Setup: Assumptions and Chain Models", |
| | ] |
| | |
| | |
| | task_solutions = [] |
| | for i in range(1, task_count + 1): |
| | task_solutions += [ |
| | f"Solution to the Problem > Task {i} Solution > Model Calculation", |
| | f"Solution to the Problem > Task {i} Solution > Model Setup: Assumptions and Chain Models" |
| | ] |
| | |
| | relevance_map["Model Conclusion > Model Advantages"] = task_solutions.copy() |
| | relevance_map["Model Conclusion > Model Limitations"] = task_solutions.copy() |
| | relevance_map["Notation and Explanations"] = task_solutions.copy() |
| | |
| | return relevance_map |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | class ContextExtractor: |
| | """Extracts relevant data from JSON for each chapter""" |
| | |
| | def get_context_for_chapter(self, chapter: Chapter, data: Dict[str, Any]) -> Dict[str, Any]: |
| | """Extract relevant JSON data for a specific chapter""" |
| | path = chapter.path |
| | |
| | |
| | if path == ["Problem Restatement", "Problem Background"]: |
| | return {"problem_background": data.get("problem_background", "")} |
| | |
| | elif path == ["Problem Restatement", "Problem Statement"]: |
| | return {"problem_requirement": data.get("problem_requirement", "")} |
| | |
| | elif path == ["Model Assumptions"]: |
| | return self._get_assumptions_context(data) |
| | |
| | elif path == ["Explanation of Assumptions"]: |
| | return {} |
| | |
| | elif self._is_task_analysis(path): |
| | return self._get_task_analysis_context(path, data) |
| | |
| | elif self._is_model_setup(path): |
| | return self._get_model_setup_context(path, data) |
| | |
| | elif self._is_model_calculation(path): |
| | return self._get_model_calculation_context(path, data) |
| | |
| | |
| | return {} |
| | |
| | def _get_assumptions_context(self, data: Dict[str, Any]) -> Dict[str, Any]: |
| | """Get context for assumptions sections""" |
| | context = {"problem_analysis": data.get("problem_analysis", "")} |
| | |
| | |
| | keys = ['task_description', 'task_analysis', 'mathematical_modeling_process'] |
| | context["tasks"] = [ |
| | {k: v for k, v in task.items() if k in keys} |
| | for task in data['tasks'] |
| | ] |
| | |
| | return context |
| | |
| | def _get_task_analysis_context(self, path: List[str], data: Dict[str, Any]) -> Dict[str, Any]: |
| | """Get context for task analysis sections""" |
| | task_num = self._extract_task_number(path[1]) |
| | if not self._is_valid_task_index(task_num, data): |
| | return {} |
| | |
| | task_data = data["tasks"][task_num] |
| | keys = ['task_analysis', 'task_description'] |
| | return { |
| | f'task_{task_num+1}': { |
| | k: v for k, v in task_data.items() if k in keys |
| | } |
| | } |
| | |
| | def _get_model_setup_context(self, path: List[str], data: Dict[str, Any]) -> Dict[str, Any]: |
| | """Get context for model setup sections""" |
| | task_num = self._extract_task_number(path[1]) |
| | if not self._is_valid_task_index(task_num, data): |
| | return {} |
| | |
| | task_data = data["tasks"][task_num] |
| | keys = ['preliminary_formulas', 'mathematical_modeling_process'] |
| | return { |
| | f'task_{task_num+1}': { |
| | k: task_data.get(k, "") for k in keys |
| | } |
| | } |
| | |
| | def _get_model_calculation_context(self, path: List[str], data: Dict[str, Any]) -> Dict[str, Any]: |
| | """Get context for model calculation sections""" |
| | task_num = self._extract_task_number(path[1]) |
| | if not self._is_valid_task_index(task_num, data): |
| | return {} |
| | |
| | task_data = data["tasks"][task_num] |
| | keys = ['mathematical_modeling_process', 'execution_result', 'solution_interpretation', 'subtask_outcome_analysis'] |
| | return { |
| | f'task_{task_num+1}': { |
| | k: task_data.get(k, "") for k in keys |
| | } |
| | } |
| | |
| | def _is_task_analysis(self, path: List[str]) -> bool: |
| | """Check if path is a task analysis section""" |
| | return (len(path) == 2 and |
| | path[0] == "Problem Analysis" and |
| | path[1].startswith("Task ")) |
| | |
| | def _is_model_setup(self, path: List[str]) -> bool: |
| | """Check if path is a model setup section""" |
| | return (len(path) == 3 and |
| | path[0] == "Solution to the Problem" and |
| | path[1].startswith("Task ") and |
| | path[2] == "Model Setup: Assumptions and Chain Models") |
| | |
| | def _is_model_calculation(self, path: List[str]) -> bool: |
| | """Check if path is a model calculation section""" |
| | return (len(path) == 3 and |
| | path[0] == "Solution to the Problem" and |
| | path[1].startswith("Task ") and |
| | path[2] == "Model Calculation") |
| | |
| | def _extract_task_number(self, task_string: str) -> int: |
| | """Extract task number from strings like 'Task 1 Analysis'""" |
| | try: |
| | return int(task_string.split()[1]) - 1 |
| | except (IndexError, ValueError): |
| | return -1 |
| | |
| | def _is_valid_task_index(self, index: int, data: Dict[str, Any]) -> bool: |
| | """Check if the task index is valid""" |
| | return 0 <= index < len(data.get("tasks", [])) |
| |
|
| | |
| | |
| | |
| |
|
| | class PromptCreator: |
| | """Creates prompts for the language model""" |
| | |
| | def __init__(self): |
| | pass |
| | |
| | def create_prompt(self, |
| | chapter: Chapter, |
| | context: Dict[str, Any], |
| | previous_chapters: List[Chapter]) -> str: |
| | """Create a prompt for generating chapter content""" |
| | |
| | json_str = json.dumps(context, indent=2) |
| | |
| | |
| | previous_text = self._format_previous_chapters(previous_chapters) |
| | |
| | if chapter.path == ["Notation and Explanations"]: |
| | return PAPER_NOTATION_PROMPT.format( |
| | previous_chapters=previous_text, |
| | ) |
| | else: |
| | if json_str == '{}': |
| | return PAPER_CHAPTER_WITH_PRECEDING_PROMPT.format( |
| | chapter_path=chapter.path_string, |
| | previous_chapters=previous_text |
| | ) |
| | else: |
| | |
| | return PAPER_CHAPTER_PROMPT.format( |
| | chapter_path=chapter.path_string, |
| | json_context=json_str, |
| | previous_chapters=previous_text |
| | ) |
| | |
| | def _format_previous_chapters(self, previous_chapters: List[Chapter]) -> str: |
| | """Format previously completed chapters for context""" |
| | if not previous_chapters: |
| | return "" |
| | |
| | text = "" |
| | for chapter in previous_chapters: |
| | text += f"Chapter: {chapter.path_string}\n" |
| | |
| | text += f"{chapter.content}\n\n" |
| | return text |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | class LatexDocumentAssembler: |
| | """Assembles the final LaTeX document from generated chapters""" |
| | |
| | def create_document(self, chapters: List[Chapter], metadata: Dict[str, Any]) -> str: |
| | """Create a complete LaTeX document""" |
| | |
| | ordered_chapters = self._reorder_chapters(chapters) |
| | |
| | |
| | document_parts = [ |
| | self._create_preamble(metadata), |
| | self._create_abstract(metadata), |
| | "\\maketitle", |
| | "\\renewcommand\\cfttoctitlefont{\\hfil\\Large\\bfseries}", |
| | "\\tableofcontents", |
| | "\\newpage", |
| | self._create_body(ordered_chapters, metadata), |
| | "\\end{document}" |
| | ] |
| | |
| | return "\n\n".join(document_parts) |
| | |
| | def _reorder_chapters(self, chapters: List[Chapter]) -> List[Chapter]: |
| | """Reorder chapters for better document structure""" |
| | reordered = [] |
| | notation_chapter = next((ch for ch in chapters if ch.path == ["Notation and Explanations"]), None) |
| | |
| | for chapter in chapters: |
| | if chapter.path != ["Notation and Explanations"]: |
| | reordered.append(chapter) |
| | |
| | if notation_chapter and chapter.path == ["Explanation of Assumptions"]: |
| | reordered.append(notation_chapter) |
| | |
| | return reordered |
| | |
| | def _add_figure(self, figures: List[str]) -> str: |
| | """Add a figure to the content""" |
| | figure_str = [] |
| | for i, figure_path in enumerate(figures): |
| | name = figure_path.split('/')[-1].split('.')[0].replace('_', '\\_') |
| | figure_str.append(f""" |
| | \\begin{{figure}}[H] |
| | \\centering |
| | \\includegraphics[width=0.5\\textwidth]{{{figure_path}}} |
| | \\caption{{{name}}} |
| | \\end{{figure}} |
| | """) |
| | return figure_str |
| |
|
| |
|
| | def _add_code(self, codes: List[str]) -> str: |
| | """ |
| | \subsection*{Python Code} |
| | \subsubsection*{main1.py} |
| | |
| | \begin{lstlisting}[language=Python, frame=single, basicstyle=\ttfamily\small] |
| | def main1(): |
| | pass |
| | \end{lstlisting} |
| | """ |
| | code_str = [ |
| | "\\clearpage", |
| | "\\section{Appendix}", |
| | ] |
| | for i, code_path in enumerate(codes): |
| | with open(code_path, 'r') as f: |
| | code = f.read() |
| | name = code_path.split('/')[-1].replace('_', '\\_') |
| | code_str.append(f""" |
| | \\subsubsection*{{{name}}} |
| | |
| | \\begin{{lstlisting}}[language=Python, frame=single, basicstyle=\\ttfamily\\small] |
| | {code} |
| | \\end{{lstlisting}} |
| | """) |
| | return code_str |
| |
|
| | def _create_preamble(self, metadata: Dict[str, Any]) -> str: |
| | """Create LaTeX preamble with document setup""" |
| | title = metadata.get("title", "paper_title") |
| | team = metadata.get("team", "team") |
| | year = metadata.get("year", "2024") |
| | problem_type = metadata.get("problem_type", "problem_type") |
| | |
| | return f"""\\documentclass{{mcmthesis}} |
| | \\mcmsetup{{CTeX = false, |
| | tcn = {team}, problem = {problem_type}, |
| | year = {year}, |
| | sheet = true, titleinsheet = true, keywordsinsheet = true, |
| | titlepage = false, abstract = true}} |
| | |
| | \\usepackage{{palatino}} |
| | \\usepackage{{algorithm}} |
| | \\usepackage{{algpseudocode}} |
| | \\usepackage{{tocloft}} |
| | \\usepackage{{amsmath}} |
| | |
| | \\usepackage{{lastpage}} |
| | \\renewcommand{{\\cftdot}}{{.}} |
| | \\renewcommand{{\\cftsecleader}}{{\\cftdotfill{{\\cftdotsep}}}} |
| | \\renewcommand{{\\cftsubsecleader}}{{\\cftdotfill{{\\cftdotsep}}}} |
| | \\renewcommand{{\\cftsubsubsecleader}}{{\\cftdotfill{{\\cftdotsep}}}} |
| | \\renewcommand{{\\headset}}{{{year}\\\\MCM/ICM\\\\Summary Sheet}} |
| | \\title{{{title}}} |
| | |
| | \\begin{{document}}""" |
| | |
| | def _create_abstract(self, metadata: Dict[str, str]) -> str: |
| | """Create the abstract section""" |
| | return f"""\\begin{{abstract}} |
| | {metadata.get('summary', '')} |
| | |
| | \\begin{{keywords}} |
| | {metadata.get('keywords', '')} |
| | \\end{{keywords}} |
| | \\end{{abstract}}""" |
| | |
| | def _create_body(self, chapters: List[Chapter], metadata: Dict[str, Any]) -> str: |
| | """Create the main body of the document from chapters""" |
| | body_parts = [] |
| | current_path = [] |
| | |
| | for chapter in chapters: |
| | |
| | if chapter.path == ["Model Conclusion", "Model Advantages"] and metadata.get('figures', []): |
| | body_parts += self._add_figure(metadata['figures']) |
| |
|
| | for i, section in enumerate(chapter.path): |
| | |
| | if i >= len(current_path) or section != current_path[i]: |
| | |
| | if len(current_path) <= i: |
| | current_path.append(section) |
| | else: |
| | current_path[i] = section |
| | current_path = current_path[:i+1] |
| | |
| | |
| | title = chapter.display_title if i == chapter.depth - 1 else section |
| | |
| | |
| | if i == 0: |
| | body_parts.append(f"\\section{{{title}}}") |
| | elif i == 1: |
| | body_parts.append(f"\\subsection{{{title}}}") |
| | elif i == 2: |
| | body_parts.append(f"\\subsubsection{{{title}}}") |
| | |
| | |
| | if chapter.is_generated and chapter.content: |
| | body_parts.append(chapter.content) |
| |
|
| | body_parts.append("\\section{References}") |
| | body_parts += self._add_code(metadata['codes']) |
| | return "\n\n".join(body_parts) |
| |
|
| | |
| | |
| | |
| |
|
| | class FileManager: |
| | """Handles file operations for saving papers and generating PDFs""" |
| | |
| | @staticmethod |
| | def save_to_file(content: str, filepath: str) -> None: |
| | """Save content to a file""" |
| | os.makedirs(os.path.dirname(filepath), exist_ok=True) |
| | with open(filepath, 'w') as f: |
| | f.write(content) |
| | print(f"Document saved to {filepath}") |
| | |
| | @staticmethod |
| | def generate_pdf(latex_path: str) -> None: |
| | """Generate a PDF from a LaTeX file""" |
| | print(f"Generating PDF from {latex_path}...") |
| | |
| | |
| | latex_dir = os.path.dirname(latex_path) |
| | subprocess.run(["pdflatex", f"-output-directory={latex_dir}", "-interaction=nonstopmode", latex_path]) |
| | subprocess.run(["pdflatex", f"-output-directory={latex_dir}", "-interaction=nonstopmode", latex_path]) |
| | |
| | |
| | FileManager._clean_temp_files(latex_path) |
| | |
| | pdf_path = latex_path.replace('.tex', '.pdf') |
| | print(f"PDF generated at {pdf_path}") |
| | |
| | @staticmethod |
| | def _clean_temp_files(latex_path: str) -> None: |
| | """Clean up temporary files created during PDF generation""" |
| | for ext in ["aux", "log", "toc", "out"]: |
| | aux_file = latex_path.replace('.tex', f'.{ext}') |
| | if os.path.exists(aux_file): |
| | os.remove(aux_file) |
| |
|
| | |
| | |
| | |
| |
|
| | class PaperGenerator: |
| | """Main class that orchestrates the paper generation process""" |
| | |
| | def __init__(self, llm): |
| | self.content_generator = ContentGenerator(llm) |
| | self.outline_generator = OutlineGenerator() |
| | self.context_extractor = ContextExtractor() |
| | self.prompt_creator = PromptCreator() |
| | self.document_assembler = LatexDocumentAssembler() |
| | self.file_manager = FileManager() |
| | self.llm = llm |
| | |
| | def generate_paper(self, |
| | json_data: Dict[str, Any], |
| | metadata: Dict[str, Any], |
| | output_dir: str, |
| | filename: str) -> None: |
| | """Generate a complete academic paper from JSON data""" |
| | |
| | task_count = len(json_data.get("tasks", [])) |
| | print(f"Starting paper generation with {task_count} tasks") |
| | chapters = self.outline_generator.create_outline(task_count) |
| | |
| | |
| | chapter_relevance_map = self.outline_generator.generate_chapter_relevance_map(task_count) |
| | |
| | |
| | completed_chapters = [] |
| | for chapter in chapters: |
| | if chapter.needs_content: |
| | self._generate_chapter_content(chapter, json_data, completed_chapters, chapter_relevance_map) |
| | completed_chapters.append(chapter) |
| | |
| | |
| | complete_metadata = self._complete_metadata(chapters, metadata) |
| | |
| | |
| | document = self.document_assembler.create_document(chapters, complete_metadata) |
| | |
| | |
| | latex_path = f"{output_dir}/{filename}.tex" |
| | self.file_manager.save_to_file(document, latex_path) |
| | self.file_manager.generate_pdf(latex_path) |
| | |
| | def _generate_chapter_content(self, |
| | chapter: Chapter, |
| | json_data: Dict[str, Any], |
| | completed_chapters: List[Chapter], |
| | chapter_relevance_map: Dict[str, List[str]]) -> None: |
| | """Generate content for a single chapter""" |
| | print(f"Generating content for: {chapter.path_string}") |
| | |
| | |
| | context = self.context_extractor.get_context_for_chapter(chapter, json_data) |
| | |
| | |
| | relevant_chapters = self._get_relevant_chapters(chapter, completed_chapters, chapter_relevance_map) |
| | |
| | |
| | prompt = self.prompt_creator.create_prompt( |
| | chapter, context, relevant_chapters |
| | ) |
| | |
| | response = self.content_generator.generate_chapter_content(prompt) |
| | |
| | |
| | |
| | |
| | chapter.content = response |
| | chapter.title = '' |
| | chapter.is_generated = True |
| | |
| | def _get_relevant_chapters(self, |
| | chapter: Chapter, |
| | completed_chapters: List[Chapter], |
| | chapter_relevance_map: Dict[str, List[str]]) -> List[Chapter]: |
| | """Filter completed chapters to only include those relevant to the current chapter""" |
| | |
| | current_path = chapter.path_string |
| | |
| | |
| | if current_path in chapter_relevance_map: |
| | relevant_paths = chapter_relevance_map[current_path] |
| | |
| | return [ch for ch in completed_chapters |
| | if ch.path_string in relevant_paths] |
| | |
| | |
| | return completed_chapters |
| |
|
| | def _format_title(self, chapter: Chapter, generated_title: str) -> str: |
| | """Format title based on chapter type""" |
| | |
| | if (chapter.path[0] == "Problem Analysis" or |
| | chapter.path[0] == "Solution to the Problem"): |
| | return generated_title |
| | return '' |
| | |
| | def _complete_metadata(self, |
| | chapters: List[Chapter], |
| | provided_metadata: Dict[str, Any]) -> Dict[str, Any]: |
| | """Complete paper metadata, generating missing fields if needed""" |
| | |
| | if not all(key in provided_metadata for key in |
| | ["title", "summary", "keywords"]): |
| | print("Generating missing paper metadata...") |
| | |
| | |
| | chapters_text = "\n\n".join( |
| | f"Chapter: {ch.path_string}\n{ch.content}" |
| | for ch in chapters if ch.is_generated |
| | ) |
| | |
| | prompt = PAPER_INFO_PROMPT.format(paper_chapters=chapters_text) |
| | |
| | |
| | max_retries = 3 |
| | generated_metadata = {} |
| | |
| | for attempt in range(max_retries): |
| | try: |
| | metadata_response = self.llm.generate(prompt) |
| | generated_metadata = parse_llm_output_to_json(metadata_response) |
| | if not generated_metadata: |
| | raise Exception("No metadata generated") |
| | break |
| | except Exception as e: |
| | print(f"Attempt {attempt+1} failed: {str(e)}") |
| | if attempt == max_retries - 1: |
| | print("All attempts to generate metadata failed") |
| | |
| | return {**generated_metadata, **provided_metadata} |
| | |
| | return provided_metadata |
| |
|
| | |
| | |
| | |
| |
|
| | def generate_paper_from_json(llm, json_data: dict, info: dict, output_dir: str, output_name: str) -> None: |
| | """Generate a paper from JSON data""" |
| | if not os.path.exists(output_dir): |
| | os.makedirs(output_dir) |
| | generator = PaperGenerator(llm) |
| | generator.generate_paper(json_data, info, output_dir, output_name) |
| |
|
| |
|
| |
|
| | if __name__ == "__main__": |
| | |
| | metadata = { |
| | "team": "Agent", |
| | "year": "2024", |
| | "problem_type": "C" |
| | } |
| | project_dir = "/Users/ann/Downloads/2024_C_2_20250307-144537" |
| | json_file_path = f"{project_dir}/json/2024_C_2.json" |
| | code_dir = f'{project_dir}/code' |
| | metadata['figures'] = [os.path.join(code_dir, f) for f in os.listdir(code_dir) if f.lower().split('.')[-1] in ['png', 'jpg', 'jpeg']] |
| | metadata['codes'] = sorted([os.path.join(code_dir, f) for f in os.listdir(code_dir) if f.lower().split('.')[-1] in ['py']]) |
| | with open(json_file_path, 'r') as f: |
| | json_data = json.loads(f.read()) |
| | json_data['tasks'] = json_data['tasks'][:] |
| | |
| | llm = LLM(model_name='gpt-4o') |
| | |
| | generate_paper_from_json(llm, json_data, metadata, f"{project_dir}/latex", 'solution') |
| |
|
| |
|