dhisana 0.0.1.dev271__tar.gz → 0.0.1.dev273__tar.gz

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 (122) hide show
  1. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/PKG-INFO +1 -1
  2. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/setup.py +1 -1
  3. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/generate_email.py +209 -99
  4. dhisana-0.0.1.dev273/src/dhisana/utils/generate_linkedin_connect_message.py +329 -0
  5. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/proxy_curl_tools.py +29 -11
  6. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana.egg-info/PKG-INFO +1 -1
  7. dhisana-0.0.1.dev271/src/dhisana/utils/generate_linkedin_connect_message.py +0 -231
  8. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/README.md +0 -0
  9. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/pyproject.toml +0 -0
  10. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/setup.cfg +0 -0
  11. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/__init__.py +0 -0
  12. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/cli/__init__.py +0 -0
  13. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/cli/cli.py +0 -0
  14. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/cli/datasets.py +0 -0
  15. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/cli/models.py +0 -0
  16. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/cli/predictions.py +0 -0
  17. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/schemas/__init__.py +0 -0
  18. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/schemas/common.py +0 -0
  19. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/schemas/sales.py +0 -0
  20. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/ui/__init__.py +0 -0
  21. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/ui/components.py +0 -0
  22. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/__init__.py +0 -0
  23. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/add_mapping.py +0 -0
  24. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/agent_tools.py +0 -0
  25. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/apollo_tools.py +0 -0
  26. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/assistant_tool_tag.py +0 -0
  27. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/built_with_api_tools.py +0 -0
  28. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/cache_output_tools.py +0 -0
  29. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/cache_output_tools_local.py +0 -0
  30. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/check_email_validity_tools.py +0 -0
  31. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/check_for_intent_signal.py +0 -0
  32. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/check_linkedin_url_validity.py +0 -0
  33. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/clay_tools.py +0 -0
  34. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/clean_properties.py +0 -0
  35. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/company_utils.py +0 -0
  36. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/compose_salesnav_query.py +0 -0
  37. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/compose_search_query.py +0 -0
  38. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/compose_three_step_workflow.py +0 -0
  39. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/composite_tools.py +0 -0
  40. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/dataframe_tools.py +0 -0
  41. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/domain_parser.py +0 -0
  42. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/email_body_utils.py +0 -0
  43. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/email_parse_helpers.py +0 -0
  44. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/email_provider.py +0 -0
  45. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/enrich_lead_information.py +0 -0
  46. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/extract_email_content_for_llm.py +0 -0
  47. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/fetch_openai_config.py +0 -0
  48. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/field_validators.py +0 -0
  49. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/g2_tools.py +0 -0
  50. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/generate_content.py +0 -0
  51. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/generate_custom_message.py +0 -0
  52. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/generate_email_response.py +0 -0
  53. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/generate_flow.py +0 -0
  54. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/generate_leads_salesnav.py +0 -0
  55. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/generate_linkedin_response_message.py +0 -0
  56. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/generate_structured_output_internal.py +0 -0
  57. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/google_custom_search.py +0 -0
  58. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/google_oauth_tools.py +0 -0
  59. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/google_workspace_tools.py +0 -0
  60. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/hubspot_clearbit.py +0 -0
  61. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/hubspot_crm_tools.py +0 -0
  62. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/instantly_tools.py +0 -0
  63. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/linkedin_crawler.py +0 -0
  64. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/lusha_tools.py +0 -0
  65. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/mailgun_tools.py +0 -0
  66. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/mailreach_tools.py +0 -0
  67. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/microsoft365_tools.py +0 -0
  68. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/openai_assistant_and_file_utils.py +0 -0
  69. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/openai_helpers.py +0 -0
  70. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/openapi_spec_to_tools.py +0 -0
  71. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/openapi_tool/__init__.py +0 -0
  72. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/openapi_tool/api_models.py +0 -0
  73. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/openapi_tool/convert_openai_spec_to_tool.py +0 -0
  74. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/openapi_tool/openapi_tool.py +0 -0
  75. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/parse_linkedin_messages_txt.py +0 -0
  76. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/profile.py +0 -0
  77. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/proxycurl_search_leads.py +0 -0
  78. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/python_function_to_tools.py +0 -0
  79. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/research_lead.py +0 -0
  80. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/sales_navigator_crawler.py +0 -0
  81. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/salesforce_crm_tools.py +0 -0
  82. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/search_router.py +0 -0
  83. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/search_router_jobs.py +0 -0
  84. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/sendgrid_tools.py +0 -0
  85. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/serarch_router_local_business.py +0 -0
  86. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/serpapi_additional_tools.py +0 -0
  87. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/serpapi_google_jobs.py +0 -0
  88. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/serpapi_google_search.py +0 -0
  89. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/serpapi_local_business_search.py +0 -0
  90. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/serpapi_search_tools.py +0 -0
  91. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/serperdev_google_jobs.py +0 -0
  92. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/serperdev_local_business.py +0 -0
  93. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/serperdev_search.py +0 -0
  94. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/smtp_email_tools.py +0 -0
  95. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/test_connect.py +0 -0
  96. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/trasform_json.py +0 -0
  97. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/web_download_parse_tools.py +0 -0
  98. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/workflow_code_model.py +0 -0
  99. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/utils/zoominfo_tools.py +0 -0
  100. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/workflow/__init__.py +0 -0
  101. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/workflow/agent.py +0 -0
  102. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/workflow/flow.py +0 -0
  103. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/workflow/task.py +0 -0
  104. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana/workflow/test.py +0 -0
  105. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana.egg-info/SOURCES.txt +0 -0
  106. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana.egg-info/dependency_links.txt +0 -0
  107. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana.egg-info/entry_points.txt +0 -0
  108. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana.egg-info/requires.txt +0 -0
  109. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/src/dhisana.egg-info/top_level.txt +0 -0
  110. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_agent_tools.py +0 -0
  111. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_apollo_company_search.py +0 -0
  112. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_apollo_lead_search.py +0 -0
  113. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_connectivity.py +0 -0
  114. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_email_body_utils.py +0 -0
  115. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_google_document.py +0 -0
  116. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_hubspot_call_logs.py +0 -0
  117. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_linkedin_serper.py +0 -0
  118. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_mailreach.py +0 -0
  119. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_mcp_connectivity.py +0 -0
  120. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_proxycurl_get_company_search_id.py +0 -0
  121. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_proxycurl_job_count.py +0 -0
  122. {dhisana-0.0.1.dev271 → dhisana-0.0.1.dev273}/tests/test_structured_output_with_mcp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dhisana
