Skip to content

Commit cc0cd5a

Browse files
committed
Add sample for manually dispatched routines and update test references
1 parent 2fb56bf commit cc0cd5a

2 files changed

Lines changed: 131 additions & 0 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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 with a manual (custom)
10+
trigger and fire it on demand via `dispatch(...)`, then record the
11+
resulting run by polling `list_runs(...)` using the synchronous
12+
AIProjectClient.
13+
14+
The routine is bound to an existing hosted agent. Because the trigger is
15+
a `CustomRoutineTrigger`, the routine never fires on its own; the sample
16+
explicitly invokes it with `project_client.beta.routines.dispatch(...)`
17+
passing an `InvokeAgentResponsesApiDispatchPayload` carrying the input
18+
sent to the agent. The sample then polls the run history until a
19+
terminal phase is reached (or a deadline elapses), printing each
20+
observed transition. The routine is deleted at the end of the sample.
21+
22+
Routines are currently a preview feature. In the Python SDK, you access
23+
these operations via `project_client.beta.routines`.
24+
25+
USAGE:
26+
python sample_routines_with_dispatch.py
27+
28+
Before running the sample:
29+
30+
pip install "azure-ai-projects>=2.2.0" python-dotenv
31+
32+
Set these environment variables with your own values:
33+
1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview
34+
page of your Microsoft Foundry portal.
35+
2) FOUNDRY_HOSTED_AGENT_NAME - The name of an existing Hosted Agent to invoke
36+
when the routine is dispatched.
37+
"""
38+
39+
import json
40+
import os
41+
import time
42+
43+
from dotenv import load_dotenv
44+
45+
from azure.core.exceptions import ResourceNotFoundError
46+
from azure.identity import DefaultAzureCredential
47+
48+
from azure.ai.projects import AIProjectClient
49+
from azure.ai.projects.models import (
50+
CustomRoutineTrigger,
51+
InvokeAgentResponsesApiDispatchPayload,
52+
InvokeAgentResponsesApiRoutineAction,
53+
RoutineRun,
54+
RoutineRunPhase,
55+
)
56+
57+
load_dotenv()
58+
59+
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
60+
agent_name = os.environ["FOUNDRY_HOSTED_AGENT_NAME"]
61+
62+
63+
with (
64+
DefaultAzureCredential() as credential,
65+
AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project_client,
66+
):
67+
68+
routine_name = "sample-routine-dispatch"
69+
70+
try:
71+
project_client.beta.routines.delete(routine_name)
72+
print(f"Routine `{routine_name}` deleted")
73+
except ResourceNotFoundError:
74+
pass
75+
76+
created = project_client.beta.routines.create_or_update(
77+
routine_name,
78+
description="Routine used by the dispatch sample.",
79+
enabled=True,
80+
triggers={
81+
"manual": CustomRoutineTrigger(
82+
provider="sample-provider",
83+
event_name="sample-event",
84+
parameters={},
85+
),
86+
},
87+
action=InvokeAgentResponsesApiRoutineAction(agent_name=agent_name),
88+
)
89+
print(f"Created routine: {created.name} enabled={created.enabled}")
90+
91+
dispatch_result = project_client.beta.routines.dispatch(
92+
routine_name,
93+
payload=InvokeAgentResponsesApiDispatchPayload(
94+
input="Say hello from a manually dispatched routine.",
95+
),
96+
)
97+
print(f"Dispatched routine: dispatch_id={dispatch_result.dispatch_id} task_id={dispatch_result.task_id}")
98+
99+
seen_phases: dict[str, RoutineRunPhase] = {}
100+
final_run: RoutineRun | None = None
101+
102+
deadline = time.monotonic() + 180
103+
while time.monotonic() < deadline:
104+
runs = list(project_client.beta.routines.list_runs(routine_name, limit=20, order="desc"))
105+
for run in runs:
106+
if seen_phases.get(run.id) == run.phase:
107+
continue
108+
seen_phases[run.id] = run.phase # type: ignore[assignment]
109+
print(
110+
f" - run_id={run.id} phase={run.phase} status={run.status} "
111+
f"trigger_type={run.trigger_type} triggered_at={run.triggered_at} ended_at={run.ended_at}"
112+
)
113+
if str(run.status).lower() == "finished":
114+
final_run = run
115+
116+
if final_run is not None:
117+
break
118+
time.sleep(5)
119+
120+
if final_run:
121+
print("Final run:")
122+
print(json.dumps(final_run.as_dict(), indent=2, default=str))
123+
# Note: retrieving the response body produced by a routine-dispatched
124+
# run via `openai_client.responses.retrieve(final_run.response_id)` is
125+
# not yet supported by the service for this scenario.
126+
else:
127+
print("Dispatch did not produce a terminal run within the deadline.")
128+
129+
project_client.beta.routines.delete(routine_name)
130+
print("Routine deleted")

sdk/ai/azure-ai-projects/tests/samples/test_samples.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ def test_chat_completions_samples(self, sample_path: str, **kwargs) -> None:
257257
"sample_routines_with_schedule_trigger.py", # Specify through AdditionalSampleTestDetail
258258
"sample_routines_crud.py", # Skipped due to service serialization issues
259259
"sample_routines_with_timer_trigger.py", # Skipped due to service serialization issues
260+
"sample_routines_with_dispatch.py", # 403: test identity lacks routines/dispatch data-action
260261
],
261262
),
262263
)

0 commit comments

Comments
 (0)