Skip to content
Open
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
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,17 @@ playground.py
**/samples/**/AGENTS.md
**/samples/**/CLAUDE.md
**/samples/**/bindings.json
# Hand-written bindings of the codex-sdk samples (process, connection and
# index binding examples).
!packages/uipath-codex-sdk/samples/durable-process-tool/bindings.json
!packages/uipath-codex-sdk/samples/integration-tool-agent/bindings.json
!packages/uipath-codex-sdk/samples/slack-message-agent/bindings.json
!packages/uipath-codex-sdk/samples/outlook-email-agent/bindings.json
!packages/uipath-codex-sdk/samples/jira-issue-agent/bindings.json
!packages/uipath-codex-sdk/samples/context-grounding-agent/bindings.json
!packages/uipath-codex-sdk/samples/deep-rag-agent/bindings.json
!packages/uipath-codex-sdk/samples/escalation-agent/bindings.json
**/samples/**/entry-points.json
**/samples/**/traces.jsonl
**/samples/**/uv.lock
**/**/.claude/settings.local.json
1 change: 1 addition & 0 deletions packages/uipath-codex-sdk/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
1,150 changes: 1,150 additions & 0 deletions packages/uipath-codex-sdk/README.md

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions packages/uipath-codex-sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
[project]
name = "uipath-codex-sdk"
version = "0.1.0"
description = "Python SDK that enables developers to build and deploy OpenAI Codex SDK agents to the UiPath Cloud Platform"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"openai-codex>=0.161.0, <0.162.0",
"aiohttp>=3.9.0",
"aiosqlite>=0.19.0",
"httpx>=0.27.0",
"uipath>=2.14.41, <2.15.0",
"uipath-platform>=0.2.34",
"uipath-runtime>=0.13.0, <0.14.0",
"uipath-llm-client[openai]>=1.17.1, <1.19.0",
]
classifiers = [
"Intended Audience :: Developers",
"Topic :: Software Development :: Build Tools",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]

[project.entry-points."uipath.middlewares"]
register = "uipath_codex_sdk.middlewares:register_middleware"

[project.entry-points."uipath.runtime.factories"]
codex = "uipath_codex_sdk.runtime:register_runtime_factory"

[project.urls]
Homepage = "https://uipath.com"
Repository = "https://github.com/UiPath/uipath-integrations-python"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[dependency-groups]
dev = [
"mypy>=1.14.1",
"ruff>=0.9.4",
"pytest>=7.4.0",
"pytest-asyncio>=1.0.0",
"uipath-dev>=0.0.85",
]

[tool.mypy]
plugins = [
"pydantic.mypy"
]
exclude = [
"samples/.*",
"template/.*"
]
follow_imports = "silent"
warn_redundant_casts = true
warn_unused_ignores = false
check_untyped_defs = true

[tool.ruff]
line-length = 88
indent-width = 4
exclude = ["samples/**", "template/**"]

[tool.ruff.lint]
select = ["E", "F", "B", "I"]

