diff --git a/sdk/ai/azure-ai-projects/.github/skills/README.md b/sdk/ai/azure-ai-projects/.github/skills/README.md
index 5fa39f44900e..c6cb95b0658c 100644
--- a/sdk/ai/azure-ai-projects/.github/skills/README.md
+++ b/sdk/ai/azure-ai-projects/.github/skills/README.md
@@ -1,17 +1,29 @@
# CoPilot skills for azure-ai-projects development
-## Prerequisite
+## General Prerequisite
* Clone the `azure-sdk-for-python` repo to your local machine, if you don't already have it:
```
git clone https://github.com/Azure/azure-sdk-for-python.git
```
* Change to the directory `sdk\ai\azure-ai-projects`.
-* Switch to the current feature branch, for example: `git switch feature/azure-ai-projects/2.3.0`.
+* Switch to the feature branch staging the next release: `git switch feature/azure-ai-projects/vnext`.
* Make sure you don't have any files edited or added in this branch (clean `git status` state).
## Emit from TypeSpec and create a PR
+### Skill Prerequisite
+
+1. Windows machine with PowerShell (Windows PowerShell 5.1+)
Install: `winget install --id Microsoft.PowerShell --source winget`
+1. Git CLI, configured user identity, and authenticated access to GitHub remote
Install: `winget install --id Git.Git --source winget`
Configure: `git config --global user.name ""` and `git config --global user.email ""`
+1. GitHub CLI (gh), authenticated (for PR creation)
Install: `winget install --id GitHub.cli --source winget`
Login: `gh auth login`
+1. Python 3.9 or newer (matches pyproject.toml requires-python >=3.9)
Install: `winget install --id Python.Python.3 --source winget`
+1. `pip` installed
Setup/upgrade: `python -m ensurepip --upgrade` and `python -m pip install --upgrade pip`
+1. TypeSpec tsp-client command available in PATH (used by skill Step 4)
Install: `npm install -g @azure-tools/typespec-client-generator-cli`
+1. Node.js + npm (typically required to install/use tsp-client)
Install: `winget install --id OpenJS.NodeJS.LTS --source winget`
+1. Dependencies for developing azure-ai-projects, per dev_requirements.txt (this covers tools such as black and azpysdk support)
Install: `python -m pip install -r dev_requirements.txt`
+1. Local clone of Azure/azure-rest-api-specs only if using the local TypeSpec source option
Setup: `git clone https://github.com/Azure/azure-rest-api-specs.git`
+
### Using GitHub CoPilot in VSCode
* Open VSCode in the current folder.
diff --git a/sdk/ai/azure-ai-projects/.github/skills/azure-ai-projects-emit-from-typespec/SKILL.md b/sdk/ai/azure-ai-projects/.github/skills/azure-ai-projects-emit-from-typespec/SKILL.md
index 14bd5540f020..7562974dc92a 100644
--- a/sdk/ai/azure-ai-projects/.github/skills/azure-ai-projects-emit-from-typespec/SKILL.md
+++ b/sdk/ai/azure-ai-projects/.github/skills/azure-ai-projects-emit-from-typespec/SKILL.md
@@ -18,21 +18,135 @@ applying post-emitter fixes, updating the changelog, installing package from sou
---
-## Step 1: Gather information from the user
+## Step 1: Preflight checks (required)
+
+Before asking workflow questions, validate prerequisites in this section.
+
+If any required check fails:
+- Stop immediately and report the failing check.
+- Show the exact install/fix command.
+- Do not continue until the user confirms it is fixed.
+
+Run these checks in order:
+
+### 1a. Confirm required commands are available
+
+Run:
+
+```
+pwsh --version
+git --version
+gh --version
+python --version
+pip --version
+node --version
+npm --version
+tsp-client --version
+```
+
+If any command is missing, stop and show the matching install command:
+- PowerShell: `winget install --id Microsoft.PowerShell --source winget`
+- Git: `winget install --id Git.Git --source winget`
+- GitHub CLI: `winget install --id GitHub.cli --source winget`
+- Python 3: `winget install --id Python.Python.3 --source winget`
+- Node.js LTS (includes npm): `winget install --id OpenJS.NodeJS.LTS --source winget`
+- TypeSpec client generator: `npm install -g @azure-tools/typespec-client-generator-cli`
+
+### 1b. Confirm working directory
+
+Run:
+
+```
+git rev-parse --show-toplevel
+git rev-parse --show-prefix
+```
+
+Expected:
+- `git rev-parse --show-prefix` returns exactly `sdk/ai/azure-ai-projects/`
+
+If not, stop and ask the user to switch to the `sdk/ai/azure-ai-projects` folder.
+
+### 1c. Confirm Python version is supported
+
+Run:
+
+```
+python -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)"
+```
+
+If this fails, stop and ask the user to install or activate Python 3.9+.
+
+### 1d. Confirm GitHub CLI authentication
+
+Run:
+
+```
+gh auth status
+```
+
+If not authenticated, stop and ask the user to run:
+
+```
+gh auth login
+```
+
+### 1e. Confirm Git identity is configured
+
+Run:
+
+```
+git config user.name
+git config user.email
+```
+
+If either value is empty, stop and ask the user to run:
+
+```
+git config user.name ""
+git config user.email ""
+```
+
+### 1f. Confirm repository is clean
+
+Run:
+
+```
+git status --porcelain
+```
+
+If output is not empty, stop and ask the user to commit/stash/discard local changes before continuing.
+
+### 1g. Install development dependencies
+
+Run:
+
+```
+python -m pip install -r dev_requirements.txt
+```
+
+If this command fails, stop and report the error to the user.
+
+Important:
+- Azure CLI (`az`) is not required for this skill workflow and must not be checked in preflight.
+- Do not proceed to Step 2 until all required preflight checks pass.
+
+---
+
+## Step 2: Gather information from the user
Ask the user the following questions **one at a time**, waiting for each answer before proceeding.
-### 1a. Topic branch name
+### 2a. Topic branch name
Ask the user to choose **one** of the following two options for the target topic branch:
1. **Create a new topic branch (with default branch name)** – Create a new topic branch for the emitted changes. If selected, this default branch name will be used "/", where `github-userid` is the user's personal GitHub ID (not the Microsoft Enterprise Managed User (EMU) account!) and `DD-MM-HHMM` is the current date-time using date, month, hour and minute. For example, if the GitHub ID is "dargilco" and the current date and time is May 1st, 2026 at 8:13am, the default branch name would be `dargilco/emit-from-typespec-01-05-0813`. This should be the default option, and the default branch name should be displayed. If you press enter without typing anything, this option will be selected.
-2. **Create a new topic branch (branch name given by user)** - Ask the user for the branch name. Mention that a common format is "/". If the user enters a branch name `feature/azure-ai-projects/2.3.0` then stop and report that they cannot emit directly to the current feature branch.
+2. **Create a new topic branch (branch name given by user)** - Ask the user for the branch name. Mention that a common format is "/". If the user enters a branch name `feature/azure-ai-projects/vnext` then stop and report that they cannot emit directly to the current feature branch.
-3. **Emit to current branch** – Emit directly to the current branch without creating a new topic branch. This is not common, but may be necessary if the user is re-running this workflow because of a previous failure, where the topic branch was already created. If the current branch is named `feature/azure-ai-projects/2.3.0` then stop and report that they cannot emit directly to the current feature branch.
+3. **Emit to current branch** – Emit directly to the current branch without creating a new topic branch. This is not common, but may be necessary if the user is re-running this workflow because of a previous failure, where the topic branch was already created. If the current branch is named `feature/azure-ai-projects/vnext` then stop and report that they cannot emit directly to the current feature branch.
-### 1b. TypeSpec source
+### 2b. TypeSpec source
Ask the user to choose **one** of the following three options for the TypeSpec source:
@@ -44,7 +158,7 @@ Ask the user to choose **one** of the following three options for the TypeSpec s
---
-## Step 2: Record the current branch
+## Step 3: Record the current branch
Before creating the topic branch, record the name of the **current Git branch**. This is the branch that the topic branch will be created from, and the branch the PR will target.
@@ -56,7 +170,7 @@ Save this as `BASE_BRANCH`.
---
-## Step 3: Create the topic branch
+## Step 4: Create the topic branch
Create the topic branch off the current branch and switch to it:
@@ -65,11 +179,11 @@ git fetch
git switch -c origin/
```
-Replace `` with the name provided by the user in Step 1a.
+Replace `` with the name provided by the user in Step 2a.
---
-## Step 4: Emit SDK from TypeSpec
+## Step 5: Emit SDK from TypeSpec
If you are emitting from latest commit or a given commit number, edit file `tsp-location.yaml` to update the full hash commit number, then in the folder `sdk/ai/azure-ai-projects` run the command: `tsp-client update --debug`
@@ -81,7 +195,7 @@ Note:
---
-## Step 5: Revert changes to files pyproject.toml and MANIFEST.in
+## Step 6: Revert changes to files pyproject.toml and MANIFEST.in
After the emit, there will be changes to `pyproject.toml` and `MANIFEST.in` that are not needed. Revert any changes to these files by running:
@@ -91,7 +205,7 @@ git restore pyproject.toml MANIFEST.in
---
-## Step 6: Commit and push
+## Step 7: Commit and push
Stage all changes (excluding file names that start with `.env`), commit, and push the topic branch:
@@ -105,7 +219,7 @@ git push -u origin
---
-## Step 7: Run post-emitter fixes
+## Step 8: Run post-emitter fixes
After a successful emit, run the PowerShell script named `PostEmitter.ps1` located in the `sdk/ai/azure-ai-projects` folder.
@@ -115,7 +229,7 @@ This script applies azure-ai-projects specific corrections to the emitted code (
---
-## Step 8: Commit and push
+## Step 9: Commit and push
Stage all changes (excluding file names that start with `.env`), commit, and push the topic branch:
@@ -129,7 +243,7 @@ git push -u origin
---
-## Step 9: Fix patched code related to preview feature headers
+## Step 10: Fix patched code related to preview feature headers
The emitted code may have introduced another beta sub-client (a new property on class `BetaOperations`). It may have also added another enum value to the existing internal class `_FoundryFeaturesOptInKeys`. This means that the client library needs to set a new HTTP request header when making REST API calls to the service, to opt-in to the new service features which are still in preview. If that's the case, do the following:
@@ -145,7 +259,7 @@ Important: Under the `azure\ai\projects` folder, you are only allowed to edit Py
---
-## Step 10: Update samples and tests
+## Step 11: Update samples and tests
If there were any breaking changes in existing APIs, like class or method renames:
* update the patched code accordingly in the client library to reflect those changes. Changes should be made to Python source file names that start with "_patch", under the `azure\ai\projects` folder.
@@ -154,13 +268,13 @@ If there were any breaking changes in existing APIs, like class or method rename
---
-## Step 11: Install package from sources
+## Step 12: Install package from sources
In the folder `sdk\ai\azure-ai-projects`, run `pip install -e .` to install the package from sources. If there are any errors, stop and report the error to the user. Do not continue.
---
-## Step 12: Run `apiview-stub-generator` to update api.md and api.metadata.yml files
+## Step 13: Run `apiview-stub-generator` to update api.md and api.metadata.yml files
In the folder `sdk\ai\azure-ai-projects`, run the following command:
@@ -178,7 +292,7 @@ rmdir /s /q build
---
-## Step 13: Commit and push
+## Step 14: Commit and push
Stage all changes (excluding file names that start with `.env`), commit, and push the topic branch:
@@ -192,9 +306,9 @@ git push -u origin
---
-## Step 14: Create a Pull Request
+## Step 15: Create a Pull Request
-Create a draft PR from the **topic branch** to the **base branch** (recorded in Step 2):
+Create a draft PR from the **topic branch** to the **base branch** (recorded in Step 3):
```
gh pr create --draft --base --head --assignee @me --title "" --body ""
@@ -205,23 +319,8 @@ gh pr create --draft --base --head --assignee @me -
You must show the user the resulting PR URL on screen when done, before you continue to the next step.
-Open a new tab in the default browser and navigate to the PR URL.
+Open a new tab in the default operating system browser and navigate to the PR URL (do not use the built-in browser in VS Code, if running this skill in the VS Code GitHub CoPilot chat window).
---
-## Step 15: Optionally run tests locally
-
-Prompt the user with this message: "Tests will run as part of the Pull Request. However, you can optionally run tests locally in a Python virtual environment, right now. It will take a few minutes. Do you want to run tests locally? (yes/no)"
-
-If the user answers "yes", run all tests from recordings. Follow these guidelines:
-* Run tests in a local Python virtual environment. Create this virtual environment if it does not already exists:
- ```
- python -m venv .venv
- ```
- and activate it:
- ```
- .venv\Scripts\activate
- ```
-* Show test progress on screen, as tests are run.
-
diff --git a/sdk/ai/azure-ai-projects/.github/skills/azure-ai-projects-update-changelog/SKILL.md b/sdk/ai/azure-ai-projects/.github/skills/azure-ai-projects-update-changelog/SKILL.md
index eeb810776294..297c15893e93 100644
--- a/sdk/ai/azure-ai-projects/.github/skills/azure-ai-projects-update-changelog/SKILL.md
+++ b/sdk/ai/azure-ai-projects/.github/skills/azure-ai-projects-update-changelog/SKILL.md
@@ -56,7 +56,7 @@ From the JSON response:
Check if CHANGELOG.md already has a section for the current version:
- If there's a section `## {CURRENT_VERSION} (Unreleased)` — we will update it
-- If there's a section `## {CURRENT_VERSION} (YYYY-MM-DD)` with an actual date — the version is already released, report this to the user and stop
+- If there's a section `## {CURRENT_VERSION} (YYYY-MM-DD)` with an actual date. If the date is in the past, then this version is already released, report this to the user and stop. If it's today's date, we will update it.
- If there's no section for `CURRENT_VERSION` — we will create a new one
---
diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md
index 9b0934c4f8d7..22ae9928800a 100644
--- a/sdk/ai/azure-ai-projects/CHANGELOG.md
+++ b/sdk/ai/azure-ai-projects/CHANGELOG.md
@@ -1,5 +1,42 @@
# Release History
+## 2.4.0 (2026-07-24)
+
+### Features Added
+
+* New stable toolbox tool `ToolSearchToolboxTool` (discriminator `toolbox_search`) for storing a tool-search tool in a toolbox.
+This replaces `ToolboxSearchPreviewToolboxTool`, which is still present but will be removed in a future release of the package. Please migrate your code to use the stable tool.
+* New class `TaskGenerationDataGenerationJobOptions` (discriminator `task_generation`) with data generation job options for multi-turn evaluation scenarios.
+* Support for non-fatal input-quality advisories from rubric evaluator generation. See new class `RubricGenerationInputQualityWarning` and new enums `RubricGenerationInputQualityWarningCode`, `RubricGenerationInputQualityWarningSeverity`, and `RubricGenerationInputQualityWarningSource`.
+* New enum `GenerationWarningType`.
+* New enum `AgentIdentityStatus` and new optional `status` property on class `AgentIdentity`.
+* New read-only property `input_quality_warnings` on class `EvaluatorGenerationJob`.
+* New read-only properties `generation_job_id` and `warnings` on class `EvaluatorVersion`.
+* New optional property `max_stalls` on class `OptimizationOptions`.
+
+### Breaking Changes
+
+Breaking changes in beta methods:
+* Method `.beta.evaluators.create_generation_job` renamed to `.beta.evaluators.begin_create_generation_job` and is now a long-running operation returning `LROPoller[EvaluatorVersion]` (previously returned `EvaluatorGenerationJob`).
+* Method `.beta.datasets.create_generation_job` renamed to `.beta.datasets.begin_create_generation_job` and is now a long-running operation returning `LROPoller[DataGenerationJobResult]`.
+* Method `.beta.agents.create_optimization_job` renamed to `.beta.agents.begin_create_optimization_job` and is now a long-running operation returning `LROPoller[OptimizationJobResult]`.
+
+### Sample updates
+
+* Added new optimization polling samples `sample_optimization_job_basic_polling.py` and `sample_optimization_job_basic_polling_async.py` under `samples/agents/optimization/`.
+* Added new evaluation samples `sample_endpoint_evaluator_with_api_key.py` and `sample_endpoint_evaluator_with_entra_id.py` under `samples/evaluations/`.
+* Added new Hosted Agent sample `sample_agent_user_identity_isolation.py` under `samples/hosted_agents/`, demonstrating per-user response-chain isolation with delegated end-user identities sent in the `x-ms-user-identity` header.
+* Added new Hosted Agent routine samples `sample_routines_with_github_issue_trigger.py` and `sample_routines_with_teams_message_trigger.py`, demonstrating GitHub issue and Microsoft Teams channel-message triggers for routines backed by a temporary Hosted Agent version.
+* Added new Hosted Agent sample `sample_toolbox_with_reminder_preview.py` under `samples/hosted_agents/`, demonstrating a Reminder Preview toolbox tool wired through a Foundry Toolbox MCP endpoint.
+* Updated Hosted Agent toolbox asset `samples/hosted_agents/assets/toolbox-agent/main.py` to use `FoundryToolbox` and `as_skills_provider()` for toolbox MCP skill discovery and wiring, replacing the earlier manual MCP session, auth, and HTTP client setup.
+* Renamed toolbox tool-search samples `sample_toolboxes_with_search_preview.py` and `sample_toolboxes_with_search_preview_async.py` to `sample_toolboxes_with_search.py` and `sample_toolboxes_with_search_async.py`.
+* Renamed the Hosted Agent image-based creation samples from `sample_create_hosted_agent.py` and `sample_create_hosted_agent_async.py` to `sample_create_hosted_agent_from_image.py` and `sample_create_hosted_agent_from_image_async.py`.
+* Relocated Hosted Agent routine trigger samples `sample_routines_with_dispatch.py`, `sample_routines_with_schedule_trigger.py`, and `sample_routines_with_timer_trigger.py` from `samples/routines/` to `samples/hosted_agents/`.
+* Removed Hosted Agent endpoint samples `sample_agent_endpoint.py` and `sample_agent_endpoint_async.py`.
+* Removed routine sample `samples/routines/sample_routines_crud.py`.
+* Removed prompt-agent toolbox skill sample `samples/agents/tools/sample_agent_toolbox_skill.py` because skill-in-toolbox is not yet supported in Prompt Agents.
+* Updated Hosted Agent toolbox samples to create temporary Hosted Agent versions for execution flows, assign Azure AI User RBAC before invoking Toolbox MCP endpoints, restore the prior endpoint, and clean up temporary resources during teardown.
+
## 2.3.0 (2026-07-01)
### Features Added
diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md
index ff4feb4c87c5..da4f812a1b7e 100644
--- a/sdk/ai/azure-ai-projects/README.md
+++ b/sdk/ai/azure-ai-projects/README.md
@@ -24,9 +24,8 @@ resources in your [Microsoft Foundry](https://ai.azure.com/) Project. Use it to:
* Model Context Protocol (MCP)
* OpenAPI
* Reminder Tool (Preview)
- * Toolbox Search (Preview)
+ * Toolbox Search
* Web Search
- * Web Search (Preview)
* Work IQ (Preview)
* **Get an OpenAI client** using `.get_openai_client()` method to run Responses, Conversations, Evaluations and Fine-Tuning operations with your Agent.
* **Create and version toolboxes** that bundle collections of tools and skills for your agents, using `.toolboxes` operations.
diff --git a/sdk/ai/azure-ai-projects/api.md b/sdk/ai/azure-ai-projects/api.md
index f9aef5d02952..a683fb81cd13 100644
--- a/sdk/ai/azure-ai-projects/api.md
+++ b/sdk/ai/azure-ai-projects/api.md
@@ -9,6 +9,7 @@ namespace azure.ai.projects
deployments: DeploymentsOperations
evaluation_rules: EvaluationRulesOperations
indexes: IndexesOperations
+ toolboxes: ToolboxesOperations
def __init__(
self,
@@ -17,6 +18,7 @@ namespace azure.ai.projects
*,
allow_preview: bool = False,
api_version: str = ...,
+ polling_interval: Optional[int] = ...,
**kwargs: Any
) -> None: ...
@@ -49,6 +51,7 @@ namespace azure.ai.projects.aio
deployments: DeploymentsOperations
evaluation_rules: EvaluationRulesOperations
indexes: IndexesOperations
+ toolboxes: ToolboxesOperations
def __init__(
self,
@@ -57,6 +60,7 @@ namespace azure.ai.projects.aio
*,
allow_preview: bool = False,
api_version: str = ...,
+ polling_interval: Optional[int] = ...,
**kwargs: Any
) -> None: ...
@@ -409,41 +413,41 @@ namespace azure.ai.projects.aio.operations
**kwargs
) -> None: ...
- @distributed_trace_async
- async def cancel_optimization_job(
- self,
- job_id: str,
- **kwargs: Any
- ) -> OptimizationJob: ...
-
@overload
- async def create_optimization_job(
+ async def begin_create_optimization_job(
self,
job: OptimizationJob,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> OptimizationJob: ...
+ ) -> AsyncLROPoller[OptimizationJobResult]: ...
@overload
- async def create_optimization_job(
+ async def begin_create_optimization_job(
self,
job: JSON,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> OptimizationJob: ...
+ ) -> AsyncLROPoller[OptimizationJobResult]: ...
@overload
- async def create_optimization_job(
+ async def begin_create_optimization_job(
self,
job: IO[bytes],
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
+ ) -> AsyncLROPoller[OptimizationJobResult]: ...
+
+ @distributed_trace_async
+ async def cancel_optimization_job(
+ self,
+ job_id: str,
+ **kwargs: Any
) -> OptimizationJob: ...
@distributed_trace_async
@@ -481,41 +485,41 @@ namespace azure.ai.projects.aio.operations
**kwargs
) -> None: ...
- @distributed_trace_async
- async def cancel_generation_job(
- self,
- job_id: str,
- **kwargs: Any
- ) -> DataGenerationJob: ...
-
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: DataGenerationJob,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> DataGenerationJob: ...
+ ) -> AsyncLROPoller[DataGenerationJobResult]: ...
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: JSON,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> DataGenerationJob: ...
+ ) -> AsyncLROPoller[DataGenerationJobResult]: ...
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: IO[bytes],
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
+ ) -> AsyncLROPoller[DataGenerationJobResult]: ...
+
+ @distributed_trace_async
+ async def cancel_generation_job(
+ self,
+ job_id: str,
+ **kwargs: Any
) -> DataGenerationJob: ...
@distributed_trace_async
@@ -643,41 +647,41 @@ namespace azure.ai.projects.aio.operations
**kwargs
) -> None: ...
- @distributed_trace_async
- async def cancel_generation_job(
- self,
- job_id: str,
- **kwargs: Any
- ) -> EvaluatorGenerationJob: ...
-
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: EvaluatorGenerationJob,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> EvaluatorGenerationJob: ...
+ ) -> AsyncLROPoller[EvaluatorVersion]: ...
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: JSON,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> EvaluatorGenerationJob: ...
+ ) -> AsyncLROPoller[EvaluatorVersion]: ...
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: IO[bytes],
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
+ ) -> AsyncLROPoller[EvaluatorVersion]: ...
+
+ @distributed_trace_async
+ async def cancel_generation_job(
+ self,
+ job_id: str,
+ **kwargs: Any
) -> EvaluatorGenerationJob: ...
@overload
@@ -2631,19 +2635,26 @@ namespace azure.ai.projects.models
class azure.ai.projects.models.AgentIdentity(_Model):
client_id: str
principal_id: str
+ status: Optional[Union[str, AgentIdentityStatus]]
@overload
def __init__(
self,
*,
client_id: str,
- principal_id: str
+ principal_id: str,
+ status: Optional[Union[str, AgentIdentityStatus]] = ...
) -> None: ...
@overload
def __init__(self, mapping: Mapping[str, Any]) -> None: ...
+ class azure.ai.projects.models.AgentIdentityStatus(str, Enum, metaclass=CaseInsensitiveEnumMeta):
+ ACTIVE = "active"
+ DISABLED = "disabled"
+
+
class azure.ai.projects.models.AgentKind(str, Enum, metaclass=CaseInsensitiveEnumMeta):
EXTERNAL = "external"
HOSTED = "hosted"
@@ -4226,6 +4237,7 @@ namespace azure.ai.projects.models
class azure.ai.projects.models.DataGenerationJobType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
SIMPLE_QNA = "simple_qna"
+ TASK_GENERATION = "task_generation"
TOOL_USE = "tool_use"
TRACES = "traces"
@@ -5008,6 +5020,7 @@ namespace azure.ai.projects.models
error: Optional[ApiError]
finished_at: Optional[datetime]
id: str
+ input_quality_warnings: Optional[list[RubricGenerationInputQualityWarning]]
inputs: Optional[EvaluatorGenerationInputs]
result: Optional[EvaluatorVersion]
status: Union[str, JobStatus]
@@ -5113,6 +5126,7 @@ namespace azure.ai.projects.models
display_name: Optional[str]
evaluator_type: Union[str, EvaluatorType]
generation_artifacts: Optional[EvaluatorGenerationArtifacts]
+ generation_job_id: Optional[str]
id: Optional[str]
metadata: Optional[dict[str, str]]
modified_at: datetime
@@ -5120,6 +5134,7 @@ namespace azure.ai.projects.models
supported_evaluation_levels: Optional[list[Union[str, EvaluationLevel]]]
tags: Optional[dict[str, str]]
version: str
+ warnings: Optional[list[Union[str, GenerationWarningType]]]
@overload
def __init__(
@@ -5555,6 +5570,10 @@ namespace azure.ai.projects.models
def __init__(self, mapping: Mapping[str, Any]) -> None: ...
+ class azure.ai.projects.models.GenerationWarningType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
+ INPUT_QUALITY = "input_quality"
+
+
class azure.ai.projects.models.GitHubIssueEvent(str, Enum, metaclass=CaseInsensitiveEnumMeta):
CLOSED = "closed"
OPENED = "opened"
@@ -7206,6 +7225,7 @@ namespace azure.ai.projects.models
eval_model: Optional[str]
evaluation_level: Optional[Union[str, EvaluationLevel]]
max_candidates: Optional[int]
+ max_stalls: Optional[int]
optimization_config: Optional[dict[str, Any]]
optimization_model: Optional[str]
@@ -7216,6 +7236,7 @@ namespace azure.ai.projects.models
eval_model: Optional[str] = ...,
evaluation_level: Optional[Union[str, EvaluationLevel]] = ...,
max_candidates: Optional[int] = ...,
+ max_stalls: Optional[int] = ...,
optimization_config: Optional[dict[str, Any]] = ...,
optimization_model: Optional[str] = ...
) -> None: ...
@@ -7893,6 +7914,50 @@ namespace azure.ai.projects.models
def __init__(self, mapping: Mapping[str, Any]) -> None: ...
+ class azure.ai.projects.models.RubricGenerationInputQualityWarning(_Model):
+ code: Union[str, RubricGenerationInputQualityWarningCode]
+ message: str
+ severity: Union[str, RubricGenerationInputQualityWarningSeverity]
+ source: Union[str, RubricGenerationInputQualityWarningSource]
+ source_index: Optional[int]
+
+ @overload
+ def __init__(
+ self,
+ *,
+ code: Union[str, RubricGenerationInputQualityWarningCode],
+ message: str,
+ severity: Union[str, RubricGenerationInputQualityWarningSeverity],
+ source: Union[str, RubricGenerationInputQualityWarningSource],
+ source_index: Optional[int] = ...
+ ) -> None: ...
+
+ @overload
+ def __init__(self, mapping: Mapping[str, Any]) -> None: ...
+
+
+ class azure.ai.projects.models.RubricGenerationInputQualityWarningCode(str, Enum, metaclass=CaseInsensitiveEnumMeta):
+ EMPTY_AGENT_INSTRUCTIONS = "empty_agent_instructions"
+ EMPTY_DATASET_CONTENT = "empty_dataset_content"
+ EMPTY_PROMPT = "empty_prompt"
+ INSUFFICIENT_TOTAL_INPUT = "insufficient_total_input"
+ LOW_TRACE_COUNT = "low_trace_count"
+ SHORT_AGENT_INSTRUCTIONS = "short_agent_instructions"
+ SHORT_DATASET_CONTENT = "short_dataset_content"
+ SHORT_PROMPT = "short_prompt"
+
+
+ class azure.ai.projects.models.RubricGenerationInputQualityWarningSeverity(str, Enum, metaclass=CaseInsensitiveEnumMeta):
+ WARNING = "warning"
+
+
+ class azure.ai.projects.models.RubricGenerationInputQualityWarningSource(str, Enum, metaclass=CaseInsensitiveEnumMeta):
+ AGENT = "agent"
+ AGGREGATE = "aggregate"
+ DATASET = "dataset"
+ PROMPT = "prompt"
+
+
class azure.ai.projects.models.SASCredentials(BaseCredentials, discriminator='SAS'):
sas_token: Optional[str]
type: Literal[CredentialType.SAS]
@@ -8281,6 +8346,25 @@ namespace azure.ai.projects.models
key "type": Required[Literal["azure_ai_target_completions"]]
+ class azure.ai.projects.models.TaskGenerationDataGenerationJobOptions(DataGenerationJobOptions, discriminator='task_generation'):
+ max_samples: int
+ model_options: DataGenerationModelOptions
+ train_split: float
+ type: Literal[DataGenerationJobType.TASK_GENERATION]
+
+ @overload
+ def __init__(
+ self,
+ *,
+ max_samples: int,
+ model_options: Optional[DataGenerationModelOptions] = ...,
+ train_split: Optional[float] = ...
+ ) -> None: ...
+
+ @overload
+ def __init__(self, mapping: Mapping[str, Any]) -> None: ...
+
+
class azure.ai.projects.models.TaxonomyCategory(_Model):
description: Optional[str]
id: str
@@ -8741,6 +8825,25 @@ namespace azure.ai.projects.models
def __init__(self, mapping: Mapping[str, Any]) -> None: ...
+ class azure.ai.projects.models.ToolSearchToolboxTool(ToolboxTool, discriminator='toolbox_search'):
+ description: str
+ name: str
+ tool_configs: dict[str, ToolConfig]
+ type: Literal[ToolboxToolType.TOOLBOX_SEARCH]
+
+ @overload
+ def __init__(
+ self,
+ *,
+ description: Optional[str] = ...,
+ name: Optional[str] = ...,
+ tool_configs: Optional[dict[str, ToolConfig]] = ...
+ ) -> None: ...
+
+ @overload
+ def __init__(self, mapping: Mapping[str, Any]) -> None: ...
+
+
class azure.ai.projects.models.ToolType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
A2A_PREVIEW = "a2a_preview"
APPLY_PATCH = "apply_patch"
@@ -8904,6 +9007,7 @@ namespace azure.ai.projects.models
MCP = "mcp"
OPENAPI = "openapi"
REMINDER_PREVIEW = "reminder_preview"
+ TOOLBOX_SEARCH = "toolbox_search"
TOOLBOX_SEARCH_PREVIEW = "toolbox_search_preview"
WEB_SEARCH = "web_search"
WORK_IQ_PREVIEW = "work_iq_preview"
@@ -9700,41 +9804,41 @@ namespace azure.ai.projects.operations
**kwargs
) -> None: ...
- @distributed_trace
- def cancel_optimization_job(
- self,
- job_id: str,
- **kwargs: Any
- ) -> OptimizationJob: ...
-
@overload
- def create_optimization_job(
+ def begin_create_optimization_job(
self,
job: OptimizationJob,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> OptimizationJob: ...
+ ) -> LROPoller[OptimizationJobResult]: ...
@overload
- def create_optimization_job(
+ def begin_create_optimization_job(
self,
job: JSON,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> OptimizationJob: ...
+ ) -> LROPoller[OptimizationJobResult]: ...
@overload
- def create_optimization_job(
+ def begin_create_optimization_job(
self,
job: IO[bytes],
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
+ ) -> LROPoller[OptimizationJobResult]: ...
+
+ @distributed_trace
+ def cancel_optimization_job(
+ self,
+ job_id: str,
+ **kwargs: Any
) -> OptimizationJob: ...
@distributed_trace
@@ -9772,41 +9876,41 @@ namespace azure.ai.projects.operations
**kwargs
) -> None: ...
- @distributed_trace
- def cancel_generation_job(
- self,
- job_id: str,
- **kwargs: Any
- ) -> DataGenerationJob: ...
-
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: DataGenerationJob,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> DataGenerationJob: ...
+ ) -> LROPoller[DataGenerationJobResult]: ...
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: JSON,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> DataGenerationJob: ...
+ ) -> LROPoller[DataGenerationJobResult]: ...
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: IO[bytes],
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
+ ) -> LROPoller[DataGenerationJobResult]: ...
+
+ @distributed_trace
+ def cancel_generation_job(
+ self,
+ job_id: str,
+ **kwargs: Any
) -> DataGenerationJob: ...
@distributed_trace
@@ -9934,41 +10038,41 @@ namespace azure.ai.projects.operations
**kwargs
) -> None: ...
- @distributed_trace
- def cancel_generation_job(
- self,
- job_id: str,
- **kwargs: Any
- ) -> EvaluatorGenerationJob: ...
-
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: EvaluatorGenerationJob,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> EvaluatorGenerationJob: ...
+ ) -> LROPoller[EvaluatorVersion]: ...
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: JSON,
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
- ) -> EvaluatorGenerationJob: ...
+ ) -> LROPoller[EvaluatorVersion]: ...
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: IO[bytes],
*,
content_type: str = "application/json",
operation_id: Optional[str] = ...,
**kwargs: Any
+ ) -> LROPoller[EvaluatorVersion]: ...
+
+ @distributed_trace
+ def cancel_generation_job(
+ self,
+ job_id: str,
+ **kwargs: Any
) -> EvaluatorGenerationJob: ...
@overload
diff --git a/sdk/ai/azure-ai-projects/api.metadata.yml b/sdk/ai/azure-ai-projects/api.metadata.yml
index cb9eb3ee38f1..3d493abef420 100644
--- a/sdk/ai/azure-ai-projects/api.metadata.yml
+++ b/sdk/ai/azure-ai-projects/api.metadata.yml
@@ -1,3 +1,3 @@
-apiMdSha256: 3769cdd6d12da4829d90f71066b705ee8951c33eac0288d5a5cc3ebc002e6df0
+apiMdSha256: 544c82773e2ee8b4aeb0ece5b64bb938f2d3703720950214d3e4c5c98e3e61fd
parserVersion: 0.3.30
pythonVersion: 3.14.3
diff --git a/sdk/ai/azure-ai-projects/apiview-properties.json b/sdk/ai/azure-ai-projects/apiview-properties.json
index fa141e2c53cf..fd4f2a8e648b 100644
--- a/sdk/ai/azure-ai-projects/apiview-properties.json
+++ b/sdk/ai/azure-ai-projects/apiview-properties.json
@@ -279,7 +279,6 @@
"azure.ai.projects.models.Reasoning": "OpenAI.Reasoning",
"azure.ai.projects.models.RecurrenceTrigger": "Azure.AI.Projects.RecurrenceTrigger",
"azure.ai.projects.models.RedTeam": "Azure.AI.Projects.RedTeam",
- "azure.ai.projects.models.ReminderPreviewTool": "Azure.AI.Projects.ReminderPreviewTool",
"azure.ai.projects.models.ReminderPreviewToolboxTool": "Azure.AI.Projects.ReminderPreviewToolboxTool",
"azure.ai.projects.models.ResponsesProtocolConfiguration": "Azure.AI.Projects.ResponsesProtocolConfiguration",
"azure.ai.projects.models.ResponseUsageInputTokensDetails": "OpenAI.ResponseUsageInputTokensDetails",
@@ -287,6 +286,7 @@
"azure.ai.projects.models.Routine": "Azure.AI.Projects.Routine",
"azure.ai.projects.models.RoutineRun": "Azure.AI.Projects.RoutineRun",
"azure.ai.projects.models.RubricBasedEvaluatorDefinition": "Azure.AI.Projects.RubricBasedEvaluatorDefinition",
+ "azure.ai.projects.models.RubricGenerationInputQualityWarning": "Azure.AI.Projects.RubricGenerationInputQualityWarning",
"azure.ai.projects.models.SASCredentials": "Azure.AI.Projects.SASCredentials",
"azure.ai.projects.models.Schedule": "Azure.AI.Projects.Schedule",
"azure.ai.projects.models.ScheduleRoutineTrigger": "Azure.AI.Projects.ScheduleRoutineTrigger",
@@ -306,6 +306,7 @@
"azure.ai.projects.models.SpecificFunctionShellParam": "OpenAI.SpecificFunctionShellParam",
"azure.ai.projects.models.StructuredInputDefinition": "Azure.AI.Projects.StructuredInputDefinition",
"azure.ai.projects.models.StructuredOutputDefinition": "Azure.AI.Projects.StructuredOutputDefinition",
+ "azure.ai.projects.models.TaskGenerationDataGenerationJobOptions": "Azure.AI.Projects.TaskGenerationDataGenerationJobOptions",
"azure.ai.projects.models.TaxonomyCategory": "Azure.AI.Projects.TaxonomyCategory",
"azure.ai.projects.models.TaxonomySubCategory": "Azure.AI.Projects.TaxonomySubCategory",
"azure.ai.projects.models.TelemetryConfig": "Azure.AI.Projects.TelemetryConfig",
@@ -335,6 +336,7 @@
"azure.ai.projects.models.ToolConfig": "Azure.AI.Projects.ToolConfig",
"azure.ai.projects.models.ToolDescription": "Azure.AI.Projects.ToolDescription",
"azure.ai.projects.models.ToolProjectConnection": "Azure.AI.Projects.ToolProjectConnection",
+ "azure.ai.projects.models.ToolSearchToolboxTool": "Azure.AI.Projects.ToolSearchToolboxTool",
"azure.ai.projects.models.ToolSearchToolParam": "OpenAI.ToolSearchToolParam",
"azure.ai.projects.models.ToolUseFineTuningDataGenerationJobOptions": "Azure.AI.Projects.ToolUseFineTuningDataGenerationJobOptions",
"azure.ai.projects.models.TracesDataGenerationJobOptions": "Azure.AI.Projects.TracesDataGenerationJobOptions",
@@ -380,9 +382,13 @@
"azure.ai.projects.models.EvaluatorDefinitionType": "Azure.AI.Projects.EvaluatorDefinitionType",
"azure.ai.projects.models.EvaluatorMetricType": "Azure.AI.Projects.EvaluatorMetricType",
"azure.ai.projects.models.EvaluatorMetricDirection": "Azure.AI.Projects.EvaluatorMetricDirection",
+ "azure.ai.projects.models.GenerationWarningType": "Azure.AI.Projects.GenerationWarningType",
"azure.ai.projects.models.PendingUploadType": "Azure.AI.Projects.PendingUploadType",
"azure.ai.projects.models.EvaluatorGenerationJobSourceType": "Azure.AI.Projects.EvaluatorGenerationJobSourceType",
"azure.ai.projects.models.JobStatus": "Azure.AI.Projects.JobStatus",
+ "azure.ai.projects.models.RubricGenerationInputQualityWarningCode": "Azure.AI.Projects.RubricGenerationInputQualityWarningCode",
+ "azure.ai.projects.models.RubricGenerationInputQualityWarningSeverity": "Azure.AI.Projects.RubricGenerationInputQualityWarningSeverity",
+ "azure.ai.projects.models.RubricGenerationInputQualityWarningSource": "Azure.AI.Projects.RubricGenerationInputQualityWarningSource",
"azure.ai.projects.models.PageOrder": "Azure.AI.Projects.PageOrder",
"azure.ai.projects.models.OperationState": "Azure.Core.Foundations.OperationState",
"azure.ai.projects.models.InsightType": "Azure.AI.Projects.InsightType",
@@ -427,6 +433,7 @@
"azure.ai.projects.models.ToolChoiceParamType": "OpenAI.ToolChoiceParamType",
"azure.ai.projects.models.TextResponseFormatConfigurationType": "OpenAI.TextResponseFormatConfigurationType",
"azure.ai.projects.models.AgentVersionStatus": "Azure.AI.Projects.AgentVersionStatus",
+ "azure.ai.projects.models.AgentIdentityStatus": "Azure.AI.Projects.AgentIdentityStatus",
"azure.ai.projects.models.AgentBlueprintReferenceType": "Azure.AI.Projects.AgentBlueprintReferenceType",
"azure.ai.projects.models.VersionSelectorType": "Azure.AI.Projects.VersionSelectorType",
"azure.ai.projects.models.AgentEndpointAuthorizationSchemeType": "Azure.AI.Projects.AgentEndpointAuthorizationSchemeType",
@@ -541,5 +548,5 @@
"azure.ai.projects.operations.ToolboxesOperations.delete_version": "Azure.AI.Projects.Toolboxes.deleteToolboxVersion",
"azure.ai.projects.aio.operations.ToolboxesOperations.delete_version": "Azure.AI.Projects.Toolboxes.deleteToolboxVersion"
},
- "CrossLanguageVersion": "32a086fb0432"
+ "CrossLanguageVersion": "856df4c68403"
}
\ No newline at end of file
diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json
index 8aa3af13b87e..72ba85a709d4 100644
--- a/sdk/ai/azure-ai-projects/assets.json
+++ b/sdk/ai/azure-ai-projects/assets.json
@@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/ai/azure-ai-projects",
- "Tag": "python/ai/azure-ai-projects_f47dcaa04b"
+ "Tag": "python/ai/azure-ai-projects_4d4ddcf68b"
}
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py
index 508fbc18d9cd..60c172ebfdd2 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py
@@ -70,6 +70,8 @@ class AIProjectClient: # pylint: disable=too-many-instance-attributes
None. Default value is None. If not set, the operation's default API version will be used. Note
that overriding this default value may result in unsupported behavior.
:paramtype api_version: str
+ :keyword int polling_interval: Default waiting time between two polls for LRO operations if no
+ Retry-After header is present.
"""
def __init__(
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py
index f1f15152e708..9796a5679697 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py
@@ -114,6 +114,8 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins
:vartype deployments: azure.ai.projects.operations.DeploymentsOperations
:ivar indexes: IndexesOperations operations
:vartype indexes: azure.ai.projects.operations.IndexesOperations
+ :ivar toolboxes: ToolboxesOperations operations
+ :vartype toolboxes: azure.ai.projects.operations.ToolboxesOperations
:param endpoint: Foundry Project endpoint in the form
"https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you
only have one Project in your Foundry Hub, or to target the default Project in your Hub, use
@@ -134,6 +136,8 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins
:keyword api_version: The API version to use for this operation. Known values are "v1". Default
value is "v1". Note that overriding this default value may result in unsupported behavior.
:paramtype api_version: str
+ :keyword int polling_interval: Default waiting time between two polls for LRO operations if no
+ Retry-After header is present.
"""
def __init__(
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_unions.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_unions.py
new file mode 100644
index 000000000000..abad0c3afee4
--- /dev/null
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_unions.py
@@ -0,0 +1,14 @@
+# coding=utf-8
+# --------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See License.txt in the project root for license information.
+# Code generated by Microsoft (R) Python Code Generator.
+# Changes may cause incorrect behavior and will be lost if the code is regenerated.
+# --------------------------------------------------------------------------
+
+from typing import TYPE_CHECKING, Union
+
+if TYPE_CHECKING:
+ from . import models as _models
+Filters = Union["_models.ComparisonFilter", "_models.CompoundFilter"]
+RoutineRunStatus = str
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py
index 43994a9b16be..3fa6b6d4831f 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py
@@ -6,4 +6,4 @@
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
# --------------------------------------------------------------------------
-VERSION = "2.3.0"
+VERSION = "2.4.0"
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py
index c195fb690de2..967ac1cf48d2 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py
@@ -70,6 +70,8 @@ class AIProjectClient: # pylint: disable=too-many-instance-attributes
None. Default value is None. If not set, the operation's default API version will be used. Note
that overriding this default value may result in unsupported behavior.
:paramtype api_version: str
+ :keyword int polling_interval: Default waiting time between two polls for LRO operations if no
+ Retry-After header is present.
"""
def __init__(
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py
index c338b00b8bf8..ce80d545efa5 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py
@@ -45,6 +45,8 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins
:ivar deployments: DeploymentsOperations operations
:vartype deployments: azure.ai.projects.aio.operations.DeploymentsOperations
:ivar indexes: IndexesOperations operations
+ :ivar toolboxes: ToolboxesOperations operations
+ :vartype toolboxes: azure.ai.projects.aio.operations.ToolboxesOperations
:vartype indexes: azure.ai.projects.aio.operations.IndexesOperations
:param endpoint: Foundry Project endpoint in the form
"https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you
@@ -66,6 +68,8 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins
:keyword api_version: The API version to use for this operation. Known values are "v1". Default
value is "v1". Note that overriding this default value may result in unsupported behavior.
:paramtype api_version: str
+ :keyword int polling_interval: Default waiting time between two polls for LRO operations if no
+ Retry-After header is present.
"""
def __init__(
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py
index ba856252bdd8..fc836a471ebe 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py
@@ -1362,7 +1362,8 @@ async def _create_version_from_code(
* Can contain hyphens in the middle
* Must not exceed 63 characters. Required.
:type agent_name: str
- :param content: Is either a _CreateAgentVersionFromCodeContent type or a JSON type. Required.
+ :param content: The content multipart request content. Is either a
+ _CreateAgentVersionFromCodeContent type or a JSON type. Required.
:type content: ~azure.ai.projects.models._models._CreateAgentVersionFromCodeContent or JSON
:keyword code_zip_sha256: SHA-256 hex digest of the uploaded code zip. Used for change
detection (dedup) and integrity verification. Required.
@@ -3819,7 +3820,7 @@ async def pending_upload(
async def get_credentials(self, name: str, version: str, **kwargs: Any) -> _models.DatasetCredential:
"""Get dataset credentials.
- Gets the SAS credential to access the storage account associated with a Dataset version.
+ Retrieves the SAS credential to access the storage account associated with a dataset version.
:param name: The name of the resource. Required.
:type name: str
@@ -3903,7 +3904,7 @@ def __init__(self, *args, **kwargs) -> None:
async def get(self, name: str, **kwargs: Any) -> _models.Deployment:
"""Get a deployment.
- Gets a deployed model.
+ Retrieves a deployed model.
:param name: Name of the deployment. Required.
:type name: str
@@ -5733,7 +5734,7 @@ async def update(
) -> _models.EvaluationTaxonomy:
"""Update an evaluation taxonomy.
- Update an evaluation taxonomy.
+ Modifies the specified evaluation taxonomy with the provided changes.
:param name: The name of the evaluation taxonomy. Required.
:type name: str
@@ -5753,7 +5754,7 @@ async def update(
) -> _models.EvaluationTaxonomy:
"""Update an evaluation taxonomy.
- Update an evaluation taxonomy.
+ Modifies the specified evaluation taxonomy with the provided changes.
:param name: The name of the evaluation taxonomy. Required.
:type name: str
@@ -5773,7 +5774,7 @@ async def update(
) -> _models.EvaluationTaxonomy:
"""Update an evaluation taxonomy.
- Update an evaluation taxonomy.
+ Modifies the specified evaluation taxonomy with the provided changes.
:param name: The name of the evaluation taxonomy. Required.
:type name: str
@@ -5793,7 +5794,7 @@ async def update(
) -> _models.EvaluationTaxonomy:
"""Update an evaluation taxonomy.
- Update an evaluation taxonomy.
+ Modifies the specified evaluation taxonomy with the provided changes.
:param name: The name of the evaluation taxonomy. Required.
:type name: str
@@ -6537,7 +6538,7 @@ async def pending_upload(
Initiates a new pending upload or retrieves an existing one for the specified evaluator
version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -6566,7 +6567,7 @@ async def pending_upload(
Initiates a new pending upload or retrieves an existing one for the specified evaluator
version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -6595,7 +6596,7 @@ async def pending_upload(
Initiates a new pending upload or retrieves an existing one for the specified evaluator
version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -6622,7 +6623,7 @@ async def pending_upload(
Initiates a new pending upload or retrieves an existing one for the specified evaluator
version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -6715,7 +6716,7 @@ async def get_credentials(
Retrieves SAS credentials for accessing the storage account associated with the specified
evaluator version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -6744,7 +6745,7 @@ async def get_credentials(
Retrieves SAS credentials for accessing the storage account associated with the specified
evaluator version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -6773,7 +6774,7 @@ async def get_credentials(
Retrieves SAS credentials for accessing the storage account associated with the specified
evaluator version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -6800,7 +6801,7 @@ async def get_credentials(
Retrieves SAS credentials for accessing the storage account associated with the specified
evaluator version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -6878,15 +6879,87 @@ async def get_credentials(
return deserialized # type: ignore
+ async def _create_generation_job_initial(
+ self,
+ job: Union[_models.EvaluatorGenerationJob, JSON, IO[bytes]],
+ *,
+ operation_id: Optional[str] = None,
+ **kwargs: Any
+ ) -> AsyncIterator[bytes]:
+ error_map: MutableMapping = {
+ 401: ClientAuthenticationError,
+ 404: ResourceNotFoundError,
+ 409: ResourceExistsError,
+ 304: ResourceNotModifiedError,
+ }
+ error_map.update(kwargs.pop("error_map", {}) or {})
+
+ _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
+ _params = kwargs.pop("params", {}) or {}
+
+ content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
+ cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None)
+
+ content_type = content_type or "application/json"
+ _content = None
+ if isinstance(job, (IOBase, bytes)):
+ _content = job
+ else:
+ _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+
+ _request = build_beta_evaluators_create_generation_job_request(
+ operation_id=operation_id,
+ content_type=content_type,
+ api_version=self._config.api_version,
+ content=_content,
+ headers=_headers,
+ params=_params,
+ )
+ path_format_arguments = {
+ "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
+ }
+ _request.url = self._client.format_url(_request.url, **path_format_arguments)
+
+ _decompress = kwargs.pop("decompress", True)
+ _stream = True
+ pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access
+ _request, stream=_stream, **kwargs
+ )
+
+ response = pipeline_response.http_response
+
+ if response.status_code not in [201]:
+ try:
+ await response.read() # Load the body in memory and close the socket
+ except (StreamConsumedError, StreamClosedError):
+ pass
+ map_error(status_code=response.status_code, response=response, error_map=error_map)
+ error = _failsafe_deserialize(
+ _models.ApiErrorResponse,
+ response,
+ )
+ raise HttpResponseError(response=response, model=error)
+
+ response_headers = {}
+ response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+
+ return deserialized # type: ignore
+
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: _models.EvaluatorGenerationJob,
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.EvaluatorGenerationJob:
+ ) -> AsyncLROPoller[_models.EvaluatorVersion]:
"""Create an evaluator generation job.
Creates an evaluator generation job. The service generates rubric-based evaluator definitions
@@ -6900,15 +6973,16 @@ async def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: EvaluatorGenerationJob. The EvaluatorGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.EvaluatorGenerationJob
+ :return: An instance of AsyncLROPoller that returns EvaluatorVersion. The EvaluatorVersion is
+ compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.EvaluatorVersion]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self, job: JSON, *, operation_id: Optional[str] = None, content_type: str = "application/json", **kwargs: Any
- ) -> _models.EvaluatorGenerationJob:
+ ) -> AsyncLROPoller[_models.EvaluatorVersion]:
"""Create an evaluator generation job.
Creates an evaluator generation job. The service generates rubric-based evaluator definitions
@@ -6922,20 +6996,21 @@ async def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: EvaluatorGenerationJob. The EvaluatorGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.EvaluatorGenerationJob
+ :return: An instance of AsyncLROPoller that returns EvaluatorVersion. The EvaluatorVersion is
+ compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.EvaluatorVersion]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: IO[bytes],
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.EvaluatorGenerationJob:
+ ) -> AsyncLROPoller[_models.EvaluatorVersion]:
"""Create an evaluator generation job.
Creates an evaluator generation job. The service generates rubric-based evaluator definitions
@@ -6949,19 +7024,20 @@ async def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
:paramtype content_type: str
- :return: EvaluatorGenerationJob. The EvaluatorGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.EvaluatorGenerationJob
+ :return: An instance of AsyncLROPoller that returns EvaluatorVersion. The EvaluatorVersion is
+ compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.EvaluatorVersion]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@distributed_trace_async
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: Union[_models.EvaluatorGenerationJob, JSON, IO[bytes]],
*,
operation_id: Optional[str] = None,
**kwargs: Any
- ) -> _models.EvaluatorGenerationJob:
+ ) -> AsyncLROPoller[_models.EvaluatorVersion]:
"""Create an evaluator generation job.
Creates an evaluator generation job. The service generates rubric-based evaluator definitions
@@ -6973,78 +7049,68 @@ async def create_generation_job(
:keyword operation_id: Client-generated unique ID for idempotent retries. When absent, the
server creates the job unconditionally. Default value is None.
:paramtype operation_id: str
- :return: EvaluatorGenerationJob. The EvaluatorGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.EvaluatorGenerationJob
+ :return: An instance of AsyncLROPoller that returns EvaluatorVersion. The EvaluatorVersion is
+ compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.EvaluatorVersion]
:raises ~azure.core.exceptions.HttpResponseError:
"""
- error_map: MutableMapping = {
- 401: ClientAuthenticationError,
- 404: ResourceNotFoundError,
- 409: ResourceExistsError,
- 304: ResourceNotModifiedError,
- }
- error_map.update(kwargs.pop("error_map", {}) or {})
-
_headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
_params = kwargs.pop("params", {}) or {}
content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
- cls: ClsType[_models.EvaluatorGenerationJob] = kwargs.pop("cls", None)
+ cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None)
+ polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True)
+ lro_delay = kwargs.pop("polling_interval", self._config.polling_interval)
+ cont_token: Optional[str] = kwargs.pop("continuation_token", None)
+ if cont_token is None:
+ raw_result = await self._create_generation_job_initial(
+ job=job,
+ operation_id=operation_id,
+ content_type=content_type,
+ cls=lambda x, y, z: x,
+ headers=_headers,
+ params=_params,
+ **kwargs
+ )
+ await raw_result.http_response.read() # type: ignore
+ kwargs.pop("error_map", None)
- content_type = content_type or "application/json"
- _content = None
- if isinstance(job, (IOBase, bytes)):
- _content = job
- else:
- _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+ def get_long_running_output(pipeline_response):
+ response_headers = {}
+ response = pipeline_response.http_response
+ response_headers["Operation-Location"] = self._deserialize(
+ "str", response.headers.get("Operation-Location")
+ )
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = _deserialize(_models.EvaluatorVersion, response.json().get("result", {}))
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+ return deserialized
- _request = build_beta_evaluators_create_generation_job_request(
- operation_id=operation_id,
- content_type=content_type,
- api_version=self._config.api_version,
- content=_content,
- headers=_headers,
- params=_params,
- )
path_format_arguments = {
"endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
}
- _request.url = self._client.format_url(_request.url, **path_format_arguments)
-
- _decompress = kwargs.pop("decompress", True)
- _stream = kwargs.pop("stream", False)
- pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access
- _request, stream=_stream, **kwargs
- )
- response = pipeline_response.http_response
-
- if response.status_code not in [201]:
- if _stream:
- try:
- await response.read() # Load the body in memory and close the socket
- except (StreamConsumedError, StreamClosedError):
- pass
- map_error(status_code=response.status_code, response=response, error_map=error_map)
- error = _failsafe_deserialize(
- _models.ApiErrorResponse,
- response,
+ if polling is True:
+ polling_method: AsyncPollingMethod = cast(
+ AsyncPollingMethod,
+ AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs),
)
- raise HttpResponseError(response=response, model=error)
-
- response_headers = {}
- response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
- response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
-
- if _stream:
- deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+ elif polling is False:
+ polling_method = cast(AsyncPollingMethod, AsyncNoPolling())
else:
- deserialized = _deserialize(_models.EvaluatorGenerationJob, response.json())
-
- if cls:
- return cls(pipeline_response, deserialized, response_headers) # type: ignore
-
- return deserialized # type: ignore
+ polling_method = polling
+ if cont_token:
+ return AsyncLROPoller[_models.EvaluatorVersion].from_continuation_token(
+ polling_method=polling_method,
+ continuation_token=cont_token,
+ client=self._client,
+ deserialization_callback=get_long_running_output,
+ )
+ return AsyncLROPoller[_models.EvaluatorVersion](
+ self._client, raw_result, get_long_running_output, polling_method # type: ignore
+ )
@distributed_trace_async
async def get_generation_job(self, job_id: str, **kwargs: Any) -> _models.EvaluatorGenerationJob:
@@ -9749,8 +9815,7 @@ async def get(self, name: str, version: str, **kwargs: Any) -> _models.ModelVers
async def delete(self, name: str, version: str, **kwargs: Any) -> None:
"""Delete a model version.
- Delete the specific version of the ModelVersion. The service returns 200 OK if the ModelVersion
- was deleted successfully or if the ModelVersion does not exist.
+ Removes the specified model version. Returns 200 whether the version existed or not.
:param name: The name of the resource. Required.
:type name: str
@@ -9811,7 +9876,7 @@ async def update(
) -> _models.ModelVersion:
"""Update a model version.
- Update an existing ModelVersion with the given version id.
+ Updates an existing model version identified by its version ID.
:param name: The name of the resource. Required.
:type name: str
@@ -9840,7 +9905,7 @@ async def update(
) -> _models.ModelVersion:
"""Update a model version.
- Update an existing ModelVersion with the given version id.
+ Updates an existing model version identified by its version ID.
:param name: The name of the resource. Required.
:type name: str
@@ -9869,7 +9934,7 @@ async def update(
) -> _models.ModelVersion:
"""Update a model version.
- Update an existing ModelVersion with the given version id.
+ Updates an existing model version identified by its version ID.
:param name: The name of the resource. Required.
:type name: str
@@ -9896,7 +9961,7 @@ async def update(
) -> _models.ModelVersion:
"""Update a model version.
- Update an existing ModelVersion with the given version id.
+ Updates an existing model version identified by its version ID.
:param name: The name of the resource. Required.
:type name: str
@@ -10157,7 +10222,7 @@ async def pending_upload(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param pending_upload_request: Required.
+ :param pending_upload_request: The pending upload request request body. Required.
:type pending_upload_request: ~azure.ai.projects.models.ModelPendingUploadRequest
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
@@ -10186,7 +10251,7 @@ async def pending_upload(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param pending_upload_request: Required.
+ :param pending_upload_request: The pending upload request request body. Required.
:type pending_upload_request: JSON
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
@@ -10215,7 +10280,7 @@ async def pending_upload(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param pending_upload_request: Required.
+ :param pending_upload_request: The pending upload request request body. Required.
:type pending_upload_request: IO[bytes]
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
@@ -10242,8 +10307,8 @@ async def pending_upload(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param pending_upload_request: Is one of the following types: ModelPendingUploadRequest, JSON,
- IO[bytes] Required.
+ :param pending_upload_request: The pending upload request request body. Is one of the following
+ types: ModelPendingUploadRequest, JSON, IO[bytes] Required.
:type pending_upload_request: ~azure.ai.projects.models.ModelPendingUploadRequest or JSON or
IO[bytes]
:return: ModelPendingUploadResponse. The ModelPendingUploadResponse is compatible with
@@ -10331,7 +10396,7 @@ async def get_credentials(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param credential_request: Required.
+ :param credential_request: The credential request request body. Required.
:type credential_request: ~azure.ai.projects.models.ModelCredentialRequest
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
@@ -10359,7 +10424,7 @@ async def get_credentials(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param credential_request: Required.
+ :param credential_request: The credential request request body. Required.
:type credential_request: JSON
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
@@ -10387,7 +10452,7 @@ async def get_credentials(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param credential_request: Required.
+ :param credential_request: The credential request request body. Required.
:type credential_request: IO[bytes]
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
@@ -10413,8 +10478,8 @@ async def get_credentials(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param credential_request: Is one of the following types: ModelCredentialRequest, JSON,
- IO[bytes] Required.
+ :param credential_request: The credential request request body. Is one of the following types:
+ ModelCredentialRequest, JSON, IO[bytes] Required.
:type credential_request: ~azure.ai.projects.models.ModelCredentialRequest or JSON or IO[bytes]
:return: DatasetCredential. The DatasetCredential is compatible with MutableMapping
:rtype: ~azure.ai.projects.models.DatasetCredential
@@ -12682,7 +12747,7 @@ async def create_from_files(
:param name: The name of the skill. Required.
:type name: str
- :param content: Required.
+ :param content: The multipart request content. Required.
:type content: ~azure.ai.projects.models.CreateSkillVersionFromFilesBody
:return: SkillVersion. The SkillVersion is compatible with MutableMapping
:rtype: ~azure.ai.projects.models.SkillVersion
@@ -12697,7 +12762,7 @@ async def create_from_files(self, name: str, content: JSON, **kwargs: Any) -> _m
:param name: The name of the skill. Required.
:type name: str
- :param content: Required.
+ :param content: The multipart request content. Required.
:type content: JSON
:return: SkillVersion. The SkillVersion is compatible with MutableMapping
:rtype: ~azure.ai.projects.models.SkillVersion
@@ -12714,7 +12779,8 @@ async def create_from_files(
:param name: The name of the skill. Required.
:type name: str
- :param content: Is either a CreateSkillVersionFromFilesBody type or a JSON type. Required.
+ :param content: The multipart request content. Is either a CreateSkillVersionFromFilesBody type
+ or a JSON type. Required.
:type content: ~azure.ai.projects.models.CreateSkillVersionFromFilesBody or JSON
:return: SkillVersion. The SkillVersion is compatible with MutableMapping
:rtype: ~azure.ai.projects.models.SkillVersion
@@ -13335,15 +13401,87 @@ async def get_next(_continuation_token=None):
return AsyncItemPaged(get_next, extract_data)
+ async def _create_generation_job_initial(
+ self,
+ job: Union[_models.DataGenerationJob, JSON, IO[bytes]],
+ *,
+ operation_id: Optional[str] = None,
+ **kwargs: Any
+ ) -> AsyncIterator[bytes]:
+ error_map: MutableMapping = {
+ 401: ClientAuthenticationError,
+ 404: ResourceNotFoundError,
+ 409: ResourceExistsError,
+ 304: ResourceNotModifiedError,
+ }
+ error_map.update(kwargs.pop("error_map", {}) or {})
+
+ _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
+ _params = kwargs.pop("params", {}) or {}
+
+ content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
+ cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None)
+
+ content_type = content_type or "application/json"
+ _content = None
+ if isinstance(job, (IOBase, bytes)):
+ _content = job
+ else:
+ _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+
+ _request = build_beta_datasets_create_generation_job_request(
+ operation_id=operation_id,
+ content_type=content_type,
+ api_version=self._config.api_version,
+ content=_content,
+ headers=_headers,
+ params=_params,
+ )
+ path_format_arguments = {
+ "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
+ }
+ _request.url = self._client.format_url(_request.url, **path_format_arguments)
+
+ _decompress = kwargs.pop("decompress", True)
+ _stream = True
+ pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access
+ _request, stream=_stream, **kwargs
+ )
+
+ response = pipeline_response.http_response
+
+ if response.status_code not in [201]:
+ try:
+ await response.read() # Load the body in memory and close the socket
+ except (StreamConsumedError, StreamClosedError):
+ pass
+ map_error(status_code=response.status_code, response=response, error_map=error_map)
+ error = _failsafe_deserialize(
+ _models.ApiErrorResponse,
+ response,
+ )
+ raise HttpResponseError(response=response, model=error)
+
+ response_headers = {}
+ response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+
+ return deserialized # type: ignore
+
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: _models.DataGenerationJob,
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.DataGenerationJob:
+ ) -> AsyncLROPoller[_models.DataGenerationJobResult]:
"""Create a data generation job.
Submits a new data generation job for asynchronous execution.
@@ -13356,15 +13494,16 @@ async def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: DataGenerationJob. The DataGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.DataGenerationJob
+ :return: An instance of AsyncLROPoller that returns DataGenerationJobResult. The
+ DataGenerationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.DataGenerationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self, job: JSON, *, operation_id: Optional[str] = None, content_type: str = "application/json", **kwargs: Any
- ) -> _models.DataGenerationJob:
+ ) -> AsyncLROPoller[_models.DataGenerationJobResult]:
"""Create a data generation job.
Submits a new data generation job for asynchronous execution.
@@ -13377,20 +13516,21 @@ async def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: DataGenerationJob. The DataGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.DataGenerationJob
+ :return: An instance of AsyncLROPoller that returns DataGenerationJobResult. The
+ DataGenerationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.DataGenerationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: IO[bytes],
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.DataGenerationJob:
+ ) -> AsyncLROPoller[_models.DataGenerationJobResult]:
"""Create a data generation job.
Submits a new data generation job for asynchronous execution.
@@ -13403,19 +13543,20 @@ async def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
:paramtype content_type: str
- :return: DataGenerationJob. The DataGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.DataGenerationJob
+ :return: An instance of AsyncLROPoller that returns DataGenerationJobResult. The
+ DataGenerationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.DataGenerationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@distributed_trace_async
- async def create_generation_job(
+ async def begin_create_generation_job(
self,
job: Union[_models.DataGenerationJob, JSON, IO[bytes]],
*,
operation_id: Optional[str] = None,
**kwargs: Any
- ) -> _models.DataGenerationJob:
+ ) -> AsyncLROPoller[_models.DataGenerationJobResult]:
"""Create a data generation job.
Submits a new data generation job for asynchronous execution.
@@ -13426,78 +13567,68 @@ async def create_generation_job(
:keyword operation_id: Client-generated unique ID for idempotent retries. When absent, the
server creates the job unconditionally. Default value is None.
:paramtype operation_id: str
- :return: DataGenerationJob. The DataGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.DataGenerationJob
+ :return: An instance of AsyncLROPoller that returns DataGenerationJobResult. The
+ DataGenerationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.DataGenerationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
- error_map: MutableMapping = {
- 401: ClientAuthenticationError,
- 404: ResourceNotFoundError,
- 409: ResourceExistsError,
- 304: ResourceNotModifiedError,
- }
- error_map.update(kwargs.pop("error_map", {}) or {})
-
_headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
_params = kwargs.pop("params", {}) or {}
content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
- cls: ClsType[_models.DataGenerationJob] = kwargs.pop("cls", None)
+ cls: ClsType[_models.DataGenerationJobResult] = kwargs.pop("cls", None)
+ polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True)
+ lro_delay = kwargs.pop("polling_interval", self._config.polling_interval)
+ cont_token: Optional[str] = kwargs.pop("continuation_token", None)
+ if cont_token is None:
+ raw_result = await self._create_generation_job_initial(
+ job=job,
+ operation_id=operation_id,
+ content_type=content_type,
+ cls=lambda x, y, z: x,
+ headers=_headers,
+ params=_params,
+ **kwargs
+ )
+ await raw_result.http_response.read() # type: ignore
+ kwargs.pop("error_map", None)
- content_type = content_type or "application/json"
- _content = None
- if isinstance(job, (IOBase, bytes)):
- _content = job
- else:
- _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+ def get_long_running_output(pipeline_response):
+ response_headers = {}
+ response = pipeline_response.http_response
+ response_headers["Operation-Location"] = self._deserialize(
+ "str", response.headers.get("Operation-Location")
+ )
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = _deserialize(_models.DataGenerationJobResult, response.json().get("result", {}))
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+ return deserialized
- _request = build_beta_datasets_create_generation_job_request(
- operation_id=operation_id,
- content_type=content_type,
- api_version=self._config.api_version,
- content=_content,
- headers=_headers,
- params=_params,
- )
path_format_arguments = {
"endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
}
- _request.url = self._client.format_url(_request.url, **path_format_arguments)
- _decompress = kwargs.pop("decompress", True)
- _stream = kwargs.pop("stream", False)
- pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access
- _request, stream=_stream, **kwargs
- )
-
- response = pipeline_response.http_response
-
- if response.status_code not in [201]:
- if _stream:
- try:
- await response.read() # Load the body in memory and close the socket
- except (StreamConsumedError, StreamClosedError):
- pass
- map_error(status_code=response.status_code, response=response, error_map=error_map)
- error = _failsafe_deserialize(
- _models.ApiErrorResponse,
- response,
+ if polling is True:
+ polling_method: AsyncPollingMethod = cast(
+ AsyncPollingMethod,
+ AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs),
)
- raise HttpResponseError(response=response, model=error)
-
- response_headers = {}
- response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
- response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
-
- if _stream:
- deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+ elif polling is False:
+ polling_method = cast(AsyncPollingMethod, AsyncNoPolling())
else:
- deserialized = _deserialize(_models.DataGenerationJob, response.json())
-
- if cls:
- return cls(pipeline_response, deserialized, response_headers) # type: ignore
-
- return deserialized # type: ignore
+ polling_method = polling
+ if cont_token:
+ return AsyncLROPoller[_models.DataGenerationJobResult].from_continuation_token(
+ polling_method=polling_method,
+ continuation_token=cont_token,
+ client=self._client,
+ deserialization_callback=get_long_running_output,
+ )
+ return AsyncLROPoller[_models.DataGenerationJobResult](
+ self._client, raw_result, get_long_running_output, polling_method # type: ignore
+ )
@distributed_trace_async
async def cancel_generation_job(self, job_id: str, **kwargs: Any) -> _models.DataGenerationJob:
@@ -13638,19 +13769,87 @@ def __init__(self, *args, **kwargs) -> None:
self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")
+ async def _create_optimization_job_initial(
+ self, job: Union[_models.OptimizationJob, JSON, IO[bytes]], *, operation_id: Optional[str] = None, **kwargs: Any
+ ) -> AsyncIterator[bytes]:
+ error_map: MutableMapping = {
+ 401: ClientAuthenticationError,
+ 404: ResourceNotFoundError,
+ 409: ResourceExistsError,
+ 304: ResourceNotModifiedError,
+ }
+ error_map.update(kwargs.pop("error_map", {}) or {})
+
+ _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
+ _params = kwargs.pop("params", {}) or {}
+
+ content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
+ cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None)
+
+ content_type = content_type or "application/json"
+ _content = None
+ if isinstance(job, (IOBase, bytes)):
+ _content = job
+ else:
+ _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+
+ _request = build_beta_agents_create_optimization_job_request(
+ operation_id=operation_id,
+ content_type=content_type,
+ api_version=self._config.api_version,
+ content=_content,
+ headers=_headers,
+ params=_params,
+ )
+ path_format_arguments = {
+ "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
+ }
+ _request.url = self._client.format_url(_request.url, **path_format_arguments)
+
+ _decompress = kwargs.pop("decompress", True)
+ _stream = True
+ pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access
+ _request, stream=_stream, **kwargs
+ )
+
+ response = pipeline_response.http_response
+
+ if response.status_code not in [201]:
+ try:
+ await response.read() # Load the body in memory and close the socket
+ except (StreamConsumedError, StreamClosedError):
+ pass
+ map_error(status_code=response.status_code, response=response, error_map=error_map)
+ error = _failsafe_deserialize(
+ _models.ApiErrorResponse,
+ response,
+ )
+ raise HttpResponseError(response=response, model=error)
+
+ response_headers = {}
+ response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+
+ return deserialized # type: ignore
+
@overload
- async def create_optimization_job(
+ async def begin_create_optimization_job(
self,
job: _models.OptimizationJob,
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.OptimizationJob:
- """Creates an agent optimization job.
+ ) -> AsyncLROPoller[_models.OptimizationJobResult]:
+ """Create an agent optimization job.
- Create an optimization job. Returns 201 with the queued job. Honours ``Operation-Id`` for
- idempotent retry.
+ Creates an optimization job and returns the queued job. Honors ``Operation-Id`` for idempotent
+ retry.
:param job: The job to create. Required.
:type job: ~azure.ai.projects.models.OptimizationJob
@@ -13660,19 +13859,20 @@ async def create_optimization_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: OptimizationJob. The OptimizationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.OptimizationJob
+ :return: An instance of AsyncLROPoller that returns OptimizationJobResult. The
+ OptimizationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.OptimizationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- async def create_optimization_job(
+ async def begin_create_optimization_job(
self, job: JSON, *, operation_id: Optional[str] = None, content_type: str = "application/json", **kwargs: Any
- ) -> _models.OptimizationJob:
- """Creates an agent optimization job.
+ ) -> AsyncLROPoller[_models.OptimizationJobResult]:
+ """Create an agent optimization job.
- Create an optimization job. Returns 201 with the queued job. Honours ``Operation-Id`` for
- idempotent retry.
+ Creates an optimization job and returns the queued job. Honors ``Operation-Id`` for idempotent
+ retry.
:param job: The job to create. Required.
:type job: JSON
@@ -13682,24 +13882,25 @@ async def create_optimization_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: OptimizationJob. The OptimizationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.OptimizationJob
+ :return: An instance of AsyncLROPoller that returns OptimizationJobResult. The
+ OptimizationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.OptimizationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- async def create_optimization_job(
+ async def begin_create_optimization_job(
self,
job: IO[bytes],
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.OptimizationJob:
- """Creates an agent optimization job.
+ ) -> AsyncLROPoller[_models.OptimizationJobResult]:
+ """Create an agent optimization job.
- Create an optimization job. Returns 201 with the queued job. Honours ``Operation-Id`` for
- idempotent retry.
+ Creates an optimization job and returns the queued job. Honors ``Operation-Id`` for idempotent
+ retry.
:param job: The job to create. Required.
:type job: IO[bytes]
@@ -13709,19 +13910,20 @@ async def create_optimization_job(
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
:paramtype content_type: str
- :return: OptimizationJob. The OptimizationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.OptimizationJob
+ :return: An instance of AsyncLROPoller that returns OptimizationJobResult. The
+ OptimizationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.OptimizationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@distributed_trace_async
- async def create_optimization_job(
+ async def begin_create_optimization_job(
self, job: Union[_models.OptimizationJob, JSON, IO[bytes]], *, operation_id: Optional[str] = None, **kwargs: Any
- ) -> _models.OptimizationJob:
- """Creates an agent optimization job.
+ ) -> AsyncLROPoller[_models.OptimizationJobResult]:
+ """Create an agent optimization job.
- Create an optimization job. Returns 201 with the queued job. Honours ``Operation-Id`` for
- idempotent retry.
+ Creates an optimization job and returns the queued job. Honors ``Operation-Id`` for idempotent
+ retry.
:param job: The job to create. Is one of the following types: OptimizationJob, JSON, IO[bytes]
Required.
@@ -13729,84 +13931,74 @@ async def create_optimization_job(
:keyword operation_id: Client-generated unique ID for idempotent retries. When absent, the
server creates the job unconditionally. Default value is None.
:paramtype operation_id: str
- :return: OptimizationJob. The OptimizationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.OptimizationJob
+ :return: An instance of AsyncLROPoller that returns OptimizationJobResult. The
+ OptimizationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.OptimizationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
- error_map: MutableMapping = {
- 401: ClientAuthenticationError,
- 404: ResourceNotFoundError,
- 409: ResourceExistsError,
- 304: ResourceNotModifiedError,
- }
- error_map.update(kwargs.pop("error_map", {}) or {})
-
_headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
_params = kwargs.pop("params", {}) or {}
content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
- cls: ClsType[_models.OptimizationJob] = kwargs.pop("cls", None)
+ cls: ClsType[_models.OptimizationJobResult] = kwargs.pop("cls", None)
+ polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True)
+ lro_delay = kwargs.pop("polling_interval", self._config.polling_interval)
+ cont_token: Optional[str] = kwargs.pop("continuation_token", None)
+ if cont_token is None:
+ raw_result = await self._create_optimization_job_initial(
+ job=job,
+ operation_id=operation_id,
+ content_type=content_type,
+ cls=lambda x, y, z: x,
+ headers=_headers,
+ params=_params,
+ **kwargs
+ )
+ await raw_result.http_response.read() # type: ignore
+ kwargs.pop("error_map", None)
- content_type = content_type or "application/json"
- _content = None
- if isinstance(job, (IOBase, bytes)):
- _content = job
- else:
- _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+ def get_long_running_output(pipeline_response):
+ response_headers = {}
+ response = pipeline_response.http_response
+ response_headers["Operation-Location"] = self._deserialize(
+ "str", response.headers.get("Operation-Location")
+ )
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = _deserialize(_models.OptimizationJobResult, response.json().get("result", {}))
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+ return deserialized
- _request = build_beta_agents_create_optimization_job_request(
- operation_id=operation_id,
- content_type=content_type,
- api_version=self._config.api_version,
- content=_content,
- headers=_headers,
- params=_params,
- )
path_format_arguments = {
"endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
}
- _request.url = self._client.format_url(_request.url, **path_format_arguments)
-
- _decompress = kwargs.pop("decompress", True)
- _stream = kwargs.pop("stream", False)
- pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access
- _request, stream=_stream, **kwargs
- )
-
- response = pipeline_response.http_response
- if response.status_code not in [201]:
- if _stream:
- try:
- await response.read() # Load the body in memory and close the socket
- except (StreamConsumedError, StreamClosedError):
- pass
- map_error(status_code=response.status_code, response=response, error_map=error_map)
- error = _failsafe_deserialize(
- _models.ApiErrorResponse,
- response,
+ if polling is True:
+ polling_method: AsyncPollingMethod = cast(
+ AsyncPollingMethod,
+ AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs),
)
- raise HttpResponseError(response=response, model=error)
-
- response_headers = {}
- response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
- response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
-
- if _stream:
- deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+ elif polling is False:
+ polling_method = cast(AsyncPollingMethod, AsyncNoPolling())
else:
- deserialized = _deserialize(_models.OptimizationJob, response.json())
-
- if cls:
- return cls(pipeline_response, deserialized, response_headers) # type: ignore
-
- return deserialized # type: ignore
+ polling_method = polling
+ if cont_token:
+ return AsyncLROPoller[_models.OptimizationJobResult].from_continuation_token(
+ polling_method=polling_method,
+ continuation_token=cont_token,
+ client=self._client,
+ deserialization_callback=get_long_running_output,
+ )
+ return AsyncLROPoller[_models.OptimizationJobResult](
+ self._client, raw_result, get_long_running_output, polling_method # type: ignore
+ )
@distributed_trace_async
async def get_optimization_job(self, job_id: str, **kwargs: Any) -> _models.OptimizationJob:
- """Get info about an agent optimization job.
+ """Get an agent optimization job.
- Get an optimization job by id.
+ Retrieves an optimization job by its identifier.
:param job_id: The ID of the job. Required.
:type job_id: str
@@ -13883,9 +14075,9 @@ def list_optimization_jobs(
agent_name: Optional[str] = None,
**kwargs: Any
) -> AsyncItemPaged["_models.OptimizationJobListItem"]:
- """Returns a list of agent optimization jobs.
+ """List agent optimization jobs.
- List optimization jobs. Supports cursor pagination and optional status / agent_name filters.
+ Lists optimization jobs with cursor pagination and optional status or agent name filters.
:keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and
100, and the
@@ -13976,10 +14168,10 @@ async def get_next(_continuation_token=None):
@distributed_trace_async
async def cancel_optimization_job(self, job_id: str, **kwargs: Any) -> _models.OptimizationJob:
- """Cancels an agent optimization job.
+ """Cancel an agent optimization job.
- Request cancellation of a running or queued job. Returns an error if the job is already in a
- terminal state.
+ Requests cancellation of a running or queued job and returns an error if the job is already in
+ a terminal state.
:param job_id: The ID of the job to cancel. Required.
:type job_id: str
@@ -14044,9 +14236,9 @@ async def cancel_optimization_job(self, job_id: str, **kwargs: Any) -> _models.O
@distributed_trace_async
async def delete_optimization_job(self, job_id: str, **kwargs: Any) -> None:
- """Deletes an agent optimization job.
+ """Delete an agent optimization job.
- Delete the job and its candidate artifacts. Cancels first if non-terminal.
+ Deletes the job and its candidate artifacts, canceling the job first if it is non-terminal.
:param job_id: The ID of the job to delete. Required.
:type job_id: str
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py
index 2ee8abc3cf86..e490237abf83 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py
@@ -292,6 +292,7 @@
RoutineRun,
RoutineTrigger,
RubricBasedEvaluatorDefinition,
+ RubricGenerationInputQualityWarning,
SASCredentials,
Schedule,
ScheduleRoutineTrigger,
@@ -311,6 +312,7 @@
SpecificFunctionShellParam,
StructuredInputDefinition,
StructuredOutputDefinition,
+ TaskGenerationDataGenerationJobOptions,
TaxonomyCategory,
TaxonomySubCategory,
TelemetryConfig,
@@ -339,6 +341,7 @@
ToolDescription,
ToolProjectConnection,
ToolSearchToolParam,
+ ToolSearchToolboxTool,
ToolUseFineTuningDataGenerationJobOptions,
ToolboxObject,
ToolboxPolicies,
@@ -374,6 +377,7 @@
AgentBlueprintReferenceType,
AgentEndpointAuthorizationSchemeType,
AgentEndpointProtocol,
+ AgentIdentityStatus,
AgentKind,
AgentObjectType,
AgentSessionStatus,
@@ -412,6 +416,7 @@
FoundryModelWarningCode,
FoundryModelWeightType,
FunctionShellToolParamEnvironmentType,
+ GenerationWarningType,
GitHubIssueEvent,
GrammarSyntax1,
ImageGenAction,
@@ -437,6 +442,9 @@
RoutineDispatchPayloadType,
RoutineRunPhase,
RoutineTriggerType,
+ RubricGenerationInputQualityWarningCode,
+ RubricGenerationInputQualityWarningSeverity,
+ RubricGenerationInputQualityWarningSource,
SampleType,
ScheduleProvisioningStatus,
ScheduleTaskType,
@@ -741,6 +749,7 @@
"RoutineRun",
"RoutineTrigger",
"RubricBasedEvaluatorDefinition",
+ "RubricGenerationInputQualityWarning",
"SASCredentials",
"Schedule",
"ScheduleRoutineTrigger",
@@ -760,6 +769,7 @@
"SpecificFunctionShellParam",
"StructuredInputDefinition",
"StructuredOutputDefinition",
+ "TaskGenerationDataGenerationJobOptions",
"TaxonomyCategory",
"TaxonomySubCategory",
"TelemetryConfig",
@@ -788,6 +798,7 @@
"ToolDescription",
"ToolProjectConnection",
"ToolSearchToolParam",
+ "ToolSearchToolboxTool",
"ToolUseFineTuningDataGenerationJobOptions",
"ToolboxObject",
"ToolboxPolicies",
@@ -820,6 +831,7 @@
"AgentBlueprintReferenceType",
"AgentEndpointAuthorizationSchemeType",
"AgentEndpointProtocol",
+ "AgentIdentityStatus",
"AgentKind",
"AgentObjectType",
"AgentSessionStatus",
@@ -858,6 +870,7 @@
"FoundryModelWarningCode",
"FoundryModelWeightType",
"FunctionShellToolParamEnvironmentType",
+ "GenerationWarningType",
"GitHubIssueEvent",
"GrammarSyntax1",
"ImageGenAction",
@@ -883,6 +896,9 @@
"RoutineDispatchPayloadType",
"RoutineRunPhase",
"RoutineTriggerType",
+ "RubricGenerationInputQualityWarningCode",
+ "RubricGenerationInputQualityWarningSeverity",
+ "RubricGenerationInputQualityWarningSource",
"SampleType",
"ScheduleProvisioningStatus",
"ScheduleTaskType",
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py
index 008f889fc25e..b7f159dd935a 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py
@@ -84,6 +84,17 @@ class AgentEndpointProtocol(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""WebSocket-based protocol for hosted voice and real-time streaming agents."""
+class AgentIdentityStatus(str, Enum, metaclass=CaseInsensitiveEnumMeta):
+ """The status of an agent identity, applicable to both the agent instance identity and the agent
+ blueprint.
+ """
+
+ ACTIVE = "active"
+ """The agent identity is active and can be used to access resources."""
+ DISABLED = "disabled"
+ """The agent identity is disabled and cannot be used to access resources."""
+
+
class AgentKind(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""Type of AgentKind."""
@@ -392,6 +403,8 @@ class DataGenerationJobType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""Single turn query and response from agent traces."""
TOOL_USE = "tool_use"
"""Tool calling conversation between user and agent."""
+ TASK_GENERATION = "task_generation"
+ """Task generation for evaluation scenarios."""
class DatasetType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
@@ -609,6 +622,16 @@ class FunctionShellToolParamEnvironmentType(str, Enum, metaclass=CaseInsensitive
"""CONTAINER_REFERENCE."""
+class GenerationWarningType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
+ """Category of a warning surfaced on a generated evaluator version. Extensible so new warning
+ categories (e.g., safety, output quality) can be introduced without a breaking change.
+ """
+
+ INPUT_QUALITY = "input_quality"
+ """The paired EvaluatorGenerationJob emitted one or more input-quality advisories. Follow
+ ``generation_job_id`` to fetch the detailed warning payloads."""
+
+
class GitHubIssueEvent(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""Known GitHub issue events that can fire a routine."""
@@ -912,6 +935,61 @@ class RoutineTriggerType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""A one-shot timer trigger."""
+class RubricGenerationInputQualityWarningCode(str, Enum, metaclass=CaseInsensitiveEnumMeta):
+ """Stable searchable machine-readable warning code for a rubric-generation input-quality warning.
+ Values are ``snake_case``; clients must tolerate additional service-defined identifiers.
+ """
+
+ EMPTY_PROMPT = "empty_prompt"
+ """A prompt source was empty or whitespace-only."""
+ SHORT_PROMPT = "short_prompt"
+ """A prompt source was non-empty but below the recommended minimum signal threshold."""
+ EMPTY_AGENT_INSTRUCTIONS = "empty_agent_instructions"
+ """An agent source resolved successfully but had no usable instructions."""
+ SHORT_AGENT_INSTRUCTIONS = "short_agent_instructions"
+ """An agent source had instructions below the recommended minimum signal threshold."""
+ EMPTY_DATASET_CONTENT = "empty_dataset_content"
+ """A dataset source resolved but contained no usable content for rubric generation."""
+ SHORT_DATASET_CONTENT = "short_dataset_content"
+ """Dataset content was below the recommended minimum signal threshold."""
+ LOW_TRACE_COUNT = "low_trace_count"
+ """A row-structured dataset had very few rows, so the generated rubric may not generalize."""
+ INSUFFICIENT_TOTAL_INPUT = "insufficient_total_input"
+ """Combined resolved input across successfully resolved sources was below the recommended minimum
+ signal threshold."""
+
+
+class RubricGenerationInputQualityWarningSeverity( # pylint: disable=name-too-long
+ str, Enum, metaclass=CaseInsensitiveEnumMeta
+):
+ """Advisory severity for a rubric-generation input-quality warning. Initial value set:
+ ``warning``.
+ """
+
+ WARNING = "warning"
+ """Non-fatal advisory; generation succeeded but output quality may be lower."""
+
+
+class RubricGenerationInputQualityWarningSource( # pylint: disable=name-too-long
+ str, Enum, metaclass=CaseInsensitiveEnumMeta
+):
+ """Warning source attribution for a rubric-generation input-quality warning. Per-source values
+ (``prompt``, ``agent``, ``dataset``) match the source category visible to the generation
+ runtime. ``aggregate`` is a synthetic value used only for warnings computed across successfully
+ resolved sources. ``traces`` is not exposed because trace sources resolve into dataset content
+ upstream.
+ """
+
+ PROMPT = "prompt"
+ """The warning applies to an inline prompt source."""
+ AGENT = "agent"
+ """The warning applies to an agent source."""
+ DATASET = "dataset"
+ """The warning applies to a dataset source (including trace-derived datasets)."""
+ AGGREGATE = "aggregate"
+ """The warning is computed across all successfully resolved sources."""
+
+
class SampleType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""The type of sample used in the analysis."""
@@ -1051,6 +1129,8 @@ class ToolboxToolType(str, Enum, metaclass=CaseInsensitiveEnumMeta):
"""WORK_IQ_PREVIEW."""
FABRIC_IQ_PREVIEW = "fabric_iq_preview"
"""FABRIC_IQ_PREVIEW."""
+ TOOLBOX_SEARCH = "toolbox_search"
+ """TOOLBOX_SEARCH."""
TOOLBOX_SEARCH_PREVIEW = "toolbox_search_preview"
"""TOOLBOX_SEARCH_PREVIEW."""
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py
index 529b03a4859a..15d2e20c44f2 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py
@@ -161,17 +161,16 @@ class Tool(_Model):
CaptureStructuredOutputsTool, CodeInterpreterTool, ComputerTool, ComputerUsePreviewTool,
CustomToolParam, MicrosoftFabricPreviewTool, FabricIQPreviewTool, FileSearchTool, FunctionTool,
ImageGenTool, LocalShellToolParam, MCPTool, MemorySearchPreviewTool, NamespaceToolParam,
- OpenApiTool, SharepointPreviewTool, FunctionShellToolParam,
- ToolSearchToolParam, WebSearchTool, WebSearchPreviewTool, WorkIQPreviewTool
+ OpenApiTool, SharepointPreviewTool, FunctionShellToolParam, ToolSearchToolParam, WebSearchTool,
+ WebSearchPreviewTool, WorkIQPreviewTool
:ivar type: Required. Known values are: "function", "file_search", "computer",
"computer_use_preview", "web_search", "mcp", "code_interpreter", "image_generation",
"local_shell", "shell", "custom", "namespace", "tool_search", "web_search_preview",
"apply_patch", "a2a_preview", "bing_custom_search_preview", "browser_automation_preview",
"fabric_dataagent_preview", "sharepoint_grounding_preview", "memory_search_preview",
- "work_iq_preview", "fabric_iq_preview", "toolbox_search_preview",
- "azure_ai_search", "azure_function", "bing_grounding", "capture_structured_outputs", and
- "openapi".
+ "work_iq_preview", "fabric_iq_preview", "toolbox_search_preview", "azure_ai_search",
+ "azure_function", "bing_grounding", "capture_structured_outputs", and "openapi".
:vartype type: str or ~azure.ai.projects.models.ToolType
"""
@@ -268,12 +267,13 @@ class ToolboxTool(_Model):
You probably want to use the sub-classes and not this class directly. Known sub-classes are:
A2APreviewToolboxTool, AzureAISearchToolboxTool, BrowserAutomationPreviewToolboxTool,
CodeInterpreterToolboxTool, FabricIQPreviewToolboxTool, FileSearchToolboxTool, MCPToolboxTool,
- OpenApiToolboxTool, ReminderPreviewToolboxTool, ToolboxSearchPreviewToolboxTool,
- WebSearchToolboxTool, WorkIQPreviewToolboxTool
+ OpenApiToolboxTool, ReminderPreviewToolboxTool, ToolSearchToolboxTool,
+ ToolboxSearchPreviewToolboxTool, WebSearchToolboxTool, WorkIQPreviewToolboxTool
:ivar type: The type of tool. Required. Known values are: "code_interpreter", "file_search",
"web_search", "mcp", "azure_ai_search", "openapi", "a2a_preview", "browser_automation_preview",
- "reminder_preview", "work_iq_preview", "fabric_iq_preview", and "toolbox_search_preview".
+ "reminder_preview", "work_iq_preview", "fabric_iq_preview", "toolbox_search", and
+ "toolbox_search_preview".
:vartype type: str or ~azure.ai.projects.models.ToolboxToolType
:ivar name: Optional user-defined name for this tool or configuration.
:vartype name: str
@@ -290,7 +290,7 @@ class ToolboxTool(_Model):
"""The type of tool. Required. Known values are: \"code_interpreter\", \"file_search\",
\"web_search\", \"mcp\", \"azure_ai_search\", \"openapi\", \"a2a_preview\",
\"browser_automation_preview\", \"reminder_preview\", \"work_iq_preview\",
- \"fabric_iq_preview\", and \"toolbox_search_preview\"."""
+ \"fabric_iq_preview\", \"toolbox_search\", and \"toolbox_search_preview\"."""
name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"])
"""Optional user-defined name for this tool or configuration."""
description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"])
@@ -1120,12 +1120,20 @@ class AgentIdentity(_Model):
:ivar client_id: The client ID of the agent instance. Also referred to as the instance ID.
Required.
:vartype client_id: str
+ :ivar status: The status of the agent identity. Present for both the agent instance identity
+ and the agent blueprint. Known values are: "active" and "disabled".
+ :vartype status: str or ~azure.ai.projects.models.AgentIdentityStatus
"""
principal_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"])
"""The principal ID of the agent instance. Required."""
client_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"])
"""The client ID of the agent instance. Also referred to as the instance ID. Required."""
+ status: Optional[Union[str, "_models.AgentIdentityStatus"]] = rest_field(
+ visibility=["read", "create", "update", "delete", "query"]
+ )
+ """The status of the agent identity. Present for both the agent instance identity and the agent
+ blueprint. Known values are: \"active\" and \"disabled\"."""
@overload
def __init__(
@@ -1133,6 +1141,7 @@ def __init__(
*,
principal_id: str,
client_id: str,
+ status: Optional[Union[str, "_models.AgentIdentityStatus"]] = None,
) -> None: ...
@overload
@@ -4813,11 +4822,11 @@ class DataGenerationJobOptions(_Model):
"""Options for managing data generation jobs.
You probably want to use the sub-classes and not this class directly. Known sub-classes are:
- SimpleQnADataGenerationJobOptions, ToolUseFineTuningDataGenerationJobOptions,
- TracesDataGenerationJobOptions
+ SimpleQnADataGenerationJobOptions, TaskGenerationDataGenerationJobOptions,
+ ToolUseFineTuningDataGenerationJobOptions, TracesDataGenerationJobOptions
:ivar type: The data generation job type. Required. Known values are: "simple_qna", "traces",
- and "tool_use".
+ "tool_use", and "task_generation".
:vartype type: str or ~azure.ai.projects.models.DataGenerationJobType
:ivar max_samples: Maximum number of samples to generate. Required.
:vartype max_samples: int
@@ -4830,8 +4839,8 @@ class DataGenerationJobOptions(_Model):
__mapping__: dict[str, _Model] = {}
type: str = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"])
- """The data generation job type. Required. Known values are: \"simple_qna\", \"traces\", and
- \"tool_use\"."""
+ """The data generation job type. Required. Known values are: \"simple_qna\", \"traces\",
+ \"tool_use\", and \"task_generation\"."""
max_samples: int = rest_field(visibility=["read", "create", "update", "delete", "query"])
"""Maximum number of samples to generate. Required."""
train_split: Optional[float] = rest_field(visibility=["read", "create", "update", "delete", "query"])
@@ -6671,6 +6680,12 @@ class EvaluatorGenerationJob(_Model):
:vartype finished_at: ~datetime.datetime
:ivar usage: Token consumption summary. Populated when the job reaches a terminal state.
:vartype usage: ~azure.ai.projects.models.EvaluatorGenerationTokenUsage
+ :ivar input_quality_warnings: Non-fatal input-quality advisories produced by the generation
+ pipeline. Read-only; service-generated; populated only on terminal jobs when advisories fired.
+ Omitted when generation was clean. Cleared when a subsequent ``PATCH`` to the paired
+ ``EvaluatorVersion.definition`` invalidates the advisories.
+ :vartype input_quality_warnings:
+ list[~azure.ai.projects.models.RubricGenerationInputQualityWarning]
"""
id: str = rest_field(visibility=["read"])
@@ -6693,6 +6708,13 @@ class EvaluatorGenerationJob(_Model):
"""The timestamp when the job finished, represented in Unix time (seconds since January 1, 1970)."""
usage: Optional["_models.EvaluatorGenerationTokenUsage"] = rest_field(visibility=["read"])
"""Token consumption summary. Populated when the job reaches a terminal state."""
+ input_quality_warnings: Optional[list["_models.RubricGenerationInputQualityWarning"]] = rest_field(
+ visibility=["read"]
+ )
+ """Non-fatal input-quality advisories produced by the generation pipeline. Read-only;
+ service-generated; populated only on terminal jobs when advisories fired. Omitted when
+ generation was clean. Cleared when a subsequent ``PATCH`` to the paired
+ ``EvaluatorVersion.definition`` invalidates the advisories."""
@overload
def __init__(
@@ -6835,6 +6857,16 @@ class EvaluatorVersion(_Model):
present only on evaluator versions created via an EvaluatorGenerationJob. Each artifact
resolves to a versioned Foundry Dataset.
:vartype generation_artifacts: ~azure.ai.projects.models.EvaluatorGenerationArtifacts
+ :ivar generation_job_id: Read-only provenance link back to the EvaluatorGenerationJob that
+ produced this version. Present only on evaluator versions created via the generation pipeline;
+ absent for manually-created versions and unaffected by subsequent ``PATCH`` calls.
+ :vartype generation_job_id: str
+ :ivar warnings: Categories of warnings surfaced on this generated evaluator version. Present
+ only on versions created via an EvaluatorGenerationJob when the paired job produced non-empty
+ warnings. Absent (treat as no warnings) when the version is not from generation, when the
+ paired job was clean, or when a subsequent ``PATCH`` to ``definition`` cleared the paired job's
+ advisories. Follow ``generation_job_id`` to fetch the detailed warning payloads.
+ :vartype warnings: list[str or ~azure.ai.projects.models.GenerationWarningType]
:ivar created_by: Creator of the evaluator. Required.
:vartype created_by: str
:ivar created_at: Creation date/time of the evaluator. Required.
@@ -6877,6 +6909,16 @@ class EvaluatorVersion(_Model):
"""Provenance artifacts from the generation pipeline. Read-only; present only on evaluator
versions created via an EvaluatorGenerationJob. Each artifact resolves to a versioned Foundry
Dataset."""
+ generation_job_id: Optional[str] = rest_field(visibility=["read"])
+ """Read-only provenance link back to the EvaluatorGenerationJob that produced this version.
+ Present only on evaluator versions created via the generation pipeline; absent for
+ manually-created versions and unaffected by subsequent ``PATCH`` calls."""
+ warnings: Optional[list[Union[str, "_models.GenerationWarningType"]]] = rest_field(visibility=["read"])
+ """Categories of warnings surfaced on this generated evaluator version. Present only on versions
+ created via an EvaluatorGenerationJob when the paired job produced non-empty warnings. Absent
+ (treat as no warnings) when the version is not from generation, when the paired job was clean,
+ or when a subsequent ``PATCH`` to ``definition`` cleared the paired job's advisories. Follow
+ ``generation_job_id`` to fetch the detailed warning payloads."""
created_by: str = rest_field(visibility=["read"])
"""Creator of the evaluator. Required."""
created_at: datetime.datetime = rest_field(visibility=["read"], format="rfc3339")
@@ -11627,6 +11669,14 @@ class OptimizationOptions(_Model):
'conversation' for per-conversation multi-turn simulation scoring. Known values are: "turn" and
"conversation".
:vartype evaluation_level: str or ~azure.ai.projects.models.EvaluationLevel
+ :ivar max_stalls: Maximum number of consecutive reflective minibatch rejections before stopping
+ early. A 'stall' occurs when the optimizer proposes a prompt change, evaluates it on a small
+ subset, and the score does not improve — so no full validation-set evaluation is triggered. The
+ counter resets whenever a minibatch passes and its full-validation score beats the current
+ best. Only a sustained plateau of ``max_stalls`` consecutive minibatch failures triggers the
+ stop. The service defaults to 5 if a value is not specified by the caller. Must be >= 1 when
+ set.
+ :vartype max_stalls: int
"""
max_candidates: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"])
@@ -11647,6 +11697,13 @@ class OptimizationOptions(_Model):
"""Evaluation granularity. Null/omitted means per-item single-turn. Set to 'conversation' for
per-conversation multi-turn simulation scoring. Known values are: \"turn\" and
\"conversation\"."""
+ max_stalls: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"])
+ """Maximum number of consecutive reflective minibatch rejections before stopping early. A 'stall'
+ occurs when the optimizer proposes a prompt change, evaluates it on a small subset, and the
+ score does not improve — so no full validation-set evaluation is triggered. The counter resets
+ whenever a minibatch passes and its full-validation score beats the current best. Only a
+ sustained plateau of ``max_stalls`` consecutive minibatch failures triggers the stop. The
+ service defaults to 5 if a value is not specified by the caller. Must be >= 1 when set."""
@overload
def __init__(
@@ -11657,6 +11714,7 @@ def __init__(
eval_model: Optional[str] = None,
optimization_model: Optional[str] = None,
evaluation_level: Optional[Union[str, "_models.EvaluationLevel"]] = None,
+ max_stalls: Optional[int] = None,
) -> None: ...
@overload
@@ -13022,6 +13080,76 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
self.type = EvaluatorDefinitionType.RUBRIC # type: ignore
+class RubricGenerationInputQualityWarning(_Model):
+ """A non-fatal advisory produced during rubric evaluator generation when resolved inputs are
+ technically valid but likely too weak to produce a high-quality rubric. Read-only;
+ service-generated. Persisted with the terminal EvaluatorGenerationJob.
+
+ :ivar code: Stable searchable machine-readable warning code. Required. Known values are:
+ "empty_prompt", "short_prompt", "empty_agent_instructions", "short_agent_instructions",
+ "empty_dataset_content", "short_dataset_content", "low_trace_count", and
+ "insufficient_total_input".
+ :vartype code: str or ~azure.ai.projects.models.RubricGenerationInputQualityWarningCode
+ :ivar severity: Advisory severity. Initial values: ``warning``. Required. "warning"
+ :vartype severity: str or ~azure.ai.projects.models.RubricGenerationInputQualityWarningSeverity
+ :ivar message: Human-readable message suitable for direct SDK/CLI/UI display. Must not include
+ raw prompt, instruction, dataset, or trace text. Required.
+ :vartype message: str
+ :ivar source: Which source category the warning applies to. ``aggregate`` is used only for
+ cross-source warnings. Required. Known values are: "prompt", "agent", "dataset", and
+ "aggregate".
+ :vartype source: str or ~azure.ai.projects.models.RubricGenerationInputQualityWarningSource
+ :ivar source_index: Zero-based index into ``EvaluatorGenerationJob.inputs.sources`` when the
+ warning applies to a specific source. Omitted for aggregate warnings and for warnings not tied
+ to one source.
+ :vartype source_index: int
+ """
+
+ code: Union[str, "_models.RubricGenerationInputQualityWarningCode"] = rest_field(
+ visibility=["read", "create", "update", "delete", "query"]
+ )
+ """Stable searchable machine-readable warning code. Required. Known values are: \"empty_prompt\",
+ \"short_prompt\", \"empty_agent_instructions\", \"short_agent_instructions\",
+ \"empty_dataset_content\", \"short_dataset_content\", \"low_trace_count\", and
+ \"insufficient_total_input\"."""
+ severity: Union[str, "_models.RubricGenerationInputQualityWarningSeverity"] = rest_field(
+ visibility=["read", "create", "update", "delete", "query"]
+ )
+ """Advisory severity. Initial values: ``warning``. Required. \"warning\""""
+ message: str = rest_field(visibility=["read", "create", "update", "delete", "query"])
+ """Human-readable message suitable for direct SDK/CLI/UI display. Must not include raw prompt,
+ instruction, dataset, or trace text. Required."""
+ source: Union[str, "_models.RubricGenerationInputQualityWarningSource"] = rest_field(
+ visibility=["read", "create", "update", "delete", "query"]
+ )
+ """Which source category the warning applies to. ``aggregate`` is used only for cross-source
+ warnings. Required. Known values are: \"prompt\", \"agent\", \"dataset\", and \"aggregate\"."""
+ source_index: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"])
+ """Zero-based index into ``EvaluatorGenerationJob.inputs.sources`` when the warning applies to a
+ specific source. Omitted for aggregate warnings and for warnings not tied to one source."""
+
+ @overload
+ def __init__(
+ self,
+ *,
+ code: Union[str, "_models.RubricGenerationInputQualityWarningCode"],
+ severity: Union[str, "_models.RubricGenerationInputQualityWarningSeverity"],
+ message: str,
+ source: Union[str, "_models.RubricGenerationInputQualityWarningSource"],
+ source_index: Optional[int] = None,
+ ) -> None: ...
+
+ @overload
+ def __init__(self, mapping: Mapping[str, Any]) -> None:
+ """
+ :param mapping: raw JSON to initialize the model.
+ :type mapping: Mapping[str, Any]
+ """
+
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
+
+
class SASCredentials(BaseCredentials, discriminator="SAS"):
"""Shared Access Signature (SAS) credential definition.
@@ -13869,6 +13997,48 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
+class TaskGenerationDataGenerationJobOptions(DataGenerationJobOptions, discriminator="task_generation"):
+ """The options for a task generation data generation job. Use with multiturn evaluation scenarios
+ and with prompt, file, or agent sources. Generated dataset rows include fields such as ``id``,
+ ``category``, ``test_case_description``, and ``desired_num_turns``.
+
+ :ivar max_samples: Maximum number of samples to generate. Required.
+ :vartype max_samples: int
+ :ivar train_split: The proportion of the generated data to be used for training when the data
+ is used for fine-tuning. The rest will be used for validation. Value should be between 0 and 1.
+ :vartype train_split: float
+ :ivar model_options: The LLM model options.
+ :vartype model_options: ~azure.ai.projects.models.DataGenerationModelOptions
+ :ivar type: The data generation job type, which is TaskGeneration for this model. Required.
+ Task generation for evaluation scenarios.
+ :vartype type: str or ~azure.ai.projects.models.TASK_GENERATION
+ """
+
+ type: Literal[DataGenerationJobType.TASK_GENERATION] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore
+ """The data generation job type, which is TaskGeneration for this model. Required. Task generation
+ for evaluation scenarios."""
+
+ @overload
+ def __init__(
+ self,
+ *,
+ max_samples: int,
+ train_split: Optional[float] = None,
+ model_options: Optional["_models.DataGenerationModelOptions"] = None,
+ ) -> None: ...
+
+ @overload
+ def __init__(self, mapping: Mapping[str, Any]) -> None:
+ """
+ :param mapping: raw JSON to initialize the model.
+ :type mapping: Mapping[str, Any]
+ """
+
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
+ self.type = DataGenerationJobType.TASK_GENERATION # type: ignore
+
+
class TaxonomyCategory(_Model):
"""Taxonomy category definition.
@@ -14946,6 +15116,45 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
+class ToolSearchToolboxTool(ToolboxTool, discriminator="toolbox_search"):
+ """A toolbox search tool stored in a toolbox.
+
+ :ivar name: Optional user-defined name for this tool or configuration.
+ :vartype name: str
+ :ivar description: Optional user-defined description for this tool or configuration.
+ :vartype description: str
+ :ivar tool_configs: Per-tool configuration map. Keys are tool names or ``*`` (catch-all
+ default). Resolution order: exact tool name match takes priority over ``*``. Unknown tool names
+ are silently ignored at runtime.
+ :vartype tool_configs: dict[str, ~azure.ai.projects.models.ToolConfig]
+ :ivar type: The type of the tool. Always ``toolbox_search``. Required. TOOLBOX_SEARCH.
+ :vartype type: str or ~azure.ai.projects.models.TOOLBOX_SEARCH
+ """
+
+ type: Literal[ToolboxToolType.TOOLBOX_SEARCH] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore
+ """The type of the tool. Always ``toolbox_search``. Required. TOOLBOX_SEARCH."""
+
+ @overload
+ def __init__(
+ self,
+ *,
+ name: Optional[str] = None,
+ description: Optional[str] = None,
+ tool_configs: Optional[dict[str, "_models.ToolConfig"]] = None,
+ ) -> None: ...
+
+ @overload
+ def __init__(self, mapping: Mapping[str, Any]) -> None:
+ """
+ :param mapping: raw JSON to initialize the model.
+ :type mapping: Mapping[str, Any]
+ """
+
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
+ self.type = ToolboxToolType.TOOLBOX_SEARCH # type: ignore
+
+
class ToolSearchToolParam(Tool, discriminator="tool_search"):
"""Tool search tool.
diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py
index aca7d018ae15..7013e5925454 100644
--- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py
+++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py
@@ -4784,7 +4784,8 @@ def _create_version_from_code(
* Can contain hyphens in the middle
* Must not exceed 63 characters. Required.
:type agent_name: str
- :param content: Is either a _CreateAgentVersionFromCodeContent type or a JSON type. Required.
+ :param content: The content multipart request content. Is either a
+ _CreateAgentVersionFromCodeContent type or a JSON type. Required.
:type content: ~azure.ai.projects.models._models._CreateAgentVersionFromCodeContent or JSON
:keyword code_zip_sha256: SHA-256 hex digest of the uploaded code zip. Used for change
detection (dedup) and integrity verification. Required.
@@ -7240,7 +7241,7 @@ def pending_upload(
def get_credentials(self, name: str, version: str, **kwargs: Any) -> _models.DatasetCredential:
"""Get dataset credentials.
- Gets the SAS credential to access the storage account associated with a Dataset version.
+ Retrieves the SAS credential to access the storage account associated with a dataset version.
:param name: The name of the resource. Required.
:type name: str
@@ -7324,7 +7325,7 @@ def __init__(self, *args, **kwargs) -> None:
def get(self, name: str, **kwargs: Any) -> _models.Deployment:
"""Get a deployment.
- Gets a deployed model.
+ Retrieves a deployed model.
:param name: Name of the deployment. Required.
:type name: str
@@ -9156,7 +9157,7 @@ def update(
) -> _models.EvaluationTaxonomy:
"""Update an evaluation taxonomy.
- Update an evaluation taxonomy.
+ Modifies the specified evaluation taxonomy with the provided changes.
:param name: The name of the evaluation taxonomy. Required.
:type name: str
@@ -9176,7 +9177,7 @@ def update(
) -> _models.EvaluationTaxonomy:
"""Update an evaluation taxonomy.
- Update an evaluation taxonomy.
+ Modifies the specified evaluation taxonomy with the provided changes.
:param name: The name of the evaluation taxonomy. Required.
:type name: str
@@ -9196,7 +9197,7 @@ def update(
) -> _models.EvaluationTaxonomy:
"""Update an evaluation taxonomy.
- Update an evaluation taxonomy.
+ Modifies the specified evaluation taxonomy with the provided changes.
:param name: The name of the evaluation taxonomy. Required.
:type name: str
@@ -9216,7 +9217,7 @@ def update(
) -> _models.EvaluationTaxonomy:
"""Update an evaluation taxonomy.
- Update an evaluation taxonomy.
+ Modifies the specified evaluation taxonomy with the provided changes.
:param name: The name of the evaluation taxonomy. Required.
:type name: str
@@ -9962,7 +9963,7 @@ def pending_upload(
Initiates a new pending upload or retrieves an existing one for the specified evaluator
version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -9991,7 +9992,7 @@ def pending_upload(
Initiates a new pending upload or retrieves an existing one for the specified evaluator
version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -10020,7 +10021,7 @@ def pending_upload(
Initiates a new pending upload or retrieves an existing one for the specified evaluator
version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -10047,7 +10048,7 @@ def pending_upload(
Initiates a new pending upload or retrieves an existing one for the specified evaluator
version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -10140,7 +10141,7 @@ def get_credentials(
Retrieves SAS credentials for accessing the storage account associated with the specified
evaluator version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -10169,7 +10170,7 @@ def get_credentials(
Retrieves SAS credentials for accessing the storage account associated with the specified
evaluator version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -10198,7 +10199,7 @@ def get_credentials(
Retrieves SAS credentials for accessing the storage account associated with the specified
evaluator version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -10225,7 +10226,7 @@ def get_credentials(
Retrieves SAS credentials for accessing the storage account associated with the specified
evaluator version.
- :param name: Required.
+ :param name: The name path parameter. Required.
:type name: str
:param version: The specific version id of the EvaluatorVersion to operate on. Required.
:type version: str
@@ -10303,15 +10304,87 @@ def get_credentials(
return deserialized # type: ignore
+ def _create_generation_job_initial(
+ self,
+ job: Union[_models.EvaluatorGenerationJob, JSON, IO[bytes]],
+ *,
+ operation_id: Optional[str] = None,
+ **kwargs: Any
+ ) -> Iterator[bytes]:
+ error_map: MutableMapping = {
+ 401: ClientAuthenticationError,
+ 404: ResourceNotFoundError,
+ 409: ResourceExistsError,
+ 304: ResourceNotModifiedError,
+ }
+ error_map.update(kwargs.pop("error_map", {}) or {})
+
+ _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
+ _params = kwargs.pop("params", {}) or {}
+
+ content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
+ cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None)
+
+ content_type = content_type or "application/json"
+ _content = None
+ if isinstance(job, (IOBase, bytes)):
+ _content = job
+ else:
+ _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+
+ _request = build_beta_evaluators_create_generation_job_request(
+ operation_id=operation_id,
+ content_type=content_type,
+ api_version=self._config.api_version,
+ content=_content,
+ headers=_headers,
+ params=_params,
+ )
+ path_format_arguments = {
+ "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
+ }
+ _request.url = self._client.format_url(_request.url, **path_format_arguments)
+
+ _decompress = kwargs.pop("decompress", True)
+ _stream = True
+ pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access
+ _request, stream=_stream, **kwargs
+ )
+
+ response = pipeline_response.http_response
+
+ if response.status_code not in [201]:
+ try:
+ response.read() # Load the body in memory and close the socket
+ except (StreamConsumedError, StreamClosedError):
+ pass
+ map_error(status_code=response.status_code, response=response, error_map=error_map)
+ error = _failsafe_deserialize(
+ _models.ApiErrorResponse,
+ response,
+ )
+ raise HttpResponseError(response=response, model=error)
+
+ response_headers = {}
+ response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+
+ return deserialized # type: ignore
+
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: _models.EvaluatorGenerationJob,
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.EvaluatorGenerationJob:
+ ) -> LROPoller[_models.EvaluatorVersion]:
"""Create an evaluator generation job.
Creates an evaluator generation job. The service generates rubric-based evaluator definitions
@@ -10325,15 +10398,16 @@ def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: EvaluatorGenerationJob. The EvaluatorGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.EvaluatorGenerationJob
+ :return: An instance of LROPoller that returns EvaluatorVersion. The EvaluatorVersion is
+ compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.EvaluatorVersion]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self, job: JSON, *, operation_id: Optional[str] = None, content_type: str = "application/json", **kwargs: Any
- ) -> _models.EvaluatorGenerationJob:
+ ) -> LROPoller[_models.EvaluatorVersion]:
"""Create an evaluator generation job.
Creates an evaluator generation job. The service generates rubric-based evaluator definitions
@@ -10347,20 +10421,21 @@ def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: EvaluatorGenerationJob. The EvaluatorGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.EvaluatorGenerationJob
+ :return: An instance of LROPoller that returns EvaluatorVersion. The EvaluatorVersion is
+ compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.EvaluatorVersion]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: IO[bytes],
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.EvaluatorGenerationJob:
+ ) -> LROPoller[_models.EvaluatorVersion]:
"""Create an evaluator generation job.
Creates an evaluator generation job. The service generates rubric-based evaluator definitions
@@ -10374,19 +10449,20 @@ def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
:paramtype content_type: str
- :return: EvaluatorGenerationJob. The EvaluatorGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.EvaluatorGenerationJob
+ :return: An instance of LROPoller that returns EvaluatorVersion. The EvaluatorVersion is
+ compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.EvaluatorVersion]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@distributed_trace
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: Union[_models.EvaluatorGenerationJob, JSON, IO[bytes]],
*,
operation_id: Optional[str] = None,
**kwargs: Any
- ) -> _models.EvaluatorGenerationJob:
+ ) -> LROPoller[_models.EvaluatorVersion]:
"""Create an evaluator generation job.
Creates an evaluator generation job. The service generates rubric-based evaluator definitions
@@ -10398,78 +10474,67 @@ def create_generation_job(
:keyword operation_id: Client-generated unique ID for idempotent retries. When absent, the
server creates the job unconditionally. Default value is None.
:paramtype operation_id: str
- :return: EvaluatorGenerationJob. The EvaluatorGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.EvaluatorGenerationJob
+ :return: An instance of LROPoller that returns EvaluatorVersion. The EvaluatorVersion is
+ compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.EvaluatorVersion]
:raises ~azure.core.exceptions.HttpResponseError:
"""
- error_map: MutableMapping = {
- 401: ClientAuthenticationError,
- 404: ResourceNotFoundError,
- 409: ResourceExistsError,
- 304: ResourceNotModifiedError,
- }
- error_map.update(kwargs.pop("error_map", {}) or {})
-
_headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
_params = kwargs.pop("params", {}) or {}
content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
- cls: ClsType[_models.EvaluatorGenerationJob] = kwargs.pop("cls", None)
+ cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None)
+ polling: Union[bool, PollingMethod] = kwargs.pop("polling", True)
+ lro_delay = kwargs.pop("polling_interval", self._config.polling_interval)
+ cont_token: Optional[str] = kwargs.pop("continuation_token", None)
+ if cont_token is None:
+ raw_result = self._create_generation_job_initial(
+ job=job,
+ operation_id=operation_id,
+ content_type=content_type,
+ cls=lambda x, y, z: x,
+ headers=_headers,
+ params=_params,
+ **kwargs
+ )
+ raw_result.http_response.read() # type: ignore
+ kwargs.pop("error_map", None)
- content_type = content_type or "application/json"
- _content = None
- if isinstance(job, (IOBase, bytes)):
- _content = job
- else:
- _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+ def get_long_running_output(pipeline_response):
+ response_headers = {}
+ response = pipeline_response.http_response
+ response_headers["Operation-Location"] = self._deserialize(
+ "str", response.headers.get("Operation-Location")
+ )
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = _deserialize(_models.EvaluatorVersion, response.json().get("result", {}))
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+ return deserialized
- _request = build_beta_evaluators_create_generation_job_request(
- operation_id=operation_id,
- content_type=content_type,
- api_version=self._config.api_version,
- content=_content,
- headers=_headers,
- params=_params,
- )
path_format_arguments = {
"endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
}
- _request.url = self._client.format_url(_request.url, **path_format_arguments)
-
- _decompress = kwargs.pop("decompress", True)
- _stream = kwargs.pop("stream", False)
- pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access
- _request, stream=_stream, **kwargs
- )
- response = pipeline_response.http_response
-
- if response.status_code not in [201]:
- if _stream:
- try:
- response.read() # Load the body in memory and close the socket
- except (StreamConsumedError, StreamClosedError):
- pass
- map_error(status_code=response.status_code, response=response, error_map=error_map)
- error = _failsafe_deserialize(
- _models.ApiErrorResponse,
- response,
+ if polling is True:
+ polling_method: PollingMethod = cast(
+ PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs)
)
- raise HttpResponseError(response=response, model=error)
-
- response_headers = {}
- response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
- response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
-
- if _stream:
- deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+ elif polling is False:
+ polling_method = cast(PollingMethod, NoPolling())
else:
- deserialized = _deserialize(_models.EvaluatorGenerationJob, response.json())
-
- if cls:
- return cls(pipeline_response, deserialized, response_headers) # type: ignore
-
- return deserialized # type: ignore
+ polling_method = polling
+ if cont_token:
+ return LROPoller[_models.EvaluatorVersion].from_continuation_token(
+ polling_method=polling_method,
+ continuation_token=cont_token,
+ client=self._client,
+ deserialization_callback=get_long_running_output,
+ )
+ return LROPoller[_models.EvaluatorVersion](
+ self._client, raw_result, get_long_running_output, polling_method # type: ignore
+ )
@distributed_trace
def get_generation_job(self, job_id: str, **kwargs: Any) -> _models.EvaluatorGenerationJob:
@@ -13168,8 +13233,7 @@ def get(self, name: str, version: str, **kwargs: Any) -> _models.ModelVersion:
def delete(self, name: str, version: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements
"""Delete a model version.
- Delete the specific version of the ModelVersion. The service returns 200 OK if the ModelVersion
- was deleted successfully or if the ModelVersion does not exist.
+ Removes the specified model version. Returns 200 whether the version existed or not.
:param name: The name of the resource. Required.
:type name: str
@@ -13230,7 +13294,7 @@ def update(
) -> _models.ModelVersion:
"""Update a model version.
- Update an existing ModelVersion with the given version id.
+ Updates an existing model version identified by its version ID.
:param name: The name of the resource. Required.
:type name: str
@@ -13259,7 +13323,7 @@ def update(
) -> _models.ModelVersion:
"""Update a model version.
- Update an existing ModelVersion with the given version id.
+ Updates an existing model version identified by its version ID.
:param name: The name of the resource. Required.
:type name: str
@@ -13288,7 +13352,7 @@ def update(
) -> _models.ModelVersion:
"""Update a model version.
- Update an existing ModelVersion with the given version id.
+ Updates an existing model version identified by its version ID.
:param name: The name of the resource. Required.
:type name: str
@@ -13315,7 +13379,7 @@ def update(
) -> _models.ModelVersion:
"""Update a model version.
- Update an existing ModelVersion with the given version id.
+ Updates an existing model version identified by its version ID.
:param name: The name of the resource. Required.
:type name: str
@@ -13576,7 +13640,7 @@ def pending_upload(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param pending_upload_request: Required.
+ :param pending_upload_request: The pending upload request request body. Required.
:type pending_upload_request: ~azure.ai.projects.models.ModelPendingUploadRequest
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
@@ -13605,7 +13669,7 @@ def pending_upload(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param pending_upload_request: Required.
+ :param pending_upload_request: The pending upload request request body. Required.
:type pending_upload_request: JSON
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
@@ -13634,7 +13698,7 @@ def pending_upload(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param pending_upload_request: Required.
+ :param pending_upload_request: The pending upload request request body. Required.
:type pending_upload_request: IO[bytes]
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
@@ -13661,8 +13725,8 @@ def pending_upload(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param pending_upload_request: Is one of the following types: ModelPendingUploadRequest, JSON,
- IO[bytes] Required.
+ :param pending_upload_request: The pending upload request request body. Is one of the following
+ types: ModelPendingUploadRequest, JSON, IO[bytes] Required.
:type pending_upload_request: ~azure.ai.projects.models.ModelPendingUploadRequest or JSON or
IO[bytes]
:return: ModelPendingUploadResponse. The ModelPendingUploadResponse is compatible with
@@ -13750,7 +13814,7 @@ def get_credentials(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param credential_request: Required.
+ :param credential_request: The credential request request body. Required.
:type credential_request: ~azure.ai.projects.models.ModelCredentialRequest
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
@@ -13778,7 +13842,7 @@ def get_credentials(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param credential_request: Required.
+ :param credential_request: The credential request request body. Required.
:type credential_request: JSON
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
@@ -13806,7 +13870,7 @@ def get_credentials(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param credential_request: Required.
+ :param credential_request: The credential request request body. Required.
:type credential_request: IO[bytes]
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
@@ -13832,8 +13896,8 @@ def get_credentials(
:type name: str
:param version: Version of the model. Required.
:type version: str
- :param credential_request: Is one of the following types: ModelCredentialRequest, JSON,
- IO[bytes] Required.
+ :param credential_request: The credential request request body. Is one of the following types:
+ ModelCredentialRequest, JSON, IO[bytes] Required.
:type credential_request: ~azure.ai.projects.models.ModelCredentialRequest or JSON or IO[bytes]
:return: DatasetCredential. The DatasetCredential is compatible with MutableMapping
:rtype: ~azure.ai.projects.models.DatasetCredential
@@ -16099,7 +16163,7 @@ def create_from_files(
:param name: The name of the skill. Required.
:type name: str
- :param content: Required.
+ :param content: The multipart request content. Required.
:type content: ~azure.ai.projects.models.CreateSkillVersionFromFilesBody
:return: SkillVersion. The SkillVersion is compatible with MutableMapping
:rtype: ~azure.ai.projects.models.SkillVersion
@@ -16114,7 +16178,7 @@ def create_from_files(self, name: str, content: JSON, **kwargs: Any) -> _models.
:param name: The name of the skill. Required.
:type name: str
- :param content: Required.
+ :param content: The multipart request content. Required.
:type content: JSON
:return: SkillVersion. The SkillVersion is compatible with MutableMapping
:rtype: ~azure.ai.projects.models.SkillVersion
@@ -16131,7 +16195,8 @@ def create_from_files(
:param name: The name of the skill. Required.
:type name: str
- :param content: Is either a CreateSkillVersionFromFilesBody type or a JSON type. Required.
+ :param content: The multipart request content. Is either a CreateSkillVersionFromFilesBody type
+ or a JSON type. Required.
:type content: ~azure.ai.projects.models.CreateSkillVersionFromFilesBody or JSON
:return: SkillVersion. The SkillVersion is compatible with MutableMapping
:rtype: ~azure.ai.projects.models.SkillVersion
@@ -16752,15 +16817,87 @@ def get_next(_continuation_token=None):
return ItemPaged(get_next, extract_data)
+ def _create_generation_job_initial(
+ self,
+ job: Union[_models.DataGenerationJob, JSON, IO[bytes]],
+ *,
+ operation_id: Optional[str] = None,
+ **kwargs: Any
+ ) -> Iterator[bytes]:
+ error_map: MutableMapping = {
+ 401: ClientAuthenticationError,
+ 404: ResourceNotFoundError,
+ 409: ResourceExistsError,
+ 304: ResourceNotModifiedError,
+ }
+ error_map.update(kwargs.pop("error_map", {}) or {})
+
+ _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
+ _params = kwargs.pop("params", {}) or {}
+
+ content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
+ cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None)
+
+ content_type = content_type or "application/json"
+ _content = None
+ if isinstance(job, (IOBase, bytes)):
+ _content = job
+ else:
+ _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+
+ _request = build_beta_datasets_create_generation_job_request(
+ operation_id=operation_id,
+ content_type=content_type,
+ api_version=self._config.api_version,
+ content=_content,
+ headers=_headers,
+ params=_params,
+ )
+ path_format_arguments = {
+ "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
+ }
+ _request.url = self._client.format_url(_request.url, **path_format_arguments)
+
+ _decompress = kwargs.pop("decompress", True)
+ _stream = True
+ pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access
+ _request, stream=_stream, **kwargs
+ )
+
+ response = pipeline_response.http_response
+
+ if response.status_code not in [201]:
+ try:
+ response.read() # Load the body in memory and close the socket
+ except (StreamConsumedError, StreamClosedError):
+ pass
+ map_error(status_code=response.status_code, response=response, error_map=error_map)
+ error = _failsafe_deserialize(
+ _models.ApiErrorResponse,
+ response,
+ )
+ raise HttpResponseError(response=response, model=error)
+
+ response_headers = {}
+ response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+
+ return deserialized # type: ignore
+
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: _models.DataGenerationJob,
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.DataGenerationJob:
+ ) -> LROPoller[_models.DataGenerationJobResult]:
"""Create a data generation job.
Submits a new data generation job for asynchronous execution.
@@ -16773,15 +16910,16 @@ def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: DataGenerationJob. The DataGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.DataGenerationJob
+ :return: An instance of LROPoller that returns DataGenerationJobResult. The
+ DataGenerationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.DataGenerationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self, job: JSON, *, operation_id: Optional[str] = None, content_type: str = "application/json", **kwargs: Any
- ) -> _models.DataGenerationJob:
+ ) -> LROPoller[_models.DataGenerationJobResult]:
"""Create a data generation job.
Submits a new data generation job for asynchronous execution.
@@ -16794,20 +16932,21 @@ def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: DataGenerationJob. The DataGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.DataGenerationJob
+ :return: An instance of LROPoller that returns DataGenerationJobResult. The
+ DataGenerationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.DataGenerationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: IO[bytes],
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.DataGenerationJob:
+ ) -> LROPoller[_models.DataGenerationJobResult]:
"""Create a data generation job.
Submits a new data generation job for asynchronous execution.
@@ -16820,19 +16959,20 @@ def create_generation_job(
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
:paramtype content_type: str
- :return: DataGenerationJob. The DataGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.DataGenerationJob
+ :return: An instance of LROPoller that returns DataGenerationJobResult. The
+ DataGenerationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.DataGenerationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@distributed_trace
- def create_generation_job(
+ def begin_create_generation_job(
self,
job: Union[_models.DataGenerationJob, JSON, IO[bytes]],
*,
operation_id: Optional[str] = None,
**kwargs: Any
- ) -> _models.DataGenerationJob:
+ ) -> LROPoller[_models.DataGenerationJobResult]:
"""Create a data generation job.
Submits a new data generation job for asynchronous execution.
@@ -16843,78 +16983,67 @@ def create_generation_job(
:keyword operation_id: Client-generated unique ID for idempotent retries. When absent, the
server creates the job unconditionally. Default value is None.
:paramtype operation_id: str
- :return: DataGenerationJob. The DataGenerationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.DataGenerationJob
+ :return: An instance of LROPoller that returns DataGenerationJobResult. The
+ DataGenerationJobResult is compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.DataGenerationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
- error_map: MutableMapping = {
- 401: ClientAuthenticationError,
- 404: ResourceNotFoundError,
- 409: ResourceExistsError,
- 304: ResourceNotModifiedError,
- }
- error_map.update(kwargs.pop("error_map", {}) or {})
-
_headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
_params = kwargs.pop("params", {}) or {}
content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
- cls: ClsType[_models.DataGenerationJob] = kwargs.pop("cls", None)
+ cls: ClsType[_models.DataGenerationJobResult] = kwargs.pop("cls", None)
+ polling: Union[bool, PollingMethod] = kwargs.pop("polling", True)
+ lro_delay = kwargs.pop("polling_interval", self._config.polling_interval)
+ cont_token: Optional[str] = kwargs.pop("continuation_token", None)
+ if cont_token is None:
+ raw_result = self._create_generation_job_initial(
+ job=job,
+ operation_id=operation_id,
+ content_type=content_type,
+ cls=lambda x, y, z: x,
+ headers=_headers,
+ params=_params,
+ **kwargs
+ )
+ raw_result.http_response.read() # type: ignore
+ kwargs.pop("error_map", None)
- content_type = content_type or "application/json"
- _content = None
- if isinstance(job, (IOBase, bytes)):
- _content = job
- else:
- _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+ def get_long_running_output(pipeline_response):
+ response_headers = {}
+ response = pipeline_response.http_response
+ response_headers["Operation-Location"] = self._deserialize(
+ "str", response.headers.get("Operation-Location")
+ )
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = _deserialize(_models.DataGenerationJobResult, response.json().get("result", {}))
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+ return deserialized
- _request = build_beta_datasets_create_generation_job_request(
- operation_id=operation_id,
- content_type=content_type,
- api_version=self._config.api_version,
- content=_content,
- headers=_headers,
- params=_params,
- )
path_format_arguments = {
"endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
}
- _request.url = self._client.format_url(_request.url, **path_format_arguments)
-
- _decompress = kwargs.pop("decompress", True)
- _stream = kwargs.pop("stream", False)
- pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access
- _request, stream=_stream, **kwargs
- )
-
- response = pipeline_response.http_response
- if response.status_code not in [201]:
- if _stream:
- try:
- response.read() # Load the body in memory and close the socket
- except (StreamConsumedError, StreamClosedError):
- pass
- map_error(status_code=response.status_code, response=response, error_map=error_map)
- error = _failsafe_deserialize(
- _models.ApiErrorResponse,
- response,
+ if polling is True:
+ polling_method: PollingMethod = cast(
+ PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs)
)
- raise HttpResponseError(response=response, model=error)
-
- response_headers = {}
- response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
- response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
-
- if _stream:
- deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+ elif polling is False:
+ polling_method = cast(PollingMethod, NoPolling())
else:
- deserialized = _deserialize(_models.DataGenerationJob, response.json())
-
- if cls:
- return cls(pipeline_response, deserialized, response_headers) # type: ignore
-
- return deserialized # type: ignore
+ polling_method = polling
+ if cont_token:
+ return LROPoller[_models.DataGenerationJobResult].from_continuation_token(
+ polling_method=polling_method,
+ continuation_token=cont_token,
+ client=self._client,
+ deserialization_callback=get_long_running_output,
+ )
+ return LROPoller[_models.DataGenerationJobResult](
+ self._client, raw_result, get_long_running_output, polling_method # type: ignore
+ )
@distributed_trace
def cancel_generation_job(self, job_id: str, **kwargs: Any) -> _models.DataGenerationJob:
@@ -17057,19 +17186,87 @@ def __init__(self, *args, **kwargs) -> None:
self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer")
self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer")
+ def _create_optimization_job_initial(
+ self, job: Union[_models.OptimizationJob, JSON, IO[bytes]], *, operation_id: Optional[str] = None, **kwargs: Any
+ ) -> Iterator[bytes]:
+ error_map: MutableMapping = {
+ 401: ClientAuthenticationError,
+ 404: ResourceNotFoundError,
+ 409: ResourceExistsError,
+ 304: ResourceNotModifiedError,
+ }
+ error_map.update(kwargs.pop("error_map", {}) or {})
+
+ _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
+ _params = kwargs.pop("params", {}) or {}
+
+ content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
+ cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None)
+
+ content_type = content_type or "application/json"
+ _content = None
+ if isinstance(job, (IOBase, bytes)):
+ _content = job
+ else:
+ _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+
+ _request = build_beta_agents_create_optimization_job_request(
+ operation_id=operation_id,
+ content_type=content_type,
+ api_version=self._config.api_version,
+ content=_content,
+ headers=_headers,
+ params=_params,
+ )
+ path_format_arguments = {
+ "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
+ }
+ _request.url = self._client.format_url(_request.url, **path_format_arguments)
+
+ _decompress = kwargs.pop("decompress", True)
+ _stream = True
+ pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access
+ _request, stream=_stream, **kwargs
+ )
+
+ response = pipeline_response.http_response
+
+ if response.status_code not in [201]:
+ try:
+ response.read() # Load the body in memory and close the socket
+ except (StreamConsumedError, StreamClosedError):
+ pass
+ map_error(status_code=response.status_code, response=response, error_map=error_map)
+ error = _failsafe_deserialize(
+ _models.ApiErrorResponse,
+ response,
+ )
+ raise HttpResponseError(response=response, model=error)
+
+ response_headers = {}
+ response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+
+ return deserialized # type: ignore
+
@overload
- def create_optimization_job(
+ def begin_create_optimization_job(
self,
job: _models.OptimizationJob,
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.OptimizationJob:
- """Creates an agent optimization job.
+ ) -> LROPoller[_models.OptimizationJobResult]:
+ """Create an agent optimization job.
- Create an optimization job. Returns 201 with the queued job. Honours ``Operation-Id`` for
- idempotent retry.
+ Creates an optimization job and returns the queued job. Honors ``Operation-Id`` for idempotent
+ retry.
:param job: The job to create. Required.
:type job: ~azure.ai.projects.models.OptimizationJob
@@ -17079,19 +17276,20 @@ def create_optimization_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: OptimizationJob. The OptimizationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.OptimizationJob
+ :return: An instance of LROPoller that returns OptimizationJobResult. The OptimizationJobResult
+ is compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.OptimizationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- def create_optimization_job(
+ def begin_create_optimization_job(
self, job: JSON, *, operation_id: Optional[str] = None, content_type: str = "application/json", **kwargs: Any
- ) -> _models.OptimizationJob:
- """Creates an agent optimization job.
+ ) -> LROPoller[_models.OptimizationJobResult]:
+ """Create an agent optimization job.
- Create an optimization job. Returns 201 with the queued job. Honours ``Operation-Id`` for
- idempotent retry.
+ Creates an optimization job and returns the queued job. Honors ``Operation-Id`` for idempotent
+ retry.
:param job: The job to create. Required.
:type job: JSON
@@ -17101,24 +17299,25 @@ def create_optimization_job(
:keyword content_type: Body Parameter content-type. Content type parameter for JSON body.
Default value is "application/json".
:paramtype content_type: str
- :return: OptimizationJob. The OptimizationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.OptimizationJob
+ :return: An instance of LROPoller that returns OptimizationJobResult. The OptimizationJobResult
+ is compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.OptimizationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@overload
- def create_optimization_job(
+ def begin_create_optimization_job(
self,
job: IO[bytes],
*,
operation_id: Optional[str] = None,
content_type: str = "application/json",
**kwargs: Any
- ) -> _models.OptimizationJob:
- """Creates an agent optimization job.
+ ) -> LROPoller[_models.OptimizationJobResult]:
+ """Create an agent optimization job.
- Create an optimization job. Returns 201 with the queued job. Honours ``Operation-Id`` for
- idempotent retry.
+ Creates an optimization job and returns the queued job. Honors ``Operation-Id`` for idempotent
+ retry.
:param job: The job to create. Required.
:type job: IO[bytes]
@@ -17128,19 +17327,20 @@ def create_optimization_job(
:keyword content_type: Body Parameter content-type. Content type parameter for binary body.
Default value is "application/json".
:paramtype content_type: str
- :return: OptimizationJob. The OptimizationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.OptimizationJob
+ :return: An instance of LROPoller that returns OptimizationJobResult. The OptimizationJobResult
+ is compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.OptimizationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
@distributed_trace
- def create_optimization_job(
+ def begin_create_optimization_job(
self, job: Union[_models.OptimizationJob, JSON, IO[bytes]], *, operation_id: Optional[str] = None, **kwargs: Any
- ) -> _models.OptimizationJob:
- """Creates an agent optimization job.
+ ) -> LROPoller[_models.OptimizationJobResult]:
+ """Create an agent optimization job.
- Create an optimization job. Returns 201 with the queued job. Honours ``Operation-Id`` for
- idempotent retry.
+ Creates an optimization job and returns the queued job. Honors ``Operation-Id`` for idempotent
+ retry.
:param job: The job to create. Is one of the following types: OptimizationJob, JSON, IO[bytes]
Required.
@@ -17148,84 +17348,73 @@ def create_optimization_job(
:keyword operation_id: Client-generated unique ID for idempotent retries. When absent, the
server creates the job unconditionally. Default value is None.
:paramtype operation_id: str
- :return: OptimizationJob. The OptimizationJob is compatible with MutableMapping
- :rtype: ~azure.ai.projects.models.OptimizationJob
+ :return: An instance of LROPoller that returns OptimizationJobResult. The OptimizationJobResult
+ is compatible with MutableMapping
+ :rtype: ~azure.core.polling.LROPoller[~azure.ai.projects.models.OptimizationJobResult]
:raises ~azure.core.exceptions.HttpResponseError:
"""
- error_map: MutableMapping = {
- 401: ClientAuthenticationError,
- 404: ResourceNotFoundError,
- 409: ResourceExistsError,
- 304: ResourceNotModifiedError,
- }
- error_map.update(kwargs.pop("error_map", {}) or {})
-
_headers = case_insensitive_dict(kwargs.pop("headers", {}) or {})
_params = kwargs.pop("params", {}) or {}
content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None))
- cls: ClsType[_models.OptimizationJob] = kwargs.pop("cls", None)
+ cls: ClsType[_models.OptimizationJobResult] = kwargs.pop("cls", None)
+ polling: Union[bool, PollingMethod] = kwargs.pop("polling", True)
+ lro_delay = kwargs.pop("polling_interval", self._config.polling_interval)
+ cont_token: Optional[str] = kwargs.pop("continuation_token", None)
+ if cont_token is None:
+ raw_result = self._create_optimization_job_initial(
+ job=job,
+ operation_id=operation_id,
+ content_type=content_type,
+ cls=lambda x, y, z: x,
+ headers=_headers,
+ params=_params,
+ **kwargs
+ )
+ raw_result.http_response.read() # type: ignore
+ kwargs.pop("error_map", None)
- content_type = content_type or "application/json"
- _content = None
- if isinstance(job, (IOBase, bytes)):
- _content = job
- else:
- _content = json.dumps(job, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore
+ def get_long_running_output(pipeline_response):
+ response_headers = {}
+ response = pipeline_response.http_response
+ response_headers["Operation-Location"] = self._deserialize(
+ "str", response.headers.get("Operation-Location")
+ )
+ response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
+
+ deserialized = _deserialize(_models.OptimizationJobResult, response.json().get("result", {}))
+ if cls:
+ return cls(pipeline_response, deserialized, response_headers) # type: ignore
+ return deserialized
- _request = build_beta_agents_create_optimization_job_request(
- operation_id=operation_id,
- content_type=content_type,
- api_version=self._config.api_version,
- content=_content,
- headers=_headers,
- params=_params,
- )
path_format_arguments = {
"endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True),
}
- _request.url = self._client.format_url(_request.url, **path_format_arguments)
-
- _decompress = kwargs.pop("decompress", True)
- _stream = kwargs.pop("stream", False)
- pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access
- _request, stream=_stream, **kwargs
- )
- response = pipeline_response.http_response
-
- if response.status_code not in [201]:
- if _stream:
- try:
- response.read() # Load the body in memory and close the socket
- except (StreamConsumedError, StreamClosedError):
- pass
- map_error(status_code=response.status_code, response=response, error_map=error_map)
- error = _failsafe_deserialize(
- _models.ApiErrorResponse,
- response,
+ if polling is True:
+ polling_method: PollingMethod = cast(
+ PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs)
)
- raise HttpResponseError(response=response, model=error)
-
- response_headers = {}
- response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location"))
- response_headers["Location"] = self._deserialize("str", response.headers.get("Location"))
-
- if _stream:
- deserialized = response.iter_bytes() if _decompress else response.iter_raw()
+ elif polling is False:
+ polling_method = cast(PollingMethod, NoPolling())
else:
- deserialized = _deserialize(_models.OptimizationJob, response.json())
-
- if cls:
- return cls(pipeline_response, deserialized, response_headers) # type: ignore
-
- return deserialized # type: ignore
+ polling_method = polling
+ if cont_token:
+ return LROPoller[_models.OptimizationJobResult].from_continuation_token(
+ polling_method=polling_method,
+ continuation_token=cont_token,
+ client=self._client,
+ deserialization_callback=get_long_running_output,
+ )
+ return LROPoller[_models.OptimizationJobResult](
+ self._client, raw_result, get_long_running_output, polling_method # type: ignore
+ )
@distributed_trace
def get_optimization_job(self, job_id: str, **kwargs: Any) -> _models.OptimizationJob:
- """Get info about an agent optimization job.
+ """Get an agent optimization job.
- Get an optimization job by id.
+ Retrieves an optimization job by its identifier.
:param job_id: The ID of the job. Required.
:type job_id: str
@@ -17302,9 +17491,9 @@ def list_optimization_jobs(
agent_name: Optional[str] = None,
**kwargs: Any
) -> ItemPaged["_models.OptimizationJobListItem"]:
- """Returns a list of agent optimization jobs.
+ """List agent optimization jobs.
- List optimization jobs. Supports cursor pagination and optional status / agent_name filters.
+ Lists optimization jobs with cursor pagination and optional status or agent name filters.
:keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and
100, and the
@@ -17394,10 +17583,10 @@ def get_next(_continuation_token=None):
@distributed_trace
def cancel_optimization_job(self, job_id: str, **kwargs: Any) -> _models.OptimizationJob:
- """Cancels an agent optimization job.
+ """Cancel an agent optimization job.
- Request cancellation of a running or queued job. Returns an error if the job is already in a
- terminal state.
+ Requests cancellation of a running or queued job and returns an error if the job is already in
+ a terminal state.
:param job_id: The ID of the job to cancel. Required.
:type job_id: str
@@ -17464,9 +17653,9 @@ def cancel_optimization_job(self, job_id: str, **kwargs: Any) -> _models.Optimiz
def delete_optimization_job( # pylint: disable=inconsistent-return-statements
self, job_id: str, **kwargs: Any
) -> None:
- """Deletes an agent optimization job.
+ """Delete an agent optimization job.
- Delete the job and its candidate artifacts. Cancels first if non-terminal.
+ Deletes the job and its candidate artifacts, canceling the job first if it is non-terminal.
:param job_id: The ID of the job to delete. Required.
:type job_id: str
diff --git a/sdk/ai/azure-ai-projects/docs/public-methods.md b/sdk/ai/azure-ai-projects/docs/public-methods.md
index 4aeda5b8802e..66716e015430 100644
--- a/sdk/ai/azure-ai-projects/docs/public-methods.md
+++ b/sdk/ai/azure-ai-projects/docs/public-methods.md
@@ -126,13 +126,13 @@ Alphabetically sorted. An asterisk at the end of the method name means is a hand
```
.beta.agents.cancel_optimization_job
-.beta.agents.create_optimization_job
+.beta.agents.begin_create_optimization_job
.beta.agents.delete_optimization_job
.beta.agents.get_optimization_job
.beta.agents.list_optimization_jobs
.beta.datasets.cancel_generation_job
-.beta.datasets.create_generation_job
+.beta.datasets.begin_create_generation_job
.beta.datasets.delete_generation_job
.beta.datasets.get_generation_job
.beta.datasets.list_generation_jobs
@@ -144,7 +144,7 @@ Alphabetically sorted. An asterisk at the end of the method name means is a hand
.beta.evaluation_taxonomies.update
.beta.evaluators.cancel_generation_job
-.beta.evaluators.create_generation_job
+.beta.evaluators.begin_create_generation_job
.beta.evaluators.create_version
.beta.evaluators.delete_generation_job
.beta.evaluators.delete_version
diff --git a/sdk/ai/azure-ai-projects/docs/tool-classes-removed-properties.md b/sdk/ai/azure-ai-projects/docs/tool-classes-removed-properties.md
deleted file mode 100644
index 7a0f4e3ebd2d..000000000000
--- a/sdk/ai/azure-ai-projects/docs/tool-classes-removed-properties.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# Tool Classes: Removed Properties (v2.2.0 → v2.3.0)
-
-The following Tool-derived classes had properties **removed** in v2.3.0 compared to v2.2.0. These properties (`name`, `description`, `tool_configs`) now only exist on the corresponding `ToolboxTool` subclasses.
-
-## General Availability Tools
-
-| Class Name | Removed Properties |
-|------------|-------------------|
-| `AzureAISearchTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `AzureFunctionTool` | `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `BingGroundingTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `CaptureStructuredOutputsTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `CodeInterpreterTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `FileSearchTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `FunctionShellToolParam` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `FunctionTool` | *(no changes)* |
-| `ImageGenTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `LocalShellToolParam` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `MCPTool` | `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `OpenApiTool` | `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `WebSearchTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-v
-## Preview Tools
-
-| Class Name | Removed Properties |
-|------------|-------------------|
-| `A2APreviewTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `BingCustomSearchPreviewTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `BrowserAutomationPreviewTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `ComputerUsePreviewTool` | *(no changes)* |
-| `FabricIQPreviewTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `MemorySearchPreviewTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `MicrosoftFabricPreviewTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `SharepointPreviewTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `ToolboxSearchPreviewTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-| `WebSearchPreviewTool` | *(no changes)* |
-| `WorkIQPreviewTool` | `name: Optional[str]`, `description: Optional[str]`, `tool_configs: Optional[dict[str, ToolConfig]]` |
-
-## Summary
-
-- **Total Tool classes analyzed:** 24
-- **Classes with removed properties:** 19
-- **Common pattern:** `name`, `description`, and `tool_configs` were moved exclusively to `ToolboxTool` subclasses
diff --git a/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic.py b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic.py
index 81de414621c6..4e914f36cfb8 100644
--- a/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic.py
+++ b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic.py
@@ -18,7 +18,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.3.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -33,7 +33,6 @@
"""
import os
-import time
from dotenv import load_dotenv
@@ -42,7 +41,6 @@
from azure.ai.projects.models import (
OptimizationAgentIdentifier as AgentIdentifier,
OptimizationEvaluatorRef as EvaluatorRef,
- JobStatus,
OptimizationJob,
OptimizationJobInputs,
OptimizationOptions,
@@ -60,8 +58,6 @@
eval_model = os.environ.get("EVAL_MODEL", "gpt-4o")
optimization_model = os.environ.get("OPTIMIZATION_MODEL", "gpt-5.1")
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
@@ -71,7 +67,7 @@
# 1. Create an optimization job.
# ------------------------------------------------------------------
print("Creating optimization job...")
- job = project_client.beta.agents.create_optimization_job(
+ result = project_client.beta.agents.begin_create_optimization_job(
job=OptimizationJob(
inputs=OptimizationJobInputs(
agent=AgentIdentifier(agent_name=agent_name),
@@ -86,42 +82,22 @@
optimization_model=optimization_model,
),
)
- )
- )
- print(f"Created job: id={job.id}, status={job.status}")
+ ),
+ polling_interval=poll_interval,
+ ).result()
+ print("Optimization job completed.")
# ------------------------------------------------------------------
- # 2. Poll until the job reaches a terminal state.
+ # 2. Inspect the results.
# ------------------------------------------------------------------
- print(f"Polling job `{job.id}` to completion...", end="", flush=True)
- while job.status not in TERMINAL_STATUSES:
- time.sleep(poll_interval)
- job = project_client.beta.agents.get_optimization_job(job_id=job.id)
- print(".", end="", flush=True)
- print()
- print(f"Final status: {job.status}")
-
- if job.warnings:
- for warning in job.warnings:
- print(f"[WARNING] {warning}")
-
- if job.status == JobStatus.FAILED:
- message = job.error.message if job.error else ""
- raise RuntimeError(f"Optimization job `{job.id}` failed: {message}")
-
- # ------------------------------------------------------------------
- # 3. Inspect the results.
- # ------------------------------------------------------------------
- if job.status == JobStatus.SUCCEEDED and job.result:
- result = job.result
- print(f"\nBaseline candidate: {result.baseline}")
- print(f"Best candidate: {result.best}")
- print(f"Candidates ({len(result.candidates or [])}):")
- for candidate in result.candidates or []:
- print(
- f" - {candidate.name}"
- f" | avg_score={candidate.avg_score:.4f}"
- f" | avg_tokens={candidate.avg_tokens:.0f}"
- )
- if candidate.eval_id:
- print(f" eval_id={candidate.eval_id}")
+ print(f"\nBaseline candidate: {result.baseline}")
+ print(f"Best candidate: {result.best}")
+ print(f"Candidates ({len(result.candidates or [])}):")
+ for candidate in result.candidates or []:
+ print(
+ f" - {candidate.name}"
+ f" | avg_score={candidate.avg_score:.4f}"
+ f" | avg_tokens={candidate.avg_tokens:.0f}"
+ )
+ if candidate.eval_id:
+ print(f" eval_id={candidate.eval_id}")
diff --git a/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_async.py
index 90137d28696c..4be9f2ee07c5 100644
--- a/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_async.py
+++ b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_async.py
@@ -15,7 +15,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.3.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -39,7 +39,6 @@
from azure.ai.projects.models import (
OptimizationAgentIdentifier as AgentIdentifier,
OptimizationEvaluatorRef as EvaluatorRef,
- JobStatus,
OptimizationJob,
OptimizationJobInputs,
OptimizationOptions,
@@ -57,8 +56,6 @@
optimization_model = os.environ.get("OPTIMIZATION_MODEL", "gpt-5.1")
poll_interval = int(os.environ.get("POLL_INTERVAL_SECONDS", "10"))
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
async def main() -> None:
async with (
@@ -70,7 +67,7 @@ async def main() -> None:
# 1. Create an optimization job.
# ------------------------------------------------------------------
print("Creating optimization job...")
- job = await project_client.beta.agents.create_optimization_job(
+ poller = await project_client.beta.agents.begin_create_optimization_job(
job=OptimizationJob(
inputs=OptimizationJobInputs(
agent=AgentIdentifier(agent_name=agent_name),
@@ -85,45 +82,26 @@ async def main() -> None:
optimization_model=optimization_model,
),
)
- )
+ ),
+ polling_interval=poll_interval,
)
- print(f"Created job: id={job.id}, status={job.status}")
+ result = await poller.result()
+ print("Optimization job completed.")
# ------------------------------------------------------------------
- # 2. Poll until terminal state.
- # ------------------------------------------------------------------
- print(f"Polling job `{job.id}` to completion...", end="", flush=True)
- while job.status not in TERMINAL_STATUSES:
- await asyncio.sleep(poll_interval)
- job = await project_client.beta.agents.get_optimization_job(job_id=job.id)
- print(".", end="", flush=True)
- print()
- print(f"Final status: {job.status}")
-
- if job.warnings:
- for warning in job.warnings:
- print(f"[WARNING] {warning}")
-
- if job.status == JobStatus.FAILED:
- message = job.error.message if job.error else ""
- raise RuntimeError(f"Optimization job `{job.id}` failed: {message}")
-
+ # 2. Inspect the results.
# ------------------------------------------------------------------
- # 3. Inspect the results.
- # ------------------------------------------------------------------
- if job.status == JobStatus.SUCCEEDED and job.result:
- result = job.result
- print(f"\nBaseline candidate: {result.baseline}")
- print(f"Best candidate: {result.best}")
- print(f"Candidates ({len(result.candidates or [])}):")
- for candidate in result.candidates or []:
- print(
- f" - {candidate.name}"
- f" | avg_score={candidate.avg_score:.4f}"
- f" | avg_tokens={candidate.avg_tokens:.0f}"
- )
- if candidate.eval_id:
- print(f" eval_id={candidate.eval_id}")
+ print(f"\nBaseline candidate: {result.baseline}")
+ print(f"Best candidate: {result.best}")
+ print(f"Candidates ({len(result.candidates or [])}):")
+ for candidate in result.candidates or []:
+ print(
+ f" - {candidate.name}"
+ f" | avg_score={candidate.avg_score:.4f}"
+ f" | avg_tokens={candidate.avg_tokens:.0f}"
+ )
+ if candidate.eval_id:
+ print(f" eval_id={candidate.eval_id}")
if __name__ == "__main__":
diff --git a/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_polling.py b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_polling.py
new file mode 100644
index 000000000000..0e1bdbacc9dc
--- /dev/null
+++ b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_polling.py
@@ -0,0 +1,138 @@
+# pylint: disable=line-too-long,useless-suppression
+# ------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------
+
+"""
+DESCRIPTION:
+ Given an AIProjectClient, this sample demonstrates how to create an agent
+ optimization job and manually poll it to completion.
+
+ Agent optimization automatically improves an agent's system prompt, model
+ choice, or tool definitions by running candidate variants against your
+ training dataset and scoring them with the evaluators you specify.
+
+USAGE:
+ python sample_optimization_job_basic_polling.py
+
+ Before running the sample:
+
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
+
+ Set these environment variables with your own values:
+ 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
+ in the overview page of your Microsoft Foundry portal.
+ 2) FOUNDRY_AGENT_NAME - Required. The name of the agent to optimize.
+ 3) DATASET_NAME - Required. The name of the registered training dataset.
+ 4) EVALUATOR_NAME - Required. The name of a registered project evaluator.
+ 5) DATASET_VERSION - Optional. Version of the training dataset. Defaults to "1".
+ 6) POLL_INTERVAL_SECONDS - Optional. Seconds between status polls. Defaults to 10.
+ 7) EVAL_MODEL - Optional. The model used for evaluation. Defaults to "gpt-4o".
+ 8) OPTIMIZATION_MODEL - Optional. The model used for optimization. Defaults to "gpt-5.1".
+"""
+
+import os
+import time
+
+from dotenv import load_dotenv
+
+from azure.identity import DefaultAzureCredential
+from azure.ai.projects import AIProjectClient
+from azure.ai.projects.models import (
+ OptimizationAgentIdentifier as AgentIdentifier,
+ OptimizationEvaluatorRef as EvaluatorRef,
+ JobStatus,
+ OptimizationJob,
+ OptimizationJobInputs,
+ OptimizationOptions,
+ OptimizationReferenceDatasetInput as ReferenceDatasetInput,
+)
+
+load_dotenv()
+
+endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
+agent_name = os.environ["FOUNDRY_AGENT_NAME"]
+dataset_name = os.environ["DATASET_NAME"]
+evaluator_name = os.environ["EVALUATOR_NAME"]
+dataset_version = os.environ.get("DATASET_VERSION", "1")
+poll_interval = int(os.environ.get("POLL_INTERVAL_SECONDS", "10"))
+eval_model = os.environ.get("EVAL_MODEL", "gpt-4o")
+optimization_model = os.environ.get("OPTIMIZATION_MODEL", "gpt-5.1")
+
+terminal_statuses = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
+
+with (
+ DefaultAzureCredential() as credential,
+ AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
+):
+
+ # ------------------------------------------------------------------
+ # 1. Create an optimization job without SDK polling.
+ # ------------------------------------------------------------------
+ print("Creating optimization job...")
+ created_jobs: list[OptimizationJob] = []
+
+ def capture_created_job(response):
+ created_jobs.append(OptimizationJob(response.http_response.json()))
+
+ project_client.beta.agents.begin_create_optimization_job(
+ job=OptimizationJob(
+ inputs=OptimizationJobInputs(
+ agent=AgentIdentifier(agent_name=agent_name),
+ train_dataset=ReferenceDatasetInput(
+ name=dataset_name,
+ version=dataset_version,
+ ),
+ evaluators=[EvaluatorRef(name=evaluator_name)],
+ options=OptimizationOptions(
+ max_candidates=3,
+ eval_model=eval_model,
+ optimization_model=optimization_model,
+ ),
+ )
+ ),
+ polling=False,
+ raw_response_hook=capture_created_job,
+ )
+ if not created_jobs:
+ raise RuntimeError("The create operation did not return an optimization job.")
+ job = created_jobs[0]
+ print(f"Created job: id={job.id}, status={job.status}")
+
+ # ------------------------------------------------------------------
+ # 2. Poll the job to completion.
+ # ------------------------------------------------------------------
+ while job.status not in terminal_statuses:
+ time.sleep(poll_interval)
+ job = project_client.beta.agents.get_optimization_job(job_id=job.id)
+ print(f"Job status: {job.status}")
+
+ if job.warnings:
+ for warning in job.warnings:
+ print(f"[WARNING] {warning}")
+
+ if job.status == JobStatus.FAILED:
+ message = job.error.message if job.error else ""
+ raise RuntimeError(f"Optimization job `{job.id}` failed: {message}")
+ if job.status == JobStatus.CANCELLED:
+ raise RuntimeError(f"Optimization job `{job.id}` was cancelled.")
+
+ # ------------------------------------------------------------------
+ # 3. Inspect the results.
+ # ------------------------------------------------------------------
+ if job.result is None:
+ raise RuntimeError(f"Optimization job `{job.id}` completed without a result.")
+
+ result = job.result
+ print(f"\nBaseline candidate: {result.baseline}")
+ print(f"Best candidate: {result.best}")
+ print(f"Candidates ({len(result.candidates or [])}):")
+ for candidate in result.candidates or []:
+ print(
+ f" - {candidate.name}"
+ f" | avg_score={candidate.avg_score:.4f}"
+ f" | avg_tokens={candidate.avg_tokens:.0f}"
+ )
+ if candidate.eval_id:
+ print(f" eval_id={candidate.eval_id}")
diff --git a/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_polling_async.py b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_polling_async.py
new file mode 100644
index 000000000000..d49bba940b1f
--- /dev/null
+++ b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_basic_polling_async.py
@@ -0,0 +1,149 @@
+# pylint: disable=line-too-long,useless-suppression
+# ------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------
+
+"""
+DESCRIPTION:
+ Given an async AIProjectClient, this sample demonstrates how to create an
+ agent optimization job and manually poll it to completion.
+
+ Agent optimization automatically improves an agent's system prompt, model
+ choice, or tool definitions by running candidate variants against your
+ training dataset and scoring them with the evaluators you specify.
+
+USAGE:
+ python sample_optimization_job_basic_polling_async.py
+
+ Before running the sample:
+
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
+
+ Set these environment variables with your own values:
+ 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
+ in the overview page of your Microsoft Foundry portal.
+ 2) FOUNDRY_AGENT_NAME - Required. The name of the agent to optimize.
+ 3) DATASET_NAME - Required. The name of the registered training dataset.
+ 4) EVALUATOR_NAME - Required. The name of a registered project evaluator.
+ 5) DATASET_VERSION - Optional. Version of the training dataset. Defaults to "1".
+ 6) POLL_INTERVAL_SECONDS - Optional. Seconds between status polls. Defaults to 10.
+ 7) EVAL_MODEL - Optional. The model used for evaluation. Defaults to "gpt-4o".
+ 8) OPTIMIZATION_MODEL - Optional. The model used for optimization. Defaults to "gpt-5.1".
+"""
+
+import asyncio
+import json
+import os
+
+from dotenv import load_dotenv
+
+from azure.core.pipeline import PipelineResponse
+from azure.core.pipeline.transport import AsyncHttpResponse, HttpRequest
+from azure.identity.aio import DefaultAzureCredential
+from azure.ai.projects.aio import AIProjectClient
+from azure.ai.projects.models import (
+ OptimizationAgentIdentifier as AgentIdentifier,
+ OptimizationEvaluatorRef as EvaluatorRef,
+ JobStatus,
+ OptimizationJob,
+ OptimizationJobInputs,
+ OptimizationOptions,
+ OptimizationReferenceDatasetInput as ReferenceDatasetInput,
+)
+
+load_dotenv()
+
+endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
+agent_name = os.environ["FOUNDRY_AGENT_NAME"]
+dataset_name = os.environ["DATASET_NAME"]
+evaluator_name = os.environ["EVALUATOR_NAME"]
+dataset_version = os.environ.get("DATASET_VERSION", "1")
+poll_interval = int(os.environ.get("POLL_INTERVAL_SECONDS", "10"))
+eval_model = os.environ.get("EVAL_MODEL", "gpt-4o")
+optimization_model = os.environ.get("OPTIMIZATION_MODEL", "gpt-5.1")
+
+terminal_statuses = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
+
+
+async def main() -> None:
+ async with (
+ DefaultAzureCredential() as credential,
+ AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
+ ):
+
+ # ------------------------------------------------------------------
+ # 1. Create an optimization job without SDK polling.
+ # ------------------------------------------------------------------
+ print("Creating optimization job...")
+ initial_responses: list[PipelineResponse[HttpRequest, AsyncHttpResponse]] = []
+
+ def capture_created_job_response(
+ response: PipelineResponse[HttpRequest, AsyncHttpResponse],
+ ) -> None:
+ initial_responses.append(response)
+
+ await project_client.beta.agents.begin_create_optimization_job(
+ job=OptimizationJob(
+ inputs=OptimizationJobInputs(
+ agent=AgentIdentifier(agent_name=agent_name),
+ train_dataset=ReferenceDatasetInput(
+ name=dataset_name,
+ version=dataset_version,
+ ),
+ evaluators=[EvaluatorRef(name=evaluator_name)],
+ options=OptimizationOptions(
+ max_candidates=3,
+ eval_model=eval_model,
+ optimization_model=optimization_model,
+ ),
+ )
+ ),
+ polling=False,
+ raw_response_hook=capture_created_job_response,
+ )
+ if not initial_responses:
+ raise RuntimeError("The create operation did not return an optimization job.")
+ job = OptimizationJob(json.loads(initial_responses[0].http_response.text()))
+ print(f"Created job: id={job.id}, status={job.status}")
+
+ # ------------------------------------------------------------------
+ # 2. Poll the job to completion.
+ # ------------------------------------------------------------------
+ while job.status not in terminal_statuses:
+ await asyncio.sleep(poll_interval)
+ job = await project_client.beta.agents.get_optimization_job(job_id=job.id)
+ print(f"Job status: {job.status}")
+
+ if job.warnings:
+ for warning in job.warnings:
+ print(f"[WARNING] {warning}")
+
+ if job.status == JobStatus.FAILED:
+ message = job.error.message if job.error else ""
+ raise RuntimeError(f"Optimization job `{job.id}` failed: {message}")
+ if job.status == JobStatus.CANCELLED:
+ raise RuntimeError(f"Optimization job `{job.id}` was cancelled.")
+
+ # ------------------------------------------------------------------
+ # 3. Inspect the results.
+ # ------------------------------------------------------------------
+ if job.result is None:
+ raise RuntimeError(f"Optimization job `{job.id}` completed without a result.")
+
+ result = job.result
+ print(f"\nBaseline candidate: {result.baseline}")
+ print(f"Best candidate: {result.best}")
+ print(f"Candidates ({len(result.candidates or [])}):")
+ for candidate in result.candidates or []:
+ print(
+ f" - {candidate.name}"
+ f" | avg_score={candidate.avg_score:.4f}"
+ f" | avg_tokens={candidate.avg_tokens:.0f}"
+ )
+ if candidate.eval_id:
+ print(f" eval_id={candidate.eval_id}")
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_cancel.py b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_cancel.py
index ade59c30249f..0a80c5bc41fd 100644
--- a/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_cancel.py
+++ b/sdk/ai/azure-ai-projects/samples/agents/optimization/sample_optimization_job_cancel.py
@@ -14,7 +14,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.3.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -63,7 +63,12 @@
# 1. Create a job.
# ------------------------------------------------------------------
print("Creating optimization job...")
- job = project_client.beta.agents.create_optimization_job(
+ created_jobs: list[OptimizationJob] = []
+
+ def capture_created_job(response):
+ created_jobs.append(OptimizationJob(response.http_response.json()))
+
+ project_client.beta.agents.begin_create_optimization_job(
job=OptimizationJob(
inputs=OptimizationJobInputs(
agent=AgentIdentifier(agent_name=agent_name),
@@ -78,8 +83,13 @@
optimization_model=optimization_model,
),
)
- )
+ ),
+ polling=False,
+ raw_response_hook=capture_created_job,
)
+ if not created_jobs:
+ raise RuntimeError("The create operation did not return an optimization job.")
+ job = created_jobs[0]
print(f"Created job: id={job.id}, status={job.status}")
# ------------------------------------------------------------------
diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py
index 467b3f89aa56..ed5a094a4fb0 100644
--- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py
+++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py
@@ -1,3 +1,4 @@
+# pylint: disable=line-too-long,useless-suppression
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_toolbox_skill.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_toolbox_skill.py
deleted file mode 100644
index f4e0e7de9e38..000000000000
--- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_toolbox_skill.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# pylint: disable=line-too-long,useless-suppression
-# ------------------------------------
-# Copyright (c) Microsoft Corporation.
-# Licensed under the MIT License.
-# ------------------------------------
-
-"""
-DESCRIPTION:
- This sample demonstrates how to expose a Skill to a Prompt Agent via a
- Toolbox, using the synchronous AIProjectClient and the OpenAI-compatible
- client.
-
- It creates a Skill with inline content describing how to compute shipping
- cost, then creates a Toolbox version that references the skill. A Prompt
- Agent is created with an `MCPTool` pointed at the toolbox's versioned
- `/mcp` endpoint. The skill's instructions are injected into the agent's
- context, so when asked a shipping-cost question the agent answers directly
- using the skill's formula.
-
- Skills are currently a preview features. In the Python SDK,
- you access these operations via `project_client.beta.skills`.
-
-USAGE:
- python sample_agent_toolbox_skill.py
-
- Before running the sample:
-
- pip install "azure-ai-projects>=2.3.0" python-dotenv openai
-
- Set these environment variables with your own values:
- 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the
- Overview page of your Microsoft Foundry portal.
- 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under
- the "Name" column in the "Models + endpoints" tab in your Microsoft
- Foundry project.
- 3) FOUNDRY_AGENT_NAME - Optional. The name of the AI agent. If not set, defaults to "MyAgent".
-"""
-
-import os
-
-from dotenv import load_dotenv
-
-from azure.ai.projects.models._models import ToolboxSearchPreviewToolboxTool
-from azure.core.exceptions import ResourceNotFoundError
-from azure.identity import DefaultAzureCredential
-
-from azure.ai.projects import AIProjectClient
-from azure.ai.projects.models import (
- MCPTool,
- PromptAgentDefinition,
- SkillInlineContent,
- ToolboxSearchPreviewToolboxTool,
- ToolboxSkillReference,
-)
-from util import create_version_with_endpoint
-
-load_dotenv()
-
-endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
-
-SKILL_NAME = "shipping-cost-skill"
-TOOLBOX_NAME = "toolbox_with_skill"
-agent_name = os.environ.get("FOUNDRY_AGENT_NAME", "MyAgent")
-
-
-with (
- DefaultAzureCredential() as credential,
- AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
- project_client.get_openai_client(agent_name=agent_name) as openai_client,
-):
-
- try:
- project_client.toolboxes.delete(TOOLBOX_NAME)
- except ResourceNotFoundError:
- pass
-
- try:
- project_client.beta.skills.delete(SKILL_NAME)
- except ResourceNotFoundError:
- pass
-
- skill_version = project_client.beta.skills.create(
- name=SKILL_NAME,
- inline_content=SkillInlineContent(
- description="Compute shipping cost for a package given weight and destination.",
- instructions=(
- "You are a shipping cost calculator. When asked to compute "
- "shipping cost, use this formula: cost (USD) = 5 + 2 * weight_kg "
- "for domestic destinations, and cost (USD) = 15 + 4 * weight_kg "
- "for international destinations. Always state the formula you used."
- ),
- metadata={"revision": "1"},
- ),
- )
- print(f"Created skill: {skill_version.name} version={skill_version.version}")
-
- toolbox_version = project_client.toolboxes.create_version(
- name=TOOLBOX_NAME,
- description="Toolbox exposing a shipping-cost skill.",
- tools=[ToolboxSearchPreviewToolboxTool()],
- skills=[ToolboxSkillReference(name=skill_version.name, version=skill_version.version)],
- )
- print(f"Created toolbox: {toolbox_version.name} version={toolbox_version.version}")
-
- toolbox_mcp_url = f"{endpoint}/toolboxes/{TOOLBOX_NAME}/versions/{toolbox_version.version}/mcp?api-version=v1"
- token = credential.get_token("https://ai.azure.com/.default").token
-
- toolbox_mcp_tool = MCPTool(
- server_label="skill-toolbox",
- server_url=toolbox_mcp_url,
- authorization=token,
- require_approval="never",
- )
-
- with create_version_with_endpoint(
- project_client=project_client,
- agent_name=agent_name,
- definition=PromptAgentDefinition(
- model=os.environ["FOUNDRY_MODEL_NAME"],
- instructions=(
- "Answer the user using the `shipping-cost-skill` instructions "
- "available in your context. Do not call `tool_search`; the "
- "skill rules are already part of your knowledge. Apply the "
- "skill's formula exactly as given and state the formula in "
- "your answer."
- ),
- temperature=0,
- tools=[toolbox_mcp_tool],
- ),
- ) as agent:
-
- user_input = "Compute the shipping cost for a 3 kg package shipped domestically."
- print(f"User: {user_input}")
- response = openai_client.responses.create(
- input=user_input,
- )
-
- for item in response.output:
- if item.type == "mcp_list_tools":
- print(f"mcp_list_tools server_label={item.server_label} tools={[t.name for t in (item.tools or [])]}")
- elif item.type == "mcp_call":
- print(f"mcp_call server_label={item.server_label} name={item.name} error={item.error}")
- if getattr(item, "output", None):
- print(f" output: {item.output}")
- elif item.type == "mcp_approval_request":
- print(f"mcp_approval_request server_label={item.server_label} name={item.name}")
- else:
- print(f"output item type={item.type}")
-
- print(f"Response: {response.output_text}")
-
- project_client.toolboxes.delete(TOOLBOX_NAME)
- print("Toolbox deleted")
- project_client.beta.skills.delete(SKILL_NAME)
- print("Skill deleted")
diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search_preview.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search.py
similarity index 94%
rename from sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search_preview.py
rename to sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search.py
index 44b4a41296d2..cc0bc3269510 100644
--- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search_preview.py
+++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search.py
@@ -10,7 +10,7 @@
invoke it from a Prompt Agent using the synchronous AIProjectClient and
the OpenAI-compatible client.
- A toolbox version that includes 'ToolboxSearchPreviewTool' exposes only
+ A toolbox version that includes 'ToolSearchToolboxTool' exposes only
two meta tools at its '/mcp' endpoint -- 'tool_search' and 'call_tool'
-- and defers every other tool behind them. The agent uses an 'MCPTool'
pointed at the toolbox's versioned '/mcp' URL to discover and invoke
@@ -20,7 +20,7 @@
'project_client.toolboxes'.
USAGE:
- python sample_toolboxes_with_search_preview.py
+ python sample_toolboxes_with_search.py
Before running the sample:
@@ -44,7 +44,7 @@
from azure.ai.projects.models import (
MCPTool,
MCPToolboxTool,
- ToolboxSearchPreviewToolboxTool,
+ ToolSearchToolboxTool,
PromptAgentDefinition,
)
@@ -75,7 +75,7 @@
toolbox_version = project_client.toolboxes.create_version(
name=TOOLBOX_NAME,
description=f"Toolbox with `{INNER_MCP_LABEL}` MCP server and tool search enabled.",
- tools=[inner_mcp_tool, ToolboxSearchPreviewToolboxTool()],
+ tools=[inner_mcp_tool, ToolSearchToolboxTool()],
)
print(f"Created toolbox `{TOOLBOX_NAME}` (version {toolbox_version.version}).")
diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search_preview_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search_async.py
similarity index 95%
rename from sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search_preview_async.py
rename to sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search_async.py
index 34dc5af711de..c5694a779f7d 100644
--- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search_preview_async.py
+++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_toolboxes_with_search_async.py
@@ -10,7 +10,7 @@
invoke it from a Prompt Agent using the asynchronous AIProjectClient and
the OpenAI-compatible client.
- A toolbox version that includes 'ToolboxSearchPreviewTool' exposes only
+ A toolbox version that includes 'ToolSearchToolboxTool' exposes only
two meta tools at its '/mcp' endpoint -- 'tool_search' and 'call_tool'
-- and defers every other tool behind them. The agent uses an 'MCPTool'
pointed at the toolbox's versioned '/mcp' URL to discover and invoke
@@ -20,7 +20,7 @@
'project_client.toolboxes'.
USAGE:
- python sample_toolboxes_with_search_preview_async.py
+ python sample_toolboxes_with_search_async.py
Before running the sample:
@@ -45,7 +45,7 @@
from azure.ai.projects.models import (
MCPTool,
MCPToolboxTool,
- ToolboxSearchPreviewToolboxTool,
+ ToolSearchToolboxTool,
PromptAgentDefinition,
)
@@ -77,7 +77,7 @@ async def main() -> None:
toolbox_version = await project_client.toolboxes.create_version(
name=TOOLBOX_NAME,
description=f"Toolbox with `{INNER_MCP_LABEL}` MCP server and tool search enabled.",
- tools=[inner_mcp_tool, ToolboxSearchPreviewToolboxTool()],
+ tools=[inner_mcp_tool, ToolSearchToolboxTool()],
)
print(f"Created toolbox `{TOOLBOX_NAME}` (version {toolbox_version.version}).")
diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_for_finetuning.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_for_finetuning.py
index 8a545c3dd6b3..c75d34c6f65b 100644
--- a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_for_finetuning.py
+++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_for_finetuning.py
@@ -61,7 +61,6 @@
DataGenerationModelOptions,
FileDataGenerationJobOutput,
FileDataGenerationJobSource,
- JobStatus,
SimpleQnADataGenerationJobOptions,
SimpleQnAFineTuningQuestionType,
)
@@ -111,8 +110,6 @@
- Standard support response: within one business day. Priority support response: within four hours.
"""
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
@@ -171,46 +168,31 @@
output_options=DataGenerationJobOutputOptions(name=output_name),
),
)
- job = project_client.beta.datasets.create_generation_job(job=job)
- print(f"Created data generation job `{job.id}` (status: `{job.status}`).")
-
- print(f"Poll job `{job.id}` until it reaches a terminal state.", end="", flush=True)
- while True:
- job = project_client.beta.datasets.get_generation_job(job_id=job.id)
- if job.status in TERMINAL_STATUSES:
- break
- time.sleep(poll_interval_seconds)
- print(".", end="", flush=True)
- print()
- print(f"Final job status: `{job.status}`.")
-
- if job.status != JobStatus.SUCCEEDED:
- message = job.error.message if job.error is not None else ""
- raise RuntimeError(f"Job `{job.id}` ended with status `{job.status}`: {message}")
+ print("Create a fine-tuning data generation job and wait for it to complete.")
+ job_result = project_client.beta.datasets.begin_create_generation_job(
+ job=job,
+ polling_interval=poll_interval_seconds,
+ ).result()
# ------------------------------------------------------------------
# 3. Inspect the generated fine-tuning file outputs.
# ------------------------------------------------------------------
# `train_split=0.8` produces two Azure OpenAI files: a training partition
# and a validation partition. Both are emitted as FileDataGenerationJobOutput
- # entries in `job.result.outputs`.
- file_outputs = [
- output
- for output in ((job.result.outputs if job.result is not None else None) or [])
- if isinstance(output, FileDataGenerationJobOutput)
- ]
+ # entries in `job_result.outputs`.
+ file_outputs = [output for output in (job_result.outputs or []) if isinstance(output, FileDataGenerationJobOutput)]
if not file_outputs:
- raise RuntimeError(f"Job `{job.id}` did not produce any file outputs.")
+ raise RuntimeError("The data generation job did not produce any file outputs.")
print(f"Generated {len(file_outputs)} fine-tuning file(s):")
for output in file_outputs:
if not output.id:
- raise RuntimeError(f"Job `{job.id}` returned a file output without an id.")
+ raise RuntimeError("A file output was returned without an id.")
# Resolve the Azure OpenAI file to surface its real filename and size.
file_info = openai_client.files.retrieve(file_id=output.id)
print(f" - filename=`{file_info.filename}` id=`{output.id}` bytes={file_info.bytes}")
- if job.result is not None and job.result.generated_samples is not None:
- print(f"Generated samples: {job.result.generated_samples}")
+ if job_result.generated_samples is not None:
+ print(f"Generated samples: {job_result.generated_samples}")
# ------------------------------------------------------------------
# 4. Clean up.
@@ -221,6 +203,3 @@
print(f"Delete the Azure OpenAI input file `{seed_file.id}`.")
openai_client.files.delete(file_id=seed_file.id)
-
- print(f"Delete the data generation job `{job.id}`.")
- project_client.beta.datasets.delete_generation_job(job_id=job.id)
diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_agent_source.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_agent_source.py
index 70063d32da26..984e7041f228 100644
--- a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_agent_source.py
+++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_agent_source.py
@@ -35,7 +35,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.2.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -53,7 +53,6 @@
"""
import os
-import time
import uuid
from datetime import datetime, timezone
@@ -70,7 +69,6 @@
DataGenerationModelOptions,
DatasetDataGenerationJobOutput,
DatasetVersion,
- JobStatus,
PromptAgentDefinition,
SimpleQnADataGenerationJobOptions,
)
@@ -114,7 +112,6 @@
agent_name = f"widgets-gizmos-support-{run_id}"
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
with (
DefaultAzureCredential() as credential,
@@ -161,48 +158,37 @@
output_options=DataGenerationJobOutputOptions(name=output_dataset_name),
),
)
- job = project_client.beta.datasets.create_generation_job(job=job)
- print(f"Created data generation job `{job.id}` (status: `{job.status}`).")
-
- print(f"Poll job `{job.id}` until it reaches a terminal state.", end="", flush=True)
- while True:
- job = project_client.beta.datasets.get_generation_job(job_id=job.id)
- if job.status in TERMINAL_STATUSES:
- break
- time.sleep(poll_interval_seconds)
- print(".", end="", flush=True)
- print()
- print(f"Final job status: `{job.status}`.")
-
- if job.status != JobStatus.SUCCEEDED:
- message = job.error.message if job.error is not None else ""
- raise RuntimeError(f"Job `{job.id}` ended with status `{job.status}`: {message}")
+ print("Creating data generation job and waiting for completion (polling is handled by the SDK)...")
+ job_result = project_client.beta.datasets.begin_create_generation_job(
+ job=job,
+ polling_interval=poll_interval_seconds,
+ ).result()
# Locate the Dataset output produced by the job.
output_name: str = ""
output_version: str = ""
- for output in (job.result.outputs if job.result is not None else None) or []:
+ for output in job_result.outputs or []:
if isinstance(output, DatasetDataGenerationJobOutput):
output_name = output.name or ""
output_version = output.version or ""
break
if not output_name or not output_version:
- raise RuntimeError(f"Job `{job.id}` did not produce a dataset output.")
+ raise RuntimeError("The data generation job did not produce a dataset output.")
dataset: DatasetVersion = project_client.datasets.get(name=output_name, version=output_version)
print(f"Generated dataset: name=`{dataset.name}` version=`{dataset.version}` id=`{dataset.id}`")
- if job.result is not None and job.result.generated_samples is not None:
- print(f"Generated samples: {job.result.generated_samples}")
+ if job_result.generated_samples is not None:
+ print(f"Generated samples: {job_result.generated_samples}")
# ------------------------------------------------------------------
- # 3. Clean up the generated dataset and the data generation job
+ # 3. Clean up the generated dataset
# (the agent is deleted in the `finally` block below).
# ------------------------------------------------------------------
print(f"Delete the generated dataset `{dataset.name}` v{dataset.version}.")
project_client.datasets.delete(name=dataset.name or "", version=dataset.version or "")
- print(f"Delete the data generation job `{job.id}`.")
- project_client.beta.datasets.delete_generation_job(job_id=job.id)
+ # Note: The data generation job is implicitly cleaned up by the service
+ # when the dataset is deleted (cascade delete).
finally:
# The agent is short-lived — always delete it, even if the job failed.
print(f"Delete the prompt agent `{agent.name}` (version {agent.version}).")
diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_file_source.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_file_source.py
index f52d331e2603..44ee1e5b29fa 100644
--- a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_file_source.py
+++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_file_source.py
@@ -31,7 +31,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.2.0" azure-identity openai python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity openai python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -66,7 +66,6 @@
DatasetDataGenerationJobOutput,
DatasetVersion,
FileDataGenerationJobSource,
- JobStatus,
PromptDataGenerationJobSource,
SimpleQnADataGenerationJobOptions,
)
@@ -119,8 +118,6 @@
EXPECTED_OUTPUT_DESCRIPTION = "Expert-level QnA pairs generated from the Widgets & Gizmos reference."
EXPECTED_OUTPUT_TAGS = {"sample": "dataset-generation-simpleqna-with-file-source", "difficulty": "expert"}
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
@@ -156,61 +153,50 @@
# - The File source contributes the source material (the reference
# document uploaded above).
# - The Prompt source contributes a steering instruction (difficulty).
- print("Create a multi-source data generation job (File + Prompt).")
- job = DataGenerationJob(
- inputs=DataGenerationJobInputs(
- name=f"simpleqna-multisource-{run_id}",
- scenario=DataGenerationJobScenario.EVALUATION,
- sources=[
- FileDataGenerationJobSource(
- description="Widgets & Gizmos product / operations reference (Azure OpenAI file).",
- id=seed_file.id,
+ print(
+ "Creating multi-source data generation job (File + Prompt) and waiting for completion (polling is handled by the SDK)..."
+ )
+ job_result = project_client.beta.datasets.begin_create_generation_job(
+ job=DataGenerationJob(
+ inputs=DataGenerationJobInputs(
+ name=f"simpleqna-multisource-{run_id}",
+ scenario=DataGenerationJobScenario.EVALUATION,
+ sources=[
+ FileDataGenerationJobSource(
+ description="Widgets & Gizmos product / operations reference (Azure OpenAI file).",
+ id=seed_file.id,
+ ),
+ PromptDataGenerationJobSource(
+ description="Specifies the question difficulty for SimpleQnA generation.",
+ prompt="Generate expert-level questions of high difficulty.",
+ ),
+ ],
+ options=SimpleQnADataGenerationJobOptions(
+ # Service requires max_samples to be between 15 and 1000.
+ max_samples=15,
+ # `simple_qna` REQUIRES model_options.
+ model_options=DataGenerationModelOptions(model=model_name),
),
- PromptDataGenerationJobSource(
- description="Specifies the question difficulty for SimpleQnA generation.",
- prompt="Generate expert-level questions of high difficulty.",
+ output_options=DataGenerationJobOutputOptions(
+ name=output_dataset_name,
+ description=EXPECTED_OUTPUT_DESCRIPTION,
+ tags=EXPECTED_OUTPUT_TAGS,
),
- ],
- options=SimpleQnADataGenerationJobOptions(
- # Service requires max_samples to be between 15 and 1000.
- max_samples=15,
- # `simple_qna` REQUIRES model_options.
- model_options=DataGenerationModelOptions(model=model_name),
- ),
- output_options=DataGenerationJobOutputOptions(
- name=output_dataset_name,
- description=EXPECTED_OUTPUT_DESCRIPTION,
- tags=EXPECTED_OUTPUT_TAGS,
),
),
- )
- job = project_client.beta.datasets.create_generation_job(job=job)
- print(f"Created data generation job `{job.id}` (status: `{job.status}`).")
-
- print(f"Poll job `{job.id}` until it reaches a terminal state.", end="", flush=True)
- while True:
- job = project_client.beta.datasets.get_generation_job(job_id=job.id)
- if job.status in TERMINAL_STATUSES:
- break
- time.sleep(poll_interval_seconds)
- print(".", end="", flush=True)
- print()
- print(f"Final job status: `{job.status}`.")
-
- if job.status != JobStatus.SUCCEEDED:
- message = job.error.message if job.error is not None else ""
- raise RuntimeError(f"Job `{job.id}` ended with status `{job.status}`: {message}")
+ polling_interval=poll_interval_seconds,
+ ).result()
# Locate the Dataset output produced by the job.
output_name: str = ""
output_version: str = ""
- for output in (job.result.outputs if job.result is not None else None) or []:
+ for output in job_result.outputs or []:
if isinstance(output, DatasetDataGenerationJobOutput):
output_name = output.name or ""
output_version = output.version or ""
break
if not output_name or not output_version:
- raise RuntimeError(f"Job `{job.id}` did not produce a dataset output.")
+ raise RuntimeError("The data generation job did not produce a dataset output.")
# ------------------------------------------------------------------
# 3. Inspect the generated dataset and show metadata propagation.
@@ -222,8 +208,8 @@
print(f"Generated dataset: name=`{dataset.name}` version=`{dataset.version}` id=`{dataset.id}`")
print(f" description: {dataset.description}")
print(f" tags: {dataset.tags}")
- if job.result is not None and job.result.generated_samples is not None:
- print(f"Generated samples: {job.result.generated_samples}")
+ if job_result.generated_samples is not None:
+ print(f"Generated samples: {job_result.generated_samples}")
# ------------------------------------------------------------------
# 4. Clean up.
@@ -234,5 +220,5 @@
print(f"Delete the Azure OpenAI input file `{seed_file.id}`.")
openai_client.files.delete(file_id=seed_file.id)
- print(f"Delete the data generation job `{job.id}`.")
- project_client.beta.datasets.delete_generation_job(job_id=job.id)
+ # Note: The data generation job is implicitly cleaned up by the service
+ # when the dataset is deleted (cascade delete).
diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_prompt_source.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_prompt_source.py
index 0230217a1d21..7db76b290cac 100644
--- a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_prompt_source.py
+++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_simpleqna_with_prompt_source.py
@@ -11,20 +11,21 @@
1. Creates a `DataGenerationJob` (scenario=EVALUATION, type=simple_qna) that
synthesizes question/answer pairs from an inline prompt and writes them
- to a new versioned Dataset.
- 2. Polls the job to completion and resolves the resulting `DatasetVersion`.
+ to a new versioned Dataset. Uses `begin_create_generation_job` which returns
+ `LROPoller[DataGenerationJobResult]`; `.result()` polls automatically.
+ 2. Resolves the resulting `DatasetVersion` from the job result.
3. Creates an OpenAI evaluation (`client.evals.create`) with builtin
Azure AI evaluators.
4. Runs the evaluation against the generated dataset by passing the
dataset's id as the run's `file_id`.
- 5. Cleans up the evaluation, the generated dataset, and the data generation job.
+ 5. Cleans up the evaluation and the generated dataset.
USAGE:
python sample_dataset_generation_job_simpleqna_with_prompt_source.py
Before running the sample:
- pip install "azure-ai-projects>=2.2.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -42,6 +43,7 @@
import os
import time
+from typing import Union
from dotenv import load_dotenv
from openai.types.eval_create_params import DataSourceConfigCustom
@@ -51,6 +53,8 @@
InputMessagesTemplateTemplateEvalItem,
SourceFileID,
)
+from openai.types.evals.run_create_response import RunCreateResponse
+from openai.types.evals.run_retrieve_response import RunRetrieveResponse
from openai.types.responses.response_input_text_param import ResponseInputTextParam
from azure.identity import DefaultAzureCredential
@@ -63,7 +67,6 @@
DataGenerationModelOptions,
DatasetDataGenerationJobOutput,
DatasetVersion,
- JobStatus,
PromptDataGenerationJobSource,
SimpleQnADataGenerationJobOptions,
TestingCriterionAzureAIEvaluator,
@@ -76,8 +79,6 @@
dataset_name = os.environ.get("DATASET_NAME", "dataset-generation-eval-sample")
poll_interval_seconds = int(os.environ.get("POLL_INTERVAL_SECONDS", "10"))
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
def main() -> None:
with (
@@ -89,7 +90,6 @@ def main() -> None:
# ------------------------------------------------------------------
# 1. Generate a QnA evaluation dataset from an inline prompt.
# ------------------------------------------------------------------
- print("Create a data generation job.")
job = DataGenerationJob(
inputs=DataGenerationJobInputs(
name="qna-from-policy-prompt",
@@ -117,33 +117,22 @@ def main() -> None:
),
),
)
- job = project_client.beta.datasets.create_generation_job(job=job)
- print(f"Created data generation job `{job.id}` (status: `{job.status}`).")
-
- print(f"Poll job `{job.id}` until it reaches a terminal state.", end="", flush=True)
- while True:
- job = project_client.beta.datasets.get_generation_job(job_id=job.id)
- if job.status in TERMINAL_STATUSES:
- break
- time.sleep(poll_interval_seconds)
- print(".", end="", flush=True)
- print()
- print(f"Final job status: `{job.status}`.")
-
- if job.status != JobStatus.SUCCEEDED:
- message = job.error.message if job.error is not None else ""
- raise RuntimeError(f"Job `{job.id}` ended with status `{job.status}`: {message}")
+ print("Creating data generation job and waiting for completion (polling is handled by the SDK)...")
+ job_result = project_client.beta.datasets.begin_create_generation_job(
+ job=job,
+ polling_interval=poll_interval_seconds,
+ ).result()
# Locate the Dataset output produced by the job.
output_name: str = ""
output_version: str = ""
- for output in (job.result.outputs if job.result is not None else None) or []:
+ for output in job_result.outputs or []:
if isinstance(output, DatasetDataGenerationJobOutput):
output_name = output.name or ""
output_version = output.version or ""
break
if not output_name or not output_version:
- raise RuntimeError(f"Job `{job.id}` did not produce a dataset output.")
+ raise RuntimeError("The data generation job did not produce a dataset output.")
# Resolve the DatasetVersion so we can use its id as the eval run's file_id.
dataset: DatasetVersion = project_client.datasets.get(name=output_name, version=output_version)
@@ -231,7 +220,7 @@ def main() -> None:
input_messages=input_message,
model=model_name,
)
- eval_run = openai_client.evals.runs.create(
+ eval_run: Union[RunCreateResponse, RunRetrieveResponse] = openai_client.evals.runs.create(
eval_id=eval_object.id,
name="generated-qna-evaluation-run",
data_source=data_source,
@@ -270,9 +259,6 @@ def main() -> None:
print(f"Delete the generated dataset `{dataset.name}` v{dataset.version}.")
project_client.datasets.delete(name=dataset.name or "", version=dataset.version or "")
- print(f"Delete the data generation job `{job.id}`.")
- project_client.beta.datasets.delete_generation_job(job_id=job.id)
-
if __name__ == "__main__":
main()
diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_traces_for_evaluation.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_traces_for_evaluation.py
index 1b79603e559d..18a5d59582b1 100644
--- a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_traces_for_evaluation.py
+++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_traces_for_evaluation.py
@@ -28,7 +28,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.2.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as
@@ -54,7 +54,6 @@
DataGenerationJobScenario,
DatasetDataGenerationJobOutput,
DatasetVersion,
- JobStatus,
PromptAgentDefinition,
TracesDataGenerationJobOptions,
TracesDataGenerationJobSource,
@@ -93,9 +92,6 @@
output_dataset_name = f"{DATASET_NAME}-{run_id}"
agent_name = f"{DATASET_NAME}-{run_id}"
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
-
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
@@ -131,9 +127,6 @@
print(f"Wait {INITIAL_INGEST_WAIT_SECONDS}s for Application Insights to ingest the spans.", flush=True)
time.sleep(INITIAL_INGEST_WAIT_SECONDS)
- # 2. Submit a data generation job that reads the agent's traces (retry
- # in case ingestion is still in flight). Small backoff so the seeded
- # spans fall inside the queried window.
start_time = seed_start - timedelta(minutes=5)
job = None
@@ -144,60 +137,50 @@
f"(attempt {attempt}/{MAX_JOB_ATTEMPTS}, "
f"window: {start_time.isoformat()} .. {end_time.isoformat()})."
)
- job = project_client.beta.datasets.create_generation_job(
- job=DataGenerationJob(
- inputs=DataGenerationJobInputs(
- name=f"traces-eval-{run_id}-a{attempt}",
- scenario=DataGenerationJobScenario.EVALUATION,
- sources=[
- TracesDataGenerationJobSource(
- description="Application Insights conversation traces for the agent.",
- agent_name=agent_name,
- start_time=start_time,
- end_time=end_time,
- ),
- ],
- # max_samples must be in [15, 1000]; caps output dataset size.
- options=TracesDataGenerationJobOptions(max_samples=15),
- output_options=DataGenerationJobOutputOptions(name=output_dataset_name),
+ try:
+ job = project_client.beta.datasets.begin_create_generation_job(
+ job=DataGenerationJob(
+ inputs=DataGenerationJobInputs(
+ name=f"traces-eval-{run_id}-a{attempt}",
+ scenario=DataGenerationJobScenario.EVALUATION,
+ sources=[
+ TracesDataGenerationJobSource(
+ description="Application Insights conversation traces for the agent.",
+ agent_name=agent_name,
+ start_time=start_time,
+ end_time=end_time,
+ ),
+ ],
+ # max_samples must be in [15, 1000]; caps output dataset size.
+ options=TracesDataGenerationJobOptions(max_samples=15),
+ output_options=DataGenerationJobOutputOptions(name=output_dataset_name),
+ ),
),
- ),
- )
- submitted_job_ids.append(job.id)
- print(f"Created data generation job `{job.id}` (status: `{job.status}`).")
-
- print(f"Poll job `{job.id}` until it reaches a terminal state.", end="", flush=True)
- while job.status not in TERMINAL_STATUSES:
- time.sleep(POLL_INTERVAL_SECONDS)
- print(".", end="", flush=True)
- job = project_client.beta.datasets.get_generation_job(job_id=job.id)
- print()
- print(f"Final job status: `{job.status}`.")
-
- if job.status == JobStatus.SUCCEEDED:
+ polling_interval=POLL_INTERVAL_SECONDS,
+ ).result()
+ print(f"Data generation job succeeded.")
break
-
- message = job.error.message if job.error is not None else ""
- if attempt == MAX_JOB_ATTEMPTS:
- raise RuntimeError(f"Job `{job.id}` failed after {MAX_JOB_ATTEMPTS} attempts: {message}")
- print(f" Attempt {attempt} failed ({message}); wait {RETRY_WAIT_SECONDS}s and retry.")
- time.sleep(RETRY_WAIT_SECONDS)
-
- assert job is not None # for type-checker; loop guarantees success path sets job
+ except Exception as e: # pylint: disable=broad-except
+ if attempt == MAX_JOB_ATTEMPTS:
+ raise RuntimeError(f"Job failed after {MAX_JOB_ATTEMPTS} attempts: {e}")
+ print(f" Attempt {attempt} failed ({e}); wait {RETRY_WAIT_SECONDS}s and retry.")
+ time.sleep(RETRY_WAIT_SECONDS)
# 3. Resolve the generated dataset.
- outputs = (job.result.outputs if job.result is not None else None) or []
+ if job is None:
+ raise RuntimeError("The data generation job did not return a result.")
+ outputs = job.outputs or []
dataset_output = next((o for o in outputs if isinstance(o, DatasetDataGenerationJobOutput)), None)
if dataset_output is None or not dataset_output.name or not dataset_output.version:
- raise RuntimeError(f"Job `{job.id}` did not produce a dataset output.")
+ raise RuntimeError("The data generation job did not produce a dataset output.")
created_dataset = project_client.datasets.get(name=dataset_output.name, version=dataset_output.version)
print(
f"Generated dataset: name=`{created_dataset.name}` "
f"version=`{created_dataset.version}` id=`{created_dataset.id}`"
)
- if job.result is not None and job.result.generated_samples is not None:
- print(f"Generated samples: {job.result.generated_samples}")
+ if job.generated_samples is not None:
+ print(f"Generated samples: {job.generated_samples}")
finally:
# Best-effort cleanup, outputs -> producers (dataset, job, conversations, agent).
@@ -211,12 +194,9 @@
except Exception as exc: # pylint: disable=broad-exception-caught
print(f" (warning) could not delete dataset: {exc}")
- for jid in submitted_job_ids:
- try:
- project_client.beta.datasets.delete_generation_job(job_id=jid)
- print(f"Deleted data generation job `{jid}`.")
- except Exception as exc: # pylint: disable=broad-exception-caught
- print(f" (warning) could not delete job `{jid}`: {exc}")
+ # Note: The data generation jobs are implicitly cleaned up by the service
+ # when the dataset is deleted (cascade delete). Attempting explicit deletion
+ # is not supported for LRO-based jobs.
if created_conversation_ids:
for cid in created_conversation_ids:
diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_traces_for_finetuning.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_traces_for_finetuning.py
index beb48c225d94..114e6c99fa35 100644
--- a/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_traces_for_finetuning.py
+++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_dataset_generation_job_traces_for_finetuning.py
@@ -28,7 +28,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.2.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as
@@ -53,7 +53,6 @@
DataGenerationJobOutputOptions,
DataGenerationJobScenario,
FileDataGenerationJobOutput,
- JobStatus,
PromptAgentDefinition,
TracesDataGenerationJobOptions,
TracesDataGenerationJobSource,
@@ -95,9 +94,6 @@
output_name = f"{DATASET_NAME}-{run_id}"
agent_name = f"{DATASET_NAME}-{run_id}"
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
-
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
@@ -106,7 +102,6 @@
created_agent = None
created_conversation_ids: List[str] = []
- submitted_job_ids: List[str] = []
created_file_ids: List[str] = []
try:
@@ -133,9 +128,6 @@
print(f"Wait {INITIAL_INGEST_WAIT_SECONDS}s for Application Insights to ingest the spans.", flush=True)
time.sleep(INITIAL_INGEST_WAIT_SECONDS)
- # 2. Submit a fine-tuning data generation job that reads the agent's
- # traces (retry in case ingestion is still in flight). Small backoff so
- # the seeded spans fall inside the queried window.
start_time = seed_start - timedelta(minutes=5)
job = None
@@ -146,64 +138,54 @@
f"(attempt {attempt}/{MAX_JOB_ATTEMPTS}, "
f"window: {start_time.isoformat()} .. {end_time.isoformat()})."
)
- job = project_client.beta.datasets.create_generation_job(
- job=DataGenerationJob(
- inputs=DataGenerationJobInputs(
- name=f"traces-ft-{run_id}-a{attempt}",
- scenario=DataGenerationJobScenario.SUPERVISED_FINETUNING,
- sources=[
- TracesDataGenerationJobSource(
- description="Application Insights conversation traces for the agent.",
- agent_name=agent_name,
- start_time=start_time,
- end_time=end_time,
- ),
- ],
- # max_samples must be in [15, 1000]; caps output dataset size.
- # train_split=0.8 splits generated samples into a training
- # and a validation Azure OpenAI file.
- options=TracesDataGenerationJobOptions(max_samples=15, train_split=0.8),
- output_options=DataGenerationJobOutputOptions(name=output_name),
+ try:
+ job = project_client.beta.datasets.begin_create_generation_job(
+ job=DataGenerationJob(
+ inputs=DataGenerationJobInputs(
+ name=f"traces-ft-{run_id}-a{attempt}",
+ scenario=DataGenerationJobScenario.SUPERVISED_FINETUNING,
+ sources=[
+ TracesDataGenerationJobSource(
+ description="Application Insights conversation traces for the agent.",
+ agent_name=agent_name,
+ start_time=start_time,
+ end_time=end_time,
+ ),
+ ],
+ # max_samples must be in [15, 1000]; caps output dataset size.
+ # train_split=0.8 splits generated samples into a training
+ # and a validation Azure OpenAI file.
+ options=TracesDataGenerationJobOptions(max_samples=15, train_split=0.8),
+ output_options=DataGenerationJobOutputOptions(name=output_name),
+ ),
),
- ),
- )
- submitted_job_ids.append(job.id)
- print(f"Created data generation job `{job.id}` (status: `{job.status}`).")
-
- print(f"Poll job `{job.id}` until it reaches a terminal state.", end="", flush=True)
- while job.status not in TERMINAL_STATUSES:
- time.sleep(POLL_INTERVAL_SECONDS)
- print(".", end="", flush=True)
- job = project_client.beta.datasets.get_generation_job(job_id=job.id)
- print()
- print(f"Final job status: `{job.status}`.")
-
- if job.status == JobStatus.SUCCEEDED:
+ polling_interval=POLL_INTERVAL_SECONDS,
+ ).result()
+ print(f"Data generation job succeeded.")
break
-
- message = job.error.message if job.error is not None else ""
- if attempt == MAX_JOB_ATTEMPTS:
- raise RuntimeError(f"Job `{job.id}` failed after {MAX_JOB_ATTEMPTS} attempts: {message}")
- print(f" Attempt {attempt} failed ({message}); wait {RETRY_WAIT_SECONDS}s and retry.")
- time.sleep(RETRY_WAIT_SECONDS)
-
- assert job is not None # for type-checker; loop guarantees success path sets job
+ except Exception as e: # pylint: disable=broad-except
+ if attempt == MAX_JOB_ATTEMPTS:
+ raise RuntimeError(f"Job failed after {MAX_JOB_ATTEMPTS} attempts: {e}")
+ print(f" Attempt {attempt} failed ({e}); wait {RETRY_WAIT_SECONDS}s and retry.")
+ time.sleep(RETRY_WAIT_SECONDS)
# 3. Resolve generated fine-tuning files.
- outputs = (job.result.outputs if job.result is not None else None) or []
+ if job is None:
+ raise RuntimeError("The data generation job did not return a result.")
+ outputs = job.outputs or []
file_outputs = [o for o in outputs if isinstance(o, FileDataGenerationJobOutput)]
if not file_outputs:
- raise RuntimeError(f"Job `{job.id}` did not produce any file outputs.")
+ raise RuntimeError("The data generation job did not produce any file outputs.")
print(f"Generated {len(file_outputs)} fine-tuning file(s):")
for output in file_outputs:
if not output.id:
- raise RuntimeError(f"Job `{job.id}` returned a file output without an id.")
+ raise RuntimeError("A file output was returned without an id.")
created_file_ids.append(output.id)
file_info = openai_client.files.retrieve(file_id=output.id)
print(f" - filename=`{file_info.filename}` id=`{output.id}` bytes={file_info.bytes}")
- if job.result is not None and job.result.generated_samples is not None:
- print(f"Generated samples: {job.result.generated_samples}")
+ if job.generated_samples is not None:
+ print(f"Generated samples: {job.generated_samples}")
finally:
# Best-effort cleanup, outputs -> producers (files, job, conversations, agent).
@@ -215,12 +197,9 @@
except Exception as exc: # pylint: disable=broad-exception-caught
print(f" (warning) could not delete file `{fid}`: {exc}")
- for jid in submitted_job_ids:
- try:
- project_client.beta.datasets.delete_generation_job(job_id=jid)
- print(f"Deleted data generation job `{jid}`.")
- except Exception as exc: # pylint: disable=broad-exception-caught
- print(f" (warning) could not delete job `{jid}`: {exc}")
+ # Note: The data generation jobs are implicitly cleaned up by the service
+ # when the files are deleted (cascade delete). Attempting explicit deletion
+ # is not supported for LRO-based jobs.
if created_conversation_ids:
for cid in created_conversation_ids:
diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py
index 6eed731eb0e6..c5921531f15d 100644
--- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py
+++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py
@@ -153,10 +153,11 @@
pprint(prompt_evaluator_latest)
print("Updating code based evaluator version")
- updated_evaluator = project_client.beta.evaluators.update_version(
+ # TODO: Remove this suppression once TypeSpec typing for EvaluatorVersion is fixed.
+ updated_evaluator = project_client.beta.evaluators.update_version( # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
name=code_evaluator.name,
version=code_evaluator.version,
- evaluator_version={
+ evaluator_version={ # pyright: ignore[reportArgumentType]
"categories": [EvaluatorCategory.SAFETY],
"display_name": "my_custom_evaluator_updated",
"description": "Custom evaluator description changed",
diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py
index f1ffe187b7f0..15fc03eaa7d7 100644
--- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py
+++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py
@@ -50,9 +50,10 @@
):
print("Creating a single evaluator version - Code based (json style)")
- code_evaluator = project_client.beta.evaluators.create_version(
+ # TODO: Remove this suppression once TypeSpec typing for EvaluatorVersion is fixed.
+ code_evaluator = project_client.beta.evaluators.create_version( # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
name="my_custom_evaluator_code",
- evaluator_version={
+ evaluator_version={ # pyright: ignore[reportArgumentType]
"name": "my_custom_evaluator_code",
"categories": [EvaluatorCategory.QUALITY],
"display_name": "my_custom_evaluator_code",
diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py
index 77efc2d9c11e..ac3a403c7225 100644
--- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py
+++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py
@@ -83,9 +83,10 @@
):
print("Creating a single evaluator version - Prompt based (json style)")
- prompt_evaluator = project_client.beta.evaluators.create_version(
+ # TODO: Remove this suppression once TypeSpec typing for EvaluatorVersion is fixed.
+ prompt_evaluator = project_client.beta.evaluators.create_version( # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
name="my_custom_evaluator_prompt",
- evaluator_version={
+ evaluator_version={ # pyright: ignore[reportArgumentType]
"name": "my_custom_evaluator_prompt",
"categories": [EvaluatorCategory.QUALITY],
"display_name": "my_custom_evaluator_prompt",
diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_all_sources.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_all_sources.py
index 09070104956a..99e4b7496962 100644
--- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_all_sources.py
+++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_all_sources.py
@@ -28,7 +28,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.2.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -48,10 +48,9 @@
"""
import os
-import time
import uuid
from datetime import datetime, timedelta, timezone
-from typing import List, cast
+from typing import List
from dotenv import load_dotenv
@@ -63,7 +62,6 @@
EvaluatorGenerationInputs,
EvaluatorGenerationJob,
EvaluatorGenerationJobSource,
- JobStatus,
PromptEvaluatorGenerationJobSource,
RubricBasedEvaluatorDefinition,
TracesEvaluatorGenerationJobSource,
@@ -85,8 +83,6 @@
multi_name = f"multi-source-{ts}-{short}"
traces_name = f"traces-source-{ts}-{short}"
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
multi_evaluator_version = ""
traces_evaluator_version = ""
@@ -128,31 +124,22 @@
else:
print("Skipping Dataset source (FOUNDRY_REFERENCE_DATASET_NAME / _VERSION not set).")
- multi_job = project_client.beta.evaluators.create_generation_job(
- job=EvaluatorGenerationJob(
- inputs=EvaluatorGenerationInputs(
- model=model_name,
- evaluator_name=multi_name,
- evaluator_display_name="Customer Support Quality (multi-source)",
- evaluator_description="Generated from prompt, agent, and dataset signals.",
- sources=multi_sources,
+ print("Waiting for multi-source job to complete (polling is handled by the SDK)...")
+ try:
+ evaluator = project_client.beta.evaluators.begin_create_generation_job(
+ job=EvaluatorGenerationJob(
+ inputs=EvaluatorGenerationInputs(
+ model=model_name,
+ evaluator_name=multi_name,
+ evaluator_display_name="Customer Support Quality (multi-source)",
+ evaluator_description="Generated from prompt, agent, and dataset signals.",
+ sources=multi_sources,
+ ),
),
- ),
- operation_id=f"rubric-multi-{short}",
- )
-
- print(f"Waiting for multi-source job `{multi_job.id}` to complete...")
- while multi_job.status not in TERMINAL_STATUSES:
- time.sleep(poll_interval_seconds)
- multi_job = project_client.beta.evaluators.get_generation_job(multi_job.id)
-
- if multi_job.status != JobStatus.SUCCEEDED:
- message = multi_job.error.message if multi_job.error is not None else ""
- print(f"Multi-source job ended with status `{cast(JobStatus, multi_job.status).value}`: {message}")
- else:
+ operation_id=f"rubric-multi-{short}",
+ polling_interval=poll_interval_seconds,
+ ).result()
# `isinstance` narrows the discriminated `definition` to the rubric subtype.
- evaluator = multi_job.result
- assert evaluator is not None
definition = evaluator.definition
assert isinstance(definition, RubricBasedEvaluatorDefinition)
multi_evaluator_version = evaluator.version or ""
@@ -160,6 +147,8 @@
f"Multi-source evaluator `{evaluator.name}` v{evaluator.version}: "
f"{len(definition.dimensions)} dimensions."
)
+ except Exception as e: # pylint: disable=broad-except
+ print(f"Multi-source job failed: {e}")
# 2. Separate `traces` + Agent companion generation job.
# The traces source requires a companion source because the service rejects
@@ -171,41 +160,33 @@
start_time = now - timedelta(days=traces_window_days)
end_time = now + timedelta(seconds=600) # small padding for clock skew
- traces_job = project_client.beta.evaluators.create_generation_job(
- job=EvaluatorGenerationJob(
- inputs=EvaluatorGenerationInputs(
- model=model_name,
- evaluator_name=traces_name,
- evaluator_display_name="Customer Support Quality (from traces)",
- evaluator_description="Generated from real Application Insights conversation traces.",
- sources=[
- TracesEvaluatorGenerationJobSource(
- description="Application Insights conversation traces for the agent.",
- agent_name=agent_name,
- start_time=start_time,
- end_time=end_time,
- ),
- AgentEvaluatorGenerationJobSource(
- description="Companion source (service rejects traces-only).",
- agent_name=agent_name,
- ),
- ],
+ print("Waiting for traces job to complete (polling is handled by the SDK)...")
+ try:
+ evaluator = project_client.beta.evaluators.begin_create_generation_job(
+ job=EvaluatorGenerationJob(
+ inputs=EvaluatorGenerationInputs(
+ model=model_name,
+ evaluator_name=traces_name,
+ evaluator_display_name="Customer Support Quality (from traces)",
+ evaluator_description="Generated from real Application Insights conversation traces.",
+ sources=[
+ TracesEvaluatorGenerationJobSource(
+ description="Application Insights conversation traces for the agent.",
+ agent_name=agent_name,
+ start_time=start_time,
+ end_time=end_time,
+ ),
+ AgentEvaluatorGenerationJobSource(
+ description="Companion source (service rejects traces-only).",
+ agent_name=agent_name,
+ ),
+ ],
+ ),
),
- ),
- operation_id=f"rubric-traces-{short}",
- )
-
- print(f"Waiting for traces job `{traces_job.id}` to complete...")
- while traces_job.status not in TERMINAL_STATUSES:
- time.sleep(poll_interval_seconds)
- traces_job = project_client.beta.evaluators.get_generation_job(traces_job.id)
-
- if traces_job.status != JobStatus.SUCCEEDED:
- message = traces_job.error.message if traces_job.error is not None else ""
- print(f"Traces job ended with status `{cast(JobStatus, traces_job.status).value}`: {message}")
- else:
- evaluator = traces_job.result
- assert evaluator is not None
+ operation_id=f"rubric-traces-{short}",
+ polling_interval=poll_interval_seconds,
+ ).result()
+ # `isinstance` narrows the discriminated `definition` to the rubric subtype.
definition = evaluator.definition
assert isinstance(definition, RubricBasedEvaluatorDefinition)
traces_evaluator_version = evaluator.version or ""
@@ -213,6 +194,8 @@
f"Traces evaluator `{evaluator.name}` v{evaluator.version}: "
f"{len(definition.dimensions)} dimensions."
)
+ except Exception as e: # pylint: disable=broad-except
+ print(f"Traces job failed: {e}")
# 3. Clean up. `delete_version` cascades to delete the generation job record.
print("Cleaning up.")
diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_basic.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_basic.py
index 98b97a79e171..4375842e523f 100644
--- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_basic.py
+++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_basic.py
@@ -13,8 +13,8 @@
1. Creates an `EvaluatorGenerationJob` whose only source is an inline
natural-language description of the application's purpose, capabilities,
and tools. The service synthesizes a rubric tailored to that application.
- 2. Polls the generation job to completion and resolves the generated
- `EvaluatorVersion`.
+ 2. Calls `begin_create_generation_job` which returns an `LROPoller[EvaluatorVersion]`;
+ `.result()` polls automatically and returns the generated `EvaluatorVersion`.
3. Creates an OpenAI evaluation referencing the generated evaluator as a
testing criterion.
4. Runs the evaluation against inline JSONL sample data.
@@ -30,7 +30,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.2.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -47,8 +47,7 @@
import time
import uuid
from datetime import datetime, timezone
-from typing import cast
-
+from typing import Union
from dotenv import load_dotenv
from openai.types.eval_create_params import DataSourceConfigCustom
from openai.types.evals.create_eval_jsonl_run_data_source_param import (
@@ -56,13 +55,14 @@
SourceFileContent,
SourceFileContentContent,
)
+from openai.types.evals.run_create_response import RunCreateResponse
+from openai.types.evals.run_retrieve_response import RunRetrieveResponse
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
EvaluatorGenerationInputs,
EvaluatorGenerationJob,
- JobStatus,
PromptEvaluatorGenerationJobSource,
RubricBasedEvaluatorDefinition,
TestingCriterionAzureAIEvaluator,
@@ -79,7 +79,6 @@
short = uuid.uuid4().hex[:6]
evaluator_name = f"reservation-quality-generated-{ts}-{short}"
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
TERMINAL_RUN_STATUSES = {"completed", "failed", "canceled"}
with (
@@ -88,7 +87,10 @@
project_client.get_openai_client() as openai_client,
):
# 1. Generate an evaluator from a single `Prompt` source.
- job = project_client.beta.evaluators.create_generation_job(
+ # The LRO polls automatically; `.result()` blocks until the job reaches a terminal state
+ # and returns the produced EvaluatorVersion directly.
+ print("Waiting for generation job to complete (polling is handled by the SDK)...")
+ evaluator = project_client.beta.evaluators.begin_create_generation_job(
job=EvaluatorGenerationJob(
inputs=EvaluatorGenerationInputs(
model=model_name,
@@ -114,25 +116,13 @@
],
),
),
- # `operation_id` makes the call idempotent - re-submitting the same id returns the existing job.
+ # `operation_id` makes the call idempotent - re-submitting the same id attaches to the existing job.
operation_id=f"rubric-eval-basic-{short}",
- )
- print(f"Created generation job `{job.id}`.")
-
- print(f"Waiting for job `{job.id}` to complete...")
- while job.status not in TERMINAL_STATUSES:
- time.sleep(poll_interval_seconds)
- job = project_client.beta.evaluators.get_generation_job(job.id)
- print(f"Job finished with status `{cast(JobStatus, job.status).value}`.")
-
- if job.status != JobStatus.SUCCEEDED:
- message = job.error.message if job.error is not None else ""
- raise RuntimeError(f"Generation job ended with status `{cast(JobStatus, job.status).value}`: {message}")
+ polling_interval=poll_interval_seconds,
+ ).result()
# On success, the evaluator is automatically saved as version 1.
# `isinstance` narrows the discriminated `definition` to the rubric subtype.
- evaluator = job.result
- assert evaluator is not None
definition = evaluator.definition
assert isinstance(definition, RubricBasedEvaluatorDefinition)
print(
@@ -171,7 +161,7 @@
)
# 3. Run the evaluation against inline JSONL sample data.
- eval_run = openai_client.evals.runs.create(
+ eval_run: Union[RunCreateResponse, RunRetrieveResponse] = openai_client.evals.runs.create(
eval_id=eval_object.id,
name=f"{evaluator.name}-run",
metadata={"sample": "rubric_evaluator_generation_basic"},
diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_iterate.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_iterate.py
index 5ab81435203d..0ca337d5bbfe 100644
--- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_iterate.py
+++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_iterate.py
@@ -25,7 +25,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.2.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -37,10 +37,8 @@
"""
import os
-import time
import uuid
from datetime import datetime, timezone
-from typing import cast
from dotenv import load_dotenv
@@ -51,7 +49,6 @@
EvaluatorDefinitionType,
EvaluatorGenerationInputs,
EvaluatorGenerationJob,
- JobStatus,
PromptEvaluatorGenerationJobSource,
RubricBasedEvaluatorDefinition,
)
@@ -67,14 +64,15 @@
short = uuid.uuid4().hex[:6]
evaluator_name = f"reservation-quality-iterate-{ts}-{short}"
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
):
# 1. Generate v1 of the evaluator from a single `Prompt` source.
- job = project_client.beta.evaluators.create_generation_job(
+ # The LRO polls automatically; `.result()` blocks until the job reaches a terminal state
+ # and returns the produced EvaluatorVersion directly.
+ print("Waiting for generation job to complete (polling is handled by the SDK)...")
+ v1 = project_client.beta.evaluators.begin_create_generation_job(
job=EvaluatorGenerationJob(
inputs=EvaluatorGenerationInputs(
model=model_name,
@@ -95,20 +93,10 @@
),
),
operation_id=f"rubric-iterate-{short}",
- )
-
- print(f"Waiting for job `{job.id}` to complete...")
- while job.status not in TERMINAL_STATUSES:
- time.sleep(poll_interval_seconds)
- job = project_client.beta.evaluators.get_generation_job(job.id)
-
- if job.status != JobStatus.SUCCEEDED:
- message = job.error.message if job.error is not None else ""
- raise RuntimeError(f"Generation job ended with status `{cast(JobStatus, job.status).value}`: {message}")
+ polling_interval=poll_interval_seconds,
+ ).result()
# `isinstance` narrows the discriminated `definition` to the rubric subtype.
- v1 = job.result
- assert v1 is not None
v1_definition = v1.definition
assert isinstance(v1_definition, RubricBasedEvaluatorDefinition)
print(
@@ -163,9 +151,10 @@
)
# 3. Save the edited definition as v2.
- v2 = project_client.beta.evaluators.create_version(
+ # TODO: Remove this suppression once TypeSpec typing for EvaluatorVersion is fixed.
+ v2 = project_client.beta.evaluators.create_version( # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
name=evaluator_name,
- evaluator_version={
+ evaluator_version={ # pyright: ignore[reportArgumentType]
"name": evaluator_name,
# Narrow each category to its enum value (the categories list is Union[str, EvaluatorCategory]).
"categories": [c.value if isinstance(c, EvaluatorCategory) else c for c in v1.categories],
diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_lifecycle.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_lifecycle.py
index e75d09e0d6df..d48a007729de 100644
--- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_lifecycle.py
+++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_generation_lifecycle.py
@@ -9,8 +9,9 @@
End-to-end scenario showing the lifecycle of rubric evaluator generation
jobs. The sample exercises:
- * `create_generation_job` with `operation_id` for idempotent re-submits.
- * `get_generation_job` to poll a single job to completion.
+ * `begin_create_generation_job` with `operation_id` for idempotent re-submits;
+ returns `LROPoller[EvaluatorVersion]` — the SDK polls automatically and
+ `.result()` blocks until the job reaches a terminal state.
* `list_generation_jobs` to enumerate recent jobs in the project.
* `delete_generation_job` to remove a finished job record.
* `delete_version` to remove the persisted evaluator that the job produced.
@@ -27,7 +28,7 @@
Before running the sample:
- pip install "azure-ai-projects>=2.2.0" azure-identity python-dotenv
+ pip install "azure-ai-projects>=2.4.0" azure-identity python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found
@@ -40,7 +41,6 @@
import os
import itertools
-import time
import uuid
from datetime import datetime, timezone
from typing import cast
@@ -53,6 +53,7 @@
from azure.ai.projects.models import (
EvaluatorGenerationInputs,
EvaluatorGenerationJob,
+ EvaluatorVersion,
JobStatus,
PageOrder,
PromptEvaluatorGenerationJobSource,
@@ -70,8 +71,6 @@
evaluator_name = f"lifecycle-demo-{ts}-{short}"
operation_id = f"rubric-lifecycle-{short}"
-TERMINAL_STATUSES = {JobStatus.SUCCEEDED, JobStatus.FAILED, JobStatus.CANCELLED}
-
# Shared job used both for the initial create and the idempotency replay.
job_body = EvaluatorGenerationJob(
inputs=EvaluatorGenerationInputs(
@@ -92,28 +91,30 @@
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
):
- # 1. Create the generation job. `operation_id` makes the call idempotent -
- # re-submitting with the same id returns the existing job.
- job = project_client.beta.evaluators.create_generation_job(job=job_body, operation_id=operation_id)
- print(f"Created generation job `{job.id}`.")
-
- replay = project_client.beta.evaluators.create_generation_job(job=job_body, operation_id=operation_id)
- assert replay.id == job.id # idempotent replay returns the same job
-
- # 2. Poll the job to completion.
- print(f"Waiting for job `{job.id}` to complete...")
- while job.status not in TERMINAL_STATUSES:
- time.sleep(poll_interval_seconds)
- job = project_client.beta.evaluators.get_generation_job(job.id)
- print(f"Job finished with status `{cast(JobStatus, job.status).value}`.")
-
- if job.status != JobStatus.SUCCEEDED:
- message = job.error.message if job.error is not None else ""
- raise RuntimeError(f"Generation job ended with status `{cast(JobStatus, job.status).value}`: {message}")
-
- evaluator = job.result
- assert evaluator is not None
- print(f"Generated evaluator `{evaluator.name}` version `{evaluator.version}`.")
+ # 1. Start the generation job LRO. `operation_id` makes the call idempotent -
+ # re-submitting with the same id returns a poller attached to the existing job.
+ poller = project_client.beta.evaluators.begin_create_generation_job(
+ job=job_body, operation_id=operation_id, polling_interval=poll_interval_seconds
+ )
+ print("Generation job started; LRO polling in progress.")
+
+ # Idempotency: a second call with the same operation_id attaches to the same job.
+ replay_poller = project_client.beta.evaluators.begin_create_generation_job(
+ job=job_body, operation_id=operation_id, polling_interval=poll_interval_seconds
+ )
+
+ # 2. Block until the LRO finishes. The SDK polls automatically; `.result()` returns
+ # the produced EvaluatorVersion once the job reaches a terminal state.
+ print("Waiting for the generation job to complete (polling is handled by the SDK)...")
+ evaluator: EvaluatorVersion = poller.result()
+ print(
+ f"Generated evaluator `{evaluator.name}` version `{evaluator.version}` "
+ f"(job `{evaluator.generation_job_id}`)."
+ )
+
+ # Verify the idempotency: the replay poller resolves to the same underlying job.
+ replay_evaluator: EvaluatorVersion = replay_poller.result()
+ assert replay_evaluator.generation_job_id == evaluator.generation_job_id
# 3. List the 5 most recent generation jobs in this project.
# `limit` controls the page size; use `itertools.islice` to cap the total.
@@ -132,6 +133,7 @@
print("Cleaning up.")
project_client.beta.evaluators.delete_version(name=evaluator.name, version=evaluator.version)
try:
- project_client.beta.evaluators.delete_generation_job(job.id)
+ if evaluator.generation_job_id is not None:
+ project_client.beta.evaluators.delete_generation_job(evaluator.generation_job_id)
except ResourceNotFoundError:
pass # already removed by the delete_version cascade
diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_manual.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_manual.py
index aab7a76eb131..4c90f8f3dc73 100644
--- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_manual.py
+++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_rubric_evaluator_manual.py
@@ -47,7 +47,7 @@
import time
import uuid
from datetime import datetime, timezone
-
+from typing import Union
from dotenv import load_dotenv
from openai.types.eval_create_params import DataSourceConfigCustom
from openai.types.evals.create_eval_jsonl_run_data_source_param import (
@@ -55,6 +55,8 @@
SourceFileContent,
SourceFileContentContent,
)
+from openai.types.evals.run_create_response import RunCreateResponse
+from openai.types.evals.run_retrieve_response import RunRetrieveResponse
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
@@ -87,9 +89,10 @@
# Each dimension is scored independently on a 1-5 scale by an LLM judge at
# evaluation time. `weight` (1-10) controls how strongly each dimension
# contributes to the normalized aggregate score.
- evaluator = project_client.beta.evaluators.create_version(
+ # TODO: Remove this suppression once TypeSpec typing for EvaluatorVersion is fixed.
+ evaluator = project_client.beta.evaluators.create_version( # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
name=evaluator_name,
- evaluator_version={
+ evaluator_version={ # pyright: ignore[reportArgumentType]
"name": evaluator_name,
"categories": [EvaluatorCategory.QUALITY],
"display_name": "Reservation Quality (Manual)",
@@ -165,7 +168,7 @@
)
# 3. Run the evaluation against inline JSONL sample data.
- eval_run = openai_client.evals.runs.create(
+ eval_run: Union[RunCreateResponse, RunRetrieveResponse] = openai_client.evals.runs.create(
eval_id=eval_object.id,
name=f"{evaluator_name}-run",
metadata={"sample": "evaluator_rubric_manual"},
diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/assets/toolbox-agent/main.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/assets/toolbox-agent/main.py
index 51ba81ad11b6..f0ba05195eca 100644
--- a/sdk/ai/azure-ai-projects/samples/hosted_agents/assets/toolbox-agent/main.py
+++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/assets/toolbox-agent/main.py
@@ -9,42 +9,28 @@
from azure.identity import DefaultAzureCredential
from dotenv import load_dotenv
-# Load environment variables from .env file
load_dotenv()
async def main() -> None:
credential = DefaultAzureCredential()
- # FoundryToolbox resolves the toolbox endpoint from the environment
- # (TOOLBOX_ENDPOINT, or FOUNDRY_PROJECT_ENDPOINT + TOOLBOX_NAME), authenticates
- # every request with the credential, and forwards the platform per-request
- # call-id. ``load_tools=False`` keeps the toolbox's tools hidden so only its
- # Agent Skills (SEP-2640) are surfaced; passing it via ``tools=`` connects the
- # MCP session that ``as_skills_provider()`` reads from.
- toolbox = FoundryToolbox(url=os.environ["MCP_SERVER_URL"], credential=credential, load_tools=False)
+ toolbox = FoundryToolbox(url=os.environ["MCP_SERVER_URL"], credential=credential)
- # as_skills_provider() discovers skills from skill://index.json on the toolbox
- # MCP session and exposes them as an agent context provider; SKILL.md bodies are
- # fetched on demand via resources/read.
- skills_provider = toolbox.as_skills_provider()
+ # set disable_load_skill_approval to avoid approval required for loading skills
+ skills_provider = toolbox.as_skills_provider(disable_load_skill_approval=True)
client = FoundryChatClient(
project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"],
model=os.environ["FOUNDRY_MODEL_NAME"],
credential=credential,
+ allow_preview=True,
)
agent = Agent(
client=client,
- name=os.environ.get("AGENT_NAME", "hosted-toolbox-mcp-skills"),
- instructions="You are a helpful assistant.",
tools=toolbox,
context_providers=[skills_provider],
- # History will be managed by the hosting infrastructure, thus there
- # is no need to store history by the service. Learn more at:
- # https://developers.openai.com/api/reference/resources/responses/methods/create
- default_options={"store": False},
)
server = ResponsesHostServer(agent)
diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/assets/toolbox-agent/requirements.txt b/sdk/ai/azure-ai-projects/samples/hosted_agents/assets/toolbox-agent/requirements.txt
index 74e9d565df41..fd97a8982681 100644
--- a/sdk/ai/azure-ai-projects/samples/hosted_agents/assets/toolbox-agent/requirements.txt
+++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/assets/toolbox-agent/requirements.txt
@@ -1,3 +1,3 @@
-agent-framework-foundry==1.10.0
-agent-framework-foundry-hosting>=1.0.0a260630
+agent-framework-foundry==1.10.2
+agent-framework-foundry-hosting==1.0.0b260721
python-dotenv
diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/rbac_util.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/rbac_util.py
new file mode 100644
index 000000000000..8e97fdaa09fd
--- /dev/null
+++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/rbac_util.py
@@ -0,0 +1,132 @@
+import os
+import uuid
+from typing import Any, cast
+from urllib.parse import urlparse
+
+from azure.core.credentials import TokenCredential
+from azure.core.exceptions import ResourceNotFoundError
+from azure.ai.projects.models import AgentVersionDetails
+
+AZURE_AI_USER_ROLE_DEFINITION_GUID = "53ca6127-db72-4b80-b1b0-d745d6d5456d"
+
+
+def _extract_resource_group_name(resource_id: str) -> str:
+ parts = resource_id.strip("/").split("/")
+ for index, part in enumerate(parts):
+ if part.lower() == "resourcegroups" and index + 1 < len(parts):
+ return parts[index + 1]
+ return ""
+
+
+def _resolve_ai_account_resource_id(
+ credential: TokenCredential,
+ account_name: str,
+ project_name: str,
+ subscription_id: str,
+) -> str:
+ from azure.mgmt.resource.resources import ResourceManagementClient
+
+ resource_client = ResourceManagementClient(credential, subscription_id)
+ project_resources = resource_client.resources.list(
+ filter="resourceType eq 'Microsoft.CognitiveServices/accounts/projects'"
+ )
+
+ project_id_segment = f"/accounts/{account_name}/projects/{project_name}".lower()
+ matching_projects = [
+ resource for resource in project_resources if resource.id and project_id_segment in resource.id.lower()
+ ]
+ if not matching_projects:
+ raise RuntimeError(f"Could not locate Foundry project '{project_name}' in subscription '{subscription_id}'.")
+
+ if not matching_projects[0].id:
+ raise RuntimeError("Foundry project resource ID is empty.")
+ resource_group_name = _extract_resource_group_name(matching_projects[0].id)
+ account_resources = resource_client.resources.list_by_resource_group(
+ resource_group_name=resource_group_name,
+ filter="resourceType eq 'Microsoft.CognitiveServices/accounts'",
+ )
+
+ account_matches = [resource.id for resource in account_resources if resource.name == account_name and resource.id]
+ if not account_matches:
+ raise RuntimeError(
+ f"Could not locate Azure AI account '{account_name}' in resource group '{resource_group_name}'."
+ )
+ return account_matches[0]
+
+
+def _ensure_agent_identity_rbac_with_role_id(
+ credential: TokenCredential, principal_id: str, scope_resource_id: str, subscription_id: str, role_id: str
+) -> tuple[bool, str]:
+ from azure.mgmt.authorization import AuthorizationManagementClient, models as authorization_models
+
+ authorization_client = AuthorizationManagementClient(credential, subscription_id)
+ role_definition_id = f"/subscriptions/{subscription_id}/providers/Microsoft.Authorization/roleDefinitions/{role_id}"
+ role_assignment_name = str(
+ uuid.uuid5(
+ uuid.NAMESPACE_URL,
+ f"{scope_resource_id}|{principal_id}|{role_definition_id}",
+ )
+ )
+
+ try:
+ authorization_client.role_assignments.get(scope_resource_id, role_assignment_name)
+ print(f"Foundry User role already assigned to principal {principal_id}.")
+ return False, role_assignment_name
+ except ResourceNotFoundError:
+ pass
+
+ create_parameters_kwargs = cast(
+ dict[str, Any],
+ {
+ "role_definition_id": role_definition_id,
+ "principal_id": principal_id,
+ "principal_type": authorization_models.PrincipalType.SERVICE_PRINCIPAL,
+ },
+ )
+ parameters = authorization_models.RoleAssignmentCreateParameters(**create_parameters_kwargs)
+
+ authorization_client.role_assignments.create(scope_resource_id, role_assignment_name, parameters)
+ print(f"Assigned Foundry User role to principal {principal_id} at scope {scope_resource_id}.")
+ return True, role_assignment_name
+
+
+def ensure_agent_identity_rbac(
+ agent: AgentVersionDetails,
+ credential: TokenCredential,
+ subscription_id: str,
+ foundry_project_endpoint: str,
+) -> None:
+ """Ensure the hosted agent identity has Foundry User role on the Foundry account.
+
+ :param agent: Agent version details containing ``instance_identity``.
+ :type agent: ~azure.ai.projects.models.AgentVersionDetails
+ :param credential: Credential used for Azure Resource Manager authorization calls.
+ :type credential: ~azure.core.credentials.TokenCredential
+ :param subscription_id: Azure subscription ID containing the Foundry project/account.
+ :type subscription_id: str
+ :param foundry_project_endpoint: Foundry project endpoint in the format
+ ``https://.services.ai.azure.com/api/projects/``.
+ :type foundry_project_endpoint: str
+ :raises RuntimeError: If the agent identity principal ID is unavailable, or if the
+ account/project resources cannot be resolved.
+ :raises ~azure.core.exceptions.HttpResponseError: If role assignment creation fails
+ for reasons other than an existing assignment.
+ """
+ if os.environ.get("SKIP_RBAC"):
+ print("Skipping RBAC setup.")
+ return
+ if not agent.instance_identity or not agent.instance_identity.principal_id:
+ raise RuntimeError("Agent instance_identity or principal_id is not available.")
+ principal_id = agent.instance_identity.principal_id
+
+ account_name = urlparse(foundry_project_endpoint).hostname.split(".")[0] # type: ignore[union-attr]
+ project_name = foundry_project_endpoint.rstrip("/").split("/api/projects/")[1].split("/")[0]
+ scope_resource_id = _resolve_ai_account_resource_id(credential, account_name, project_name, subscription_id)
+
+ _ensure_agent_identity_rbac_with_role_id(
+ credential=credential,
+ principal_id=principal_id,
+ scope_resource_id=scope_resource_id,
+ subscription_id=subscription_id,
+ role_id=AZURE_AI_USER_ROLE_DEFINITION_GUID,
+ )
diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_agent_user_identity_isolation.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_agent_user_identity_isolation.py
index 83daa9486a28..d2384254bb6a 100644
--- a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_agent_user_identity_isolation.py
+++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_agent_user_identity_isolation.py
@@ -1,3 +1,4 @@
+# pylint: disable=line-too-long,useless-suppression
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_routines_with_teams_message_trigger.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_routines_with_teams_message_trigger.py
new file mode 100644
index 000000000000..bf7daba193a9
--- /dev/null
+++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_routines_with_teams_message_trigger.py
@@ -0,0 +1,212 @@
+# pylint: disable=line-too-long,useless-suppression
+# ------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------
+
+"""
+DESCRIPTION:
+ This sample demonstrates how to create a Routine that fires when a new
+ Microsoft Teams channel message arrives, then record the resulting runs by
+ polling `list_runs(...)` using the synchronous AIProjectClient.
+
+ The sample uploads the basic hosted-agent code from `assets/basic-agent/`
+ as a temporary hosted-agent version, routes the configured hosted agent
+ name to that version, and creates a routine configured with a
+ `CustomRoutineTrigger`. The trigger uses a Teams-compatible custom
+ connection and listens for the `on_new_channel_message` event on a specific
+ Teams channel. After creating the routine, post a message to the configured
+ channel to fire it. The sample polls the routine run history for a short
+ period and then deletes the routine and hosted-agent version.
+
+ Routines are currently a preview feature. In the Python SDK, you access
+ these operations via `project_client.beta.routines`.
+
+USAGE:
+ python sample_routines_with_teams_message_trigger.py
+
+ Before running the sample:
+
+ pip install "azure-ai-projects>=2.3.0" python-dotenv
+
+ Set these environment variables with your own values:
+ 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview
+ page of your Microsoft Foundry portal.
+ 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model used by the
+ temporary hosted agent.
+ 3) FOUNDRY_HOSTED_AGENT_NAME - Optional. The hosted agent name to route to
+ the temporary uploaded version. Defaults to `MyHostedAgent`.
+ 4) TEAMS_CONNECTION_NAME - The Teams custom connection ID or name.
+ Defaults to `teams-conn`.
+ 5) TEAMS_CHANNEL_URL - A Teams channel URL like the sample URL
+ below. When set, the sample derives `groupId` and `channelId` from it.
+ 6) POLL_INTERVAL_SECONDS - Optional. Seconds to sleep between run-history polls.
+ Defaults to 10.
+
+ Sample channel:
+ https://teams.microsoft.com/l/channel//?groupId=&tenantId=
+"""
+
+import json
+import os
+import time
+from urllib.parse import parse_qs, unquote, urlparse
+
+from dotenv import load_dotenv
+
+from azure.core.exceptions import ResourceNotFoundError
+from azure.identity import DefaultAzureCredential
+
+from azure.ai.projects import AIProjectClient
+from azure.ai.projects.models import (
+ CodeConfiguration,
+ CustomRoutineTrigger,
+ HostedAgentDefinition,
+ InvokeAgentResponsesApiRoutineAction,
+ ProtocolVersionRecord,
+ RoutineRun,
+)
+
+from hosted_agents_util import create_version_from_code, select_basic_agent_code_zip
+
+
+def parse_teams_channel_url(channel_url: str) -> tuple[str | None, str | None]:
+ parsed = urlparse(channel_url)
+ path_parts = [part for part in parsed.path.split("/") if part]
+
+ channel_id = None
+ if len(path_parts) >= 3 and path_parts[0] == "l" and path_parts[1] == "channel":
+ channel_id = unquote(path_parts[2])
+
+ query = parse_qs(parsed.query)
+ group_id = query.get("groupId", [None])[0]
+ return group_id, channel_id
+
+
+load_dotenv()
+
+endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
+agent_name = os.environ.get("FOUNDRY_HOSTED_AGENT_NAME", "MyHostedAgent")
+model_name = os.environ["FOUNDRY_MODEL_NAME"]
+teams_connection_name = os.environ.get("TEAMS_CONNECTION_NAME", "teams-conn")
+teams_channel_url = os.environ["TEAMS_CHANNEL_URL"]
+teams_group_id, teams_channel_id = parse_teams_channel_url(teams_channel_url)
+poll_interval_seconds = int(os.environ.get("POLL_INTERVAL_SECONDS", "10"))
+use_remote_build = os.environ.get("FOUNDRY_HOSTED_AGENT_REMOTE_BUILD", "true").strip().lower() == "true"
+dependency_resolution, code_zip_stream = select_basic_agent_code_zip(True)
+
+
+def main() -> None:
+ with (
+ code_zip_stream as code_stream,
+ DefaultAzureCredential() as credential,
+ AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
+ create_version_from_code(
+ project_client=project_client,
+ agent_name=agent_name,
+ description="Teams channel routine sample hosted agent uploaded from assets/basic-agent.",
+ definition=HostedAgentDefinition(
+ cpu="0.5",
+ memory="1Gi",
+ code_configuration=CodeConfiguration(
+ runtime="python_3_14",
+ entry_point=["python", "main.py"],
+ dependency_resolution=dependency_resolution,
+ ),
+ environment_variables={
+ "FOUNDRY_PROJECT_ENDPOINT": endpoint,
+ "FOUNDRY_MODEL_NAME": model_name,
+ },
+ protocol_versions=[
+ ProtocolVersionRecord(protocol="responses", version="2.0.0"),
+ ProtocolVersionRecord(protocol="invocations", version="2.0.0"),
+ ],
+ ),
+ metadata={"enableVnextExperience": "true"},
+ code=code_stream,
+ ),
+ ):
+ routine_name = "sample-routine-teams-channel-message"
+
+ print(f"Preparing routine `{routine_name}` for Teams channel {teams_channel_id}.")
+ print(f"Using Teams channel URL: {teams_channel_url}")
+ print({"group_id": teams_group_id, "channel_id": teams_channel_id})
+ try:
+ print(f"Deleting any existing routine `{routine_name}`.")
+ project_client.beta.routines.delete(routine_name)
+ print(f"Routine `{routine_name}` deleted")
+ except ResourceNotFoundError:
+ pass
+
+ print(f"Creating routine `{routine_name}`.")
+ created = project_client.beta.routines.create_or_update(
+ routine_name,
+ description="Routine used by the Teams channel message trigger sample.",
+ enabled=True,
+ triggers={
+ "incoming": CustomRoutineTrigger(
+ provider="teams",
+ event_name="on_new_channel_message",
+ parameters={
+ "connection_id": teams_connection_name,
+ "thread_type": "channel",
+ "group_id": teams_group_id,
+ "channel_id": teams_channel_id,
+ },
+ )
+ },
+ action=InvokeAgentResponsesApiRoutineAction(agent_name=agent_name),
+ )
+ print(
+ f"Created routine: {created.name} enabled={created.enabled} "
+ f"provider=teams event_name=on_new_channel_message group_id={teams_group_id}"
+ )
+ print("Post a new message to the configured Teams channel to fire the routine.")
+ print("Waiting for a routine run for up to 10 minutes...")
+
+ try:
+ seen_phases: dict[str, str] = {}
+ final_run: RoutineRun | None = None
+ run_was_triggered = False
+ terminal_statuses = {"finished", "failed", "killed"}
+
+ deadline = time.monotonic() + 600
+ while deadline > time.monotonic():
+ runs = list(project_client.beta.routines.list_runs(routine_name, limit=20, order="desc"))
+ for run in runs:
+ run_was_triggered = True
+ current_phase = str(run.phase)
+ if seen_phases.get(run.id) == current_phase:
+ continue
+ seen_phases[run.id] = current_phase
+ print(
+ f" - run_id={run.id} phase={run.phase} status={run.status} "
+ f"trigger_type={run.trigger_type} triggered_at={run.triggered_at} ended_at={run.ended_at}"
+ )
+ if str(run.status).lower() in terminal_statuses:
+ final_run = run
+
+ if final_run is not None:
+ break
+ time.sleep(poll_interval_seconds)
+
+ if final_run:
+ print("Final run:")
+ print(json.dumps(final_run.as_dict(), indent=2, default=str))
+ print(f"The response Id is {final_run.response_id}")
+ elif run_was_triggered:
+ print("A routine run was observed, but no terminal run state was reached within the deadline.")
+ else:
+ print("No Teams-triggered run was observed within the deadline.")
+ except KeyboardInterrupt:
+ print("Interrupted by user; cleaning up routine before exiting.")
+ finally:
+ try:
+ project_client.beta.routines.delete(routine_name)
+ print("Routine deleted")
+ except ResourceNotFoundError:
+ pass
+
+
+if __name__ == "__main__":
+ main()
diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_toolbox_with_reminder_preview.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_toolbox_with_reminder_preview.py
new file mode 100644
index 000000000000..9720e0c81978
--- /dev/null
+++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_toolbox_with_reminder_preview.py
@@ -0,0 +1,183 @@
+# pylint: disable=line-too-long,useless-suppression
+# ------------------------------------
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+# ------------------------------------
+
+"""
+DESCRIPTION:
+ Create a Toolbox version that exposes a Reminder Preview tool over a
+ Foundry Toolbox MCP endpoint, then upload ``assets/toolbox-agent/`` as a
+ REMOTE_BUILD code asset for a Hosted Agent version. The sample waits for
+ the new version to become active, assigns Azure AI User RBAC to the hosted
+ agent identity on the Foundry account, temporarily routes the Hosted Agent
+ endpoint to that version, sends a reminder request through the Responses
+ API, queries routines to find the service-created one-shot routine, and
+ finally restores the previous endpoint and deletes the temporary agent
+ version and toolbox.
+
+ The hosted agent must already exist; create it first with:
+ samples/hosted_agents/sample_create_hosted_agent_from_image.py
+
+USAGE:
+ python sample_toolbox_with_reminder_preview.py
+
+ Before running the sample:
+
+ pip install "azure-ai-projects>=2.3.0" azure-identity azure-mgmt-authorization azure-mgmt-resource python-dotenv
+
+ Set these environment variables with your own values:
+ 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the
+ Overview page of your Microsoft Foundry portal.
+ 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model.
+ 3) FOUNDRY_HOSTED_AGENT_NAME - Optional. The Hosted Agent name. Defaults to
+ `MyHostedAgent`. The Hosted Agent must already exist.
+ 4) AZURE_SUBSCRIPTION_ID - The Azure subscription ID containing the
+ Foundry project/account. This is used to assign Azure AI User RBAC to
+ the hosted agent identity.
+"""
+
+import os
+import time
+from pathlib import Path
+
+from dotenv import load_dotenv
+
+from azure.core.exceptions import ResourceNotFoundError
+from azure.identity import DefaultAzureCredential
+
+from azure.ai.projects import AIProjectClient
+from azure.ai.projects.models import (
+ CodeConfiguration,
+ CodeDependencyResolution,
+ HostedAgentDefinition,
+ ProtocolVersionRecord,
+ ReminderPreviewToolboxTool,
+)
+
+from hosted_agents_util import create_version_from_code
+from rbac_util import ensure_agent_identity_rbac
+from util import zip_directory
+
+load_dotenv()
+
+endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
+model_name = os.environ["FOUNDRY_MODEL_NAME"]
+subscription_id = os.environ["AZURE_SUBSCRIPTION_ID"]
+agent_name = os.environ.get("FOUNDRY_HOSTED_AGENT_NAME", "MyHostedAgent")
+
+_HOSTED_AGENT_SOURCE_DIR = Path(__file__).parent / "assets" / "toolbox-agent"
+
+
+TOOLBOX_NAME = "toolbox_with_reminder_preview"
+
+
+def list_routine_names(project_client: AIProjectClient) -> set[str]:
+ routines = list(project_client.beta.routines.list())
+ return {routine.name for routine in routines if routine.name}
+
+
+def main() -> None:
+ created_routine_names: set[str] = set()
+ with (
+ DefaultAzureCredential() as credential,
+ AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
+ ):
+ try:
+ project_client.toolboxes.delete(TOOLBOX_NAME)
+ except ResourceNotFoundError:
+ pass
+
+ toolbox_version = project_client.toolboxes.create_version(
+ name=TOOLBOX_NAME,
+ description="Toolbox exposing a reminder preview tool.",
+ tools=[
+ ReminderPreviewToolboxTool(
+ name="reminder",
+ description="Schedule a reminder to re-invoke the agent after a short delay.",
+ ),
+ ],
+ metadata={"enableVnextExperience": "true"},
+ )
+ print(f"Created toolbox: {toolbox_version.name} version={toolbox_version.version}")
+
+ toolbox_mcp_url = f"{endpoint}/toolboxes/{TOOLBOX_NAME}/versions/{toolbox_version.version}/mcp?api-version=v1"
+
+ zip_filename = "hosted-toolbox-mcp-reminder-preview-agent.zip"
+ _, _, zip_path = zip_directory(_HOSTED_AGENT_SOURCE_DIR, zip_filename)
+ try:
+ with (
+ zip_path.open("rb") as code_stream,
+ create_version_from_code(
+ project_client=project_client,
+ agent_name=agent_name,
+ description="Hosted agent code for toolbox MCP reminder preview tool.",
+ definition=HostedAgentDefinition(
+ cpu="0.5",
+ memory="1Gi",
+ code_configuration=CodeConfiguration(
+ runtime="python_3_13",
+ entry_point=["python", "main.py"],
+ dependency_resolution=CodeDependencyResolution.REMOTE_BUILD,
+ ),
+ environment_variables={
+ "FOUNDRY_PROJECT_ENDPOINT": endpoint,
+ "FOUNDRY_MODEL_NAME": model_name,
+ "MCP_SERVER_URL": toolbox_mcp_url,
+ },
+ protocol_versions=[
+ ProtocolVersionRecord(protocol="responses", version="2.0.0"),
+ ProtocolVersionRecord(protocol="invocations", version="2.0.0"),
+ ],
+ ),
+ code=code_stream,
+ ) as agent,
+ project_client.get_openai_client(agent_name=agent_name) as hosted_openai_client,
+ ):
+
+ # toolbox requires the hosted agent identity to have Foundry User RBAC on the Foundry account, so assign it here
+ ensure_agent_identity_rbac(
+ agent=agent,
+ credential=credential,
+ subscription_id=subscription_id,
+ foundry_project_endpoint=endpoint,
+ )
+
+ routines_before = list_routine_names(project_client)
+
+ user_input = "Use the reminder tool to remind me in 1 minute to check the coffee."
+ print(f"User: {user_input}")
+ response = hosted_openai_client.responses.create(
+ input=user_input,
+ )
+
+ response_text = response.output_text or ""
+ print("Response:")
+ print(response_text.encode("utf-8", errors="replace").decode("utf-8"))
+
+ print("Routines after scheduling the reminder:")
+ deadline = time.monotonic() + 30
+ while time.monotonic() < deadline:
+ routines_after = list_routine_names(project_client)
+ created_routine_names = routines_after - routines_before
+ if created_routine_names:
+ break
+ print("No new routine found yet; checking again shortly...")
+ time.sleep(5)
+
+ if created_routine_names:
+ print("Retrieved new routine details:")
+ for routine_name in sorted(created_routine_names):
+ routine = project_client.beta.routines.get(routine_name)
+ print(f" - {routine.name} enabled={routine.enabled} description={routine.description!r}")
+ else:
+ print(
+ "No new routine was visible in project_client.beta.routines.list() after scheduling the reminder."
+ )
+ finally:
+ project_client.toolboxes.delete(TOOLBOX_NAME)
+ print("Toolbox deleted")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_toolbox_with_skill.py b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_toolbox_with_skill.py
index e4f029489353..430f8fd0e46a 100644
--- a/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_toolbox_with_skill.py
+++ b/sdk/ai/azure-ai-projects/samples/hosted_agents/sample_toolbox_with_skill.py
@@ -6,30 +6,21 @@
"""
DESCRIPTION:
- Demonstrates deploying a code-based Hosted Agent that discovers and uses
- skills from a Foundry Toolbox MCP endpoint via Agent Framework
- `FoundryToolbox()`.
-
- The sample:
- 1. Creates a shipping-cost skill.
- 2. Creates a toolbox version that references the skill.
- 3. Packages ``assets/toolbox-agent/`` source as a zip at runtime
- (REMOTE_BUILD - the service resolves dependencies from requirements.txt).
- 4. Deploys a new Hosted Agent version, forwarding the project endpoint,
- model name, and toolbox MCP URL to the hosted code.
- 5. Waits for the version to become active.
- 6. Sends a query to the agent via the Responses API.
- 7. Cleans up created resources (agent version, toolbox, and skill).
-
- The hosted agent must already exist; create it first with:
- samples/hosted_agents/sample_create_hosted_agent_from_image.py
+ Create a shipping-cost Skill and a Toolbox version that exposes it over a
+ Foundry Toolbox MCP endpoint, then upload ``assets/toolbox-agent/`` as a
+ REMOTE_BUILD code asset for a Hosted Agent version. The sample waits for
+ the new version to become active, assigns Azure AI User RBAC to the hosted
+ agent identity on the Foundry account, temporarily routes the Hosted Agent
+ endpoint to that version, sends a query through the Responses API, and
+ finally restores the previous endpoint and deletes the temporary agent
+ version, toolbox, and skill.
USAGE:
python sample_toolbox_with_skill.py
Before running the sample:
- pip install "azure-ai-projects>=2.3.0" python-dotenv
+ pip install "azure-ai-projects>=2.3.0" azure-identity azure-mgmt-authorization azure-mgmt-resource python-dotenv
Set these environment variables with your own values:
1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the
@@ -38,16 +29,14 @@
the "Name" column in the "Models + endpoints" tab in your Foundry project.
3) FOUNDRY_HOSTED_AGENT_NAME - Optional. The Hosted Agent name. Defaults to
`MyHostedAgent`. The Hosted Agent must already exist.
+ 4) AZURE_SUBSCRIPTION_ID - The Azure subscription ID containing the
+ Foundry project/account. This is used to assign Azure AI User RBAC to
+ the hosted agent identity.
"""
import os
-import sys
from pathlib import Path
-_SAMPLES_DIR = Path(__file__).resolve().parents[1]
-if str(_SAMPLES_DIR) not in sys.path:
- sys.path.insert(0, str(_SAMPLES_DIR))
-
from dotenv import load_dotenv
from azure.identity import DefaultAzureCredential
@@ -60,12 +49,13 @@
)
from hosted_agents_util import create_version_from_code
+from rbac_util import ensure_agent_identity_rbac
from util import zip_directory
from azure.core.exceptions import ResourceNotFoundError
from azure.ai.projects.models import (
SkillInlineContent,
- ToolboxSearchPreviewToolboxTool,
+ ToolSearchToolboxTool,
ToolboxSkillReference,
)
@@ -73,6 +63,7 @@
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
model_name = os.environ["FOUNDRY_MODEL_NAME"]
+subscription_id = os.environ["AZURE_SUBSCRIPTION_ID"]
agent_name = os.environ.get("FOUNDRY_HOSTED_AGENT_NAME", "MyHostedAgent")
_HOSTED_AGENT_SOURCE_DIR = Path(__file__).parent / "assets" / "toolbox-agent"
@@ -114,7 +105,7 @@ def main() -> None:
toolbox_version = project_client.toolboxes.create_version(
name=TOOLBOX_NAME,
description="Toolbox exposing a shipping-cost skill.",
- tools=[ToolboxSearchPreviewToolboxTool()],
+ tools=[ToolSearchToolboxTool()],
skills=[ToolboxSkillReference(name=skill_version.name, version=skill_version.version)],
)
print(f"Created toolbox: {toolbox_version.name} version={toolbox_version.version}")
@@ -147,10 +138,18 @@ def main() -> None:
protocol_versions=[ProtocolVersionRecord(protocol="responses", version="2.0.0")],
),
code=code_stream,
- ),
+ ) as agent,
project_client.get_openai_client(agent_name=agent_name) as hosted_openai_client,
):
+ # toolbox requires the hosted agent identity to have Foundry User RBAC on the Foundry account, so assign it here
+ ensure_agent_identity_rbac(
+ agent=agent,
+ credential=credential,
+ subscription_id=subscription_id,
+ foundry_project_endpoint=endpoint,
+ )
+
user_input = "Compute the shipping cost for a 3 kg package shipped domestically."
print(f"User: {user_input}")
response = hosted_openai_client.responses.create(input=user_input)
diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py
index 8d84a889a14c..d7a9e0e886c6 100644
--- a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py
+++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py
@@ -181,17 +181,17 @@ def test_models_samples(self, sample_path: str, **kwargs) -> None:
# fails the test).
@servicePreparer()
- @additionalSampleTests(
- [
- AdditionalSampleTestDetail(
- test_id="sample_dataset_generation_job_simpleqna_with_prompt_source",
- sample_filename="sample_dataset_generation_job_simpleqna_with_prompt_source.py",
- env_vars={
- "POLL_INTERVAL_SECONDS": "60",
- },
- ),
- ]
- )
+ # @additionalSampleTests(
+ # [
+ # AdditionalSampleTestDetail(
+ # test_id="sample_dataset_generation_job_simpleqna_with_prompt_source",
+ # sample_filename="sample_dataset_generation_job_simpleqna_with_prompt_source.py",
+ # env_vars={
+ # "POLL_INTERVAL_SECONDS": "60",
+ # },
+ # ),
+ # ]
+ # )
@pytest.mark.parametrize(
"sample_path",
get_sample_paths(
@@ -208,6 +208,7 @@ def test_models_samples(self, sample_path: str, **kwargs) -> None:
)
@SamplePathPasser()
@recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX)
+ # To run this test: pytest tests/samples/test_samples.py::TestSamples::test_datasets_samples[sample_dataset_generation_job_simpleqna_with_prompt_source] -s
def test_datasets_samples(self, sample_path: str, **kwargs) -> None:
env_vars = get_sample_env_vars(kwargs)
executor = SyncSampleExecutor(self, sample_path, env_vars=env_vars, **kwargs)
@@ -270,13 +271,6 @@ def test_chat_completions_samples(self, sample_path: str, **kwargs) -> None:
"ZIP_FILE_PATH": "tests/samples/assets/basic-agent.zip",
},
),
- AdditionalSampleTestDetail(
- test_id="sample_toolbox_with_skill",
- sample_filename="sample_toolbox_with_skill.py",
- env_vars={
- "ZIP_FILE_PATH": "tests/samples/assets/toolbox-agent.zip",
- },
- ),
AdditionalSampleTestDetail(
test_id="sample_agent_user_identity_isolation",
sample_filename="sample_agent_user_identity_isolation.py",
@@ -293,7 +287,7 @@ def test_chat_completions_samples(self, sample_path: str, **kwargs) -> None:
get_sample_paths(
"hosted_agents",
samples_to_skip=[
- "sample_toolbox_with_skill.py", # Specified through AdditionalSampleTestDetail
+ "sample_toolbox_with_skill.py", # Skip due to RBAC assignment that cannot be recorded
"sample_create_hosted_agent_from_code.py", # Specified through AdditionalSampleTestDetail
"sample_agent_user_identity_isolation.py", # Specified through AdditionalSampleTestDetail
"sample_session_log_stream.py", # Specified through AdditionalSampleTestDetail
@@ -303,6 +297,8 @@ def test_chat_completions_samples(self, sample_path: str, **kwargs) -> None:
"sample_routines_with_schedule_trigger.py", # 500
"sample_routines_with_timer_trigger.py", # Timer is used causing request response not matched
"sample_routines_with_github_issue_trigger.py", # Cannot run without interact on Github
+ "sample_routines_with_teams_message_trigger.py", # Cannot run without live Teams event
+ "sample_toolbox_with_reminder_preview.py", # Skip due to RBAC assignment that cannot be recorded
],
),
)
diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml.saved
similarity index 85%
rename from sdk/ai/azure-ai-projects/tsp-location.yaml
rename to sdk/ai/azure-ai-projects/tsp-location.yaml.saved
index 8a709427b4f4..91c4a5456ac9 100644
--- a/sdk/ai/azure-ai-projects/tsp-location.yaml
+++ b/sdk/ai/azure-ai-projects/tsp-location.yaml.saved
@@ -1,5 +1,5 @@
directory: specification/ai-foundry/data-plane/Foundry/src/sdk-python-js-azure-ai-projects
-commit: f47a66a24ffa5b1a28cc18d2785cf379225eb9ec
+commit: 5f1334500df34faa63e0255a18f3072b0219cebe
repo: Azure/azure-rest-api-specs
additionalDirectories:
- specification/ai-foundry/data-plane/Foundry/src/agents
@@ -17,9 +17,7 @@ additionalDirectories:
- specification/ai-foundry/data-plane/Foundry/src/insights
- specification/ai-foundry/data-plane/Foundry/src/memory-stores
- specification/ai-foundry/data-plane/Foundry/src/models
- - specification/ai-foundry/data-plane/Foundry/src/openai/conversations
- - specification/ai-foundry/data-plane/Foundry/src/openai/evaluations
- - specification/ai-foundry/data-plane/Foundry/src/openai/responses
+ - specification/ai-foundry/data-plane/Foundry/src/openai
- specification/ai-foundry/data-plane/Foundry/src/red-teams
- specification/ai-foundry/data-plane/Foundry/src/routines
- specification/ai-foundry/data-plane/Foundry/src/schedules