3
- Version: 0.0.1.dev271
3
+ Version: 0.0.1.dev273
4
4
  Summary: A Python SDK for Dhisana AI Platform
5
5
  Home-page: https://github.com/dhisana-ai/dhisana-python-sdk
6
6
  Author: Admin
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='dhisana',
5
- version='0.0.1-dev271',
5
+ version='0.0.1-dev273',
6
6
  description='A Python SDK for Dhisana AI Platform',
7
7
  author='Admin',
8
8
  author_email='contact@dhisana.ai',
@@ -15,7 +15,25 @@ from pydantic import BaseModel, ConfigDict
15
15
  # ---------------------------------------------------------------------------------------
16
16
  # CONSTANTS
17
17
  # ---------------------------------------------------------------------------------------
18
- DEFAULT_OPENAI_MODEL = "gpt-4.1"
18
+ DEFAULT_OPENAI_MODEL = "gpt-4.1" # Larger context length - used for generating instructions
19
+ DEFAULT_EMAIL_GEN_MODEL = "gpt-5.1-chat" # Better content generation - used for generating email
20
+
21
+ # -----------------------------------------------------------------------------
22
+ # Email Generation Instructions schema (intermediate step)
23
+ # -----------------------------------------------------------------------------
24
+ class EmailGenerationInstructions(BaseModel):
25
+ """Instructions generated by the first step to guide email generation."""
26
+ recipient_context: str # Key info about the lead/recipient
27
+ sender_context: str # Key info about the sender
28
+ campaign_context: str # Product, value prop, pain points, etc.
29
+ conversation_context: str # Summary of any existing conversation
30
+ tone_and_style: str # How the email should sound
31
+ key_points_to_include: str # Main points to cover in the email
32
+ call_to_action: str # What action to request
33
+ formatting_requirements: str # HTML vs plain text, signature format, etc.
34
+
35
+ model_config = ConfigDict(extra="forbid")
36
+
19
37
 
