deepagents-printshop 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. agents/content_editor/__init__.py +1 -0
  2. agents/content_editor/agent.py +279 -0
  3. agents/content_editor/content_reviewer.py +327 -0
  4. agents/content_editor/versioned_agent.py +455 -0
  5. agents/latex_specialist/__init__.py +1 -0
  6. agents/latex_specialist/agent.py +531 -0
  7. agents/latex_specialist/latex_analyzer.py +510 -0
  8. agents/latex_specialist/latex_optimizer.py +1192 -0
  9. agents/qa_orchestrator/__init__.py +1 -0
  10. agents/qa_orchestrator/agent.py +603 -0
  11. agents/qa_orchestrator/langgraph_workflow.py +733 -0
  12. agents/qa_orchestrator/pipeline_types.py +72 -0
  13. agents/qa_orchestrator/quality_gates.py +495 -0
  14. agents/qa_orchestrator/workflow_coordinator.py +139 -0
  15. agents/research_agent/__init__.py +1 -0
  16. agents/research_agent/agent.py +258 -0
  17. agents/research_agent/llm_report_generator.py +1023 -0
  18. agents/research_agent/report_generator.py +536 -0
  19. agents/visual_qa/__init__.py +1 -0
  20. agents/visual_qa/agent.py +410 -0
  21. deepagents_printshop-0.1.0.dist-info/METADATA +744 -0
  22. deepagents_printshop-0.1.0.dist-info/RECORD +37 -0
  23. deepagents_printshop-0.1.0.dist-info/WHEEL +4 -0
  24. deepagents_printshop-0.1.0.dist-info/entry_points.txt +2 -0
  25. deepagents_printshop-0.1.0.dist-info/licenses/LICENSE +86 -0
  26. tools/__init__.py +1 -0
  27. tools/change_tracker.py +419 -0
  28. tools/content_type_loader.py +171 -0
  29. tools/graph_generator.py +281 -0
  30. tools/latex_generator.py +374 -0
  31. tools/llm_latex_generator.py +678 -0
  32. tools/magazine_layout.py +462 -0
  33. tools/pattern_injector.py +250 -0
  34. tools/pattern_learner.py +477 -0
  35. tools/pdf_compiler.py +386 -0
  36. tools/version_manager.py +346 -0
  37. tools/visual_qa.py +799 -0
