uipath 2.1.5__tar.gz → 2.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {uipath-2.1.5 → uipath-2.1.6}/PKG-INFO +1 -1
- {uipath-2.1.5 → uipath-2.1.6}/pyproject.toml +1 -1
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/llm_gateway_service.py +149 -13
- uipath-2.1.6/tests/sdk/services/test_llm_schema_cleanup.py +232 -0
- uipath-2.1.6/tests/sdk/services/test_llm_service.py +290 -0
- {uipath-2.1.5 → uipath-2.1.6}/uv.lock +846 -845
- uipath-2.1.5/tests/sdk/services/test_llm_service.py +0 -83
- {uipath-2.1.5 → uipath-2.1.6}/.cursorrules +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.editorconfig +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.gitattributes +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.github/workflows/cd.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.github/workflows/ci.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.github/workflows/commitlint.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.github/workflows/lint.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.github/workflows/publish-dev.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.github/workflows/publish-docs.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.github/workflows/slack.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.github/workflows/test.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.gitignore +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.pre-commit-config.yaml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.python-version +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.vscode/extensions.json +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.vscode/launch.json +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/.vscode/settings.json +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/CONTRIBUTING.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/LICENSE +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/README.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/CONTRIBUTING.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/FAQ.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/assets/env-preparation-failed-dark.png +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/assets/env-preparation-failed-light.png +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/assets/favicon.png +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/assets/logo-dark.svg +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/assets/logo-light.svg +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/cli/index.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/actions.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/assets/cloud_env_var_dark.gif +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/assets/cloud_env_var_light.gif +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/assets/cloud_env_var_secret_dark.png +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/assets/cloud_env_var_secret_light.png +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/assets/copy_path_dark.png +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/assets/copy_path_light.png +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/assets.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/attachments.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/buckets.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/connections.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/context_grounding.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/environment_variables.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/getting_started.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/jobs.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/llm_gateway.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/processes.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/queues.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/core/traced.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/hooks.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/index.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/javascripts/extra.js +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/overrides/main.html +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/overrides/partials/actions.html +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/overrides/partials/logo.html +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/release_policy.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/sample_images/google-ADK-agent/agent-output.png +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/docs/stylesheets/extra.css +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/justfile +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/mkdocs.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/py.typed +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/samples/google-ADK-agent/.env.example +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/samples/google-ADK-agent/README.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/samples/google-ADK-agent/input.json +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/samples/google-ADK-agent/multi_tool_agent/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/samples/google-ADK-agent/multi_tool_agent/agent.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/samples/google-ADK-agent/pyproject.toml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/samples/google-ADK-agent/uv.lock +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/scripts/debug_test.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/scripts/lint_httpx_client.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/README.md +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/_auth_server.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/_client_credentials.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/_models.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/_portal_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/auth_config.json +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/index.html +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/localhost.crt +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_auth/localhost.key +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_runtime/_contracts.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_runtime/_escalation.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_runtime/_hitl.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_runtime/_logging.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_runtime/_runtime.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_templates/.rels.template +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_templates/main.py.template +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_common.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_console.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_constants.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_debug.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_folders.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_input_args.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_processes.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_project_files.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_studio_project.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_tracing.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/_utils/_uv_helpers.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/cli_auth.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/cli_deploy.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/cli_init.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/cli_invoke.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/cli_new.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/cli_pack.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/cli_publish.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/cli_push.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/cli_run.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/middlewares.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_cli/spinner.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_config.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_execution_context.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_folder_context.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/_base_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/actions_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/api_client.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/assets_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/attachments_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/buckets_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/connections_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/context_grounding_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/folder_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/jobs_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/processes_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_services/queues_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_uipath.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/_endpoint.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/_infer_bindings.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/_logs.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/_read_overwrites.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/_request_override.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/_request_spec.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/_ssl_context.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/_url.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/_user_agent.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/_utils/constants.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/action_schema.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/actions.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/assets.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/attachment.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/buckets.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/connections.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/context_grounding.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/context_grounding_index.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/errors.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/exceptions.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/interrupt_models.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/job.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/llm_gateway.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/processes.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/models/queues.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/py.typed +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/telemetry/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/telemetry/_constants.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/telemetry/_track.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/tracing/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/tracing/_otel_exporters.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/tracing/_traced.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/tracing/_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/utils/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/src/uipath/utils/_endpoints_manager.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/conftest.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/mocks/bindings_script.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/mocks/pyproject.toml +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/mocks/simple_script.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/mocks/uipath-mock.json +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/mocks/uipath-simple-script-mock.json +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/test_hitl.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/test_init.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/test_invoke.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/test_new.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/test_pack.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/test_publish.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/test_push.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/test_run.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/test_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/utils/project_details.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/cli/utils/uipath_json.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/conftest.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/conftest.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_actions_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_api_client.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_assets_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_attachments_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_base_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_buckets_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_connections_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_context_grounding_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_folder_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_jobs_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_llm_integration.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_processes_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_queues_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/test_bindings_inference.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/test_config.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/sdk/test_overwrites.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/tracing/test_otel_exporters.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/tracing/test_span_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/tracing/test_traced.py +0 -0
- {uipath-2.1.5 → uipath-2.1.6}/tests/tracing/test_tracing_manager.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: uipath
|
3
|
-
Version: 2.1.
|
3
|
+
Version: 2.1.6
|
4
4
|
Summary: Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools.
|
5
5
|
Project-URL: Homepage, https://uipath.com
|
6
6
|
Project-URL: Repository, https://github.com/UiPath/uipath-python
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "uipath"
|
3
|
-
version = "2.1.
|
3
|
+
version = "2.1.6"
|
4
4
|
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
|
5
5
|
readme = { file = "README.md", content-type = "text/markdown" }
|
6
6
|
requires-python = ">=3.10"
|
@@ -17,7 +17,9 @@ Classes:
|
|
17
17
|
"""
|
18
18
|
|
19
19
|
import json
|
20
|
-
from typing import Any, Dict, List, Optional
|
20
|
+
from typing import Any, Dict, List, Optional, Union
|
21
|
+
|
22
|
+
from pydantic import BaseModel
|
21
23
|
|
22
24
|
from .._config import Config
|
23
25
|
from .._execution_context import ExecutionContext
|
@@ -76,6 +78,67 @@ class EmbeddingModels(object):
|
|
76
78
|
text_embedding_ada_002 = "text-embedding-ada-002"
|
77
79
|
|
78
80
|
|
81
|
+
def _cleanup_schema(model_class: type[BaseModel]) -> Dict[str, Any]:
|
82
|
+
"""Clean up a Pydantic model schema for use with LLM Gateway.
|
83
|
+
|
84
|
+
This function converts a Pydantic model's JSON schema to a format that's
|
85
|
+
compatible with the LLM Gateway's JSON schema requirements by removing
|
86
|
+
titles and other metadata that might cause validation issues.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
model_class (type[BaseModel]): A Pydantic BaseModel class to convert to schema.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
dict: A cleaned JSON schema dictionary suitable for LLM Gateway response_format.
|
93
|
+
|
94
|
+
Examples:
|
95
|
+
```python
|
96
|
+
from pydantic import BaseModel
|
97
|
+
from typing import List
|
98
|
+
|
99
|
+
class Country(BaseModel):
|
100
|
+
name: str
|
101
|
+
capital: str
|
102
|
+
languages: List[str]
|
103
|
+
|
104
|
+
schema = _cleanup_schema(Country)
|
105
|
+
# Returns a clean schema without titles and unnecessary metadata
|
106
|
+
```
|
107
|
+
"""
|
108
|
+
schema = model_class.model_json_schema()
|
109
|
+
|
110
|
+
def clean_properties(properties):
|
111
|
+
"""Clean property definitions by removing titles and cleaning nested items."""
|
112
|
+
cleaned_props = {}
|
113
|
+
for prop_name, prop_def in properties.items():
|
114
|
+
if isinstance(prop_def, dict):
|
115
|
+
cleaned_prop = {}
|
116
|
+
for key, value in prop_def.items():
|
117
|
+
if key == "title": # Skip title
|
118
|
+
continue
|
119
|
+
elif key == "items" and isinstance(value, dict):
|
120
|
+
# Clean nested items
|
121
|
+
cleaned_items = {}
|
122
|
+
for item_key, item_value in value.items():
|
123
|
+
if item_key != "title":
|
124
|
+
cleaned_items[item_key] = item_value
|
125
|
+
cleaned_prop[key] = cleaned_items
|
126
|
+
else:
|
127
|
+
cleaned_prop[key] = value
|
128
|
+
cleaned_props[prop_name] = cleaned_prop
|
129
|
+
return cleaned_props
|
130
|
+
|
131
|
+
# Create clean schema
|
132
|
+
clean_schema = {
|
133
|
+
"type": "object",
|
134
|
+
"properties": clean_properties(schema.get("properties", {})),
|
135
|
+
"required": schema.get("required", []),
|
136
|
+
"additionalProperties": False,
|
137
|
+
}
|
138
|
+
|
139
|
+
return clean_schema
|
140
|
+
|
141
|
+
|
79
142
|
class UiPathOpenAIService(BaseService):
|
80
143
|
"""Service for calling UiPath's LLM Gateway using OpenAI-compatible API.
|
81
144
|
|
@@ -146,7 +209,7 @@ class UiPathOpenAIService(BaseService):
|
|
146
209
|
model: str = ChatModels.gpt_4o_mini_2024_07_18,
|
147
210
|
max_tokens: int = 50,
|
148
211
|
temperature: float = 0,
|
149
|
-
response_format: Optional[Dict[str, Any]] = None,
|
212
|
+
response_format: Optional[Union[Dict[str, Any], type[BaseModel]]] = None,
|
150
213
|
api_version: str = API_VERSION,
|
151
214
|
):
|
152
215
|
"""Generate chat completions using UiPath's LLM Gateway service.
|
@@ -168,9 +231,11 @@ class UiPathOpenAIService(BaseService):
|
|
168
231
|
temperature (float, optional): Temperature for sampling, between 0 and 1.
|
169
232
|
Lower values (closer to 0) make output more deterministic and focused,
|
170
233
|
higher values make it more creative and random. Defaults to 0.
|
171
|
-
response_format (Optional[Dict[str, Any]], optional):
|
172
|
-
that the model must output.
|
173
|
-
|
234
|
+
response_format (Optional[Union[Dict[str, Any], type[BaseModel]]], optional):
|
235
|
+
An object specifying the format that the model must output. Can be either:
|
236
|
+
- A dictionary with response format configuration (traditional format)
|
237
|
+
- A Pydantic BaseModel class (automatically converted to JSON schema)
|
238
|
+
Used to enable JSON mode or other structured outputs. Defaults to None.
|
174
239
|
api_version (str, optional): The API version to use. Defaults to API_VERSION.
|
175
240
|
|
176
241
|
Returns:
|
@@ -198,11 +263,31 @@ class UiPathOpenAIService(BaseService):
|
|
198
263
|
max_tokens=200,
|
199
264
|
temperature=0.3
|
200
265
|
)
|
266
|
+
|
267
|
+
# Using Pydantic model for structured response
|
268
|
+
from pydantic import BaseModel
|
269
|
+
from typing import List
|
270
|
+
|
271
|
+
class Country(BaseModel):
|
272
|
+
name: str
|
273
|
+
capital: str
|
274
|
+
languages: List[str]
|
275
|
+
|
276
|
+
response = await service.chat_completions(
|
277
|
+
messages=[
|
278
|
+
{"role": "system", "content": "You are a helpful assistant. Respond with structured JSON."},
|
279
|
+
{"role": "user", "content": "Tell me about Canada."}
|
280
|
+
],
|
281
|
+
response_format=Country, # Pass BaseModel directly
|
282
|
+
max_tokens=1000
|
283
|
+
)
|
201
284
|
```
|
202
285
|
|
203
286
|
Note:
|
204
287
|
The conversation history can be included to provide context to the model.
|
205
288
|
Each message should have both 'role' and 'content' keys.
|
289
|
+
When using a Pydantic BaseModel as response_format, it will be automatically
|
290
|
+
converted to the appropriate JSON schema format for the LLM Gateway.
|
206
291
|
"""
|
207
292
|
endpoint = EndpointManager.get_passthrough_endpoint().format(
|
208
293
|
model=model, api_version=api_version
|
@@ -215,9 +300,24 @@ class UiPathOpenAIService(BaseService):
|
|
215
300
|
"temperature": temperature,
|
216
301
|
}
|
217
302
|
|
218
|
-
#
|
303
|
+
# Handle response_format - convert BaseModel to schema if needed
|
219
304
|
if response_format:
|
220
|
-
|
305
|
+
if isinstance(response_format, type) and issubclass(
|
306
|
+
response_format, BaseModel
|
307
|
+
):
|
308
|
+
# Convert Pydantic model to JSON schema format
|
309
|
+
cleaned_schema = _cleanup_schema(response_format)
|
310
|
+
request_body["response_format"] = {
|
311
|
+
"type": "json_schema",
|
312
|
+
"json_schema": {
|
313
|
+
"name": response_format.__name__.lower(),
|
314
|
+
"strict": True,
|
315
|
+
"schema": cleaned_schema,
|
316
|
+
},
|
317
|
+
}
|
318
|
+
else:
|
319
|
+
# Use provided dictionary format directly
|
320
|
+
request_body["response_format"] = response_format
|
221
321
|
|
222
322
|
response = await self.request_async(
|
223
323
|
"POST",
|
@@ -258,7 +358,7 @@ class UiPathLlmChatService(BaseService):
|
|
258
358
|
top_p: float = 1,
|
259
359
|
tools: Optional[List[ToolDefinition]] = None,
|
260
360
|
tool_choice: Optional[ToolChoice] = None,
|
261
|
-
response_format: Optional[Dict[str, Any]] = None,
|
361
|
+
response_format: Optional[Union[Dict[str, Any], type[BaseModel]]] = None,
|
262
362
|
api_version: str = NORMALIZED_API_VERSION,
|
263
363
|
):
|
264
364
|
"""Generate chat completions using UiPath's normalized LLM Gateway API.
|
@@ -295,9 +395,11 @@ class UiPathLlmChatService(BaseService):
|
|
295
395
|
tool_choice (Optional[ToolChoice], optional): Controls which tools the model can call.
|
296
396
|
Can be "auto" (model decides), "none" (no tools), or a specific tool choice.
|
297
397
|
Defaults to None.
|
298
|
-
response_format (Optional[Dict[str, Any]], optional):
|
299
|
-
that the model must output.
|
300
|
-
|
398
|
+
response_format (Optional[Union[Dict[str, Any], type[BaseModel]]], optional):
|
399
|
+
An object specifying the format that the model must output. Can be either:
|
400
|
+
- A dictionary with response format configuration (traditional format)
|
401
|
+
- A Pydantic BaseModel class (automatically converted to JSON schema)
|
402
|
+
Used to enable JSON mode or other structured outputs. Defaults to None.
|
301
403
|
api_version (str, optional): The normalized API version to use.
|
302
404
|
Defaults to NORMALIZED_API_VERSION.
|
303
405
|
|
@@ -349,6 +451,25 @@ class UiPathLlmChatService(BaseService):
|
|
349
451
|
presence_penalty=0.2,
|
350
452
|
n=3 # Generate 3 alternative responses
|
351
453
|
)
|
454
|
+
|
455
|
+
# Using Pydantic model for structured response
|
456
|
+
from pydantic import BaseModel
|
457
|
+
from typing import List
|
458
|
+
|
459
|
+
class Country(BaseModel):
|
460
|
+
name: str
|
461
|
+
capital: str
|
462
|
+
languages: List[str]
|
463
|
+
|
464
|
+
response = await service.chat_completions(
|
465
|
+
messages=[
|
466
|
+
{"role": "system", "content": "You are a helpful assistant. Respond with structured JSON."},
|
467
|
+
{"role": "user", "content": "Tell me about Canada."}
|
468
|
+
],
|
469
|
+
response_format=Country, # Pass BaseModel directly
|
470
|
+
max_tokens=1000
|
471
|
+
)
|
472
|
+
)
|
352
473
|
```
|
353
474
|
|
354
475
|
Note:
|
@@ -370,9 +491,24 @@ class UiPathLlmChatService(BaseService):
|
|
370
491
|
"top_p": top_p,
|
371
492
|
}
|
372
493
|
|
373
|
-
#
|
494
|
+
# Handle response_format - convert BaseModel to schema if needed
|
374
495
|
if response_format:
|
375
|
-
|
496
|
+
if isinstance(response_format, type) and issubclass(
|
497
|
+
response_format, BaseModel
|
498
|
+
):
|
499
|
+
# Convert Pydantic model to JSON schema format
|
500
|
+
cleaned_schema = _cleanup_schema(response_format)
|
501
|
+
request_body["response_format"] = {
|
502
|
+
"type": "json_schema",
|
503
|
+
"json_schema": {
|
504
|
+
"name": response_format.__name__.lower(),
|
505
|
+
"strict": True,
|
506
|
+
"schema": cleaned_schema,
|
507
|
+
},
|
508
|
+
}
|
509
|
+
else:
|
510
|
+
# Use provided dictionary format directly
|
511
|
+
request_body["response_format"] = response_format
|
376
512
|
|
377
513
|
# Add tools if provided - convert to UiPath format
|
378
514
|
if tools:
|
@@ -0,0 +1,232 @@
|
|
1
|
+
"""Tests for the _cleanup_schema function in LLM Gateway Service."""
|
2
|
+
|
3
|
+
from typing import List, Optional
|
4
|
+
|
5
|
+
from pydantic import BaseModel
|
6
|
+
|
7
|
+
from uipath._services.llm_gateway_service import _cleanup_schema
|
8
|
+
|
9
|
+
|
10
|
+
# Simple test models
|
11
|
+
class SimpleModel(BaseModel):
|
12
|
+
name: str
|
13
|
+
age: int
|
14
|
+
active: bool
|
15
|
+
|
16
|
+
|
17
|
+
class ModelWithList(BaseModel):
|
18
|
+
names: List[str]
|
19
|
+
numbers: List[int]
|
20
|
+
|
21
|
+
|
22
|
+
class ModelWithOptional(BaseModel):
|
23
|
+
required_field: str
|
24
|
+
optional_field: Optional[str] = None
|
25
|
+
|
26
|
+
|
27
|
+
# Complex nested models for comprehensive testing
|
28
|
+
class Task(BaseModel):
|
29
|
+
task_id: int
|
30
|
+
description: str
|
31
|
+
completed: bool
|
32
|
+
|
33
|
+
|
34
|
+
class Project(BaseModel):
|
35
|
+
project_id: int
|
36
|
+
name: str
|
37
|
+
tasks: List[Task]
|
38
|
+
|
39
|
+
|
40
|
+
class Team(BaseModel):
|
41
|
+
team_id: int
|
42
|
+
team_name: str
|
43
|
+
members: List[str]
|
44
|
+
projects: List[Project]
|
45
|
+
|
46
|
+
|
47
|
+
class Department(BaseModel):
|
48
|
+
department_id: int
|
49
|
+
department_name: str
|
50
|
+
teams: List[Team]
|
51
|
+
|
52
|
+
|
53
|
+
class Company(BaseModel):
|
54
|
+
company_id: int
|
55
|
+
company_name: str
|
56
|
+
departments: List[Department]
|
57
|
+
|
58
|
+
|
59
|
+
class TestCleanupSchema:
|
60
|
+
"""Test cases for the _cleanup_schema function."""
|
61
|
+
|
62
|
+
def test_simple_model_cleanup(self):
|
63
|
+
"""Test cleanup of a simple model without nested structures."""
|
64
|
+
schema = _cleanup_schema(SimpleModel)
|
65
|
+
|
66
|
+
assert schema["type"] == "object"
|
67
|
+
assert schema["additionalProperties"] is False
|
68
|
+
assert "required" in schema
|
69
|
+
assert set(schema["required"]) == {"name", "age", "active"}
|
70
|
+
|
71
|
+
# Check properties are cleaned (no titles)
|
72
|
+
properties = schema["properties"]
|
73
|
+
assert "name" in properties
|
74
|
+
assert "age" in properties
|
75
|
+
assert "active" in properties
|
76
|
+
|
77
|
+
# Ensure no 'title' fields are present
|
78
|
+
for _prop_name, prop_def in properties.items():
|
79
|
+
assert "title" not in prop_def
|
80
|
+
|
81
|
+
def test_model_with_list_cleanup(self):
|
82
|
+
"""Test cleanup of a model with list fields."""
|
83
|
+
schema = _cleanup_schema(ModelWithList)
|
84
|
+
|
85
|
+
assert schema["type"] == "object"
|
86
|
+
assert schema["additionalProperties"] is False
|
87
|
+
|
88
|
+
# Check list properties
|
89
|
+
names_prop = schema["properties"]["names"]
|
90
|
+
assert names_prop["type"] == "array"
|
91
|
+
assert "items" in names_prop
|
92
|
+
assert names_prop["items"]["type"] == "string"
|
93
|
+
# Ensure no 'title' in items
|
94
|
+
assert "title" not in names_prop["items"]
|
95
|
+
|
96
|
+
numbers_prop = schema["properties"]["numbers"]
|
97
|
+
assert numbers_prop["type"] == "array"
|
98
|
+
assert "items" in numbers_prop
|
99
|
+
assert numbers_prop["items"]["type"] == "integer"
|
100
|
+
assert "title" not in numbers_prop["items"]
|
101
|
+
|
102
|
+
def test_model_with_optional_cleanup(self):
|
103
|
+
"""Test cleanup of a model with optional fields."""
|
104
|
+
schema = _cleanup_schema(ModelWithOptional)
|
105
|
+
|
106
|
+
assert schema["type"] == "object"
|
107
|
+
assert schema["additionalProperties"] is False
|
108
|
+
|
109
|
+
# Only required_field should be in required array
|
110
|
+
assert schema["required"] == ["required_field"]
|
111
|
+
|
112
|
+
# Both fields should be in properties
|
113
|
+
assert "required_field" in schema["properties"]
|
114
|
+
assert "optional_field" in schema["properties"]
|
115
|
+
|
116
|
+
def test_complex_nested_model_cleanup(self):
|
117
|
+
"""Test cleanup of the complex nested Company model."""
|
118
|
+
schema = _cleanup_schema(Company)
|
119
|
+
|
120
|
+
assert schema["type"] == "object"
|
121
|
+
assert schema["additionalProperties"] is False
|
122
|
+
assert set(schema["required"]) == {"company_id", "company_name", "departments"}
|
123
|
+
|
124
|
+
# Check top-level properties
|
125
|
+
properties = schema["properties"]
|
126
|
+
assert "company_id" in properties
|
127
|
+
assert "company_name" in properties
|
128
|
+
assert "departments" in properties
|
129
|
+
|
130
|
+
# Check departments is array of objects
|
131
|
+
departments_prop = properties["departments"]
|
132
|
+
assert departments_prop["type"] == "array"
|
133
|
+
assert "items" in departments_prop
|
134
|
+
assert "title" not in departments_prop["items"]
|
135
|
+
|
136
|
+
# Verify no 'title' fields exist anywhere in the schema
|
137
|
+
self._assert_no_titles_recursive(schema)
|
138
|
+
|
139
|
+
def test_schema_structure_integrity(self):
|
140
|
+
"""Test that the cleaned schema maintains proper JSON Schema structure."""
|
141
|
+
schema = _cleanup_schema(Company)
|
142
|
+
|
143
|
+
# Must have these top-level keys
|
144
|
+
required_keys = {"type", "properties", "required", "additionalProperties"}
|
145
|
+
assert all(key in schema for key in required_keys)
|
146
|
+
|
147
|
+
# Type must be object
|
148
|
+
assert schema["type"] == "object"
|
149
|
+
|
150
|
+
# additionalProperties must be False
|
151
|
+
assert schema["additionalProperties"] is False
|
152
|
+
|
153
|
+
# Properties must be a dict
|
154
|
+
assert isinstance(schema["properties"], dict)
|
155
|
+
|
156
|
+
# Required must be a list
|
157
|
+
assert isinstance(schema["required"], list)
|
158
|
+
|
159
|
+
def test_email_field_handling(self):
|
160
|
+
"""Test that EmailStr fields are properly handled."""
|
161
|
+
schema = _cleanup_schema(Team)
|
162
|
+
|
163
|
+
members_prop = schema["properties"]["members"]
|
164
|
+
assert members_prop["type"] == "array"
|
165
|
+
|
166
|
+
# EmailStr should be treated as string with format
|
167
|
+
items = members_prop["items"]
|
168
|
+
assert items["type"] == "string"
|
169
|
+
# Email format might be present
|
170
|
+
if "format" in items:
|
171
|
+
assert items["format"] == "email"
|
172
|
+
|
173
|
+
def test_nested_objects_cleanup(self):
|
174
|
+
"""Test that nested objects are properly cleaned."""
|
175
|
+
schema = _cleanup_schema(Department)
|
176
|
+
|
177
|
+
# Check teams property (array of Team objects)
|
178
|
+
teams_prop = schema["properties"]["teams"]
|
179
|
+
assert teams_prop["type"] == "array"
|
180
|
+
assert "items" in teams_prop
|
181
|
+
|
182
|
+
# The items should not have title
|
183
|
+
team_items = teams_prop["items"]
|
184
|
+
assert "title" not in team_items
|
185
|
+
|
186
|
+
# If it's a nested object, check its properties
|
187
|
+
if "properties" in team_items:
|
188
|
+
for _prop_name, prop_def in team_items["properties"].items():
|
189
|
+
assert "title" not in prop_def
|
190
|
+
|
191
|
+
def _assert_no_titles_recursive(self, obj):
|
192
|
+
"""Recursively assert that no 'title' fields exist in the schema."""
|
193
|
+
if isinstance(obj, dict):
|
194
|
+
assert "title" not in obj, f"Found 'title' field in: {obj}"
|
195
|
+
for value in obj.values():
|
196
|
+
self._assert_no_titles_recursive(value)
|
197
|
+
elif isinstance(obj, list):
|
198
|
+
for item in obj:
|
199
|
+
self._assert_no_titles_recursive(item)
|
200
|
+
|
201
|
+
def test_function_returns_dict(self):
|
202
|
+
"""Test that the function returns a dictionary."""
|
203
|
+
result = _cleanup_schema(SimpleModel)
|
204
|
+
assert isinstance(result, dict)
|
205
|
+
|
206
|
+
def test_function_with_inheritance(self):
|
207
|
+
"""Test cleanup with model inheritance."""
|
208
|
+
|
209
|
+
class BaseEntity(BaseModel):
|
210
|
+
id: int
|
211
|
+
created_at: str
|
212
|
+
|
213
|
+
class ExtendedEntity(BaseEntity):
|
214
|
+
name: str
|
215
|
+
description: Optional[str] = None
|
216
|
+
|
217
|
+
schema = _cleanup_schema(ExtendedEntity)
|
218
|
+
|
219
|
+
# Should include fields from both base and derived class
|
220
|
+
properties = schema["properties"]
|
221
|
+
assert "id" in properties
|
222
|
+
assert "created_at" in properties
|
223
|
+
assert "name" in properties
|
224
|
+
assert "description" in properties
|
225
|
+
|
226
|
+
# Required fields from both classes
|
227
|
+
required_fields = set(schema["required"])
|
228
|
+
assert "id" in required_fields
|
229
|
+
assert "created_at" in required_fields
|
230
|
+
assert "name" in required_fields
|
231
|
+
# description is optional, so not in required
|
232
|
+
assert "description" not in required_fields
|