amd-gaia 0.15.0__py3-none-any.whl → 0.15.2__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 (185) hide show
  1. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +222 -223
  2. amd_gaia-0.15.2.dist-info/RECORD +182 -0
  3. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
  4. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +1 -0
  5. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +20 -20
  6. gaia/__init__.py +29 -29
  7. gaia/agents/__init__.py +19 -19
  8. gaia/agents/base/__init__.py +9 -9
  9. gaia/agents/base/agent.py +2132 -2177
  10. gaia/agents/base/api_agent.py +119 -120
  11. gaia/agents/base/console.py +1967 -1841
  12. gaia/agents/base/errors.py +237 -237
  13. gaia/agents/base/mcp_agent.py +86 -86
  14. gaia/agents/base/tools.py +88 -83
  15. gaia/agents/blender/__init__.py +7 -0
  16. gaia/agents/blender/agent.py +553 -556
  17. gaia/agents/blender/agent_simple.py +133 -135
  18. gaia/agents/blender/app.py +211 -211
  19. gaia/agents/blender/app_simple.py +41 -41
  20. gaia/agents/blender/core/__init__.py +16 -16
  21. gaia/agents/blender/core/materials.py +506 -506
  22. gaia/agents/blender/core/objects.py +316 -316
  23. gaia/agents/blender/core/rendering.py +225 -225
  24. gaia/agents/blender/core/scene.py +220 -220
  25. gaia/agents/blender/core/view.py +146 -146
  26. gaia/agents/chat/__init__.py +9 -9
  27. gaia/agents/chat/agent.py +809 -835
  28. gaia/agents/chat/app.py +1065 -1058
  29. gaia/agents/chat/session.py +508 -508
  30. gaia/agents/chat/tools/__init__.py +15 -15
  31. gaia/agents/chat/tools/file_tools.py +96 -96
  32. gaia/agents/chat/tools/rag_tools.py +1744 -1729
  33. gaia/agents/chat/tools/shell_tools.py +437 -436
  34. gaia/agents/code/__init__.py +7 -7
  35. gaia/agents/code/agent.py +549 -549
  36. gaia/agents/code/cli.py +377 -0
  37. gaia/agents/code/models.py +135 -135
  38. gaia/agents/code/orchestration/__init__.py +24 -24
  39. gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
  40. gaia/agents/code/orchestration/checklist_generator.py +713 -713
  41. gaia/agents/code/orchestration/factories/__init__.py +9 -9
  42. gaia/agents/code/orchestration/factories/base.py +63 -63
  43. gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
  44. gaia/agents/code/orchestration/factories/python_factory.py +106 -106
  45. gaia/agents/code/orchestration/orchestrator.py +841 -841
  46. gaia/agents/code/orchestration/project_analyzer.py +391 -391
  47. gaia/agents/code/orchestration/steps/__init__.py +67 -67
  48. gaia/agents/code/orchestration/steps/base.py +188 -188
  49. gaia/agents/code/orchestration/steps/error_handler.py +314 -314
  50. gaia/agents/code/orchestration/steps/nextjs.py +828 -828
  51. gaia/agents/code/orchestration/steps/python.py +307 -307
  52. gaia/agents/code/orchestration/template_catalog.py +469 -469
  53. gaia/agents/code/orchestration/workflows/__init__.py +14 -14
  54. gaia/agents/code/orchestration/workflows/base.py +80 -80
  55. gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
  56. gaia/agents/code/orchestration/workflows/python.py +94 -94
  57. gaia/agents/code/prompts/__init__.py +11 -11
  58. gaia/agents/code/prompts/base_prompt.py +77 -77
  59. gaia/agents/code/prompts/code_patterns.py +2034 -2036
  60. gaia/agents/code/prompts/nextjs_prompt.py +40 -40
  61. gaia/agents/code/prompts/python_prompt.py +109 -109
  62. gaia/agents/code/schema_inference.py +365 -365
  63. gaia/agents/code/system_prompt.py +41 -41
  64. gaia/agents/code/tools/__init__.py +42 -42
  65. gaia/agents/code/tools/cli_tools.py +1138 -1138
  66. gaia/agents/code/tools/code_formatting.py +319 -319
  67. gaia/agents/code/tools/code_tools.py +769 -769
  68. gaia/agents/code/tools/error_fixing.py +1347 -1347
  69. gaia/agents/code/tools/external_tools.py +180 -180
  70. gaia/agents/code/tools/file_io.py +845 -845
  71. gaia/agents/code/tools/prisma_tools.py +190 -190
  72. gaia/agents/code/tools/project_management.py +1016 -1016
  73. gaia/agents/code/tools/testing.py +321 -321
  74. gaia/agents/code/tools/typescript_tools.py +122 -122
  75. gaia/agents/code/tools/validation_parsing.py +461 -461
  76. gaia/agents/code/tools/validation_tools.py +806 -806
  77. gaia/agents/code/tools/web_dev_tools.py +1758 -1758
  78. gaia/agents/code/validators/__init__.py +16 -16
  79. gaia/agents/code/validators/antipattern_checker.py +241 -241
  80. gaia/agents/code/validators/ast_analyzer.py +197 -197
  81. gaia/agents/code/validators/requirements_validator.py +145 -145
  82. gaia/agents/code/validators/syntax_validator.py +171 -171
  83. gaia/agents/docker/__init__.py +7 -7
  84. gaia/agents/docker/agent.py +643 -642
  85. gaia/agents/emr/__init__.py +8 -8
  86. gaia/agents/emr/agent.py +1504 -1506
  87. gaia/agents/emr/cli.py +1322 -1322
  88. gaia/agents/emr/constants.py +475 -475
  89. gaia/agents/emr/dashboard/__init__.py +4 -4
  90. gaia/agents/emr/dashboard/server.py +1972 -1974
  91. gaia/agents/jira/__init__.py +11 -11
  92. gaia/agents/jira/agent.py +894 -894
  93. gaia/agents/jira/jql_templates.py +299 -299
  94. gaia/agents/routing/__init__.py +7 -7
  95. gaia/agents/routing/agent.py +567 -570
  96. gaia/agents/routing/system_prompt.py +75 -75
  97. gaia/agents/summarize/__init__.py +11 -0
  98. gaia/agents/summarize/agent.py +885 -0
  99. gaia/agents/summarize/prompts.py +129 -0
  100. gaia/api/__init__.py +23 -23
  101. gaia/api/agent_registry.py +238 -238
  102. gaia/api/app.py +305 -305
  103. gaia/api/openai_server.py +575 -575
  104. gaia/api/schemas.py +186 -186
  105. gaia/api/sse_handler.py +373 -373
  106. gaia/apps/__init__.py +4 -4
  107. gaia/apps/llm/__init__.py +6 -6
  108. gaia/apps/llm/app.py +184 -169
  109. gaia/apps/summarize/app.py +116 -633
  110. gaia/apps/summarize/html_viewer.py +133 -133
  111. gaia/apps/summarize/pdf_formatter.py +284 -284
  112. gaia/audio/__init__.py +2 -2
  113. gaia/audio/audio_client.py +439 -439
  114. gaia/audio/audio_recorder.py +269 -269
  115. gaia/audio/kokoro_tts.py +599 -599
  116. gaia/audio/whisper_asr.py +432 -432
  117. gaia/chat/__init__.py +16 -16
  118. gaia/chat/app.py +428 -430
  119. gaia/chat/prompts.py +522 -522
  120. gaia/chat/sdk.py +1228 -1225
  121. gaia/cli.py +5659 -5632
  122. gaia/database/__init__.py +10 -10
  123. gaia/database/agent.py +176 -176
  124. gaia/database/mixin.py +290 -290
  125. gaia/database/testing.py +64 -64
  126. gaia/eval/batch_experiment.py +2332 -2332
  127. gaia/eval/claude.py +542 -542
  128. gaia/eval/config.py +37 -37
  129. gaia/eval/email_generator.py +512 -512
  130. gaia/eval/eval.py +3179 -3179
  131. gaia/eval/groundtruth.py +1130 -1130
  132. gaia/eval/transcript_generator.py +582 -582
  133. gaia/eval/webapp/README.md +167 -167
  134. gaia/eval/webapp/package-lock.json +875 -875
  135. gaia/eval/webapp/package.json +20 -20
  136. gaia/eval/webapp/public/app.js +3402 -3402
  137. gaia/eval/webapp/public/index.html +87 -87
  138. gaia/eval/webapp/public/styles.css +3661 -3661
  139. gaia/eval/webapp/server.js +415 -415
  140. gaia/eval/webapp/test-setup.js +72 -72
  141. gaia/installer/__init__.py +23 -0
  142. gaia/installer/init_command.py +1275 -0
  143. gaia/installer/lemonade_installer.py +619 -0
  144. gaia/llm/__init__.py +10 -2
  145. gaia/llm/base_client.py +60 -0
  146. gaia/llm/exceptions.py +12 -0
  147. gaia/llm/factory.py +70 -0
  148. gaia/llm/lemonade_client.py +3421 -3221
  149. gaia/llm/lemonade_manager.py +294 -294
  150. gaia/llm/providers/__init__.py +9 -0
  151. gaia/llm/providers/claude.py +108 -0
  152. gaia/llm/providers/lemonade.py +118 -0
  153. gaia/llm/providers/openai_provider.py +79 -0
  154. gaia/llm/vlm_client.py +382 -382
  155. gaia/logger.py +189 -189
  156. gaia/mcp/agent_mcp_server.py +245 -245
  157. gaia/mcp/blender_mcp_client.py +138 -138
  158. gaia/mcp/blender_mcp_server.py +648 -648
  159. gaia/mcp/context7_cache.py +332 -332
  160. gaia/mcp/external_services.py +518 -518
  161. gaia/mcp/mcp_bridge.py +811 -550
  162. gaia/mcp/servers/__init__.py +6 -6
  163. gaia/mcp/servers/docker_mcp.py +83 -83
  164. gaia/perf_analysis.py +361 -0
  165. gaia/rag/__init__.py +10 -10
  166. gaia/rag/app.py +293 -293
  167. gaia/rag/demo.py +304 -304
  168. gaia/rag/pdf_utils.py +235 -235
  169. gaia/rag/sdk.py +2194 -2194
  170. gaia/security.py +183 -163
  171. gaia/talk/app.py +287 -289
  172. gaia/talk/sdk.py +538 -538
  173. gaia/testing/__init__.py +87 -87
  174. gaia/testing/assertions.py +330 -330
  175. gaia/testing/fixtures.py +333 -333
  176. gaia/testing/mocks.py +493 -493
  177. gaia/util.py +46 -46
  178. gaia/utils/__init__.py +33 -33
  179. gaia/utils/file_watcher.py +675 -675
  180. gaia/utils/parsing.py +223 -223
  181. gaia/version.py +100 -100
  182. amd_gaia-0.15.0.dist-info/RECORD +0 -168
  183. gaia/agents/code/app.py +0 -266
  184. gaia/llm/llm_client.py +0 -723
  185. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/top_level.txt +0 -0
