Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
11 changes: 9 additions & 2 deletions sdk/ai/azure-ai-projects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,19 @@ Breaking changes in beta methods:
* Added `sample_routines_with_timer_trigger.py` to demonstrate triggering a routine with a timer.
* Added `sample_routines_with_schedule_trigger.py` to demonstrate triggering a routine on a recurring cron schedule via `ScheduleRoutineTrigger`.
* Added `sample_routines_with_dispatch.py` to demonstrate manually firing a routine on demand via `routines.dispatch(...)` using a `CustomRoutineTrigger`.
* Added `sample_skill_in_toolbox.py` demonstrating how to expose a Skill to a Prompt Agent via a Toolbox using `MCPTool`.
* Added new Hosted Agent sample `sample_toolbox_with_skill.py` under `samples/hosted_agents/`, demonstrating a code-based Hosted Agent that uses Toolbox MCP skills.
* Updated `sample_dataset_generation_job_traces_for_evaluation.py` and `sample_dataset_generation_job_traces_for_finetuning.py` to create a temporary agent, seed conversations, retry the data generation job over the trace window, and clean up all created resources.
* Updated `sample_memory_crud.py` and `sample_memory_crud_async.py` to demonstrate memory item CRUD (`create_memory`, `get_memory`, `update_memory`, `list_memories`, `delete_memory`) in addition to memory store CRUD.
* Updated the rubric evaluator generation samples (`sample_rubric_evaluator_generation_basic.py`, `sample_rubric_evaluator_generation_iterate.py`, `sample_rubric_evaluator_generation_lifecycle.py`, `sample_rubric_evaluator_generation_all_sources.py`) to use the typed `EvaluatorGenerationJob` / `EvaluatorGenerationInputs` / `*EvaluatorGenerationJobSource` models. The job inputs are now nested under `inputs` per the service contract, and the traces source uses `datetime` values for `start_time` / `end_time`.
* Updated Hosted Agent code-upload samples (`sample_create_hosted_agent_from_code.py`, `sample_create_hosted_agent_from_code_async.py`) to target runtime `python_3_14`, since `python_3_12` is no longer supported.
* Updated Hosted Agent echo-agent assets (`samples/hosted_agents/assets/echo-agent/main.py`, `echo-agent.zip`, `echo-agent-prebuilt.zip`) to use `@app.response_handler`, resolving a response-handling issue.
* Updated Hosted Agent echo-agent assets (`samples/hosted_agents/assets/echo-agent/main.py`, `echo-agent-prebuilt.zip`) to use `@app.response_handler`, resolving a response-handling issue. The remote-build code-upload sample now builds the echo-agent zip from `samples/hosted_agents/assets/echo-agent/` at runtime instead of relying on a checked-in `echo-agent.zip`, so users can update the agent code and rerun the sample with their changes.
* Updated Skills upload/download samples (`sample_skills_upload_and_download.py`, `sample_skills_upload_and_download_async.py`) to build the `team-status-update.zip` package from `samples/skills/assets/team-status-update/` at runtime instead of relying on a checked-in zip archive, so users can update the skill content and rerun the sample with their changes.
* Updated scheduled evaluation samples (`sample_scheduled_evaluations.py`, `sample_scheduled_agent_traces_evaluation_smart_filter.py`) to import `ResourceManagementClient` from `azure.mgmt.resource.resources`.
* Relocated and renamed `sample_skill_in_toolbox.py` (from `samples/hosted_agents/`) to `samples/agents/tools/sample_agent_toolbox_skill.py`.
* Relocated Skills samples from `samples/hosted_agents/` to `samples/skills/`:
* `sample_skills_crud.py`.
* `sample_skills_upload_and_download.py`.
* Relocated Toolbox sample from `samples/hosted_agents/` to `samples/toolboxes/sample_toolboxes_crud.py`.

## 2.2.0 (2026-05-29)

Expand Down
1 change: 1 addition & 0 deletions sdk/ai/azure-ai-projects/MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ include LICENSE
include azure/ai/projects/py.typed
recursive-include tests *.py
recursive-include samples *.py *.md
recursive-include samples/skills/assets *.txt *.ttf
include azure/__init__.py
include azure/ai/__init__.py
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_44980e76d0"
"Tag": "python/ai/azure-ai-projects_a295b83447"
}
3 changes: 3 additions & 0 deletions sdk/ai/azure-ai-projects/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ pytyped = ["py.typed"]
[tool.azure-sdk-build]
verifytypes = false

[tool.mypy]
exclude = '.*samples[\\/]hosted_agents[\\/]assets[\\/].*main\.py$'

