Python: replace pre-commit with prek, add PEP 723 script deps, clean up dev dependencies (#3748)

* python: replace pre-commit with prek, add PEP 723 script deps, clean up dev dependencies

- Replace pre-commit with prek (Rust-native, faster pre-commit alternative)
- Move supported hooks to repo: builtin for zero-clone speed
- Add new builtin hooks: trailing-whitespace, check-merge-conflict, detect-private-key, check-added-large-files
- Update all hook versions to latest (pre-commit-hooks v6, pyupgrade v3.21.2, bandit 1.9.3, uv-pre-commit 0.10.0)
- Add PEP 723 inline script metadata to 34 samples with external deps
- Remove autogen-agentchat/autogen-ext from dev deps (now declared per-sample)
- Remove unused dev deps: pytest-env, tomli-w
- Add agent-framework-core>=1.0.0b260130 lower bound to all 21 packages
- Update CI workflow to use j178/prek-action
- Update docs: DEV_SETUP.md, AGENTS.md, CODING_STANDARD.md, SAMPLE_GUIDELINES.md

* updated lock

* python: fix prek config paths for local execution and CI workflow

Remove global 'files: ^python/' filter and strip python/ prefix from all path patterns in .pre-commit-config.yaml so prek finds files when run from the python/ directory. Update CI workflow to use --cd python instead of --config path. Include trailing whitespace fixes and dev dependency cleanup.

* python: move helper scripts to scripts/ folder and exclude from checks

* python: exclude AGENTS.md from prek markdown code lint

* python: exclude AGENTS.md and azure_ai_search sample from markdown lint

* fix m365 sample

* python: ignore CPY rule for samples with PEP 723 headers

* fix in dev_setup

* python: replace aiofiles with regular open in samples

* python: suppress reportUnusedImport in markdown code block checker

* python: use samples pyright config for markdown code block checker

Write a temp pyrightconfig.json matching pyrightconfig.samples.json rules (typeCheckingMode=off, only reportMissingImports and reportAttributeAccessIssue). Filter output to only fail on these rules since syntax-level errors (top-level await, undefined vars) are expected in README documentation snippets.

* python: use markdown-code-lint with fixed globs instead of prek file list

The prek-markdown-code-lint task received all changed files including non-README markdown and files with pre-existing broken imports. Replace with the standard markdown-code-lint task which uses the correct glob patterns (README.md, packages/**/README.md, samples/**/*.md).

* python: exclude READMEs with pre-existing broken imports from markdown lint

* python: fix broken README code snippets instead of excluding them

- ag-ui: replace TextContent (removed) with content.type == 'text'
- durabletask: fix import path to durabletask.worker.TaskHubGrpcWorker
- orchestrations: use constructor params instead of .participants() method
- observability: mark deprecated code blocks as plain text, filter
  reportMissingImports to agent_framework modules only
- remove README excludes from markdown-code-lint task

* add revision to gaia download

* feat(python): parallelize checks across packages

Run (package × task) cross-product in parallel using ThreadPoolExecutor
and subprocesses. Key changes:

- Add scripts/task_runner.py with shared parallel execution engine
- Update run_tasks_in_packages_if_exists.py to accept multiple tasks
- Update run_tasks_in_changed_packages.py with --files flag and parallel support
- Add check-packages poe task (fmt+lint+pyright+mypy in parallel)
- Add prek-markdown-code-lint and prek-samples-check with change detection
- Split CI code quality workflow into parallel prek and mypy jobs
- Update DEV_SETUP.md to document new parallel behavior

Core package changes still trigger checks on all packages.

* feat(ci): split code quality into 4 parallel jobs

Split the single prek job into parallel jobs:
- pre-commit-hooks: lightweight hooks (SKIP=poe-check)
- package-checks: fmt/lint/pyright/mypy via check-packages
- samples-markdown: samples-lint, samples-syntax, markdown-code-lint
- mypy: change-detected mypy checks

All 4 jobs run concurrently (×2 Python versions = 8 runners).

* feat(ci): use only Python 3.10 for code quality checks

* refactor(python): add future annotations and remove quoted types

Add `from __future__ import annotations` to 93 package files that
used quoted string annotations, then run pyupgrade --py310-plus to
remove the now-unnecessary quotes.

Fixes https://github.com/microsoft/agent-framework/issues/3578
This commit is contained in:
Eduard van Valkenburg
2026-02-09 18:51:01 +01:00
committed by GitHub
Unverified
parent ad0dac3c86
commit 977c3adfb2
177 changed files with 1373 additions and 1010 deletions
+26 -17
View File
@@ -1,59 +1,68 @@
files: ^python/
fail_fast: true
exclude: ^scripts/
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
- repo: builtin
hooks:
- id: check-toml
name: Check TOML files
files: \.toml$
exclude: ^python/packages/lab/cookiecutter-agent-framework-lab/
exclude: ^packages/lab/cookiecutter-agent-framework-lab/
- id: check-yaml
name: Check YAML files
files: \.yaml$
- id: check-json
name: Check JSON files
files: \.json$
exclude: ^.*\.vscode\/.*|^python/demos/samples/chatkit-integration/frontend/(tsconfig.*\.json|package-lock\.json)$
exclude: ^.*\.vscode\/.*|^demos/samples/chatkit-integration/frontend/(tsconfig.*\.json|package-lock\.json)$
- id: end-of-file-fixer
name: Fix End of File
files: \.py$
exclude: ^python/packages/lab/cookiecutter-agent-framework-lab/
exclude: ^packages/lab/cookiecutter-agent-framework-lab/
- id: mixed-line-ending
name: Check Mixed Line Endings
files: \.py$
exclude: ^python/packages/lab/cookiecutter-agent-framework-lab/
exclude: ^packages/lab/cookiecutter-agent-framework-lab/
- id: trailing-whitespace
name: Trim Trailing Whitespace
exclude: ^packages/lab/cookiecutter-agent-framework-lab/
- id: check-merge-conflict
name: Check Merge Conflicts
- id: detect-private-key
name: Detect Private Keys
- id: check-added-large-files
name: Check Added Large Files
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-ast
name: Check Valid Python Samples
types: ["python"]
exclude: ^python/packages/lab/cookiecutter-agent-framework-lab/
exclude: ^packages/lab/cookiecutter-agent-framework-lab/
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
rev: v3.21.2
hooks:
- id: pyupgrade
name: Upgrade Python syntax
args: [--py310-plus]
exclude: ^python/packages/lab/cookiecutter-agent-framework-lab/
exclude: ^packages/lab/cookiecutter-agent-framework-lab/
- repo: local
hooks:
- id: poe-check
name: Run checks through Poe
entry: uv --directory ./python run poe pre-commit-check
entry: uv run poe prek-check
language: system
files: ^python/
- repo: https://github.com/PyCQA/bandit
rev: 1.8.5
rev: 1.9.3
hooks:
- id: bandit
name: Bandit Security Checks
args: ["-c", "python/pyproject.toml"]
args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]"]
- repo: https://github.com/astral-sh/uv-pre-commit
# uv version.
rev: 0.7.18
rev: 0.10.0
hooks:
# Update the uv lockfile
- id: uv-lock
name: Update uv lockfile
files: python/pyproject.toml
args: [--project, python]
files: pyproject.toml
+1 -1
View File
@@ -9,7 +9,7 @@
"command": "uv",
"args": [
"run",
"pre-commit",
"prek",
"run",
"-a"
],
+2 -15
View File
@@ -61,22 +61,9 @@ from agent_framework.azure import AzureOpenAIChatClient, AzureAIAgentClient
- **Comments**: Avoid excessive comments; prefer clear code
- **Formatting**: Format only files you changed, not the entire codebase
## Sample Structure
## Samples
1. Copyright header: `# Copyright (c) Microsoft. All rights reserved.`
2. Required imports
3. Module docstring: `"""This sample demonstrates..."""`
4. Helper functions
5. Main function(s) demonstrating functionality
6. Entry point: `if __name__ == "__main__": asyncio.run(main())`
When modifying samples, update associated README files in the same or parent folders.
### Samples Syntax Checking
Run `uv run poe samples-syntax` to check samples for syntax errors and missing imports from `agent_framework`. This uses a relaxed pyright configuration that validates imports without strict type checking.
Some samples depend on external packages (e.g., `azure.ai.agentserver.agentframework`, `microsoft_agents`) that are not installed in the dev environment. These are excluded in `pyrightconfig.samples.json`. When adding or modifying these excluded samples, add them to the exclude list and manually verify they have no import errors from `agent_framework` packages by temporarily removing them from the exclude list and running the check.
See [samples/SAMPLE_GUIDELINES.md](samples/SAMPLE_GUIDELINES.md) for sample structure, external dependency handling (PEP 723), and syntax checking instructions.
## Package Documentation
+7
View File
@@ -264,6 +264,13 @@ After the package has been released and gained a measure of confidence:
2. Add the package to the `[all]` extra in `packages/core/pyproject.toml`
3. Create a provider folder in `agent_framework/` with lazy loading `__init__.py`
### Versioning and Core Dependency
All non-core packages declare a lower bound on `agent-framework-core` (e.g., `"agent-framework-core>=1.0.0b260130"`). Follow these rules when bumping versions:
- **Core version changes**: When `agent-framework-core` is updated with breaking or significant changes and its version is bumped, update the `agent-framework-core>=...` lower bound in every other package's `pyproject.toml` to match the new core version.
- **Non-core version changes**: Non-core packages (connectors, extensions) can have their own versions incremented independently while keeping the existing core lower bound pinned. Only raise the core lower bound if the non-core package actually depends on new core APIs.
### Installation Options
Connectors are distributed as separate packages and are not imported by default in the core package. Users install the specific connectors they need:
+25 -19
View File
@@ -64,11 +64,11 @@ uv venv --python $PYTHON_VERSION
uv sync --dev
# Install all the tools and dependencies
uv run poe install
# Install pre-commit hooks
uv run poe pre-commit-install
# Install prek hooks
uv run poe prek-install
```
Alternatively, you can reinstall the venv, pacakges, dependencies and pre-commit hooks with a single command (but this requires poe in the current env), this is especially useful if you want to switch python versions:
Alternatively, you can reinstall the venv, pacakges, dependencies and prek hooks with a single command (but this requires poe in the current env), this is especially useful if you want to switch python versions:
```bash
uv run poe setup -p 3.13
@@ -144,7 +144,7 @@ To run the same checks that run during a commit and the GitHub Action `Python Co
uv run poe check
```
Ideally you should run these checks before committing any changes, when you install using the instructions above the pre-commit hooks should be installed already.
Ideally you should run these checks before committing any changes, when you install using the instructions above the prek hooks should be installed already.
## Code Coverage
@@ -196,10 +196,10 @@ and then you can run the following tasks:
uv sync --all-extras --dev
```
After this initial setup, you can use the following tasks to manage your development environment. It is advised to use the following setup command since that also installs the pre-commit hooks.
After this initial setup, you can use the following tasks to manage your development environment. It is advised to use the following setup command since that also installs the prek hooks.
#### `setup`
Set up the development environment with a virtual environment, install dependencies and pre-commit hooks:
Set up the development environment with a virtual environment, install dependencies and prek hooks:
```bash
uv run poe setup
# or with specific Python version
@@ -220,36 +220,36 @@ uv run poe venv
uv run poe venv --python 3.12
```
#### `pre-commit-install`
Install pre-commit hooks:
#### `prek-install`
Install prek hooks:
```bash
uv run poe pre-commit-install
uv run poe prek-install
```
### Code Quality and Formatting
Each of the following tasks are designed to run against both the main `agent-framework` package and the extension packages, ensuring consistent code quality across the project.
Each of the following tasks run against both the main `agent-framework` package and the extension packages in parallel, ensuring consistent code quality across the project.
#### `fmt` (format)
Format code using ruff:
Format code using ruff (runs in parallel across all packages):
```bash
uv run poe fmt
```
#### `lint`
Run linting checks and fix issues:
Run linting checks and fix issues (runs in parallel across all packages):
```bash
uv run poe lint
```
#### `pyright`
Run Pyright type checking:
Run Pyright type checking (runs in parallel across all packages):
```bash
uv run poe pyright
```
#### `mypy`
Run MyPy type checking:
Run MyPy type checking (runs in parallel across all packages):
```bash
uv run poe mypy
```
@@ -270,8 +270,14 @@ uv run poe markdown-code-lint
### Comprehensive Checks
#### `check-packages`
Run all package-level quality checks (format, lint, pyright, mypy) in parallel across all packages. This runs the full cross-product of (package × check) concurrently:
```bash
uv run poe check-packages
```
#### `check`
Run all quality checks (format, lint, pyright, mypy, test, markdown lint):
Run all quality checks including package checks, samples, tests and markdown lint:
```bash
uv run poe check
```
@@ -279,7 +285,7 @@ uv run poe check
### Testing
#### `test`
Run unit tests with coverage by invoking the `test` task in each package sequentially:
Run unit tests with coverage by invoking the `test` task in each package in parallel:
```bash
uv run poe test
```
@@ -325,10 +331,10 @@ Publish packages to PyPI:
uv run poe publish
```
## Pre-commit Hooks
## Prek Hooks
Pre-commit hooks run automatically on commit and execute a subset of the checks on changed files only. You can also run all checks using pre-commit directly:
Prek hooks run automatically on commit and execute a subset of the checks on changed files only. Package-level checks (fmt, lint, pyright) run in parallel but only for packages with changed files. Markdown and sample checks are skipped when no relevant files were changed. If the `core` package is changed, all packages are checked. You can also run all checks using prek directly:
```bash
uv run pre-commit run -a
uv run prek run -a
```
+2 -2
View File
@@ -6,5 +6,5 @@ uv venv --python $PYTHON_VERSION
uv sync --dev
# Install all the tools and dependencies
uv run poe install
# Install pre-commit hooks
uv run poe pre-commit-install
# Install prek hooks
uv run poe prek-install
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import base64
import json
import re
@@ -169,7 +171,7 @@ class A2AAgent(AgentTelemetryLayer, BaseAgent):
msg = f"Invalid timeout type: {type(timeout)}. Expected float, httpx.Timeout, or None."
raise TypeError(msg)
async def __aenter__(self) -> "A2AAgent":
async def __aenter__(self) -> A2AAgent:
"""Async context manager entry."""
return self
+1 -1
View File
@@ -23,7 +23,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"a2a-sdk>=0.3.5",
]
+1 -2
View File
@@ -40,7 +40,6 @@ add_agent_framework_fastapi_endpoint(app, agent, "/")
```python
import asyncio
from agent_framework import TextContent
from agent_framework.ag_ui import AGUIChatClient
async def main():
@@ -48,7 +47,7 @@ async def main():
# Stream responses
async for update in client.get_response("Hello!", stream=True):
for content in update.contents:
if isinstance(content, TextContent):
if content.type == "text" and content.text:
print(content.text, end="", flush=True)
print()
@@ -2,6 +2,8 @@
"""AG-UI Chat Client implementation."""
from __future__ import annotations
import json
import logging
import sys
@@ -216,7 +218,7 @@ class AGUIChatClient(
http_client: httpx.AsyncClient | None = None,
timeout: float = 60.0,
additional_properties: dict[str, Any] | None = None,
middleware: Sequence["ChatAndFunctionMiddlewareTypes"] | None = None,
middleware: Sequence[ChatAndFunctionMiddlewareTypes] | None = None,
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
**kwargs: Any,
) -> None:
@@ -2,6 +2,8 @@
"""FastAPI endpoint creation for AG-UI agents."""
from __future__ import annotations
import copy
import logging
from collections.abc import AsyncGenerator, Sequence
@@ -77,7 +79,7 @@ def add_agent_framework_fastapi_endpoint(
)
logger.info(f"Received request at {path}: {input_data.get('run_id', 'no-run-id')}")
async def event_generator() -> AsyncGenerator[str, None]:
async def event_generator() -> AsyncGenerator[str]:
encoder = EventEncoder()
event_count = 0
async for event in wrapped_agent.run_agent(input_data):
@@ -2,6 +2,8 @@
"""Event converter for AG-UI protocol events to Agent Framework types."""
from __future__ import annotations
from typing import Any
from agent_framework import (
@@ -2,6 +2,8 @@
"""HTTP service for AG-UI protocol communication."""
from __future__ import annotations
import json
import logging
from collections.abc import AsyncIterable
@@ -148,7 +150,7 @@ class AGUIHttpService:
if self._owns_client and self.http_client:
await self.http_client.aclose()
async def __aenter__(self) -> "AGUIHttpService":
async def __aenter__(self) -> AGUIHttpService:
"""Enter async context manager."""
return self
@@ -2,6 +2,8 @@
"""Message format conversion between AG-UI and Agent Framework."""
from __future__ import annotations
import json
import logging
from typing import Any, cast
@@ -6,6 +6,8 @@ Most orchestration helpers have been moved inline to _run.py.
This module retains utilities that may be useful for testing or extensions.
"""
from __future__ import annotations
import json
import logging
from typing import Any
@@ -2,6 +2,8 @@
"""Predictive state handling utilities."""
from __future__ import annotations
import json
import logging
import re
@@ -2,6 +2,8 @@
"""Tool handling helpers."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
@@ -29,7 +31,7 @@ def _collect_mcp_tool_functions(mcp_tools: list[Any]) -> list[Any]:
return functions
def collect_server_tools(agent: "SupportsAgentRun") -> list[Any]:
def collect_server_tools(agent: SupportsAgentRun) -> list[Any]:
"""Collect server tools from an agent.
This includes both regular tools from default_options and MCP tools.
@@ -64,7 +66,7 @@ def collect_server_tools(agent: "SupportsAgentRun") -> list[Any]:
return server_tools
def register_additional_client_tools(agent: "SupportsAgentRun", client_tools: list[Any] | None) -> None:
def register_additional_client_tools(agent: SupportsAgentRun, client_tools: list[Any] | None) -> None:
"""Register client tools as additional declaration-only tools to avoid server execution.
Args:
@@ -2,6 +2,8 @@
"""Simplified AG-UI orchestration - single linear flow."""
from __future__ import annotations
import json
import logging
import uuid
@@ -742,8 +744,8 @@ def _build_messages_snapshot(
async def run_agent_stream(
input_data: dict[str, Any],
agent: SupportsAgentRun,
config: "AgentConfig",
) -> "AsyncGenerator[BaseEvent, None]":
config: AgentConfig,
) -> AsyncGenerator[BaseEvent]:
"""Run agent and yield AG-UI events.
This is the single entry point for all AG-UI agent runs. It follows a simple
@@ -2,6 +2,8 @@
"""Utility functions for AG-UI integration."""
from __future__ import annotations
import copy
import json
import uuid
@@ -2,6 +2,8 @@
"""Example agent demonstrating predictive state updates with document writing."""
from __future__ import annotations
from agent_framework import ChatAgent, ChatClientProtocol, tool
from agent_framework.ag_ui import AgentFrameworkAgent
@@ -2,6 +2,8 @@
"""Recipe agent example demonstrating shared state management (Feature 3)."""
from __future__ import annotations
from enum import Enum
from typing import Any
@@ -2,6 +2,8 @@
"""Task steps agent demonstrating agentic generative UI (Feature 6)."""
from __future__ import annotations
import asyncio
from collections.abc import AsyncGenerator
from enum import Enum
@@ -128,7 +130,7 @@ class TaskStepsAgentWithExecution:
"""Delegate all other attribute access to base agent."""
return getattr(self._base_agent, name)
async def run_agent(self, input_data: dict[str, Any]) -> AsyncGenerator[Any, None]:
async def run_agent(self, input_data: dict[str, Any]) -> AsyncGenerator[Any]:
"""Run the agent and then simulate step execution."""
import logging
import uuid
@@ -2,6 +2,8 @@
"""Example agent demonstrating Tool-based Generative UI (Feature 5)."""
from __future__ import annotations
import sys
from typing import TYPE_CHECKING, Any, TypedDict
@@ -2,6 +2,8 @@
"""Weather agent example demonstrating backend tool rendering."""
from __future__ import annotations
from typing import Any
from agent_framework import ChatAgent, ChatClientProtocol, tool
@@ -2,6 +2,8 @@
"""Example FastAPI server with AG-UI endpoints."""
from __future__ import annotations
import logging
import os
from typing import cast
@@ -292,7 +292,6 @@ Create a file named `client.py`:
import asyncio
import os
from agent_framework import TextContent
from agent_framework.ag_ui import AGUIChatClient
@@ -333,7 +332,7 @@ async def main():
# Stream text content as it arrives
for content in update.contents:
if isinstance(content, TextContent) and content.text:
if content.type == "text" and content.text:
print(content.text, end="", flush=True)
print() # New line after response
@@ -9,6 +9,8 @@ This example demonstrates advanced AGUIChatClient features including:
- Error handling
"""
from __future__ import annotations
import asyncio
import os
from typing import cast
@@ -18,6 +18,8 @@ This demonstrates the HYBRID pattern matching .NET AGUIClient implementation:
This matches .NET pattern: thread maintains state, tools execute on appropriate side.
"""
from __future__ import annotations
import asyncio
import logging
import os
@@ -2,6 +2,8 @@
"""AG-UI server example with server-side tools."""
from __future__ import annotations
import logging
import os
+1 -1
View File
@@ -22,7 +22,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"ag-ui-protocol>=0.1.9",
"fastapi>=0.115.0",
"uvicorn>=0.30.0"
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import AsyncIterable, Awaitable, Mapping, MutableMapping, Sequence
from typing import Any, ClassVar, Final, Generic, Literal, TypedDict
+1 -1
View File
@@ -23,7 +23,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"anthropic>=0.70.0,<1",
]
@@ -1,6 +1,8 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import Awaitable, Callable, MutableSequence
from typing import TYPE_CHECKING, Any, ClassVar, Literal
@@ -23,7 +23,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"azure-search-documents==11.7.0b2",
]
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import Callable, MutableMapping, Sequence
from typing import TYPE_CHECKING, Any, Generic, cast
@@ -141,7 +143,7 @@ class AzureAIAgentsProvider(Generic[TOptions_co]):
)
self._should_close_client = True
async def __aenter__(self) -> "Self":
async def __aenter__(self) -> Self:
"""Async context manager entry."""
return self
@@ -177,7 +179,7 @@ class AzureAIAgentsProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Create a new agent on the Azure AI service and return a ChatAgent.
This method creates a persistent agent on the Azure AI service with the specified
@@ -274,7 +276,7 @@ class AzureAIAgentsProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Retrieve an existing agent from the service and return a ChatAgent.
This method fetches an agent by ID from the Azure AI service
@@ -330,7 +332,7 @@ class AzureAIAgentsProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Wrap an existing Agent SDK object as a ChatAgent without making HTTP calls.
Use this method when you already have an Agent object from a previous
@@ -383,7 +385,7 @@ class AzureAIAgentsProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Create a ChatAgent from an Agent SDK object.
Args:
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import ast
import json
import os
@@ -346,7 +348,7 @@ class AzureAIAgentClient(
self._should_close_client = should_close_client # Track whether we should close client connection
self._agent_definition: Agent | None = None # Cached definition for existing agent
async def __aenter__(self) -> "Self":
async def __aenter__(self) -> Self:
"""Async context manager entry."""
return self
@@ -1047,9 +1049,7 @@ class AzureAIAgentClient(
return tool_definitions
def _prepare_mcp_resources(
self, tools: Sequence["ToolProtocol | MutableMapping[str, Any]"]
) -> list[dict[str, Any]]:
def _prepare_mcp_resources(self, tools: Sequence[ToolProtocol | MutableMapping[str, Any]]) -> list[dict[str, Any]]:
"""Prepare MCP tool resources for approval mode configuration."""
mcp_tools = [tool for tool in tools if isinstance(tool, HostedMCPTool)]
if not mcp_tools:
@@ -1142,7 +1142,7 @@ class AzureAIAgentClient(
return additional_messages, instructions, required_action_results
async def _prepare_tools_for_azure_ai(
self, tools: Sequence["ToolProtocol | MutableMapping[str, Any]"], run_options: dict[str, Any] | None = None
self, tools: Sequence[ToolProtocol | MutableMapping[str, Any]], run_options: dict[str, Any] | None = None
) -> list[ToolDefinition | dict[str, Any]]:
"""Prepare tool definitions for the Azure AI Agents API."""
tool_definitions: list[ToolDefinition | dict[str, Any]] = []
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import Callable, Mapping, MutableMapping, Sequence
from typing import Any, ClassVar, Generic, TypedDict, TypeVar, cast
@@ -295,7 +297,7 @@ class RawAzureAIClient(RawOpenAIResponsesClient[TAzureAIClientOptions], Generic[
# Complete setup with core observability
enable_instrumentation(enable_sensitive_data=enable_sensitive_data)
async def __aenter__(self) -> "Self":
async def __aenter__(self) -> Self:
"""Async context manager entry."""
return self
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import Callable, MutableMapping, Sequence
from typing import Any, Generic
@@ -168,7 +170,7 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Create a new agent on the Azure AI service and return a local ChatAgent wrapper.
Args:
@@ -270,7 +272,7 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Retrieve an existing agent from the Azure AI service and return a local ChatAgent wrapper.
You must provide either name or reference. Use `as_agent()` if you already have
@@ -330,7 +332,7 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Wrap an SDK agent version object into a ChatAgent without making HTTP calls.
Use this when you already have an AgentVersionDetails from a previous API call.
@@ -370,7 +372,7 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Create a ChatAgent from an AgentVersionDetails.
Args:
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import os
from collections.abc import Mapping, MutableMapping, Sequence
from typing import Any, ClassVar, Literal, cast
+1 -1
View File
@@ -23,7 +23,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"azure-ai-projects >= 2.0.0b3",
"azure-ai-agents == 1.2.0b5",
"aiohttp",
@@ -6,6 +6,8 @@ This module provides the AgentFunctionApp class that integrates Microsoft Agent
with Azure Durable Entities, enabling stateful and durable AI agent execution.
"""
from __future__ import annotations
import json
import re
import uuid
@@ -7,6 +7,8 @@ Using entities instead of orchestrations provides better state management and
allows for long-running agent conversations.
"""
from __future__ import annotations
import asyncio
from collections.abc import Callable
from typing import Any, cast
@@ -22,7 +22,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"agent-framework-durabletask",
"azure-functions",
"azure-functions-durable",
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import asyncio
import json
import sys
+1 -1
View File
@@ -23,7 +23,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"boto3>=1.35.0,<2.0.0",
"botocore>=1.35.0,<2.0.0",
]
@@ -2,6 +2,8 @@
"""Converter utilities for converting ChatKit thread items to Agent Framework messages."""
from __future__ import annotations
import logging
import sys
from collections.abc import Awaitable, Callable, Sequence
+1 -1
View File
@@ -22,7 +22,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"openai-chatkit>=1.4.0,<2.0.0",
]
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import contextlib
import sys
from collections.abc import AsyncIterable, Awaitable, Callable, MutableMapping, Sequence
@@ -100,13 +102,13 @@ class ClaudeAgentOptions(TypedDict, total=False):
disallowed_tools: list[str]
"""Blocklist of tools. Claude cannot use these tools."""
mcp_servers: dict[str, "McpServerConfig"]
mcp_servers: dict[str, McpServerConfig]
"""MCP server configurations for external tools."""
permission_mode: "PermissionMode"
permission_mode: PermissionMode
"""Permission handling mode ("default", "acceptEdits", "plan", "bypassPermissions")."""
can_use_tool: "CanUseTool"
can_use_tool: CanUseTool
"""Permission callback for tool use."""
max_turns: int
@@ -115,16 +117,16 @@ class ClaudeAgentOptions(TypedDict, total=False):
max_budget_usd: float
"""Budget limit in USD."""
hooks: dict[str, list["HookMatcher"]]
hooks: dict[str, list[HookMatcher]]
"""Pre/post tool hooks."""
add_dirs: list[str | Path]
"""Additional directories to add to context."""
sandbox: "SandboxSettings"
sandbox: SandboxSettings
"""Sandbox configuration for bash isolation."""
agents: dict[str, "AgentDefinition"]
agents: dict[str, AgentDefinition]
"""Custom agent definitions."""
output_format: dict[str, Any]
@@ -133,7 +135,7 @@ class ClaudeAgentOptions(TypedDict, total=False):
enable_file_checkpointing: bool
"""Enable file checkpointing for rewind."""
betas: list["SdkBeta"]
betas: list[SdkBeta]
"""Beta features to enable."""
@@ -328,7 +330,7 @@ class ClaudeAgent(BaseAgent, Generic[TOptions]):
normalized = normalize_tools(tool)
self._custom_tools.extend(normalized)
async def __aenter__(self) -> "ClaudeAgent[TOptions]":
async def __aenter__(self) -> ClaudeAgent[TOptions]:
"""Start the agent when entering async context."""
await self.start()
return self
+1 -1
View File
@@ -23,7 +23,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"claude-agent-sdk>=0.1.25",
]
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
from collections.abc import AsyncIterable, Awaitable, Sequence
from typing import Any, ClassVar, Literal, overload
@@ -213,7 +215,7 @@ class CopilotStudioAgent(BaseAgent):
stream: Literal[False] = False,
thread: AgentThread | None = None,
**kwargs: Any,
) -> "Awaitable[AgentResponse]": ...
) -> Awaitable[AgentResponse]: ...
@overload
def run(
@@ -232,7 +234,7 @@ class CopilotStudioAgent(BaseAgent):
stream: bool = False,
thread: AgentThread | None = None,
**kwargs: Any,
) -> "Awaitable[AgentResponse] | ResponseStream[AgentResponseUpdate, AgentResponse]":
) -> Awaitable[AgentResponse] | ResponseStream[AgentResponseUpdate, AgentResponse]:
"""Get a response from the agent.
This method returns the final result of the agent's execution
+1 -1
View File
@@ -23,7 +23,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"microsoft-agents-copilotstudio-client>=0.3.1",
]
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import inspect
import re
import sys
@@ -729,7 +731,7 @@ class RawChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
self._async_exit_stack = AsyncExitStack()
self._update_agent_name_and_description()
async def __aenter__(self) -> "Self":
async def __aenter__(self) -> Self:
"""Enter the async context manager.
If any of the chat_client or local_mcp_tools are context managers,
@@ -787,7 +789,7 @@ class RawChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
| MutableMapping[str, Any]
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
options: "ChatOptions[TResponseModelT]",
options: ChatOptions[TResponseModelT],
**kwargs: Any,
) -> Awaitable[AgentResponse[TResponseModelT]]: ...
@@ -803,7 +805,7 @@ class RawChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
| MutableMapping[str, Any]
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
options: "TOptions_co | ChatOptions[None] | None" = None,
options: TOptions_co | ChatOptions[None] | None = None,
**kwargs: Any,
) -> Awaitable[AgentResponse[Any]]: ...
@@ -819,7 +821,7 @@ class RawChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
| MutableMapping[str, Any]
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
options: "TOptions_co | ChatOptions[Any] | None" = None,
options: TOptions_co | ChatOptions[Any] | None = None,
**kwargs: Any,
) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ...
@@ -834,7 +836,7 @@ class RawChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
| MutableMapping[str, Any]
| list[ToolProtocol | Callable[..., Any] | MutableMapping[str, Any]]
| None = None,
options: "TOptions_co | ChatOptions[Any] | None" = None,
options: TOptions_co | ChatOptions[Any] | None = None,
**kwargs: Any,
) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]:
"""Run the agent with the given messages and options.
@@ -1149,9 +1151,9 @@ class RawChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
server_name: str = "Agent",
version: str | None = None,
instructions: str | None = None,
lifespan: Callable[["Server[Any]"], AbstractAsyncContextManager[Any]] | None = None,
lifespan: Callable[[Server[Any]], AbstractAsyncContextManager[Any]] | None = None,
**kwargs: Any,
) -> "Server[Any]":
) -> Server[Any]:
"""Create an MCP server from an agent instance.
This function automatically creates a MCP server from an agent instance, it uses the provided arguments to
@@ -1177,7 +1179,7 @@ class RawChatAgent(BaseAgent, Generic[TOptions_co]): # type: ignore[misc]
if kwargs:
server_args.update(kwargs)
server: "Server[Any]" = Server(**server_args) # type: ignore[call-arg]
server: Server[Any] = Server(**server_args) # type: ignore[call-arg]
agent_tool = self.as_tool(name=self._get_agent_name())
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from abc import ABC, abstractmethod
from collections.abc import (
@@ -137,7 +139,7 @@ class ChatClientProtocol(Protocol[TOptions_contra]):
messages: str | ChatMessage | Sequence[str | ChatMessage],
*,
stream: Literal[False] = ...,
options: "ChatOptions[TResponseModelT]",
options: ChatOptions[TResponseModelT],
**kwargs: Any,
) -> Awaitable[ChatResponse[TResponseModelT]]: ...
@@ -147,7 +149,7 @@ class ChatClientProtocol(Protocol[TOptions_contra]):
messages: str | ChatMessage | Sequence[str | ChatMessage],
*,
stream: Literal[False] = ...,
options: "TOptions_contra | ChatOptions[None] | None" = None,
options: TOptions_contra | ChatOptions[None] | None = None,
**kwargs: Any,
) -> Awaitable[ChatResponse[Any]]: ...
@@ -157,7 +159,7 @@ class ChatClientProtocol(Protocol[TOptions_contra]):
messages: str | ChatMessage | Sequence[str | ChatMessage],
*,
stream: Literal[True],
options: "TOptions_contra | ChatOptions[Any] | None" = None,
options: TOptions_contra | ChatOptions[Any] | None = None,
**kwargs: Any,
) -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
@@ -166,7 +168,7 @@ class ChatClientProtocol(Protocol[TOptions_contra]):
messages: str | ChatMessage | Sequence[str | ChatMessage],
*,
stream: bool = False,
options: "TOptions_contra | ChatOptions[Any] | None" = None,
options: TOptions_contra | ChatOptions[Any] | None = None,
**kwargs: Any,
) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
"""Send input and return the response.
@@ -366,7 +368,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[TOptions_co]):
messages: str | ChatMessage | Sequence[str | ChatMessage],
*,
stream: Literal[False] = ...,
options: "ChatOptions[TResponseModelT]",
options: ChatOptions[TResponseModelT],
**kwargs: Any,
) -> Awaitable[ChatResponse[TResponseModelT]]: ...
@@ -376,7 +378,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[TOptions_co]):
messages: str | ChatMessage | Sequence[str | ChatMessage],
*,
stream: Literal[False] = ...,
options: "TOptions_co | ChatOptions[None] | None" = None,
options: TOptions_co | ChatOptions[None] | None = None,
**kwargs: Any,
) -> Awaitable[ChatResponse[Any]]: ...
@@ -386,7 +388,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[TOptions_co]):
messages: str | ChatMessage | Sequence[str | ChatMessage],
*,
stream: Literal[True],
options: "TOptions_co | ChatOptions[Any] | None" = None,
options: TOptions_co | ChatOptions[Any] | None = None,
**kwargs: Any,
) -> ResponseStream[ChatResponseUpdate, ChatResponse[Any]]: ...
@@ -395,7 +397,7 @@ class BaseChatClient(SerializationMixin, ABC, Generic[TOptions_co]):
messages: str | ChatMessage | Sequence[str | ChatMessage],
*,
stream: bool = False,
options: "TOptions_co | ChatOptions[Any] | None" = None,
options: TOptions_co | ChatOptions[Any] | None = None,
**kwargs: Any,
) -> Awaitable[ChatResponse[Any]] | ResponseStream[ChatResponseUpdate, ChatResponse[Any]]:
"""Get a response from a chat client.
@@ -443,10 +445,10 @@ class BaseChatClient(SerializationMixin, ABC, Generic[TOptions_co]):
default_options: TOptions_co | Mapping[str, Any] | None = None,
chat_message_store_factory: Callable[[], ChatMessageStoreProtocol] | None = None,
context_provider: ContextProvider | None = None,
middleware: Sequence["MiddlewareTypes"] | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
**kwargs: Any,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Create a ChatAgent with this client.
This is a convenience method that creates a ChatAgent instance with this
+6 -4
View File
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import asyncio
import base64
import logging
@@ -333,7 +335,7 @@ class MCPTool:
parse_prompt_results: Literal[True] | Callable[[types.GetPromptResult], Any] | None = True,
session: ClientSession | None = None,
request_timeout: int | None = None,
chat_client: "ChatClientProtocol | None" = None,
chat_client: ChatClientProtocol | None = None,
additional_properties: dict[str, Any] | None = None,
) -> None:
"""Initialize the MCP Tool base.
@@ -940,7 +942,7 @@ class MCPStdioTool(MCPTool):
args: list[str] | None = None,
env: dict[str, str] | None = None,
encoding: str | None = None,
chat_client: "ChatClientProtocol | None" = None,
chat_client: ChatClientProtocol | None = None,
additional_properties: dict[str, Any] | None = None,
**kwargs: Any,
) -> None:
@@ -1059,7 +1061,7 @@ class MCPStreamableHTTPTool(MCPTool):
approval_mode: (Literal["always_require", "never_require"] | HostedMCPSpecificApproval | None) = None,
allowed_tools: Collection[str] | None = None,
terminate_on_close: bool | None = None,
chat_client: "ChatClientProtocol | None" = None,
chat_client: ChatClientProtocol | None = None,
additional_properties: dict[str, Any] | None = None,
http_client: httpx.AsyncClient | None = None,
**kwargs: Any,
@@ -1173,7 +1175,7 @@ class MCPWebsocketTool(MCPTool):
description: str | None = None,
approval_mode: (Literal["always_require", "never_require"] | HostedMCPSpecificApproval | None) = None,
allowed_tools: Collection[str] | None = None,
chat_client: "ChatClientProtocol | None" = None,
chat_client: ChatClientProtocol | None = None,
additional_properties: dict[str, Any] | None = None,
**kwargs: Any,
) -> None:
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from abc import ABC, abstractmethod
from collections.abc import MutableSequence, Sequence
@@ -50,7 +52,7 @@ class Context:
self,
instructions: str | None = None,
messages: Sequence[ChatMessage] | None = None,
tools: Sequence["ToolProtocol"] | None = None,
tools: Sequence[ToolProtocol] | None = None,
):
"""Create a new Context object.
@@ -61,7 +63,7 @@ class Context:
"""
self.instructions = instructions
self.messages: Sequence[ChatMessage] = messages or []
self.tools: Sequence["ToolProtocol"] = tools or []
self.tools: Sequence[ToolProtocol] = tools or []
# region ContextProvider
@@ -151,7 +153,7 @@ class ContextProvider(ABC):
"""
pass
async def __aenter__(self) -> "Self":
async def __aenter__(self) -> Self:
"""Enter the async context manager.
Override this method to perform any setup operations when the context provider is entered.
@@ -1,6 +1,8 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
from typing import Annotated, Any, ClassVar, TypeVar
from pydantic import Field, UrlConstraints
@@ -48,7 +50,7 @@ class AFBaseSettings(BaseSettings):
kwargs = {k: v for k, v in kwargs.items() if v is not None}
super().__init__(**kwargs)
def __new__(cls: type["TSettings"], *args: Any, **kwargs: Any) -> "TSettings":
def __new__(cls: type[TSettings], *args: Any, **kwargs: Any) -> TSettings:
"""Override the __new__ method to set the env_prefix."""
# for both, if supplied but None, set to default
if "env_file_encoding" in kwargs and kwargs["env_file_encoding"] is not None:
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import json
import re
from collections.abc import Mapping, MutableMapping
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import os
from typing import Any, Final
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
from collections.abc import MutableMapping, Sequence
from typing import Any, Protocol, TypeVar
@@ -74,7 +76,7 @@ class ChatMessageStoreProtocol(Protocol):
@classmethod
async def deserialize(
cls, serialized_store_state: MutableMapping[str, Any], **kwargs: Any
) -> "ChatMessageStoreProtocol":
) -> ChatMessageStoreProtocol:
"""Creates a new instance of the store from previously serialized state.
This method, together with ``serialize()`` can be used to save and load messages from a persistent store
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import contextlib
import importlib
import logging
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, ClassVar, Generic
@@ -63,7 +65,7 @@ class AzureOpenAIAssistantsClient(
ad_token: str | None = None,
ad_token_provider: AsyncAzureADTokenProvider | None = None,
token_endpoint: str | None = None,
credential: "TokenCredential | None" = None,
credential: TokenCredential | None = None,
default_headers: Mapping[str, str] | None = None,
async_client: AsyncAzureOpenAI | None = None,
env_file_path: str | None = None,
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import json
import logging
import sys
@@ -175,7 +177,7 @@ class AzureOpenAIChatClient( # type: ignore[misc]
env_file_path: str | None = None,
env_file_encoding: str | None = None,
instruction_role: str | None = None,
middleware: Sequence["MiddlewareTypes"] | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
**kwargs: Any,
) -> None:
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
@@ -15,7 +17,7 @@ logger: logging.Logger = logging.getLogger(__name__)
def get_entra_auth_token(
credential: "TokenCredential",
credential: TokenCredential,
token_endpoint: str,
**kwargs: Any,
) -> str | None:
@@ -49,7 +51,7 @@ def get_entra_auth_token(
async def get_entra_auth_token_async(
credential: "AsyncTokenCredential", token_endpoint: str, **kwargs: Any
credential: AsyncTokenCredential, token_endpoint: str, **kwargs: Any
) -> str | None:
"""Retrieve a async Microsoft Entra Auth Token for a given token endpoint.
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import Mapping, Sequence
from typing import TYPE_CHECKING, Any, Generic
@@ -74,7 +76,7 @@ class AzureOpenAIResponsesClient( # type: ignore[misc]
env_file_path: str | None = None,
env_file_encoding: str | None = None,
instruction_role: str | None = None,
middleware: Sequence["MiddlewareTypes"] | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
**kwargs: Any,
) -> None:
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import logging
import sys
from collections.abc import Awaitable, Callable, Mapping
@@ -110,7 +112,7 @@ class AzureOpenAISettings(AFBaseSettings):
default_token_endpoint: str = DEFAULT_AZURE_TOKEN_ENDPOINT
def get_azure_auth_token(
self, credential: "TokenCredential", token_endpoint: str | None = None, **kwargs: Any
self, credential: TokenCredential, token_endpoint: str | None = None, **kwargs: Any
) -> str | None:
"""Retrieve a Microsoft Entra Auth Token for a given token endpoint for the use with Azure OpenAI.
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import Awaitable, Callable, MutableMapping, Sequence
from typing import TYPE_CHECKING, Any, Generic, cast
@@ -177,7 +179,7 @@ class OpenAIAssistantProvider(Generic[TOptions_co]):
self._client = AsyncOpenAI(**client_args)
async def __aenter__(self) -> "Self":
async def __aenter__(self) -> Self:
"""Async context manager entry."""
return self
@@ -206,7 +208,7 @@ class OpenAIAssistantProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Create a new assistant on OpenAI and return a ChatAgent.
This method creates a new assistant on the OpenAI service and wraps it
@@ -314,7 +316,7 @@ class OpenAIAssistantProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Retrieve an existing assistant by ID and return a ChatAgent.
This method fetches an existing assistant from OpenAI by its ID
@@ -380,7 +382,7 @@ class OpenAIAssistantProvider(Generic[TOptions_co]):
default_options: TOptions_co | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
context_provider: ContextProvider | None = None,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Wrap an existing SDK Assistant object as a ChatAgent.
This method does NOT make any HTTP calls. It simply wraps an already-
@@ -524,7 +526,7 @@ class OpenAIAssistantProvider(Generic[TOptions_co]):
context_provider: ContextProvider | None,
default_options: TOptions_co | None = None,
**kwargs: Any,
) -> "ChatAgent[TOptions_co]":
) -> ChatAgent[TOptions_co]:
"""Create a ChatAgent from an Assistant.
Args:
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import json
import sys
from collections.abc import (
@@ -227,7 +229,7 @@ class OpenAIAssistantsClient( # type: ignore[misc]
async_client: AsyncOpenAI | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
middleware: Sequence["MiddlewareTypes"] | None = None,
middleware: Sequence[MiddlewareTypes] | None = None,
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
**kwargs: Any,
) -> None:
@@ -325,7 +327,7 @@ class OpenAIAssistantsClient( # type: ignore[misc]
self.thread_id: str | None = thread_id
self._should_delete_assistant: bool = False
async def __aenter__(self) -> "Self":
async def __aenter__(self) -> Self:
"""Async context manager entry."""
return self
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import json
import sys
from collections.abc import AsyncIterable, Awaitable, Callable, Mapping, MutableMapping, Sequence
@@ -305,7 +307,7 @@ class RawOpenAIChatClient( # type: ignore[misc]
run_options["response_format"] = type_to_response_format_param(response_format)
return run_options
def _parse_response_from_openai(self, response: ChatCompletion, options: Mapping[str, Any]) -> "ChatResponse":
def _parse_response_from_openai(self, response: ChatCompletion, options: Mapping[str, Any]) -> ChatResponse:
"""Parse a response from OpenAI into a ChatResponse."""
response_metadata = self._get_metadata_from_chat_response(response)
messages: list[ChatMessage] = []
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Any
@@ -29,7 +31,7 @@ class ContentFilterResult:
severity: ContentFilterResultSeverity = ContentFilterResultSeverity.SAFE
@classmethod
def from_inner_error_result(cls, inner_error_results: dict[str, Any]) -> "ContentFilterResult":
def from_inner_error_result(cls, inner_error_results: dict[str, Any]) -> ContentFilterResult:
"""Creates a ContentFilterResult from the inner error results.
Args:
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import (
AsyncIterable,
@@ -815,7 +817,7 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
self,
response: OpenAIResponse | ParsedResponse[BaseModel],
options: dict[str, Any],
) -> "ChatResponse":
) -> ChatResponse:
"""Parse an OpenAI Responses API response into a ChatResponse."""
structured_response: BaseModel | None = response.output_parsed if isinstance(response, ParsedResponse) else None # type: ignore[reportUnknownMemberType]
@@ -945,7 +947,7 @@ class RawOpenAIResponsesClient( # type: ignore[misc]
)
case "code_interpreter_call": # ResponseOutputCodeInterpreterCall
call_id = getattr(item, "call_id", None) or getattr(item, "id", None)
outputs: list["Content"] = []
outputs: list[Content] = []
if item_outputs := getattr(item, "outputs", None):
for code_output in item_outputs:
if getattr(code_output, "type", None) == "logs":
@@ -1456,7 +1458,7 @@ class OpenAIResponsesClient( # type: ignore[misc]
env_file_path: str | None = None,
env_file_encoding: str | None = None,
middleware: (
Sequence["ChatMiddleware | ChatMiddlewareCallable | FunctionMiddleware | FunctionMiddlewareCallable"] | None
Sequence[ChatMiddleware | ChatMiddlewareCallable | FunctionMiddleware | FunctionMiddlewareCallable] | None
) = None,
function_invocation_configuration: FunctionInvocationConfiguration | None = None,
**kwargs: Any,
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import logging
from collections.abc import Awaitable, Callable, Mapping, MutableMapping, Sequence
from copy import copy
@@ -1,5 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import sys
from collections.abc import Callable, Mapping
from pathlib import Path
@@ -1,4 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import os
from collections.abc import MutableMapping
from contextvars import ContextVar
@@ -101,7 +103,7 @@ class Property(SerializationMixin):
@classmethod
def from_dict(
cls, value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
) -> "Property":
) -> Property:
"""Create a Property instance from a dictionary, dispatching to the appropriate subclass."""
# Only dispatch if we're being called on the base Property class
if cls is not Property:
@@ -211,7 +213,7 @@ class PropertySchema(SerializationMixin):
@classmethod
def from_dict(
cls, value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
) -> "PropertySchema":
) -> PropertySchema:
"""Create a PropertySchema instance from a dictionary, filtering out 'kind' field."""
# Filter out 'kind', 'type', 'name', and 'description' fields that may appear in YAML
# but aren't PropertySchema params
@@ -491,7 +493,7 @@ class AgentDefinition(SerializationMixin):
@classmethod
def from_dict(
cls, value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
) -> "AgentDefinition":
) -> AgentDefinition:
"""Create an AgentDefinition instance from a dictionary, dispatching to the appropriate subclass."""
# Only dispatch if we're being called on the base AgentDefinition class
if cls is not AgentDefinition:
@@ -537,7 +539,7 @@ class Tool(SerializationMixin):
@classmethod
def from_dict(
cls: type[TTool], value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
) -> "TTool":
) -> TTool:
"""Create a Tool instance from a dictionary, dispatching to the appropriate subclass."""
# Only dispatch if we're being called on the base Tool class
if cls is not Tool:
@@ -867,7 +869,7 @@ class Resource(SerializationMixin):
@classmethod
def from_dict(
cls, value: MutableMapping[str, Any], /, *, dependencies: MutableMapping[str, Any] | None = None
) -> "Resource":
) -> Resource:
"""Create a Resource instance from a dictionary, dispatching to the appropriate subclass."""
# Only dispatch if we're being called on the base Resource class
if cls is not Resource:
@@ -7,6 +7,8 @@ This module implements handlers for:
- InvokePromptAgent: Invoke a local prompt-based agent
"""
from __future__ import annotations
import json
from collections.abc import AsyncGenerator
from typing import Any, cast
@@ -185,7 +187,7 @@ def _build_messages_from_state(ctx: ActionContext) -> list[ChatMessage]:
@action_handler("InvokeAzureAgent")
async def handle_invoke_azure_agent(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]:
async def handle_invoke_azure_agent(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]:
"""Invoke a hosted Azure AI agent.
Supports both Python-style and .NET-style YAML schemas:
@@ -523,7 +525,7 @@ def _normalize_variable_path(variable: str) -> str:
@action_handler("InvokePromptAgent")
async def handle_invoke_prompt_agent(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]:
async def handle_invoke_prompt_agent(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]:
"""Invoke a local prompt-based agent (similar to InvokeAzureAgent but for local agents).
Action schema:
@@ -16,6 +16,8 @@ network calls. The `return; yield` pattern makes a function an async generator w
actually yielding any events.
"""
from __future__ import annotations
from collections.abc import AsyncGenerator
from typing import TYPE_CHECKING, Any, cast
@@ -37,7 +39,7 @@ logger = get_logger("agent_framework.declarative.workflows.actions")
@action_handler("SetValue")
async def handle_set_value(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_set_value(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Set a value in the workflow state.
Action schema:
@@ -63,7 +65,7 @@ async def handle_set_value(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent,
@action_handler("SetVariable")
async def handle_set_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_set_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Set a variable in the workflow state (.NET workflow format).
This is an alias for SetValue with 'variable' instead of 'path'.
@@ -113,7 +115,7 @@ def _normalize_variable_path(variable: str) -> str:
@action_handler("AppendValue")
async def handle_append_value(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_append_value(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Append a value to a list in the workflow state.
Action schema:
@@ -139,7 +141,7 @@ async def handle_append_value(ctx: ActionContext) -> AsyncGenerator[WorkflowEven
@action_handler("SendActivity")
async def handle_send_activity(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_send_activity(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Send text or attachments to the user.
Action schema (object form):
@@ -189,7 +191,7 @@ async def handle_send_activity(ctx: ActionContext) -> AsyncGenerator[WorkflowEve
@action_handler("EmitEvent")
async def handle_emit_event(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_emit_event(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Emit a custom workflow event.
Action schema:
@@ -213,7 +215,7 @@ async def handle_emit_event(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent,
yield CustomEvent(name=name, data=evaluated_data)
def _evaluate_dict_values(d: dict[str, Any], state: "WorkflowState") -> dict[str, Any]:
def _evaluate_dict_values(d: dict[str, Any], state: WorkflowState) -> dict[str, Any]:
"""Recursively evaluate PowerFx expressions in a dictionary.
Args:
@@ -245,7 +247,7 @@ def _evaluate_dict_values(d: dict[str, Any], state: "WorkflowState") -> dict[str
@action_handler("SetTextVariable")
async def handle_set_text_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_set_text_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Set a text variable with string interpolation support.
This is similar to SetVariable but supports multi-line text with
@@ -281,7 +283,7 @@ async def handle_set_text_variable(ctx: ActionContext) -> AsyncGenerator[Workflo
@action_handler("SetMultipleVariables")
async def handle_set_multiple_variables(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_set_multiple_variables(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Set multiple variables at once.
Action schema:
@@ -313,7 +315,7 @@ async def handle_set_multiple_variables(ctx: ActionContext) -> AsyncGenerator[Wo
@action_handler("ResetVariable")
async def handle_reset_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_reset_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Reset a variable to its default/blank state.
Action schema:
@@ -336,7 +338,7 @@ async def handle_reset_variable(ctx: ActionContext) -> AsyncGenerator[WorkflowEv
@action_handler("ClearAllVariables")
async def handle_clear_all_variables(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_clear_all_variables(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Clear all turn-scoped variables.
Action schema:
@@ -350,7 +352,7 @@ async def handle_clear_all_variables(ctx: ActionContext) -> AsyncGenerator[Workf
@action_handler("CreateConversation")
async def handle_create_conversation(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_create_conversation(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Create a new conversation context.
Action schema (.NET style):
@@ -399,7 +401,7 @@ async def handle_create_conversation(ctx: ActionContext) -> AsyncGenerator[Workf
@action_handler("AddConversationMessage")
async def handle_add_conversation_message(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_add_conversation_message(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Add a message to a conversation.
Action schema:
@@ -451,7 +453,7 @@ async def handle_add_conversation_message(ctx: ActionContext) -> AsyncGenerator[
@action_handler("CopyConversationMessages")
async def handle_copy_conversation_messages(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_copy_conversation_messages(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Copy messages from one conversation to another.
Action schema:
@@ -506,7 +508,7 @@ async def handle_copy_conversation_messages(ctx: ActionContext) -> AsyncGenerato
@action_handler("RetrieveConversationMessages")
async def handle_retrieve_conversation_messages(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_retrieve_conversation_messages(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Retrieve messages from a conversation and store in a variable.
Action schema:
@@ -547,7 +549,7 @@ async def handle_retrieve_conversation_messages(ctx: ActionContext) -> AsyncGene
yield # Make it a generator
def _interpolate_string(text: str, state: "WorkflowState") -> str:
def _interpolate_string(text: str, state: WorkflowState) -> str:
"""Interpolate {Variable.Path} references in a string.
Args:
@@ -7,6 +7,8 @@ This module implements handlers for:
- TryCatch: Try-catch-finally error handling
"""
from __future__ import annotations
from collections.abc import AsyncGenerator
from dataclasses import dataclass
@@ -44,7 +46,7 @@ class ErrorEvent(WorkflowEvent):
@action_handler("ThrowException")
async def handle_throw_exception(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_throw_exception(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Raise an exception that can be caught by TryCatch.
Action schema:
@@ -67,7 +69,7 @@ async def handle_throw_exception(ctx: ActionContext) -> AsyncGenerator[WorkflowE
@action_handler("TryCatch")
async def handle_try_catch(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]:
async def handle_try_catch(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]:
"""Try-catch-finally error handling.
Action schema:
@@ -23,6 +23,8 @@ the full RecalcEngine API. We work around this by:
See: dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/PowerFx/
"""
from __future__ import annotations
import logging
import sys
from collections.abc import Mapping
@@ -148,7 +150,7 @@ class DeclarativeWorkflowState:
"""
self._state = state
def initialize(self, inputs: "Mapping[str, Any] | None" = None) -> None:
def initialize(self, inputs: Mapping[str, Any] | None = None) -> None:
"""Initialize the declarative state with inputs.
Args:
@@ -814,7 +816,7 @@ class DeclarativeActionExecutor(Executor):
async def _ensure_state_initialized(
self,
ctx: "WorkflowContext[Any, Any]",
ctx: WorkflowContext[Any, Any],
trigger: Any,
) -> DeclarativeWorkflowState:
"""Ensure declarative state is initialized.
@@ -11,6 +11,8 @@ action definitions and creates a proper workflow graph with:
- Loop edges for foreach
"""
from __future__ import annotations
from typing import Any
from agent_framework._workflows import (
@@ -10,6 +10,8 @@ Each YAML action becomes a real Executor node in the workflow graph,
enabling checkpointing, visualization, and pause/resume capabilities.
"""
from __future__ import annotations
from collections.abc import Mapping
from pathlib import Path
from typing import Any, cast
@@ -506,7 +508,7 @@ class WorkflowFactory:
f"Invalid agent definition. Expected 'file', 'kind', or 'connection': {agent_def}"
)
def register_agent(self, name: str, agent: SupportsAgentRun | AgentExecutor) -> "WorkflowFactory":
def register_agent(self, name: str, agent: SupportsAgentRun | AgentExecutor) -> WorkflowFactory:
"""Register an agent instance with the factory for use in workflows.
Registered agents are available to InvokeAzureAgent actions by name.
@@ -552,7 +554,7 @@ class WorkflowFactory:
self._agents[name] = agent
return self
def register_binding(self, name: str, func: Any) -> "WorkflowFactory":
def register_binding(self, name: str, func: Any) -> WorkflowFactory:
"""Register a function binding with the factory for use in workflow actions.
Bindings allow workflow actions to invoke Python functions by name.
@@ -7,6 +7,8 @@ workflow actions defined in YAML. Each action type (InvokeAzureAgent, Foreach, e
has a corresponding handler registered via the @action_handler decorator.
"""
from __future__ import annotations
from collections.abc import AsyncGenerator, Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
@@ -27,13 +29,13 @@ class ActionContext:
for executing nested actions (for control flow constructs like Foreach).
"""
state: "WorkflowState"
state: WorkflowState
"""The current workflow state with variables and agent results."""
action: dict[str, Any]
"""The action definition from the YAML."""
execute_actions: "ExecuteActionsFn"
execute_actions: ExecuteActionsFn
"""Function to execute a list of nested actions (for Foreach, If, etc.)."""
agents: dict[str, Any]
@@ -150,7 +152,7 @@ class ActionHandler(Protocol):
def __call__(
self,
ctx: ActionContext,
) -> AsyncGenerator[WorkflowEvent, None]:
) -> AsyncGenerator[WorkflowEvent]:
"""Execute the action and yield events.
Args:
@@ -8,6 +8,8 @@ This module implements handlers for human input patterns:
- ExternalLoop processing: Loop while waiting for external input
"""
from __future__ import annotations
from collections.abc import AsyncGenerator
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, cast
@@ -75,7 +77,7 @@ class ExternalLoopEvent(WorkflowEvent):
@action_handler("Question")
async def handle_question(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_question(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Handle Question action - request human input with optional validation.
Action schema:
@@ -140,7 +142,7 @@ async def handle_question(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, N
@action_handler("RequestExternalInput")
async def handle_request_external_input(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_request_external_input(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Handle RequestExternalInput action - request input from external system.
Action schema:
@@ -197,7 +199,7 @@ async def handle_request_external_input(ctx: ActionContext) -> AsyncGenerator[Wo
@action_handler("WaitForInput")
async def handle_wait_for_input(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent, None]: # noqa: RUF029
async def handle_wait_for_input(ctx: ActionContext) -> AsyncGenerator[WorkflowEvent]: # noqa: RUF029
"""Handle WaitForInput action - pause and wait for external input.
Action schema:
@@ -231,7 +233,7 @@ async def handle_wait_for_input(ctx: ActionContext) -> AsyncGenerator[WorkflowEv
def process_external_loop(
input_config: dict[str, Any],
state: "WorkflowState",
state: WorkflowState,
) -> tuple[bool, str | None]:
"""Process the externalLoop.when pattern from action input.
@@ -10,6 +10,8 @@ These functions can be used as fallbacks when PowerFx is not available,
or registered with the PowerFx engine when it is available.
"""
from __future__ import annotations
from typing import Any, cast
@@ -9,6 +9,8 @@ This module provides state management for declarative workflows, handling:
- Agent results and context
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any, cast
@@ -624,7 +626,7 @@ class WorkflowState:
"""Reset the agent result for a new agent invocation."""
self._agent.clear()
def clone(self) -> "WorkflowState":
def clone(self) -> WorkflowState:
"""Create a shallow copy of the state.
Returns:
+1 -1
View File
@@ -22,7 +22,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"powerfx>=0.0.31; python_version < '3.14'",
"pyyaml>=6.0,<7.0",
]
@@ -6,6 +6,8 @@ This module provides a clean abstraction layer for managing conversations
while wrapping AgentFramework's AgentThread underneath.
"""
from __future__ import annotations
import time
import uuid
from abc import ABC, abstractmethod
@@ -2,6 +2,8 @@
"""Agent Framework entity discovery implementation."""
from __future__ import annotations
import ast
import importlib
import importlib.util
@@ -2,6 +2,8 @@
"""Agent Framework executor implementation."""
from __future__ import annotations
import json
import logging
from collections.abc import AsyncGenerator
@@ -184,7 +186,7 @@ class AgentFrameworkExecutor:
raise EntityNotFoundError(f"Entity '{entity_id}' not found")
return entity_info
async def execute_streaming(self, request: AgentFrameworkRequest) -> AsyncGenerator[Any, None]:
async def execute_streaming(self, request: AgentFrameworkRequest) -> AsyncGenerator[Any]:
"""Execute request and stream results in OpenAI format.
Args:
@@ -229,7 +231,7 @@ class AgentFrameworkExecutor:
# Aggregate into final response
return await self.message_mapper.aggregate_to_response(events, request)
async def execute_entity(self, entity_id: str, request: AgentFrameworkRequest) -> AsyncGenerator[Any, None]:
async def execute_entity(self, entity_id: str, request: AgentFrameworkRequest) -> AsyncGenerator[Any]:
"""Execute the entity and yield raw Agent Framework events plus trace events.
Args:
@@ -286,7 +288,7 @@ class AgentFrameworkExecutor:
async def _execute_agent(
self, agent: SupportsAgentRun, request: AgentFrameworkRequest, trace_collector: Any
) -> AsyncGenerator[Any, None]:
) -> AsyncGenerator[Any]:
"""Execute Agent Framework agent with trace collection and optional thread support.
Args:
@@ -361,7 +363,7 @@ class AgentFrameworkExecutor:
async def _execute_workflow(
self, workflow: Workflow, request: AgentFrameworkRequest, trace_collector: Any
) -> AsyncGenerator[Any, None]:
) -> AsyncGenerator[Any]:
"""Execute Agent Framework workflow with checkpoint support via conversation items.
Args:
@@ -2,6 +2,8 @@
"""Agent Framework message mapper implementation."""
from __future__ import annotations
import json
import logging
import time
@@ -6,6 +6,8 @@ This executor mirrors the AgentFrameworkExecutor interface but routes
requests to OpenAI's API instead of executing local entities.
"""
from __future__ import annotations
import logging
import os
from collections.abc import AsyncGenerator
@@ -76,7 +78,7 @@ class OpenAIExecutor:
return self._client
async def execute_streaming(self, request: AgentFrameworkRequest) -> AsyncGenerator[Any, None]:
async def execute_streaming(self, request: AgentFrameworkRequest) -> AsyncGenerator[Any]:
"""Execute request via OpenAI and stream results in OpenAI format.
This mirrors AgentFrameworkExecutor.execute_streaming() interface.
@@ -2,6 +2,8 @@
"""FastAPI server implementation."""
from __future__ import annotations
import asyncio
import importlib.metadata
import inspect
@@ -287,7 +289,7 @@ class DevServer:
"""Create the FastAPI application."""
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
# Startup
logger.info("Starting Agent Framework Server")
await self._ensure_executor()
@@ -623,7 +625,7 @@ class DevServer:
entity_path = Path(entity_path_str)
# Stream deployment events
async def event_generator() -> AsyncGenerator[str, None]:
async def event_generator() -> AsyncGenerator[str]:
async for event in self.deployment_manager.deploy(config, entity_path):
# Format as SSE
import json
@@ -1085,7 +1087,7 @@ class DevServer:
async def _stream_execution(
self, executor: AgentFrameworkExecutor, request: AgentFrameworkRequest
) -> AsyncGenerator[str, None]:
) -> AsyncGenerator[str]:
"""Stream execution directly through executor."""
try:
# Collect events for final response.completed event
@@ -1155,7 +1157,7 @@ class DevServer:
async def _stream_openai_execution(
self, executor: OpenAIExecutor, request: AgentFrameworkRequest
) -> AsyncGenerator[str, None]:
) -> AsyncGenerator[str]:
"""Stream execution through OpenAI executor.
OpenAI events are already in final format - no conversion or aggregation needed.
@@ -1212,7 +1214,7 @@ class DevServer:
async def _stream_with_cancellation(
self, executor: AgentFrameworkExecutor, request: AgentFrameworkRequest, response_id: str
) -> AsyncGenerator[str, None]:
) -> AsyncGenerator[str]:
"""Stream execution with automatic cancellation on client disconnect.
This wrapper adds cancellation support to the execution stream:
@@ -1231,7 +1233,7 @@ class DevServer:
"""
task = None
async def execution_wrapper() -> AsyncGenerator[str, None]:
async def execution_wrapper() -> AsyncGenerator[str]:
"""Inner wrapper to handle the actual execution."""
try:
logger.debug(f"[CANCELLATION] Starting execution for {response_id}")
@@ -2,6 +2,8 @@
"""Simplified tracing integration for Agent Framework Server."""
from __future__ import annotations
import logging
from collections.abc import Generator, Sequence
from contextlib import contextmanager
@@ -120,9 +122,7 @@ class SimpleTraceCollector(SpanExporter):
@contextmanager
def capture_traces(
response_id: str | None = None, entity_id: str | None = None
) -> Generator[SimpleTraceCollector, None, None]:
def capture_traces(response_id: str | None = None, entity_id: str | None = None) -> Generator[SimpleTraceCollector]:
"""Context manager to capture traces during execution.
Args:
@@ -559,7 +559,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
// Backend successfully returned conversations list
setAvailableConversations(conversations);
if (conversations.length > 0) {
// Found conversations on backend - use most recent
const mostRecent = conversations[0];
@@ -614,7 +614,7 @@ export function AgentView({ selectedAgent, onDebugEvent }: AgentViewProps) {
// Check for incomplete stream and resume if needed
const state = loadStreamingState(mostRecent.id);
if (state && !state.completed) {
accumulatedTextRef.current = state.accumulatedText || "";
// Add assistant message with resumed text
@@ -565,7 +565,7 @@ export const WorkflowFlow = memo(function WorkflowFlow({
0% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: -10; }
}
/* Dark theme styles for React Flow controls */
.dark .react-flow__controls {
background-color: rgba(31, 41, 55, 0.9) !important;
@@ -13,7 +13,7 @@ export function LoadingSpinner({ size = "md", className }: LoadingSpinnerProps)
"animate-spin",
{
"h-4 w-4": size === "sm",
"h-6 w-6": size === "md",
"h-6 w-6": size === "md",
"h-8 w-8": size === "lg",
},
className
@@ -9,8 +9,8 @@ interface LoadingStateProps {
fullPage?: boolean
}
export function LoadingState({
message = "Loading...",
export function LoadingState({
message = "Loading...",
description,
size = "md",
className,
@@ -1,6 +1,6 @@
/**
* Streaming State Persistence
*
*
* Manages browser storage of streaming response state to enable:
* - Resume interrupted streams after page refresh
* - Replay cached events before fetching new ones
@@ -73,7 +73,7 @@ export function loadStreamingState(conversationId: string): StreamingState | nul
try {
const key = getStorageKey(conversationId);
const data = localStorage.getItem(key);
if (!data) {
return null;
}
@@ -111,9 +111,9 @@ export function updateStreamingState(
try {
const existing = loadStreamingState(conversationId);
const sequenceNumber = "sequence_number" in event ? event.sequence_number : undefined;
const newEvents = existing ? [...existing.events, event] : [event];
const state: StreamingState = {
conversationId,
responseId,
@@ -174,7 +174,7 @@ export function clearExpiredStreamingStates(): void {
if (data) {
const state: StreamingState = JSON.parse(data);
const age = now - state.timestamp;
if (age > STATE_EXPIRY_MS || state.completed) {
localStorage.removeItem(key);
}
+1 -1
View File
@@ -23,7 +23,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"fastapi>=0.104.0",
"uvicorn[standard]>=0.24.0",
"python-dotenv>=1.0.0",
+3 -3
View File
@@ -15,15 +15,15 @@ The durable task integration lets you host Microsoft Agent Framework agents usin
### Basic Usage Example
```python
from durabletask import TaskHubGrpcWorker
from durabletask.worker import TaskHubGrpcWorker
from agent_framework.azure import DurableAIAgentWorker
# Create the worker
with TaskHubGrpcWorker(...) as worker:
# Register the agent worker wrapper
agent_worker = DurableAIAgentWorker(worker)
# Register the agent
agent_worker.add_agent(my_agent)
```
+1 -1
View File
@@ -22,7 +22,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"durabletask>=1.3.0",
"durabletask-azuremanaged>=1.3.0",
"python-dateutil>=2.8.0",
+1 -1
View File
@@ -23,7 +23,7 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"agent-framework-core",
"agent-framework-core>=1.0.0b260130",
"foundry-local-sdk>=0.5.1,<1",
]

Some files were not shown because too many files have changed in this diff Show More