@@ -1,512 +1,512 @@
1
- # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
- # SPDX-License-Identifier: MIT
3
-
4
- import argparse
5
- import json
6
- from datetime import datetime
7
- from pathlib import Path
8
-
9
- from gaia.eval.claude import ClaudeClient
10
- from gaia.eval.config import DEFAULT_CLAUDE_MODEL
11
- from gaia.logger import get_logger
12
-
13
-
14
- class EmailGenerator:
15
- """Generates example business emails for testing email processing and summarization."""
16
-
17
- def __init__(self, claude_model=None, max_tokens=8192):
18
- self.log = get_logger(__name__)
19
-
20
- # Initialize Claude client for dynamic content generation
21
- if claude_model is None:
22
- claude_model = DEFAULT_CLAUDE_MODEL
23
- try:
24
- self.claude_client = ClaudeClient(model=claude_model, max_tokens=max_tokens)
25
- self.log.info(f"Initialized Claude client with model: {claude_model}")
26
- except Exception as e:
27
- self.log.error(f"Failed to initialize Claude client: {e}")
28
- raise ValueError(
29
- f"Could not initialize Claude client. Please ensure ANTHROPIC_API_KEY is set. Error: {e}"
30
- )
31
-
32
- # Email templates with different use cases
33
- self.email_templates = {
34
- "project_update": {
35
- "description": "Project status update and milestone communication",
36
- "sender_roles": [
37
- "Project Manager",
38
- "Team Lead",
39
- "Product Manager",
40
- "Engineering Manager",
41
- ],
42
- "recipient_types": ["stakeholders", "team members", "management"],
43
- "context": "A professional project update email sharing progress, milestones achieved, upcoming deliverables, and any blockers or risks.",
44
- },
45
- "meeting_request": {
46
- "description": "Meeting invitation and scheduling coordination",
47
- "sender_roles": [
48
- "Project Manager",
49
- "Executive Assistant",
50
- "Team Lead",
51
- "Business Analyst",
52
- ],
53
- "recipient_types": ["team members", "stakeholders", "clients"],
54
- "context": "A formal meeting request email with agenda, purpose, scheduling details, and expectations for attendees.",
55
- },
56
- "customer_support": {
57
- "description": "Customer service response and issue resolution",
58
- "sender_roles": [
59
- "Customer Support Representative",
60
- "Technical Support Specialist",
61
- "Account Manager",
62
- "Support Team Lead",
63
- ],
64
- "recipient_types": ["customers", "clients", "users"],
65
- "context": "A professional customer support email addressing user issues, providing solutions, and maintaining positive customer relationships.",
66
- },
67
- "sales_outreach": {
68
- "description": "Sales prospecting and business development communication",
69
- "sender_roles": [
70
- "Sales Representative",
71
- "Business Development Manager",
72
- "Account Executive",
73
- "Sales Manager",
74
- ],
75
- "recipient_types": ["prospects", "leads", "potential clients"],
76
- "context": "A persuasive sales email introducing products or services, highlighting value propositions, and encouraging engagement.",
77
- },
78
- "internal_announcement": {
79
- "description": "Company-wide announcements and policy communications",
80
- "sender_roles": [
81
- "CEO",
82
- "HR Manager",
83
- "Operations Manager",
84
- "Communications Team",
85
- ],
86
- "recipient_types": ["all employees", "department teams", "management"],
87
- "context": "An official internal announcement covering company news, policy changes, organizational updates, or important notifications.",
88
- },
89
- "technical_discussion": {
90
- "description": "Technical problem-solving and architecture discussions",
91
- "sender_roles": [
92
- "Senior Developer",
93
- "Technical Architect",
94
- "DevOps Engineer",
95
- "CTO",
96
- ],
97
- "recipient_types": [
98
- "development team",
99
- "technical leads",
100
- "engineering",
101
- ],
102
- "context": "A detailed technical email discussing system architecture, code reviews, technical challenges, or solution proposals.",
103
- },
104
- "vendor_communication": {
105
- "description": "External vendor and supplier coordination",
106
- "sender_roles": [
107
- "Procurement Manager",
108
- "Operations Manager",
109
- "Project Manager",
110
- "Finance Director",
111
- ],
112
- "recipient_types": ["vendors", "suppliers", "contractors"],
113
- "context": "A professional vendor communication covering contracts, deliverables, payment terms, or service requirements.",
114
- },
115
- "performance_feedback": {
116
- "description": "Employee performance reviews and feedback communication",
117
- "sender_roles": [
118
- "HR Manager",
119
- "Direct Manager",
120
- "Team Lead",
121
- "Director",
122
- ],
123
- "recipient_types": ["employees", "team members", "direct reports"],
124
- "context": "A constructive performance feedback email covering achievements, areas for improvement, and development opportunities.",
125
- },
126
- }
127
-
128
- def _estimate_tokens(self, text):
129
- """Rough token estimation (approximately 4 characters per token)."""
130
- return len(text) // 4
131
-
132
- def _generate_email_with_claude(self, email_type, target_tokens):
133
- """Generate an email using Claude based on the email type and target token count."""
134
- if email_type not in self.email_templates:
135
- raise ValueError(f"Unknown email type: {email_type}")
136
-
137
- template = self.email_templates[email_type]
138
-
139
- # Create a detailed prompt for Claude
140
- prompt = f"""Generate a realistic business email for the following scenario:
141
-
142
- Email Type: {template['description']}
143
- Context: {template['context']}
144
- Sender Role Options: {', '.join(template['sender_roles'])}
145
- Recipient Types: {', '.join(template['recipient_types'])}
146
- Target Length: Approximately {target_tokens} tokens (about {target_tokens * 4} characters)
147
-
148
- Please create a detailed, realistic business email that includes:
149
- 1. Professional email header (From, To, Subject, Date)
150
- 2. Appropriate greeting and professional tone
151
- 3. Clear purpose and main content relevant to the email type
152
- 4. Specific details, requests, or information as appropriate
153
- 5. Professional closing and signature
154
- 6. Realistic names, companies, and scenarios
155
-
156
- Format the email as a complete email message with:
157
- - Subject line
158
- - Professional sender and recipient information
159
- - Proper email structure and formatting
160
- - Content appropriate for the business context
161
-
162
- Make the email feel authentic and professional, with realistic details and appropriate tone for the email type. The email should be approximately {target_tokens} tokens long.
163
-
164
- Generate only the email content, no additional commentary."""
165
-
166
- try:
167
- # Generate the email using Claude with usage tracking
168
- self.log.info(
169
- f"Generating {email_type} email with Claude (target: {target_tokens} tokens)"
170
- )
171
- response = self.claude_client.get_completion_with_usage(prompt)
172
-
173
- generated_content = (
174
- response["content"][0].text
175
- if isinstance(response["content"], list)
176
- else response["content"]
177
- )
178
- actual_tokens = self._estimate_tokens(generated_content)
179
-
180
- self.log.info(
181
- f"Generated email: {actual_tokens} tokens (target: {target_tokens})"
182
- )
183
-
184
- return generated_content, response["usage"], response["cost"]
185
-
186
- except Exception as e:
187
- self.log.error(f"Error generating email with Claude: {e}")
188
- raise RuntimeError(f"Failed to generate email for {email_type}: {e}")
189
-
190
- def _extend_content_with_claude(
191
- self, base_content, target_tokens, email_type, current_usage, current_cost
192
- ):
193
- """Extend existing content to reach target token count using Claude."""
194
- current_tokens = self._estimate_tokens(base_content)
195
-
196
- if current_tokens >= target_tokens:
197
- return base_content, current_usage, current_cost
198
-
199
- needed_tokens = target_tokens - current_tokens
200
- template = self.email_templates[email_type]
201
-
202
- extension_prompt = f"""Continue the following business email to make it approximately {needed_tokens} more tokens longer.
203
-
204
- Current email:
205
- {base_content}
206
-
207
- Please add more realistic content that:
208
- 1. Maintains the same professional tone and context
209
- 2. Continues naturally from where it left off
210
- 3. Adds approximately {needed_tokens} more tokens of content
211
- 4. Includes meaningful details relevant to a {template['description']}
212
- 5. Maintains professional email format and structure
213
-
214
- Generate only the additional email content (without repeating the existing content)."""
215
-
216
- try:
217
- self.log.info(f"Extending email by ~{needed_tokens} tokens")
218
- response = self.claude_client.get_completion_with_usage(extension_prompt)
219
-
220
- extension_content = (
221
- response["content"][0].text
222
- if isinstance(response["content"], list)
223
- else response["content"]
224
- )
225
- extended_content = base_content + "\n\n" + extension_content
226
-
227
- # Combine usage and cost data
228
- total_usage = {
229
- "input_tokens": current_usage["input_tokens"]
230
- + response["usage"]["input_tokens"],
231
- "output_tokens": current_usage["output_tokens"]
232
- + response["usage"]["output_tokens"],
233
- "total_tokens": current_usage["total_tokens"]
234
- + response["usage"]["total_tokens"],
235
- }
236
-
237
- total_cost = {
238
- "input_cost": current_cost["input_cost"]
239
- + response["cost"]["input_cost"],
240
- "output_cost": current_cost["output_cost"]
241
- + response["cost"]["output_cost"],
242
- "total_cost": current_cost["total_cost"]
243
- + response["cost"]["total_cost"],
244
- }
245
-
246
- actual_tokens = self._estimate_tokens(extended_content)
247
- self.log.info(f"Extended email to {actual_tokens} tokens")
248
-
249
- return extended_content, total_usage, total_cost
250
-
251
- except Exception as e:
252
- self.log.error(f"Error extending email with Claude: {e}")
253
- # Return original content if extension fails
254
- return base_content, current_usage, current_cost
255
-
256
- def generate_email(self, email_type, target_tokens=800):
257
- """Generate a single business email of specified type and approximate token count using Claude."""
258
- if email_type not in self.email_templates:
259
- raise ValueError(f"Unknown email type: {email_type}")
260
-
261
- template = self.email_templates[email_type]
262
-
263
- try:
264
- # Generate email with Claude
265
- content, usage, cost = self._generate_email_with_claude(
266
- email_type, target_tokens
267
- )
268
- actual_tokens = self._estimate_tokens(content)
269
-
270
- # If we're significantly under target, try to extend
271
- if actual_tokens < target_tokens * 0.8: # If less than 80% of target
272
- self.log.info(
273
- f"Email too short ({actual_tokens} tokens), extending to reach target"
274
- )
275
- content, usage, cost = self._extend_content_with_claude(
276
- content, target_tokens, email_type, usage, cost
277
- )
278
- actual_tokens = self._estimate_tokens(content)
279
-
280
- # Add metadata
281
- metadata = {
282
- "email_type": email_type,
283
- "description": template["description"],
284
- "sender_roles": template["sender_roles"],
285
- "recipient_types": template["recipient_types"],
286
- "estimated_tokens": actual_tokens,
287
- "target_tokens": target_tokens,
288
- "generated_date": datetime.now().isoformat(),
289
- "claude_model": self.claude_client.model,
290
- "claude_usage": usage,
291
- "claude_cost": cost,
292
- }
293
-
294
- return content, metadata
295
-
296
- except Exception as e:
297
- self.log.error(f"Failed to generate email for {email_type}: {e}")
298
- raise
299
-
300
- def generate_email_set(self, output_dir, target_tokens=800, count_per_type=1):
301
- """Generate a set of business emails and save them to the output directory."""
302
- output_dir = Path(output_dir)
303
- # Create emails subdirectory for organized output
304
- emails_dir = output_dir / "emails"
305
- emails_dir.mkdir(parents=True, exist_ok=True)
306
- output_dir = emails_dir # Use emails subdirectory as base
307
-
308
- generated_files = []
309
- all_metadata = []
310
- total_usage = {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0}
311
- total_cost = {"input_cost": 0.0, "output_cost": 0.0, "total_cost": 0.0}
312
-
313
- for email_type in self.email_templates.keys():
314
- for i in range(count_per_type):
315
- self.log.info(f"Generating {email_type} email {i+1}/{count_per_type}")
316
-
317
- # Generate email
318
- content, metadata = self.generate_email(email_type, target_tokens)
319
-
320
- # Create filename
321
- if count_per_type == 1:
322
- filename = f"{email_type}_email.txt"
323
- else:
324
- filename = f"{email_type}_email_{i+1}.txt"
325
-
326
- # Save email file
327
- file_path = output_dir / filename
328
- with open(file_path, "w", encoding="utf-8") as f:
329
- f.write(content)
330
-
331
- # Update metadata with file info
332
- metadata["filename"] = filename
333
- metadata["file_path"] = str(file_path)
334
- metadata["file_size_bytes"] = len(content.encode("utf-8"))
335
-
336
- generated_files.append(str(file_path))
337
- all_metadata.append(metadata)
338
-
339
- # Accumulate usage and cost
340
- usage = metadata["claude_usage"]
341
- cost = metadata["claude_cost"]
342
- total_usage["input_tokens"] += usage["input_tokens"]
343
- total_usage["output_tokens"] += usage["output_tokens"]
344
- total_usage["total_tokens"] += usage["total_tokens"]
345
- total_cost["input_cost"] += cost["input_cost"]
346
- total_cost["output_cost"] += cost["output_cost"]
347
- total_cost["total_cost"] += cost["total_cost"]
348
-
349
- self.log.info(
350
- f"Generated {filename} ({metadata['estimated_tokens']} tokens, ${cost['total_cost']:.4f})"
351
- )
352
-
353
- # Create summary metadata file
354
- summary = {
355
- "generation_info": {
356
- "generated_date": datetime.now().isoformat(),
357
- "total_files": len(generated_files),
358
- "target_tokens_per_file": target_tokens,
359
- "email_types": list(self.email_templates.keys()),
360
- "files_per_type": count_per_type,
361
- "claude_model": self.claude_client.model,
362
- "total_claude_usage": total_usage,
363
- "total_claude_cost": total_cost,
364
- },
365
- "emails": all_metadata,
366
- }
367
-
368
- summary_path = output_dir / "email_metadata.json"
369
- with open(summary_path, "w", encoding="utf-8") as f:
370
- json.dump(summary, f, indent=2)
371
-
372
- self.log.info(f"Generated {len(generated_files)} email files in {output_dir}")
373
- self.log.info(
374
- f"Total cost: ${total_cost['total_cost']:.4f} ({total_usage['total_tokens']:,} tokens)"
375
- )
376
- self.log.info(f"Summary metadata saved to {summary_path}")
377
-
378
- return {
379
- "output_directory": str(output_dir),
380
- "generated_files": generated_files,
381
- "metadata_file": str(summary_path),
382
- "summary": summary,
383
- }
384
-
385
-
386
- def main():
387
- """Command line interface for email generation."""
388
- parser = argparse.ArgumentParser(
389
- description="Generate example business emails using Claude AI for testing email processing and summarization",
390
- formatter_class=argparse.RawDescriptionHelpFormatter,
391
- epilog="""
392
- Examples:
393
- # Generate one email of each type with ~800 tokens
394
- python -m gaia.eval.email_generator -o ./output/emails
395
-
396
- # Generate larger emails (~1500 tokens each)
397
- python -m gaia.eval.email_generator -o ./output/emails --target-tokens 1500
398
-
399
- # Generate multiple emails per type
400
- python -m gaia.eval.email_generator -o ./output/emails --count-per-type 3
401
-
402
- # Generate specific email types only
403
- python -m gaia.eval.email_generator -o ./output/emails --email-types project_update sales_outreach
404
-
405
- # Generate small emails for quick testing
406
- python -m gaia.eval.email_generator -o ./test_emails --target-tokens 400
407
-
408
- # Use different Claude model
409
- python -m gaia.eval.email_generator -o ./output/emails --claude-model claude-3-opus-20240229
410
- """,
411
- )
412
-
413
- parser.add_argument(
414
- "-o",
415
- "--output-dir",
416
- type=str,
417
- required=True,
418
- help="Output directory for generated email files",
419
- )
420
- parser.add_argument(
421
- "--target-tokens",
422
- type=int,
423
- default=800,
424
- help="Target token count per email (approximate, default: 800)",
425
- )
426
- parser.add_argument(
427
- "--count-per-type",
428
- type=int,
429
- default=1,
430
- help="Number of emails to generate per email type (default: 1)",
431
- )
432
- parser.add_argument(
433
- "--email-types",
434
- nargs="+",
435
- choices=[
436
- "project_update",
437
- "meeting_request",
438
- "customer_support",
439
- "sales_outreach",
440
- "internal_announcement",
441
- "technical_discussion",
442
- "vendor_communication",
443
- "performance_feedback",
444
- ],
445
- help="Specific email types to generate (default: all types)",
446
- )
447
- parser.add_argument(
448
- "--claude-model",
449
- type=str,
450
- default=None,
451
- help=f"Claude model to use for email generation (default: {DEFAULT_CLAUDE_MODEL})",
452
- )
453
-
454
- args = parser.parse_args()
455
-
456
- try:
457
- generator = EmailGenerator(claude_model=args.claude_model)
458
- except Exception as e:
459
- print(f"❌ Error initializing email generator: {e}")
460
- print("Make sure ANTHROPIC_API_KEY is set in your environment.")
461
- return 1
462
-
463
- try:
464
- # Filter email types if specified
465
- original_templates = None
466
- if args.email_types:
467
- # Temporarily filter the templates
468
- original_templates = generator.email_templates.copy()
469
- generator.email_templates = {
470
- k: v
471
- for k, v in generator.email_templates.items()
472
- if k in args.email_types
473
- }
474
-
475
- result = generator.generate_email_set(
476
- output_dir=args.output_dir,
477
- target_tokens=args.target_tokens,
478
- count_per_type=args.count_per_type,
479
- )
480
-
481
- print("✅ Successfully generated business emails")
482
- print(f" Output directory: {result['output_directory']}")
483
- print(f" Generated files: {len(result['generated_files'])}")
484
- print(f" Metadata file: {result['metadata_file']}")
485
-
486
- # Show summary stats
487
- summary = result["summary"]
488
- generation_info = summary["generation_info"]
489
- total_tokens = generation_info["total_claude_usage"]["total_tokens"]
490
- total_cost = generation_info["total_claude_cost"]["total_cost"]
491
- avg_tokens = total_tokens / len(summary["emails"]) if summary["emails"] else 0
492
-
493
- print(f" Total tokens used: {total_tokens:,}")
494
- print(f" Total cost: ${total_cost:.4f}")
495
- print(f" Average tokens per file: {avg_tokens:.0f}")
496
- print(f" Average cost per file: ${total_cost/len(summary['emails']):.4f}")
497
- print(f" Email types: {', '.join(generation_info['email_types'])}")
498
- print(f" Claude model: {generation_info['claude_model']}")
499
-
500
- # Restore original templates if they were filtered
501
- if args.email_types and original_templates is not None:
502
- generator.email_templates = original_templates
503
-
504
- except Exception as e:
505
- print(f"❌ Error generating emails: {e}")
506
- return 1
507
-
508
- return 0
509
-
510
-
511
- if __name__ == "__main__":
512
- exit(main())
1
+ # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import argparse
5
+ import json
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+
9
+ from gaia.eval.claude import ClaudeClient
10
+ from gaia.eval.config import DEFAULT_CLAUDE_MODEL
11
+ from gaia.logger import get_logger
12
+
13
+
14
+ class EmailGenerator:
15
+ """Generates example business emails for testing email processing and summarization."""
16
+
17
+ def __init__(self, claude_model=None, max_tokens=8192):
18
+ self.log = get_logger(__name__)
19
+
20
+ # Initialize Claude client for dynamic content generation
21
+ if claude_model is None:
22
+ claude_model = DEFAULT_CLAUDE_MODEL
23
+ try:
24
+ self.claude_client = ClaudeClient(model=claude_model, max_tokens=max_tokens)
25
+ self.log.info(f"Initialized Claude client with model: {claude_model}")
26
+ except Exception as e:
27
+ self.log.error(f"Failed to initialize Claude client: {e}")
28
+ raise ValueError(
29
+ f"Could not initialize Claude client. Please ensure ANTHROPIC_API_KEY is set. Error: {e}"
30
+ )
31
+
32
+ # Email templates with different use cases
33
+ self.email_templates = {
34
+ "project_update": {
35
+ "description": "Project status update and milestone communication",
36
+ "sender_roles": [
37
+ "Project Manager",
38
+ "Team Lead",
39
+ "Product Manager",
40
+ "Engineering Manager",
41
+ ],
42
+ "recipient_types": ["stakeholders", "team members", "management"],
43
+ "context": "A professional project update email sharing progress, milestones achieved, upcoming deliverables, and any blockers or risks.",
44
+ },
45
+ "meeting_request": {
46
+ "description": "Meeting invitation and scheduling coordination",
47
+ "sender_roles": [
48
+ "Project Manager",
49
+ "Executive Assistant",
50
+ "Team Lead",
51
+ "Business Analyst",
52
+ ],
53
+ "recipient_types": ["team members", "stakeholders", "clients"],
54
+ "context": "A formal meeting request email with agenda, purpose, scheduling details, and expectations for attendees.",
55
+ },
56
+ "customer_support": {
57
+ "description": "Customer service response and issue resolution",
58
+ "sender_roles": [
59
+ "Customer Support Representative",
60
+ "Technical Support Specialist",
61
+ "Account Manager",
62
+ "Support Team Lead",
63
+ ],
64
+ "recipient_types": ["customers", "clients", "users"],
65
+ "context": "A professional customer support email addressing user issues, providing solutions, and maintaining positive customer relationships.",
66
+ },
67
+ "sales_outreach": {
68
+ "description": "Sales prospecting and business development communication",
69
+ "sender_roles": [
70
+ "Sales Representative",
71
+ "Business Development Manager",
72
+ "Account Executive",
73
+ "Sales Manager",
74
+ ],
75
+ "recipient_types": ["prospects", "leads", "potential clients"],
76
+ "context": "A persuasive sales email introducing products or services, highlighting value propositions, and encouraging engagement.",
77
+ },
78
+ "internal_announcement": {
79
+ "description": "Company-wide announcements and policy communications",
80
+ "sender_roles": [
81
+ "CEO",
82
+ "HR Manager",
83
+ "Operations Manager",
84
+ "Communications Team",
85
+ ],
86
+ "recipient_types": ["all employees", "department teams", "management"],
87
+ "context": "An official internal announcement covering company news, policy changes, organizational updates, or important notifications.",
88
+ },
89
+ "technical_discussion": {
90
+ "description": "Technical problem-solving and architecture discussions",
91
+ "sender_roles": [
92
+ "Senior Developer",
93
+ "Technical Architect",
94
+ "DevOps Engineer",
95
+ "CTO",
96
+ ],
97
+ "recipient_types": [
98
+ "development team",
99
+ "technical leads",
100
+ "engineering",
101
+ ],
102
+ "context": "A detailed technical email discussing system architecture, code reviews, technical challenges, or solution proposals.",
103
+ },
104
+ "vendor_communication": {
105
+ "description": "External vendor and supplier coordination",
106
+ "sender_roles": [
107
+ "Procurement Manager",
108
+ "Operations Manager",
109
+ "Project Manager",
110
+ "Finance Director",
111
+ ],
112
+ "recipient_types": ["vendors", "suppliers", "contractors"],
113
+ "context": "A professional vendor communication covering contracts, deliverables, payment terms, or service requirements.",
114
+ },
115
+ "performance_feedback": {
116
+ "description": "Employee performance reviews and feedback communication",
117
+ "sender_roles": [
118
+ "HR Manager",
119
+ "Direct Manager",
120
+ "Team Lead",
121
+ "Director",
122
+ ],
123
+ "recipient_types": ["employees", "team members", "direct reports"],
124
+ "context": "A constructive performance feedback email covering achievements, areas for improvement, and development opportunities.",
125
+ },
126
+ }
127
+
128
+ def _estimate_tokens(self, text):
129
+ """Rough token estimation (approximately 4 characters per token)."""
130
+ return len(text) // 4
131
+
132
+ def _generate_email_with_claude(self, email_type, target_tokens):
133
+ """Generate an email using Claude based on the email type and target token count."""
134
+ if email_type not in self.email_templates:
135
+ raise ValueError(f"Unknown email type: {email_type}")
136
+
137
+ template = self.email_templates[email_type]
138
+
139
+ # Create a detailed prompt for Claude
140
+ prompt = f"""Generate a realistic business email for the following scenario:
141
+
142
+ Email Type: {template['description']}
143
+ Context: {template['context']}
144
+ Sender Role Options: {', '.join(template['sender_roles'])}
145
+ Recipient Types: {', '.join(template['recipient_types'])}
146
+ Target Length: Approximately {target_tokens} tokens (about {target_tokens * 4} characters)
147
+
148
+ Please create a detailed, realistic business email that includes:
149
+ 1. Professional email header (From, To, Subject, Date)
150
+ 2. Appropriate greeting and professional tone
151
+ 3. Clear purpose and main content relevant to the email type
152
+ 4. Specific details, requests, or information as appropriate
153
+ 5. Professional closing and signature
154
+ 6. Realistic names, companies, and scenarios
155
+
156
+ Format the email as a complete email message with:
157
+ - Subject line
158
+ - Professional sender and recipient information
159
+ - Proper email structure and formatting
160
+ - Content appropriate for the business context
161
+
162
+ Make the email feel authentic and professional, with realistic details and appropriate tone for the email type. The email should be approximately {target_tokens} tokens long.
163
+
164
+ Generate only the email content, no additional commentary."""
165
+
166
+ try:
167
+ # Generate the email using Claude with usage tracking
168
+ self.log.info(
169
+ f"Generating {email_type} email with Claude (target: {target_tokens} tokens)"
170
+ )
171
+ response = self.claude_client.get_completion_with_usage(prompt)
172
+
173
+ generated_content = (
174
+ response["content"][0].text
175
+ if isinstance(response["content"], list)
176
+ else response["content"]
177
+ )
178
+ actual_tokens = self._estimate_tokens(generated_content)
179
+
180
+ self.log.info(
181
+ f"Generated email: {actual_tokens} tokens (target: {target_tokens})"
182
+ )
183
+
184
+ return generated_content, response["usage"], response["cost"]
185
+
186
+ except Exception as e:
187
+ self.log.error(f"Error generating email with Claude: {e}")
188
+ raise RuntimeError(f"Failed to generate email for {email_type}: {e}")
189
+
190
+ def _extend_content_with_claude(
191
+ self, base_content, target_tokens, email_type, current_usage, current_cost
192
+ ):
193
+ """Extend existing content to reach target token count using Claude."""
194
+ current_tokens = self._estimate_tokens(base_content)
195
+
196
+ if current_tokens >= target_tokens:
197
+ return base_content, current_usage, current_cost
198
+
199
+ needed_tokens = target_tokens - current_tokens
200
+ template = self.email_templates[email_type]
201
+
202
+ extension_prompt = f"""Continue the following business email to make it approximately {needed_tokens} more tokens longer.
203
+
204
+ Current email:
205
+ {base_content}
206
+
207
+ Please add more realistic content that:
208
+ 1. Maintains the same professional tone and context
209
+ 2. Continues naturally from where it left off
210
+ 3. Adds approximately {needed_tokens} more tokens of content
211
+ 4. Includes meaningful details relevant to a {template['description']}
212
+ 5. Maintains professional email format and structure
213
+
214
+ Generate only the additional email content (without repeating the existing content)."""
215
+
216
+ try:
217
+ self.log.info(f"Extending email by ~{needed_tokens} tokens")
218
+ response = self.claude_client.get_completion_with_usage(extension_prompt)
219
+
220
+ extension_content = (
221
+ response["content"][0].text
222
+ if isinstance(response["content"], list)
223
+ else response["content"]
224
+ )
225
+ extended_content = base_content + "\n\n" + extension_content
226
+
227
+ # Combine usage and cost data
228
+ total_usage = {
229
+ "input_tokens": current_usage["input_tokens"]
230
+ + response["usage"]["input_tokens"],
231
+ "output_tokens": current_usage["output_tokens"]
232
+ + response["usage"]["output_tokens"],
233
+ "total_tokens": current_usage["total_tokens"]
234
+ + response["usage"]["total_tokens"],
235
+ }
236
+
237
+ total_cost = {
238
+ "input_cost": current_cost["input_cost"]
239
+ + response["cost"]["input_cost"],
240
+ "output_cost": current_cost["output_cost"]
241
+ + response["cost"]["output_cost"],
242
+ "total_cost": current_cost["total_cost"]
243
+ + response["cost"]["total_cost"],
244
+ }
245
+
246
+ actual_tokens = self._estimate_tokens(extended_content)
247
+ self.log.info(f"Extended email to {actual_tokens} tokens")
248
+
249
+ return extended_content, total_usage, total_cost
250
+
251
+ except Exception as e:
252
+ self.log.error(f"Error extending email with Claude: {e}")
253
+ # Return original content if extension fails
254
+ return base_content, current_usage, current_cost
255
+
256
+ def generate_email(self, email_type, target_tokens=800):
257
+ """Generate a single business email of specified type and approximate token count using Claude."""
258
+ if email_type not in self.email_templates:
259
+ raise ValueError(f"Unknown email type: {email_type}")
260
+
261
+ template = self.email_templates[email_type]
262
+
263
+ try:
264
+ # Generate email with Claude
265
+ content, usage, cost = self._generate_email_with_claude(
266
+ email_type, target_tokens
267
+ )
268
+ actual_tokens = self._estimate_tokens(content)
269
+
270
+ # If we're significantly under target, try to extend
271
+ if actual_tokens < target_tokens * 0.8: # If less than 80% of target
272
+ self.log.info(
273
+ f"Email too short ({actual_tokens} tokens), extending to reach target"
274
+ )
275
+ content, usage, cost = self._extend_content_with_claude(
276
+ content, target_tokens, email_type, usage, cost
277
+ )
278
+ actual_tokens = self._estimate_tokens(content)
279
+
280
+ # Add metadata
281
+ metadata = {
282
+ "email_type": email_type,
283
+ "description": template["description"],
284
+ "sender_roles": template["sender_roles"],
285
+ "recipient_types": template["recipient_types"],
286
+ "estimated_tokens": actual_tokens,
287
+ "target_tokens": target_tokens,
288
+ "generated_date": datetime.now().isoformat(),
289
+ "claude_model": self.claude_client.model,
290
+ "claude_usage": usage,
291
+ "claude_cost": cost,
292
+ }
293
+
294
+ return content, metadata
295
+
296
+ except Exception as e:
297
+ self.log.error(f"Failed to generate email for {email_type}: {e}")
298
+ raise
299
+
300
+ def generate_email_set(self, output_dir, target_tokens=800, count_per_type=1):
301
+ """Generate a set of business emails and save them to the output directory."""
302
+ output_dir = Path(output_dir)
303
+ # Create emails subdirectory for organized output
304
+ emails_dir = output_dir / "emails"
305
+ emails_dir.mkdir(parents=True, exist_ok=True)
306
+ output_dir = emails_dir # Use emails subdirectory as base
307
+
308
+ generated_files = []
309
+ all_metadata = []
310
+ total_usage = {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0}
311
+ total_cost = {"input_cost": 0.0, "output_cost": 0.0, "total_cost": 0.0}
312
+
313
+ for email_type in self.email_templates.keys():
314
+ for i in range(count_per_type):
315
+ self.log.info(f"Generating {email_type} email {i+1}/{count_per_type}")
316
+
317
+ # Generate email
318
+ content, metadata = self.generate_email(email_type, target_tokens)
319
+
320
+ # Create filename
321
+ if count_per_type == 1:
322
+ filename = f"{email_type}_email.txt"
323
+ else:
324
+ filename = f"{email_type}_email_{i+1}.txt"
325
+
326
+ # Save email file
327
+ file_path = output_dir / filename
328
+ with open(file_path, "w", encoding="utf-8") as f:
329
+ f.write(content)
330
+
331
+ # Update metadata with file info
332
+ metadata["filename"] = filename
333
+ metadata["file_path"] = str(file_path)
334
+ metadata["file_size_bytes"] = len(content.encode("utf-8"))
335
+
336
+ generated_files.append(str(file_path))
337
+ all_metadata.append(metadata)
338
+
339
+ # Accumulate usage and cost
340
+ usage = metadata["claude_usage"]
341
+ cost = metadata["claude_cost"]
342
+ total_usage["input_tokens"] += usage["input_tokens"]
343
+ total_usage["output_tokens"] += usage["output_tokens"]
344
+ total_usage["total_tokens"] += usage["total_tokens"]
345
+ total_cost["input_cost"] += cost["input_cost"]
346
+ total_cost["output_cost"] += cost["output_cost"]
347
+ total_cost["total_cost"] += cost["total_cost"]
348
+
349
+ self.log.info(
350
+ f"Generated {filename} ({metadata['estimated_tokens']} tokens, ${cost['total_cost']:.4f})"
351
+ )
352
+
353
+ # Create summary metadata file
354
+ summary = {
355
+ "generation_info": {
356
+ "generated_date": datetime.now().isoformat(),
357
+ "total_files": len(generated_files),
358
+ "target_tokens_per_file": target_tokens,
359
+ "email_types": list(self.email_templates.keys()),
360
+ "files_per_type": count_per_type,
361
+ "claude_model": self.claude_client.model,
362
+ "total_claude_usage": total_usage,
363
+ "total_claude_cost": total_cost,
364
+ },
365
+ "emails": all_metadata,
366
+ }
367
+
368
+ summary_path = output_dir / "email_metadata.json"
369
+ with open(summary_path, "w", encoding="utf-8") as f:
370
+ json.dump(summary, f, indent=2)
371
+
372
+ self.log.info(f"Generated {len(generated_files)} email files in {output_dir}")
373
+ self.log.info(
374
+ f"Total cost: ${total_cost['total_cost']:.4f} ({total_usage['total_tokens']:,} tokens)"
375
+ )
376
+ self.log.info(f"Summary metadata saved to {summary_path}")
377
+
378
+ return {
379
+ "output_directory": str(output_dir),
380
+ "generated_files": generated_files,
381
+ "metadata_file": str(summary_path),
382
+ "summary": summary,
383
+ }
384
+
385
+
386
+ def main():
387
+ """Command line interface for email generation."""
388
+ parser = argparse.ArgumentParser(
389
+ description="Generate example business emails using Claude AI for testing email processing and summarization",
390
+ formatter_class=argparse.RawDescriptionHelpFormatter,
391
+ epilog="""
392
+ Examples:
393
+ # Generate one email of each type with ~800 tokens
394
+ python -m gaia.eval.email_generator -o ./output/emails
395
+
396
+ # Generate larger emails (~1500 tokens each)
397
+ python -m gaia.eval.email_generator -o ./output/emails --target-tokens 1500
398
+
399
+ # Generate multiple emails per type
400
+ python -m gaia.eval.email_generator -o ./output/emails --count-per-type 3
401
+
402
+ # Generate specific email types only
403
+ python -m gaia.eval.email_generator -o ./output/emails --email-types project_update sales_outreach
404
+
405
+ # Generate small emails for quick testing
406
+ python -m gaia.eval.email_generator -o ./test_emails --target-tokens 400
407
+
408
+ # Use different Claude model
409
+ python -m gaia.eval.email_generator -o ./output/emails --claude-model claude-3-opus-20240229
410
+ """,
411
+ )
412
+
413
+ parser.add_argument(
414
+ "-o",
415
+ "--output-dir",
416
+ type=str,
417
+ required=True,
418
+ help="Output directory for generated email files",
419
+ )
420
+ parser.add_argument(
421
+ "--target-tokens",
422
+ type=int,
423
+ default=800,
424
+ help="Target token count per email (approximate, default: 800)",
425
+ )
426
+ parser.add_argument(
427
+ "--count-per-type",
428
+ type=int,
429
+ default=1,
430
+ help="Number of emails to generate per email type (default: 1)",
431
+ )
432
+ parser.add_argument(
433
+ "--email-types",
434
+ nargs="+",
435
+ choices=[
436
+ "project_update",
437
+ "meeting_request",
438
+ "customer_support",
439
+ "sales_outreach",
440
+ "internal_announcement",
441
+ "technical_discussion",
442
+ "vendor_communication",
443
+ "performance_feedback",
444
+ ],
445
+ help="Specific email types to generate (default: all types)",
446
+ )
447
+ parser.add_argument(
448
+ "--claude-model",
449
+ type=str,
450
+ default=None,
451
+ help=f"Claude model to use for email generation (default: {DEFAULT_CLAUDE_MODEL})",
452
+ )
453
+
454
+ args = parser.parse_args()
455
+
456
+ try:
457
+ generator = EmailGenerator(claude_model=args.claude_model)
458
+ except Exception as e:
459
+ print(f"❌ Error initializing email generator: {e}")
460
+ print("Make sure ANTHROPIC_API_KEY is set in your environment.")
461
+ return 1
462
+
463
+ try:
464
+ # Filter email types if specified
465
+ original_templates = None
466
+ if args.email_types:
467
+ # Temporarily filter the templates
468
+ original_templates = generator.email_templates.copy()
469
+ generator.email_templates = {
470
+ k: v
471
+ for k, v in generator.email_templates.items()
472
+ if k in args.email_types
473
+ }
474
+
475
+ result = generator.generate_email_set(
476
+ output_dir=args.output_dir,
477
+ target_tokens=args.target_tokens,
478
+ count_per_type=args.count_per_type,
479
+ )
480
+
481
+ print("✅ Successfully generated business emails")
482
+ print(f" Output directory: {result['output_directory']}")
483
+ print(f" Generated files: {len(result['generated_files'])}")
484
+ print(f" Metadata file: {result['metadata_file']}")
485
+
486
+ # Show summary stats
487
+ summary = result["summary"]
488
+ generation_info = summary["generation_info"]
489
+ total_tokens = generation_info["total_claude_usage"]["total_tokens"]
490
+ total_cost = generation_info["total_claude_cost"]["total_cost"]
491
+ avg_tokens = total_tokens / len(summary["emails"]) if summary["emails"] else 0
492
+
493
+ print(f" Total tokens used: {total_tokens:,}")
494
+ print(f" Total cost: ${total_cost:.4f}")
495
+ print(f" Average tokens per file: {avg_tokens:.0f}")
496
+ print(f" Average cost per file: ${total_cost/len(summary['emails']):.4f}")
497
+ print(f" Email types: {', '.join(generation_info['email_types'])}")
498
+ print(f" Claude model: {generation_info['claude_model']}")
499
+
500
+ # Restore original templates if they were filtered
501
+ if args.email_types and original_templates is not None:
502
+ generator.email_templates = original_templates
503
+
504
+ except Exception as e:
505
+ print(f"❌ Error generating emails: {e}")
506
+ return 1
507
+
508
+ return 0
509
+
510
+
511
+ if __name__ == "__main__":
512
+ exit(main())