[tool.ruff.lint.per-file-ignores]
"*" = ["E501"]

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
markers = [
"contract: contract tests against the real bundled codex binary (local mocks, no network)",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Populated by `uipath auth`
UIPATH_URL=
UIPATH_ACCESS_TOKEN=
UIPATH_TENANT_ID=
UIPATH_ORGANIZATION_ID=
58 changes: 58 additions & 0 deletions packages/uipath-codex-sdk/samples/analyze-files-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Document Q&A (Analyze Files, job attachments)

A Codex agent that answers a question about an attached document:

| Input | Tool | Output |
| --- | --- | --- |
| `document` (a job attachment), `question` | `Analyze_Files`: the file and an analysis task go to the agent's model | `{answer, source}`, `source` being the input document |

As in LangChain agents, the model never opens the file itself: it sees the
attachment as a JobAttachment object (`{"ID", "FullName", "MimeType"}`) and
passes it to the tool. See the main README's "Job attachments" section.

## Run locally

```bash
cd samples/analyze-files-agent
uv sync
uv run uipath auth # pick the tenant, e.g. --alpha
uv run python upload.py invoice.pdf "What is the total amount due?" # writes input.json
uv run uipath run agent --file input.json
```

`upload.py` uploads the file as an Orchestrator attachment and writes its
`{ID, FullName, MimeType}` into `input.json`. Images (PNG, JPEG, GIF, WebP)
and documents such as PDFs work; a file over 30 MB fails the run.

## Run on Orchestrator

Publish the agent and start a job: the `document` input is a file picker
(the schema marks it as a `JobAttachment`).

## Evaluate with mocked tools

`evaluations/eval-sets/evaluation-set-mocked.json` runs the agent with its
UiPath tools mocked, so it needs no tenant resources besides the LLM.

The document is not downloaded or sent to a model: `Analyze Files` returns a
fixed `analysisResult`. The input's attachment id is a placeholder, which is
fine while the tool is mocked.

```bash
uv run uipath eval agent evaluations/eval-sets/evaluation-set-mocked.json --no-report
```

See the main README's "Mocking tools in evals" section.

## Trace

```
Agent run - agent attachments: the document (in)
├─ LLM call
├─ Tool call - Analyze_Files toolType Internal, attachments: the document (in)
│ └─ Analyze Files input {attachments, analysisTask}, output {analysisResult}
│ ├─ attachments_get_blob_uri
│ └─ LLM call the task and the file, attachments: the document (in)
├─ LLM call
└─ Agent output attachments: the document (out)
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"agents": {
"agent": "main.py:agent"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"version": "1.0",
"id": "analyze-files-agent-mocked",
"name": "Document questions (mocked Analyze Files)",
"evaluatorRefs": [
"evaluator-contains",
"evaluator-tool-call-order"
],
"evaluations": [
{
"id": "total-mockito",
"name": "Mockito: a fixed analysis",
"inputs": {
"document": {
"ID": "00000000-0000-0000-0000-000000000000",
"FullName": "invoice.pdf",
"MimeType": "application/pdf"
},
"question": "What is the total amount due, and by when?"
},
"evaluationCriterias": {
"evaluator-contains": {
"searchText": "1,234.00"
},
"evaluator-tool-call-order": {
"toolCallsOrder": [
"Analyze_Files"
]
}
},
"mockingStrategy": {
"type": "mockito",
"config": [
{
"function": "Analyze Files",
"then": [
{
"type": "return",
"value": {
"analysisResult": "The total amount due is 1,234.00 EUR, payable by 2026-12-01."
}
}
]
}
]
}
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "1.0",
"id": "evaluator-contains",
"description": "Check that the output contains the expected text (case-insensitive).",
"evaluatorTypeId": "uipath-contains",
"evaluatorConfig": {
"name": "Contains",
"case_sensitive": false,
"targetOutputKey": "*"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": "1.0",
"id": "evaluator-tool-call-order",
"description": "Check which tools the agent called, in order.",
"evaluatorTypeId": "uipath-tool-call-order",
"evaluatorConfig": {
"name": "Tool Call Order",
"strict": false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"document": {
"ID": "00000000-0000-0000-0000-000000000000",
"FullName": "invoice.pdf",
"MimeType": "application/pdf"
},
"question": "What is the total amount due, and by when?"
}
42 changes: 42 additions & 0 deletions packages/uipath-codex-sdk/samples/analyze-files-agent/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Analyze Files: the agent answers a question about an attached document.

The input carries a job attachment (an Orchestrator file). The model sees it
as a JobAttachment object (``ID``, ``FullName``, ``MimeType``) and cannot open
it itself: it hands it to ``Analyze_Files``, which downloads the file and asks
the agent's model about it. The output names the document it answered from;
an output attachment field may only name an attachment the run knows.
"""

from pydantic import BaseModel, Field

from uipath_codex_sdk import (
UiPathAttachment,
UiPathCodexAgent,
uipath_analyze_files_tool,
)


class Request(BaseModel):
document: UiPathAttachment = Field(description="The document to read")
question: str = Field(description="What to find out about the document")


class Output(BaseModel):
answer: str = Field(description="The answer, from the document's contents")
source: UiPathAttachment | None = Field(
None, description="The document the answer comes from"
)


agent = UiPathCodexAgent(
model="gpt-5.6-terra",
input_schema=Request,
output_schema=Output,
prompt=(
"Answer this question about the attached document: {question}\n"
"Read the document with Analyze_Files (one call, with a precise "
"analysis task), answer from its result only, and return the document "
"as the source."
),
tools=[uipath_analyze_files_tool()],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[project]
name = "codex-analyze-files-agent"
version = "0.0.1"
description = "UiPath Codex SDK agent that answers questions about a job attachment with the Analyze Files tool"
authors = [{ name = "John Doe", email = "john.doe@myemail.com" }]
requires-python = ">=3.11"
dependencies = [
"uipath-codex-sdk>=0.1.0",
]

[dependency-groups]
dev = [
"uipath-dev",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
33 changes: 33 additions & 0 deletions packages/uipath-codex-sdk/samples/analyze-files-agent/upload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Upload a local file as an Orchestrator attachment and write input.json.

uv run python upload.py invoice.pdf "What is the total amount due?"

Uses the folder of the current ``uipath auth`` session (``UIPATH_FOLDER_PATH``
when set), the same one the Analyze Files tool reads attachments from.
"""

import json
import mimetypes
import sys
from pathlib import Path

from dotenv import load_dotenv
from uipath.platform import UiPath


def main() -> None:
if len(sys.argv) != 3:
sys.exit(f"usage: {sys.argv[0]} <file> <question>")
load_dotenv()
path = Path(sys.argv[1])
key = UiPath().attachments.upload(name=path.name, source_path=str(path))
mime = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
document = {"ID": str(key), "FullName": path.name, "MimeType": mime}
Path("input.json").write_text(
json.dumps({"document": document, "question": sys.argv[2]}, indent=2) + "\n"
)
print(f"Uploaded {path.name} as attachment {key}; wrote input.json")


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Populated by `uipath auth`
UIPATH_URL=
UIPATH_ACCESS_TOKEN=
UIPATH_TENANT_ID=
UIPATH_ORGANIZATION_ID=
Loading
Loading