Skip to content
Open
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,376 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# OpenAI Exercise: Multi-Model SDR with Guardrails\n",
"\n",
"This notebook is based on concepts taught in `2_openai` and completes the exercise from the second-last cell of `3_lab3.ipynb`:\n",
"- try different models,\n",
"- add more input and output guardrails,\n",
"- use structured outputs for email generation.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Concept Consolidation From `2_openai`\n",
"\n",
"1. `1_lab1`: OpenAI Agents SDK basics (`Agent`, `Runner`, `trace`) with a simple single-agent run.\n",
"2. `2_lab2`: Agentic workflow for SDR: parallel candidate generation, tool usage (`@function_tool`), manager orchestration, and handoffs.\n",
"3. `3_lab3`: model abstraction (`OpenAIChatCompletionsModel`), structured outputs with Pydantic, and guardrails.\n",
"4. `4_lab4`: deep research pattern with planner/search/writer specialization and reusable async workflow functions.\n",
"\n",
"Applied in this notebook:\n",
"- Three OpenAI models for different writing styles.\n",
"- Structured email outputs via Pydantic schema.\n",
"- Two input guardrails and two output guardrails.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from dotenv import load_dotenv\n",
"from openai import AsyncOpenAI\n",
"from pydantic import BaseModel, Field\n",
"from agents import (\n",
" Agent,\n",
" Runner,\n",
" trace,\n",
" function_tool,\n",
" set_default_openai_client,\n",
" set_default_openai_api,\n",
" OpenAIChatCompletionsModel,\n",
" input_guardrail,\n",
" output_guardrail,\n",
" GuardrailFunctionOutput,\n",
")\n",
"from agents.exceptions import InputGuardrailTripwireTriggered, OutputGuardrailTripwireTriggered\n",
"import os\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Environment setup (using OpenAI key as requested)\n",
"\n",
"load_dotenv(override=True)\n",
"\n",
"openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n",
"if not openai_api_key:\n",
" raise ValueError(\"OPENAI_API_KEY is missing from .env\")\n",
"\n",
"openai_client = AsyncOpenAI(api_key=openai_api_key)\n",
"set_default_openai_client(openai_client, use_for_tracing=True)\n",
"set_default_openai_api(\"chat_completions\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Different models (exercise requirement: try different models)\n",
"\n",
"MODEL_PROFESSIONAL = OpenAIChatCompletionsModel(model=\"gpt-4o-mini\", openai_client=openai_client)\n",
"MODEL_CREATIVE = OpenAIChatCompletionsModel(model=\"gpt-4.1-mini\", openai_client=openai_client)\n",
"MODEL_CONCISE = OpenAIChatCompletionsModel(model=\"gpt-4.1-nano\", openai_client=openai_client)\n",
"MODEL_GUARDRAIL = \"gpt-4o-mini\"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Structured outputs (exercise requirement: structured email generation)\n",
"\n",
"class ColdEmail(BaseModel):\n",
" subject: str = Field(description=\"Compelling subject line under 60 chars\")\n",
" body_markdown: str = Field(description=\"Email body in markdown\")\n",
" tone: str = Field(description=\"Tone used in the email\")\n",
" cta: str = Field(description=\"Clear call to action\")\n",
"\n",
"\n",
"class NameCheckOutput(BaseModel):\n",
" has_personal_name: bool\n",
" reason: str\n",
"\n",
"\n",
"class PromptSafetyOutput(BaseModel):\n",
" unsafe_or_irrelevant: bool\n",
" reason: str\n",
"\n",
"\n",
"class QualityCheckOutput(BaseModel):\n",
" unacceptable: bool\n",
" reason: str\n",
"\n",
"\n",
"class ComplianceCheckOutput(BaseModel):\n",
" risky_claims: bool\n",
" reason: str\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Sales agents using different models\n",
"\n",
"instructions_professional = \"\"\"\n",
"You are a professional SDR for ComplAI.\n",
"Generate a serious, credible cold sales email.\n",
"Output MUST follow the ColdEmail schema.\n",
"\"\"\"\n",
"\n",
"instructions_creative = \"\"\"\n",
"You are a creative SDR for ComplAI.\n",
"Generate an engaging and memorable cold sales email.\n",
"Output MUST follow the ColdEmail schema.\n",
"\"\"\"\n",
"\n",
"instructions_concise = \"\"\"\n",
"You are a concise SDR for ComplAI.\n",
"Generate a short, direct cold sales email.\n",
"Output MUST follow the ColdEmail schema.\n",
"\"\"\"\n",
"\n",
"sales_agent_1 = Agent(\n",
" name=\"Professional Sales Agent\",\n",
" instructions=instructions_professional,\n",
" model=MODEL_PROFESSIONAL,\n",
" output_type=ColdEmail,\n",
")\n",
"\n",
"sales_agent_2 = Agent(\n",
" name=\"Creative Sales Agent\",\n",
" instructions=instructions_creative,\n",
" model=MODEL_CREATIVE,\n",
" output_type=ColdEmail,\n",
")\n",
"\n",
"sales_agent_3 = Agent(\n",
" name=\"Concise Sales Agent\",\n",
" instructions=instructions_concise,\n",
" model=MODEL_CONCISE,\n",
" output_type=ColdEmail,\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Tools: convert agents to tools + preview sender tool\n",
"\n",
"description = \"Generate one cold sales email draft in the ColdEmail schema\"\n",
"tool1 = sales_agent_1.as_tool(tool_name=\"sales_agent_1\", tool_description=description)\n",
"tool2 = sales_agent_2.as_tool(tool_name=\"sales_agent_2\", tool_description=description)\n",
"tool3 = sales_agent_3.as_tool(tool_name=\"sales_agent_3\", tool_description=description)\n",
"\n",
"\n",
"@function_tool\n",
"def send_preview_email(subject: str, body_markdown: str) -> dict:\n",
" \"\"\"Preview-send an email (no external provider call).\"\"\"\n",
" print(\"\\n=== PREVIEW EMAIL ===\")\n",
" print(\"Subject:\", subject)\n",
" print(\"Body:\\n\", body_markdown)\n",
" print(\"=== END PREVIEW ===\\n\")\n",
" return {\"status\": \"preview_sent\"}\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Guardrail agents\n",
"\n",
"name_guardrail_agent = Agent(\n",
" name=\"Name Guardrail\",\n",
" instructions=\"Detect whether the message includes a personal name to target.\",\n",
" output_type=NameCheckOutput,\n",
" model=MODEL_GUARDRAIL,\n",
")\n",
"\n",
"prompt_guardrail_agent = Agent(\n",
" name=\"Prompt Safety Guardrail\",\n",
" instructions=(\n",
" \"Mark unsafe_or_irrelevant as true if the request asks for unethical content, \"\n",
" \"spammy deception, or is not about writing a cold sales email.\"\n",
" ),\n",
" output_type=PromptSafetyOutput,\n",
" model=MODEL_GUARDRAIL,\n",
")\n",
"\n",
"quality_guardrail_agent = Agent(\n",
" name=\"Output Quality Guardrail\",\n",
" instructions=(\n",
" \"Given a generated email, mark unacceptable true if it lacks a clear CTA, \"\n",
" \"is unprofessional, or too vague to send.\"\n",
" ),\n",
" output_type=QualityCheckOutput,\n",
" model=MODEL_GUARDRAIL,\n",
")\n",
"\n",
"compliance_guardrail_agent = Agent(\n",
" name=\"Compliance Guardrail\",\n",
" instructions=(\n",
" \"Given a generated email, mark risky_claims true if it contains fabricated guarantees, \"\n",
" \"false compliance promises, or deceptive claims.\"\n",
" ),\n",
" output_type=ComplianceCheckOutput,\n",
" model=MODEL_GUARDRAIL,\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Input guardrails (exercise requirement: more input guardrails)\n",
"\n",
"@input_guardrail\n",
"async def guardrail_against_personal_names(ctx, agent, message):\n",
" result = await Runner.run(name_guardrail_agent, message, context=ctx.context)\n",
" check = result.final_output\n",
" return GuardrailFunctionOutput(\n",
" output_info={\"reason\": check.reason},\n",
" tripwire_triggered=check.has_personal_name,\n",
" )\n",
"\n",
"\n",
"@input_guardrail\n",
"async def guardrail_prompt_safety(ctx, agent, message):\n",
" result = await Runner.run(prompt_guardrail_agent, message, context=ctx.context)\n",
" check = result.final_output\n",
" return GuardrailFunctionOutput(\n",
" output_info={\"reason\": check.reason},\n",
" tripwire_triggered=check.unsafe_or_irrelevant,\n",
" )\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Output guardrails (exercise requirement: add output guardrails)\n",
"\n",
"@output_guardrail\n",
"async def guardrail_output_quality(ctx, agent, output):\n",
" result = await Runner.run(quality_guardrail_agent, str(output), context=ctx.context)\n",
" check = result.final_output\n",
" return GuardrailFunctionOutput(\n",
" output_info={\"reason\": check.reason},\n",
" tripwire_triggered=check.unacceptable,\n",
" )\n",
"\n",
"\n",
"@output_guardrail\n",
"async def guardrail_output_compliance(ctx, agent, output):\n",
" result = await Runner.run(compliance_guardrail_agent, str(output), context=ctx.context)\n",
" check = result.final_output\n",
" return GuardrailFunctionOutput(\n",
" output_info={\"reason\": check.reason},\n",
" tripwire_triggered=check.risky_claims,\n",
" )\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Manager agent\n",
"\n",
"manager_instructions = \"\"\"\n",
"You are Sales Manager at ComplAI.\n",
"1. Use all three sales_agent tools to generate draft candidates.\n",
"2. Select the single best candidate for response likelihood and professionalism.\n",
"3. Use send_preview_email with the final subject and body_markdown.\n",
"4. Return the selected draft in the ColdEmail schema.\n",
"Do not target a person by name.\n",
"\"\"\"\n",
"\n",
"sales_manager = Agent(\n",
" name=\"Sales Manager\",\n",
" instructions=manager_instructions,\n",
" tools=[tool1, tool2, tool3, send_preview_email],\n",
" model=\"gpt-4o-mini\",\n",
" output_type=ColdEmail,\n",
" input_guardrails=[guardrail_against_personal_names, guardrail_prompt_safety],\n",
" output_guardrails=[guardrail_output_quality, guardrail_output_compliance],\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Run example\n",
"\n",
"message = \"Write a cold sales email for a CTO at a mid-size fintech. Sender is Head of Business Development.\"\n",
"\n",
"try:\n",
" with trace(\"Week2 Exercise - Multi-Model SDR with Guardrails\"):\n",
" result = await Runner.run(sales_manager, message)\n",
"\n",
" final_email = result.final_output\n",
" print(final_email)\n",
"\n",
"except InputGuardrailTripwireTriggered as e:\n",
" print(\"Input guardrail triggered:\", e)\n",
"\n",
"except OutputGuardrailTripwireTriggered as e:\n",
" print(\"Output guardrail triggered:\", e)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Notes\n",
"\n",
"- If an input guardrail triggers, adjust the prompt (for example, remove personal names).\n",
"- If an output guardrail triggers, rerun with tighter instructions.\n",
"- This implementation stays within the tools and concepts taught in the `2_openai` labs.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading