Skip to content

Commit 9a3fde1

Browse files
committed
chore: improve ticket classification sample
1 parent b97f81b commit 9a3fde1

4 files changed

Lines changed: 69 additions & 31 deletions

File tree

samples/ticket-classification/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Support Ticket Classification System
22

3-
Use LangGraph with Azure OpenAI to automatically classify support tickets into predefined categories with confidence scores. UiPath Orchestrator API integration for human approval step.
3+
Use LangGraph with Azure OpenAI to automatically classify support tickets into predefined categories with confidence scores. UiPath Action Center integration for human approval step.
44

55
## Debug
66

@@ -52,7 +52,8 @@ The input ticket should be in the following format:
5252
```json
5353
{
5454
"message": "The ticket message or description",
55-
"ticket_id": "Unique ticket identifier"
55+
"ticket_id": "Unique ticket identifier",
56+
"assignee"[optional]: "username or email of the person assigned to handle escalations"
5657
}
5758
```
5859

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1-
%%{init: {'flowchart': {'curve': 'linear'}}}%%
1+
---
2+
config:
3+
flowchart:
4+
curve: linear
5+
---
26
graph TD;
37
__start__([<p>__start__</p>]):::first
48
classify(classify)
5-
create_action(create_action)
6-
human_approval(human_approval)
9+
human_approval_node(human_approval_node)
710
notify_team(notify_team)
811
__end__([<p>__end__</p>]):::last
912
__start__ --> classify;
10-
classify --> create_action;
11-
create_action --> human_approval;
12-
human_approval --> notify_team;
13+
classify --> human_approval_node;
1314
notify_team --> __end__;
15+
human_approval_node -.-> classify;
16+
human_approval_node -.-> notify_team;
1417
classDef default fill:#f2f0ff,line-height:1.2
1518
classDef first fill-opacity:0
1619
classDef last fill:#bfb6fc

samples/ticket-classification/escalation app/escalation_agent_app.uiapp

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

samples/ticket-classification/main.py

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
import os
3-
from typing import Literal, Optional
3+
from typing import Literal, Optional, List
44

55
from langchain_openai import AzureChatOpenAI
66
from langchain_core.output_parsers import PydanticOutputParser
@@ -10,14 +10,15 @@
1010
from pydantic import BaseModel, Field
1111

1212
from uipath_sdk import UiPathSDK
13-
13+
from uipath_sdk._models import CreateAction
1414
logger = logging.getLogger(__name__)
1515

1616
uipath = UiPathSDK()
1717

1818
class GraphInput(BaseModel):
1919
message: str
2020
ticket_id: str
21+
assignee: Optional[str]
2122

2223
class GraphOutput(BaseModel):
2324
label: str
@@ -26,9 +27,11 @@ class GraphOutput(BaseModel):
2627
class GraphState(BaseModel):
2728
message: str
2829
ticket_id: str
30+
assignee: Optional[str] = None
2931
label: Optional[str] = None
3032
confidence: Optional[float] = None
31-
33+
predicted_categories: List[str] = []
34+
human_approval: Optional[bool] = None
3235

3336
class TicketClassification(BaseModel):
3437
label: Literal["security", "error", "system", "billing", "performance"] = Field(
@@ -78,6 +81,10 @@ def get_azure_openai_api_key() -> str:
7881

7982
return api_key
8083

84+
def decide_next_node(state: GraphState) -> Literal["classify", "notify_team"]:
85+
if state.human_approval is True:
86+
return "notify_team"
87+
return "classify"
8188

8289
async def classify(state: GraphState) -> GraphState:
8390
"""Classify the support ticket using LLM."""
@@ -87,51 +94,77 @@ async def classify(state: GraphState) -> GraphState:
8794
azure_endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
8895
api_version="2024-10-21"
8996
)
97+
new_state = GraphState(
98+
message=state.message,
99+
ticket_id=state.ticket_id,
100+
assignee=state.assignee,
101+
predicted_categories=state.predicted_categories.copy(),
102+
human_approval=state.human_approval
103+
)
104+
105+
if len(new_state.predicted_categories) > 0:
106+
prompt.append(("user", f"The ticket is 100% not part of the following categories '{new_state.predicted_categories}'. Choose another one."))
107+
90108
_prompt = prompt.partial(
91109
format_instructions=output_parser.get_format_instructions()
92110
)
93111
chain = _prompt | llm | output_parser
94112

95113
try:
96-
result = await chain.ainvoke({"ticket_text": state.message})
97-
print(result)
98-
state.label = result.label
99-
state.confidence = result.confidence
114+
result = await chain.ainvoke({"ticket_text": new_state.message})
115+
new_state.label = result.label
116+
new_state.predicted_categories.append(result.label)
117+
new_state.confidence = result.confidence
100118
logger.info(
101119
f"Ticket classified with label: {result.label} confidence score: {result.confidence}"
102120
)
103-
return state
121+
return new_state
104122
except Exception as e:
105123
logger.error(f"Classification failed: {str(e)}")
106-
state.label = "error"
107-
state.confidence = 0.0
108-
return state
124+
return GraphState(
125+
message=new_state.message,
126+
ticket_id=new_state.ticket_id,
127+
assignee=new_state.assignee,
128+
predicted_categories=new_state.predicted_categories,
129+
human_approval=new_state.human_approval,
130+
label="error",
131+
confidence=0.0
132+
)
109133

110134
async def wait_for_human(state: GraphState) -> GraphState:
111135
logger.info("Wait for human approval")
112-
feedback = interrupt(f"Label: {state.label} Confidence: {state.confidence}")
113-
114-
if isinstance(feedback, bool) and feedback is True:
115-
return Command(goto="notify_team")
116-
else:
117-
return Command(goto=END)
136+
action_data = interrupt(CreateAction(name="escalation_agent_app",
137+
title="Action Required: Review classification",
138+
data={
139+
"AgentOutput": f"This is how I classified the ticket: '{state.ticket_id}', with message '{state.message}' \n Label: '{state.label}' Confidence: '{state.confidence}'",
140+
"AgentName": "ticket-classification "},
141+
app_version=1,
142+
assignee=state.assignee,
143+
))
144+
new_state = GraphState(
145+
message=state.message,
146+
ticket_id=state.ticket_id,
147+
assignee=state.assignee,
148+
predicted_categories=state.predicted_categories.copy(),
149+
human_approval=isinstance(action_data["Answer"], bool) and action_data["Answer"] is True
150+
)
151+
return new_state
118152

119-
async def notify_team(state: GraphState) -> GraphState:
153+
async def notify_team(state: GraphState) -> GraphOutput:
120154
logger.info("Send team email notification")
121-
print(state)
122-
return state
155+
return GraphOutput(label=state.label, confidence=state.confidence)
123156

124157
"""Process a support ticket through the workflow."""
125158

126159
builder = StateGraph(GraphState, input=GraphInput, output=GraphOutput)
127160

128161
builder.add_node("classify", classify)
129-
builder.add_node("human_approval", wait_for_human)
162+
builder.add_node("human_approval_node", wait_for_human)
130163
builder.add_node("notify_team", notify_team)
131164

132165
builder.add_edge(START, "classify")
133-
builder.add_edge("classify", "human_approval")
134-
builder.add_edge("human_approval", "notify_team")
166+
builder.add_edge("classify", "human_approval_node")
167+
builder.add_conditional_edges("human_approval_node", decide_next_node)
135168
builder.add_edge("notify_team", END)
136169

137170

0 commit comments

Comments
 (0)