Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/ai/azure-ai-projects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Breaking changes in beta classes:
* Added Hosted Agent code-upload samples `sample_create_hosted_agent_from_code.py` and `sample_create_hosted_agent_from_code_async.py`, demonstrating uploading a code package (zip) as a new hosted agent version.
* The Hosted Agent creation sample also demonstrates assigning the hosted agent managed identity the Azure AI User RBAC role on the backing Azure AI account.
* Updated the other Hosted Agent samples to reuse an existing Hosted Agent as a prerequisite, instead of creating a new hosted agent version in each sample.
* Added Routines samples `sample_routines_crud.py` demonstring CRUD operations and `sample_routines_with_timer_trigger.py` trigger a routine by a timer.
* Added Toolbox tool-search sample `sample_toolboxes_with_search_preview.py` and `sample_toolboxes_with_search_preview_async.py`, demonstrating creating a Toolbox version with `ToolboxSearchPreviewTool` and invoking `MCPTool`.
* Added `.beta.models` samples under `samples/models/`:
* `sample_models_basic.py` — synchronous end-to-end registration via the `create` helper (uses `azcopy`), followed by `get`, `list_versions`, `list`, `get_credentials`, `update`, and `delete`.
Expand Down
2 changes: 1 addition & 1 deletion sdk/ai/azure-ai-projects/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/ai/azure-ai-projects",
"Tag": "python/ai/azure-ai-projects_449a9f8e06"
"Tag": "python/ai/azure-ai-projects_11a3786ca2"
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
"start_time": start_time,
"end_time": end_time,
"max_traces": args.max_traces,
"filter_strategy": "smart_filtering"
"filter_strategy": "smart_filtering",
}

if args.agent_id:
Expand Down Expand Up @@ -173,4 +173,4 @@
print(f"\n✗ Evaluation run failed: {run.error}")

client.evals.delete(eval_id=eval_object.id)
print("Evaluation deleted")
print("Evaluation deleted")
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
Schedule,
RecurrenceTrigger,
DailyRecurrenceSchedule,
EvaluationScheduleTask
EvaluationScheduleTask,
)
import time

Expand Down Expand Up @@ -198,6 +198,7 @@ def assign_rbac(): # pylint: disable=too-many-statements
print("An unexpected error occurred. Please check the error details above.")
raise


def schedule_trace_evaluation():
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"]
Expand Down Expand Up @@ -284,7 +285,7 @@ def schedule_trace_evaluation():
"start_time": start_time,
"end_time": end_time,
"max_traces": args.max_traces,
"filter_strategy": "smart_filtering"
"filter_strategy": "smart_filtering",
}

if args.agent_id:
Expand All @@ -304,7 +305,7 @@ def schedule_trace_evaluation():
eval_run_object = {
"eval_id": eval_object.id,
"name": "trace_eval_with_smart_filter",
"data_source": data_source
"data_source": data_source,
}

print("Eval Run:")
Expand Down Expand Up @@ -335,5 +336,6 @@ def schedule_trace_evaluation():
client.evals.delete(eval_id=eval_object.id)
print("Evaluation deleted")


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# pylint: disable=line-too-long,useless-suppression
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------

"""
DESCRIPTION:
This sample demonstrates how to perform CRUD operations on Routines
using the synchronous AIProjectClient.

It creates a routine bound to an existing hosted agent, retrieves it,
toggles its `enabled` state via `disable` / `enable`, lists routines,
and finally deletes it. A `CustomRoutineTrigger` is used to keep the
sample self-contained (no GitHub or schedule resources required).

Routines are currently a preview feature. In the Python SDK, you access
these operations via `project_client.beta.routines`.

USAGE:
python sample_routines_crud.py

Before running the sample:

pip install "azure-ai-projects>=2.2.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_HOSTED_AGENT_NAME - The name of an existing Hosted Agent to invoke
when the routine fires.
"""

import os

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 (
CustomRoutineTrigger,
InvokeAgentResponsesApiRoutineAction,
Routine,
RoutineTrigger,
)

load_dotenv()

endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
agent_name = os.environ["FOUNDRY_HOSTED_AGENT_NAME"]


def print_routine_state(routine: Routine) -> None:
print(f" - routine `{routine.name}` enabled={routine.enabled} description={routine.description!r}")


with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project_client,
):

routine_name = "sample-routine"

try:
project_client.beta.routines.delete(routine_name)
print(f"Routine `{routine_name}` deleted")
except ResourceNotFoundError:
pass

triggers: dict[str, RoutineTrigger] = {
"manual": CustomRoutineTrigger(
provider="sample-provider",
event_name="sample-event",
parameters={"source": "sample_routines_crud"},
),
}

