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,410 @@
1
+ """Dynamic Visual QA Agent that processes findings and applies improvements."""
2
+
3
+ import os
4
+ import json
5
+ import re
6
+ import shutil
7
+ from pathlib import Path
8
+ from typing import Dict, List, Tuple, Optional
9
+ from dataclasses import dataclass
10
+
11
+ import sys
12
+ import os
13
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
14
+
15
+ from tools.visual_qa import VisualQAAgent, DocumentVisualQA
16
+ from tools.latex_generator import LaTeXGenerator, DocumentConfig
17
+ from tools.llm_latex_generator import LLMLaTeXGenerator
18
+ from tools.pdf_compiler import PDFCompiler
19
+ from tools.version_manager import VersionManager
20
+ from tools.change_tracker import ChangeTracker
21
+
22
+
23
+ @dataclass
24
+ class ImprovementAction:
25
+ """Represents a specific improvement action to take."""
26
+ issue_type: str
27
+ description: str
28
+ latex_fix: str
29
+ priority: int # 1-10, higher is more important
30
+
31
+
32
+ class VisualQAFeedbackAgent:
33
+ """Agent that processes Visual QA results and applies dynamic improvements."""
34
+
35
+ def __init__(self, content_source: str = ""):
36
+ self.content_source = content_source
37
+ self.visual_qa = VisualQAAgent(content_source=content_source)
38
+ self.pdf_compiler = PDFCompiler()
39
+ self.llm_latex_generator = LLMLaTeXGenerator()
40
+ self.improvement_patterns = self._load_improvement_patterns()
41
+ self.version_manager = VersionManager()
42
+ self.change_tracker = ChangeTracker()
43
+
44
+ def _load_improvement_patterns(self) -> Dict[str, Dict]:
45
+ """Load patterns for mapping Visual QA issues to LaTeX improvements."""
46
+ return {
47
+ "line_spacing": {
48
+ "keywords": ["line spacing", "linespacing", "spacing between lines"],
49
+ "latex_fixes": [
50
+ "\\linespread{0.9}",
51
+ "\\linespread{1.1}",
52
+ "\\setlength{\\baselineskip}{1.2\\baselineskip}"
53
+ ]
54
+ },
55
+ "paragraph_spacing": {
56
+ "keywords": ["paragraph spacing", "parskip", "spacing between paragraphs"],
57
+ "latex_fixes": [
58
+ "\\setlength{\\parskip}{0.5em plus 0.1em minus 0.05em}",
59
+ "\\setlength{\\parskip}{1em plus 0.2em minus 0.1em}",
60
+ "\\setlength{\\parskip}{1.2em plus 0.3em minus 0.1em}"
61
+ ]
62
+ },
63
+ "section_spacing": {
64
+ "keywords": ["section spacing", "uneven spacing", "spacing between sections"],
65
+ "latex_fixes": [
66
+ "\\titlespacing\\section{0pt}{1.5em plus 0.2em minus 0.1em}{0.8em plus 0.1em minus 0.05em}",
67
+ "\\titlespacing\\subsection{0pt}{1.2em plus 0.15em minus 0.08em}{0.6em plus 0.08em minus 0.04em}"
68
+ ]
69
+ },
70
+ "typography": {
71
+ "keywords": ["font size", "typography", "readability", "text flow"],
72
+ "latex_fixes": [
73
+ "\\usepackage[11pt]{extsizes}",
74
+ "\\usepackage{microtype}",
75
+ "\\setlength{\\textwidth}{0.9\\textwidth}"
76
+ ]
77
+ },
78
+ "table_formatting": {
79
+ "keywords": ["table", "tabular", "formatting", "alignment"],
80
+ "latex_fixes": [
81
+ "\\renewcommand{\\arraystretch}{1.2}",
82
+ "\\setlength{\\tabcolsep}{6pt}",
83
+ "\\usepackage{longtabu}"
84
+ ]
85
+ },
86
+ "header_footer": {
87
+ "keywords": ["header", "footer", "page numbers", "headheight"],
88
+ "latex_fixes": [
89
+ "\\setlength{\\headheight}{14.5pt}",
90
+ "\\addtolength{\\topmargin}{-2.5pt}"
91
+ ]
92
+ }
93
+ }
94
+
95
+ def analyze_and_improve(self, pdf_path: str, max_iterations: int = 3) -> Tuple[str, List[str], Optional[str]]:
96
+ """
97
+ Analyze PDF with Visual QA and iteratively improve it.
98
+
99
+ Returns:
100
+ Tuple of (final_pdf_path, improvements_made, final_version_name)
101
+ """
102
+ current_pdf = pdf_path
103
+ improvements_made = []
104
+ final_version = None
105
+
106
+ for iteration in range(max_iterations):
107
+ print(f"\nšŸ”„ Visual QA Iteration {iteration + 1}/{max_iterations}")
108
+ print("=" * 50)
109
+
110
+ # Run Visual QA analysis
111
+ qa_results = self.visual_qa.validate_pdf_visual_quality(current_pdf)
112
+
113
+ print(f"šŸ“Š Current Score: {qa_results.overall_score:.1f}/100")
114
+
115
+ # Stop if score is good enough
116
+ if qa_results.overall_score >= 90:
117
+ print("āœ… Quality target achieved!")
118
+ break
119
+
120
+ # Extract improvement actions from QA results
121
+ actions = self._extract_improvement_actions(qa_results)
122
+
123
+ if not actions:
124
+ print("ā„¹ļø No more actionable improvements found")
125
+ break
126
+
127
+ print(f"šŸ”§ Found {len(actions)} potential improvements:")
128
+ for action in actions:
129
+ print(f" - {action.description} (Priority: {action.priority})")
130
+
131
+ # Apply improvements
132
+ tex_path = pdf_path.replace('.pdf', '.tex')
133
+ improved_tex = self._apply_improvements(tex_path, actions)
134
+
135
+ # Track version before compilation
136
+ version_name = f"v3_visual_qa_iter{iteration + 1}"
137
+ parent_version = "v2_latex_optimized" if iteration == 0 else f"v3_visual_qa_iter{iteration}"
138
+
139
+ # Read old and new LaTeX content for version tracking
140
+ with open(tex_path, 'r', encoding='utf-8') as f:
141
+ old_latex_content = f.read()
142
+ with open(improved_tex, 'r', encoding='utf-8') as f:
143
+ new_latex_content = f.read()
144
+
145
+ # Recompile PDF to iterations folder
146
+ iterations_dir = Path("artifacts/reviewed_content/v3_visual_qa/iterations")
147
+ iterations_dir.mkdir(parents=True, exist_ok=True)
148
+ new_pdf_path = str(iterations_dir / f"iteration_{iteration + 1}.pdf")
149
+
150
+ if self._compile_improved_tex(improved_tex, new_pdf_path):
151
+ # Save version to reviewed_content
152
+ version_dir = Path(f"artifacts/reviewed_content/{version_name}")
153
+ version_dir.mkdir(parents=True, exist_ok=True)
154
+
155
+ # Save improved .tex file to version directory
156
+ tex_filename = f"{self.content_source}.tex" if self.content_source else "research_report.tex"
157
+ pdf_filename = f"{self.content_source}.pdf" if self.content_source else "research_report.pdf"
158
+ version_tex_path = version_dir / tex_filename
159
+ with open(version_tex_path, 'w', encoding='utf-8') as f:
160
+ f.write(new_latex_content)
161
+
162
+ # Copy PDF to version directory
163
+ version_pdf_path = version_dir / pdf_filename
164
+ shutil.copy(new_pdf_path, version_pdf_path)
165
+
166
+ # Track version in version manager
167
+ content_dict = {tex_filename: new_latex_content}
168
+ self.version_manager.create_version(
169
+ content_dict=content_dict,
170
+ version_name=version_name,
171
+ agent_name="visual_qa_feedback",
172
+ parent_version=parent_version,
173
+ metadata={
174
+ "iteration": iteration + 1,
175
+ "improvements": [action.description for action in actions],
176
+ "qa_score": qa_results.overall_score
177
+ }
178
+ )
179
+
180
+ # Track changes in version history
181
+ old_content_dict = {tex_filename: old_latex_content}
182
+ new_content_dict = {tex_filename: new_latex_content}
183
+ self.change_tracker.create_change_report(
184
+ old_version=parent_version,
185
+ new_version=version_name,
186
+ old_content=old_content_dict,
187
+ new_content=new_content_dict
188
+ )
189
+
190
+ print(f"āœ… Version {version_name} tracked in version_history")
191
+ print(f"āœ… Generated improved PDF: {new_pdf_path}")
192
+
193
+ # Update current PDF for next iteration
194
+ current_pdf = new_pdf_path
195
+ improvements_made.extend([action.description for action in actions])
196
+ final_version = version_name # Track the final version created
197
+ else:
198
+ print("āŒ Compilation failed, reverting changes")
199
+ break
200
+
201
+ return current_pdf, improvements_made, final_version
202
+
203
+ def _extract_improvement_actions(self, qa_results: DocumentVisualQA) -> List[ImprovementAction]:
204
+ """Extract actionable improvements from Visual QA results."""
205
+ actions = []
206
+
207
+ # Analyze all page issues
208
+ all_issues = []
209
+ for page_result in qa_results.page_results:
210
+ all_issues.extend(page_result.issues_found)
211
+
212
+ # Map issues to improvement actions using AI analysis
213
+ for issue in all_issues:
214
+ action = self._map_issue_to_action(issue, qa_results.overall_score)
215
+ if action:
216
+ actions.append(action)
217
+
218
+ # Sort by priority
219
+ actions.sort(key=lambda x: x.priority, reverse=True)
220
+
221
+ return actions[:3] # Limit to top 3 actions per iteration
222
+
223
+ def _map_issue_to_action(self, issue: str, current_score: float) -> Optional[ImprovementAction]:
224
+ """Map a specific issue to an improvement action."""
225
+ issue_lower = issue.lower()
226
+
227
+ # Priority based on current score (lower score = higher priority fixes)
228
+ base_priority = max(1, 10 - int(current_score / 10))
229
+
230
+ for pattern_name, pattern_info in self.improvement_patterns.items():
231
+ for keyword in pattern_info["keywords"]:
232
+ if keyword in issue_lower:
233
+ # Select appropriate fix based on issue context
234
+ latex_fix = self._select_best_fix(issue, pattern_info["latex_fixes"])
235
+
236
+ return ImprovementAction(
237
+ issue_type=pattern_name,
238
+ description=f"Fix {pattern_name}: {issue}",
239
+ latex_fix=latex_fix,
240
+ priority=base_priority + self._calculate_issue_priority(issue)
241
+ )
242
+
243
+ return None
244
+
245
+ def _select_best_fix(self, issue: str, available_fixes: List[str]) -> str:
246
+ """Select the most appropriate fix for the specific issue."""
247
+ issue_lower = issue.lower()
248
+
249
+ # Simple heuristics for fix selection
250
+ if "reduce" in issue_lower or "decrease" in issue_lower:
251
+ return available_fixes[0] # Usually the "smaller" option
252
+ elif "increase" in issue_lower or "improve" in issue_lower:
253
+ return available_fixes[-1] # Usually the "larger" option
254
+ else:
255
+ return available_fixes[len(available_fixes) // 2] # Middle option
256
+
257
+ def _calculate_issue_priority(self, issue: str) -> int:
258
+ """Calculate additional priority based on issue severity."""
259
+ issue_lower = issue.lower()
260
+
261
+ # High priority keywords
262
+ if any(word in issue_lower for word in ["unreadable", "poor", "bad", "error"]):
263
+ return 3
264
+ elif any(word in issue_lower for word in ["improve", "enhance", "better"]):
265
+ return 2
266
+ elif any(word in issue_lower for word in ["slightly", "minor", "small"]):
267
+ return 1
268
+ else:
269
+ return 2
270
+
271
+ def _apply_improvements(self, tex_path: str, actions: List[ImprovementAction]) -> str:
272
+ """Apply improvement actions to LaTeX document using LLM reasoning."""
273
+ # Read current LaTeX content
274
+ with open(tex_path, 'r') as f:
275
+ content = f.read()
276
+
277
+ # Extract issue descriptions from actions
278
+ issues = [action.description for action in actions]
279
+
280
+ # Use LLM-based LaTeX generator to apply fixes intelligently
281
+ print(f"šŸ¤– Using LLM to apply {len(issues)} improvements...")
282
+ fixed_latex, success, fixes_applied = self.llm_latex_generator.apply_visual_qa_fixes(
283
+ content, issues
284
+ )
285
+
286
+ if not success:
287
+ print("āš ļø LLM fixes failed, falling back to manual approach")
288
+ # Fallback to simple approach
289
+ for action in actions:
290
+ fixed_latex = self._apply_latex_fix_simple(fixed_latex, action)
291
+
292
+ # Write improved version
293
+ improved_path = tex_path.replace('.tex', '_improved.tex')
294
+ with open(improved_path, 'w', encoding='utf-8') as f:
295
+ f.write(fixed_latex)
296
+
297
+ return improved_path
298
+
299
+ def _apply_latex_fix_simple(self, content: str, action: ImprovementAction) -> str:
300
+ """Apply a specific LaTeX fix to the content (simple fallback method)."""
301
+ # This is the old simple method, kept as fallback
302
+ # Insert fix in preamble before \begin{document}
303
+ begin_doc_pos = content.find('\\begin{document}')
304
+ if begin_doc_pos == -1:
305
+ return content
306
+
307
+ # Add improvement comment and fix
308
+ improvement_block = f"""
309
+ % Visual QA Improvement: {action.description}
310
+ {action.latex_fix}
311
+
312
+ """
313
+
314
+ # Insert before \begin{document}
315
+ improved_content = (
316
+ content[:begin_doc_pos] +
317
+ improvement_block +
318
+ content[begin_doc_pos:]
319
+ )
320
+
321
+ return improved_content
322
+
323
+ def _compile_improved_tex(self, tex_path: str, output_pdf: str, max_corrections: int = 3) -> bool:
324
+ """
325
+ Compile improved LaTeX to PDF with LLM self-correction on errors.
326
+
327
+ If compilation fails, uses LLM to analyze the error and fix it,
328
+ then tries again. Repeats up to max_corrections times.
329
+ """
330
+ try:
331
+ # First compilation attempt
332
+ success, message = self.pdf_compiler.compile(tex_path)
333
+ if success:
334
+ # Move generated PDF to desired location
335
+ generated_pdf = tex_path.replace('.tex', '.pdf')
336
+ if os.path.exists(generated_pdf) and generated_pdf != output_pdf:
337
+ # Remove existing file first (Windows compatibility)
338
+ if os.path.exists(output_pdf):
339
+ os.remove(output_pdf)
340
+ shutil.move(generated_pdf, output_pdf)
341
+ return True
342
+
343
+ # Compilation failed - enter self-correction loop
344
+ print(f"āš ļø Initial compilation failed. Starting LLM self-correction...")
345
+
346
+ # Read the failed LaTeX
347
+ with open(tex_path, 'r', encoding='utf-8') as f:
348
+ failed_latex = f.read()
349
+
350
+ # Use LLM to self-correct based on compilation error
351
+ corrected_latex, correction_success, corrections = \
352
+ self.llm_latex_generator.self_correct_compilation_errors(
353
+ failed_latex, message, max_attempts=max_corrections
354
+ )
355
+
356
+ if not correction_success:
357
+ print(f"āŒ LLM self-correction failed after {max_corrections} attempts")
358
+ return False
359
+
360
+ # Write the corrected LaTeX
361
+ with open(tex_path, 'w', encoding='utf-8') as f:
362
+ f.write(corrected_latex)
363
+
364
+ # Try compiling the corrected version
365
+ print("šŸ”„ Compiling LLM-corrected LaTeX...")
366
+ success, message = self.pdf_compiler.compile(tex_path)
367
+
368
+ if success:
369
+ generated_pdf = tex_path.replace('.tex', '.pdf')
370
+ if os.path.exists(generated_pdf) and generated_pdf != output_pdf:
371
+ # Remove existing file first (Windows compatibility)
372
+ if os.path.exists(output_pdf):
373
+ os.remove(output_pdf)
374
+ shutil.move(generated_pdf, output_pdf)
375
+ print(f"āœ… LLM self-correction successful! PDF generated.")
376
+ return True
377
+ else:
378
+ print(f"āŒ Compilation still failed after LLM correction: {message}")
379
+ return False
380
+
381
+ except Exception as e:
382
+ print(f"āŒ Compilation error: {e}")
383
+ return False
384
+
385
+
386
+ def main():
387
+ """Test the dynamic Visual QA feedback system."""
388
+ if len(os.sys.argv) != 2:
389
+ print("Usage: python visual_qa_agent.py <pdf_path>")
390
+ return
391
+
392
+ pdf_path = os.sys.argv[1]
393
+ agent = VisualQAFeedbackAgent()
394
+
395
+ print("šŸŽÆ Starting Dynamic Visual QA Improvement Process")
396
+ print("=" * 60)
397
+
398
+ final_pdf, improvements, final_version = agent.analyze_and_improve(pdf_path)
399
+
400
+ print(f"\nšŸŽ‰ Process Complete!")
401
+ print(f"šŸ“„ Final PDF: {final_pdf}")
402
+ if final_version:
403
+ print(f"šŸ“¦ Final Version: {final_version}")
404
+ print(f"šŸ”§ Improvements Made: {len(improvements)}")
405
+ for i, improvement in enumerate(improvements, 1):
406
+ print(f" {i}. {improvement}")
407
+
408
+
409
+ if __name__ == "__main__":
410
+ main()