20
38
  # -----------------------------------------------------------------------------
21
39
  # Email Copy schema
@@ -68,28 +86,18 @@ FRAMEWORK_VARIATIONS = [
68
86
  ]
69
87
 
70
88
  # -----------------------------------------------------------------------------
71
- # Core function to generate an email copy
89
+ # Step 1: Generate email instructions using gpt-4.1 (larger context)
72
90
  # -----------------------------------------------------------------------------
73
- async def generate_personalized_email_copy(
91
+ async def generate_email_instructions(
74
92
  email_context: ContentGenerationContext,
75
93
  message_instructions: MessageGenerationInstructions,
76
94
  variation_text: str,
77
95
  tool_config: Optional[List[Dict]] = None,
78
- ) -> dict:
96
+ ) -> EmailGenerationInstructions:
79
97
  """
80
- Generate a personalized email using the provided context and instructions.
81
-
82
- Steps:
83
- 1. Build a prompt referencing 6 main info:
84
- (a) Lead Info
85
- (b) Sender Info
86
- (c) Campaign Info
87
- (d) Messaging Instructions
88
- (e) Additional Data (vector store) if any
89
- (f) Current Conversation Context
90
- 2. Generate an initial draft with or without vector store usage.
91
- 3. Optionally refine if a vector store was used and user instructions were not provided.
92
- 4. Return the final subject & body.
98
+ Step 1 of 2-step email generation process.
99
+ Uses gpt-4.1 (larger context length) to process all context and generate
100
+ concise instructions for email generation.
93
101
  """
94
102
  cleaned_context = cleanup_email_context(email_context)
95
103
 
