A guide to build OpenAI Agents SDK Multi-agent sales team

A guide to build OpenAI Agents SDK Multi-agent sales team

Imagine having an AI-powered sales team that can automatically identify leads, gather crucial information, and craft personalized emails, all while you focus on strategic decisions. This is now easily achievable with the OpenAI Agents SDK.

This article will guide you through building such a system, step by step. We'll explore how to create a multi-agent sales team that leverages the power of AI to automate lead generation and personalize outreach, significantly enhancing your sales process.

Key Insights: AI Sales Team in a Nutshell

  • Automated Sales Workflow: We're building a system that automates the initial stages of sales outreach, from lead identification to personalized email drafting.
  • Multi-Agent Collaboration: The system utilizes multiple AI agents, each with a specific role (like Sales Development Rep and Email Writer), working together to achieve a common goal.
  • Data-Driven Personalization: By extracting data from platforms like LinkedIn, the system ensures that outreach is highly personalized and relevant to each lead.

The result? A streamlined sales process that saves time, reduces manual effort, and increases the chances of engaging potential clients effectively.

Why Should You Care About AI in Sales?

Why is automating sales outreach with AI agents a worthwhile endeavor? Because it directly addresses critical challenges in modern sales:

  • Efficiency: Manually researching leads and writing personalized emails is time-consuming. AI agents can perform these tasks much faster, freeing up your sales team to focus on higher-value activities like closing deals.
  • Personalization at Scale: Generic outreach rarely works. AI allows you to personalize communication for each lead based on their professional background and interests, significantly improving engagement rates.
  • Scalability: As your business grows, manually scaling your sales outreach becomes increasingly difficult. An AI-powered system can handle a larger volume of leads without proportionally increasing workload.

Ultimately, an AI sales team can lead to a more efficient, personalized, and scalable sales process, resulting in better lead engagement and potentially higher conversion rates.

Let's dive into building your AI sales team!

Defining the Sales Context

Before we introduce our AI agents, we need to establish a shared understanding of the information they'll be working with. This is where the SalesContext comes in. We define a data structure to hold all relevant information about a sales lead.

from dataclasses import dataclass
from typing import Any, Dict, Optional

@dataclass
class SalesContext:
    name: Optional[str]
    linkedin_url: Optional[str]
    profile_data: Optional[Dict[str, Any]]
    email_draft: Optional[str]        

This SalesContext dataclass will be used throughout our system to store:

  • name: The name of the sales lead.
  • linkedin_url: The LinkedIn profile URL of the lead.
  • profile_data: A dictionary containing extracted profile information from LinkedIn.
  • email_draft: The personalized email draft generated for the lead.

By using this shared context, all our agents will have access to the same information, ensuring seamless collaboration.

Assembling Your AI Sales Team: Defining the Agents

Now, let's introduce the agents that will form our AI sales team. We'll define three key roles:

  1. Sales Team Lead: This agent acts as the orchestrator, managing the entire sales workflow. It receives lead information and delegates tasks to other agents.
  2. Sales Development Rep (SDR): This agent is responsible for researching leads, specifically by extracting information from LinkedIn profiles.
  3. Cold Email Specialist: This agent focuses on crafting personalized and effective cold outreach emails based on the information gathered by the SDR.

Let's define these agents using the OpenAI Agents SDK. We'll start by importing necessary components and defining instructions for each agent.

from agents import Agent, RunContextWrapper, RunResult, Runner, handoff
from agents.extensions.handoff_prompt import prompt_with_handoff_instructions

# Instructions for each agent (defined separately for clarity, not shown here for brevity but will be detailed later)
SALES_TEAM_LEAD_INSTRUCTIONS = """..."""
SALES_DEVELOPMENT_REP_INSTRUCTIONS = """..."""
COLD_EMAIL_SPECIALIST_INSTRUCTIONS = """..."""