[tool.azure-sdk-conda]
in_bundle = false

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
you access these operations via `project_client.beta.skills`.
USAGE:
python sample_skill_in_toolbox.py
python sample_agent_toolbox_skill.py
Before running the sample:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.mgmt.authorization import AuthorizationManagementClient
from azure.mgmt.resource import ResourceManagementClient
from azure.mgmt.resource.resources import ResourceManagementClient
import uuid
from azure.ai.projects.models import (
TestingCriterionAzureAIEvaluator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.mgmt.authorization import AuthorizationManagementClient
from azure.mgmt.resource import ResourceManagementClient
from azure.mgmt.resource.resources import ResourceManagementClient
import uuid
from azure.ai.projects.models import (
AgentVersionDetails,
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,41 +1,40 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

# import asyncio
# import logging
import asyncio
import logging

# from azure.ai.agentserver.responses import (
# CreateResponse,
# ResponseContext,
# ResponsesAgentServerHost,
# TextResponse,
# )
from azure.ai.agentserver.responses import (
CreateResponse,
ResponseContext,
ResponsesAgentServerHost,
TextResponse,
)

# # Configure logging
# logging.basicConfig(
# level=logging.INFO,
# format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
# )
# logger = logging.getLogger(__name__)
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

# app = ResponsesAgentServerHost()
app = ResponsesAgentServerHost()


# @app.response_handler
# async def handler(request: CreateResponse, context: ResponseContext, cancellation_signal: asyncio.Event):
# """Echo the user's input back as a single message."""
# input_text = await context.get_input_text()
# logger.info(f"Received input: {input_text}")
@app.response_handler
async def handler(
request: CreateResponse, context: ResponseContext, cancellation_signal: asyncio.Event
) -> TextResponse:
"""Echo the user's input back as a single message."""
input_text = await context.get_input_text()
logger.info(f"Received input: {input_text}")

# output_text = f"Echo: {input_text}"
# logger.info(f"Sending output: {output_text}")
output_text = f"Echo: {input_text}"
logger.info(f"Sending output: {output_text}")

# return TextResponse(context, request, text=output_text)
return TextResponse(context, request, text=output_text)


# def main() -> None:
# app.run()
def main() -> None:
app.run()


# if __name__ == "__main__":
# main()
if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
import os
from collections.abc import Callable, Generator

import httpx
from typing import Any, cast
from agent_framework import Agent, SkillsProvider
from agent_framework.foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer # type: ignore[import-untyped]
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from dotenv import load_dotenv
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamable_http_client

MCPSkillsSource = cast(Any, __import__("agent_framework", fromlist=["MCPSkillsSource"]).MCPSkillsSource)

load_dotenv()


class ToolboxAuth(httpx.Auth):
"""Attach a fresh Foundry bearer token to every request."""

requires_response_body = True

def __init__(self, token_provider: Callable[[], str]) -> None:
self._token_provider = token_provider

def auth_flow(self, request: httpx.Request) -> Generator[httpx.Request, httpx.Response, None]:
request.headers["Authorization"] = f"Bearer {self._token_provider()}"
yield request


async def main() -> None:
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
deployment = os.environ["FOUNDRY_MODEL_NAME"]
toolbox_mcp_url = os.environ["MCP_SERVER_URL"]

credential = DefaultAzureCredential()
token_provider = get_bearer_token_provider(credential, "https://ai.azure.com/.default")

async with (
httpx.AsyncClient(
auth=ToolboxAuth(token_provider),
headers={"Foundry-Features": "Toolboxes=V1Preview"},
timeout=httpx.Timeout(30.0, read=300.0),
follow_redirects=True,
) as http_client,
streamable_http_client(
url=toolbox_mcp_url,
http_client=http_client,
) as (read, write, _),
ClientSession(read, write) as session,
):
await session.initialize()

skills_provider = SkillsProvider(MCPSkillsSource(client=session))

agent = Agent(
client=FoundryChatClient(
project_endpoint=project_endpoint,
model=deployment,
credential=credential,
),
name=os.environ.get("AGENT_NAME", "hosted-toolbox-mcp-skills"),
instructions=(
"Use available toolbox skills to answer pricing questions. "
"For shipping cost requests, follow the skill formula exactly "
"and show the formula used."
),
context_providers=[skills_provider],
default_options={"store": False},
)

server = ResponsesHostServer(agent)
await server.run_async()


if __name__ == "__main__":
asyncio.run(main())
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
agent-framework-foundry==1.8.2
agent-framework-foundry-hosting==1.0.0a260618
azure-ai-agentserver-core
azure-ai-agentserver-invocations
azure-ai-agentserver-responses
azure-ai-projects
azure-identity
httpx
mcp
python-dotenv
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import asyncio
import hashlib
import sys
import time
from pathlib import Path
from typing import Tuple

_SAMPLES_DIR = Path(__file__).resolve().parents[1]
if str(_SAMPLES_DIR) not in sys.path:
sys.path.insert(0, str(_SAMPLES_DIR))

from util import build_skill_zip

from azure.ai.projects import AIProjectClient
from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient
from azure.ai.projects.models import (
Expand All @@ -19,25 +26,31 @@ def select_echo_agent_code_zip(
) -> Tuple[CodeDependencyResolution, str, bytes, str]:
"""Pick the dependency-resolution mode and matching echo-agent zip, and load it.

When ``use_remote_build`` is ``True``, returns REMOTE_BUILD with
``assets/echo-agent.zip``; otherwise BUNDLED with
``assets/echo-agent-prebuilt.zip``.
When ``use_remote_build`` is ``True``, returns REMOTE_BUILD with a zip
built from ``assets/echo-agent/``; otherwise BUNDLED with
the checked-in ``assets/echo-agent-prebuilt.zip``.

Reads the zip bytes, computes its SHA-256, and prints a one-line summary.
Computes the zip SHA-256 and prints a one-line summary.

Returns ``(dependency_resolution, zip_filename, zip_bytes, zip_sha256)``.
"""
dependency_resolution = (
CodeDependencyResolution.REMOTE_BUILD if use_remote_build else CodeDependencyResolution.BUNDLED
)
zip_filename = "echo-agent.zip" if use_remote_build else "echo-agent-prebuilt.zip"
zip_path = _ASSETS_DIR / zip_filename
zip_bytes = zip_path.read_bytes()
zip_sha256 = hashlib.sha256(zip_bytes).hexdigest()
print(
f"Loaded code zip from {zip_path} (dependency_resolution={dependency_resolution.value}): "
f"{len(zip_bytes)} bytes, sha256={zip_sha256}"
)

if use_remote_build:
zip_filename = "echo-agent.zip"
zip_bytes, zip_sha256, _ = build_skill_zip(_ASSETS_DIR / "echo-agent", zip_filename)
else:
zip_filename = "echo-agent-prebuilt.zip"
zip_path = _ASSETS_DIR / zip_filename
zip_bytes = zip_path.read_bytes()
zip_sha256 = hashlib.sha256(zip_bytes).hexdigest()
print(
f"Loaded code zip from {zip_path} (dependency_resolution={dependency_resolution.value}): "
f"{len(zip_bytes)} bytes, sha256={zip_sha256}"
)

return dependency_resolution, zip_filename, zip_bytes, zip_sha256


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from azure.core.exceptions import ResourceNotFoundError
from azure.mgmt.authorization import AuthorizationManagementClient, models as authorization_models
from azure.mgmt.authorization.aio import AuthorizationManagementClient as AsyncAuthorizationManagementClient
from azure.mgmt.resource import ResourceManagementClient
from azure.mgmt.resource.resources import ResourceManagementClient
from azure.mgmt.resource.resources.aio import ResourceManagementClient as AsyncResourceManagementClient
from azure.ai.projects.models import AgentVersionDetails

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
* `false` (BUNDLED) — uploads `assets/echo-agent-prebuilt.zip`, which
bundles the agent source plus a `packages/` folder with Linux-built
dependencies, so the service skips pip entirely.
* `true` (REMOTE_BUILD) — uploads `assets/echo-agent.zip`, which contains
only the agent source plus `requirements.txt`; the service resolves
dependencies remotely from the public package index.
* `true` (REMOTE_BUILD) — zips and uploads `assets/echo-agent/`, which
contains only the agent source plus `requirements.txt`; the service
resolves dependencies remotely from the public package index.
The agent must already exist; create it with
`samples/hosted_agents/sample_create_hosted_agent.py`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
* `false` (BUNDLED) — uploads `assets/echo-agent-prebuilt.zip`, which
bundles the agent source plus a `packages/` folder with Linux-built
dependencies, so the service skips pip entirely.
* `true` (REMOTE_BUILD) — uploads `assets/echo-agent.zip`, which contains
only the agent source plus `requirements.txt`; the service resolves
dependencies remotely from the public package index.
* `true` (REMOTE_BUILD) — zips and uploads `assets/echo-agent/`, which
contains only the agent source plus `requirements.txt`; the service
resolves dependencies remotely from the public package index.
The agent must already exist; create it with
`samples/hosted_agents/sample_create_hosted_agent_async.py`.
Expand Down
Loading
Loading