@@ -110,110 +118,212 @@ async def generate_personalized_email_copy(
110
118
  conversation_data = cleaned_context.current_conversation_context or ConversationContext()
111
119
 
112
120
  html_note = (
113
- f"\n Provide the HTML body using this guidance/template when possible:\n {message_instructions.html_template}"
121
+ f"\n HTML Template to follow: {message_instructions.html_template}"
114
122
  if getattr(message_instructions, "html_template", None)
115
123
  else ""
116
124
  )
117
- important_requirements = """
118
- IMPORTANT REQUIREMENTS:
125
+
126
+ allow_html = getattr(message_instructions, "allow_html", False)
127
+ format_requirement = "HTML email with body_html field" if allow_html else "Plain text only (no HTML tags)"
128
+
129
+ # Construct the prompt for generating instructions
130
+ instructions_prompt = f"""
131
+ Generate email writing instructions based on the following business context.
132
+
133
+ LEAD INFORMATION:
134
+ {lead_data.dict()}
135
+
136
+ SENDER INFORMATION:
137
+ Full Name: {sender_data.sender_full_name or ''}
138
+ First Name: {sender_data.sender_first_name or ''}
139
+ Last Name: {sender_data.sender_last_name or ''}
140
+ Bio: {sender_data.sender_bio or ''}
141
+ Appointment Booking URL: {sender_data.sender_appointment_booking_url or ''}
142
+
143
+ CAMPAIGN INFORMATION:
144
+ Product Name: {campaign_data.product_name or ''}
145
+ Value Proposition: {campaign_data.value_prop or ''}
146
+ Call To Action: {campaign_data.call_to_action or ''}
147
+ Pain Points: {campaign_data.pain_points or []}
148
+ Proof Points: {campaign_data.proof_points or []}
149
+ Email Triage Guidelines: {campaign_data.email_triage_guidelines or ''}
150
+ LinkedIn Triage Guidelines: {campaign_data.linkedin_triage_guidelines or ''}
151
+
152
+ MESSAGING FRAMEWORK:
153
+ {selected_instructions}{html_note}
154
+
155
+ CONVERSATION HISTORY:
156
+ Email Thread: {conversation_data.current_email_thread or ''}
157
+ LinkedIn Thread: {conversation_data.current_linkedin_thread or ''}
158
+
159
+ OUTPUT FIELDS:
160
+ - recipient_context: Key details about the lead (name, company, role, relevant background)
161
+ - sender_context: Sender name and relevant info for signature
162
+ - campaign_context: Product or service being promoted, value proposition, key pain points
163
+ - conversation_context: Summary of any prior conversation if exists
164
+ - tone_and_style: Professional, friendly, etc.
165
+ - key_points_to_include: Specific points to mention in the email
166
+ - call_to_action: What action the recipient should take
167
+ - formatting_requirements: Format is {format_requirement}. Salutation format, signature requirements.
168
+ """
169
+
170
+ # Check if a vector store is available
171
+ vector_store_id = (email_context.external_known_data.external_openai_vector_store_id
172
+ if email_context.external_known_data else None)
173
+
174
+ instructions_response = None
175
+ instructions_status = ""
176
+
177
+ # Generate instructions using gpt-4.1 (larger context)
178
+ if vector_store_id:
179
+ instructions_response, instructions_status = await get_structured_output_with_assistant_and_vector_store(
180
+ prompt=instructions_prompt,
181
+ response_format=EmailGenerationInstructions,
182
+ vector_store_id=vector_store_id,
183
+ model=DEFAULT_OPENAI_MODEL,
184
+ tool_config=tool_config,
185
+ use_cache=email_context.message_instructions.use_cache if email_context.message_instructions else True
186
+ )
187
+ else:
188
+ instructions_response, instructions_status = await get_structured_output_internal(
189
+ prompt=instructions_prompt,
190
+ response_format=EmailGenerationInstructions,
191
+ model=DEFAULT_OPENAI_MODEL,
192
+ tool_config=tool_config,
193
+ use_cache=email_context.message_instructions.use_cache if email_context.message_instructions else True
194
+ )
195
+
196
+ if instructions_status != "SUCCESS":
197
+ raise Exception("Error: Could not generate email instructions in step 1.")
198
+
199
+ return instructions_response
200
+
201
+
202
+ # -----------------------------------------------------------------------------
203
+ # Step 2: Generate email using gpt-5.1-chat (better content generation)
204
+ # -----------------------------------------------------------------------------
205
+ async def generate_email_from_instructions(
206
+ instructions: EmailGenerationInstructions,
207
+ message_instructions: MessageGenerationInstructions,
208
+ tool_config: Optional[List[Dict]] = None,
209
+ use_cache: bool = True,
210
+ ) -> EmailCopy:
211
+ """
212
+ Step 2 of 2-step email generation process.
213
+ Uses gpt-5.1-chat (better content generation) to generate the email
214
+ from the condensed instructions.
215
+ """
216
+ allow_html = getattr(message_instructions, "allow_html", False)
217
+
218
+ if allow_html:
219
+ output_requirements = """
220
+ OUTPUT REQUIREMENTS:
119
221
  - Output must be JSON with "subject", "body", and "body_html" fields.
120
222
  - "body_html" should be clean HTML suitable for email (no external assets), inline styles welcome.
121
223
  - "body" must be the plain-text equivalent of "body_html".
122
- - Keep it concise and relevant. No placeholders or extra instructions.
123
- - Do not include PII or internal references, guids or content identifiers in the email.
124
- - Use conversational names for company/person placeholders when provided.
125
- - Email has salutation Hi <First Name>, unless otherwise specified.
126
- - Make sure the signature in body has the sender_first_name correct and in the format the user specified.
127
- - Do Not Make up information. use the information provided in the context and instructions only.
128
- - Do Not use em dash in the generated output.
129
- """
130
- if not getattr(message_instructions, "allow_html", False):
131
- important_requirements = """
132
- IMPORTANT REQUIREMENTS:
224
+ """
225
+ else:
226
+ output_requirements = """
227
+ OUTPUT REQUIREMENTS:
133
228
  - Output must be JSON with "subject" and "body" fields only.
134
229
  - In the subject or body DO NOT include any HTML tags like <a>, <b>, <i>, etc.
135
230
  - The body and subject should be in plain text.
136
- - If there is a link provided in email use it as is. dont wrap it in any HTML tags.
137
- - Keep it concise and relevant. No placeholders or extra instructions.
138
- - Do not include PII or internal references, guids or content identifiers in the email.
139
- - User conversational name for company name if used.
140
- - Email has saluation Hi <First Name>, unless otherwise specified.
141
- - <First Name> is the first name of the lead. Its conversational name. It does not have any special characters or spaces.
142
- - Make sure the signature in body has the sender_first_name is correct and in the format user has specified.
143
- - Do Not Make up information. use the information provided in the context and instructions only.
144
- - Make sure the body text is well-formatted and that newline and carriage-return characters are correctly present and preserved in the message body.
145
- - Do Not use em dash in the generated output.
146
- """
231
+ - If there is a link provided, use it as is without wrapping in any HTML tags.
232
+ """
233
+
234
+ email_prompt = f"""
235
+ Generate a personalized business email based on these specifications:
147
236
 
148
- # Construct the consolidated prompt
149
- initial_prompt = f"""
150
- Hi AI Assistant,
237
+ RECIPIENT CONTEXT:
238
+ {instructions.recipient_context}
151
239
 
152
- Below is the context in 6 main sections. Use it to craft a concise, professional email:
240
+ SENDER CONTEXT:
241
+ {instructions.sender_context}
153
242
 
154
- 1) Lead Information:
155
- {lead_data.dict()}
243
+ CAMPAIGN CONTEXT:
244
+ {instructions.campaign_context}
156
245
 