@@ -0,0 +1,536 @@
1
+ """
2
+ Research Report Generator using LaTeX.
3
+
4
+ This module demonstrates the LaTeX report generation capabilities
5
+ for the DeepAgents PrintShop research agent.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import Dict
12
+
13
+ # Add tools to path
14
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
15
+
16
+ from tools.latex_generator import (
17
+ LaTeXGenerator, DocumentConfig, markdown_to_latex
18
+ )
19
+ from tools.pdf_compiler import PDFCompiler
20
+ from tools.content_type_loader import ContentTypeLoader
21
+
22
+
23
+ class ResearchReportGenerator:
24
+ """Generate comprehensive LaTeX research reports."""
25
+
26
+ def __init__(self, output_dir: str = "artifacts/output"):
27
+ """
28
+ Initialize the report generator.
29
+
30
+ Args:
31
+ output_dir: Directory to save generated files
32
+ """
33
+ self.output_dir = Path(output_dir)
34
+ self.output_dir.mkdir(parents=True, exist_ok=True)
35
+ self.artifacts_dir = Path("artifacts")
36
+ self.content_dir = self.artifacts_dir / "sample_content"
37
+ self.data_dir = self.content_dir / "data"
38
+ self.images_dir = self.content_dir / "images"
39
+
40
+ def load_markdown_content(self, filename: str) -> str:
41
+ """Load markdown content from the sample_content directory."""
42
+ file_path = self.content_dir / filename
43
+ if file_path.exists():
44
+ with open(file_path, 'r', encoding='utf-8') as f:
45
+ return f.read()
46
+ return ""
47
+
48
+ def load_config_from_markdown(self) -> Dict:
49
+ """Load document configuration from config.md file.
50
+
51
+ Uses ContentTypeLoader to resolve the content type and extract
52
+ document class, font size, and paper size from the type definition.
53
+ Parses remaining config sections (metadata, manifest, options) from config.md.
54
+ """
55
+ config_md = self.load_markdown_content("config.md")
56
+ config = {}
57
+
58
+ if not config_md:
59
+ return config
60
+
61
+ lines = config_md.split('\n')
62
+ current_section = None
63
+ content_lines = []
64
+
65
+ for line in lines:
66
+ if line.startswith('## '):
67
+ if current_section and content_lines:
68
+ config[current_section] = self._parse_config_section_simple(current_section, content_lines)
69
+ current_section = line.replace('## ', '').strip().lower()
70
+ content_lines = []
71
+ elif line.strip() and not line.startswith('#'):
72
+ content_lines.append(line)
73
+
74
+ if current_section and content_lines:
75
+ config[current_section] = self._parse_config_section_simple(current_section, content_lines)
76
+
77
+ # Load content type definition
78
+ type_id = config.get('content type', 'research_report')
79
+ if isinstance(type_id, str):
80
+ type_id = type_id.strip()
81
+
82
+ loader = ContentTypeLoader()
83
+ content_type = loader.load_type(type_id)
84
+
85
+ # Inject type defaults into config
86
+ config['document class'] = content_type.document_class
87
+ config['_content_type'] = content_type
88
+ config['_type_font_size'] = content_type.default_font_size
89
+ config['_type_paper_size'] = content_type.default_paper_size
90
+
91
+ # Parse project metadata into top-level fields
92
+ project_meta = config.get('project metadata', '')
93
+ if isinstance(project_meta, str):
94
+ for line in project_meta.split('\n'):
95
+ line = line.strip()
96
+ if line.startswith('- ') and ':' in line:
97
+ key, value = line[2:].split(':', 1)
98
+ key = key.strip().strip('*').lower()
99
+ value = value.strip()
100
+ if key == 'title':
101
+ config['title'] = value
102
+ elif key == 'authors':
103
+ config['authors'] = [a.strip() for a in value.split(',')]
104
+
105
+ return config
106
+
107
+ def _parse_config_section_simple(self, section_name: str, content_lines: list):
108
+ """Parse configuration sections from config.md."""
109
+ if section_name in ['document options', 'headers and footers']:
110
+ result = {}
111
+ for line in content_lines:
112
+ if line.startswith('- ') and ':' in line:
113
+ key_value = line[2:].split(':', 1)
114
+ if len(key_value) == 2:
115
+ key = key_value[0].strip()
116
+ value = key_value[1].strip()
117
+ if value.lower() in ['true', 'false']:
118
+ value = value.lower() == 'true'
119
+ result[key] = value
120
+ return result
121
+ elif section_name == 'content manifest':
122
+ structure = []
123
+ for line in content_lines:
124
+ if line.strip() and line[0].isdigit():
125
+ parts = line.split('.', 1)
126
+ if len(parts) == 2:
127
+ section_def = parts[1].strip()
128
+ if ':' in section_def:
129
+ title, source = section_def.split(':', 1)
130
+ structure.append({
131
+ 'title': title.strip(),
132
+ 'source': source.strip(),
133
+ 'type': 'markdown' if source.strip().endswith('.md') else 'auto'
134
+ })
135
+ else:
136
+ structure.append({
137
+ 'title': section_def,
138
+ 'source': None,
139
+ 'type': 'auto'
140
+ })
141
+ return structure
142
+ else:
143
+ content = '\n'.join(content_lines).strip()
144
+ return content
145
+
146
+ def _process_markdown_with_csv(self, markdown_content: str) -> str:
147
+ """Process markdown content with CSV table support using LaTeX optimizer."""
148
+ from agents.latex_specialist.latex_optimizer import LaTeXOptimizer
149
+
150
+ optimizer = LaTeXOptimizer()
151
+ # Use the LaTeX optimizer's enhanced markdown processing
152
+ return optimizer._markdown_to_latex_content(markdown_content)
153
+
154
+ def generate_document_from_structure(self, gen, structure: list, config_data: dict):
155
+ """Generate document sections based on configurable structure."""
156
+ section_config = config_data.get('section configuration', {})
157
+ main_level = int(section_config.get('main_section_level', 1))
158
+ sub_level = int(section_config.get('subsection_level', 2))
159
+
160
+ for section in structure:
161
+ title = section['title']
162
+ source = section.get('source')
163
+ section_type = section.get('type', 'auto')
164
+
165
+ if title.lower() == 'abstract':
166
+ # Handle abstract specially
167
+ abstract_content = config_data.get('abstract', 'Abstract content not found.')
168
+ gen.add_section("Abstract", abstract_content.strip(), level=main_level)
169
+
170
+ elif section_type == 'markdown' and source:
171
+ # Load and process markdown file
172
+ markdown_content = self.load_markdown_content(source)
173
+ if markdown_content:
174
+ if source in ['performance_table.md', 'research_areas.md', 'detailed_results.md']:
175
+ # Files that should be processed as raw markdown with CSV support
176
+ processed_content = self._process_markdown_with_csv(markdown_content)
177
+ gen.add_raw_latex(processed_content)
178
+ else:
179
+ # Files that should be processed with section headers
180
+ self.process_markdown_with_sections(gen, markdown_content, title, main_level, sub_level)
181
+ else:
182
+ gen.add_section(title, f"Content not found: {source}", level=main_level)
183
+
184
+ elif section_type == 'auto':
185
+ # Auto-generated sections (like Visualizations)
186
+ if title.lower() == 'visualizations':
187
+ self.generate_visualizations_section(gen, main_level)
188
+ else:
189
+ gen.add_section(title, "Auto-generated content placeholder", level=main_level)
190
+
191
+ def process_markdown_with_sections(self, gen, markdown: str, main_title: str, main_level: int, sub_level: int):
192
+ """Process markdown content with section handling."""
193
+ lines = markdown.split('\n')
194
+ current_section = []
195
+ section_title = None
196
+
197
+ # Add main section
198
+ gen.add_section(main_title, "", level=main_level)
199
+
200
+ for line in lines:
201
+ if line.startswith('# '):
202
+ # Skip main title (already added)
203
+ continue
204
+ elif line.startswith('## '):
205
+ # Save previous subsection if exists
206
+ if section_title and current_section:
207
+ content = '\n'.join(current_section).strip()
208
+ gen.add_section(section_title, content, level=sub_level)
209
+ # Start new subsection
210
+ section_title = line.replace('## ', '').strip()
211
+ current_section = []
212
+ elif line.startswith('### '):
213
+ # Save previous subsection if exists
214
+ if section_title and current_section:
215
+ content = '\n'.join(current_section).strip()
216
+ gen.add_section(section_title, content, level=sub_level)
217
+ # Start new subsection
218
+ section_title = line.replace('### ', '').strip()
219
+ current_section = []
220
+ else:
221
+ current_section.append(line)
222
+
223
+ # Add last subsection
224
+ if section_title and current_section:
225
+ content = '\n'.join(current_section).strip()
226
+ gen.add_section(section_title, content, level=sub_level)
227
+
228
+ def generate_visualizations_section(self, gen, level: int):
229
+ """Generate the visualizations section."""
230
+ gen.add_section("Visualizations", "", level=level)
231
+
232
+ tikz_code = """
233
+ % Simple neural network diagram
234
+ \\node[circle, draw, minimum size=1cm] (input) at (0,0) {Input};
235
+ \\node[circle, draw, minimum size=1cm] (hidden1) at (3,1) {H1};
236
+ \\node[circle, draw, minimum size=1cm] (hidden2) at (3,-1) {H2};
237
+ \\node[circle, draw, minimum size=1cm] (output) at (6,0) {Output};
238
+
239
+ \\draw[->] (input) -- (hidden1);
240
+ \\draw[->] (input) -- (hidden2);
241
+ \\draw[->] (hidden1) -- (output);
242
+ \\draw[->] (hidden2) -- (output);
243
+ """
244
+ gen.add_raw_latex(f"""
245
+ \\begin{{figure}}[htbp]
246
+ \\centering
247
+ \\begin{{tikzpicture}}
248
+ {tikz_code}
249
+ \\end{{tikzpicture}}
250
+ \\caption{{Neural Network Architecture}}
251
+ \\label{{fig:neural_net}}
252
+ \\end{{figure}}
253
+ """)
254
+
255
+ gen.add_raw_latex("""
256
+ The neural network architecture is shown in Figure~\\ref{fig:neural_net}.
257
+ In a complete report, you would include figures using commands like:
258
+
259
+ \\begin{verbatim}
260
+ \\includegraphics[width=0.8\\textwidth]{artifacts/images/results_graph.jpg}
261
+ \\end{verbatim}
262
+
263
+ For wrapped figures with text flow, use the wrapfig environment.
264
+ """)
265
+
266
+ def generate_sample_report(self) -> str:
267
+ """
268
+ Generate a comprehensive sample research report demonstrating all features.
269
+
270
+ Returns:
271
+ Path to the generated .tex file
272
+ """
273
+ # Load configuration from markdown
274
+ config_data = self.load_config_from_markdown()
275
+
276
+ # Get document options and type defaults
277
+ doc_options = config_data.get('document options', {})
278
+ headers_footers = config_data.get('headers and footers', {})
279
+ type_font_size = config_data.get('_type_font_size', '12pt')
280
+ type_paper_size = config_data.get('_type_paper_size', 'letterpaper')
281
+
282
+ # Configure the document (type defaults, overridden by config options)
283
+ config = DocumentConfig(
284
+ doc_class=config_data.get('document class', 'article'),
285
+ font_size=doc_options.get('font_size', type_font_size),
286
+ paper_size=doc_options.get('paper_size', type_paper_size),
287
+ title=config_data.get('title', 'Research Report'),
288
+ author=config_data.get('authors', ['Anonymous'])[0] if isinstance(config_data.get('authors'), list) else config_data.get('authors', 'Anonymous'),
289
+ date=r"\today",
290
+ include_toc=doc_options.get('include_toc', True),
291
+ include_bibliography=doc_options.get('include_bibliography', True),
292
+ two_column=doc_options.get('two_column', False),
293
+ header_left=headers_footers.get('header_left', 'Research Report'),
294
+ header_right=headers_footers.get('header_right', r'\today'),
295
+ footer_center=headers_footers.get('footer_center', r'\thepage')
296
+ )
297
+
298
+ # Create the generator
299
+ gen = LaTeXGenerator(config)
300
+
301
+ # Get document structure from config
302
+ document_structure = config_data.get('content manifest', [])
303
+
304
+ if document_structure:
305
+ # Use configurable document structure
306
+ self.generate_document_from_structure(gen, document_structure, config_data)
307
+ else:
308
+ # Fallback to default structure if no config found
309
+ gen.add_section("Abstract", config_data.get('abstract', 'No abstract provided'), level=1)
310
+
311
+ # Note: CSV tables are now handled via inline markdown references
312
+
313
+ # Add citations in bibliography
314
+ self.add_bibliography_entries(gen)
315
+
316
+ # Add CSV-based table
317
+ csv_file = self.data_dir / "model_performance.csv"
318
+ if csv_file.exists():
319
+ gen.add_raw_latex("""
320
+
321
+ \\subsection{Detailed Performance Metrics}
322
+
323
+ The complete performance data is presented below:
324
+
325
+ """)
326
+ # For CSV tables, we'll read and create a table manually
327
+ # since csvsimple might need specific configuration
328
+ import csv
329
+ with open(csv_file, 'r') as f:
330
+ reader = csv.reader(f)
331
+ rows = list(reader)
332
+ if rows:
333
+ headers = rows[0]
334
+ data_rows = rows[1:]
335
+ gen.add_table(
336
+ caption="Complete Model Performance Data",
337
+ headers=headers,
338
+ rows=data_rows,
339
+ label="tab:complete_perf"
340
+ )
341
+
342
+ # Add training metrics table
343
+ csv_file2 = self.data_dir / "training_metrics.csv"
344
+ if csv_file2.exists():
345
+ import csv
346
+ with open(csv_file2, 'r') as f:
347
+ reader = csv.reader(f)
348
+ rows = list(reader)
349
+ if rows:
350
+ headers = rows[0]
351
+ # Only show first 5 rows to keep table concise
352
+ data_rows = rows[1:6]
353
+ gen.add_table(
354
+ caption="Training Progression (First 5 Epochs)",
355
+ headers=headers,
356
+ rows=data_rows,
357
+ label="tab:training"
358
+ )
359
+
360
+ # Add Results discussion
361
+ results_md = self.load_markdown_content("results.md")
362
+ if results_md:
363
+ # Process results markdown
364
+ results_lines = results_md.split('\n')
365
+ current_section = []
366
+ section_title = None
367
+
368
+ for line in results_lines:
369
+ if line.startswith('# '):
370
+ continue
371
+ elif line.startswith('## '):
372
+ if section_title and current_section:
373
+ content = '\n'.join(current_section).strip()
374
+ gen.add_section(section_title, content, level=2)
375
+ section_title = line.replace('## ', '').strip()
376
+ current_section = []
377
+ elif line.startswith('### '):
378
+ if section_title and current_section:
379
+ content = '\n'.join(current_section).strip()
380
+ gen.add_section(section_title, content, level=2)
381
+ section_title = line.replace('### ', '').strip()
382
+ current_section = []
383
+ else:
384
+ current_section.append(line)
385
+
386
+ if section_title and current_section:
387
+ content = '\n'.join(current_section).strip()
388
+ gen.add_section(section_title, content, level=2)
389
+
390
+ # Add a TikZ diagram (simple graph example)
391
+ gen.add_section("Visualizations", "", level=1)
392
+
393
+ tikz_code = """
394
+ % Simple neural network diagram
395
+ \\node[circle, draw, minimum size=1cm] (input) at (0,0) {Input};
396
+ \\node[circle, draw, minimum size=1cm] (hidden1) at (3,1) {H1};
397
+ \\node[circle, draw, minimum size=1cm] (hidden2) at (3,-1) {H2};
398
+ \\node[circle, draw, minimum size=1cm] (output) at (6,0) {Output};
399
+
400
+ \\draw[->] (input) -- (hidden1);
401
+ \\draw[->] (input) -- (hidden2);
402
+ \\draw[->] (hidden1) -- (output);
403
+ \\draw[->] (hidden2) -- (output);
404
+ """
405
+ gen.add_tikz_diagram(tikz_code, "Simple Neural Network Architecture", label="fig:nn")
406
+
407
+ # Add note about images (since we may not have actual image files)
408
+ gen.add_raw_latex("""
409
+
410
+ \\subsection{Image Placement Examples}
411
+
412
+ In a complete report, you would include figures using commands like:
413
+
414
+ \\begin{verbatim}
415
+ \\includegraphics[width=0.8\\textwidth]{artifacts/images/results_graph.jpg}
416
+ \\end{verbatim}
417
+
418
+ For wrapped figures with text flow, use the wrapfig environment.
419
+ """)
420
+
421
+ # Add citations in bibliography
422
+ gen.add_bib_entry(
423
+ "\\bibitem{vaswani2017attention}\n"
424
+ "Vaswani, A., et al. (2017). "
425
+ "Attention is all you need. "
426
+ "In Advances in neural information processing systems (pp. 5998-6008)."
427
+ )
428
+ gen.add_bib_entry(
429
+ "\\bibitem{devlin2018bert}\n"
430
+ "Devlin, J., et al. (2018). "
431
+ "BERT: Pre-training of deep bidirectional transformers for language understanding. "
432
+ "arXiv preprint arXiv:1810.04805."
433
+ )
434
+ gen.add_bib_entry(
435
+ "\\bibitem{brown2020gpt3}\n"
436
+ "Brown, T., et al. (2020). "
437
+ "Language models are few-shot learners. "
438
+ "Advances in neural information processing systems, 33, 1877-1901."
439
+ )
440
+
441
+ # Add conclusion from markdown
442
+ conclusion_md = self.load_markdown_content("conclusion.md")
443
+ if conclusion_md:
444
+ gen.add_raw_latex(markdown_to_latex(conclusion_md))
445
+
446
+ # Save the document
447
+ output_file = self.output_dir / "research_report.tex"
448
+ gen.save(str(output_file))
449
+
450
+ return str(output_file)
451
+
452
+ # Note: CSV table handling has been moved to inline markdown references
453
+ # Tables are now defined in markdown files with CSV_TABLE comments
454
+
455
+ def add_bibliography_entries(self, gen):
456
+ """Add bibliography entries to the document."""
457
+ gen.add_bib_entry(
458
+ "\\bibitem{vaswani2017attention}\n"
459
+ "Vaswani, A., et al. (2017). "
460
+ "Attention is all you need. "
461
+ "In Advances in neural information processing systems (pp. 5998-6008)."
462
+ )
463
+ gen.add_bib_entry(
464
+ "\\bibitem{devlin2018bert}\n"
465
+ "Devlin, J., et al. (2018). "
466
+ "BERT: Pre-training of deep bidirectional transformers for language understanding. "
467
+ "arXiv preprint arXiv:1810.04805."
468
+ )
469
+ gen.add_bib_entry(
470
+ "\\bibitem{brown2020gpt3}\n"
471
+ "Brown, T., et al. (2020). "
472
+ "Language models are few-shot learners. "
473
+ "Advances in neural information processing systems, 33, 1877-1901."
474
+ )
475
+
476
+ def compile_to_pdf(self, tex_file: str) -> bool:
477
+ """
478
+ Compile the LaTeX file to PDF.
479
+
480
+ Args:
481
+ tex_file: Path to the .tex file
482
+
483
+ Returns:
484
+ True if successful, False otherwise
485
+ """
486
+ compiler = PDFCompiler(output_dir=str(self.output_dir))
487
+
488
+ # Check LaTeX installation
489
+ is_installed, message = compiler.validate_latex_installation()
490
+ print(f"LaTeX Installation: {message}")
491
+
492
+ if not is_installed:
493
+ print("ERROR: LaTeX is not installed. Cannot compile PDF.")
494
+ return False
495
+
496
+ # Compile the document (2 runs for references)
497
+ success, message = compiler.compile(tex_file, runs=2)
498
+ print(f"\nCompilation result: {message}")
499
+
500
+ return success
501
+
502
+
503
+ def main():
504
+ """Main function to demonstrate report generation."""
505
+ print("=" * 60)
506
+ print("DeepAgents PrintShop - LaTeX Research Report Generator")
507
+ print("=" * 60)
508
+ print()
509
+
510
+ generator = ResearchReportGenerator()
511
+
512
+ print("Generating LaTeX report...")
513
+ tex_file = generator.generate_sample_report()
514
+ print(f"✓ LaTeX file created: {tex_file}")
515
+ print()
516
+
517
+ print("Compiling to PDF...")
518
+ success = generator.compile_to_pdf(tex_file)
519
+
520
+ if success:
521
+ print()
522
+ print("=" * 60)
523
+ print("✓ Report generation complete!")
524
+ print(f" LaTeX file: {tex_file}")
525
+ print(f" PDF file: {tex_file.replace('.tex', '.pdf')}")
526
+ print("=" * 60)
527
+ else:
528
+ print()
529
+ print("=" * 60)
530
+ print("⚠ LaTeX file created but PDF compilation failed.")
531
+ print(f" You can manually compile: pdflatex {tex_file}")
532
+ print("=" * 60)
533
+
534
+
535
+ if __name__ == "__main__":
536
+ main()
@@ -0,0 +1 @@
1
+ """Visual QA Agent - PDF quality analysis and validation."""