Skip to content

Commit 2105b10

Browse files
Chibi Vikramclaude
andcommitted
feat: add calculator agent with RPA process invocation
Add new agent variant demonstrating suspend/resume with calculator RPA process: **New Files:** - graph_calculator.py - Calculator agent invoking RPA process for arithmetic operations - agent-calculator.py - Symlink to graph_calculator.py for entry point - agent-calculator.mermaid - Visual flowchart for calculator agent - evaluations/eval-sets/test_calculator_process.json - Evaluation set with 4 test cases: * Addition: 12 + 12 = 24 * Subtraction: 50 - 20 = 30 * Multiplication: 7 * 8 = 56 * Division: 100 / 4 = 25 **Updated Files:** - entry-points.json - Add agent-calculator entry point definition - README.md - Document calculator variant in "Three Graph Variants" section **Key Features:** - Demonstrates InvokeProcess with structured input (operands + operator) - Shows how to extract calculation results from RPA process output - Reuses existing uipath-contains evaluator for result validation - Complete with evaluation set for automated testing **Usage:** ```bash uv run uipath eval agent-calculator evaluations/eval-sets/test_calculator_process.json ``` This complements the existing suspend/resume sample with a practical computational workflow example. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 09393b1 commit 2105b10

6 files changed

Lines changed: 302 additions & 2 deletions

File tree

samples/tool-calling-suspend-resume/README.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ async def invoke_process_node(state: State) -> State:
9292
### Core Agent Files
9393
- **`graph.py`** - Agent using real `InvokeProcess` for RPA integration
9494
- **`graph_simple.py`** - Simplified agent using dict payload (no auth needed)
95-
- **`langgraph.json`** - Defines graph entrypoints: `graph` and `agent-simple`
95+
- **`graph_calculator.py`** - Calculator agent invoking RPA process for calculations
96+
- **`langgraph.json`** - Defines graph entrypoints: `graph`, `agent-simple`, and `agent-calculator`
9697

9798
### Demo & Testing
9899
- **`demo_suspend_resume.py`** - 🎬 **START HERE!** Interactive demo of suspend/resume
@@ -245,7 +246,7 @@ Later, when the RPA process completes:
245246
3. Agent completes execution
246247
4. **Evaluators run** on final output
247248

248-
## Two Graph Variants
249+
## Three Graph Variants
249250

