Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ _What's available?_
- Triggers / Bindings : Blob, Cosmos DB, Event Grid, Event Hub, HTTP, Kafka, MySQL, Queue, ServiceBus, SQL, Timer, and Warmup
- Create a Python Function on Linux using a custom docker image
- Triggers / Bindings : Custom binding support
- Pluggable markdown Agent injection through provider extension packages

Agent APIs are provider-neutral and add no binding metadata. Install a provider
package such as `azurefunctions-agents-extension-agent-framework`, then use
`FunctionApp.markdown_agent(provider=...)`, `AgentFunctionApp`, or `AgentDFApp`. Durable
support is installed through the provider package's `[durable]` extra and is
not imported by the core SDK. Each Agent binding may select a different
installed provider; `AgentFunctionApp` supplies a default, which all Durable Agent calls
use.

#### Get Started

Expand Down
4 changes: 3 additions & 1 deletion azure/functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ._eventgrid import CloudEvent, EventGridEvent, EventGridOutputEvent
from ._cosmosdb import Document, DocumentList
from ._http import HttpRequest, HttpResponse
from .decorators import (FunctionApp, Function, Blueprint,
from .decorators import (AgentDFApp, AgentFunctionApp, FunctionApp, Function, Blueprint,
DecoratorApi, DataType, AuthLevel,
Cardinality, AccessRights, HttpMethod,
AsgiFunctionApp, WsgiFunctionApp,
Expand Down Expand Up @@ -94,6 +94,8 @@

# PyStein implementation
'FunctionApp',
'AgentFunctionApp',
'AgentDFApp',
'Function',
'FunctionRegister',
'DecoratorApi',
Expand Down
4 changes: 3 additions & 1 deletion azure/functions/decorators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from .core import Cardinality, AccessRights, CosmosDBChangeFeedMode
from .function_app import FunctionApp, Function, DecoratorApi, DataType, \
from .function_app import AgentDFApp, AgentFunctionApp, FunctionApp, Function, DecoratorApi, DataType, \
AuthLevel, Blueprint, ExternalHttpFunctionApp, AsgiFunctionApp, \
WsgiFunctionApp, FunctionRegister, TriggerApi, BindingApi, \
SettingsApi, BlobSource, McpPropertyType
Expand All @@ -10,6 +10,8 @@

__all__ = [
'FunctionApp',
'AgentFunctionApp',
'AgentDFApp',
'Function',
'FunctionRegister',
'DecoratorApi',
Expand Down
29 changes: 29 additions & 0 deletions azure/functions/decorators/_agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import importlib

_AGENTS_BASE_MODULE = 'azurefunctions.extensions.agents.base'
_agents_base = None


def _agent_provider_distribution(provider: str) -> str:
normalized = provider.replace('_', '-')
Comment thread
hallvictoria marked this conversation as resolved.
return f'azurefunctions-agents-extension-{normalized}'


def _import_agents_base():
global _agents_base
if _agents_base is None:
_agents_base = importlib.import_module(_AGENTS_BASE_MODULE)
return _agents_base


def _load_agents_base(provider: str):
try:
return _import_agents_base()
except ImportError as exc:
distribution = _agent_provider_distribution(provider)
raise ImportError(
f"Agent provider {provider!r} is not installed. "
f"Install {distribution!r}."
) from exc
64 changes: 63 additions & 1 deletion azure/functions/decorators/function_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from abc import ABC
from datetime import time
from typing import Any, Callable, Dict, List, Optional, Union, \
from typing import Any, Callable, cast, Dict, List, Optional, Union, \
Iterable

from azure.functions.decorators.blob import BlobTrigger, BlobInput, BlobOutput
Expand Down Expand Up @@ -62,6 +62,7 @@
from .._http_wsgi import WsgiMiddleware, Context
from azure.functions.decorators.mysql import MySqlInput, MySqlOutput, \
MySqlTrigger
from ._agents import _agent_provider_distribution, _load_agents_base


class Function(object):
Expand Down Expand Up @@ -4540,6 +4541,67 @@ def __init__(self,
"""
super().__init__(auth_level=http_auth_level)

def markdown_agent(self, *, provider: str,
**kwargs: Any) -> Callable[..., Any]:
"""Inject a provider Agent built from a markdown definition."""
agents_base = _load_agents_base(provider)
return cast(Callable[..., Any], agents_base.markdown_agent(
self, provider=provider, **kwargs))


class AgentFunctionApp(FunctionApp):
"""FunctionApp configured for one pluggable Agent provider."""

def __init__(self,
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION,
*, provider: str, app_root=None, **provider_options):
super().__init__(http_auth_level=http_auth_level)
self._agent_provider = provider
agents_base = _load_agents_base(provider)
agents_base.configure_app(
self,
provider=provider,
app_root=app_root,
provider_options=provider_options,
)

def markdown_agent(self, **kwargs: Any) -> Callable[..., Any]:
return super().markdown_agent(provider=self._agent_provider, **kwargs)


class AgentDFApp(AgentFunctionApp):
"""AgentFunctionApp with optional replay-safe Durable Agent orchestration."""

def __init__(self,
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION,
*, provider: str, app_root=None, **provider_options):
super().__init__(
http_auth_level=http_auth_level,
provider=provider,
app_root=app_root,
**provider_options,
)
try:
_load_agents_base(provider).configure_durable_app(self)
except ImportError as exc:
distribution = _agent_provider_distribution(provider)
raise ImportError(
f"Durable Agent support is not installed. "
f"Install {distribution + '[durable]'!r}."
) from exc

def orchestration_trigger(self, context_name: str,
orchestration: Optional[str] = None,
input_type: Optional[type] = None):
agents_base = _load_agents_base(self._agent_provider)
Comment thread
hallvictoria marked this conversation as resolved.
return agents_base.durable_orchestration_trigger(
self,
sdk_decorator=super().orchestration_trigger,
context_name=context_name,
orchestration=orchestration,
input_type=input_type,
)


class Blueprint(TriggerApi, BindingApi, SettingsApi):
"""
Expand Down
37 changes: 36 additions & 1 deletion docs/ProgModelSpec.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from abc import ABC
from typing import Callable, Dict, List, Optional, Union, Iterable
from typing import Any, Callable, Dict, List, Optional, Union, Iterable

from azure.functions import AsgiMiddleware, WsgiMiddleware
from azure.functions.decorators.core import Binding, BlobSource, Trigger, DataType, \
Expand Down Expand Up @@ -1329,6 +1329,41 @@ class FunctionApp(FunctionRegister, TriggerApi, BindingApi):
"""
pass

def markdown_agent(self, *, provider: str, **kwargs: Any) -> Callable:
"""Inject an Agent supplied by a provider extension.

:param provider: Registered Agent provider ID.
:param kwargs: Provider and markdown binding options.
:return: Decorator function.
"""
pass


class AgentFunctionApp(FunctionApp):
"""FunctionApp configured for one Agent provider."""

def __init__(self,
http_auth_level: Union[AuthLevel, str] = AuthLevel.FUNCTION,
*, provider: str, app_root=None,
**provider_options: Any):
"""Configure an app with a provider and immutable defaults."""
pass

def markdown_agent(self, *, provider: Optional[str] = None,
**kwargs: Any) -> Callable:
"""Inject an Agent using the configured provider by default."""
pass


class AgentDFApp(AgentFunctionApp):
"""AgentFunctionApp with optional replay-safe Durable Agent orchestration."""

def orchestration_trigger(self, context_name: str,
orchestration: Optional[str] = None,
input_type: Optional[type] = None) -> Callable:
"""Register an orchestrator with a Durable Agent context."""
pass


class BluePrint(TriggerApi, BindingApi, SettingsApi):
"""Functions container class where all the functions
Expand Down
Loading
Loading