For this, we first define the underlying azure LLM model (note that the following code can be highly simplified if we use OpenAI api key instead of azure):
1from dotenv import load_dotenv
2from agents import Agent, Runner, trace
34load_dotenv(override=True)56from openai import AsyncAzureOpenAI
7from agents import Agent, OpenAIChatCompletionsModel
8import os
910# Configure Azure OpenAI client (use Async version)11azure_client = AsyncAzureOpenAI(12 api_key=os.getenv("AZURE_OPENAI_API_KEY"),13 api_version=os.getenv("AZURE_API_VERSION","2024-10-21"),# Use the latest API version14 azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT")15)1617# Create a model instance18azure_model = OpenAIChatCompletionsModel(19 model=os.getenv("AZURE_OPENAI_MODEL"),# Your Azure deployment name20 openai_client=azure_client
21)
Then we can define the Agent:
1agent = Agent(2 name="Jokester",3 instructions="You are a joke teller",4 model=azure_model
5)67with trace("Telling a joke"):8 result =await Runner.run(agent,"Tell a joke about Autonomous AI Agents")9print(result.final_output)
instructions is in essence the system prompt.
The argument in Runner.run is now our user prompt.
1.3. Results from Agents
1.3.1. Streaming Response
1result = Runner.run_streamed(sales_agent1,input="Write a cold sales email")23asyncfor event in result.stream_events():4if event.type=="raw_response_event"andisinstance(event.data, ResponseTextDeltaEvent):5print(event.data.delta, end="", flush=True)
1.3.2. Ordinary Coroutine Response
1result =await Runner.run(sales_agent1,input="Write a cold sales email")2result.final_output
2. Tools
2.1. What we did in the past ...
In the past we need to:
Define a function
Define the metadata of this function in json
Provide the metadata to LLM model as tools
According to the result from LLM model, get the right tools, apply the arguments from LLM response
For more detail, please revisit our previous article:
OpenAI has later provided a simplified solution via a special annotation:
2.2. @function_tool
2.2.1. Applied to functions
Assume that we have registered an account in Sendgrid, then we define a simple tool:
1import sendgrid
2import os
3from sendgrid.helpers.mail import Mail, Email, To, Content
4from agents import Agent, Runner, trace, function_tool
56@function_tool7defsend_email():8 sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))9 from_email = Email("james.lee@wonderbricks.com")# Change to your verified sender10 to_email = To("machingclee@gmail.com")# Change to your recipient11 content = Content("text/plain","This is an important test email")12 mail = Mail(from_email, to_email,"Test email", content).get()13 response = sg.client.mail.send.post(request_body=mail)14print(response.status_code)
@function_tool turns the function send_test_egmail into something called a DataClass. Let's plug the send_email function into the following:
1from dataclasses import is_dataclass, asdict
2import json
34print(f"Is dataclass? {is_dataclass(send_email)}")56if is_dataclass(send_email):7# excluding the function for readability8 send_email_dict = asdict(send_email)9 clean_dict ={k: v for k, v in send_email_dict.items()10if k !='on_invoke_tool'}11print(json.dumps(clean_dict, indent=2))
For print on line-4:
1Is dataclass? True
For print on line-11:
1{2"name":"send_email",3"description":"Send out an email with the given body to all sales prospects",4"params_json_schema":{5"properties":{6"body":{7"title":"Body",8"type":"string"9}10},11"required":[12"body"13],14"title":"send_email_args",15"type":"object",16"additionalProperties":false17},18"strict_json_schema":true,19"is_enabled":true20}
This is exactly what we have written in 3. Apply the Tools, and now we can bypass this tedious step as the annotation has done it for us.
2.2.2. Applied to agents
We use tools to let agent inject arguments and get a result.
We can also inject arguments into an agent (as a user prompt) and get a result, so can an agent be a tool as well?
YES! Let's define 3 agents separately:
1instructions1 ="""
2You are a sales agent working for ComplAI,
3a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI.
4You write professional, serious cold emails.
5"""67instructions2 ="""
8You are a humorous, engaging sales agent working for ComplAI,
9a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI.
10You write witty, engaging cold emails that are likely to get a response.
11"""1213instructions3 ="""
14You are a busy sales agent working for ComplAI,
15a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI.
16You write concise, to the point cold emails.
17"""1819sales_agent1 = Agent(20 name="Professional Sales Agent",21 instructions=instructions1,22 model=azure_model
23)24sales_agent2 = Agent(25 name="Engaging Sales Agent",26 instructions=instructions2,27 model=azure_model
28)2930sales_agent3 = Agent(31 name="Busy Sales Agent",32 instructions=instructions3,33 model=azure_model
34)
And finally define another agent to apply these tools:
1instructions ="""
2You are a Sales Manager at ComplAI. Your goal is to find the single best cold sales email using the sales_agent tools.
34Follow these steps carefully:
51. Generate Drafts: Use all three sales_agent tools to generate three different email drafts. Do not proceed until all three drafts are ready.
672. Evaluate and Select: Review the drafts and choose the single best email using your judgment of which one is most effective.
893. Use the send_email tool to send the best email (and only the best email) to the user.
1011Crucial Rules:
12- You must use the sales agent tools to generate the drafts — do not write them yourself.
13- You must send ONE email using the send_email tool — never more than one.
14"""1516sales_manager = Agent(name="Sales Manager",17 instructions=instructions,18 tools=agent_tools_to_write_letters,19 model=azure_model)2021message ="Send a cold sales email addressed to 'Dear James Lee'"2223result =await Runner.run(sales_manager, message)
3. Handoffs
A handoff is a mechanism to delegate the workflow (with result) to another agent.
Handoffs and Agents-as-tools are similar:
In both cases, an Agent can collaborate with another Agent
With tools, control responses back
With handoffs, control passes forward
Let's describe a usecase using handoff:
3.1. Handoff Agent
3.1.1. Tools from Agents
1subject_instructions ="""
2You can write a subject for a cold sales email.
3You are given a message and you need to write a subject for an email that is likely to get a response.
4"""56html_instructions ="""
7You can convert a text email body to an HTML email body.
8You are given a text email body which might have some markdown
9and you need to convert it to an HTML email body with simple, clear, compelling layout and design.
10"""1112subject_writer = Agent(name="Email subject writer", instructions=subject_instructions, model=azure_model)13subject_tool = subject_writer.as_tool(tool_name="subject_writer", tool_description="Write a subject for a cold sales email")1415html_converter = Agent(name="HTML email body converter", instructions=html_instructions, model=azure_model)16html_tool = html_converter.as_tool(tool_name="html_converter",tool_description="Convert a text email body to an HTML email body")
3.1.2. Tools from Sendgrid
1@function_tool2defsend_html_email(subject:str, html_body:str)-> Dict[str,str]:3""" Send out an email with the given subject and HTML body to all sales prospects """4 sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))5 from_email = Email("james.lee@wonderbricks.com")# Change to your verified sender6 to_email = To("machingclee@gmail.com")# Change to your recipient7 content = Content("text/html", html_body)8 mail = Mail(from_email, to_email, subject, content).get()9 sg.client.mail.send.post(request_body=mail)10return{"status":"success"}
3.1.3. Declare Handoff Agent
handoff_description is how agent announce itself to the world in case another agent wants to use it.
1instructions ="""
2You are an email formatter and sender. You receive the body of an email to be sent.
3You first use the subject_writer tool to write a subject for the email, then use the html_converter tool to convert the body to HTML.
4Finally, you use the send_html_email tool to send the email with the subject and HTML body.
5"""67emailer_agent = Agent(8 name="Email Manager",9 instructions=instructions,10 tools=[subject_tool, html_tool, send_html_email],11 model=azure_model,12 handoff_description="Convert an email to HTML and send it")
3.2. Combine the above, From Agent1 to Handoff Agent
Combining everything above, we have (recall also that we have defined agent1_tool, agent2_tool and agent3_tool in 〈2.2.2. Applied to agents〉):
1sales_manager_instructions ="""
2You are a Sales Manager at ComplAI. Your goal is to find the single best cold sales email using the sales_agent tools.
34Follow these steps carefully:
51. Generate Drafts: Use all three sales_agent tools to generate three different email drafts. Do not proceed until all three drafts are ready.
672. Evaluate and Select: Review the drafts and choose the single best email using your judgment of which one is most effective.
8You can use the tools multiple times if you're not satisfied with the results from the first try.
9103. Handoff for Sending: Pass ONLY the winning email draft to the 'Email Manager' agent. The Email Manager will take care of formatting and sending.
1112Crucial Rules:
13- You must use the sales agent tools to generate the drafts — do not write them yourself.
14- You must hand off exactly ONE email to the Email Manager — never more than one.
15"""1617new_sales_manager = Agent(18 name="Sales Manager",19 instructions=sales_manager_instructions,20 tools=[agent1_tool, agent2_tool, agent3_tool],21 handoffs=[emailer_agent],22 model=azure_model)2324message ="Send out a cold sales email addressed to Dear James Lee"2526await Runner.run(new_sales_manager, message)
4. Guardrails
4.1. What is it?
Guardrail severes as a guard to validate our input and output, which determines whether our flow should continue given an input/output is obtained.
If the answer is no, it will throw an exception to stop our program from proceeding any further.
4.2. Define Guardrail Agent
1from agents import Agent, Runner, trace, function_tool, \
2 OpenAIChatCompletionsModel, input_guardrail, GuardrailFunctionOutput
34classNameCheckOutput(BaseModel):5 is_name_in_message:bool6 name:str78guardrail_agent = Agent(9 name="Name check",10 instructions="Check if the user is including someone's personal name in what they want you to do.",11 output_type=NameCheckOutput,12 model="gpt-4o-mini"13)
1careful_sales_manager = Agent(2 name="Sales Manager",3 instructions=sales_manager_instructions,4 tools=tools,5 handoffs=[emailer_agent],6 model="gpt-4o-mini",7 input_guardrails=[guardrail_against_name]8)910message ="Send out a cold sales email addressed to Dear CEO from Alice"1112with trace("Protected Automated SDR"):13 result =await Runner.run(careful_sales_manager, message)