2-assistants/7_orchestration.py (136 lines of code) (raw):
import json
from typing import Optional
from pydantic import BaseModel
from demo_util import color, function_to_schema
from openai import OpenAI
class Agent(BaseModel):
name: str = "Agent"
model: str = "gpt-4o"
instructions: str = "You are a helpful Agent"
tools: list = []
class Response(BaseModel):
agent: Optional[Agent]
messages: list
client = OpenAI()
# ===== Runtime =====
def run_full_turn(agent, messages):
current_agent = agent
num_init_messages = len(messages)
messages = messages.copy()
while True:
# turn python functions into tools and save a reverse map
tool_schemas = [function_to_schema(tool) for tool in current_agent.tools]
tools = {tool.__name__: tool for tool in current_agent.tools}
# === 1. get openai completion ===
response = client.chat.completions.create(
model=agent.model,
messages=[{"role": "system", "content": current_agent.instructions}]
+ messages,
tools=tool_schemas or None,
)
message = response.choices[0].message
messages.append(message)
if message.content: # print agent response
print(color(f"{current_agent.name}:", "yellow"), message.content)
if not message.tool_calls: # if finished handeling tool calls, break
break
# === 2. handle tool calls ===
for tool_call in message.tool_calls:
result = execute_tool_call(tool_call, tools, current_agent.name)
if type(result) is Agent: # if agent transfer, update current agent
current_agent = result
result = (
f"Transferred to {current_agent.name}. Adopt persona immediately."
)
result_message = {
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
}
messages.append(result_message)
# ==== 3. return last agent used and new messages =====
return Response(agent=current_agent, messages=messages[num_init_messages:])
def execute_tool_call(tool_call, tools, agent_name):
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(color(f"{agent_name}:", "yellow"), color(f"{name}({args})", "magenta"))
return tools[name](**args) # call corresponding function with provided arguments
# ===== Agents =====
def escalate_to_human(summary):
"""Only call this if explicitly asked to."""
print(color("Escalating to human agent...", "red"))
print("\n=== Escalation Report ===")
print(f"Summary: {summary}")
print("=========================\n")
exit()
def transfer_to_sales_agent():
"""User for anything sales or buying related."""
return sales_agent
def transfer_to_issues_and_repairs():
"""User for issues, repairs, or refunds."""
return issues_and_repairs_agent
def transfer_back_to_triage():
"""Call this if the user brings up a topic outside of your purview,
including escalating to human."""
return triage_agent
triage_agent = Agent(
name="Triage Agent",
instructions=(
"You are a customer service bot for ACME Inc. "
"Introduce yourself. Always be very brief. "
"Gather information to direct the customer to the right department. "
"But make your questions subtle and natural."
),
tools=[transfer_to_sales_agent, transfer_to_issues_and_repairs, escalate_to_human],
)
def execute_order(product, price: int):
"""Price should be in USD."""
print("\n\n=== Order Summary ===")
print(f"Product: {product}")
print(f"Price: ${price}")
print("=================\n")
confirm = input("Confirm order? y/n: ").strip().lower()
if confirm == "y":
print(color("Order execution successful!", "green"))
return "Success"
else:
print(color("Order cancelled!", "red"))
return "User cancelled order."
sales_agent = Agent(
name="Sales Agent",
instructions=(
"You are a sales agent for ACME Inc."
"Always answer in a sentence or less."
"Follow the following routine with the user:"
"1. Ask them about any problems in their life related to catching roadrunners.\n"
"2. Casually mention one of ACME's crazy made-up products can help.\n"
" - Don't mention price.\n"
"3. Once the user is bought in, drop a ridiculous price.\n"
"4. Only after everything, and if the user says yes, "
"tell them a crazy caveat and execute their order.\n"
""
),
tools=[execute_order, transfer_back_to_triage],
)
def look_up_item(search_query):
"""Use to find item ID.
Search query can be a description or keywords."""
item_id = "item_132612938"
print(color("Found item:", "green"), item_id)
return item_id
def execute_refund(item_id, reason="not provided"):
print(color("\n\n=== Refund Summary ===", "green"))
print(color(f"Item ID: {item_id}", "green"))
print(color(f"Reason: {reason}", "green"))
print("=================\n")
print(color("Refund execution successful!", "green"))
return "success"
issues_and_repairs_agent = Agent(
name="Issues and Repairs Agent",
instructions=(
"You are a customer support agent for ACME Inc."
"Always answer in a sentence or less."
"Follow the following routine with the user:"
"1. First, ask probing questions and understand the user's problem deeper.\n"
" - unless the user has already provided a reason.\n"
"2. Propose a fix (make one up).\n"
"3. ONLY if not satisfied, offer a refund.\n"
"4. If accepted, search for the ID and then execute refund."
""
),
tools=[execute_refund, look_up_item, transfer_back_to_triage],
)
# ===== Demo Loop =====
agent = triage_agent
messages = []
while True:
user = input(color("User: ", "blue") + "\033[90m")
messages.append({"role": "user", "content": user})
response = run_full_turn(agent, messages)
agent = response.agent
messages.extend(response.messages)