Skip to content

Commit 8ae4cdf

Browse files
committed
test: init flow
1 parent 7588cbe commit 8ae4cdf

14 files changed

Lines changed: 348 additions & 14 deletions

File tree

packages/uipath-openai-agents/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ plugins = [
7070
"pydantic.mypy"
7171
]
7272
exclude = [
73-
"samples/.*"
73+
"samples/.*",
74+
"testcases/.*"
7475
]
7576
follow_imports = "silent"
7677
warn_redundant_casts = true

packages/uipath-openai-agents/samples/agent-as-tools/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pydantic import BaseModel, Field
44

55
from uipath_openai_agents.chat import UiPathChatOpenAI
6+
from uipath_openai_agents.chat.supported_models import OpenAIModels
67

78
"""
89
This example shows the agents-as-tools pattern adapted for UiPath coded agents.
@@ -32,7 +33,7 @@ def main() -> Agent:
3233
"""Configure UiPath OpenAI client and return the orchestrator agent."""
3334
# Configure UiPath OpenAI client for agent execution
3435
# This routes all OpenAI API calls through UiPath's LLM Gateway
35-
MODEL = "gpt-4o-2024-11-20"
36+
MODEL = OpenAIModels.gpt_5_1_2025_11_13
3637
uipath_openai_client = UiPathChatOpenAI(model_name=MODEL)
3738
_openai_shared.set_default_openai_client(uipath_openai_client.async_client)
3839

packages/uipath-openai-agents/samples/rag-assistant/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
from agents.models import _openai_shared
1414

1515
from uipath_openai_agents.chat import UiPathChatOpenAI
16+
from uipath_openai_agents.chat.supported_models import OpenAIModels
1617

1718

1819
def main() -> Agent:
1920
"""Configure UiPath OpenAI client and return the assistant agent."""
2021
# Configure UiPath OpenAI client for agent execution
2122
# This routes all OpenAI API calls through UiPath's LLM Gateway
22-
MODEL = "gpt-4o-2024-11-20"
23+
MODEL = OpenAIModels.gpt_5_1_2025_11_13
2324
uipath_openai_client = UiPathChatOpenAI(model_name=MODEL)
2425
_openai_shared.set_default_openai_client(uipath_openai_client.async_client)
2526

packages/uipath-openai-agents/samples/triage-agent/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from pydantic import BaseModel
44

55
from uipath_openai_agents.chat import UiPathChatOpenAI
6+
from uipath_openai_agents.chat.supported_models import OpenAIModels
67

78
"""
89
This example shows the handoffs/routing pattern adapted for UiPath coded agents.
@@ -24,7 +25,7 @@ def main() -> Agent:
2425
"""Configure UiPath OpenAI client and return the triage agent."""
2526
# Configure UiPath OpenAI client for agent execution
2627
# This routes all OpenAI API calls through UiPath's LLM Gateway
27-
MODEL = "gpt-4o-2024-11-20"
28+
MODEL = OpenAIModels.gpt_5_1_2025_11_13
2829
uipath_openai_client = UiPathChatOpenAI(model_name=MODEL)
2930
_openai_shared.set_default_openai_client(uipath_openai_client.async_client)
3031

packages/uipath-openai-agents/src/uipath_openai_agents/_cli/_templates/main.py.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def get_weather(location: str) -> str:
1919
return f"The weather in {location} is sunny and 32 degrees Celsius."
2020

2121

22-
MODEL = OpenAIModels.gpt_5_2_2025_12_11
22+
MODEL = OpenAIModels.gpt_5_1_2025_11_13
2323