250251
### 1. graph.py (Production)
251252
Uses real `InvokeProcess` for RPA integration:
@@ -273,6 +274,33 @@ resume_data = interrupt({
273274

274275
Perfect for local development and testing suspend/resume logic.
275276

277+
### 3. graph_calculator.py (Calculator with RPA)
278+
Demonstrates calculator operations via RPA process invocation:
279+
```python
280+
from uipath.platform.common import InvokeProcess
281+
282+
invoke_request = InvokeProcess(
283+
name="Calculator",
284+
input_arguments={
285+
"a": state.a,
286+
"b": state.b,
287+
"operator": state.operator,
288+
},
289+
process_folder_path="Shared",
290+
)
291+
process_output = interrupt(invoke_request)
292+
```
293+
294+
This variant shows how to:
295+
- Pass structured input to RPA processes (operands and operator)
296+
- Extract calculation results from process output
297+
- Use suspend/resume for computational RPA workflows
298+
299+
**Test with evaluations:**
300+
```bash
301+
uv run uipath eval agent-calculator evaluations/eval-sets/test_calculator_process.json
302+
```
303+
276304
## Common Patterns
277305

278306
### Pattern 1: Simple Suspend/Resume
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
%%{init: {'flowchart': {'curve': 'linear'}}}%%
2+
graph TD;
3+
__start__([<p>__start__</p>]):::startclass;
4+
calculator_process(calculator_process):::otherclass;
5+
__end__([<p>__end__</p>]):::endclass;
6+
__start__ --> calculator_process;
7+
calculator_process --> __end__;
8+
classDef startclass fill:#ffdfba;
9+
classDef endclass fill:#baffc9;
10+
classDef otherclass fill:#fad7de;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
graph_calculator.py

samples/tool-calling-suspend-resume/entry-points.json

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,87 @@
153153
}
154154
]
155155
}
156+
},
157+
{
158+
"filePath": "agent-calculator",
159+
"uniqueId": "a1b2c3d4-5e6f-7g8h-9i0j-1k2l3m4n5o6p",
160+
"type": "agent",
161+
"input": {
162+
"type": "object",
163+
"properties": {
164+
"a": {
165+
"title": "Operand A",
166+
"type": "integer"
167+
},
168+
"b": {
169+
"title": "Operand B",
170+
"type": "integer"
171+
},
172+
"operator": {
173+
"title": "Operator",
174+
"type": "string"
175+
}
176+
},
177+
"required": [
178+
"a",
179+
"b",
180+
"operator"
181+
]
182+
},
183+
"output": {
184+
"type": "object",
185+
"properties": {
186+
"result": {
187+
"title": "Result",
188+
"type": "string"
189+
},
190+
"calculation": {
191+
"title": "Calculation",
192+
"type": "string"
193+
}
194+
},
195+
"required": [
196+
"result",
197+
"calculation"
198+
]
199+
},
200+
"graph": {
201+
"nodes": [
202+
{
203+
"id": "__start__",
204+
"name": "__start__",
205+
"type": "__start__",
206+
"subgraph": null,
207+
"metadata": {}
208+
},
209+
{
210+
"id": "calculator_process",
211+
"name": "calculator_process",
212+
"type": "node",
213+
"subgraph": null,
214+
"metadata": {}
215+
},
216+
{
217+
"id": "__end__",
218+
"name": "__end__",
219+
"type": "__end__",
220+
"subgraph": null,
221+
"metadata": {}
222+
}
223+
],
224+
"edges": [
225+
{
226+
"source": "__start__",
227+
"target": "calculator_process",
228+
"label": null
229+
},
230+
{
231+
"source": "calculator_process",
232+
"target": "__end__",
233+
"label": null
234+
}
235+
]
236+
}
156237
}
157238
]
158239
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"version": "1.0",
3+
"id": "test-calculator-process",
4+
"name": "Calculator Process Test (Suspend/Resume with RPA)",
5+
"description": "Tests suspend/resume pattern with calculator RPA process invocation. Note: This eval set demonstrates process invocation - evaluations will suspend while process runs and complete after resume.",
6+
"evaluatorRefs": ["uipath-contains"],
7+
"evaluations": [
8+
{
9+
"id": "test_calculator_addition",
10+
"name": "Calculator: Addition (12 + 12)",
11+
"inputs": {
12+
"a": 12,
13+
"b": 12,
14+
"operator": "+"
15+
},
16+
"evaluationCriterias": {
17+
"uipath-contains": {
18+
"searchText": "24"
19+
}
20+
}
21+
},
22+
{
23+
"id": "test_calculator_subtraction",
24+
"name": "Calculator: Subtraction (50 - 20)",
25+
"inputs": {
26+
"a": 50,
27+
"b": 20,
28+
"operator": "-"
29+
},
30+
"evaluationCriterias": {
31+
"uipath-contains": {
32+
"searchText": "30"
33+
}
34+
}
35+
},
36+
{
37+
"id": "test_calculator_multiplication",
38+
"name": "Calculator: Multiplication (7 * 8)",
39+
"inputs": {
40+
"a": 7,
41+
"b": 8,
42+
"operator": "*"
43+
},
44+
"evaluationCriterias": {
45+
"uipath-contains": {
46+
"searchText": "56"
47+
}
48+
}
49+
},
50+
{
51+
"id": "test_calculator_division",
52+
"name": "Calculator: Division (100 / 4)",
53+
"inputs": {
54+
"a": 100,
55+
"b": 4,
56+
"operator": "/"
57+
},
58+
"evaluationCriterias": {
59+
"uipath-contains": {
60+
"searchText": "25"
61+
}
62+
}
63+
}
64+
]
65+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""Calculator agent demonstrating suspend/resume with RPA process invocation for calculations."""
2+
3+
import logging
4+
5+
from langgraph.constants import END, START
6+
from langgraph.graph import StateGraph
7+
from langgraph.types import interrupt
8+
from pydantic import BaseModel
9+
from uipath.platform.common import InvokeProcess
10+
11+
# Configure logging
12+
logging.basicConfig(
13+
level=logging.INFO,
14+
format="%(asctime)s [%(levelname)s] %(message)s",
15+
datefmt="%Y-%m-%d %H:%M:%S",
16+
)
17+
logger = logging.getLogger(__name__)
18+
19+
20+
class Input(BaseModel):
21+
"""Input for the calculator agent."""
22+
23+
a: int
24+
b: int
25+
operator: str
26+
27+
28+
class Output(BaseModel):
29+
"""Output from the calculator agent."""
30+
31+
result: str
32+
calculation: str
33+
34+
35+
class State(BaseModel):
36+
"""Internal state for the calculator agent."""
37+
38+
a: int
39+
b: int
40+
operator: str
41+
result: str = ""
42+
calculation: str = ""
43+
44+
45+
async def calculator_process_node(state: State) -> State:
46+
"""Node that invokes an RPA calculator process and suspends execution."""
47+
logger.info("=" * 80)
48+
logger.info("CALCULATOR NODE: Starting calculator_process_node")
49+
logger.info(f"CALCULATOR NODE: Operand A: {state.a}")
50+
logger.info(f"CALCULATOR NODE: Operand B: {state.b}")
51+
logger.info(f"CALCULATOR NODE: Operator: {state.operator}")
52+
53+
# Create an InvokeProcess request for calculator
54+
invoke_request = InvokeProcess(
55+
name="Calculator",
56+
input_arguments={
57+
"a": state.a,
58+
"b": state.b,
59+
"operator": state.operator,
60+
},
61+
process_folder_path="Shared",
62+
)
63+
64+
logger.info(
65+
f"CALCULATOR NODE: Created InvokeProcess request: {invoke_request.model_dump()}"
66+
)
67+
logger.info("🔴 CALCULATOR NODE: About to call interrupt() - SUSPENDING EXECUTION")
68+
logger.info("=" * 80)
69+
70+
# Interrupt execution and capture the process output
71+
# The runtime will detect this and return SUSPENDED status
72+
# When resumed, the return value will contain the process output
73+
process_output = interrupt(invoke_request)
74+
75+
# This code won't execute until the process completes and execution resumes
76+
logger.info("=" * 80)
77+
logger.info("🟢 CALCULATOR NODE: Execution RESUMED after interrupt()")
78+
logger.info("CALCULATOR NODE: RPA calculator process has completed")
79+
logger.info(f"CALCULATOR NODE: Process output: {process_output}")
80+
logger.info("=" * 80)
81+
82+
# Extract result from process output
83+
if isinstance(process_output, dict):
84+
result = process_output.get("result", "")
85+
calculation = f"{state.a} {state.operator} {state.b} = {result}"
86+
else:
87+
result = str(process_output)
88+
calculation = f"{state.a} {state.operator} {state.b} = {result}"
89+
90+
logger.info(f"CALCULATOR NODE: Calculation: {calculation}")
91+
92+
return State(
93+
a=state.a,
94+
b=state.b,
95+
operator=state.operator,
96+
result=str(result),
97+
calculation=calculation,
98+
)
99+
100+
101+
# Build the graph
102+
builder = StateGraph(state_schema=State, input=Input, output=Output)
103+
104+
# Add single node that invokes the calculator process
105+
builder.add_node("calculator_process", calculator_process_node)
106+
107+
# Connect: START -> calculator_process -> END
108+
builder.add_edge(START, "calculator_process")
109+
builder.add_edge("calculator_process", END)
110+
111+
# Compile with checkpointer (required for interrupts to work)
112+
from langgraph.checkpoint.memory import MemorySaver
113+
114+
checkpointer = MemorySaver()
115+
graph = builder.compile(checkpointer=checkpointer)

0 commit comments

Comments
 (0)