Skip to content

Commit 14182e9

Browse files
committed
support batch
1 parent 5694019 commit 14182e9

File tree

5 files changed

+168
-5
lines changed

5 files changed

+168
-5
lines changed

.cursorrules

+2-2
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ class RetryNode(Node):
715715
To **gracefully handle** the exception (after all retries) rather than raising it, override:
716716

717717
```python
718-
def exec_fallback(self, shared, prep_res, exc):
718+
def exec_fallback(self, prep_res, exc):
719719
raise exc
720720
```
721721

@@ -735,7 +735,7 @@ class SummarizeFile(Node):
735735
summary = call_llm(prompt) # might fail
736736
return summary
737737

738-
def exec_fallback(self, shared, prep_res, exc):
738+
def exec_fallback(self, prep_res, exc):
739739
# Provide a simple fallback instead of crashing
740740
return "There was an error processing your request."
741741

flow.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ def exec(self, url):
5757
content = get_html_content(url)
5858
return {"url": url, "content": content}
5959

60+
def exec_fallback(self, prep_res, exc):
61+
# This is called after all retries are exhausted
62+
url = prep_res["url"] # Extract URL from the prep_res input pair
63+
logger.error(f"Failed to retrieve content from {url} after all retries: {exc}")
64+
return {"url": url, "content": None}
65+
6066
def post(self, shared, prep_res, exec_res_list):
6167
# Store only non-empty webpage contents
6268
valid_contents = [res for res in exec_res_list if res["content"]]
@@ -97,9 +103,11 @@ def exec(self, url_content_pair):
97103
```yaml
98104
factors:
99105
- name: "factor_name"
106+
action: "action to take"
100107
actionable: true/false
101108
details: "supporting details if actionable"
102109
- name: "another_factor"
110+
action: "action to take"
103111
actionable: true/false
104112
details: "supporting details if actionable"
105113
```"""
@@ -115,7 +123,7 @@ def exec(self, url_content_pair):
115123
logger.debug(f"Successfully parsed YAML from LLM response for {url}")
116124
return {"url": url, "analysis": analysis}
117125

118-
def exec_fallback(self, shared, prep_res, exc):
126+
def exec_fallback(self, prep_res, exc):
119127
# This is called after all retries are exhausted
120128
url = prep_res["url"] # Extract URL from the prep_res input pair
121129
logger.error(f"Failed to analyze content from {url} after all retries: {exc}")
@@ -233,8 +241,8 @@ def post(self, shared, prep_res, exec_res):
233241
logger.info("Initializing flow nodes")
234242
search_node = SearchPersonNode()
235243
content_node = ContentRetrievalNode()
236-
analyze_node = AnalyzeResultsBatchNode(max_retries=3) # Retry up to 3 times before using fallback
237-
draft_node = DraftOpeningNode()
244+
analyze_node = AnalyzeResultsBatchNode(max_retries=2, wait=10) # Retry up to 3 times before using fallback
245+
draft_node = DraftOpeningNode(max_retries=3, wait=10)
238246

239247
# Connect nodes in the flow
240248
logger.info("Connecting nodes in personalization flow")

input.csv

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
first_name,last_name,keywords
2+
Elon,Musk,Tesla SpaceX entrepreneur
3+
Sundar,Pichai,Google CEO tech
4+
Tim,Cook,Apple iPhone CEO

main_batch.py

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import csv
2+
import argparse
3+
import os
4+
import json
5+
from flow import cold_outreach_flow
6+
7+
def main():
8+
"""
9+
Batch processing script for the Cold Outreach Opener Generator.
10+
Processes multiple people from a CSV file and generates personalized opening messages.
11+
"""
12+
# Parse command line arguments
13+
parser = argparse.ArgumentParser(description='Process multiple cold outreach targets from a CSV file.')
14+
parser.add_argument('--input', default='input.csv', help='Input CSV file (default: input.csv)')
15+
parser.add_argument('--output', default='output.csv', help='Output CSV file (default: output.csv)')
16+
args = parser.parse_args()
17+
18+
# Check if input file exists
19+
if not os.path.exists(args.input):
20+
print(f"Error: Input file '{args.input}' not found.")
21+
return
22+
23+
# Hardcoded personalization factors
24+
personalization_factors = [
25+
{
26+
"name": "personal_connection",
27+
"description": "Check if target person has Columbia University affiliation",
28+
"action": "If they do, mention shared connection to Columbia"
29+
},
30+
{
31+
"name": "recent_promotion",
32+
"description": "Check if target person was recently promoted",
33+
"action": "If they were, congratulate them on their new role"
34+
},
35+
{
36+
"name": "recent_talks",
37+
"description": "Check if target person gave talks recently",
38+
"action": "If they did, mention enjoying their insights"
39+
}
40+
]
41+
42+
# Extract factor names for later use
43+
factor_names = [factor["name"] for factor in personalization_factors]
44+
print(factor_names)
45+
46+
# Hardcoded style preference
47+
style = "Be concise, specific, and casual in 30 words or less. For example: 'Heard about your talk on the future of space exploration—loved your take on creating a more sustainable path for space travel.'"
48+
49+
# Read input CSV
50+
input_data = []
51+
with open(args.input, 'r', newline='', encoding='utf-8') as csvfile:
52+
reader = csv.DictReader(csvfile)
53+
for row in reader:
54+
if 'first_name' in row and 'last_name' in row and 'keywords' in row:
55+
input_data.append(row)
56+
57+
if not input_data:
58+
print(f"Error: No valid data found in '{args.input}'. CSV should have columns: first_name, last_name, keywords")
59+
return
60+
61+
# Process each person
62+
results = []
63+
total = len(input_data)
64+
for i, person in enumerate(input_data, 1):
65+
print(f"\nProcessing {i}/{total}: {person['first_name']} {person['last_name']}")
66+
67+
# Prepare input data
68+
shared = {
69+
"input": {
70+
"first_name": person['first_name'],
71+
"last_name": person['last_name'],
72+
"keywords": person['keywords'],
73+
"personalization_factors": personalization_factors,
74+
"style": style
75+
}
76+
}
77+
78+
# Run the flow
79+
try:
80+
cold_outreach_flow.run(shared)
81+
82+
# Prepare result row
83+
# Extract URLs as comma-separated string
84+
urls = [result.get("link", "") for result in shared.get("search_results", []) if "link" in result]
85+
url_string = ",".join(urls)
86+
87+
# Extract personalization details
88+
personalization_data = {}
89+
for factor_name, details in shared.get("personalization", {}).items():
90+
personalization_data[factor_name + "_actionable"] = str(details.get("actionable", False))
91+
personalization_data[factor_name + "_details"] = details.get("details", "")
92+
93+
result = {
94+
'first_name': person['first_name'],
95+
'last_name': person['last_name'],
96+
'keywords': person['keywords'],
97+
'opening_message': shared.get("output", {}).get("opening_message", ""),
98+
'search_results': url_string,
99+
**personalization_data # Add all personalization fields
100+
}
101+
results.append(result)
102+
103+
# Display the result
104+
print(f"Generated opener: {result['opening_message']}")
105+
106+
except Exception as e:
107+
print(f"Error processing {person['first_name']} {person['last_name']}: {str(e)}")
108+
# Add failed row with error message
109+
results.append({
110+
'first_name': person['first_name'],
111+
'last_name': person['last_name'],
112+
'keywords': person['keywords'],
113+
'opening_message': f"ERROR: {str(e)}",
114+
'search_results': "",
115+
# Include empty personalization fields for consistency with successful rows
116+
**{f"{factor}_actionable": "False" for factor in factor_names},
117+
**{f"{factor}_details": "" for factor in factor_names}
118+
})
119+
120+
# Write results to output CSV
121+
if results:
122+
# Determine all field names by examining all the result rows
123+
all_fields = set()
124+
for result in results:
125+
all_fields.update(result.keys())
126+
127+
fieldnames = ['first_name', 'last_name', 'keywords', 'opening_message', 'search_results']
128+
# Add personalization fields in a specific order
129+
for factor in factor_names:
130+
if f"{factor}_actionable" in all_fields:
131+
fieldnames.append(f"{factor}_actionable")
132+
if f"{factor}_details" in all_fields:
133+
fieldnames.append(f"{factor}_details")
134+
135+
with open(args.output, 'w', newline='', encoding='utf-8') as csvfile:
136+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
137+
writer.writeheader()
138+
writer.writerows(results)
139+
140+
print(f"\nProcessing complete. Results written to '{args.output}'")
141+
else:
142+
print("\nNo results to write.")
143+
144+
if __name__ == "__main__":
145+
main()

output.csv

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
first_name,last_name,keywords,opening_message,search_results,recent_promotion_actionable,recent_promotion_details,recent_talks_actionable,recent_talks_details
2+
Elon,Musk,Tesla SpaceX entrepreneur,"Elon, congratulations on heading DOGE! Caught your recent Rogan interview—your insights on streamlining government efficiency while balancing innovation priorities were fascinating.","https://www.tesla.com/elon-musk,https://en.wikipedia.org/wiki/Elon_Musk,https://www.investopedia.com/articles/personal-finance/061015/how-elon-musk-became-elon-musk.asp,https://x.com/elonmusk?lang=en,https://www.biography.com/business-leaders/elon-musk,https://www.reddit.com/r/IAmA/comments/kkn5v/ask_tesla_spacex_and_paypal_cofounder_elon_musk/,https://twitter.com/elonmusk,https://www.reddit.com/r/SpaceXMasterrace/comments/sasbcw/elon_musk_was_not_the_founder_of_spacex_he_took/,https://www.space.com/18849-elon-musk.html,https://www.forbes.com/profile/elon-musk/",True,"Elon Musk was appointed as a senior advisor to United States president Donald Trump and de facto head of the Department of Government Efficiency (DOGE) in January 2025. | The content mentions Elon Musk's very recent appointment as (unofficial) head of the so-called Department of Government Efficiency (DOGE) following the 2024 election, where he now has 'unprecedented sway over federal policy and spending.' This appears to be a significant new role for him in government. | Elon Musk was recently appointed to lead the Department of Government Efficiency (DOGE) under President Trump's second administration in January 2025. This is a temporary organization tasked with cutting unnecessary federal spending and modernizing technology within the federal government. | Elon Musk recently became part of the government as head of the Department of Government Efficiency (DOGE). The content references his 'new government role' and discusses his activities leading DOGE, including sending emails to federal employees.",True,"Musk recently spoke at Trump's Presidential Parade in January 2025, where he made controversial remarks and gestures. He also gave an interview with psychologist Jordan Peterson in 2024 where he discussed his religious views, stating he considers himself a 'cultural Christian' and believes in the teachings of Jesus Christ. | Musk was recently interviewed by Joe Rogan (mentioned as 'Joe Rogan Interviews Elon Musk: Here Are The Key Moments' from February 28, 2025) and also spoke at the Conservative Political Action Conference on February 20, 2025."
3+
Sundar,Pichai,Google CEO tech,"Hi Sundar,
4+
5+
Your recent lecture at Carnegie Mellon about AI becoming a ""natural extension"" of daily life really resonated with me, especially your insights on Project Astra and DeepMind's mathematical breakthroughs.","https://en.wikipedia.org/wiki/Sundar_Pichai,https://www.linkedin.com/in/sundarpichai,https://mitsloan.mit.edu/ideas-made-to-matter/googles-sundar-pichai-says-tech-a-powerful-agent-change,https://www.productplan.com/learn/sundar-pichai-product-manager-to-ceo/,https://blog.google/authors/sundar-pichai/,https://www.weforum.org/stories/2020/01/this-is-how-quantum-computing-will-change-our-lives-8a0d33657f/,https://www.cmu.edu/news/stories/archives/2024/september/google-ceo-sundar-pichai-launches-the-2024-25-presidents-lecture-series-at-carnegie-mellon,https://www.nytimes.com/2018/11/08/business/sundar-pichai-google-corner-office.html,https://www.youtube.com/watch?v=OsxwBmp3iFU,https://www.theguardian.com/technology/2015/aug/10/google-sundar-pichai-ceo-alphabet",True,"The article announces Sundar Pichai's promotion to CEO of Google as part of a restructuring where Google became a subsidiary of Alphabet, with Larry Page moving to head Alphabet. This was announced on August 10, 2015.",True,"The content describes Pichai giving a talk at MIT Forefront where he discussed AI, future of work, diversity and inclusion, and climate change with MIT president L. Rafael Reif in March 2022 | The content mentions 'Google I/O 2024' where Sundar Pichai shared news about updates across Gemini, Android, Search and Photos, which indicates he recently gave a presentation or talk at this event. | Sundar Pichai recently gave a lecture at Carnegie Mellon University on September 18, 2024, titled 'The AI Platform Shift and the Opportunity Ahead' as part of the President's Lecture Series. He discussed AI innovations including Project Astra, Google DeepMind's success in the International Mathematical Olympiad, and his vision for AI becoming a 'natural extension' in everyday life. | The webpage is about a talk/interview titled 'Building the Future: Sundar Pichai on A.I., Regulation and What's Next for Google' which appears to be a recent discussion where he shared insights about AI, regulation, and Google's future."
6+
Tim,Cook,Apple iPhone CEO ,"Tim, your recent London interview about your Apple journey was refreshing. I especially appreciated your candid reflections on how Steve Jobs recruited you—it offers valuable perspective for today's tech leaders.","https://en.wikipedia.org/wiki/Tim_Cook,https://www.apple.com/leadership/tim-cook/,https://www.youtube.com/watch?v=m4RVTK7iU1c,https://www.apple.com/leadership/,https://x.com/tim_cook/status/1890068457825394918?lang=en,https://stratechery.com/2013/tim-cook-is-a-great-ceo/,https://x.com/tim_cook?lang=en,https://www.reddit.com/r/apple/comments/xa0vg9/tim_cook_has_been_ceo_of_apple_since_2011_since/,https://investor.apple.com/our_values/default.aspx,https://www.forbes.com/profile/tim-cook/",,,True,"The webpage shows Tim Cook giving an interview titled 'The Job Interview' where he discusses how Steve Jobs recruited him and other topics. This indicates a recent talk/interview that could be referenced. | Tim Cook was interviewed in London recently (mentioned in Dec 14, 2024 article: 'Apple CEO Tim Cook Declares New iPhone Era In Detailed Interview'). The interview covered business advice, his personal story, and Apple Intelligence."

0 commit comments

Comments
 (0)