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.
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +222 -223
- amd_gaia-0.15.2.dist-info/RECORD +182 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2132 -2177
- gaia/agents/base/api_agent.py +119 -120
- gaia/agents/base/console.py +1967 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +88 -83
- gaia/agents/blender/__init__.py +7 -0
- gaia/agents/blender/agent.py +553 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +809 -835
- gaia/agents/chat/app.py +1065 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1744 -1729
- gaia/agents/chat/tools/shell_tools.py +437 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2034 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +643 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1504 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1972 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +184 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +428 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5659 -5632
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/installer/__init__.py +23 -0
- gaia/installer/init_command.py +1275 -0
- gaia/installer/lemonade_installer.py +619 -0
- gaia/llm/__init__.py +10 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3421 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +118 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +183 -163
- gaia/talk/app.py +287 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.15.0.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -723
- {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/top_level.txt +0 -0
gaia/eval/email_generator.py
CHANGED
|
@@ -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())
|