Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions cookbook/examples/streamlit_apps/gtm_outreach/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
# GTM B2B Outreach Multi-Agent System

A powerful multi-agent system that automates B2B outreach by finding target companies, identifying decision-makers, researching insights, and generating personalized emails. Built with AI agents using OpenAI GPT models and Exa search capabilities.

---

## 🚀 Features

- **Company Discovery**: AI-powered search to find companies matching your targeting criteria
- **Contact Identification**: Automatically discovers decision-makers and key contacts
- **Phone Number Research**: Finds phone numbers for identified contacts
- **Company Research**: Gathers insights from websites and Reddit discussions
- **Email Generation**: Creates personalized outreach emails in multiple styles
- **Interactive Dashboard**: Streamlit-based web interface for easy operation

---

## 🏗️ Architecture

The system consists of 5 specialized AI agents working in sequence:

- **CompanyFinderAgent**: Discovers target companies using Exa search
- **ContactFinderAgent**: Identifies decision-makers and contacts
- **PhoneFinderAgent**: Researches phone numbers for contacts
- **ResearchAgent**: Collects company insights and intelligence
- **EmailWriterAgent**: Generates personalized outreach emails

---

## 📋 Prerequisites

- Python 3.8+
- OpenAI API key
- Exa API key

---

## 🛠️ Installation

1. Clone the repository
```bash
git clone <repository-url>
cd agno/cookbook/examples/streamlit_apps/gtm_outreach
```

2. Create a virtual environment
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```

3. Install dependencies
```bash
# Option 1: Install from requirements.txt
pip install -r requirements.txt

# Option 2: Generate fresh requirements (requires pip-tools)
chmod +x generate_requirements.sh
./generate_requirements.sh
pip install -r requirements.txt
```

---

## 🔑 API Keys Setup

### OpenAI API Key
1. Visit [OpenAI Platform](https://platform.openai.com/)
2. Create an account or sign in
3. Go to API Keys section
4. Create a new API key

### Exa API Key
1. Visit [Exa](https://exa.ai/)
2. Sign up for an account
3. Get your API key from the dashboard

### Environment Variables (Optional)
```bash
export OPENAI_API_KEY="your_openai_key_here"
export EXA_API_KEY="your_exa_key_here"
```

---

## 🚀 Usage

### Starting the Application
```bash
streamlit run app.py
```
The application will open in your browser at [http://localhost:8501](http://localhost:8501)

### Using the Interface
1. **Configure API Keys**: Enter your OpenAI and Exa API keys in the sidebar
2. **Define Target Companies**: Describe your ideal customer profile
3. **Describe Your Offering**: Explain what you're selling/offering
4. **Set Parameters**:
- Your name and company
- Calendar link (optional)
- Number of companies (1-10)
- Email style (Professional, Casual, Cold, Consultative)
5. Click **"Start Outreach"** to begin the automated process

### Email Styles
- **Professional**: Clear, respectful, and businesslike tone
- **Casual**: Friendly, approachable, first-name basis
- **Cold**: Strong hook with tight value proposition
- **Consultative**: Insight-led approach with soft call-to-action

---

## 📊 Output

The system provides comprehensive results:

- **Companies**: List of target companies with fit reasoning
- **Contacts**: Decision-makers with titles and email addresses
- **Phone Numbers**: Contact phone numbers with verification status
- **Research Insights**: Key intelligence about each company
- **Personalized Emails**: Ready-to-send outreach emails

---

## 🏃‍♂️ Example Workflow

```python
# Example target description
target_desc = """
SaaS companies with 50-200 employees in the fintech space,
particularly those focused on payment processing or digital banking
"""