157
- 2) Sender Information:
158
- Full Name: {sender_data.sender_full_name or ''}
159
- First Name: {sender_data.sender_first_name or ''}
160
- Last Name: {sender_data.sender_last_name or ''}
161
- Bio: {sender_data.sender_bio or ''}
162
- Appointment Booking URL: {sender_data.sender_appointment_booking_url or ''}
246
+ CONVERSATION CONTEXT:
247
+ {instructions.conversation_context}
163
248
 
164
- 3) Campaign Information:
165
- Product Name: {campaign_data.product_name or ''}
166
- Value Proposition: {campaign_data.value_prop or ''}
167
- Call To Action: {campaign_data.call_to_action or ''}
168
- Pain Points: {campaign_data.pain_points or []}
169
- Proof Points: {campaign_data.proof_points or []}
170
- Triage Guidelines (Email): {campaign_data.email_triage_guidelines or ''}
171
- Triage Guidelines (LinkedIn): {campaign_data.linkedin_triage_guidelines or ''}
249
+ TONE AND STYLE:
250
+ {instructions.tone_and_style}
172
251
 
173
- 4) Messaging Instructions (template/framework):
174
- {selected_instructions}{html_note}
252
+ KEY POINTS TO INCLUDE:
253
+ {instructions.key_points_to_include}
175
254
 
176
- 5) External Data / Vector Store:
177
- (I will be provided with file_search tool if present.)
255
+ CALL TO ACTION:
256
+ {instructions.call_to_action}
178
257
 
179
- 6) Current Conversation Context:
180
- Email Thread: {conversation_data.current_email_thread or ''}
181
- LinkedIn Thread: {conversation_data.current_linkedin_thread or ''}
258
+ FORMATTING REQUIREMENTS:
259
+ {instructions.formatting_requirements}
182
260
 