async def on_handoff_callback(ctx: RunContextWrapper[SalesContext]):
    print("\\\\n Handoff just happened")

# AGENTS
sales_team_lead = Agent[SalesContext](
    name="Sales Team Lead",
    instructions=prompt_with_handoff_instructions(SALES_TEAM_LEAD_INSTRUCTIONS),
    model="gpt-4o", # Using gpt-4o model for better performance
)

sales_development_rep = Agent[SalesContext](
    name="Sales Development Rep",
    instructions=prompt_with_handoff_instructions(SALES_DEVELOPMENT_REP_INSTRUCTIONS),
    tools=[extract_linkedin_profile], # We'll define this tool later
    model="gpt-4o",
)

cold_email_specialist = Agent[SalesContext](
    name="Cold Email Specialist",
    instructions=prompt_with_handoff_instructions(COLD_EMAIL_SPECIALIST_INSTRUCTIONS),
    tools=[generate_email], # We'll define this tool later
    model="gpt-4o",
)

        

In this code:

  • We define each agent using the Agent class from the openai-agents-sdk.
  • name: We give each agent a descriptive name.
  • instructions: We provide instructions that define the role and responsibilities of each agent. We use prompt_with_handoff_instructions to ensure the agents understand how to handle handoffs effectively.
  • tools: We specify the tools that each agent can use. sales_development_rep uses extract_linkedin_profile, and cold_email_specialist uses generate_email. We'll define these tools shortly.
  • model: We specify the language model to be used by each agent, in this case, gpt-4o for better performance.
  • on_handoff_callback: This function is called whenever a handoff occurs between agents, allowing us to track the workflow.

Let's look at the instructions we provide to each agent in more detail.

Sales Team Lead Instructions (SALES_TEAM_LEAD_INSTRUCTIONS)

SALES_TEAM_LEAD_INSTRUCTIONS = """
You are the Sales Team Lead responsible for managing the sales workflow.
Your job is to:
1. Take in lead information (name and LinkedIn URL)
2. Decide which team member to assign tasks to
3. Coordinate the overall process

If there's no user profile outside of name and linkedin url, you should first instruct the Sales Development Rep to extract the LinkedIn profile.
After the user's info has been populated, instruct the Cold Email Specialist agent to draft a personalized email.
"""

        

The Sales Team Lead is instructed to manage the sales process, take lead information, delegate tasks, and coordinate the workflow. It's also instructed to initiate LinkedIn profile extraction if needed and then delegate email drafting.

Sales Development Rep Instructions (SALES_DEVELOPMENT_REP_INSTRUCTIONS)

SALES_DEVELOPMENT_REP_INSTRUCTIONS = """
You are a Sales Development Rep responsible for researching leads.
Your job is to extract LinkedIn profile information.

Use the extract_linkedin_profile tool to get profile data

You do not do anything else other than the tool given to you.

Once you're done with your job, you should ping your supervisor agent using a tool.
"""

        

The SDR is clearly instructed to focus solely on extracting LinkedIn profile information using the extract_linkedin_profile tool and nothing else.

Cold Email Specialist Instructions (COLD_EMAIL_SPECIALIST_INSTRUCTIONS)

COLD_EMAIL_SPECIALIST_INSTRUCTIONS = """
You are a Cold Email Specialist responsible for drafting highly personalized, effective outreach emails.
Once you're done with your job, you should ping your supervisor agent using a tool
"""

        

The Cold Email Specialist's role is to draft personalized outreach emails. Like the SDR, it's instructed to notify its supervisor (Sales Team Lead) upon task completion.

Equipping Your Team: Defining the Tools

Our agents are defined, but they need tools to perform their tasks. We'll define two essential tools:

  1. extract_linkedin_profile: This tool allows the Sales Development Rep to extract profile data from a given LinkedIn URL.
  2. generate_email: This tool enables the Cold Email Specialist to generate a personalized outreach email.

