-
Notifications
You must be signed in to change notification settings - Fork 30
sample: add ticket-triage-agent (System 1/System 2 pattern with Jev) #1907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+608
−0
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
0ae4594
feat(samples): add ticket-triage-agent with System 1/System 2 triage
akshaylive cab8e9e
fix(ticket-triage-agent): use the real typesafe-sdk instead of a mock
akshaylive cdd816a
fix(ticket-triage-agent): fail fast on missing folder/task id, fix wo…
akshaylive ae3dd74
fix(ticket-triage-agent): run blocking triage/escalation off the even…
akshaylive 2453cff
fix(ticket-triage-agent): address remaining review findings
akshaylive File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
| 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 | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "version": "2.0", | ||
| "resources": [] | ||
| } |
143 changes: 143 additions & 0 deletions
143
packages/uipath/samples/ticket-triage-agent/evaluations/eval-sets/default.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" } | ||
| } | ||
| } | ||
| ] | ||
| } |
17 changes: 17 additions & 0 deletions
17
packages/uipath/samples/ticket-triage-agent/evaluations/evaluators/department-routing.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
| } | ||
| } | ||
| } |
16 changes: 16 additions & 0 deletions
16
packages/uipath/samples/ticket-triage-agent/evaluations/evaluators/escalation-decision.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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." | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.