Skip to content
Merged
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
9 changes: 9 additions & 0 deletions packages/uipath/samples/ticket-triage-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
UIPATH_URL=https://cloud.uipath.com/your_org/your_tenant
UIPATH_ACCESS_TOKEN=your_access_token_here

# Required for the escalation path: Action Center tasks must be created in
# an Orchestrator folder.
UIPATH_FOLDER_PATH=Shared

# Required for Tier 1 triage: API key for TypeSafe AI's Jev model.
TYPESAFE_API_KEY=your_typesafe_api_key_here
155 changes: 155 additions & 0 deletions packages/uipath/samples/ticket-triage-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Ticket Triage Agent: System 1 / System 2 Pattern

This sample demonstrates a two-tier triage pattern for support tickets:

1. **Tier 1 (System 1) - fast structured triage.** A "System One" model
takes the ticket text and a set of typed questions, and returns
calibrated typed answers (a classification, a 0-1 probability, and a
score) instead of free text - orders of magnitude cheaper and faster than an
LLM call. This sample uses TypeSafe AI's `Jev` model via the
`typesafe-sdk` package.
Comment thread
akshaylive marked this conversation as resolved.
2. **Tier 2 (System 2) - branch on the triage result.**
- If the ticket is urgent, the customer sounds frustrated, or the
department routing is low-confidence, the agent **escalates to a
human** via a UiPath Action Center QuickForm task, attaching the Tier 1
decision as context so the reviewer isn't starting cold.
- Otherwise, the agent calls a **real LLM** (UiPath LLM Gateway) with a
department-specific system prompt to draft a reply, and returns it as
an auto-resolved ticket. No human, and no expensive LLM call for
routing, is needed for the common case.

## About the "Jev" model (important - read this)