# Example offering
offering_desc = """
AI-powered fraud detection solution that reduces false positives
by 40% while maintaining 99.9% accuracy in fraud detection
"""
```

---

## 📁 Project Structure

```
gtm-b2b-outreach/
├── app.py # Streamlit web application
├── agent.py # Agent definitions and pipeline functions
├── requirements.in # Input requirements
├── requirements.txt # Pinned dependencies
├── generate_requirements.sh # Requirements generation script
└── README.md # This file
```

---

## 🔧 Core Dependencies

- [agno](https://github.com/agency-swarm/agno): Multi-agent framework
- [streamlit](https://streamlit.io/): Web interface
- [openai](https://openai.com/): GPT model integration
- [exa_py](https://exa.ai/): Web search capabilities
- [pydantic](https://pydantic.dev/): Data validation

---

## 🛠️ Customization

### Adding New Email Styles
```python
def get_email_style_instruction(style_key: str) -> str:
styles = {
"Professional": "Style: Professional. Clear, respectful, and businesslike.",
"YourStyle": "Your custom style instruction here.",
}
return styles.get(style_key, styles["Professional"])
```

### Modifying Agent Instructions
Each agent can be customized by updating the `instructions` parameter in the respective `create_*_agent()` functions.

### Adjusting Model Selection
```python
model=OpenAIChat(id="gpt-4") # or "gpt-3.5-turbo"
```

---

## ⚠️ Important Notes

- **Rate Limits**: Be mindful of API rate limits for both OpenAI and Exa
- **Costs**: Monitor usage as API calls incur costs
- **Data Privacy**: Ensure compliance with data protection regulations
- **Email Deliverability**: Generated emails should be reviewed before sending

---

## 🐛 Troubleshooting

### Common Issues

- **API Key Errors**: Verify keys are correct and have sufficient credits. Check environment variable names.
- **JSON Parsing Errors**: The system includes fallback JSON extraction. Check agent instructions for proper JSON formatting.
- **No Results Found**: Refine your target company description. Try broader search criteria.
- **Streamlit Issues**: Clear browser cache. Restart the application. Check console for errors.

---

## 📈 Performance Tips

- Start with fewer companies (3-5) for testing
- Use specific targeting criteria for better results
- Review and refine generated content before use
- Monitor API usage and costs
174 changes: 174 additions & 0 deletions cookbook/examples/streamlit_apps/gtm_outreach/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import json
from typing import Any, Dict, List, Optional

from agno.agent import Agent
from agno.memory.v2 import Memory
from agno.models.openai import OpenAIChat
from agno.tools.exa import ExaTools


def create_company_finder_agent() -> Agent:
exa_tools = ExaTools(category="company")
memory = Memory()
return Agent(
model=OpenAIChat(id="gpt-5"),
tools=[exa_tools],
memory=memory,
add_history_to_messages=True,
num_history_responses=6,
session_id="gtm_outreach_company_finder",
show_tool_calls=True,
instructions=[
"You are CompanyFinderAgent. Use ExaTools to search the web for companies that match the targeting criteria.",
"Return ONLY valid JSON with key 'companies' as a list; respect the requested limit provided in the user prompt.",
"Each item must have: name, website, why_fit (1-2 lines).",
],
)


def create_contact_finder_agent() -> Agent:
exa_tools = ExaTools()
memory = Memory()
return Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[exa_tools],
memory=memory,
add_history_to_messages=True,
num_history_responses=6,
session_id="gtm_outreach_contact_finder",
show_tool_calls=True,
instructions=[
"You are ContactFinderAgent. Use ExaTools to collect as many relevant decision makers as possible per company...",
"Return ONLY valid JSON with key 'companies' as a list; each has: name, contacts: [{full_name, title, email, inferred}]",
],
)


def create_phone_finder_agent() -> Agent:
exa_tools = ExaTools()
memory = Memory()
return Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[exa_tools],
memory=memory,
add_history_to_messages=True,
num_history_responses=6,
session_id="gtm_outreach_phone_finder",
show_tool_calls=True,
instructions=[
"You are PhoneFinderAgent. Use ExaTools to find phone numbers...",
"Return ONLY valid JSON with key 'companies' as a list; each has: name, contacts: [{full_name, phone_number, phone_type, verified}]",
],
)


def create_research_agent() -> Agent:
exa_tools = ExaTools()
memory = Memory()
return Agent(
model=OpenAIChat(id="gpt-5"),
tools=[exa_tools],
memory=memory,
add_history_to_messages=True,
num_history_responses=6,
session_id="gtm_outreach_researcher",
show_tool_calls=True,
instructions=[
"You are ResearchAgent. Collect insights from websites + Reddit...",
"Return ONLY valid JSON with key 'companies' as a list; each has: name, insights: [strings].",
],
)


def get_email_style_instruction(style_key: str) -> str:
styles = {
"Professional": "Style: Professional. Clear, respectful, and businesslike.",
"Casual": "Style: Casual. Friendly, approachable, first-name basis.",
"Cold": "Style: Cold email. Strong hook, tight value proposition.",
"Consultative": "Style: Consultative. Insight-led, soft CTA.",
}
return styles.get(style_key, styles["Professional"])


def create_email_writer_agent(style_key: str = "Professional") -> Agent:
memory = Memory()
style_instruction = get_email_style_instruction(style_key)
return Agent(
model=OpenAIChat(id="gpt-5"),
tools=[],
memory=memory,
add_history_to_messages=True,
num_history_responses=6,
session_id="gtm_outreach_email_writer",
show_tool_calls=False,
instructions=[
"You are EmailWriterAgent. Write concise, personalized B2B outreach emails.",
style_instruction,
"Return ONLY valid JSON with key 'emails' as a list of items: {company, contact, subject, body}.",
],
)


# -------- Utility Functions -------- #


def extract_json_or_raise(text: str) -> Dict[str, Any]:
try:
return json.loads(text)
except Exception:
start = text.find("{")
end = text.rfind("}")
if start != -1 and end != -1 and end > start:
return json.loads(text[start : end + 1])
raise


def run_company_finder(
agent: Agent, target_desc: str, offering_desc: str, max_companies: int
) -> List[Dict[str, str]]:
prompt = f"Find exactly {max_companies} companies...\nTargeting: {target_desc}\nOffering: {offering_desc}"
resp = agent.run(prompt)
data = extract_json_or_raise(str(resp.content))
return data.get("companies", [])[: max(1, min(max_companies, 10))]


def run_contact_finder(
agent: Agent, companies: List[Dict[str, str]], target_desc: str, offering_desc: str
) -> List[Dict[str, Any]]:
prompt = f"For each company below, find contacts...\nCompanies JSON: {json.dumps(companies)}"
resp = agent.run(prompt)
data = extract_json_or_raise(str(resp.content))
return data.get("companies", [])


def run_phone_finder(
agent: Agent, contacts_data: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
prompt = f"For each contact below, find phone numbers...\nContacts JSON: {json.dumps(contacts_data)}"
resp = agent.run(prompt)
data = extract_json_or_raise(str(resp.content))
return data.get("companies", [])


def run_research(agent: Agent, companies: List[Dict[str, str]]) -> List[Dict[str, Any]]:
prompt = (
f"For each company, gather insights...\nCompanies JSON: {json.dumps(companies)}"
)
resp = agent.run(prompt)
data = extract_json_or_raise(str(resp.content))
return data.get("companies", [])


def run_email_writer(
agent: Agent,
contacts_data: List[Dict[str, Any]],
research_data: List[Dict[str, Any]],
offering_desc: str,
sender_name: str,
sender_company: str,
calendar_link: Optional[str],
) -> List[Dict[str, str]]:
prompt = f"Write outreach emails...\nContacts JSON: {json.dumps(contacts_data)}\nResearch JSON: {json.dumps(research_data)}"
resp = agent.run(prompt)
data = extract_json_or_raise(str(resp.content))
return data.get("emails", [])
Loading