uipath 2.1.5__tar.gz → 2.1.7__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.7}/PKG-INFO +1 -1
- {uipath-2.1.5 → uipath-2.1.7}/pyproject.toml +1 -1
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/__init__.py +2 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_common.py +19 -2
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_studio_project.py +61 -1
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/cli_pack.py +3 -1
- uipath-2.1.7/src/uipath/_cli/cli_pull.py +215 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/cli_push.py +3 -1
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/llm_gateway_service.py +149 -13
- uipath-2.1.7/tests/cli/test_pull.py +399 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/test_push.py +14 -5
- uipath-2.1.7/tests/cli/utils/common.py +7 -0
- uipath-2.1.7/tests/sdk/services/test_llm_schema_cleanup.py +232 -0
- uipath-2.1.7/tests/sdk/services/test_llm_service.py +290 -0
- {uipath-2.1.5 → uipath-2.1.7}/uv.lock +846 -845
- uipath-2.1.5/tests/sdk/services/test_llm_service.py +0 -83
- {uipath-2.1.5 → uipath-2.1.7}/.cursorrules +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.editorconfig +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.gitattributes +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.github/workflows/cd.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.github/workflows/ci.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.github/workflows/commitlint.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.github/workflows/lint.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.github/workflows/publish-dev.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.github/workflows/publish-docs.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.github/workflows/slack.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.github/workflows/test.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.gitignore +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.pre-commit-config.yaml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.python-version +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.vscode/extensions.json +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.vscode/launch.json +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/.vscode/settings.json +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/CONTRIBUTING.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/LICENSE +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/README.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/CONTRIBUTING.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/FAQ.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/assets/env-preparation-failed-dark.png +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/assets/env-preparation-failed-light.png +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/assets/favicon.png +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/assets/logo-dark.svg +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/assets/logo-light.svg +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/cli/index.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/actions.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/assets/cloud_env_var_dark.gif +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/assets/cloud_env_var_light.gif +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/assets/cloud_env_var_secret_dark.png +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/assets/cloud_env_var_secret_light.png +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/assets/copy_path_dark.png +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/assets/copy_path_light.png +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/assets.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/attachments.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/buckets.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/connections.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/context_grounding.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/environment_variables.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/getting_started.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/jobs.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/llm_gateway.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/processes.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/queues.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/core/traced.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/hooks.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/index.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/javascripts/extra.js +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/overrides/main.html +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/overrides/partials/actions.html +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/overrides/partials/logo.html +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/release_policy.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/sample_images/google-ADK-agent/agent-output.png +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/docs/stylesheets/extra.css +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/justfile +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/mkdocs.yml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/py.typed +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/samples/google-ADK-agent/.env.example +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/samples/google-ADK-agent/README.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/samples/google-ADK-agent/input.json +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/samples/google-ADK-agent/multi_tool_agent/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/samples/google-ADK-agent/multi_tool_agent/agent.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/samples/google-ADK-agent/pyproject.toml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/samples/google-ADK-agent/uv.lock +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/scripts/debug_test.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/scripts/lint_httpx_client.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/README.md +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/_auth_server.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/_client_credentials.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/_models.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/_oidc_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/_portal_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/auth_config.json +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/index.html +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/localhost.crt +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_auth/localhost.key +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_runtime/_contracts.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_runtime/_escalation.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_runtime/_hitl.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_runtime/_logging.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_runtime/_runtime.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_templates/.psmdcp.template +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_templates/.rels.template +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_templates/[Content_Types].xml.template +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_templates/main.py.template +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_templates/package.nuspec.template +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_console.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_constants.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_debug.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_folders.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_input_args.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_parse_ast.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_processes.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_project_files.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_tracing.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/_utils/_uv_helpers.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/cli_auth.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/cli_deploy.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/cli_init.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/cli_invoke.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/cli_new.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/cli_publish.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/cli_run.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/middlewares.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_cli/spinner.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_config.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_execution_context.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_folder_context.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/_base_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/actions_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/api_client.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/assets_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/attachments_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/buckets_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/connections_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/context_grounding_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/folder_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/jobs_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/processes_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_services/queues_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_uipath.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/_endpoint.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/_infer_bindings.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/_logs.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/_read_overwrites.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/_request_override.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/_request_spec.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/_ssl_context.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/_url.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/_user_agent.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/_utils/constants.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/action_schema.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/actions.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/assets.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/attachment.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/buckets.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/connections.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/context_grounding.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/context_grounding_index.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/errors.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/exceptions.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/interrupt_models.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/job.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/llm_gateway.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/processes.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/models/queues.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/py.typed +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/telemetry/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/telemetry/_constants.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/telemetry/_track.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/tracing/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/tracing/_otel_exporters.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/tracing/_traced.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/tracing/_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/utils/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/src/uipath/utils/_endpoints_manager.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/__init__.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/conftest.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/mocks/bindings_script.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/mocks/pyproject.toml +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/mocks/simple_script.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/mocks/uipath-mock.json +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/mocks/uipath-simple-script-mock.json +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/test_hitl.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/test_init.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/test_invoke.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/test_new.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/test_pack.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/test_publish.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/test_run.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/test_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/utils/project_details.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/cli/utils/uipath_json.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/conftest.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/conftest.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_actions_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_api_client.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_assets_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_attachments_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_base_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_buckets_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_connections_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_context_grounding_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_folder_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_jobs_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_llm_integration.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_processes_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_queues_service.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/services/test_uipath_llm_integration.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/test_bindings_inference.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/test_config.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/sdk/test_overwrites.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/tracing/test_otel_exporters.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/tracing/test_span_utils.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/tests/tracing/test_traced.py +0 -0
- {uipath-2.1.5 → uipath-2.1.7}/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.7
|
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.7"
|
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"
|
@@ -10,6 +10,7 @@ from .cli_invoke import invoke as invoke # type: ignore
|
|
10
10
|
from .cli_new import new as new # type: ignore
|
11
11
|
from .cli_pack import pack as pack # type: ignore
|
12
12
|
from .cli_publish import publish as publish # type: ignore
|
13
|
+
from .cli_pull import pull as pull # type: ignore
|
13
14
|
from .cli_push import push as push # type: ignore
|
14
15
|
from .cli_run import run as run # type: ignore
|
15
16
|
|
@@ -65,3 +66,4 @@ cli.add_command(deploy)
|
|
65
66
|
cli.add_command(auth)
|
66
67
|
cli.add_command(invoke)
|
67
68
|
cli.add_command(push)
|
69
|
+
cli.add_command(pull)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
from typing import Optional
|
3
|
+
from urllib.parse import urlparse
|
3
4
|
|
4
5
|
import click
|
5
6
|
|
@@ -29,7 +30,7 @@ def environment_options(function):
|
|
29
30
|
return function
|
30
31
|
|
31
32
|
|
32
|
-
def get_env_vars(spinner: Optional[Spinner] = None) -> list[str
|
33
|
+
def get_env_vars(spinner: Optional[Spinner] = None) -> list[str]:
|
33
34
|
base_url = os.environ.get("UIPATH_URL")
|
34
35
|
token = os.environ.get("UIPATH_ACCESS_TOKEN")
|
35
36
|
|
@@ -42,7 +43,8 @@ def get_env_vars(spinner: Optional[Spinner] = None) -> list[str | None]:
|
|
42
43
|
click.echo("UIPATH_URL, UIPATH_ACCESS_TOKEN")
|
43
44
|
click.get_current_context().exit(1)
|
44
45
|
|
45
|
-
|
46
|
+
# at this step we know for sure that both base_url and token exist. type checking can be disabled
|
47
|
+
return [base_url, token] # type: ignore
|
46
48
|
|
47
49
|
|
48
50
|
def serialize_object(obj):
|
@@ -69,3 +71,18 @@ def serialize_object(obj):
|
|
69
71
|
# Return primitive types as is
|
70
72
|
else:
|
71
73
|
return obj
|
74
|
+
|
75
|
+
|
76
|
+
def get_org_scoped_url(base_url: str) -> str:
|
77
|
+
"""Get organization scoped URL from base URL.
|
78
|
+
|
79
|
+
Args:
|
80
|
+
base_url: The base URL to scope
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
str: The organization scoped URL
|
84
|
+
"""
|
85
|
+
parsed = urlparse(base_url)
|
86
|
+
org_name, *_ = parsed.path.strip("/").split("/")
|
87
|
+
org_scoped_url = f"{parsed.scheme}://{parsed.netloc}/{org_name}"
|
88
|
+
return org_scoped_url
|
@@ -1,5 +1,6 @@
|
|
1
|
-
from typing import List, Optional, Union
|
1
|
+
from typing import Any, List, Optional, Union
|
2
2
|
|
3
|
+
import httpx
|
3
4
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
4
5
|
|
5
6
|
|
@@ -136,3 +137,62 @@ class ProjectStructure(BaseModel):
|
|
136
137
|
if isinstance(v, int):
|
137
138
|
return str(v)
|
138
139
|
return v
|
140
|
+
|
141
|
+
|
142
|
+
def get_project_structure(
|
143
|
+
project_id: str,
|
144
|
+
base_url: str,
|
145
|
+
token: str,
|
146
|
+
client: httpx.Client,
|
147
|
+
) -> ProjectStructure:
|
148
|
+
"""Retrieve the project's file structure from UiPath Cloud.
|
149
|
+
|
150
|
+
Makes an API call to fetch the complete file structure of a project,
|
151
|
+
including all files and folders. The response is validated against
|
152
|
+
the ProjectStructure model.
|
153
|
+
|
154
|
+
Args:
|
155
|
+
project_id: The ID of the project
|
156
|
+
base_url: The base URL for the API
|
157
|
+
token: Authentication token
|
158
|
+
client: HTTP client to use for requests
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
ProjectStructure: The complete project structure
|
162
|
+
|
163
|
+
Raises:
|
164
|
+
httpx.HTTPError: If the API request fails
|
165
|
+
"""
|
166
|
+
headers = {"Authorization": f"Bearer {token}"}
|
167
|
+
url = (
|
168
|
+
f"{base_url}/studio_/backend/api/Project/{project_id}/FileOperations/Structure"
|
169
|
+
)
|
170
|
+
|
171
|
+
response = client.get(url, headers=headers)
|
172
|
+
response.raise_for_status()
|
173
|
+
return ProjectStructure.model_validate(response.json())
|
174
|
+
|
175
|
+
|
176
|
+
def download_file(base_url: str, file_id: str, client: httpx.Client, token: str) -> Any:
|
177
|
+
file_url = f"{base_url}/File/{file_id}"
|
178
|
+
response = client.get(file_url, headers={"Authorization": f"Bearer {token}"})
|
179
|
+
response.raise_for_status()
|
180
|
+
return response
|
181
|
+
|
182
|
+
|
183
|
+
def get_folder_by_name(
|
184
|
+
structure: ProjectStructure, folder_name: str
|
185
|
+
) -> Optional[ProjectFolder]:
|
186
|
+
"""Get a folder from the project structure by name.
|
187
|
+
|
188
|
+
Args:
|
189
|
+
structure: The project structure
|
190
|
+
folder_name: Name of the folder to find
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
Optional[ProjectFolder]: The found folder or None
|
194
|
+
"""
|
195
|
+
for folder in structure.folders:
|
196
|
+
if folder.name == folder_name:
|
197
|
+
return folder
|
198
|
+
return None
|
@@ -276,7 +276,9 @@ def display_project_info(config):
|
|
276
276
|
|
277
277
|
|
278
278
|
@click.command()
|
279
|
-
@click.argument(
|
279
|
+
@click.argument(
|
280
|
+
"root", type=click.Path(exists=True, file_okay=False, dir_okay=True), default="."
|
281
|
+
)
|
280
282
|
@click.option(
|
281
283
|
"--nolock",
|
282
284
|
is_flag=True,
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# type: ignore
|
2
|
+
"""CLI command for pulling remote project files from UiPath StudioWeb solution.
|
3
|
+
|
4
|
+
This module provides functionality to pull remote project files from a UiPath StudioWeb solution.
|
5
|
+
It handles:
|
6
|
+
- File downloads from source_code and evals folders
|
7
|
+
- Maintaining folder structure locally
|
8
|
+
- File comparison using hashes
|
9
|
+
- Interactive confirmation for overwriting files
|
10
|
+
"""
|
11
|
+
|
12
|
+
# type: ignore
|
13
|
+
import hashlib
|
14
|
+
import json
|
15
|
+
import os
|
16
|
+
from typing import Dict, Set
|
17
|
+
|
18
|
+
import click
|
19
|
+
import httpx
|
20
|
+
from dotenv import load_dotenv
|
21
|
+
|
22
|
+
from .._utils._ssl_context import get_httpx_client_kwargs
|
23
|
+
from ..telemetry import track
|
24
|
+
from ._utils._common import get_env_vars, get_org_scoped_url
|
25
|
+
from ._utils._console import ConsoleLogger
|
26
|
+
from ._utils._studio_project import (
|
27
|
+
ProjectFile,
|
28
|
+
ProjectFolder,
|
29
|
+
download_file,
|
30
|
+
get_folder_by_name,
|
31
|
+
get_project_structure,
|
32
|
+
)
|
33
|
+
|
34
|
+
console = ConsoleLogger()
|
35
|
+
load_dotenv(override=True)
|
36
|
+
|
37
|
+
|
38
|
+
def compute_normalized_hash(content: str) -> str:
|
39
|
+
"""Compute hash of normalized content.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
content: Content to hash
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
str: SHA256 hash of the normalized content
|
46
|
+
"""
|
47
|
+
try:
|
48
|
+
# Try to parse as JSON to handle formatting
|
49
|
+
json_content = json.loads(content)
|
50
|
+
normalized = json.dumps(json_content, indent=2)
|
51
|
+
except json.JSONDecodeError:
|
52
|
+
# Not JSON, normalize line endings
|
53
|
+
normalized = content.replace("\r\n", "\n").replace("\r", "\n")
|
54
|
+
|
55
|
+
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
56
|
+
|
57
|
+
|
58
|
+
def collect_files_from_folder(
|
59
|
+
folder: ProjectFolder, base_path: str, files_dict: Dict[str, ProjectFile]
|
60
|
+
) -> None:
|
61
|
+
"""Recursively collect all files from a folder and its subfolders.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
folder: The folder to collect files from
|
65
|
+
base_path: Base path for file paths
|
66
|
+
files_dict: Dictionary to store collected files
|
67
|
+
"""
|
68
|
+
# Add files from current folder
|
69
|
+
for file in folder.files:
|
70
|
+
file_path = os.path.join(base_path, file.name)
|
71
|
+
files_dict[file_path] = file
|
72
|
+
|
73
|
+
# Recursively process subfolders
|
74
|
+
for subfolder in folder.folders:
|
75
|
+
subfolder_path = os.path.join(base_path, subfolder.name)
|
76
|
+
collect_files_from_folder(subfolder, subfolder_path, files_dict)
|
77
|
+
|
78
|
+
|
79
|
+
def download_folder_files(
|
80
|
+
folder: ProjectFolder,
|
81
|
+
base_path: str,
|
82
|
+
base_url: str,
|
83
|
+
token: str,
|
84
|
+
client: httpx.Client,
|
85
|
+
processed_files: Set[str],
|
86
|
+
) -> None:
|
87
|
+
"""Download files from a folder recursively.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
folder: The folder to download files from
|
91
|
+
base_path: Base path for local file storage
|
92
|
+
base_url: Base URL for API requests
|
93
|
+
token: Authentication token
|
94
|
+
client: HTTP client
|
95
|
+
processed_files: Set to track processed files
|
96
|
+
"""
|
97
|
+
files_dict: Dict[str, ProjectFile] = {}
|
98
|
+
collect_files_from_folder(folder, "", files_dict)
|
99
|
+
|
100
|
+
for file_path, remote_file in files_dict.items():
|
101
|
+
local_path = os.path.join(base_path, file_path)
|
102
|
+
local_dir = os.path.dirname(local_path)
|
103
|
+
|
104
|
+
# Create directory if it doesn't exist
|
105
|
+
if not os.path.exists(local_dir):
|
106
|
+
os.makedirs(local_dir)
|
107
|
+
|
108
|
+
# Download remote file
|
109
|
+
response = download_file(base_url, remote_file.id, client, token)
|
110
|
+
remote_content = response.read().decode("utf-8")
|
111
|
+
remote_hash = compute_normalized_hash(remote_content)
|
112
|
+
|
113
|
+
if os.path.exists(local_path):
|
114
|
+
# Read and hash local file
|
115
|
+
with open(local_path, "r", encoding="utf-8") as f:
|
116
|
+
local_content = f.read()
|
117
|
+
local_hash = compute_normalized_hash(local_content)
|
118
|
+
|
119
|
+
# Compare hashes
|
120
|
+
if local_hash != remote_hash:
|
121
|
+
styled_path = click.style(str(file_path), fg="cyan")
|
122
|
+
console.warning(f"File {styled_path}" + " differs from remote version.")
|
123
|
+
response = click.prompt("Do you want to override it? (y/n)", type=str)
|
124
|
+
if response.lower() == "y":
|
125
|
+
with open(local_path, "w", encoding="utf-8", newline="\n") as f:
|
126
|
+
f.write(remote_content)
|
127
|
+
console.success(f"Updated {click.style(str(file_path), fg='cyan')}")
|
128
|
+
else:
|
129
|
+
console.info(f"Skipped {click.style(str(file_path), fg='cyan')}")
|
130
|
+
else:
|
131
|
+
console.info(
|
132
|
+
f"File {click.style(str(file_path), fg='cyan')} is up to date"
|
133
|
+
)
|
134
|
+
else:
|
135
|
+
# File doesn't exist locally, create it
|
136
|
+
with open(local_path, "w", encoding="utf-8", newline="\n") as f:
|
137
|
+
f.write(remote_content)
|
138
|
+
console.success(f"Downloaded {click.style(str(file_path), fg='cyan')}")
|
139
|
+
|
140
|
+
processed_files.add(file_path)
|
141
|
+
|
142
|
+
|
143
|
+
@click.command()
|
144
|
+
@click.argument(
|
145
|
+
"root", type=click.Path(exists=True, file_okay=False, dir_okay=True), default="."
|
146
|
+
)
|
147
|
+
@track
|
148
|
+
def pull(root: str) -> None:
|
149
|
+
"""Pull remote project files from Studio Web Project.
|
150
|
+
|
151
|
+
This command pulls the remote project files from a UiPath Studio Web project.
|
152
|
+
It downloads files from the source_code and evals folders, maintaining the
|
153
|
+
folder structure locally. Files are compared using hashes before overwriting,
|
154
|
+
and user confirmation is required for differing files.
|
155
|
+
|
156
|
+
Args:
|
157
|
+
root: The root directory to pull files into
|
158
|
+
|
159
|
+
Environment Variables:
|
160
|
+
UIPATH_PROJECT_ID: Required. The ID of the UiPath Studio Web project
|
161
|
+
|
162
|
+
Example:
|
163
|
+
$ uipath pull
|
164
|
+
$ uipath pull /path/to/project
|
165
|
+
"""
|
166
|
+
if not os.getenv("UIPATH_PROJECT_ID", False):
|
167
|
+
console.error("UIPATH_PROJECT_ID environment variable not found.")
|
168
|
+
|
169
|
+
[base_url, token] = get_env_vars()
|
170
|
+
base_api_url = f"{get_org_scoped_url(base_url)}/studio_/backend/api/Project/{os.getenv('UIPATH_PROJECT_ID')}/FileOperations"
|
171
|
+
|
172
|
+
with console.spinner("Pulling UiPath project files..."):
|
173
|
+
try:
|
174
|
+
with httpx.Client(**get_httpx_client_kwargs()) as client:
|
175
|
+
# Get project structure
|
176
|
+
structure = get_project_structure(
|
177
|
+
os.getenv("UIPATH_PROJECT_ID"), # type: ignore
|
178
|
+
get_org_scoped_url(base_url),
|
179
|
+
token,
|
180
|
+
client,
|
181
|
+
)
|
182
|
+
|
183
|
+
processed_files: Set[str] = set()
|
184
|
+
|
185
|
+
# Process source_code folder
|
186
|
+
source_code_folder = get_folder_by_name(structure, "source_code")
|
187
|
+
if source_code_folder:
|
188
|
+
download_folder_files(
|
189
|
+
source_code_folder,
|
190
|
+
root,
|
191
|
+
base_api_url,
|
192
|
+
token,
|
193
|
+
client,
|
194
|
+
processed_files,
|
195
|
+
)
|
196
|
+
else:
|
197
|
+
console.warning("No source_code folder found in remote project")
|
198
|
+
|
199
|
+
# Process evals folder
|
200
|
+
evals_folder = get_folder_by_name(structure, "evals")
|
201
|
+
if evals_folder:
|
202
|
+
evals_path = os.path.join(root, "evals")
|
203
|
+
download_folder_files(
|
204
|
+
evals_folder,
|
205
|
+
evals_path,
|
206
|
+
base_api_url,
|
207
|
+
token,
|
208
|
+
client,
|
209
|
+
processed_files,
|
210
|
+
)
|
211
|
+
else:
|
212
|
+
console.warning("No evals folder found in remote project")
|
213
|
+
|
214
|
+
except Exception as e:
|
215
|
+
console.error(f"Failed to pull UiPath project: {str(e)}")
|
@@ -492,7 +492,9 @@ def upload_source_files_to_project(
|
|
492
492
|
|
493
493
|
|
494
494
|
@click.command()
|
495
|
-
@click.argument(
|
495
|
+
@click.argument(
|
496
|
+
"root", type=click.Path(exists=True, file_okay=False, dir_okay=True), default="."
|
497
|
+
)
|
496
498
|
@click.option(
|
497
499
|
"--nolock",
|
498
500
|
is_flag=True,
|
@@ -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:
|