Skip to content

Commit af90503

Browse files
howieleungCopilot
andauthored
Howie/samples 4 (#47240)
* samples * routine samples * Remove sample_skill_in_toolbox.py as part of cleanup * update samples * recording * Update assets.json Tag and enable additional sample tests in test_samples.py * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * upgrade version --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent a048d8e commit af90503

9 files changed

Lines changed: 432 additions & 19 deletions

File tree

sdk/ai/azure-ai-projects/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release History
22

3+
## 2.3.0 (Unreleased)
4+
5+
### Sample updates
6+
7+
* Added Routines samples `sample_routines_crud.py` demonstrating CRUD operations and `sample_routines_with_timer_trigger.py` demonstrating triggering a routine by a timer.
8+
39
## 2.2.0 (2026-05-29)
410

511
### Features Added

sdk/ai/azure-ai-projects/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "python",
44
"TagPrefix": "python/ai/azure-ai-projects",
5-
"Tag": "python/ai/azure-ai-projects_449a9f8e06"
5+
"Tag": "python/ai/azure-ai-projects_11a3786ca2"
66
}

sdk/ai/azure-ai-projects/azure/ai/projects/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
# Changes may cause incorrect behavior and will be lost if the code is regenerated.
77
# --------------------------------------------------------------------------
88

9-
VERSION = "2.2.0"
9+
VERSION = "2.3.0"

sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_trace_evaluation_smart_filter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127
"start_time": start_time,
128128
"end_time": end_time,
129129
"max_traces": args.max_traces,
130-
"filter_strategy": "smart_filtering"
130+
"filter_strategy": "smart_filtering",
131131
}
132132

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

175175
client.evals.delete(eval_id=eval_object.id)
176-
print("Evaluation deleted")
176+
print("Evaluation deleted")

sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_agent_traces_evaluation_smart_filter.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
Schedule,
4444
RecurrenceTrigger,
4545
DailyRecurrenceSchedule,
46-
EvaluationScheduleTask
46+
EvaluationScheduleTask,
4747
)
4848
import time
4949

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

201+
201202
def schedule_trace_evaluation():
202203
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
203204
model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"]
@@ -284,7 +285,7 @@ def schedule_trace_evaluation():
284285
"start_time": start_time,
285286
"end_time": end_time,
286287
"max_traces": args.max_traces,
287-
"filter_strategy": "smart_filtering"
288+
"filter_strategy": "smart_filtering",
288289
}
289290

290291
if args.agent_id:
@@ -304,7 +305,7 @@ def schedule_trace_evaluation():
304305
eval_run_object = {
305306
"eval_id": eval_object.id,
306307
"name": "trace_eval_with_smart_filter",
307-
"data_source": data_source
308+
"data_source": data_source,
308309
}
309310

310311
print("Eval Run:")
@@ -335,5 +336,6 @@ def schedule_trace_evaluation():
335336
client.evals.delete(eval_id=eval_object.id)
336337
print("Evaluation deleted")
337338