Let's define the extract_linkedin_profile tool first. This tool will use a web scraping service (scraperapi.com) to fetch the LinkedIn profile page and then parse the markdown content using another function (parse_linkedin_profile) which leverages OpenAI to extract structured data.

import os
from typing import Any, Dict
from agents import RunContextWrapper, function_tool
from dotenv import load_dotenv
import requests
from agent_tools.utils.linkedin import parse_linkedin_profile # Assuming this utility function exists in agent_tools/utils/linkedin.py
from models.sales import SalesContext # Importing SalesContext

load_dotenv()

scraper_api_key = os.environ.get("SCRAPER_API_KEY") # Ensure you have SCRAPER_API_KEY in your .env file

@function_tool
def extract_linkedin_profile(
    wrapper: RunContextWrapper[SalesContext], linkedin_url: str
) -> Dict[str, Any]:
    """Extract profile data from a LinkedIn URL"""
    print("Start scraping & extracting LinkedIn")

    payload = {
        "api_key": scraper_api_key,
        "url": linkedin_url,
        "output_format": "markdown",
    }
    response = requests.get(
        "<https://api.scraperapi.com/>", # Using scraperapi for web scraping
        params=payload,
    )

    page_markdown = response.text

    # Extract the user's profile from the response using parse_linkedin_profile utility
    profile_data = parse_linkedin_profile(page_markdown)

    # Update the context with the extracted profile data
    wrapper.context["profile_data"] = profile_data

    print("Finished LinkedIn extration.")

    return profile_data

        