183
- {important_requirements}
261
+ {output_requirements}
262
+
263
+ ADDITIONAL REQUIREMENTS:
264
+ - Keep it concise and relevant. No placeholders.
265
+ - Do not include internal references or content identifiers in the email.
266
+ - Use only the information provided.
267
+ - Ensure the body text is well-formatted with proper newlines.
268
+ - Do not use em dash in the output.
184
269
  """
185
270
 
186
- # Check if a vector store is available
187
- vector_store_id = (email_context.external_known_data.external_openai_vector_store_id
188
- if email_context.external_known_data else None)
271
+ # Generate email using gpt-5.1-chat (better content generation)
272
+ email_response, email_status = await get_structured_output_internal(
273
+ prompt=email_prompt,
274
+ response_format=EmailCopy,
275
+ model=DEFAULT_EMAIL_GEN_MODEL,
276
+ tool_config=tool_config,
277
+ use_cache=use_cache
278
+ )
189
279
 
190
- initial_response = None
191
- initial_status = ""
280
+ if email_status != "SUCCESS":
281
+ raise Exception("Error: Could not generate email in step 2.")
282
+
283
+ return email_response
192
284
 
193
- # Generate initial draft
194
- if vector_store_id:
195
- initial_response, initial_status = await get_structured_output_with_assistant_and_vector_store(
196
- prompt=initial_prompt,
197
- response_format=EmailCopy,
198
- vector_store_id=vector_store_id,
199
- model=DEFAULT_OPENAI_MODEL,
200
- tool_config=tool_config,
201
- use_cache=email_context.message_instructions.use_cache if email_context.message_instructions else True
202
- )
203
- else:
204
- # Otherwise, generate the initial draft internally
205
- initial_response, initial_status = await get_structured_output_internal(
206
- prompt=initial_prompt,
207
- response_format=EmailCopy,
208
- model=DEFAULT_OPENAI_MODEL,
209
- tool_config=tool_config,
210
- use_cache=email_context.message_instructions.use_cache if email_context.message_instructions else True
211
- )
212
285
 
213
- if initial_status != "SUCCESS":
214
- raise Exception("Error: Could not generate initial draft for the personalized email.")
215
- plain_body = initial_response.body
216
- html_body = getattr(initial_response, "body_html", None)
286
+ # -----------------------------------------------------------------------------
287
+ # Core function to generate an email copy (2-step process)
288
+ # -----------------------------------------------------------------------------
289
+ async def generate_personalized_email_copy(
290
+ email_context: ContentGenerationContext,
291
+ message_instructions: MessageGenerationInstructions,
292
+ variation_text: str,
293
+ tool_config: Optional[List[Dict]] = None,
294
+ ) -> dict:
295
+ """
296
+ Generate a personalized email using a 2-step process:
297
+
298
+ Step 1: Use gpt-4.1 (larger context length) to process all context and generate
299
+ concise instructions for email generation.
300
+
301
+ Step 2: Use gpt-5.1-chat (better content generation) to generate the actual
302
+ email from the condensed instructions.
303
+
304
+ This approach leverages gpt-4.1's larger context window to process extensive
305
+ lead/campaign data, and gpt-5.1-chat's superior content generation for the
306
+ final email output.
307
+ """
308
+ # Step 1: Generate instructions using gpt-4.1
309
+ instructions = await generate_email_instructions(
310
+ email_context=email_context,
311
+ message_instructions=message_instructions,
312
+ variation_text=variation_text,
313
+ tool_config=tool_config,
314
+ )
315
+
316
+ # Step 2: Generate email using gpt-5.1-chat
317
+ use_cache = email_context.message_instructions.use_cache if email_context.message_instructions else True
318
+ email_response = await generate_email_from_instructions(
319
+ instructions=instructions,
320
+ message_instructions=message_instructions,
321
+ tool_config=tool_config,
322
+ use_cache=use_cache,
323
+ )
324
+
325
+ plain_body = email_response.body
326
+ html_body = getattr(email_response, "body_html", None)
217
327
  if not plain_body and html_body:
218
328
  plain_body = _html_to_plain_text(html_body)
219
329
 
@@ -225,7 +335,7 @@ async def generate_personalized_email_copy(
225
335
  receiver_name=email_context.lead_info.full_name or "",
226
336
  receiver_email=email_context.lead_info.email or "",
227
337
  iso_datetime=datetime.utcnow().isoformat(),
228
- subject=initial_response.subject,
338
+ subject=email_response.subject,
229
339
  body=plain_body,
230
340
  html_body=html_body if getattr(message_instructions, "allow_html", False) else None,
231
341
  )