mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Merge branch 'main' into feat/durable_task
This commit is contained in:
@@ -8,6 +8,10 @@ inputs:
|
||||
os:
|
||||
description: The operating system to set up
|
||||
required: true
|
||||
exclude-packages:
|
||||
description: Space-separated list of packages to exclude from uv sync
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
@@ -19,6 +23,20 @@ runs:
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ inputs.os }}-${{ inputs.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Exclude incompatible workspace packages
|
||||
if: ${{ inputs.exclude-packages != '' }}
|
||||
shell: bash
|
||||
run: |
|
||||
for pkg in ${{ inputs.exclude-packages }}; do
|
||||
for f in python/packages/*/pyproject.toml; do
|
||||
if grep -q "name = \"$pkg\"" "$f"; then
|
||||
pkg_dir=$(dirname "$f" | sed 's|python/||')
|
||||
echo "Excluding workspace package: $pkg ($pkg_dir)"
|
||||
sed -i.bak '/\[tool\.uv\.workspace\]/a\exclude = ["'"$pkg_dir"'"]' python/pyproject.toml
|
||||
sed -i.bak '/'"$pkg"' = { workspace = true }/d' python/pyproject.toml
|
||||
fi
|
||||
done
|
||||
done
|
||||
- name: Install the project
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10"]
|
||||
python-version: ["3.11"]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
defaults:
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10"]
|
||||
python-version: ["3.11"]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
defaults:
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10"]
|
||||
python-version: ["3.11"]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
defaults:
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10"]
|
||||
python-version: ["3.11"]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
defaults:
|
||||
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
environment: integration
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
UV_PYTHON: "3.10"
|
||||
UV_PYTHON: "3.11"
|
||||
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||
|
||||
@@ -67,6 +67,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
os: ${{ runner.os }}
|
||||
exclude-packages: ${{ matrix.python-version == '3.10' && 'agent-framework-github-copilot' || '' }}
|
||||
env:
|
||||
# Configure a constant location for the uv cache
|
||||
UV_CACHE_DIR: /tmp/.uv-cache
|
||||
|
||||
@@ -288,7 +288,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: integration
|
||||
env:
|
||||
UV_PYTHON: "3.10"
|
||||
UV_PYTHON: "3.11"
|
||||
OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI__CHATMODELID }}
|
||||
OPENAI_RESPONSES_MODEL_ID: ${{ vars.OPENAI__RESPONSESMODELID }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI__APIKEY }}
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
run:
|
||||
working-directory: python
|
||||
env:
|
||||
UV_PYTHON: "3.10"
|
||||
UV_PYTHON: "3.11"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
# Save the PR number to a file since the workflow_run event
|
||||
|
||||
@@ -34,12 +34,13 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
os: ${{ runner.os }}
|
||||
exclude-packages: ${{ matrix.python-version == '3.10' && 'agent-framework-github-copilot' || '' }}
|
||||
env:
|
||||
# Configure a constant location for the uv cache
|
||||
UV_CACHE_DIR: /tmp/.uv-cache
|
||||
# Unit tests
|
||||
- name: Run all tests
|
||||
run: uv run poe all-tests
|
||||
run: uv run poe all-tests ${{ matrix.python-version == '3.10' && '--ignore-glob=packages/github_copilot/**' || '' }}
|
||||
working-directory: ./python
|
||||
|
||||
# Surface failing tests
|
||||
|
||||
@@ -20,6 +20,13 @@ When making changes to a package, check if the following need updates:
|
||||
- The package's `AGENTS.md` file (adding/removing/renaming public APIs, architecture changes, import path changes)
|
||||
- The agent skills in `.github/skills/` if conventions, commands, or workflows change
|
||||
|
||||
## Pull Request Description Guidance
|
||||
|
||||
When preparing a PR description:
|
||||
- Follow the repository PR template at `.github/pull_request_template.md` and keep its structure/headings.
|
||||
- Describe the net change relative to `main` (this is implied; do not call it out explicitly as "vs main").
|
||||
- Do not add ad-hoc validation sections (for example, "Validation" or "Tests run"); CI/CD and the template checklist cover validation status.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
Run `uv run poe` from the `python/` directory to see available commands. See [DEV_SETUP.md](DEV_SETUP.md) for detailed usage.
|
||||
|
||||
@@ -37,9 +37,8 @@ from agent_framework.openai._responses_client import RawOpenAIResponsesClient
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import (
|
||||
ApproximateLocation,
|
||||
CodeInterpreterContainerAuto,
|
||||
AutoCodeInterpreterToolParam,
|
||||
CodeInterpreterTool,
|
||||
FoundryFeaturesOptInKeys,
|
||||
ImageGenTool,
|
||||
MCPTool,
|
||||
PromptAgentDefinition,
|
||||
@@ -66,7 +65,6 @@ if sys.version_info >= (3, 11):
|
||||
else:
|
||||
from typing_extensions import Self, TypedDict # type: ignore # pragma: no cover
|
||||
|
||||
|
||||
logger = logging.getLogger("agent_framework.azure")
|
||||
|
||||
|
||||
@@ -79,9 +77,6 @@ class AzureAIProjectAgentOptions(OpenAIResponsesOptions, total=False):
|
||||
reasoning: Reasoning # type: ignore[misc]
|
||||
"""Configuration for enabling reasoning capabilities (requires azure.ai.projects.models.Reasoning)."""
|
||||
|
||||
foundry_features: FoundryFeaturesOptInKeys | str
|
||||
"""Optional Foundry preview feature opt-in for agent version creation."""
|
||||
|
||||
|
||||
AzureAIClientOptionsT = TypeVar(
|
||||
"AzureAIClientOptionsT",
|
||||
@@ -123,6 +118,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
model_deployment_name: str | None = None,
|
||||
credential: AzureCredentialTypes | None = None,
|
||||
use_latest_version: bool | None = None,
|
||||
allow_preview: bool | None = None,
|
||||
env_file_path: str | None = None,
|
||||
env_file_encoding: str | None = None,
|
||||
**kwargs: Any,
|
||||
@@ -148,6 +144,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
AsyncTokenCredential, or a callable token provider.
|
||||
use_latest_version: Boolean flag that indicates whether to use latest agent version
|
||||
if it exists in the service.
|
||||
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
|
||||
env_file_path: Path to environment file for loading settings.
|
||||
env_file_encoding: Encoding of the environment file.
|
||||
kwargs: Additional keyword arguments passed to the parent class.
|
||||
@@ -208,11 +205,14 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
# Use provided credential
|
||||
if not credential:
|
||||
raise ValueError("Azure credential is required when project_client is not provided.")
|
||||
project_client = AIProjectClient(
|
||||
endpoint=resolved_endpoint,
|
||||
credential=credential, # type: ignore[arg-type]
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
project_client_kwargs: dict[str, Any] = {
|
||||
"endpoint": resolved_endpoint,
|
||||
"credential": credential, # type: ignore[arg-type]
|
||||
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
|
||||
}
|
||||
if allow_preview is not None:
|
||||
project_client_kwargs["allow_preview"] = allow_preview
|
||||
project_client = AIProjectClient(**project_client_kwargs)
|
||||
should_close_client = True
|
||||
|
||||
# Initialize parent
|
||||
@@ -413,8 +413,6 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
"definition": PromptAgentDefinition(**args),
|
||||
"description": self.agent_description,
|
||||
}
|
||||
if foundry_features := run_options.get("foundry_features"):
|
||||
create_version_kwargs["foundry_features"] = foundry_features
|
||||
|
||||
created_agent = await self.project_client.agents.create_version(**create_version_kwargs)
|
||||
|
||||
@@ -513,7 +511,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
"temperature": ("temperature",),
|
||||
"top_p": ("top_p",),
|
||||
"reasoning": ("reasoning",),
|
||||
"foundry_features": ("foundry_features",),
|
||||
"allow_preview": ("allow_preview",),
|
||||
}
|
||||
|
||||
for run_keys in agent_level_option_to_run_keys.values():
|
||||
@@ -939,7 +937,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[AzureAIClientOptionsT], Generic[
|
||||
if file_ids is None and isinstance(container, dict):
|
||||
file_ids = container.get("file_ids")
|
||||
resolved = resolve_file_ids(file_ids)
|
||||
tool_container = CodeInterpreterContainerAuto(file_ids=resolved)
|
||||
tool_container = AutoCodeInterpreterToolParam(file_ids=resolved)
|
||||
return CodeInterpreterTool(container=tool_container, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
@@ -1244,6 +1242,7 @@ class AzureAIClient(
|
||||
model_deployment_name: str | None = None,
|
||||
credential: AzureCredentialTypes | None = None,
|
||||
use_latest_version: bool | None = None,
|
||||
allow_preview: bool | None = None,
|
||||
middleware: Sequence[ChatAndFunctionMiddlewareTypes] | None = None,
|
||||
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
|
||||
env_file_path: str | None = None,
|
||||
@@ -1268,6 +1267,7 @@ class AzureAIClient(
|
||||
or AsyncTokenCredential.
|
||||
use_latest_version: Boolean flag that indicates whether to use latest agent version
|
||||
if it exists in the service.
|
||||
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``
|
||||
middleware: Optional sequence of chat middlewares to include.
|
||||
function_invocation_configuration: Optional function invocation configuration.
|
||||
env_file_path: Path to environment file for loading settings.
|
||||
@@ -1318,6 +1318,7 @@ class AzureAIClient(
|
||||
model_deployment_name=model_deployment_name,
|
||||
credential=credential,
|
||||
use_latest_version=use_latest_version,
|
||||
allow_preview=allow_preview,
|
||||
middleware=middleware,
|
||||
function_invocation_configuration=function_invocation_configuration,
|
||||
env_file_path=env_file_path,
|
||||
|
||||
@@ -18,6 +18,7 @@ from agent_framework._sessions import AgentSession, BaseContextProvider, Session
|
||||
from agent_framework._settings import load_settings
|
||||
from agent_framework.azure._entra_id_authentication import AzureCredentialTypes
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from openai.types.responses import ResponseInputItemParam
|
||||
|
||||
from ._shared import AzureAISettings
|
||||
|
||||
@@ -58,6 +59,7 @@ class FoundryMemoryProvider(BaseContextProvider):
|
||||
project_client: AIProjectClient | None = None,
|
||||
project_endpoint: str | None = None,
|
||||
credential: AzureCredentialTypes | None = None,
|
||||
allow_preview: bool | None = None,
|
||||
memory_store_name: str,
|
||||
scope: str | None = None,
|
||||
context_prompt: str | None = None,
|
||||
@@ -74,6 +76,7 @@ class FoundryMemoryProvider(BaseContextProvider):
|
||||
credential: Azure credential for authentication. Accepts a TokenCredential,
|
||||
AsyncTokenCredential, or a callable token provider.
|
||||
Required when project_client is not provided.
|
||||
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
|
||||
memory_store_name: The name of the memory store to use.
|
||||
scope: The namespace that logically groups and isolates memories (e.g., user ID).
|
||||
If None, `session_id` will be used.
|
||||
@@ -100,11 +103,14 @@ class FoundryMemoryProvider(BaseContextProvider):
|
||||
)
|
||||
if not credential:
|
||||
raise ValueError("Azure credential is required when project_client is not provided.")
|
||||
project_client = AIProjectClient(
|
||||
endpoint=resolved_endpoint,
|
||||
credential=credential, # type: ignore[arg-type]
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
project_client_kwargs: dict[str, Any] = {
|
||||
"endpoint": resolved_endpoint,
|
||||
"credential": credential, # type: ignore[arg-type]
|
||||
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
|
||||
}
|
||||
if allow_preview is not None:
|
||||
project_client_kwargs["allow_preview"] = allow_preview
|
||||
project_client = AIProjectClient(**project_client_kwargs)
|
||||
|
||||
if not memory_store_name:
|
||||
raise ValueError("memory_store_name is required")
|
||||
@@ -169,8 +175,8 @@ class FoundryMemoryProvider(BaseContextProvider):
|
||||
return
|
||||
|
||||
# Convert input messages to memory search item format
|
||||
items = [
|
||||
{"type": "text", "text": msg.text}
|
||||
items: list[ResponseInputItemParam] = [
|
||||
{"type": "message", "role": "user", "content": msg.text}
|
||||
for msg in context.input_messages
|
||||
if msg and msg.text and msg.text.strip()
|
||||
]
|
||||
@@ -224,7 +230,7 @@ class FoundryMemoryProvider(BaseContextProvider):
|
||||
messages_to_store.extend(context.response.messages)
|
||||
|
||||
# Filter and convert messages to memory update item format
|
||||
items: list[dict[str, str]] = []
|
||||
items: list[ResponseInputItemParam] = []
|
||||
for message in messages_to_store:
|
||||
if message.role in {"user", "assistant", "system"} and message.text and message.text.strip():
|
||||
if message.role == "user":
|
||||
|
||||
@@ -102,6 +102,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
project_endpoint: str | None = None,
|
||||
model: str | None = None,
|
||||
credential: AzureCredentialTypes | None = None,
|
||||
allow_preview: bool | None = None,
|
||||
env_file_path: str | None = None,
|
||||
env_file_encoding: str | None = None,
|
||||
) -> None:
|
||||
@@ -117,6 +118,7 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
credential: Azure credential for authentication. Accepts a TokenCredential,
|
||||
AsyncTokenCredential, or a callable token provider.
|
||||
Required when project_client is not provided.
|
||||
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
|
||||
env_file_path: Path to environment file for loading settings.
|
||||
env_file_encoding: Encoding of the environment file.
|
||||
|
||||
@@ -146,11 +148,14 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
if not credential:
|
||||
raise ValueError("Azure credential is required when project_client is not provided.")
|
||||
|
||||
project_client = AIProjectClient(
|
||||
endpoint=resolved_endpoint,
|
||||
credential=credential, # type: ignore[arg-type]
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
project_client_kwargs: dict[str, Any] = {
|
||||
"endpoint": resolved_endpoint,
|
||||
"credential": credential, # type: ignore[arg-type]
|
||||
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
|
||||
}
|
||||
if allow_preview is not None:
|
||||
project_client_kwargs["allow_preview"] = allow_preview
|
||||
project_client = AIProjectClient(**project_client_kwargs)
|
||||
self._should_close_client = True
|
||||
|
||||
self._project_client = project_client
|
||||
@@ -199,7 +204,6 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
response_format = opts.get("response_format")
|
||||
rai_config = opts.get("rai_config")
|
||||
reasoning = opts.get("reasoning")
|
||||
foundry_features = opts.get("foundry_features")
|
||||
|
||||
args: dict[str, Any] = {"model": resolved_model}
|
||||
|
||||
@@ -246,8 +250,6 @@ class AzureAIProjectAgentProvider(Generic[OptionsCoT]):
|
||||
"definition": PromptAgentDefinition(**args),
|
||||
"description": description,
|
||||
}
|
||||
if foundry_features:
|
||||
create_version_kwargs["foundry_features"] = foundry_features
|
||||
|
||||
created_agent = await self._project_client.agents.create_version(**create_version_kwargs)
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@ from azure.ai.agents.models import (
|
||||
from azure.ai.projects.models import (
|
||||
CodeInterpreterTool,
|
||||
MCPTool,
|
||||
TextResponseFormatConfigurationResponseFormatJsonObject,
|
||||
TextResponseFormatConfigurationResponseFormatText,
|
||||
TextResponseFormatJsonObject,
|
||||
TextResponseFormatJsonSchema,
|
||||
TextResponseFormatText,
|
||||
Tool,
|
||||
WebSearchPreviewTool,
|
||||
)
|
||||
@@ -479,11 +479,7 @@ def _prepare_mcp_tool_dict_for_azure_ai(tool_dict: dict[str, Any]) -> MCPTool:
|
||||
|
||||
def create_text_format_config(
|
||||
response_format: type[BaseModel] | Mapping[str, Any],
|
||||
) -> (
|
||||
TextResponseFormatJsonSchema
|
||||
| TextResponseFormatConfigurationResponseFormatJsonObject
|
||||
| TextResponseFormatConfigurationResponseFormatText
|
||||
):
|
||||
) -> TextResponseFormatJsonSchema | TextResponseFormatJsonObject | TextResponseFormatText:
|
||||
"""Convert response_format into Azure text format configuration."""
|
||||
if isinstance(response_format, type) and issubclass(response_format, BaseModel):
|
||||
schema = response_format.model_json_schema()
|
||||
@@ -513,9 +509,9 @@ def create_text_format_config(
|
||||
config_kwargs["description"] = format_config["description"]
|
||||
return TextResponseFormatJsonSchema(**config_kwargs)
|
||||
if format_type == "json_object":
|
||||
return TextResponseFormatConfigurationResponseFormatJsonObject()
|
||||
return TextResponseFormatJsonObject()
|
||||
if format_type == "text":
|
||||
return TextResponseFormatConfigurationResponseFormatText()
|
||||
return TextResponseFormatText()
|
||||
|
||||
raise IntegrationInvalidRequestException("response_format must be a Pydantic model or mapping.")
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ from agent_framework.openai._responses_client import RawOpenAIResponsesClient
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import (
|
||||
ApproximateLocation,
|
||||
CodeInterpreterContainerAuto,
|
||||
AutoCodeInterpreterToolParam,
|
||||
CodeInterpreterTool,
|
||||
FileSearchTool,
|
||||
ImageGenTool,
|
||||
@@ -1296,7 +1296,7 @@ def test_from_azure_ai_tools_mcp() -> None:
|
||||
|
||||
def test_from_azure_ai_tools_code_interpreter() -> None:
|
||||
"""Test from_azure_ai_tools with Code Interpreter tool."""
|
||||
ci_tool = CodeInterpreterTool(container=CodeInterpreterContainerAuto(file_ids=["file-1"]))
|
||||
ci_tool = CodeInterpreterTool(container=AutoCodeInterpreterToolParam(file_ids=["file-1"]))
|
||||
parsed_tools = from_azure_ai_tools([ci_tool])
|
||||
assert len(parsed_tools) == 1
|
||||
assert parsed_tools[0]["type"] == "code_interpreter"
|
||||
|
||||
@@ -86,6 +86,7 @@ class TestInit:
|
||||
provider = FoundryMemoryProvider(
|
||||
project_endpoint="https://test.project.endpoint",
|
||||
credential=mock_credential, # type: ignore[arg-type]
|
||||
allow_preview=True,
|
||||
memory_store_name="test_store",
|
||||
scope="user_123",
|
||||
)
|
||||
@@ -93,6 +94,7 @@ class TestInit:
|
||||
mock_ai_project_client.assert_called_once_with(
|
||||
endpoint="https://test.project.endpoint",
|
||||
credential=mock_credential,
|
||||
allow_preview=True,
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
|
||||
|
||||
@@ -112,9 +112,7 @@ class SkillResource:
|
||||
self._accepts_kwargs: bool = False
|
||||
if function is not None:
|
||||
sig = inspect.signature(function)
|
||||
self._accepts_kwargs = any(
|
||||
p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()
|
||||
)
|
||||
self._accepts_kwargs = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values())
|
||||
|
||||
|
||||
class Skill:
|
||||
|
||||
@@ -1458,7 +1458,7 @@ def _update_conversation_id(
|
||||
if conversation_id is None:
|
||||
return
|
||||
if "chat_options" in kwargs:
|
||||
kwargs["chat_options"].conversation_id = conversation_id
|
||||
kwargs["chat_options"]["conversation_id"] = conversation_id
|
||||
else:
|
||||
kwargs["conversation_id"] = conversation_id
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
||||
async_client: AsyncOpenAI | None = None,
|
||||
project_client: Any | None = None,
|
||||
project_endpoint: str | None = None,
|
||||
allow_preview: bool | None = None,
|
||||
env_file_path: str | None = None,
|
||||
env_file_encoding: str | None = None,
|
||||
instruction_role: str | None = None,
|
||||
@@ -120,6 +121,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
||||
project_endpoint: The Azure AI Foundry project endpoint URL.
|
||||
When provided with ``credential``, an ``AIProjectClient`` will be created
|
||||
and used to obtain the OpenAI client. Requires the ``azure-ai-projects`` package.
|
||||
allow_preview: Enables preview opt-in on internally-created ``AIProjectClient``.
|
||||
env_file_path: Use the environment settings file as a fallback to using env vars.
|
||||
env_file_encoding: The encoding of the environment settings file, defaults to 'utf-8'.
|
||||
instruction_role: The role to use for 'instruction' messages, for example, summarization
|
||||
@@ -189,6 +191,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
||||
project_client=project_client,
|
||||
project_endpoint=project_endpoint,
|
||||
credential=credential,
|
||||
allow_preview=allow_preview,
|
||||
)
|
||||
|
||||
azure_openai_settings = load_settings(
|
||||
@@ -246,21 +249,9 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
||||
project_client: AIProjectClient | None,
|
||||
project_endpoint: str | None,
|
||||
credential: AzureCredentialTypes | AzureTokenProvider | None,
|
||||
allow_preview: bool | None = None,
|
||||
) -> AsyncOpenAI:
|
||||
"""Create an AsyncOpenAI client from an Azure AI Foundry project.
|
||||
|
||||
Args:
|
||||
project_client: An existing AIProjectClient to use.
|
||||
project_endpoint: The Azure AI Foundry project endpoint URL.
|
||||
credential: Azure credential for authentication.
|
||||
|
||||
Returns:
|
||||
An AsyncAzureOpenAI client obtained from the project client.
|
||||
|
||||
Raises:
|
||||
ValueError: If required parameters are missing or
|
||||
the azure-ai-projects package is not installed.
|
||||
"""
|
||||
"""Create an AsyncOpenAI client from an Azure AI Foundry project."""
|
||||
if project_client is not None:
|
||||
return project_client.get_openai_client()
|
||||
|
||||
@@ -268,11 +259,14 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
|
||||
raise ValueError("Azure AI project endpoint is required when project_client is not provided.")
|
||||
if not credential:
|
||||
raise ValueError("Azure credential is required when using project_endpoint without a project_client.")
|
||||
project_client = AIProjectClient(
|
||||
endpoint=project_endpoint,
|
||||
credential=credential, # type: ignore[arg-type]
|
||||
user_agent=AGENT_FRAMEWORK_USER_AGENT,
|
||||
)
|
||||
project_client_kwargs: dict[str, Any] = {
|
||||
"endpoint": project_endpoint,
|
||||
"credential": credential, # type: ignore[arg-type]
|
||||
"user_agent": AGENT_FRAMEWORK_USER_AGENT,
|
||||
}
|
||||
if allow_preview is not None:
|
||||
project_client_kwargs["allow_preview"] = allow_preview
|
||||
project_client = AIProjectClient(**project_client_kwargs)
|
||||
return project_client.get_openai_client()
|
||||
|
||||
@override
|
||||
|
||||
@@ -327,7 +327,9 @@ class RawOpenAIChatClient( # type: ignore[misc]
|
||||
messages = prepend_instructions_to_messages(list(messages), instructions, role="system")
|
||||
|
||||
# Start with a copy of options
|
||||
run_options = {k: v for k, v in options.items() if v is not None and k not in {"instructions", "tools"}}
|
||||
run_options = {
|
||||
k: v for k, v in options.items() if v is not None and k not in {"instructions", "tools", "conversation_id"}
|
||||
}
|
||||
|
||||
# messages
|
||||
if messages and "messages" not in run_options:
|
||||
|
||||
@@ -34,7 +34,7 @@ dependencies = [
|
||||
# connectors and functions
|
||||
"openai>=1.99.0",
|
||||
"azure-identity>=1,<2",
|
||||
"azure-ai-projects == 2.0.0b4",
|
||||
"azure-ai-projects>=2.0.0,<3.0",
|
||||
"mcp[ws]>=1.24.0,<2",
|
||||
"packaging>=24.1",
|
||||
]
|
||||
@@ -55,7 +55,7 @@ all = [
|
||||
"agent-framework-devui",
|
||||
"agent-framework-durabletask",
|
||||
"agent-framework-foundry-local",
|
||||
"agent-framework-github-copilot",
|
||||
"agent-framework-github-copilot; python_version >= '3.11'",
|
||||
"agent-framework-lab",
|
||||
"agent-framework-mem0",
|
||||
"agent-framework-ollama",
|
||||
|
||||
@@ -626,6 +626,73 @@ async def test_streaming_with_none_delta(
|
||||
assert any(msg.contents for msg in results)
|
||||
|
||||
|
||||
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
|
||||
async def test_cmc_with_conversation_id(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[Message],
|
||||
mock_chat_completion_response: ChatCompletion,
|
||||
) -> None:
|
||||
"""Test that conversation_id is excluded from the completions create call."""
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
|
||||
azure_chat_client = AzureOpenAIChatClient()
|
||||
await azure_chat_client.get_response(
|
||||
messages=chat_history,
|
||||
options={"conversation_id": "12345"},
|
||||
)
|
||||
|
||||
call_kwargs = mock_create.call_args.kwargs
|
||||
assert "conversation_id" not in call_kwargs
|
||||
|
||||
|
||||
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
|
||||
async def test_cmc_streaming_with_conversation_id(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
chat_history: list[Message],
|
||||
mock_streaming_chat_completion_response: AsyncStream[ChatCompletionChunk],
|
||||
) -> None:
|
||||
"""Test that conversation_id is excluded from the streaming completions create call."""
|
||||
mock_create.return_value = mock_streaming_chat_completion_response
|
||||
chat_history.append(Message(text="hello world", role="user"))
|
||||
|
||||
azure_chat_client = AzureOpenAIChatClient()
|
||||
async for _ in azure_chat_client.get_response(
|
||||
messages=chat_history,
|
||||
options={"conversation_id": "12345"},
|
||||
stream=True,
|
||||
):
|
||||
pass
|
||||
|
||||
call_kwargs = mock_create.call_args.kwargs
|
||||
assert "conversation_id" not in call_kwargs
|
||||
|
||||
|
||||
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
|
||||
async def test_cmc_agent_with_service_session_id(
|
||||
mock_create: AsyncMock,
|
||||
azure_openai_unit_test_env: dict[str, str],
|
||||
mock_chat_completion_response: ChatCompletion,
|
||||
) -> None:
|
||||
"""Test that agent.run() with a session containing service_session_id works correctly."""
|
||||
mock_create.return_value = mock_chat_completion_response
|
||||
|
||||
azure_chat_client = AzureOpenAIChatClient()
|
||||
agent = azure_chat_client.as_agent(
|
||||
name="TestAgent",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
session = agent.get_session(service_session_id="12345")
|
||||
response = await agent.run("hello", session=session)
|
||||
|
||||
assert response is not None
|
||||
call_kwargs = mock_create.call_args.kwargs
|
||||
assert "conversation_id" not in call_kwargs
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_story_text() -> str:
|
||||
"""Returns a story about Emily and David."""
|
||||
|
||||
@@ -3449,3 +3449,66 @@ async def test_streaming_function_calling_response_includes_reasoning_and_tool_r
|
||||
reasoning_contents = [c for msg in response.messages for c in msg.contents if c.type == "text_reasoning"]
|
||||
assert len(reasoning_contents) >= 1
|
||||
assert reasoning_contents[0].id == "rs_test123"
|
||||
|
||||
|
||||
# region _update_conversation_id unit tests
|
||||
|
||||
|
||||
class TestUpdateConversationId:
|
||||
"""Tests for _update_conversation_id handling dict chat_options."""
|
||||
|
||||
def test_chat_options_as_dict(self):
|
||||
"""When chat_options is a plain dict, conversation_id should be set via key access."""
|
||||
from agent_framework._tools import _update_conversation_id
|
||||
|
||||
kwargs: dict[str, Any] = {"chat_options": {}}
|
||||
_update_conversation_id(kwargs, "conv_1")
|
||||
assert kwargs["chat_options"]["conversation_id"] == "conv_1"
|
||||
|
||||
def test_chat_options_as_typed_dict(self):
|
||||
"""When chat_options is a ChatOptions TypedDict, conversation_id should be set via key access."""
|
||||
from agent_framework import ChatOptions
|
||||
from agent_framework._tools import _update_conversation_id
|
||||
|
||||
opts: ChatOptions = {"temperature": 0.5}
|
||||
kwargs: dict[str, Any] = {"chat_options": opts}
|
||||
_update_conversation_id(kwargs, "conv_2")
|
||||
assert kwargs["chat_options"]["conversation_id"] == "conv_2"
|
||||
|
||||
def test_no_chat_options_falls_back_to_kwargs(self):
|
||||
"""When chat_options is absent, conversation_id should be set directly on kwargs."""
|
||||
from agent_framework._tools import _update_conversation_id
|
||||
|
||||
kwargs: dict[str, Any] = {}
|
||||
_update_conversation_id(kwargs, "conv_4")
|
||||
assert kwargs["conversation_id"] == "conv_4"
|
||||
|
||||
def test_none_conversation_id_is_noop(self):
|
||||
"""When conversation_id is None, kwargs should not be modified."""
|
||||
from agent_framework._tools import _update_conversation_id
|
||||
|
||||
kwargs: dict[str, Any] = {"chat_options": {}}
|
||||
_update_conversation_id(kwargs, None)
|
||||
assert "conversation_id" not in kwargs["chat_options"]
|
||||
assert "conversation_id" not in kwargs
|
||||
|
||||
def test_options_dict_also_updated(self):
|
||||
"""The optional options dict should also receive conversation_id."""
|
||||
from agent_framework._tools import _update_conversation_id
|
||||
|
||||
kwargs: dict[str, Any] = {"chat_options": {}}
|
||||
options: dict[str, Any] = {}
|
||||
_update_conversation_id(kwargs, "conv_5", options)
|
||||
assert kwargs["chat_options"]["conversation_id"] == "conv_5"
|
||||
assert options["conversation_id"] == "conv_5"
|
||||
|
||||
def test_dict_overwrites_existing_conversation_id(self):
|
||||
"""When a dict already has a conversation_id, it should be overwritten."""
|
||||
from agent_framework._tools import _update_conversation_id
|
||||
|
||||
kwargs: dict[str, Any] = {"chat_options": {"conversation_id": "old_id"}}
|
||||
_update_conversation_id(kwargs, "new_id")
|
||||
assert kwargs["chat_options"]["conversation_id"] == "new_id"
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
@@ -1161,6 +1161,21 @@ def test_prepare_options_removes_parallel_tool_calls_when_no_tools(openai_unit_t
|
||||
assert "parallel_tool_calls" not in prepared_options
|
||||
|
||||
|
||||
def test_prepare_options_excludes_conversation_id(openai_unit_test_env: dict[str, str]) -> None:
|
||||
"""Test that conversation_id is excluded from prepared options for chat completions."""
|
||||
client = OpenAIChatClient()
|
||||
|
||||
messages = [Message(role="user", text="test")]
|
||||
options = {"conversation_id": "12345", "temperature": 0.7}
|
||||
|
||||
prepared_options = client._prepare_options(messages, options)
|
||||
|
||||
# conversation_id is not a valid parameter for AsyncCompletions.create()
|
||||
assert "conversation_id" not in prepared_options
|
||||
# Other options should still be present
|
||||
assert prepared_options["temperature"] == 0.7
|
||||
|
||||
|
||||
async def test_streaming_exception_handling(openai_unit_test_env: dict[str, str]) -> None:
|
||||
"""Test that streaming errors are properly handled."""
|
||||
client = OpenAIChatClient()
|
||||
|
||||
@@ -544,19 +544,19 @@ class TestFunctionExecutor:
|
||||
static_wrapped = staticmethod(my_async_func)
|
||||
|
||||
# Direct check on descriptor object fails (this is the bug)
|
||||
assert not asyncio.iscoroutinefunction(static_wrapped)
|
||||
assert not asyncio.iscoroutinefunction(static_wrapped) # type: ignore[reportDeprecated]
|
||||
assert isinstance(static_wrapped, staticmethod)
|
||||
|
||||
# But unwrapping __func__ reveals the async function
|
||||
unwrapped = static_wrapped.__func__
|
||||
assert asyncio.iscoroutinefunction(unwrapped)
|
||||
assert asyncio.iscoroutinefunction(unwrapped) # type: ignore[reportDeprecated]
|
||||
|
||||
# When accessed via class attribute, Python's descriptor protocol
|
||||
# automatically unwraps it, so it works:
|
||||
class C:
|
||||
async_static = static_wrapped
|
||||
|
||||
assert asyncio.iscoroutinefunction(C.async_static) # Works via descriptor protocol
|
||||
assert asyncio.iscoroutinefunction(C.async_static) # type: ignore[reportDeprecated] # Works via descriptor protocol
|
||||
|
||||
|
||||
class TestExecutorExplicitTypes:
|
||||
|
||||
+1
-2
@@ -15,11 +15,10 @@ import json
|
||||
import logging
|
||||
import uuid
|
||||
from abc import abstractmethod
|
||||
from collections.abc import Mapping
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass, field
|
||||
from inspect import isawaitable
|
||||
from typing import Any, cast
|
||||
from collections.abc import Callable
|
||||
|
||||
from agent_framework import (
|
||||
Content,
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any, cast
|
||||
from collections.abc import Callable
|
||||
from typing import Any, cast
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
@@ -26,12 +26,11 @@ from agent_framework._tools import FunctionTool, ToolTypes
|
||||
from agent_framework._types import AgentRunInputs, normalize_tools
|
||||
from agent_framework.exceptions import AgentException
|
||||
from copilot import CopilotClient, CopilotSession
|
||||
from copilot.generated.session_events import SessionEvent, SessionEventType
|
||||
from copilot.generated.session_events import PermissionRequest, SessionEvent, SessionEventType
|
||||
from copilot.types import (
|
||||
CopilotClientOptions,
|
||||
MCPServerConfig,
|
||||
MessageOptions,
|
||||
PermissionRequest,
|
||||
PermissionRequestResult,
|
||||
ResumeSessionConfig,
|
||||
SessionConfig,
|
||||
@@ -529,7 +528,7 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
||||
"""Convert an FunctionTool to a Copilot SDK tool."""
|
||||
|
||||
async def handler(invocation: ToolInvocation) -> ToolResult:
|
||||
args = invocation.get("arguments", {})
|
||||
args: dict[str, Any] = invocation.arguments or {}
|
||||
try:
|
||||
if ai_func.input_model:
|
||||
args_instance = ai_func.input_model(**args)
|
||||
@@ -537,13 +536,13 @@ class GitHubCopilotAgent(BaseAgent, Generic[OptionsT]):
|
||||
else:
|
||||
result = await ai_func.invoke(arguments=args)
|
||||
return ToolResult(
|
||||
textResultForLlm=str(result),
|
||||
resultType="success",
|
||||
text_result_for_llm=str(result),
|
||||
result_type="success",
|
||||
)
|
||||
except Exception as e:
|
||||
return ToolResult(
|
||||
textResultForLlm=f"Error: {e}",
|
||||
resultType="failure",
|
||||
text_result_for_llm=f"Error: {e}",
|
||||
result_type="failure",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "agent-framework-github-copilot"
|
||||
description = "GitHub Copilot integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.11"
|
||||
version = "1.0.0b260304"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
@@ -15,7 +15,6 @@ classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
@@ -24,7 +23,7 @@ classifiers = [
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core>=1.0.0rc3",
|
||||
"github-copilot-sdk>=0.1.0",
|
||||
"github-copilot-sdk>=0.1.32",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
@@ -66,7 +65,7 @@ include = ["agent_framework_github_copilot"]
|
||||
[tool.mypy]
|
||||
plugins = ['pydantic.mypy']
|
||||
strict = true
|
||||
python_version = "3.10"
|
||||
python_version = "3.11"
|
||||
ignore_missing_imports = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
|
||||
@@ -16,6 +16,7 @@ from agent_framework import (
|
||||
)
|
||||
from agent_framework.exceptions import AgentException
|
||||
from copilot.generated.session_events import Data, SessionEvent, SessionEventType
|
||||
from copilot.types import ToolInvocation, ToolResult
|
||||
|
||||
from agent_framework_github_copilot import GitHubCopilotAgent, GitHubCopilotOptions
|
||||
|
||||
@@ -745,10 +746,11 @@ class TestGitHubCopilotAgentToolConversion:
|
||||
config = call_args[0][0]
|
||||
copilot_tool = config["tools"][0]
|
||||
|
||||
result = await copilot_tool.handler({"arguments": {"arg": "test"}})
|
||||
result = await copilot_tool.handler(ToolInvocation(arguments={"arg": "test"}))
|
||||
|
||||
assert result["resultType"] == "success"
|
||||
assert result["textResultForLlm"] == "Result: test"
|
||||
assert isinstance(result, ToolResult)
|
||||
assert result.result_type == "success"
|
||||
assert result.text_result_for_llm == "Result: test"
|
||||
|
||||
async def test_tool_handler_returns_failure_result_on_error(
|
||||
self,
|
||||
@@ -770,11 +772,61 @@ class TestGitHubCopilotAgentToolConversion:
|
||||
config = call_args[0][0]
|
||||
copilot_tool = config["tools"][0]
|
||||
|
||||
result = await copilot_tool.handler({"arguments": {"arg": "test"}})
|
||||
result = await copilot_tool.handler(ToolInvocation(arguments={"arg": "test"}))
|
||||
|
||||
assert result["resultType"] == "failure"
|
||||
assert "Something went wrong" in result["textResultForLlm"]
|
||||
assert "Something went wrong" in result["error"]
|
||||
assert isinstance(result, ToolResult)
|
||||
assert result.result_type == "failure"
|
||||
assert "Something went wrong" in result.text_result_for_llm
|
||||
assert "Something went wrong" in result.error
|
||||
|
||||
async def test_tool_handler_rejects_raw_dict_invocation(
|
||||
self,
|
||||
mock_client: MagicMock,
|
||||
mock_session: MagicMock,
|
||||
) -> None:
|
||||
"""Test that tool handler raises TypeError when called with a raw dict instead of ToolInvocation."""
|
||||
|
||||
def my_tool(arg: str) -> str:
|
||||
"""A test tool."""
|
||||
return f"Result: {arg}"
|
||||
|
||||
agent = GitHubCopilotAgent(client=mock_client, tools=[my_tool])
|
||||
await agent.start()
|
||||
|
||||
await agent._get_or_create_session(AgentSession()) # type: ignore
|
||||
|
||||
call_args = mock_client.create_session.call_args
|
||||
config = call_args[0][0]
|
||||
copilot_tool = config["tools"][0]
|
||||
|
||||
with pytest.raises((TypeError, AttributeError)):
|
||||
await copilot_tool.handler({"arguments": {"arg": "test"}})
|
||||
|
||||
async def test_tool_handler_with_empty_arguments(
|
||||
self,
|
||||
mock_client: MagicMock,
|
||||
mock_session: MagicMock,
|
||||
) -> None:
|
||||
"""Test that tool handler handles ToolInvocation with empty arguments."""
|
||||
|
||||
def no_args_tool() -> str:
|
||||
"""A tool with no arguments."""
|
||||
return "no args result"
|
||||
|
||||
agent = GitHubCopilotAgent(client=mock_client, tools=[no_args_tool])
|
||||
await agent.start()
|
||||
|
||||
await agent._get_or_create_session(AgentSession()) # type: ignore
|
||||
|
||||
call_args = mock_client.create_session.call_args
|
||||
config = call_args[0][0]
|
||||
copilot_tool = config["tools"][0]
|
||||
|
||||
result = await copilot_tool.handler(ToolInvocation(arguments={}))
|
||||
|
||||
assert isinstance(result, ToolResult)
|
||||
assert result.result_type == "success"
|
||||
assert result.text_result_for_llm == "no args result"
|
||||
|
||||
def test_copilot_tool_passthrough(
|
||||
self,
|
||||
@@ -784,7 +836,7 @@ class TestGitHubCopilotAgentToolConversion:
|
||||
from copilot.types import Tool as CopilotTool
|
||||
|
||||
async def tool_handler(invocation: Any) -> Any:
|
||||
return {"textResultForLlm": "result", "resultType": "success"}
|
||||
return {"text_result_for_llm": "result", "result_type": "success"}
|
||||
|
||||
copilot_tool = CopilotTool(
|
||||
name="direct_tool",
|
||||
@@ -813,7 +865,7 @@ class TestGitHubCopilotAgentToolConversion:
|
||||
return arg
|
||||
|
||||
async def tool_handler(invocation: Any) -> Any:
|
||||
return {"textResultForLlm": "result", "resultType": "success"}
|
||||
return {"text_result_for_llm": "result", "result_type": "success"}
|
||||
|
||||
copilot_tool = CopilotTool(
|
||||
name="direct_tool",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Azure AI Agent Examples
|
||||
|
||||
This folder contains examples demonstrating different ways to create and use agents with the Azure AI client from the `agent_framework.azure` package. These examples use the `AzureAIClient` with the `azure-ai-projects` 2.x (V2) API surface (see [changelog](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/CHANGELOG.md#200b1-2025-11-11)). For V1 (`azure-ai-agents` 1.x) samples using `AzureAIAgentClient`, see the [Azure AI V1 examples folder](../azure_ai_agent/).
|
||||
This folder contains examples demonstrating different ways to create and use agents with the Azure AI client from the `agent_framework.azure` package. These examples use the `AzureAIClient` with the `azure-ai-projects` 2.x (V2) API surface (see [changelog](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/CHANGELOG.md#200b1-2025-11-11)). For V1 (`azure-ai-agents` 1.x) samples using `AzureAIAgentClient`, see the [Azure AI V1 examples folder](../azure_ai_agent/). When using preview-only agent creation features on GA SDK versions, create `AIProjectClient` with `allow_preview=True`.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+8
-6
@@ -15,16 +15,18 @@ SECURITY NOTE: Only enable file permissions when you trust the agent's actions.
|
||||
import asyncio
|
||||
|
||||
from agent_framework.github import GitHubCopilotAgent
|
||||
from copilot.types import PermissionRequest, PermissionRequestResult
|
||||
from copilot.generated.session_events import PermissionRequest
|
||||
from copilot.types import PermissionRequestResult
|
||||
|
||||
|
||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
||||
def prompt_permission(
|
||||
request: PermissionRequest, context: dict[str, str]
|
||||
) -> PermissionRequestResult:
|
||||
"""Permission handler that prompts the user for approval."""
|
||||
kind = request.get("kind", "unknown")
|
||||
print(f"\n[Permission Request: {kind}]")
|
||||
print(f"\n[Permission Request: {request.kind}]")
|
||||
|
||||
if "path" in request:
|
||||
print(f" Path: {request.get('path')}")
|
||||
if request.path is not None:
|
||||
print(f" Path: {request.path}")
|
||||
|
||||
response = input("Approve? (y/n): ").strip().lower()
|
||||
if response in ("y", "yes"):
|
||||
|
||||
@@ -15,7 +15,8 @@ of MCP-related actions.
|
||||
import asyncio
|
||||
|
||||
from agent_framework.github import GitHubCopilotAgent
|
||||
from copilot.types import MCPServerConfig, PermissionRequest, PermissionRequestResult
|
||||
from copilot.generated.session_events import PermissionRequest
|
||||
from copilot.types import MCPServerConfig, PermissionRequestResult
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
@@ -24,8 +25,7 @@ load_dotenv()
|
||||
|
||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
||||
"""Permission handler that prompts the user for approval."""
|
||||
kind = request.get("kind", "unknown")
|
||||
print(f"\n[Permission Request: {kind}]")
|
||||
print(f"\n[Permission Request: {request.kind}]")
|
||||
|
||||
response = input("Approve? (y/n): ").strip().lower()
|
||||
if response in ("y", "yes"):
|
||||
|
||||
+7
-7
@@ -21,18 +21,18 @@ More permissions mean more potential for unintended actions.
|
||||
import asyncio
|
||||
|
||||
from agent_framework.github import GitHubCopilotAgent
|
||||
from copilot.types import PermissionRequest, PermissionRequestResult
|
||||
from copilot.generated.session_events import PermissionRequest
|
||||
from copilot.types import PermissionRequestResult
|
||||
|
||||
|
||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
||||
"""Permission handler that prompts the user for approval."""
|
||||
kind = request.get("kind", "unknown")
|
||||
print(f"\n[Permission Request: {kind}]")
|
||||
print(f"\n[Permission Request: {request.kind}]")
|
||||
|
||||
if "command" in request:
|
||||
print(f" Command: {request.get('command')}")
|
||||
if "path" in request:
|
||||
print(f" Path: {request.get('path')}")
|
||||
if request.full_command_text is not None:
|
||||
print(f" Command: {request.full_command_text}")
|
||||
if request.path is not None:
|
||||
print(f" Path: {request.path}")
|
||||
|
||||
response = input("Approve? (y/n): ").strip().lower()
|
||||
if response in ("y", "yes"):
|
||||
|
||||
@@ -14,16 +14,16 @@ Shell commands have full access to your system within the permissions of the run
|
||||
import asyncio
|
||||
|
||||
from agent_framework.github import GitHubCopilotAgent
|
||||
from copilot.types import PermissionRequest, PermissionRequestResult
|
||||
from copilot.generated.session_events import PermissionRequest
|
||||
from copilot.types import PermissionRequestResult
|
||||
|
||||
|
||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
||||
"""Permission handler that prompts the user for approval."""
|
||||
kind = request.get("kind", "unknown")
|
||||
print(f"\n[Permission Request: {kind}]")
|
||||
print(f"\n[Permission Request: {request.kind}]")
|
||||
|
||||
if "command" in request:
|
||||
print(f" Command: {request.get('command')}")
|
||||
if request.full_command_text is not None:
|
||||
print(f" Command: {request.full_command_text}")
|
||||
|
||||
response = input("Approve? (y/n): ").strip().lower()
|
||||
if response in ("y", "yes"):
|
||||
|
||||
@@ -14,16 +14,16 @@ URL fetching allows the agent to access any URL accessible from your network.
|
||||
import asyncio
|
||||
|
||||
from agent_framework.github import GitHubCopilotAgent
|
||||
from copilot.types import PermissionRequest, PermissionRequestResult
|
||||
from copilot.generated.session_events import PermissionRequest
|
||||
from copilot.types import PermissionRequestResult
|
||||
|
||||
|
||||
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
|
||||
"""Permission handler that prompts the user for approval."""
|
||||
kind = request.get("kind", "unknown")
|
||||
print(f"\n[Permission Request: {kind}]")
|
||||
print(f"\n[Permission Request: {request.kind}]")
|
||||
|
||||
if "url" in request:
|
||||
print(f" URL: {request.get('url')}")
|
||||
if request.url is not None:
|
||||
print(f" URL: {request.url}")
|
||||
|
||||
response = input("Approve? (y/n): ").strip().lower()
|
||||
if response in ("y", "yes"):
|
||||
|
||||
Generated
+291
-1178
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user