339+
338340
if __name__ == "__main__":
339341
main()
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# pylint: disable=line-too-long,useless-suppression
2+
# ------------------------------------
3+
# Copyright (c) Microsoft Corporation.
4+
# Licensed under the MIT License.
5+
# ------------------------------------
6+
7+
"""
8+
DESCRIPTION:
9+
This sample demonstrates how to perform CRUD operations on Routines
10+
using the synchronous AIProjectClient.
11+
12+
It creates a routine bound to an existing hosted agent, retrieves it,
13+
toggles its `enabled` state via `disable` / `enable`, lists routines,
14+
and finally deletes it. A `CustomRoutineTrigger` is used to keep the
15+
sample self-contained (no GitHub or schedule resources required).
16+
17+
Routines are currently a preview feature. In the Python SDK, you access
18+
these operations via `project_client.beta.routines`.
19+
20+
USAGE:
21+
python sample_routines_crud.py
22+
23+
Before running the sample:
24+
25+
pip install "azure-ai-projects>=2.2.0" python-dotenv
26+
27+
Set these environment variables with your own values:
28+
1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview
29+
page of your Microsoft Foundry portal.
30+
2) FOUNDRY_HOSTED_AGENT_NAME - The name of an existing Hosted Agent to invoke
31+
when the routine fires.
32+
"""
33+
34+
import os
35+
36+
from dotenv import load_dotenv
37+
38+
from azure.core.exceptions import ResourceNotFoundError
39+
from azure.identity import DefaultAzureCredential
40+
41+
from azure.ai.projects import AIProjectClient
42+
from azure.ai.projects.models import (
43+
CustomRoutineTrigger,
44+
InvokeAgentResponsesApiRoutineAction,
45+
Routine,
46+
RoutineTrigger,
47+
)
48+
49+
load_dotenv()
50+
51+
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
52+
agent_name = os.environ["FOUNDRY_HOSTED_AGENT_NAME"]
53+
54+
55+
def print_routine_state(routine: Routine) -> None:
56+
print(f" - routine `{routine.name}` enabled={routine.enabled} description={routine.description!r}")
57+
58+
59+
with (
60+
DefaultAzureCredential() as credential,
61+
AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project_client,
62+
):
63+
64+
routine_name = "sample-routine"
65+
66+
try:
67+
project_client.beta.routines.delete(routine_name)
68+
print(f"Routine `{routine_name}` deleted")
69+
except ResourceNotFoundError:
70+
pass
71+
72+
triggers: dict[str, RoutineTrigger] = {
73+
"manual": CustomRoutineTrigger(
74+
provider="sample-provider",
75+
event_name="sample-event",
76+
parameters={"source": "sample_routines_crud"},
77+
),
78+
}
79+
80+
action = InvokeAgentResponsesApiRoutineAction(agent_name=agent_name)
81+
82+
created = project_client.beta.routines.create_or_update(
83+
routine_name,
84+
description="Routine created by the azure-ai-projects sample.",
85+
enabled=True,
86+
triggers=triggers,
87+
action=action,
88+
)
89+
print(f"Created routine: {created.name} enabled={created.enabled}")
90+
91+
disabled = project_client.beta.routines.disable(routine_name)
92+
print(f"Disabled routine: {disabled.name} enabled={disabled.enabled}")
93+
94+
fetched = project_client.beta.routines.get(routine_name)
95+
print("Retrieved routine after disable:")
96+
print_routine_state(fetched)
97+
98+
enabled = project_client.beta.routines.enable(routine_name)
99+
print(f"Enabled routine: {enabled.name} enabled={enabled.enabled}")
100+
101+
fetched = project_client.beta.routines.get(routine_name)
102+
print("Retrieved routine after enable:")
103+
print_routine_state(fetched)
104+
105+
routines = list(project_client.beta.routines.list())
106+
print(f"Found {len(routines)} routine(s):")
107+
for item in routines:
108+
print(f" - {item.name} enabled={item.enabled}")
109+
110+
project_client.beta.routines.delete(routine_name)
111+
print("Routine deleted")
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# pylint: disable=line-too-long,useless-suppression
2+
# ------------------------------------
3+
# Copyright (c) Microsoft Corporation.
4+
# Licensed under the MIT License.
5+
# ------------------------------------
6+
7+
"""
8+
DESCRIPTION:
9+
This sample demonstrates how to create a Routine that fires automatically
10+
from a one-shot timer trigger, then record the resulting runs by polling
11+
`list_runs(...)` using the synchronous AIProjectClient.
12+
13+
The routine is bound to an existing hosted agent and scheduled to fire a
14+
short time in the future. The sample then polls the run history until a
15+
terminal phase is reached (or a deadline elapses), printing each observed
16+
transition. The routine is deleted at the end of the sample.
17+
18+
Routines are currently a preview feature. In the Python SDK, you access
19+
these operations via `project_client.beta.routines`.
20+
21+
USAGE:
22+
python sample_routines_with_timer_trigger.py
23+
24+
Before running the sample:
25+
26+
pip install "azure-ai-projects>=2.2.0" python-dotenv
27+
28+
Set these environment variables with your own values:
29+
1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview
30+
page of your Microsoft Foundry portal.
31+
2) FOUNDRY_HOSTED_AGENT_NAME - The name of an existing Hosted Agent to invoke
32+
when the routine timer fires.
33+
"""
34+
35+
import datetime
36+
import json
37+
import os
38+
import time
39+
40+
from dotenv import load_dotenv
41+
42+
from azure.core.exceptions import ResourceNotFoundError
43+
from azure.core.settings import settings
44+
45+
settings.tracing_implementation = "opentelemetry"
46+
from opentelemetry import trace
47+
from opentelemetry.sdk.trace import TracerProvider
48+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, ConsoleSpanExporter
49+
from azure.monitor.opentelemetry import configure_azure_monitor
50+
from azure.ai.projects.telemetry import AIProjectInstrumentor
51+
52+
from azure.identity import DefaultAzureCredential
53+
54+
from azure.ai.projects import AIProjectClient
55+
from azure.ai.projects.models import (
56+
InvokeAgentResponsesApiRoutineAction,
57+
RoutineRun,
58+
RoutineRunPhase,
59+
TimerRoutineTrigger,
60+
)
61+
62+
load_dotenv()
63+
64+
# Console exporter: spans printed to stdout as they finish.
65+
tracer_provider = TracerProvider()
66+
tracer_provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
67+
trace.set_tracer_provider(tracer_provider)
68+
tracer = trace.get_tracer(__name__)
69+
AIProjectInstrumentor().instrument()
70+
71+
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
72+
agent_name = os.environ["FOUNDRY_HOSTED_AGENT_NAME"]
73+
74+
75+
with (
76+
DefaultAzureCredential() as credential,
77+
AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project_client,
78+
):
79+
# Azure Monitor exporter: same spans also sent to the Application Insights
80+
# resource attached to the Foundry project, viewable in the "Tracing" tab
81+
# on ai.azure.com.
82+
configure_azure_monitor(connection_string=project_client.telemetry.get_application_insights_connection_string())
83+
84+
routine_name = "sample-routine-timer"
85+
86+
try:
87+
project_client.beta.routines.delete(routine_name)
88+
print(f"Routine `{routine_name}` deleted")
89+
except ResourceNotFoundError:
90+
pass
91+
92+
fire_at = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=20)
93+
created = project_client.beta.routines.create_or_update(
94+
routine_name,
95+
description="Routine used by the timer-trigger sample.",
96+
enabled=True,
97+
triggers={"once": TimerRoutineTrigger(at=fire_at)},
98+
action=InvokeAgentResponsesApiRoutineAction(agent_name=agent_name),
99+
)
100+
print(f"Created routine: {created.name} enabled={created.enabled} fire_at={fire_at.isoformat()}")
101+
102+
terminal_phases = {RoutineRunPhase.COMPLETED, RoutineRunPhase.FAILED}
103+
seen_phases: dict[str, RoutineRunPhase] = {}
104+
final_run: RoutineRun | None = None
105+
106+
deadline = time.monotonic() + 180
107+
while time.monotonic() < deadline:
108+
runs = list(project_client.beta.routines.list_runs(routine_name, limit=20, order="desc"))
109+
for run in runs:
110+
if run.id is None:
111+
continue
112+
if seen_phases.get(run.id) == run.phase:
113+
continue
114+
seen_phases[run.id] = run.phase # type: ignore[assignment]
115+
print(
116+
f" - run_id={run.id} phase={run.phase} status={run.status} "
117+
f"trigger_type={run.trigger_type} triggered_at={run.triggered_at} ended_at={run.ended_at}"
118+
)
119+
if str(run.status).lower() == "finished":
120+
final_run = run
121+
122+
if final_run is not None:
123+
break
124+
time.sleep(5)
125+
126+
if final_run:
127+
print("Final run:")
128+
print(json.dumps(final_run.as_dict(), indent=2, default=str))
129+
# Note: retrieving the response body produced by a routine-dispatched
130+
# run via `openai_client.responses.retrieve(final_run.response_id)` is
131+
# not yet supported by the service for this scenario.
132+
else:
133+
print("Timer did not produce a terminal run within the deadline.")
134+
135+
project_client.beta.routines.delete(routine_name)
136+
print("Routine deleted")

0 commit comments

Comments
 (0)