Skip to content

Commit dfeb7ee

Browse files
jssmithclaudetconley1428
authored
Customer service workflow fixes, including continue-as-new (#222)
* Port continue-as-new fix for customer service workflow - Add case-insensitive string matching in FAQ lookup tool - Update init_agents to return tuple with agent map for continue-as-new state management - Implement continue-as-new functionality with proper state serialization - Fix client output to skip duplicate user message on print 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * cleanup --------- Co-authored-by: Claude <[email protected]> Co-authored-by: tconley1428 <[email protected]>
1 parent 8043fb0 commit dfeb7ee

File tree

3 files changed

+71
-25
lines changed

3 files changed

+71
-25
lines changed

openai_agents/customer_service/customer_service.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations as _annotations
22

3+
from typing import Dict, Tuple
4+
35
from agents import Agent, RunContextWrapper, function_tool, handoff
46
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
57
from pydantic import BaseModel
@@ -23,19 +25,20 @@ class AirlineAgentContext(BaseModel):
2325
description_override="Lookup frequently asked questions.",
2426
)
2527
async def faq_lookup_tool(question: str) -> str:
26-
if "bag" in question or "baggage" in question:
28+
question_lower = question.lower()
29+
if "bag" in question_lower or "baggage" in question_lower:
2730
return (
2831
"You are allowed to bring one bag on the plane. "
2932
"It must be under 50 pounds and 22 inches x 14 inches x 9 inches."
3033
)
31-
elif "seats" in question or "plane" in question:
34+
elif "seats" in question_lower or "plane" in question_lower:
3235
return (
3336
"There are 120 seats on the plane. "
3437
"There are 22 business class seats and 98 economy seats. "
3538
"Exit rows are rows 4 and 16. "
3639
"Rows 5-8 are Economy Plus, with extra legroom. "
3740
)
38-
elif "wifi" in question:
41+
elif "wifi" in question_lower:
3942
return "We have free wifi on the plane, join Airline-Wifi"
4043
return "I'm sorry, I don't know the answer to that question."
4144

