#!/usr/bin/env python3 """ Enhanced PDF report generation for biomni conversation histories. This script provides additional customization options for biomni reports: - Custom styling and branding - Formatted code blocks - Section organization - Metadata inclusion - Export format options (PDF, HTML, Markdown) Usage: python generate_report.py --input conversation.json --output report.pdf python generate_report.py --agent-object agent --output report.pdf --format html """ import argparse import json from pathlib import Path from typing import Dict, List, Optional, Any from datetime import datetime def format_conversation_history( messages: List[Dict[str, Any]], include_metadata: bool = True, include_code: bool = True, include_timestamps: bool = False ) -> str: """ Format conversation history into structured markdown. Args: messages: List of conversation message dictionaries include_metadata: Include metadata section include_code: Include code blocks include_timestamps: Include message timestamps Returns: Formatted markdown string """ sections = [] # Header sections.append("# Biomni Analysis Report\n") # Metadata if include_metadata: sections.append("## Metadata\n") sections.append(f"- **Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") sections.append(f"- **Number of interactions**: {len(messages)}") sections.append("\n---\n") # Process messages sections.append("## Analysis\n") for i, msg in enumerate(messages, 1): role = msg.get('role', 'unknown') content = msg.get('content', '') if role == 'user': sections.append(f"### Task {i // 2 + 1}\n") sections.append(f"**Query:**\n```\n{content}\n```\n") elif role == 'assistant': sections.append(f"**Response:**\n") # Check if content contains code if include_code and ('```' in content or 'import ' in content): # Attempt to separate text and code parts = content.split('```') for j, part in enumerate(parts): if j % 2 == 0: # Text content if part.strip(): sections.append(f"{part.strip()}\n") else: # Code content # Check if language is specified lines = part.split('\n', 1) if len(lines) > 1 and lines[0].strip() in ['python', 'r', 'bash', 'sql']: lang = lines[0].strip() code = lines[1] else: lang = 'python' # Default to python code = part sections.append(f"```{lang}\n{code}\n```\n") else: sections.append(f"{content}\n") sections.append("\n---\n") return '\n'.join(sections) def markdown_to_html(markdown_content: str, title: str = "Biomni Report") -> str: """ Convert markdown to styled HTML. Args: markdown_content: Markdown string title: HTML page title Returns: HTML string """ # Simple markdown to HTML conversion # For production use, consider using a library like markdown or mistune html_template = f""" {title}
{markdown_to_html_simple(markdown_content)}
""" return html_template def markdown_to_html_simple(md: str) -> str: """Simple markdown to HTML converter (basic implementation).""" lines = md.split('\n') html_lines = [] in_code_block = False in_list = False for line in lines: # Code blocks if line.startswith('```'): if in_code_block: html_lines.append('') in_code_block = False else: lang = line[3:].strip() html_lines.append(f'
')
                in_code_block = True
            continue

        if in_code_block:
            html_lines.append(line)
            continue

        # Headers
        if line.startswith('# '):
            html_lines.append(f'

{line[2:]}

') elif line.startswith('## '): html_lines.append(f'

{line[3:]}

') elif line.startswith('### '): html_lines.append(f'

{line[4:]}

') # Lists elif line.startswith('- '): if not in_list: html_lines.append('') in_list = False # Horizontal rule if line.strip() == '---': html_lines.append('
') # Bold elif '**' in line: line = line.replace('**', '', 1).replace('**', '', 1) html_lines.append(f'

{line}

') # Regular paragraph elif line.strip(): html_lines.append(f'

{line}

') else: html_lines.append('
') if in_list: html_lines.append('') return '\n'.join(html_lines) def generate_report( conversation_data: Dict[str, Any], output_path: Path, format: str = 'markdown', title: Optional[str] = None ): """ Generate formatted report from conversation data. Args: conversation_data: Conversation history dictionary output_path: Output file path format: Output format ('markdown', 'html', or 'pdf') title: Report title """ messages = conversation_data.get('messages', []) if not title: title = f"Biomni Analysis - {datetime.now().strftime('%Y-%m-%d')}" # Generate markdown markdown_content = format_conversation_history(messages) if format == 'markdown': output_path.write_text(markdown_content) print(f"✓ Markdown report saved to {output_path}") elif format == 'html': html_content = markdown_to_html(markdown_content, title) output_path.write_text(html_content) print(f"✓ HTML report saved to {output_path}") elif format == 'pdf': # For PDF generation, we'd typically use a library like weasyprint or reportlab # This is a placeholder implementation print("PDF generation requires additional dependencies (weasyprint or reportlab)") print("Falling back to HTML format...") html_path = output_path.with_suffix('.html') html_content = markdown_to_html(markdown_content, title) html_path.write_text(html_content) print(f"✓ HTML report saved to {html_path}") print(" To convert to PDF:") print(f" 1. Install weasyprint: pip install weasyprint") print(f" 2. Run: weasyprint {html_path} {output_path}") else: raise ValueError(f"Unsupported format: {format}") def main(): """Main entry point for CLI usage.""" parser = argparse.ArgumentParser( description="Generate enhanced reports from biomni conversation histories" ) parser.add_argument( '--input', type=Path, required=True, help='Input conversation history JSON file' ) parser.add_argument( '--output', type=Path, required=True, help='Output report file path' ) parser.add_argument( '--format', choices=['markdown', 'html', 'pdf'], default='markdown', help='Output format (default: markdown)' ) parser.add_argument( '--title', type=str, help='Report title (optional)' ) args = parser.parse_args() # Load conversation data try: with open(args.input, 'r') as f: conversation_data = json.load(f) except FileNotFoundError: print(f"❌ Input file not found: {args.input}") return 1 except json.JSONDecodeError: print(f"❌ Invalid JSON in input file: {args.input}") return 1 # Generate report try: generate_report( conversation_data, args.output, format=args.format, title=args.title ) return 0 except Exception as e: print(f"❌ Error generating report: {e}") return 1 if __name__ == '__main__': import sys sys.exit(main())