action = InvokeAgentResponsesApiRoutineAction(agent_name=agent_name)

created = project_client.beta.routines.create_or_update(
routine_name,
description="Routine created by the azure-ai-projects sample.",
enabled=True,
triggers=triggers,
action=action,
)
print(f"Created routine: {created.name} enabled={created.enabled}")

disabled = project_client.beta.routines.disable(routine_name)
print(f"Disabled routine: {disabled.name} enabled={disabled.enabled}")

fetched = project_client.beta.routines.get(routine_name)
print("Retrieved routine after disable:")
print_routine_state(fetched)

enabled = project_client.beta.routines.enable(routine_name)
print(f"Enabled routine: {enabled.name} enabled={enabled.enabled}")

fetched = project_client.beta.routines.get(routine_name)
print("Retrieved routine after enable:")
print_routine_state(fetched)

routines = list(project_client.beta.routines.list())
print(f"Found {len(routines)} routine(s):")
for item in routines:
print(f" - {item.name} enabled={item.enabled}")

project_client.beta.routines.delete(routine_name)
print("Routine deleted")
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# 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 automatically
from a one-shot timer trigger, then record the resulting runs by polling
`list_runs(...)` using the synchronous AIProjectClient.

The routine is bound to an existing hosted agent and scheduled to fire a
short time in the future. The sample then polls the run history until a
terminal phase is reached (or a deadline elapses), printing each observed
transition. The routine is deleted at the end of the sample.

Routines are currently a preview feature. In the Python SDK, you access
these operations via `project_client.beta.routines`.

USAGE:
python sample_routines_with_timer_trigger.py

Before running the sample:

pip install "azure-ai-projects>=2.2.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_HOSTED_AGENT_NAME - The name of an existing Hosted Agent to invoke
when the routine timer fires.
"""

import datetime
import json
import os
import time

from dotenv import load_dotenv

from azure.core.exceptions import ResourceNotFoundError
from azure.core.settings import settings

settings.tracing_implementation = "opentelemetry"
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, ConsoleSpanExporter
from azure.monitor.opentelemetry import configure_azure_monitor
from azure.ai.projects.telemetry import AIProjectInstrumentor

from azure.identity import DefaultAzureCredential

from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
InvokeAgentResponsesApiRoutineAction,
RoutineRun,
RoutineRunPhase,
TimerRoutineTrigger,
)

load_dotenv()

# Console exporter: spans printed to stdout as they finish.
tracer_provider = TracerProvider()
tracer_provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(tracer_provider)
tracer = trace.get_tracer(__name__)
AIProjectInstrumentor().instrument()

endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
agent_name = os.environ["FOUNDRY_HOSTED_AGENT_NAME"]


with (
DefaultAzureCredential() as credential,
AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project_client,
):
# Azure Monitor exporter: same spans also sent to the Application Insights
# resource attached to the Foundry project, viewable in the "Tracing" tab
# on ai.azure.com.
configure_azure_monitor(connection_string=project_client.telemetry.get_application_insights_connection_string())

routine_name = "sample-routine-timer"

try:
project_client.beta.routines.delete(routine_name)
print(f"Routine `{routine_name}` deleted")
except ResourceNotFoundError:
pass

fire_at = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=20)
created = project_client.beta.routines.create_or_update(
routine_name,
description="Routine used by the timer-trigger sample.",
enabled=True,
triggers={"once": TimerRoutineTrigger(at=fire_at)},
action=InvokeAgentResponsesApiRoutineAction(agent_name=agent_name),
)
print(f"Created routine: {created.name} enabled={created.enabled} fire_at={fire_at.isoformat()}")

terminal_phases = {RoutineRunPhase.COMPLETED, RoutineRunPhase.FAILED}
seen_phases: dict[str, RoutineRunPhase] = {}
final_run: RoutineRun | None = None

deadline = time.monotonic() + 180
while time.monotonic() < deadline:
runs = list(project_client.beta.routines.list_runs(routine_name, limit=20, order="desc"))
for run in runs:
if run.id is None:
continue
if seen_phases.get(run.id) == run.phase:
continue
seen_phases[run.id] = run.phase # type: ignore[assignment]
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() == "finished":
final_run = run
Comment on lines +119 to +120

if final_run is not None:
break
time.sleep(5)

if final_run:
print("Final run:")
print(json.dumps(final_run.as_dict(), indent=2, default=str))
# Note: retrieving the response body produced by a routine-dispatched
# run via `openai_client.responses.retrieve(final_run.response_id)` is
# not yet supported by the service for this scenario.
else:
print("Timer did not produce a terminal run within the deadline.")

project_client.beta.routines.delete(routine_name)
print("Routine deleted")
Loading
Loading