In this code:

  • We use the @function_tool decorator from the openai-agents-sdk to define this function as a tool that agents can use.
  • The function takes a linkedin_url as input.
  • It uses scraperapi.com to fetch the LinkedIn profile in markdown format. You'll need to sign up for a ScraperAPI account and set your SCRAPER_API_KEY as an environment variable.
  • It calls parse_linkedin_profile (which we'll detail shortly) to extract structured data from the markdown content.
  • Crucially, it updates the shared SalesContext by adding the extracted profile_data to wrapper.context["profile_data"]. This makes the extracted data available to other agents.

Now, let's look at the generate_email tool, which the Cold Email Specialist will use. This tool will leverage OpenAI's API to generate a personalized email based on the extracted LinkedIn profile data.

import os
from agents import RunContextWrapper, function_tool
from dotenv import load_dotenv
from openai import OpenAI

from models.sales import SalesContext # Importing SalesContext

load_dotenv()

openai_api_key = os.environ.get("OPENAI_API_KEY") # Ensure you have OPENAI_API_KEY in your .env file

client = OpenAI(api_key=openai_api_key)

@function_tool
def generate_email(
    wrapper: RunContextWrapper[SalesContext],
) -> str:
    """Generate a personalized outbound sales email based on LinkedIn profile data"""

    if not wrapper.context.get("profile_data"):
        return "Error: No LinkedIn profile data available. Please extract profile data first."

    system_prompt = "You're an expert at writing personalized outbound sales emails. Write a concise, persuasive email that connects with"

    prompt_details = f"""
RECIPIENT INFORMATION:
{wrapper.context["name"]}
{wrapper.context["profile_data"]}

EMAIL DETAILS:
- Sender Name: AI Agent Company
- Sender Company: Company of Agents

Guidelines:
- Keep the email concise (1 paragraph)
- Write like Josh Braun (shine a light on a problem that the prospect might not know)
- No jargons, no hard pitch, just pique interest
"""

    response = client.responses.create(
        model="gpt-4o-mini", # Using gpt-4o-mini for email generation
        input=[
            {
                "role": "system",
                "content": [{"type": "input_text", "text": system_prompt}],
            },
            {
                "role": "user",
                "content": [{"type": "input_text", "text": prompt_details}],
            },
        ],
        text={"format": {"type": "text"}},
        reasoning={},
        tools=[],
        temperature=0.7,
        max_output_tokens=2048,
        top_p=1,
        store=True,
    )

    generated_email = response.output_text
    # Update the context with the generated email
    wrapper.context["email_draft"] = generated_email # Storing the generated email in the shared context

    return generated_email

        

In this generate_email tool:

  • It also uses the @function_tool decorator.
  • It checks if profile_data is available in the SalesContext. If not, it returns an error message, ensuring that the SDR has done its job first.
  • It constructs a detailed prompt for the OpenAI API, including: A system_prompt that sets the AI's role as an expert email writer. prompt_details that provide recipient information (name and profile_data) and email guidelines (conciseness, tone, etc.).
  • It uses client.responses.create to call the OpenAI API (using gpt-4o-mini model for cost-effectiveness). You'll need to set your OPENAI_API_KEY as an environment variable.
  • It extracts the generated email text from the API response (response.output_text).
  • It updates the SalesContext by storing the generated_email in wrapper.context["email_draft"], making it accessible to the Sales Team Lead.

Finally, let's look at the parse_linkedin_profile utility function used within extract_linkedin_profile. This function takes the markdown content of a LinkedIn profile and uses OpenAI to extract structured data based on a predefined JSON schema.

import logging
import os
import json
from openai import OpenAI
from schemas.linkedin_schema import LINKEDIN_PROFILE_SCHEMA # Assuming this schema is defined in schemas/linkedin_schema.py

# Initialize OpenAI client
openai_api_key = os.environ.get("OPENAI_API_KEY") # Ensure you have OPENAI_API_KEY in your .env file
client = OpenAI(api_key=openai_api_key)

def parse_linkedin_profile(markdown_content: str):
    """Extract structured data from LinkedIn profile HTML content using OpenAI API"""
    logging.info("Starting LinkedIn profile structured output extraction")
    response = client.responses.create(
        model="gpt-4o-mini", # Using gpt-4o-mini for cost-effectiveness
        input=[
            {
                "role": "system",
                "content": [
                    {
                        "type": "input_text",
                        "text": "You're an expert at looking at a person's LinkedIn page and extract out relevant information.",
                    }
                ],
            },
            {
                "role": "user",
                "content": [{"type": "input_text", "text": markdown_content}],
            },
        ],
        text={
            "format": {
                "type": "json_schema",
                "name": "linkedin_profile",
                "strict": True,
                "schema": LINKEDIN_PROFILE_SCHEMA, # Using the predefined JSON schema
            }
        },
        reasoning={},
        tools=[],
        temperature=0.5,
        top_p=1,
    )

    return json.loads(response.output_text)

        

In parse_linkedin_profile:

  • It uses OpenAI's client.responses.create to process the markdown_content.
  • It provides a system prompt instructing the model to act as a LinkedIn data extraction expert.
  • It specifies the output format as json_schema and uses LINKEDIN_PROFILE_SCHEMA to guide the extraction process. This schema (defined in schemas/linkedin_schema.py) defines the structure of the LinkedIn profile data we want to extract (e.g., current_role, company, experience, education, etc.).
  • It returns the extracted data as a JSON object.

The LINKEDIN_PROFILE_SCHEMA itself is a JSON schema that defines the structure of the LinkedIn profile data we want to extract. Here's an example of what it might look like:

LINKEDIN_PROFILE_SCHEMA = {
    "type": "object",
    "properties": {
        "current_role": {
            "type": "string",
            "description": "The current job title of the person on LinkedIn.",
        },
        "company": {
            "type": "string",
            "description": "The name of the company where the person is currently employed.",
        },
        "industry": {
            "type": "string",
            "description": "The industry the person works in.",
        },
        "experience": {
            "type": "array",
            "description": "A list of previous job positions held by the person.",
            "items": {
                "type": "object",
                "properties": {
                    "title": {
                        "type": "string",
                        "description": "The job title of the position held.",
                    },
                    "company": {
                        "type": "string",
                        "description": "The name of the company where the person was previously employed.",
                    },
                    "duration": {
                        "type": "string",
                        "description": "The time range the position was held.",
                    },
                },
                "required": ["title", "company", "duration"],
                "additionalProperties": False,
            },
        },
        "education": {
            "type": "array",
            "description": "A list of schools and degrees the person has.",
            "items": {
                "type": "object",
                "properties": {
                    "school": {
                        "type": "string",
                        "description": "The name of the school the person attended.",
                    },
                    "degree": {
                        "type": "string",
                        "description": "The degree the person earned.",
                    },
                    "date_range": {
                        "type": "string",
                        "description": "The time range the degree was earned.",
                    },
                },
            },
        },
        "interests": {
            "type": "array",
            "description": "A list of professional interests mentioned on the profile.",
            "items": {
                "type": "string",
                "description": "A professional interest or topic",
            },
        },
        "recent_activity": {
            "type": "string",
            "description": "Brief description of recent activity on LinkedIn, if visible.",
        },
    },
    "required": [
        "current_role",
        "company",
        "industry",
        "experience",
        "education",
        "interests",
        "recent_activity",
    ],
    "additionalProperties": False,
}

        

This schema ensures that the parse_linkedin_profile function extracts data in a consistent and structured format, which is crucial for the subsequent email generation step.

Orchestrating the Workflow: Agent Handoffs

Now that we have our agents and tools defined, we need to orchestrate how they work together. This is where agent handoffs come in. We'll define handoffs for the Sales Team Lead to delegate tasks to the SDR and Cold Email Specialist.

sales_team_lead.handoffs = [
    handoff(agent=sales_development_rep, on_handoff=on_handoff_callback),
    handoff(agent=cold_email_specialist, on_handoff=on_handoff_callback),
]

sales_development_rep.handoffs = [
    handoff(agent=sales_team_lead, on_handoff=on_handoff_callback) # SDR can hand back to team lead
]

cold_email_specialist.handoffs = [
    handoff(agent=sales_team_lead, on_handoff=on_handoff_callback) # Email specialist can hand back to team lead
]

        

Here, we define the handoffs attribute for each agent:

  • sales_team_lead.handoffs: The Sales Team Lead can hand off tasks to both the sales_development_rep and the cold_email_specialist.
  • sales_development_rep.handoffs: The SDR can hand back to the sales_team_lead after completing LinkedIn profile extraction.
  • cold_email_specialist.handoffs: The Cold Email Specialist can hand back to the sales_team_lead after drafting the email.

The handoff() function specifies the target agent and an optional on_handoff_callback function, which we use to print a message whenever a handoff occurs, helping us track the workflow.

Putting It All Together: Running the Multi-Agent System

With agents, tools, and handoffs defined, we can now create functions to run our multi-agent system. Let's start with a function to process a single sales lead:

async def process_sales_lead(lead: dict) -> RunResult:
    """Process a sales lead through the multi-agent workflow"""
    name = lead["name"]
    linkedin_url = lead["linkedin_url"]

    context: SalesContext = SalesContext( # Initialize SalesContext object
        name=name,
        linkedin_url=linkedin_url,
        profile_data=None,
        email_draft=None,
    )

    print(f"\\\\n Processing lead: {name} ({linkedin_url})")

    final_result = await Runner.run(
        starting_agent=sales_team_lead, # Start with the Sales Team Lead
        input=f"We have a new lead: {name} ({linkedin_url}). Please coordinate the process to research this lead and create a personalized outreach email.",
        context=context, # Pass the SalesContext
        max_turns=15, # Limit the number of turns to prevent infinite loops
    )

    return final_result

        

In process_sales_lead:

  • It takes a lead dictionary as input, containing name and linkedin_url.
  • It initializes a SalesContext object with the lead's information.
  • It uses Runner.run to start the multi-agent workflow. starting_agent: We specify sales_team_lead as the starting agent, as it's the orchestrator. input: We provide an initial input message to the Sales Team Lead, instructing it to research the lead and create an email. context: We pass the initialized SalesContext object. max_turns: We set a max_turns limit to prevent the agents from running indefinitely.
  • It returns the RunResult object, which contains information about the execution, including the final output and the last agent to act.

To process multiple leads in parallel, we can use the run_dict_tasks_in_parallel utility function:

from data.sales_leads import leads # Assuming leads are defined in data/sales_leads.py
from miscs.run_parallel_agents import run_dict_tasks_in_parallel # Assuming this utility function exists in miscs/run_parallel_agents.py
import asyncio

def display_lead_result(lead: dict, final_result: RunResult):
    print("Final results:")
    print(f"""
    Input: {final_result.input}
    Final message from agent: {final_result.final_output}
    Last agent: {final_result.last_agent.name}
    """)

async def process_multiple_leads_in_parallel():
    """Process a list of predefined leads in parallel"""
    print("\\\\n===== Sales Outreach Multi-Agent System =====")
    results = await run_dict_tasks_in_parallel(
        process_function=process_sales_lead,
        input_dicts=leads, # Using a list of leads defined in data/sales_leads.py
        show_progress=True,
        result_handler=display_lead_result, # Function to display results for each lead
    )

    return results

def main():
    """Main entry point for the application"""
    print("Starting Sales Outreach Multi-Agent System...")
    asyncio.run(process_multiple_leads_in_parallel())

if __name__ == "__main__":
    main()

        

In process_multiple_leads_in_parallel:

  • It uses run_dict_tasks_in_parallel to process a list of leads defined in data/sales_leads.py concurrently.
  • process_function: We pass our process_sales_lead function as the function to be executed for each lead.
  • input_dicts: We provide the leads list as input.
  • show_progress=True: This will display a progress bar during execution.
  • result_handler=display_lead_result: We specify display_lead_result to handle and display the results for each processed lead.

The main function simply calls process_multiple_leads_in_parallel using asyncio.run to start the asynchronous execution.

To run this system, you would need to:

  1. Set up environment variables: Ensure you have OPENAI_API_KEY and SCRAPER_API_KEY set in your .env file.
  2. Install required libraries: Install openai-agents-sdk, python-dotenv, requests, and any other dependencies.
  3. Create necessary files: Create sales_leads.py in the data directory with a list of leads, linkedin.py and linkedin_schema.py in agent_tools/utils and schemas directories respectively, and run_parallel_agents.py in miscs directory if you are using parallel processing.
  4. Run multi_agents.py: Execute the Python script.

You should see output in your terminal showing the progress of lead processing, agent handoffs, and final results for each lead. You can also check the OpenAI traces dashboard (platform.openai.com/traces) to see detailed execution traces and timings for each agent and tool call.

Conclusion

Important Considerations:

  • Model Limitations: While gpt-4o and gpt-4o-mini are powerful, they are not perfect. Prompt engineering and careful instructions are crucial to ensure agents behave as expected. Models like gpt-4o-mini might have limitations with handoffs, requiring careful prompt design.
  • Data Quality: The quality of LinkedIn profile data and the effectiveness of the email generation depend on the accuracy of the scraping and parsing tools and the quality of the prompts.
  • Ethical Considerations: Web scraping and automated outreach should be done ethically and responsibly, respecting privacy and terms of service.

The Future of AI in Sales:

This example is just the beginning. AI agents have the potential to revolutionize sales in many ways, including:

  • Advanced Lead Qualification: Integrating more sophisticated data sources and AI models for deeper lead qualification.
  • Dynamic Workflow Adaptation: Creating systems that can dynamically adjust the sales workflow based on lead behavior and engagement.
  • Integration with CRM and Sales Platforms: Seamlessly integrating AI agents with existing sales tools and CRM systems for end-to-end automation.

要查看或添加评论,请登录

Hai N.的更多文章

社区洞察