amd-gaia 0.14.3__py3-none-any.whl → 0.15.1__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 (181) hide show
  1. {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
  2. amd_gaia-0.15.1.dist-info/RECORD +178 -0
  3. {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
  4. {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/licenses/LICENSE.md +20 -20
  5. gaia/__init__.py +29 -29
  6. gaia/agents/__init__.py +19 -19
  7. gaia/agents/base/__init__.py +9 -9
  8. gaia/agents/base/agent.py +2177 -2177
  9. gaia/agents/base/api_agent.py +120 -120
  10. gaia/agents/base/console.py +1841 -1841
  11. gaia/agents/base/errors.py +237 -237
  12. gaia/agents/base/mcp_agent.py +86 -86
  13. gaia/agents/base/tools.py +83 -83
  14. gaia/agents/blender/agent.py +556 -556
  15. gaia/agents/blender/agent_simple.py +133 -135
  16. gaia/agents/blender/app.py +211 -211
  17. gaia/agents/blender/app_simple.py +41 -41
  18. gaia/agents/blender/core/__init__.py +16 -16
  19. gaia/agents/blender/core/materials.py +506 -506
  20. gaia/agents/blender/core/objects.py +316 -316
  21. gaia/agents/blender/core/rendering.py +225 -225
  22. gaia/agents/blender/core/scene.py +220 -220
  23. gaia/agents/blender/core/view.py +146 -146
  24. gaia/agents/chat/__init__.py +9 -9
  25. gaia/agents/chat/agent.py +835 -835
  26. gaia/agents/chat/app.py +1058 -1058
  27. gaia/agents/chat/session.py +508 -508
  28. gaia/agents/chat/tools/__init__.py +15 -15
  29. gaia/agents/chat/tools/file_tools.py +96 -96
  30. gaia/agents/chat/tools/rag_tools.py +1729 -1729
  31. gaia/agents/chat/tools/shell_tools.py +436 -436
  32. gaia/agents/code/__init__.py +7 -7
  33. gaia/agents/code/agent.py +549 -549
  34. gaia/agents/code/cli.py +377 -0
  35. gaia/agents/code/models.py +135 -135
  36. gaia/agents/code/orchestration/__init__.py +24 -24
  37. gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
  38. gaia/agents/code/orchestration/checklist_generator.py +713 -713
  39. gaia/agents/code/orchestration/factories/__init__.py +9 -9
  40. gaia/agents/code/orchestration/factories/base.py +63 -63
  41. gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
  42. gaia/agents/code/orchestration/factories/python_factory.py +106 -106
  43. gaia/agents/code/orchestration/orchestrator.py +841 -841
  44. gaia/agents/code/orchestration/project_analyzer.py +391 -391
  45. gaia/agents/code/orchestration/steps/__init__.py +67 -67
  46. gaia/agents/code/orchestration/steps/base.py +188 -188
  47. gaia/agents/code/orchestration/steps/error_handler.py +314 -314
  48. gaia/agents/code/orchestration/steps/nextjs.py +828 -828
  49. gaia/agents/code/orchestration/steps/python.py +307 -307
  50. gaia/agents/code/orchestration/template_catalog.py +469 -469
  51. gaia/agents/code/orchestration/workflows/__init__.py +14 -14
  52. gaia/agents/code/orchestration/workflows/base.py +80 -80
  53. gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
  54. gaia/agents/code/orchestration/workflows/python.py +94 -94
  55. gaia/agents/code/prompts/__init__.py +11 -11
  56. gaia/agents/code/prompts/base_prompt.py +77 -77
  57. gaia/agents/code/prompts/code_patterns.py +2036 -2036
  58. gaia/agents/code/prompts/nextjs_prompt.py +40 -40
  59. gaia/agents/code/prompts/python_prompt.py +109 -109
  60. gaia/agents/code/schema_inference.py +365 -365
  61. gaia/agents/code/system_prompt.py +41 -41
  62. gaia/agents/code/tools/__init__.py +42 -42
  63. gaia/agents/code/tools/cli_tools.py +1138 -1138
  64. gaia/agents/code/tools/code_formatting.py +319 -319
  65. gaia/agents/code/tools/code_tools.py +769 -769
  66. gaia/agents/code/tools/error_fixing.py +1347 -1347
  67. gaia/agents/code/tools/external_tools.py +180 -180
  68. gaia/agents/code/tools/file_io.py +845 -845
  69. gaia/agents/code/tools/prisma_tools.py +190 -190
  70. gaia/agents/code/tools/project_management.py +1016 -1016
  71. gaia/agents/code/tools/testing.py +321 -321
  72. gaia/agents/code/tools/typescript_tools.py +122 -122
  73. gaia/agents/code/tools/validation_parsing.py +461 -461
  74. gaia/agents/code/tools/validation_tools.py +806 -806
  75. gaia/agents/code/tools/web_dev_tools.py +1758 -1758
  76. gaia/agents/code/validators/__init__.py +16 -16
  77. gaia/agents/code/validators/antipattern_checker.py +241 -241
  78. gaia/agents/code/validators/ast_analyzer.py +197 -197
  79. gaia/agents/code/validators/requirements_validator.py +145 -145
  80. gaia/agents/code/validators/syntax_validator.py +171 -171
  81. gaia/agents/docker/__init__.py +7 -7
  82. gaia/agents/docker/agent.py +642 -642
  83. gaia/agents/emr/__init__.py +8 -8
  84. gaia/agents/emr/agent.py +1506 -1506
  85. gaia/agents/emr/cli.py +1322 -1322
  86. gaia/agents/emr/constants.py +475 -475
  87. gaia/agents/emr/dashboard/__init__.py +4 -4
  88. gaia/agents/emr/dashboard/server.py +1974 -1974
  89. gaia/agents/jira/__init__.py +11 -11
  90. gaia/agents/jira/agent.py +894 -894
  91. gaia/agents/jira/jql_templates.py +299 -299
  92. gaia/agents/routing/__init__.py +7 -7
  93. gaia/agents/routing/agent.py +567 -570
  94. gaia/agents/routing/system_prompt.py +75 -75
  95. gaia/agents/summarize/__init__.py +11 -0
  96. gaia/agents/summarize/agent.py +885 -0
  97. gaia/agents/summarize/prompts.py +129 -0
  98. gaia/api/__init__.py +23 -23
  99. gaia/api/agent_registry.py +238 -238
  100. gaia/api/app.py +305 -305
  101. gaia/api/openai_server.py +575 -575
  102. gaia/api/schemas.py +186 -186
  103. gaia/api/sse_handler.py +373 -373
  104. gaia/apps/__init__.py +4 -4
  105. gaia/apps/llm/__init__.py +6 -6
  106. gaia/apps/llm/app.py +173 -169
  107. gaia/apps/summarize/app.py +116 -633
  108. gaia/apps/summarize/html_viewer.py +133 -133
  109. gaia/apps/summarize/pdf_formatter.py +284 -284
  110. gaia/audio/__init__.py +2 -2
  111. gaia/audio/audio_client.py +439 -439
  112. gaia/audio/audio_recorder.py +269 -269
  113. gaia/audio/kokoro_tts.py +599 -599
  114. gaia/audio/whisper_asr.py +432 -432
  115. gaia/chat/__init__.py +16 -16
  116. gaia/chat/app.py +430 -430
  117. gaia/chat/prompts.py +522 -522
  118. gaia/chat/sdk.py +1228 -1225
  119. gaia/cli.py +5481 -5621
  120. gaia/database/__init__.py +10 -10
  121. gaia/database/agent.py +176 -176
  122. gaia/database/mixin.py +290 -290
  123. gaia/database/testing.py +64 -64
  124. gaia/eval/batch_experiment.py +2332 -2332
  125. gaia/eval/claude.py +542 -542
  126. gaia/eval/config.py +37 -37
  127. gaia/eval/email_generator.py +512 -512
  128. gaia/eval/eval.py +3179 -3179
  129. gaia/eval/groundtruth.py +1130 -1130
  130. gaia/eval/transcript_generator.py +582 -582
  131. gaia/eval/webapp/README.md +167 -167
  132. gaia/eval/webapp/package-lock.json +875 -875
  133. gaia/eval/webapp/package.json +20 -20
  134. gaia/eval/webapp/public/app.js +3402 -3402
  135. gaia/eval/webapp/public/index.html +87 -87
  136. gaia/eval/webapp/public/styles.css +3661 -3661
  137. gaia/eval/webapp/server.js +415 -415
  138. gaia/eval/webapp/test-setup.js +72 -72
  139. gaia/llm/__init__.py +9 -2
  140. gaia/llm/base_client.py +60 -0
  141. gaia/llm/exceptions.py +12 -0
  142. gaia/llm/factory.py +70 -0
  143. gaia/llm/lemonade_client.py +3236 -3221
  144. gaia/llm/lemonade_manager.py +294 -294
  145. gaia/llm/providers/__init__.py +9 -0
  146. gaia/llm/providers/claude.py +108 -0
  147. gaia/llm/providers/lemonade.py +120 -0
  148. gaia/llm/providers/openai_provider.py +79 -0
  149. gaia/llm/vlm_client.py +382 -382
  150. gaia/logger.py +189 -189
  151. gaia/mcp/agent_mcp_server.py +245 -245
  152. gaia/mcp/blender_mcp_client.py +138 -138
  153. gaia/mcp/blender_mcp_server.py +648 -648
  154. gaia/mcp/context7_cache.py +332 -332
  155. gaia/mcp/external_services.py +518 -518
  156. gaia/mcp/mcp_bridge.py +811 -550
  157. gaia/mcp/servers/__init__.py +6 -6
  158. gaia/mcp/servers/docker_mcp.py +83 -83
  159. gaia/perf_analysis.py +361 -0
  160. gaia/rag/__init__.py +10 -10
  161. gaia/rag/app.py +293 -293
  162. gaia/rag/demo.py +304 -304
  163. gaia/rag/pdf_utils.py +235 -235
  164. gaia/rag/sdk.py +2194 -2194
  165. gaia/security.py +163 -163
  166. gaia/talk/app.py +289 -289
  167. gaia/talk/sdk.py +538 -538
  168. gaia/testing/__init__.py +87 -87
  169. gaia/testing/assertions.py +330 -330
  170. gaia/testing/fixtures.py +333 -333
  171. gaia/testing/mocks.py +493 -493
  172. gaia/util.py +46 -46
  173. gaia/utils/__init__.py +33 -33
  174. gaia/utils/file_watcher.py +675 -675
  175. gaia/utils/parsing.py +223 -223
  176. gaia/version.py +100 -100
  177. amd_gaia-0.14.3.dist-info/RECORD +0 -168
  178. gaia/agents/code/app.py +0 -266
  179. gaia/llm/llm_client.py +0 -729
  180. {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
  181. {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.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())