Skip to content

Commit 067059c

Browse files
howieleungCopilotCopilot
authored
Add sample routines for hosted agents with various triggers (#48121)
* Add sample routines for hosted agents with various triggers * Remove commented-out test for routines samples in test_samples.py * Update asset tag and add new sample routines to test_samples.py * Add new sample routines for scheduled and timer triggers in test_samples.py * Remove sample routines CRUD script from hosted_agents directory * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * clean up * Fix azure-ai-projects README routines sample path Co-authored-by: howieleung <177042912+howieleung@users.noreply.github.com> * readme * Update azure-ai-projects changelog sample entry Co-authored-by: howieleung <177042912+howieleung@users.noreply.github.com> * revert chagne log --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: howieleung <177042912+howieleung@users.noreply.github.com>
1 parent 7712632 commit 067059c

8 files changed

Lines changed: 319 additions & 190 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ The table below lists the operation groups supported by the client library, with
186186
| Models (preview) | | `samples/models/` |
187187
| Red teams (preview) | | `samples/red_team/` |
188188
| Responses | [Responses API](https://platform.openai.com/docs/api-reference/responses) | `samples/responses/` |
189-
| Routines (preview) | [Routines overview](https://learn.microsoft.com/azure/foundry/agents/concepts/routines) | `samples/routines/` |
189+
| Routines (preview) | [Routines overview](https://learn.microsoft.com/azure/foundry/agents/concepts/routines) | `samples/hosted_agents/` |
190190
| Sessions | [Manage hosted sessions](https://learn.microsoft.com/azure/foundry/agents/how-to/manage-hosted-sessions?pivots=python) | `samples/hosted_agents/` |
191191
| Skills (preview) | | `samples/skills/` |
192192
| Toolboxes | [Curate intent-based toolbox in Foundry](https://learn.microsoft.com/azure/foundry/agents/how-to/tools/toolbox?pivots=python) | `samples/hosted_agents/`, `samples/toolboxes/` |

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_875c4f833f"
5+
"Tag": "python/ai/azure-ai-projects_f47dcaa04b"
66
}

sdk/ai/azure-ai-projects/samples/routines/sample_routines_with_dispatch.py renamed to sdk/ai/azure-ai-projects/samples/hosted_agents/sample_routines_with_dispatch.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
resulting run by polling `list_runs(...)` using the synchronous
1212
AIProjectClient.
1313
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.
14+
The sample uploads the basic hosted-agent code from `assets/basic-agent/`
15+
as a temporary hosted-agent version and routes the configured hosted agent
16+
name to that version. Because the trigger is a `CustomRoutineTrigger`, the
17+
routine never fires on its own; the sample explicitly invokes it with
18+
`project_client.beta.routines.dispatch(...)` passing an
19+
`InvokeAgentResponsesApiDispatchPayload` carrying the input sent to the
20+
agent. The sample then polls the run history until a terminal phase is
21+
reached (or a deadline elapses), printing each observed transition. The
22+
routine and hosted-agent version are deleted at the end of the sample.
2123
2224
Routines are currently a preview feature. In the Python SDK, you access
2325
these operations via `project_client.beta.routines`.
@@ -32,8 +34,10 @@
3234
Set these environment variables with your own values:
3335
1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview
3436
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+
2) FOUNDRY_MODEL_NAME - The deployment name of the AI model used by the
38+
temporary hosted agent.
39+
3) FOUNDRY_HOSTED_AGENT_NAME - Optional. The Hosted Agent name. Defaults to
40+
`MyHostedAgent`.
3741
"""
3842

3943
import json
@@ -47,22 +51,51 @@
4751

4852
from azure.ai.projects import AIProjectClient
4953
from azure.ai.projects.models import (
54+
CodeConfiguration,
55+
CodeDependencyResolution,
5056
CustomRoutineTrigger,
57+
HostedAgentDefinition,
5158
InvokeAgentResponsesApiDispatchPayload,
5259
InvokeAgentResponsesApiRoutineAction,
60+
ProtocolVersionRecord,
5361
RoutineRun,
5462
RoutineRunPhase,
5563
)
5664

65+
from hosted_agents_util import create_version_from_code, select_basic_agent_code_zip
66+
5767
load_dotenv()
5868

5969
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
60-
agent_name = os.environ["FOUNDRY_HOSTED_AGENT_NAME"]
70+
agent_name = os.environ.get("FOUNDRY_HOSTED_AGENT_NAME", "MyHostedAgent")
71+
model_name = os.environ["FOUNDRY_MODEL_NAME"]
72+
dependency_resolution, code_zip_stream = select_basic_agent_code_zip(True)
6173

6274

6375
with (
76+
code_zip_stream as code_stream,
6477
DefaultAzureCredential() as credential,
6578
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
79+
create_version_from_code(
80+
project_client=project_client,
81+
agent_name=agent_name,
82+
description="Routines dispatch hosted agent uploaded from assets/basic-agent.",
83+
definition=HostedAgentDefinition(
84+
cpu="0.5",
85+
memory="1Gi",
86+
code_configuration=CodeConfiguration(
87+
runtime="python_3_14",
88+
entry_point=["python", "main.py"],
89+
dependency_resolution=CodeDependencyResolution.REMOTE_BUILD,
90+
),
91+
environment_variables={
92+
"FOUNDRY_PROJECT_ENDPOINT": endpoint,
93+
"FOUNDRY_MODEL_NAME": model_name,
94+
},
95+
protocol_versions=[ProtocolVersionRecord(protocol="responses", version="2.0.0")],
96+
),
97+
code=code_stream,
98+
),
6699
):
67100

68101
routine_name = "sample-routine-dispatch"
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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 when a GitHub
10+
issue is opened in a GitHub repository.
11+
12+
The sample first uploads the basic hosted-agent code from
13+
`samples/hosted_agents/assets/basic-agent/` as a temporary hosted-agent
14+
version, routes the configured hosted agent name to that version, and then
15+
creates a routine configured with a `GitHubIssueRoutineTrigger`. The trigger
16+
uses a GitHub-compatible Foundry RemoteTool connection supplied through
17+
`GITHUB_CONNECTION_NAME`. After creating the routine, open an issue in the
18+
configured repository to fire it. The sample polls the routine run history
19+
for a short period and then deletes the routine and hosted-agent version.
20+
21+
Routines are currently a preview feature. In the Python SDK, you access
22+
these operations via `project_client.beta.routines`.
23+
24+
USAGE:
25+
python sample_routines_with_github_issue_trigger.py
26+
27+
Before running the sample:
28+
29+
pip install "azure-ai-projects>=2.3.0" python-dotenv
30+
31+
Set these environment variables with your own values:
32+
1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview
33+
page of your Microsoft Foundry portal.
34+
2) FOUNDRY_MODEL_NAME - The deployment name of the AI model used by the
35+
temporary hosted agent.
36+
3) FOUNDRY_HOSTED_AGENT_NAME - Optional. The hosted agent name to route to
37+
the temporary uploaded version. Defaults to `MyHostedAgent`.
38+
4) GITHUB_CONNECTION_NAME - The Foundry GitHub RemoteTool connection name.
39+
The connection must be GitHub-compatible and use PAT or OAuth2 credentials.
40+
5) GITHUB_USERNAME - The GitHub owner or organization name.
41+
6) GITHUB_REPOSITORY - The GitHub repository name in the format of https://github.com/xxx/xxx.git.
42+
7) POLL_INTERVAL_SECONDS - Optional. Seconds to sleep between run-history polls.
43+
Defaults to 10.
44+
"""
45+
46+
import json
47+
import os
48+
import time
49+
50+
from dotenv import load_dotenv
51+
52+
from azure.core.exceptions import ResourceNotFoundError
53+
from azure.identity import DefaultAzureCredential
54+
55+
from azure.ai.projects import AIProjectClient
56+
from azure.ai.projects.models import (
57+
CodeConfiguration,
58+
GitHubIssueEvent,
59+
GitHubIssueRoutineTrigger,
60+
HostedAgentDefinition,
61+
InvokeAgentResponsesApiRoutineAction,
62+
ProtocolVersionRecord,
63+
RoutineRun,
64+
)
65+
66+
from hosted_agents_util import create_version_from_code, select_basic_agent_code_zip
67+
68+
load_dotenv()
69+
70+
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
71+
agent_name = os.environ.get("FOUNDRY_HOSTED_AGENT_NAME", "MyHostedAgent")
72+
model_name = os.environ["FOUNDRY_MODEL_NAME"]
73+
github_connection_name = os.environ["GITHUB_CONNECTION_NAME"]
74+
poll_interval_seconds = int(os.environ.get("POLL_INTERVAL_SECONDS", "10"))
75+
76+
github_owner = os.environ["GITHUB_USERNAME"]
77+
github_repository = os.environ["GITHUB_REPOSITORY"]
78+
79+
80+
def main() -> None:
81+
dependency_resolution, code_zip_stream = select_basic_agent_code_zip(True)
82+
83+
with (
84+
code_zip_stream as code_stream,
85+
DefaultAzureCredential() as credential,
86+
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
87+
create_version_from_code(
88+
project_client=project_client,
89+
agent_name=agent_name,
90+
description="GitHub issue routine sample hosted agent uploaded from assets/basic-agent.",
91+
definition=HostedAgentDefinition(
92+
cpu="0.5",
93+
memory="1Gi",
94+
code_configuration=CodeConfiguration(
95+
runtime="python_3_14",
96+
entry_point=["python", "main.py"],
97+
dependency_resolution=dependency_resolution,
98+
),
99+
environment_variables={
100+
"FOUNDRY_PROJECT_ENDPOINT": endpoint,
101+
"FOUNDRY_MODEL_NAME": model_name,
102+
},
103+
protocol_versions=[ProtocolVersionRecord(protocol="responses", version="2.0.0")],
104+
),
105+
code=code_stream,
106+
),
107+
):
108+
routine_name = "sample-routine-github-issue"
109+
110+
print(f"Preparing routine `{routine_name}` for {github_repository}.")
111+
try:
112+
print(f"Deleting any existing routine `{routine_name}`.")
113+
project_client.beta.routines.delete(routine_name)
114+
print(f"Routine `{routine_name}` deleted")
115+
except ResourceNotFoundError:
116+
pass
117+
118+
print(f"Creating routine `{routine_name}`.")
119+
created = project_client.beta.routines.create_or_update(
120+
routine_name,
121+
description="Routine used by the GitHub issue trigger sample.",
122+
enabled=True,
123+
triggers={
124+
"on-issue": GitHubIssueRoutineTrigger(
125+
connection_id=github_connection_name, # Currently accepts a connection name.
126+
owner=github_owner,
127+
repository=github_repository,
128+
issue_event=GitHubIssueEvent.OPENED,
129+
),
130+
},
131+
action=InvokeAgentResponsesApiRoutineAction(agent_name=agent_name),
132+
)
133+
print(
134+
f"Created routine: {created.name} enabled={created.enabled} "
135+
f"repo={github_owner}/{github_repository} event={GitHubIssueEvent.OPENED}"
136+
)
137+
print(f"Open a GitHub issue in {github_repository} to fire the routine.")
138+
print("Waiting for a routine run for up to 10 minutes...")
139+
140+
try:
141+
seen_phases: dict[str, str] = {}
142+
final_run: RoutineRun | None = None
143+
run_was_triggered = False
144+
terminal_statuses = {"finished", "failed", "killed"}
145+
146+
deadline = time.monotonic() + 600
147+
while time.monotonic() < deadline:
148+
runs = list(project_client.beta.routines.list_runs(routine_name, limit=20, order="desc"))
149+
for run in runs:
150+
run_was_triggered = True
151+
current_phase = str(run.phase)
152+
if seen_phases.get(run.id) == current_phase:
153+
continue
154+
seen_phases[run.id] = current_phase
155+
print(
156+
f" - run_id={run.id} phase={run.phase} status={run.status} "
157+
f"trigger_type={run.trigger_type} triggered_at={run.triggered_at} ended_at={run.ended_at}"
158+
)
159+
if str(run.status).lower() in terminal_statuses:
160+
final_run = run
161+
162+
if final_run is not None:
163+
break
164+
time.sleep(poll_interval_seconds)
165+
166+
if final_run:
167+
print("Final run:")
168+
print(json.dumps(final_run.as_dict(), indent=2, default=str))
169+
print(f"The response Id is {final_run.response_id}")
170+
elif run_was_triggered:
171+
print("A routine run was observed, but no terminal run state was reached within the deadline.")
172+
else:
173+
print("No GitHub issue-triggered run was observed within the deadline.")
174+
except KeyboardInterrupt:
175+
print("Interrupted by user; cleaning up routine before exiting.")
176+
finally:
177+
try:
178+
project_client.beta.routines.delete(routine_name)
179+
print("Routine deleted")
180+
except ResourceNotFoundError:
181+
pass
182+
183+
184+
if __name__ == "__main__":
185+
main()

sdk/ai/azure-ai-projects/samples/routines/sample_routines_with_schedule_trigger.py renamed to sdk/ai/azure-ai-projects/samples/hosted_agents/sample_routines_with_schedule_trigger.py

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@
1010
recurring cron schedule, then record the resulting runs by polling
1111
`list_runs(...)` using the synchronous AIProjectClient.
1212
13-
The routine is bound to an existing hosted agent and scheduled with a
13+
The sample uploads the basic hosted-agent code from `assets/basic-agent/`
14+
as a temporary hosted-agent version, routes the configured hosted agent
15+
name to that version, and schedules the routine with a
1416
`ScheduleRoutineTrigger` using a 5-field cron expression. The service
1517
enforces a minimum interval of five minutes, so the sample polls the
1618
run history for up to ~6 minutes to catch the first fire, prints each
17-
observed phase transition, then deletes the routine.
19+
observed phase transition, then deletes the routine and hosted-agent
20+
version.
1821
1922
Routines are currently a preview feature. In the Python SDK, you access
2023
these operations via `project_client.beta.routines`.
@@ -29,15 +32,19 @@
2932
Set these environment variables with your own values:
3033
1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview
3134
page of your Microsoft Foundry portal.
32-
2) FOUNDRY_HOSTED_AGENT_NAME - The name of an existing Hosted Agent to invoke
33-
when the routine schedule fires.
34-
3) POLL_INTERVAL_SECONDS - Optional. Seconds to sleep between run-history polls.
35-
Defaults to 15.
35+
2) FOUNDRY_MODEL_NAME - The deployment name of the AI model used by the
36+
temporary hosted agent.
37+
3) FOUNDRY_HOSTED_AGENT_NAME - Optional. The Hosted Agent name. Defaults to
38+
`MyHostedAgent`.
39+
4) POLL_INTERVAL_SECONDS - Optional. Seconds to sleep between run-history polls.
40+
Defaults to 15.
3641
"""
3742

3843
import json
3944
import os
45+
import sys
4046
import time
47+
from pathlib import Path
4148

4249
from dotenv import load_dotenv
4350

@@ -46,23 +53,55 @@
4653

4754
from azure.ai.projects import AIProjectClient
4855
from azure.ai.projects.models import (
56+
CodeConfiguration,
57+
HostedAgentDefinition,
4958
InvokeAgentResponsesApiRoutineAction,
59+
ProtocolVersionRecord,
5060
RoutineRun,
5161
RoutineRunPhase,
5262
ScheduleRoutineTrigger,
5363
)
5464

65+
_HOSTED_AGENTS_DIR = Path(__file__).resolve().parent
66+
if str(_HOSTED_AGENTS_DIR) not in sys.path:
67+
sys.path.insert(0, str(_HOSTED_AGENTS_DIR))
68+
69+
from hosted_agents_util import create_version_from_code, select_basic_agent_code_zip
70+
5571
load_dotenv()
5672

5773
endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
58-
agent_name = os.environ["FOUNDRY_HOSTED_AGENT_NAME"]
74+
agent_name = os.environ.get("FOUNDRY_HOSTED_AGENT_NAME", "MyHostedAgent")
75+
model_name = os.environ["FOUNDRY_MODEL_NAME"]
5976
poll_interval_seconds = int(os.environ.get("POLL_INTERVAL_SECONDS", "15"))
77+
dependency_resolution, code_zip_stream = select_basic_agent_code_zip(True)
6078

6179

6280
def main() -> None:
6381
with (
82+
code_zip_stream as code_stream,
6483
DefaultAzureCredential() as credential,
6584
AIProjectClient(endpoint=endpoint, credential=credential) as project_client,
85+
create_version_from_code(
86+
project_client=project_client,
87+
agent_name=agent_name,
88+
description="Routines schedule hosted agent uploaded from assets/basic-agent.",
89+
definition=HostedAgentDefinition(
90+
cpu="0.5",
91+
memory="1Gi",
92+
code_configuration=CodeConfiguration(
93+
runtime="python_3_14",
94+
entry_point=["python", "main.py"],
95+
dependency_resolution=dependency_resolution,
96+
),
97+
environment_variables={
98+
"FOUNDRY_PROJECT_ENDPOINT": endpoint,
99+
"FOUNDRY_MODEL_NAME": model_name,
100+
},
101+
protocol_versions=[ProtocolVersionRecord(protocol="responses", version="2.0.0")],
102+
),
103+
code=code_stream,
104+
),
66105
):
67106
routine_name = "sample-routine-schedule"
68107

0 commit comments

Comments
 (0)