@@ -74,7 +77,9 @@ async def on_seat_booking_handoff(
7477
### AGENTS
7578

7679

77-
def init_agents() -> Agent[AirlineAgentContext]:
80+
def init_agents() -> Tuple[
81+
Agent[AirlineAgentContext], Dict[str, Agent[AirlineAgentContext]]
82+
]:
7883
"""
7984
Initialize the agents for the airline customer service workflow.
8085
:return: triage agent
@@ -121,7 +126,9 @@ def init_agents() -> Agent[AirlineAgentContext]:
121126

122127
faq_agent.handoffs.append(triage_agent)
123128
seat_booking_agent.handoffs.append(triage_agent)
124-
return triage_agent
129+
return triage_agent, {
130+
agent.name: agent for agent in [faq_agent, seat_booking_agent, triage_agent]
131+
}
125132

126133

127134
class ProcessUserMessageInput(BaseModel):

openai_agents/customer_service/run_customer_service_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ async def main():
6767
CustomerServiceWorkflow.process_user_message, message_input
6868
)
6969
history.extend(new_history)
70-
print(*new_history, sep="\n")
70+
print(*new_history[1:], sep="\n")
7171
except WorkflowUpdateFailedError:
7272
print("** Stale conversation. Reloading...")
7373
length = len(history)
Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations as _annotations
22

33
from agents import (
4-
Agent,
4+
HandoffCallItem,
55
HandoffOutputItem,
66
ItemHelpers,
77
MessageOutputItem,
@@ -12,6 +12,7 @@
1212
TResponseInputItem,
1313
trace,
1414
)
15+
from pydantic import dataclasses
1516
from temporalio import workflow
1617

1718
from openai_agents.customer_service.customer_service import (
@@ -21,32 +22,65 @@
2122
)
2223

2324

25+
@dataclasses.dataclass
26+
class CustomerServiceWorkflowState:
27+
printed_history: list[str]
28+
current_agent_name: str
29+
context: AirlineAgentContext
30+
input_items: list[TResponseInputItem]
31+
32+
2433
@workflow.defn
2534
class CustomerServiceWorkflow:
2635
@workflow.init
27-
def __init__(self, input_items: list[TResponseInputItem] | None = None):
36+
def __init__(
37+
self, customer_service_state: CustomerServiceWorkflowState | None = None
38+
):
2839
self.run_config = RunConfig()
29-
self.chat_history: list[str] = []
30-
self.current_agent: Agent[AirlineAgentContext] = init_agents()
31-
self.context = AirlineAgentContext()
32-
self.input_items = [] if input_items is None else input_items
40+
41+
starting_agent, self.agent_map = init_agents()
42+
self.current_agent = (
43+
self.agent_map[customer_service_state.current_agent_name]
44+
if customer_service_state
45+
else starting_agent
46+
)
47+
self.context = (
48+
customer_service_state.context
49+
if customer_service_state
50+
else AirlineAgentContext()
51+
)
52+
self.printed_history: list[str] = (
53+
customer_service_state.printed_history if customer_service_state else []
54+
)
55+
self.input_items = (
56+
customer_service_state.input_items if customer_service_state else []
57+
)
3358

3459
@workflow.run
35-
async def run(self, input_items: list[TResponseInputItem] | None = None):
60+
async def run(
61+
self, customer_service_state: CustomerServiceWorkflowState | None = None
62+
):
3663
await workflow.wait_condition(
3764
lambda: workflow.info().is_continue_as_new_suggested()
3865
and workflow.all_handlers_finished()
3966
)
40-
workflow.continue_as_new(self.input_items)
67+
workflow.continue_as_new(
68+
CustomerServiceWorkflowState(
69+
printed_history=self.printed_history,
70+
current_agent_name=self.current_agent.name,
71+
context=self.context,
72+
input_items=self.input_items,
73+
)
74+
)
4175

4276
@workflow.query
4377
def get_chat_history(self) -> list[str]:
44-
return self.chat_history
78+
return self.printed_history
4579

4680
@workflow.update
4781
async def process_user_message(self, input: ProcessUserMessageInput) -> list[str]:
48-
length = len(self.chat_history)
49-
self.chat_history.append(f"User: {input.user_input}")
82+
length = len(self.printed_history)
83+
self.printed_history.append(f"User: {input.user_input}")
5084
with trace("Customer service", group_id=workflow.info().workflow_id):
5185
self.input_items.append({"content": input.user_input, "role": "user"})
5286
result = await Runner.run(
@@ -59,33 +93,38 @@ async def process_user_message(self, input: ProcessUserMessageInput) -> list[str
5993
for new_item in result.new_items:
6094
agent_name = new_item.agent.name
6195
if isinstance(new_item, MessageOutputItem):
62-
self.chat_history.append(
96+
self.printed_history.append(
6397
f"{agent_name}: {ItemHelpers.text_message_output(new_item)}"
6498
)
6599
elif isinstance(new_item, HandoffOutputItem):
66-
self.chat_history.append(
100+
self.printed_history.append(
67101
f"Handed off from {new_item.source_agent.name} to {new_item.target_agent.name}"
68102
)
103+
elif isinstance(new_item, HandoffCallItem):
104+
self.printed_history.append(
105+
f"{agent_name}: Handed off to tool {new_item.raw_item.name}"
106+
)
69107
elif isinstance(new_item, ToolCallItem):
70-
self.chat_history.append(f"{agent_name}: Calling a tool")
108+
self.printed_history.append(f"{agent_name}: Calling a tool")
71109
elif isinstance(new_item, ToolCallOutputItem):
72-
self.chat_history.append(
110+
self.printed_history.append(
73111
f"{agent_name}: Tool call output: {new_item.output}"
74112
)
75113
else:
76-
self.chat_history.append(
114+
self.printed_history.append(
77115
f"{agent_name}: Skipping item: {new_item.__class__.__name__}"
78116
)
79117
self.input_items = result.to_input_list()
80118
self.current_agent = result.last_agent
81-
workflow.set_current_details("\n\n".join(self.chat_history))
82-
return self.chat_history[length:]
119+
workflow.set_current_details("\n\n".join(self.printed_history))
120+
121+
return self.printed_history[length:]
83122

84123
@process_user_message.validator
85124
def validate_process_user_message(self, input: ProcessUserMessageInput) -> None:
86125
if not input.user_input:
87126
raise ValueError("User input cannot be empty.")
88127
if len(input.user_input) > 1000:
89128
raise ValueError("User input is too long. Please limit to 1000 characters.")
90-
if input.chat_length != len(self.chat_history):
129+
if input.chat_length != len(self.printed_history):
91130
raise ValueError("Stale chat history. Please refresh the chat.")

0 commit comments

Comments
 (0)