This sample calls TypeSafe AI's real `typesafe-sdk` PyPI package, not a
mock. `typesafe-sdk` is a young package (first published 2026-09-09), so
before wiring it in we statically inspected the wheel's source (no
install/execution): it's a normal, apparently auto-generated API client
(the response schemas reference an OpenAPI spec) with no `eval`/`exec`/
`subprocess` calls, and no exfiltration of environment variables. Its
`httpx2` dependency turned out to be an unrelated, independent package
(the pydantic org's next-generation `httpx`), not something specific to
TypeSafe AI. If you're pulling a new AI SDK into your own project, reading
the source before trusting it is a cheap, worthwhile step regardless of how
long the package has existed.

```python
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

client = TypeSafeClient() # reads TYPESAFE_API_KEY from the environment
response = client.system_one(state=..., questions={...})
```

`pyproject.toml` pins `typesafe-sdk>=0.7.0` rather than leaving it
unbounded, since `uipath`'s own `uv` install already applies a
minimum-package-age safety check that skips the very newest release.

## Prerequisites

* [UV package manager](https://docs.astral.sh/uv/) installed
* A TypeSafe AI API key (`TYPESAFE_API_KEY`) for the Tier 1 triage step
* A UiPath Orchestrator tenant with:
* Access to the LLM Gateway (for the auto-reply path)
* Access to Action Center (for the escalation path)

## Setup

### Step 1: Create and activate a virtual environment

```bash
uv venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
```

### Step 2: Install dependencies

```bash
uv sync
```

### Step 3: Configure credentials

Copy `.env.example` to `.env` and fill in your Orchestrator URL, access
token, and TypeSafe AI API key:

```bash
cp .env.example .env
```

### Step 4: Initialize the agent

```bash
uv run uipath init
```

### Step 5: Run the agent

```bash
uipath run main --input-file input.json
```

Try editing `input.json` to see both branches:

* A calm, clearly-worded billing request (like the default input) -> the
agent auto-drafts a reply via the LLM Gateway and returns it directly.
* An urgent or angry-sounding message (e.g. mentioning "urgent", "ASAP", or
"furious") -> the agent creates an Action Center task instead and returns
its task id.

## How it works

1. `triage_ticket` sends the ticket's subject/message to `Jev` (via
`TypeSafeClient.system_one`) as `state`, along with three typed
`questions` (`department: Choice`, `is_urgent: Noul`,
`frustration: Score`), and gets back a `TriageDecision`.
2. `needs_escalation` checks the triage output against fixed thresholds
(urgency, frustration, routing confidence) in `main.py`.
3. On escalation, `escalate_to_human` creates an Action Center QuickForm
task via `client.tasks.create_quickform(...)`, with the ticket and the
triage decision as task data for the reviewer.
4. Otherwise, `draft_auto_reply` calls `client.llm.chat_completions(...)`
(UiPath LLM Gateway) with a department-specific system prompt to draft a
reply.

## Evaluations

`evaluations/eval-sets/default.json` exercises the Tier 1 routing/escalation
logic against 11 tickets (billing, technical, and sales; calm and
auto-replied vs. urgent/angry/low-confidence and escalated), using two
evaluators:

* `DepartmentRoutingEvaluator` (`evaluations/evaluators/department-routing.json`)
- a `uipath-multiclass-classification` evaluator checking
`triage.department` against the expected class.
* `EscalationDecisionEvaluator` (`evaluations/evaluators/escalation-decision.json`)
- a `uipath-binary-classification` evaluator checking the `escalated`
boolean against the expected outcome.

Two cases (`sales-demo-request-wrong-department`,
`billing-refund-calm-wrong-escalation`) have deliberately wrong ground truth,
mirroring the pattern in `classification_agent`, to demonstrate the
evaluators catching a mismatch.

Every case calls the real Jev API (`TYPESAFE_API_KEY`), and Tier 2 always
runs too: `draft_auto_reply` and `escalate_to_human` call real UiPath
services (LLM Gateway, Action Center), so the full eval set requires valid
credentials in `.env` for all 11 cases, not just the ones the two
evaluators grade.

**Heads up:** none of the current evaluators grade `auto_reply` or
`action_task_id`, but the ~3 cases whose triage escalates still create a
real Action Center QuickForm task each run, and nothing in this sample
cleans them up. Expect leftover tasks in Action Center after repeated
`uipath eval` runs.

```bash
uipath eval
```

## Publish your coded agent

Once tested locally, publish the agent to Orchestrator:

```bash
uipath pack
uipath publish
```
4 changes: 4 additions & 0 deletions packages/uipath/samples/ticket-triage-agent/bindings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"version": "2.0",
"resources": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"version": "1.0",
"id": "TicketTriageEval",
"name": "Ticket Triage Routing & Escalation Evaluation",
"evaluatorRefs": [
"DepartmentRoutingEvaluator",
"EscalationDecisionEvaluator"
],
"evaluations": [
{
"id": "billing-refund-calm",
"name": "Billing - calm refund request (auto-reply)",
"inputs": {
"subject": "Duplicate charge on my account",
"message": "Hi, I was charged twice for order A-104. Could you please refund the duplicate charge? Thanks for your help."
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "billing" },
"EscalationDecisionEvaluator": { "expectedClass": "false" }
}
},
{
"id": "billing-invoice-question",
"name": "Billing - invoice clarification (auto-reply)",
"inputs": {
"subject": "Question about my invoice",
"message": "I noticed my invoice this month includes a charge I do not recognize. Could you clarify the payment breakdown? Thanks."
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "billing" },
"EscalationDecisionEvaluator": { "expectedClass": "false" }
}
},
{
"id": "technical-bug-report",
"name": "Technical - routine bug report (auto-reply)",
"inputs": {
"subject": "App crashes on login",
"message": "The app crashes every time I try to log in. This started after the last update. Please help me fix this bug."
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "technical" },
"EscalationDecisionEvaluator": { "expectedClass": "false" }
}
},
{
"id": "technical-outage-urgent",
"name": "Technical - urgent outage (escalate: high urgency)",
"inputs": {
"subject": "URGENT: service down",
"message": "Our integration has been down for 3 days and this is unacceptable. We need this fixed immediately, it is critical."
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "technical" },
"EscalationDecisionEvaluator": { "expectedClass": "true" }
}
},
{
"id": "sales-pricing-question",
"name": "Sales - pricing inquiry (auto-reply)",
"inputs": {
"subject": "Question about pricing plans",
"message": "Hi, I would like a quote for upgrading to your premium plan. Can you share pricing details?"
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "sales" },
"EscalationDecisionEvaluator": { "expectedClass": "false" }
}
},
{
"id": "sales-demo-request",
"name": "Sales - demo request (auto-reply)",
"inputs": {
"subject": "Requesting a demo",
"message": "We are considering your product and would like to schedule a demo to see the upgrade options."
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "sales" },
"EscalationDecisionEvaluator": { "expectedClass": "false" }
}
},
{
"id": "billing-angry-customer",
"name": "Billing - angry customer (escalate: high frustration)",
"inputs": {
"subject": "This is ridiculous",
"message": "I have been charged the wrong amount again and I am furious, this is unacceptable and terrible service."
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "billing" },
"EscalationDecisionEvaluator": { "expectedClass": "true" }
}
},
{
"id": "technical-calm-minor-bug",
"name": "Technical - minor, no-rush bug (auto-reply)",
"inputs": {
"subject": "Minor bug report",
"message": "Just a small bug: the export button is not working as expected. No rush, whenever you get a chance, thanks."
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "technical" },
"EscalationDecisionEvaluator": { "expectedClass": "false" }
}
},
{
"id": "ambiguous-low-confidence",
"name": "Ambiguous ticket (escalate: low routing confidence)",
"inputs": {
"subject": "Quick question",
"message": "Can someone tell me more about how things work around here?"
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "billing" },
"EscalationDecisionEvaluator": { "expectedClass": "true" }
}
},
{
"id": "sales-demo-request-wrong-department",
"name": "Sales - demo request (DELIBERATELY WRONG: ground truth department set to 'technical')",
"inputs": {
"subject": "Requesting a demo",
"message": "We are considering your product and would like to schedule a demo to see the upgrade options."
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "technical" },
"EscalationDecisionEvaluator": { "expectedClass": "false" }
}
},
{
"id": "billing-refund-calm-wrong-escalation",
"name": "Billing - calm refund request (DELIBERATELY WRONG: ground truth escalation set to 'true')",
"inputs": {
"subject": "Duplicate charge on my account",
"message": "Hi, I was charged twice for order A-104. Could you please refund the duplicate charge? Thanks for your help."
},
"evaluationCriterias": {
"DepartmentRoutingEvaluator": { "expectedClass": "billing" },
"EscalationDecisionEvaluator": { "expectedClass": "true" }
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "1.0",
"id": "DepartmentRoutingEvaluator",
"name": "DepartmentRoutingEvaluator",
"description": "Checks whether Tier 1 (Jev) routed the ticket to the expected department",
"evaluatorTypeId": "uipath-multiclass-classification",
"evaluatorConfig": {
"name": "DepartmentRoutingEvaluator",
"targetOutputKey": "triage.department",
"classes": ["billing", "technical", "sales"],
"metricType": "precision",
"averaging": "macro",
"defaultEvaluationCriteria": {
"expectedClass": "technical"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": "1.0",
"id": "EscalationDecisionEvaluator",
"name": "EscalationDecisionEvaluator",
"description": "Checks whether the agent escalated to a human (Action Center) exactly when it should have",
"evaluatorTypeId": "uipath-binary-classification",
"evaluatorConfig": {
"name": "EscalationDecisionEvaluator",
"targetOutputKey": "escalated",
"positiveClass": "true",
"metricType": "f-score",
"defaultEvaluationCriteria": {
"expectedClass": "false"
}
}
}
4 changes: 4 additions & 0 deletions packages/uipath/samples/ticket-triage-agent/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"subject": "Duplicate charge on my account",
"message": "Hi, I was charged twice for order A-104. Could you please refund the duplicate charge? Thanks for your help."
}
Loading
Loading