2424
# Initialize the UiPath OpenAI client
2525
uipath_openai_client = UiPathChatOpenAI(model_name=MODEL)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Common utilities for OpenAI Agents testcases."""
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"""
2+
Simple trace assertion - just check that expected spans exist with required attributes.
3+
Much simpler than the tree-based approach.
4+
"""
5+
6+
import json
7+
from typing import Any
8+
9+
10+
def load_traces(traces_file: str) -> list[dict[str, Any]]:
11+
"""Load traces from a JSONL file."""
12+
traces = []
13+
with open(traces_file, "r", encoding="utf-8") as f:
14+
for line in f:
15+
if line.strip():
16+
traces.append(json.loads(line))
17+
return traces
18+
19+
20+
def load_expected_traces(expected_file: str) -> list[dict[str, Any]]:
21+
"""Load expected trace definitions from a JSON file."""
22+
with open(expected_file, "r", encoding="utf-8") as f:
23+
data = json.load(f)
24+
return data.get("required_spans", [])
25+
26+
27+
def get_attributes(span: dict[str, Any]) -> dict[str, Any]:
28+
"""
29+
Parse attributes from a span.
30+
Supports both formats:
31+
- Old format: 'Attributes' as a JSON string
32+
- New format: 'attributes' as a dict
33+
"""
34+
# New format: attributes is already a dict
35+
if "attributes" in span and isinstance(span["attributes"], dict):
36+
return span["attributes"]
37+
# Old format: Attributes is a JSON string
38+
attributes_str = span.get("Attributes", "{}")
39+
try:
40+
return json.loads(attributes_str)
41+
except json.JSONDecodeError:
42+
return {}
43+
44+
45+
def matches_value(expected_value: Any, actual_value: Any) -> bool:
46+
"""
47+
Check if an actual value matches the expected value.
48+
Supports:
49+
- List of possible values: ["value1", "value2"]
50+
- Wildcard: "*" (any value accepted)
51+
- Exact match: "value"
52+
"""
53+
# Wildcard - accept any value
54+
if expected_value == "*":
55+
return True
56+
# List of possible values
57+
if isinstance(expected_value, list):
58+
return actual_value in expected_value
59+
# Exact match
60+
return expected_value == actual_value
61+
62+
63+
def matches_expected(span: dict[str, Any], expected: dict[str, Any]) -> bool:
64+
"""
65+
Check if a span matches the expected definition.
66+
Supports both formats:
67+
- Old format: 'Name', 'SpanType' fields
68+
- New format: 'name', 'attributes.span_type' fields
69+
"""
70+
# Check name - can be a string or list of possible names
71+
expected_name = expected.get("name")
72+
# Support both old format (Name) and new format (name)
73+
actual_name = span.get("name") or span.get("Name")
74+
if isinstance(expected_name, list):
75+
if actual_name not in expected_name:
76+
return False
77+
elif expected_name != actual_name:
78+
return False
79+
# Check span type if specified
80+
if "span_type" in expected:
81+
# Old format: SpanType field
82+
# New format: attributes.span_type field
83+
actual_span_type = span.get("SpanType")
84+
if not actual_span_type:
85+
actual_attrs = get_attributes(span)
86+
actual_span_type = actual_attrs.get("span_type")
87+
if actual_span_type != expected["span_type"]:
88+
return False
89+
# Check attributes if specified
90+
if "attributes" in expected:
91+
actual_attrs = get_attributes(span)
92+
for key, expected_value in expected["attributes"].items():
93+
if key not in actual_attrs:
94+
return False
95+
# Use flexible value matching
96+
if not matches_value(expected_value, actual_attrs[key]):
97+
return False
98+
return True
99+
100+
101+
def assert_traces(traces_file: str, expected_file: str) -> None:
102+
"""
103+
Assert that all expected traces exist in the traces file.
104+
Args:
105+
traces_file: Path to the traces.jsonl file
106+
expected_file: Path to the expected_traces.json file
107+
Raises:
108+
AssertionError: If any expected trace is not found
109+
"""
110+
traces = load_traces(traces_file)
111+
expected_spans = load_expected_traces(expected_file)
112+
print(f"Loaded {len(traces)} traces from {traces_file}")
113+
print(f"Checking {len(expected_spans)} expected spans...")
114+
missing_spans = []
115+
for expected in expected_spans:
116+
# Find a matching span
117+
found = False
118+
name = expected["name"]
119+
# Handle both string and list of names
120+
name_str = name if isinstance(name, str) else f"[{' | '.join(name)}]"
121+
122+
for span in traces:
123+
if matches_expected(span, expected):
124+
found = True
125+
print(f"✓ Found span: {name_str}")
126+
break
127+
if not found:
128+
missing_spans.append(name_str)
129+
print(f"✗ Missing span: {name_str}")
130+
131+
print("Traces file content:")
132+
with open(traces_file, "r", encoding="utf-8") as f:
133+
print(f.read())
134+
if missing_spans:
135+
print(f"\n=== Dumping raw traces from {traces_file} ===")
136+
with open(traces_file, "r", encoding="utf-8") as f:
137+
print(f.read())
138+
print("\n=== End of traces dump ===\n")
139+
raise AssertionError(
140+
f"Missing expected spans: {', '.join(missing_spans)}\n"
141+
f"Expected {len(expected_spans)} spans, found {len(expected_spans) - len(missing_spans)}"
142+
)
143+
print(f"\n✓ All {len(expected_spans)} expected spans found!")

packages/uipath-openai-agents/testcases/common/validate_output.sh

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,18 @@ debug_print_uipath_output() {
2222
fi
2323
}
2424

25-
validate_output() {
26-
echo "Printing output file for validation..."
27-
debug_print_uipath_output
28-
29-
echo "Validating output..."
30-
python src/assert.py || { echo "Validation failed!"; exit 1; }
31-
32-
echo "Testcase completed successfully."
25+
# Run assertions from the testcase's src directory
26+
run_assertions() {
27+
echo "Running assertions..."
28+
if [ -f "src/assert.py" ]; then
29+
# Use the Python from the virtual environment
30+
# Prepend the common directory to the python path so it can be resolved
31+
PYTHONPATH="../common:$PYTHONPATH" python src/assert.py
32+
else
33+
echo "assert.py not found in src directory!"
34+
exit 1
35+
fi
3336
}
3437

35-
validate_output
38+
debug_print_uipath_output
39+
run_assertions
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# OpenAI Agents Integration Test - Init Flow
2+
3+
This testcase validates the complete init-flow for OpenAI Agents SDK integration with UiPath.
4+
5+
## What it tests
6+
7+
1. **Project Setup**: Creates a new UiPath agent project using `uipath new agent`
8+
2. **Initialization**: Runs `uipath init` to generate configuration files
9+
3. **Packaging**: Packs the agent into a NuGet package
10+
4. **Deployment Execution**: Runs the agent with UiPath platform integration
11+
5. **Local Execution**: Runs the agent locally with tracing enabled
12+
6. **Output Validation**: Validates the agent output structure and status
13+
7. **Trace Validation**: Verifies that expected OpenTelemetry spans are generated
14+
15+
## Files
16+
17+
- **pyproject.toml**: Project dependencies and configuration
18+
- **input.json**: Test input for the agent
19+
- **run.sh**: Main test script that executes all steps
20+
- **expected_traces.json**: Expected OpenTelemetry spans for validation
21+
- **src/assert.py**: Assertion script that validates outputs and traces
22+
23+
## Expected Traces
24+
25+
The test validates that the following spans are generated:
26+
27+
- **Agent workflow**: OpenAI Agents SDK top-level agent span (AGENT kind)
28+
- **response**: OpenAI Responses API call span (LLM kind) - note that OpenAI Agents SDK uses the Responses API, not ChatCompletion
29+
30+
## Running the test
31+
32+
```bash
33+
cd testcases/init-flow
34+
bash run.sh
35+
```
36+
37+
The test requires:
38+
- `CLIENT_ID`: UiPath OAuth client ID
39+
- `CLIENT_SECRET`: UiPath OAuth client secret
40+
- `BASE_URL`: UiPath platform base URL
41+
42+
## Success Criteria
43+
44+
The test passes if:
45+
46+
1. NuGet package (.nupkg) is created successfully
47+
2. Agent executes without errors (status: "successful")
48+
3. Output contains the expected "result" field
49+
4. Local run output contains "Successful execution."
50+
5. All expected OpenTelemetry spans are present in traces.jsonl
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"description": "OpenAI Agents SDK integration - checks key spans exist",
3+
"required_spans": [
4+
{
5+
"name": "Agent workflow",
6+
"attributes": {
7+
"openinference.span.kind": "AGENT"
8+
}
9+
},
10+
{
11+
"name": "response",
12+
"attributes": {
13+
"openinference.span.kind": "LLM",
14+
"llm.system": "openai"
15+
}
16+
}
17+
]
18+
}

0 commit comments

Comments
 (0)