mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Introducing support for declarative yaml spec (#2002)
* first work on declarative * initial version of the declarative support * fix tests and mypy * fix parameters of functiontool * slight logic improvement * remove path until merge * updates from comments * create dispatcher and spec type, json_schema method * fix mypy, skipping model * updated lock * fixed declarative tests and renamed some other test files * refined loader * updated lock * fix mypy * added readme to samples folder * fixes from review * undid test file rename
This commit is contained in:
committed by
GitHub
Unverified
parent
d2d0f46e15
commit
92df9e14bf
@@ -59,6 +59,7 @@
|
||||
"OPENAI",
|
||||
"opentelemetry",
|
||||
"OTEL",
|
||||
"powerfx",
|
||||
"protos",
|
||||
"pydantic",
|
||||
"pytestmark",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
@@ -1 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
@@ -589,7 +589,7 @@ class ChatAgent(BaseAgent):
|
||||
chat_message_store_factory: Callable[[], ChatMessageStoreProtocol] | None = None,
|
||||
context_providers: ContextProvider | list[ContextProvider] | AggregateContextProvider | None = None,
|
||||
middleware: Middleware | list[Middleware] | None = None,
|
||||
# chat option params
|
||||
# chat options
|
||||
allow_multiple_tool_calls: bool | None = None,
|
||||
conversation_id: str | None = None,
|
||||
frequency_penalty: float | None = None,
|
||||
|
||||
@@ -214,6 +214,7 @@ def _merge_chat_options(
|
||||
*,
|
||||
base_chat_options: ChatOptions | Any | None,
|
||||
model_id: str | None = None,
|
||||
allow_multiple_tool_calls: bool | None = None,
|
||||
frequency_penalty: float | None = None,
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
@@ -239,6 +240,7 @@ def _merge_chat_options(
|
||||
Keyword Args:
|
||||
base_chat_options: Optional base ChatOptions to merge with direct parameters.
|
||||
model_id: The model_id to use for the agent.
|
||||
allow_multiple_tool_calls: Whether to allow multiple tool calls in a single response.
|
||||
frequency_penalty: The frequency penalty to use.
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
@@ -270,6 +272,7 @@ def _merge_chat_options(
|
||||
|
||||
return base_chat_options & ChatOptions(
|
||||
model_id=model_id,
|
||||
allow_multiple_tool_calls=allow_multiple_tool_calls,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
@@ -485,6 +488,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage],
|
||||
*,
|
||||
allow_multiple_tool_calls: bool | None = None,
|
||||
frequency_penalty: float | None = None,
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
@@ -517,6 +521,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
messages: The message or messages to send to the model.
|
||||
|
||||
Keyword Args:
|
||||
allow_multiple_tool_calls: Whether to allow multiple tool calls in a single response.
|
||||
frequency_penalty: The frequency penalty to use.
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
@@ -545,6 +550,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
chat_options = _merge_chat_options(
|
||||
base_chat_options=kwargs.pop("chat_options", None),
|
||||
model_id=model_id,
|
||||
allow_multiple_tool_calls=allow_multiple_tool_calls,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
@@ -580,6 +586,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage],
|
||||
*,
|
||||
allow_multiple_tool_calls: bool | None = None,
|
||||
frequency_penalty: float | None = None,
|
||||
logit_bias: dict[str | int, float] | None = None,
|
||||
max_tokens: int | None = None,
|
||||
@@ -612,6 +619,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
messages: The message or messages to send to the model.
|
||||
|
||||
Keyword Args:
|
||||
allow_multiple_tool_calls: Whether to allow multiple tool calls in a single response.
|
||||
frequency_penalty: The frequency penalty to use.
|
||||
logit_bias: The logit bias to use.
|
||||
max_tokens: The maximum number of tokens to generate.
|
||||
@@ -640,6 +648,7 @@ class BaseChatClient(SerializationMixin, ABC):
|
||||
chat_options = _merge_chat_options(
|
||||
base_chat_options=kwargs.pop("chat_options", None),
|
||||
model_id=model_id,
|
||||
allow_multiple_tool_calls=allow_multiple_tool_calls,
|
||||
frequency_penalty=frequency_penalty,
|
||||
logit_bias=logit_bias,
|
||||
max_tokens=max_tokens,
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
IMPORT_PATH = "agent_framework_declarative"
|
||||
PACKAGE_NAME = "agent-framework-declarative"
|
||||
_IMPORTS = ["__version__", "AgentFactory", "DeclarativeLoaderError", "ProviderLookupError", "ProviderTypeMapping"]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name in _IMPORTS:
|
||||
try:
|
||||
return getattr(importlib.import_module(IMPORT_PATH), name)
|
||||
except ModuleNotFoundError as exc:
|
||||
raise ModuleNotFoundError(
|
||||
f"The '{PACKAGE_NAME}' package is not installed, please do `pip install {PACKAGE_NAME}`"
|
||||
) from exc
|
||||
raise AttributeError(f"Module {IMPORT_PATH} has no attribute {name}.")
|
||||
|
||||
|
||||
def __dir__() -> list[str]:
|
||||
return _IMPORTS
|
||||
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from agent_framework_declarative import (
|
||||
AgentFactory,
|
||||
DeclarativeLoaderError,
|
||||
ProviderLookupError,
|
||||
ProviderTypeMapping,
|
||||
__version__,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AgentFactory",
|
||||
"DeclarativeLoaderError",
|
||||
"ProviderLookupError",
|
||||
"ProviderTypeMapping",
|
||||
"__version__",
|
||||
]
|
||||
@@ -48,6 +48,7 @@ all = [
|
||||
"agent-framework-azurefunctions",
|
||||
"agent-framework-chatkit",
|
||||
"agent-framework-copilotstudio",
|
||||
"agent-framework-declarative",
|
||||
"agent-framework-devui",
|
||||
"agent-framework-lab",
|
||||
"agent-framework-mem0",
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
@@ -0,0 +1,11 @@
|
||||
# Get Started with Microsoft Agent Framework Declarative
|
||||
|
||||
Please install this package via pip:
|
||||
|
||||
```bash
|
||||
pip install agent-framework-declarative --pre
|
||||
```
|
||||
|
||||
## Declarative features
|
||||
|
||||
The declarative packages provides support for building agents based on a declarative yaml specification.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from importlib import metadata
|
||||
|
||||
from ._loader import AgentFactory, DeclarativeLoaderError, ProviderLookupError, ProviderTypeMapping
|
||||
|
||||
try:
|
||||
__version__ = metadata.version(__name__)
|
||||
except metadata.PackageNotFoundError:
|
||||
__version__ = "0.0.0" # Fallback for development mode
|
||||
|
||||
__all__ = ["AgentFactory", "DeclarativeLoaderError", "ProviderLookupError", "ProviderTypeMapping", "__version__"]
|
||||
@@ -0,0 +1,422 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from collections.abc import Callable, Mapping
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal, TypedDict
|
||||
|
||||
import yaml
|
||||
from agent_framework import (
|
||||
AIFunction,
|
||||
ChatAgent,
|
||||
ChatClientProtocol,
|
||||
HostedCodeInterpreterTool,
|
||||
HostedFileContent,
|
||||
HostedFileSearchTool,
|
||||
HostedMCPSpecificApproval,
|
||||
HostedMCPTool,
|
||||
HostedVectorStoreContent,
|
||||
HostedWebSearchTool,
|
||||
ToolProtocol,
|
||||
)
|
||||
from agent_framework._tools import _create_model_from_json_schema # type: ignore
|
||||
from agent_framework.exceptions import AgentFrameworkException
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from ._models import (
|
||||
AnonymousConnection,
|
||||
ApiKeyConnection,
|
||||
CodeInterpreterTool,
|
||||
FileSearchTool,
|
||||
FunctionTool,
|
||||
McpServerToolSpecifyApprovalMode,
|
||||
McpTool,
|
||||
Model,
|
||||
ModelOptions,
|
||||
PromptAgent,
|
||||
ReferenceConnection,
|
||||
RemoteConnection,
|
||||
Tool,
|
||||
WebSearchTool,
|
||||
agent_schema_dispatch,
|
||||
)
|
||||
|
||||
|
||||
class ProviderTypeMapping(TypedDict, total=True):
|
||||
package: str
|
||||
name: str
|
||||
model_id_field: str
|
||||
|
||||
|
||||
PROVIDER_TYPE_OBJECT_MAPPING: dict[str, ProviderTypeMapping] = {
|
||||
"AzureOpenAI.Chat": {
|
||||
"package": "agent_framework.azure",
|
||||
"name": "AzureOpenAIChatClient",
|
||||
"model_id_field": "deployment_name",
|
||||
},
|
||||
"AzureOpenAI.Assistants": {
|
||||
"package": "agent_framework.azure",
|
||||
"name": "AzureOpenAIAssistantsClient",
|
||||
"model_id_field": "deployment_name",
|
||||
},
|
||||
"AzureOpenAI.Responses": {
|
||||
"package": "agent_framework.azure",
|
||||
"name": "AzureOpenAIResponsesClient",
|
||||
"model_id_field": "deployment_name",
|
||||
},
|
||||
"OpenAI.Chat": {
|
||||
"package": "agent_framework.openai",
|
||||
"name": "OpenAIChatClient",
|
||||
"model_id_field": "model_id",
|
||||
},
|
||||
"OpenAI.Assistants": {
|
||||
"package": "agent_framework.openai",
|
||||
"name": "OpenAIAssistantsClient",
|
||||
"model_id_field": "model_id",
|
||||
},
|
||||
"OpenAI.Responses": {
|
||||
"package": "agent_framework.openai",
|
||||
"name": "OpenAIResponsesClient",
|
||||
"model_id_field": "model_id",
|
||||
},
|
||||
"AzureAIAgentClient": {
|
||||
"package": "agent_framework.azure",
|
||||
"name": "AzureAIAgentClient",
|
||||
"model_id_field": "model_deployment_name",
|
||||
},
|
||||
"AzureAIClient": {
|
||||
"package": "agent_framework.azure",
|
||||
"name": "AzureAIClient",
|
||||
"model_id_field": "model_deployment_name",
|
||||
},
|
||||
"Anthropic.Chat": {
|
||||
"package": "agent_framework.anthropic",
|
||||
"name": "AnthropicChatClient",
|
||||
"model_id_field": "model_id",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class DeclarativeLoaderError(AgentFrameworkException):
|
||||
"""Exception raised for errors in the declarative loader."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ProviderLookupError(DeclarativeLoaderError):
|
||||
"""Exception raised for errors in provider type lookup."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AgentFactory:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
chat_client: ChatClientProtocol | None = None,
|
||||
bindings: Mapping[str, Any] | None = None,
|
||||
connections: Mapping[str, Any] | None = None,
|
||||
client_kwargs: Mapping[str, Any] | None = None,
|
||||
additional_mappings: Mapping[str, ProviderTypeMapping] | None = None,
|
||||
default_provider: str = "AzureAIClient",
|
||||
env_file: str | None = None,
|
||||
) -> None:
|
||||
"""Create the agent factory, with bindings.
|
||||
|
||||
Args:
|
||||
chat_client: An optional ChatClientProtocol instance to use as a dependency,
|
||||
this will be passed to the ChatAgent that get's created.
|
||||
If you need to create multiple agents with different chat clients,
|
||||
do not pass this and instead provide the chat client in the YAML definition.
|
||||
bindings: An optional dictionary of bindings to use when creating agents.
|
||||
connections: An optional dictionary of connections to resolve ReferenceConnections.
|
||||
client_kwargs: An optional dictionary of keyword arguments to pass to chat client constructor.
|
||||
additional_mappings: An optional dictionary to extend the provider type to object mapping.
|
||||
Should have the structure:
|
||||
|
||||
..code-block:: python
|
||||
|
||||
additional_mappings = {
|
||||
"Provider.ApiType": {
|
||||
"package": "package.name",
|
||||
"name": "ClassName",
|
||||
"model_id_field": "field_name_in_constructor",
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
Here, "Provider.ApiType" is the lookup key used when both provider and apiType are specified in the
|
||||
model, "Provider" is also allowed.
|
||||
Package refers to which model needs to be imported, Name is the class name of the ChatClientProtocol
|
||||
implementation, and model_id_field is the name of the field in the constructor
|
||||
that accepts the model.id value.
|
||||
default_provider: The default provider used when model.provider is not specified,
|
||||
default is "AzureAIClient".
|
||||
env_file: An optional path to a .env file to load environment variables from.
|
||||
"""
|
||||
self.chat_client = chat_client
|
||||
self.bindings = bindings
|
||||
self.connections = connections
|
||||
self.client_kwargs = client_kwargs or {}
|
||||
self.additional_mappings = additional_mappings or {}
|
||||
self.default_provider: str = default_provider
|
||||
load_dotenv(dotenv_path=env_file)
|
||||
|
||||
def create_agent_from_yaml_path(self, yaml_path: str | Path) -> ChatAgent:
|
||||
"""Create a ChatAgent from a YAML file path.
|
||||
|
||||
This method does the following things:
|
||||
1. Loads the YAML file into a AgentSchema object using open and agent_schema_dispatch.
|
||||
2. Validates that the loaded object is a PromptAgent.
|
||||
3. Creates the appropriate ChatClient based on the model provider and apiType.
|
||||
4. Parses the tools, options, and response format from the PromptAgent.
|
||||
5. Creates and returns a ChatAgent instance with the configured properties.
|
||||
|
||||
Args:
|
||||
yaml_path: Path to the YAML file representation of a AgentSchema object
|
||||
|
||||
Returns:
|
||||
The ``ChatAgent`` instance created from the YAML file.
|
||||
|
||||
Raises:
|
||||
DeclarativeLoaderError: If the YAML does not represent a PromptAgent.
|
||||
ProviderLookupError: If the provider type is unknown or unsupported.
|
||||
ValueError: If a ReferenceConnection cannot be resolved.
|
||||
ModuleNotFoundError: If the required module for the provider type cannot be imported.
|
||||
AttributeError: If the required class for the provider type cannot be found in the module.
|
||||
"""
|
||||
if not isinstance(yaml_path, Path):
|
||||
yaml_path = Path(yaml_path)
|
||||
if not yaml_path.exists():
|
||||
raise DeclarativeLoaderError(f"YAML file not found at path: {yaml_path}")
|
||||
with open(yaml_path) as f:
|
||||
yaml_str = f.read()
|
||||
return self.create_agent_from_yaml(yaml_str)
|
||||
|
||||
def create_agent_from_yaml(self, yaml_str: str) -> ChatAgent:
|
||||
"""Create a ChatAgent from a YAML string.
|
||||
|
||||
This method does the following things:
|
||||
1. Loads the YAML string into a AgentSchema object using agent_schema_dispatch.
|
||||
2. Validates that the loaded object is a PromptAgent.
|
||||
3. Creates the appropriate ChatClient based on the model provider and apiType.
|
||||
4. Parses the tools, options, and response format from the PromptAgent.
|
||||
5. Creates and returns a ChatAgent instance with the configured properties.
|
||||
|
||||
Args:
|
||||
yaml_str: YAML string representation of a AgentSchema object
|
||||
|
||||
Returns:
|
||||
The ``ChatAgent`` instance created from the YAML string.
|
||||
|
||||
Raises:
|
||||
DeclarativeLoaderError: If the YAML does not represent a PromptAgent.
|
||||
ProviderLookupError: If the provider type is unknown or unsupported.
|
||||
ValueError: If a ReferenceConnection cannot be resolved.
|
||||
ModuleNotFoundError: If the required module for the provider type cannot be imported.
|
||||
AttributeError: If the required class for the provider type cannot be found in the module.
|
||||
"""
|
||||
prompt_agent = agent_schema_dispatch(yaml.safe_load(yaml_str))
|
||||
if not isinstance(prompt_agent, PromptAgent):
|
||||
raise DeclarativeLoaderError("Only yaml definitions for a PromptAgent are supported for agent creation.")
|
||||
|
||||
# Step 1: Create the ChatClient
|
||||
client = self._get_client(prompt_agent)
|
||||
# Step 2: Get the chat options
|
||||
chat_options = self._parse_chat_options(prompt_agent.model)
|
||||
if tools := self._parse_tools(prompt_agent.tools):
|
||||
chat_options["tools"] = tools
|
||||
if output_schema := prompt_agent.outputSchema:
|
||||
chat_options["response_format"] = _create_model_from_json_schema("agent", output_schema.to_json_schema())
|
||||
# Step 3: Create the agent instance
|
||||
return ChatAgent(
|
||||
chat_client=client,
|
||||
name=prompt_agent.name,
|
||||
description=prompt_agent.description,
|
||||
instructions=prompt_agent.instructions,
|
||||
**chat_options,
|
||||
)
|
||||
|
||||
def _get_client(self, prompt_agent: PromptAgent) -> ChatClientProtocol:
|
||||
"""Create the ChatClientProtocol instance based on the PromptAgent model."""
|
||||
if not prompt_agent.model:
|
||||
# if no model is defined, use the supplied chat_client
|
||||
if self.chat_client:
|
||||
return self.chat_client
|
||||
raise DeclarativeLoaderError(
|
||||
"ChatClient must be provided to create agent from PromptAgent, "
|
||||
"alternatively define a model in the PromptAgent."
|
||||
)
|
||||
|
||||
setup_dict: dict[str, Any] = {}
|
||||
setup_dict.update(self.client_kwargs)
|
||||
|
||||
# parse connections
|
||||
if prompt_agent.model.connection:
|
||||
match prompt_agent.model.connection:
|
||||
case ApiKeyConnection():
|
||||
setup_dict["api_key"] = prompt_agent.model.connection.apiKey
|
||||
if prompt_agent.model.connection.endpoint:
|
||||
setup_dict["endpoint"] = prompt_agent.model.connection.endpoint
|
||||
case RemoteConnection() | AnonymousConnection():
|
||||
setup_dict["endpoint"] = prompt_agent.model.connection.endpoint
|
||||
case ReferenceConnection():
|
||||
if not self.connections:
|
||||
raise ValueError("Connections must be provided to resolve ReferenceConnection")
|
||||
# find the referenced connection
|
||||
if prompt_agent.model.connection.name and (
|
||||
value := self.connections.get(prompt_agent.model.connection.name)
|
||||
):
|
||||
setup_dict[prompt_agent.model.connection.name] = value
|
||||
else:
|
||||
raise ValueError(
|
||||
f"ReferenceConnection with name {prompt_agent.model.connection.name} not found in provided "
|
||||
"connections."
|
||||
)
|
||||
|
||||
# Any client we create, needs a model.id
|
||||
if not prompt_agent.model.id:
|
||||
# if prompt_agent.model is defined, but no id, use the supplied chat_client
|
||||
if self.chat_client:
|
||||
return self.chat_client
|
||||
# or raise, since we cannot create a client without model id
|
||||
raise DeclarativeLoaderError(
|
||||
"ChatClient must be provided to create agent from PromptAgent, or define model.id in the PromptAgent."
|
||||
)
|
||||
# if provider is defined, use that, if possible with apiType, fallback to default_provider
|
||||
mapping = self._retrieve_provider_configuration(prompt_agent.model)
|
||||
module_name = mapping["package"]
|
||||
class_name = mapping["name"]
|
||||
module = __import__(module_name, fromlist=[class_name])
|
||||
agent_class = getattr(module, class_name)
|
||||
setup_dict[mapping["model_id_field"]] = prompt_agent.model.id
|
||||
return agent_class(**setup_dict) # type: ignore[no-any-return]
|
||||
|
||||
def _parse_chat_options(self, model: Model | None) -> dict[str, Any]:
|
||||
"""Parse ModelOptions into chat options dictionary."""
|
||||
chat_options: dict[str, Any] = {}
|
||||
if not model or not model.options or not isinstance(model.options, ModelOptions):
|
||||
return chat_options
|
||||
options = model.options
|
||||
if options.frequencyPenalty is not None:
|
||||
chat_options["frequency_penalty"] = options.frequencyPenalty
|
||||
if options.presencePenalty is not None:
|
||||
chat_options["presence_penalty"] = options.presencePenalty
|
||||
if options.maxOutputTokens is not None:
|
||||
chat_options["max_tokens"] = options.maxOutputTokens
|
||||
if options.temperature is not None:
|
||||
chat_options["temperature"] = options.temperature
|
||||
if options.topP is not None:
|
||||
chat_options["top_p"] = options.topP
|
||||
if options.seed is not None:
|
||||
chat_options["seed"] = options.seed
|
||||
if options.stopSequences:
|
||||
chat_options["stop"] = options.stopSequences
|
||||
if options.allowMultipleToolCalls is not None:
|
||||
chat_options["allow_multiple_tool_calls"] = options.allowMultipleToolCalls
|
||||
if (chat_tool_mode := options.additionalProperties.pop("chatToolMode", None)) is not None:
|
||||
chat_options["tool_choice"] = chat_tool_mode
|
||||
if options.additionalProperties:
|
||||
chat_options["additional_chat_options"] = options.additionalProperties
|
||||
return chat_options
|
||||
|
||||
def _parse_tools(self, tools: list[Tool] | None) -> list[ToolProtocol] | None:
|
||||
"""Parse tool resources into ToolProtocol instances."""
|
||||
if not tools:
|
||||
return None
|
||||
return [self._parse_tool(tool_resource) for tool_resource in tools]
|
||||
|
||||
def _parse_tool(self, tool_resource: Tool) -> ToolProtocol:
|
||||
"""Parse a single tool resource into a ToolProtocol instance."""
|
||||
match tool_resource:
|
||||
case FunctionTool():
|
||||
func: Callable[..., Any] | None = None
|
||||
if self.bindings and tool_resource.bindings:
|
||||
for binding in tool_resource.bindings:
|
||||
if binding.name and (func := self.bindings.get(binding.name)):
|
||||
break
|
||||
return AIFunction( # type: ignore
|
||||
name=tool_resource.name, # type: ignore
|
||||
description=tool_resource.description, # type: ignore
|
||||
input_model=tool_resource.parameters.to_json_schema() if tool_resource.parameters else None,
|
||||
func=func,
|
||||
)
|
||||
case WebSearchTool():
|
||||
return HostedWebSearchTool(
|
||||
description=tool_resource.description, additional_properties=tool_resource.options
|
||||
)
|
||||
case FileSearchTool():
|
||||
add_props: dict[str, Any] = {}
|
||||
if tool_resource.ranker is not None:
|
||||
add_props["ranker"] = tool_resource.ranker
|
||||
if tool_resource.scoreThreshold is not None:
|
||||
add_props["score_threshold"] = tool_resource.scoreThreshold
|
||||
if tool_resource.filters:
|
||||
add_props["filters"] = tool_resource.filters
|
||||
return HostedFileSearchTool(
|
||||
inputs=[HostedVectorStoreContent(id) for id in tool_resource.vectorStoreIds or []],
|
||||
description=tool_resource.description,
|
||||
max_results=tool_resource.maximumResultCount,
|
||||
additional_properties=add_props,
|
||||
)
|
||||
case CodeInterpreterTool():
|
||||
return HostedCodeInterpreterTool(
|
||||
inputs=[HostedFileContent(file_id=file) for file in tool_resource.fileIds or []],
|
||||
description=tool_resource.description,
|
||||
)
|
||||
case McpTool():
|
||||
approval_mode: HostedMCPSpecificApproval | Literal["always_require", "never_require"] | None = None
|
||||
if tool_resource.approvalMode is not None:
|
||||
if tool_resource.approvalMode.kind == "always":
|
||||
approval_mode = "always_require"
|
||||
elif tool_resource.approvalMode.kind == "never":
|
||||
approval_mode = "never_require"
|
||||
elif isinstance(tool_resource.approvalMode, McpServerToolSpecifyApprovalMode):
|
||||
approval_mode = {}
|
||||
if tool_resource.approvalMode.alwaysRequireApprovalTools:
|
||||
approval_mode["always_require_approval"] = (
|
||||
tool_resource.approvalMode.alwaysRequireApprovalTools
|
||||
)
|
||||
if tool_resource.approvalMode.neverRequireApprovalTools:
|
||||
approval_mode["never_require_approval"] = (
|
||||
tool_resource.approvalMode.neverRequireApprovalTools
|
||||
)
|
||||
if not approval_mode:
|
||||
approval_mode = None
|
||||
return HostedMCPTool(
|
||||
name=tool_resource.name, # type: ignore
|
||||
description=tool_resource.description,
|
||||
url=tool_resource.url, # type: ignore
|
||||
allowed_tools=tool_resource.allowedTools,
|
||||
approval_mode=approval_mode,
|
||||
)
|
||||
case _:
|
||||
raise ValueError(f"Unsupported tool kind: {tool_resource.kind}")
|
||||
|
||||
def _retrieve_provider_configuration(self, model: Model) -> ProviderTypeMapping:
|
||||
"""Retrieve the provider configuration based on the model's provider and apiType.
|
||||
|
||||
If only provider is specified, it will be used.
|
||||
If both provider and apiType are specified, both will be used.
|
||||
If neither is specified, the default_provider will be used.
|
||||
|
||||
Args:
|
||||
model: The Model instance containing provider and apiType information.
|
||||
|
||||
Returns:
|
||||
A dictionary containing the package, name, and model_id_field for the provider.
|
||||
|
||||
Raises:
|
||||
ProviderLookupError: If the provider type is not supported or can't be found.
|
||||
"""
|
||||
class_lookup = (
|
||||
f"{model.provider}.{model.apiType}"
|
||||
if model.apiType
|
||||
else f"{model.provider}"
|
||||
if model.provider
|
||||
else self.default_provider
|
||||
)
|
||||
if class_lookup in self.additional_mappings:
|
||||
return self.additional_mappings[class_lookup]
|
||||
if class_lookup not in PROVIDER_TYPE_OBJECT_MAPPING:
|
||||
raise ProviderLookupError(f"Unsupported provider type: {class_lookup}")
|
||||
return PROVIDER_TYPE_OBJECT_MAPPING[class_lookup]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
||||
[project]
|
||||
name = "agent-framework-declarative"
|
||||
description = "Declarative specification support for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "1.0.0b251028"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://aka.ms/agent-framework"
|
||||
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
||||
urls.release_notes = "https://github.com/microsoft/agent-framework/releases?q=tag%3Apython-1&expanded=true"
|
||||
urls.issues = "https://github.com/microsoft/agent-framework/issues"
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"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",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core",
|
||||
"powerfx>=0.0.31; python_version < '3.14'",
|
||||
"pyyaml>=6.0,<7.0",
|
||||
]
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"types-PyYaml"
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
prerelease = "if-necessary-or-explicit"
|
||||
environments = [
|
||||
"sys_platform == 'darwin'",
|
||||
"sys_platform == 'linux'",
|
||||
"sys_platform == 'win32'"
|
||||
]
|
||||
|
||||
|
||||
[tool.uv-dynamic-versioning]
|
||||
fallback-version = "0.0.0"
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = 'tests'
|
||||
addopts = "-ra -q -r fEX"
|
||||
asyncio_mode = "auto"
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
filterwarnings = [
|
||||
"ignore:Support for class-based `config` is deprecated:DeprecationWarning:pydantic.*"
|
||||
]
|
||||
timeout = 120
|
||||
|
||||
[tool.ruff]
|
||||
extend = "../../pyproject.toml"
|
||||
|
||||
[tool.coverage.run]
|
||||
omit = [
|
||||
"**/__init__.py"
|
||||
]
|
||||
|
||||
[tool.pyright]
|
||||
extends = "../../pyproject.toml"
|
||||
exclude = ['tests']
|
||||
|
||||
[tool.mypy]
|
||||
plugins = ['pydantic.mypy']
|
||||
strict = true
|
||||
python_version = "3.10"
|
||||
ignore_missing_imports = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
check_untyped_defs = true
|
||||
warn_return_any = true
|
||||
show_error_codes = true
|
||||
warn_unused_ignores = false
|
||||
disallow_incomplete_defs = true
|
||||
disallow_untyped_decorators = true
|
||||
exclude = [
|
||||
'_models.py$',
|
||||
]
|
||||
|
||||
[tool.bandit]
|
||||
targets = ["agent_framework_declarative"]
|
||||
exclude_dirs = ["tests"]
|
||||
|
||||
[tool.poe]
|
||||
executor.type = "uv"
|
||||
include = "../../shared_tasks.toml"
|
||||
[tool.poe.tasks]
|
||||
mypy = "mypy --config-file $POE_ROOT/pyproject.toml agent_framework_declarative"
|
||||
test = "pytest --cov=agent_framework_declarative --cov-report=term-missing:skip-covered tests"
|
||||
|
||||
[build-system]
|
||||
requires = ["flit-core >= 3.11,<4.0"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
@@ -0,0 +1,456 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from agent_framework_declarative._models import (
|
||||
AgentDefinition,
|
||||
AgentManifest,
|
||||
AnonymousConnection,
|
||||
ApiKeyConnection,
|
||||
ArrayProperty,
|
||||
CodeInterpreterTool,
|
||||
Connection,
|
||||
CustomTool,
|
||||
FileSearchTool,
|
||||
FunctionTool,
|
||||
McpServerApprovalMode,
|
||||
McpServerToolAlwaysRequireApprovalMode,
|
||||
McpServerToolNeverRequireApprovalMode,
|
||||
McpServerToolSpecifyApprovalMode,
|
||||
McpTool,
|
||||
ModelResource,
|
||||
ObjectProperty,
|
||||
OpenApiTool,
|
||||
PromptAgent,
|
||||
Property,
|
||||
PropertySchema,
|
||||
ReferenceConnection,
|
||||
RemoteConnection,
|
||||
Resource,
|
||||
ToolResource,
|
||||
WebSearchTool,
|
||||
agent_schema_dispatch,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.skipif(sys.version_info >= (3, 14), reason="Skipping on Python 3.14+")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_content,expected_type,expected_attributes",
|
||||
[
|
||||
# Agent Manifest (no kind field)
|
||||
(
|
||||
"""
|
||||
name: my-manifest
|
||||
description: A test manifest
|
||||
""",
|
||||
AgentManifest,
|
||||
{"name": "my-manifest", "description": "A test manifest"},
|
||||
),
|
||||
# PromptAgent
|
||||
(
|
||||
"""
|
||||
kind: Prompt
|
||||
name: assistant
|
||||
description: A helpful assistant
|
||||
model:
|
||||
id: gpt-4
|
||||
""",
|
||||
PromptAgent,
|
||||
{"name": "assistant", "description": "A helpful assistant"},
|
||||
),
|
||||
# AgentDefinition
|
||||
(
|
||||
"""
|
||||
kind: Agent
|
||||
name: base-agent
|
||||
description: A base agent
|
||||
""",
|
||||
AgentDefinition,
|
||||
{"name": "base-agent", "description": "A base agent"},
|
||||
),
|
||||
# ModelResource
|
||||
(
|
||||
"""
|
||||
kind: Model
|
||||
name: my-model
|
||||
id: gpt-4
|
||||
""",
|
||||
ModelResource,
|
||||
{"name": "my-model", "id": "gpt-4"},
|
||||
),
|
||||
# ToolResource
|
||||
(
|
||||
"""
|
||||
kind: Tool
|
||||
name: my-tool
|
||||
id: search-tool
|
||||
""",
|
||||
ToolResource,
|
||||
{"name": "my-tool", "id": "search-tool"},
|
||||
),
|
||||
# Resource (base)
|
||||
(
|
||||
"""
|
||||
kind: Resource
|
||||
name: generic-resource
|
||||
""",
|
||||
Resource,
|
||||
{"name": "generic-resource"},
|
||||
),
|
||||
# FunctionTool
|
||||
(
|
||||
"""
|
||||
kind: function
|
||||
name: get_weather
|
||||
description: Get the weather
|
||||
""",
|
||||
FunctionTool,
|
||||
{"name": "get_weather", "description": "Get the weather"},
|
||||
),
|
||||
# CustomTool
|
||||
(
|
||||
"""
|
||||
kind: custom
|
||||
name: custom_tool
|
||||
description: A custom tool
|
||||
""",
|
||||
CustomTool,
|
||||
{"name": "custom_tool", "description": "A custom tool"},
|
||||
),
|
||||
# WebSearchTool
|
||||
(
|
||||
"""
|
||||
kind: web_search
|
||||
name: search
|
||||
description: Search the web
|
||||
""",
|
||||
WebSearchTool,
|
||||
{"name": "search", "description": "Search the web"},
|
||||
),
|
||||
# FileSearchTool
|
||||
(
|
||||
"""
|
||||
kind: file_search
|
||||
name: file_search
|
||||
description: Search files
|
||||
""",
|
||||
FileSearchTool,
|
||||
{"name": "file_search", "description": "Search files"},
|
||||
),
|
||||
# McpTool
|
||||
(
|
||||
"""
|
||||
kind: mcp
|
||||
name: mcp_tool
|
||||
description: An MCP tool
|
||||
serverName: my-server
|
||||
""",
|
||||
McpTool,
|
||||
{"name": "mcp_tool", "serverName": "my-server"},
|
||||
),
|
||||
# OpenApiTool
|
||||
(
|
||||
"""
|
||||
kind: openapi
|
||||
name: api_tool
|
||||
description: An OpenAPI tool
|
||||
specification: https://api.example.com/openapi.json
|
||||
""",
|
||||
OpenApiTool,
|
||||
{"name": "api_tool", "specification": "https://api.example.com/openapi.json"},
|
||||
),
|
||||
# CodeInterpreterTool
|
||||
(
|
||||
"""
|
||||
kind: code_interpreter
|
||||
name: code_tool
|
||||
description: A code interpreter tool
|
||||
""",
|
||||
CodeInterpreterTool,
|
||||
{"name": "code_tool", "description": "A code interpreter tool"},
|
||||
),
|
||||
# ReferenceConnection
|
||||
(
|
||||
"""
|
||||
kind: reference
|
||||
name: my-connection
|
||||
target: target-connection
|
||||
""",
|
||||
ReferenceConnection,
|
||||
{"name": "my-connection", "target": "target-connection"},
|
||||
),
|
||||
# RemoteConnection
|
||||
(
|
||||
"""
|
||||
kind: remote
|
||||
endpoint: https://api.example.com
|
||||
""",
|
||||
RemoteConnection,
|
||||
{"endpoint": "https://api.example.com"},
|
||||
),
|
||||
# ApiKeyConnection
|
||||
(
|
||||
"""
|
||||
kind: key
|
||||
apiKey: secret-key
|
||||
endpoint: https://api.example.com
|
||||
""",
|
||||
ApiKeyConnection,
|
||||
{"apiKey": "secret-key", "endpoint": "https://api.example.com"},
|
||||
),
|
||||
# AnonymousConnection
|
||||
(
|
||||
"""
|
||||
kind: anonymous
|
||||
endpoint: https://api.example.com
|
||||
""",
|
||||
AnonymousConnection,
|
||||
{"endpoint": "https://api.example.com"},
|
||||
),
|
||||
# Connection (base)
|
||||
(
|
||||
"""
|
||||
kind: connection
|
||||
authenticationMode: oauth
|
||||
""",
|
||||
Connection,
|
||||
{"authenticationMode": "oauth"},
|
||||
),
|
||||
# ArrayProperty
|
||||
(
|
||||
"""
|
||||
kind: array
|
||||
name: items
|
||||
description: An array of items
|
||||
""",
|
||||
ArrayProperty,
|
||||
{"name": "items", "description": "An array of items"},
|
||||
),
|
||||
# ObjectProperty
|
||||
(
|
||||
"""
|
||||
kind: object
|
||||
name: config
|
||||
description: Configuration object
|
||||
""",
|
||||
ObjectProperty,
|
||||
{"name": "config", "description": "Configuration object"},
|
||||
),
|
||||
# Property (base)
|
||||
(
|
||||
"""
|
||||
kind: property
|
||||
name: field
|
||||
description: A property field
|
||||
""",
|
||||
Property,
|
||||
{"name": "field", "description": "A property field"},
|
||||
),
|
||||
# McpServerToolAlwaysRequireApprovalMode
|
||||
(
|
||||
"""
|
||||
kind: always
|
||||
""",
|
||||
McpServerToolAlwaysRequireApprovalMode,
|
||||
{},
|
||||
),
|
||||
# McpServerToolNeverRequireApprovalMode
|
||||
(
|
||||
"""
|
||||
kind: never
|
||||
""",
|
||||
McpServerToolNeverRequireApprovalMode,
|
||||
{},
|
||||
),
|
||||
# McpServerToolSpecifyApprovalMode
|
||||
(
|
||||
"""
|
||||
kind: specify
|
||||
alwaysRequireApprovalTools: []
|
||||
neverRequireApprovalTools: []
|
||||
""",
|
||||
McpServerToolSpecifyApprovalMode,
|
||||
{},
|
||||
),
|
||||
# McpServerApprovalMode (base)
|
||||
(
|
||||
"""
|
||||
kind: approval_mode
|
||||
""",
|
||||
McpServerApprovalMode,
|
||||
{},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_agent_schema_dispatch_all_types(yaml_content: str, expected_type: type, expected_attributes: dict[str, Any]):
|
||||
"""Test that agent_schema_dispatch correctly loads all MAML object types."""
|
||||
result = agent_schema_dispatch(yaml.safe_load(yaml_content))
|
||||
|
||||
# Check the type is correct
|
||||
assert isinstance(result, expected_type), f"Expected {expected_type.__name__}, got {type(result).__name__}"
|
||||
|
||||
# Check expected attributes
|
||||
for attr_name, attr_value in expected_attributes.items():
|
||||
assert hasattr(result, attr_name), f"Result missing attribute '{attr_name}'"
|
||||
assert getattr(result, attr_name) == attr_value, (
|
||||
f"Attribute '{attr_name}' has value {getattr(result, attr_name)}, expected {attr_value}"
|
||||
)
|
||||
|
||||
|
||||
def test_agent_schema_dispatch_unknown_kind():
|
||||
"""Test that agent_schema_dispatch returns None for unknown kind."""
|
||||
yaml_content = """
|
||||
kind: unknown_type
|
||||
name: test
|
||||
"""
|
||||
result = agent_schema_dispatch(yaml.safe_load(yaml_content))
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_agent_schema_dispatch_complex_agent_manifest():
|
||||
"""Test loading a complex agent manifest with nested objects."""
|
||||
yaml_content = """
|
||||
name: complex-manifest
|
||||
description: A complete manifest
|
||||
template:
|
||||
kind: Prompt
|
||||
name: assistant
|
||||
description: A helpful assistant
|
||||
model:
|
||||
id: gpt-4
|
||||
provider: openai
|
||||
tools:
|
||||
- kind: web_search
|
||||
name: search
|
||||
description: Search the web
|
||||
- kind: function
|
||||
name: calculator
|
||||
description: Calculate math
|
||||
resources:
|
||||
- kind: model
|
||||
name: model1
|
||||
id: gpt-4
|
||||
- kind: tool
|
||||
name: tool1
|
||||
id: search
|
||||
"""
|
||||
result = agent_schema_dispatch(yaml.safe_load(yaml_content))
|
||||
|
||||
assert isinstance(result, AgentManifest)
|
||||
assert result.name == "complex-manifest"
|
||||
assert result.description == "A complete manifest"
|
||||
assert isinstance(result.template, PromptAgent)
|
||||
assert result.template.name == "assistant"
|
||||
assert len(result.resources) == 2
|
||||
assert isinstance(result.resources[0], ModelResource)
|
||||
assert isinstance(result.resources[1], ToolResource)
|
||||
|
||||
|
||||
def test_agent_schema_dispatch_prompt_agent_with_tools():
|
||||
"""Test loading a prompt agent with multiple tools."""
|
||||
yaml_content = """
|
||||
kind: Prompt
|
||||
name: multi-tool-agent
|
||||
description: Agent with multiple tools
|
||||
model:
|
||||
id: gpt-4
|
||||
tools:
|
||||
- kind: web_search
|
||||
name: search
|
||||
description: Search the web
|
||||
- kind: function
|
||||
name: get_weather
|
||||
description: Get weather information
|
||||
- kind: code_interpreter
|
||||
name: code
|
||||
description: Execute code
|
||||
"""
|
||||
result = agent_schema_dispatch(yaml.safe_load(yaml_content))
|
||||
|
||||
assert isinstance(result, PromptAgent)
|
||||
assert result.name == "multi-tool-agent"
|
||||
assert len(result.tools) == 3
|
||||
# Tools are polymorphically created based on their kind
|
||||
assert result.tools[0].kind == "web_search"
|
||||
assert result.tools[1].kind == "function"
|
||||
assert result.tools[2].kind == "code_interpreter"
|
||||
|
||||
|
||||
def test_agent_schema_dispatch_model_resource():
|
||||
"""Test loading a model resource."""
|
||||
yaml_content = """
|
||||
kind: Model
|
||||
name: my-model
|
||||
id: gpt-4
|
||||
"""
|
||||
result = agent_schema_dispatch(yaml.safe_load(yaml_content))
|
||||
|
||||
assert isinstance(result, ModelResource)
|
||||
assert result.id == "gpt-4"
|
||||
|
||||
|
||||
def test_agent_schema_dispatch_property_schema_with_nested_properties():
|
||||
"""Test loading a property schema with nested properties."""
|
||||
yaml_content = """
|
||||
kind: property_schema
|
||||
strict: true
|
||||
properties:
|
||||
- kind: property
|
||||
name: name
|
||||
description: User name
|
||||
- kind: object
|
||||
name: address
|
||||
description: User address
|
||||
properties:
|
||||
- kind: property
|
||||
name: street
|
||||
description: Street address
|
||||
- kind: property
|
||||
name: city
|
||||
description: City name
|
||||
- kind: array
|
||||
name: tags
|
||||
description: User tags
|
||||
"""
|
||||
result = agent_schema_dispatch(yaml.safe_load(yaml_content))
|
||||
|
||||
assert isinstance(result, PropertySchema)
|
||||
assert result.strict is True
|
||||
assert len(result.properties) == 3
|
||||
# Properties are polymorphically created based on their kind
|
||||
assert result.properties[0].kind == "property"
|
||||
assert result.properties[1].kind == "object"
|
||||
assert result.properties[2].kind == "array"
|
||||
|
||||
|
||||
def _get_agent_sample_yaml_files() -> list[tuple[Path, Path]]:
|
||||
"""Helper function to collect all YAML files from agent-samples directory."""
|
||||
current_file = Path(__file__)
|
||||
repo_root = current_file.parent.parent.parent.parent # tests -> declarative -> packages -> python
|
||||
agent_samples_dir = repo_root.parent / "agent-samples"
|
||||
|
||||
if not agent_samples_dir.exists():
|
||||
return []
|
||||
|
||||
yaml_files = list(agent_samples_dir.rglob("*.yaml")) + list(agent_samples_dir.rglob("*.yml"))
|
||||
return [(yaml_file, agent_samples_dir) for yaml_file in yaml_files]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"yaml_file,agent_samples_dir",
|
||||
_get_agent_sample_yaml_files(),
|
||||
ids=lambda x: x[0].name if isinstance(x, tuple) else str(x),
|
||||
)
|
||||
def test_agent_schema_dispatch_agent_samples(yaml_file: Path, agent_samples_dir: Path):
|
||||
"""Test that agent_schema_dispatch successfully loads a YAML file from agent-samples directory."""
|
||||
with open(yaml_file) as f:
|
||||
content = f.read()
|
||||
result = agent_schema_dispatch(yaml.safe_load(content))
|
||||
# Result can be None for unknown kinds, but should not raise exceptions
|
||||
assert result is not None, f"agent_schema_dispatch returned None for {yaml_file.relative_to(agent_samples_dir)}"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,16 +81,17 @@ agent-framework = { workspace = true }
|
||||
agent-framework-core = { workspace = true }
|
||||
agent-framework-a2a = { workspace = true }
|
||||
agent-framework-ag-ui = { workspace = true }
|
||||
agent-framework-anthropic = { workspace = true }
|
||||
agent-framework-azure-ai = { workspace = true }
|
||||
agent-framework-azurefunctions = { workspace = true }
|
||||
agent-framework-chatkit = { workspace = true }
|
||||
agent-framework-copilotstudio = { workspace = true }
|
||||
agent-framework-declarative = { workspace = true }
|
||||
agent-framework-devui = { workspace = true }
|
||||
agent-framework-lab = { workspace = true }
|
||||
agent-framework-mem0 = { workspace = true }
|
||||
agent-framework-redis = { workspace = true }
|
||||
agent-framework-devui = { workspace = true }
|
||||
agent-framework-purview = { workspace = true }
|
||||
agent-framework-anthropic = { workspace = true }
|
||||
agent-framework-redis = { workspace = true }
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
# Declarative Agent Samples
|
||||
|
||||
This folder contains sample code demonstrating how to use the **Microsoft Agent Framework Declarative** package to create agents from YAML specifications. The declarative approach allows you to define your agents in a structured, configuration-driven way, separating agent behavior from implementation details.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the declarative package via pip:
|
||||
|
||||
```bash
|
||||
pip install agent-framework-declarative --pre
|
||||
```
|
||||
|
||||
## What is Declarative Agent Framework?
|
||||
|
||||
The declarative package provides support for building agents based on YAML specifications. This approach offers several benefits:
|
||||
|
||||
- **Cross-Platform Compatibility**: Write one YAML definition and create agents in both Python and .NET - the same agent configuration works across both platforms
|
||||
- **Separation of Concerns**: Define agent behavior in YAML files separate from your implementation code
|
||||
- **Reusability**: Share and version agent configurations independently across projects and languages
|
||||
- **Flexibility**: Easily swap between different LLM providers and configurations
|
||||
- **Maintainability**: Update agent instructions and settings without modifying code
|
||||
|
||||
## Samples in This Folder
|
||||
|
||||
### 1. **Get Weather Agent** ([`get_weather_agent.py`](./get_weather_agent.py))
|
||||
|
||||
Demonstrates how to create an agent with custom function tools using the declarative approach.
|
||||
|
||||
- Uses Azure OpenAI Responses client
|
||||
- Shows how to bind Python functions to the agent using the `bindings` parameter
|
||||
- Loads agent configuration from `agent-samples/chatclient/GetWeather.yaml`
|
||||
- Implements a simple weather lookup function tool
|
||||
|
||||
**Key concepts**: Function binding, Azure OpenAI integration, tool usage
|
||||
|
||||
### 2. **Microsoft Learn Agent** ([`microsoft_learn_agent.py`](./microsoft_learn_agent.py))
|
||||
|
||||
Shows how to create an agent that can search and retrieve information from Microsoft Learn documentation using the Model Context Protocol (MCP).
|
||||
|
||||
- Uses Azure AI Foundry client with MCP server integration
|
||||
- Demonstrates async context managers for proper resource cleanup
|
||||
- Loads agent configuration from `agent-samples/foundry/MicrosoftLearnAgent.yaml`
|
||||
- Uses Azure CLI credentials for authentication
|
||||
- Leverages MCP to access Microsoft documentation tools
|
||||
|
||||
**Requirements**: `pip install agent-framework-azure-ai --pre`
|
||||
|
||||
**Key concepts**: Azure AI Foundry integration, MCP server usage, async patterns, resource management
|
||||
|
||||
### 3. **Azure OpenAI Responses Agent** ([`azure_openai_responses_agent.py`](./azure_openai_responses_agent.py))
|
||||
|
||||
Illustrates a basic agent using Azure OpenAI with structured responses.
|
||||
|
||||
- Uses Azure OpenAI Responses client
|
||||
- Shows how to pass credentials via `client_kwargs`
|
||||
- Loads agent configuration from `agent-samples/azure/AzureOpenAIResponses.yaml`
|
||||
- Demonstrates accessing structured response data
|
||||
|
||||
**Key concepts**: Azure OpenAI integration, credential management, structured outputs
|
||||
|
||||
### 4. **OpenAI Responses Agent** ([`openai_responses_agent.py`](./openai_responses_agent.py))
|
||||
|
||||
Demonstrates the simplest possible agent using OpenAI directly.
|
||||
|
||||
- Uses OpenAI API (requires `OPENAI_API_KEY` environment variable)
|
||||
- Shows minimal configuration needed for basic agent creation
|
||||
- Loads agent configuration from `agent-samples/openai/OpenAIResponses.yaml`
|
||||
|
||||
**Key concepts**: OpenAI integration, minimal setup, environment-based configuration
|
||||
|
||||
## Agent Samples Repository
|
||||
|
||||
All the YAML configuration files referenced in these samples are located in the [`agent-samples`](../../../../agent-samples/) folder at the repository root. This folder contains declarative agent specifications organized by provider:
|
||||
|
||||
- **`agent-samples/azure/`** - Azure OpenAI agent configurations
|
||||
- **`agent-samples/chatclient/`** - Chat client agent configurations with tools
|
||||
- **`agent-samples/foundry/`** - Azure AI Foundry agent configurations
|
||||
- **`agent-samples/openai/`** - OpenAI agent configurations
|
||||
|
||||
**Important**: These YAML files are **platform-agnostic** and work with both Python and .NET implementations of the Agent Framework. You can use the exact same YAML definition to create agents in either language, making it easy to share agent configurations across different technology stacks.
|
||||
|
||||
These YAML files define:
|
||||
- Agent instructions and system prompts
|
||||
- Model selection and parameters
|
||||
- Tool and function configurations
|
||||
- Provider-specific settings
|
||||
- MCP server integrations (where applicable)
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Creating an Agent from YAML String
|
||||
|
||||
```python
|
||||
from agent_framework.declarative import AgentFactory
|
||||
|
||||
with open("agent.yaml", "r") as f:
|
||||
yaml_str = f.read()
|
||||
|
||||
agent = AgentFactory().create_agent_from_yaml(yaml_str)
|
||||
# response = await agent.run("Your query here")
|
||||
```
|
||||
|
||||
### Creating an Agent from YAML Path
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from agent_framework.declarative import AgentFactory
|
||||
|
||||
yaml_path = Path("agent.yaml")
|
||||
agent = AgentFactory().create_agent_from_yaml_path(yaml_path)
|
||||
# response = await agent.run("Your query here")
|
||||
```
|
||||
|
||||
### Binding Custom Functions
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from agent_framework.declarative import AgentFactory
|
||||
|
||||
def my_function(param: str) -> str:
|
||||
return f"Result: {param}"
|
||||
|
||||
agent_factory = AgentFactory(bindings={"my_function": my_function})
|
||||
agent = agent_factory.create_agent_from_yaml_path(Path("agent_with_tool.yaml"))
|
||||
```
|
||||
|
||||
### Using Credentials
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from agent_framework.declarative import AgentFactory
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
agent = AgentFactory(
|
||||
client_kwargs={"credential": AzureCliCredential()}
|
||||
).create_agent_from_yaml_path(Path("azure_agent.yaml"))
|
||||
```
|
||||
|
||||
### Adding Custom Provider Mappings
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from agent_framework.declarative import AgentFactory
|
||||
# from my_custom_module import MyCustomChatClient
|
||||
|
||||
# Register a custom provider mapping
|
||||
agent_factory = AgentFactory(
|
||||
additional_mappings={
|
||||
"MyProvider": {
|
||||
"package": "my_custom_module",
|
||||
"name": "MyCustomChatClient",
|
||||
"model_id_field": "model_id",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Now you can reference "MyProvider" in your YAML
|
||||
# Example YAML snippet:
|
||||
# model:
|
||||
# provider: MyProvider
|
||||
# id: my-model-name
|
||||
|
||||
agent = agent_factory.create_agent_from_yaml_path(Path("custom_provider.yaml"))
|
||||
```
|
||||
|
||||
This allows you to extend the declarative framework with custom chat client implementations. The mapping requires:
|
||||
- **package**: The Python package/module to import from
|
||||
- **name**: The class name of your ChatClientProtocol implementation
|
||||
- **model_id_field**: The constructor parameter name that accepts the value of the `model.id` field from the YAML
|
||||
|
||||
You can reference your custom provider using either `Provider.ApiType` format or just `Provider` in your YAML configuration, as long as it matches the registered mapping.
|
||||
|
||||
### Using PowerFx Formulas in YAML
|
||||
|
||||
The declarative framework supports PowerFx formulas in YAML values, enabling dynamic configuration based on environment variables and conditional logic. Prefix any value with `=` to evaluate it as a PowerFx expression.
|
||||
|
||||
#### Environment Variable Lookup
|
||||
|
||||
Access environment variables using the `Env.<variable_name>` syntax:
|
||||
|
||||
```yaml
|
||||
model:
|
||||
connection:
|
||||
kind: key
|
||||
apiKey: =Env.OPENAI_API_KEY
|
||||
endpoint: =Env.BASE_URL & "/v1" # String concatenation with &
|
||||
|
||||
options:
|
||||
temperature: 0.7
|
||||
maxOutputTokens: =Env.MAX_TOKENS # Will be converted to appropriate type
|
||||
```
|
||||
|
||||
#### Conditional Logic
|
||||
|
||||
Use PowerFx operators for conditional configuration. This is particularly useful for adjusting parameters based on which model is being used:
|
||||
|
||||
```yaml
|
||||
model:
|
||||
id: =Env.MODEL_NAME
|
||||
options:
|
||||
# Set max tokens based on model - using conditional logic
|
||||
maxOutputTokens: =If(Env.MODEL_NAME = "gpt-5", 8000, 4000)
|
||||
|
||||
# Adjust temperature for different environments
|
||||
temperature: =If(Env.ENVIRONMENT = "production", 0.3, 0.7)
|
||||
|
||||
# Use logical operators for complex conditions
|
||||
seed: =If(Env.ENVIRONMENT = "production" And Env.DETERMINISTIC = "true", 42, Blank())
|
||||
```
|
||||
|
||||
#### Supported PowerFx Features
|
||||
|
||||
- **String operations**: Concatenation (`&`), comparison (`=`, `<>`), substring testing (`in`, `exactin`)
|
||||
- **Logical operators**: `And`, `Or`, `Not` (also `&&`, `||`, `!`)
|
||||
- **Arithmetic**: Basic math operations (`+`, `-`, `*`, `/`)
|
||||
- **Conditional**: `If(condition, true_value, false_value)`
|
||||
- **Environment access**: `Env.<VARIABLE_NAME>`
|
||||
|
||||
Example with multiple features:
|
||||
|
||||
```yaml
|
||||
instructions: =If(
|
||||
Env.USE_EXPERT_MODE = "true",
|
||||
"You are an expert AI assistant with advanced capabilities. " & Env.CUSTOM_INSTRUCTIONS,
|
||||
"You are a helpful AI assistant."
|
||||
)
|
||||
|
||||
model:
|
||||
options:
|
||||
stopSequences: =If("gpt-4" in Env.MODEL_NAME, ["END", "STOP"], ["END"])
|
||||
```
|
||||
|
||||
**Note**: PowerFx evaluation happens when the YAML is loaded, not at runtime. Use environment variables (via `.env` file or `env_file` parameter) to make configurations flexible across environments.
|
||||
|
||||
## Running the Samples
|
||||
|
||||
Each sample can be run independently. Make sure you have the required environment variables set:
|
||||
|
||||
- For Azure samples: Ensure you're logged in via Azure CLI (`az login`)
|
||||
- For OpenAI samples: Set `OPENAI_APIKEY` environment variable
|
||||
|
||||
```bash
|
||||
# Run a specific sample
|
||||
python get_weather_agent.py
|
||||
python microsoft_learn_agent.py
|
||||
python azure_openai_responses_agent.py
|
||||
python openai_responses_agent.py
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Agent Framework Declarative Package](../../../packages/declarative/) - Main declarative package documentation
|
||||
- [Agent Samples](../../../../agent-samples/) - Additional declarative agent YAML specifications
|
||||
- [Agent Framework Core](../../../packages/core/) - Core agent framework documentation
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Explore the YAML files in the `agent-samples` folder to understand the configuration format
|
||||
2. Try modifying the samples to use different models or instructions
|
||||
3. Create your own declarative agent configurations
|
||||
4. Build custom function tools and bind them to your agents
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework.declarative import AgentFactory
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
|
||||
async def main():
|
||||
"""Create an agent from a declarative yaml specification and run it."""
|
||||
# get the path
|
||||
current_path = Path(__file__).parent
|
||||
yaml_path = current_path.parent.parent.parent.parent / "agent-samples" / "azure" / "AzureOpenAIResponses.yaml"
|
||||
|
||||
# load the yaml from the path
|
||||
with yaml_path.open("r") as f:
|
||||
yaml_str = f.read()
|
||||
|
||||
# create the agent from the yaml
|
||||
agent = AgentFactory(client_kwargs={"credential": AzureCliCredential()}).create_agent_from_yaml(yaml_str)
|
||||
# use the agent
|
||||
response = await agent.run("Why is the sky blue, answer in Dutch?")
|
||||
print("Agent response:", response.value.model_dump_json(indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from random import randint
|
||||
from typing import Literal
|
||||
|
||||
from agent_framework.azure import AzureOpenAIResponsesClient
|
||||
from agent_framework.declarative import AgentFactory
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
|
||||
def get_weather(location: str, unit: Literal["celsius", "fahrenheit"] = "celsius") -> str:
|
||||
"""A simple function tool to get weather information."""
|
||||
return f"The weather in {location} is {randint(-10, 30) if unit == 'celsius' else randint(30, 100)} degrees {unit}."
|
||||
|
||||
|
||||
async def main():
|
||||
"""Create an agent from a declarative yaml specification and run it."""
|
||||
# get the path
|
||||
current_path = Path(__file__).parent
|
||||
yaml_path = current_path.parent.parent.parent.parent / "agent-samples" / "chatclient" / "GetWeather.yaml"
|
||||
|
||||
# load the yaml from the path
|
||||
with yaml_path.open("r") as f:
|
||||
yaml_str = f.read()
|
||||
|
||||
# create the AgentFactory with a chat client and bindings
|
||||
agent_factory = AgentFactory(
|
||||
AzureOpenAIResponsesClient(credential=AzureCliCredential()),
|
||||
bindings={"get_weather": get_weather},
|
||||
)
|
||||
# create the agent from the yaml
|
||||
agent = agent_factory.create_agent_from_yaml(yaml_str)
|
||||
# use the agent
|
||||
response = await agent.run("What's the weather in Amsterdam, in celsius?")
|
||||
print("Agent response:", response.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework.declarative import AgentFactory
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
|
||||
async def main():
|
||||
"""Create an agent from a declarative yaml specification and run it."""
|
||||
# get the path
|
||||
current_path = Path(__file__).parent
|
||||
yaml_path = current_path.parent.parent.parent.parent / "agent-samples" / "foundry" / "MicrosoftLearnAgent.yaml"
|
||||
|
||||
# create the agent from the yaml
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
AgentFactory(client_kwargs={"async_credential": credential}).create_agent_from_yaml_path(yaml_path) as agent,
|
||||
):
|
||||
response = await agent.run("How do I create a storage account with private endpoint using bicep?")
|
||||
print("Agent response:", response.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,26 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework.declarative import AgentFactory
|
||||
|
||||
|
||||
async def main():
|
||||
"""Create an agent from a declarative yaml specification and run it."""
|
||||
# get the path
|
||||
current_path = Path(__file__).parent
|
||||
yaml_path = current_path.parent.parent.parent.parent / "agent-samples" / "openai" / "OpenAIResponses.yaml"
|
||||
|
||||
# load the yaml from the path
|
||||
with yaml_path.open("r") as f:
|
||||
yaml_str = f.read()
|
||||
|
||||
# create the agent from the yaml
|
||||
agent = AgentFactory().create_agent_from_yaml(yaml_str)
|
||||
# use the agent
|
||||
response = await agent.run("Why is the sky blue, answer in Dutch?")
|
||||
print("Agent response:", response.value)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Generated
+73
@@ -32,6 +32,7 @@ members = [
|
||||
"agent-framework-chatkit",
|
||||
"agent-framework-copilotstudio",
|
||||
"agent-framework-core",
|
||||
"agent-framework-declarative",
|
||||
"agent-framework-devui",
|
||||
"agent-framework-lab",
|
||||
"agent-framework-mem0",
|
||||
@@ -300,6 +301,7 @@ all = [
|
||||
{ name = "agent-framework-azurefunctions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-chatkit", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-copilotstudio", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-declarative", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-devui", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-lab", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-mem0", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
@@ -316,6 +318,7 @@ requires-dist = [
|
||||
{ name = "agent-framework-azurefunctions", marker = "extra == 'all'", editable = "packages/azurefunctions" },
|
||||
{ name = "agent-framework-chatkit", marker = "extra == 'all'", editable = "packages/chatkit" },
|
||||
{ name = "agent-framework-copilotstudio", marker = "extra == 'all'", editable = "packages/copilotstudio" },
|
||||
{ name = "agent-framework-declarative", marker = "extra == 'all'", editable = "packages/declarative" },
|
||||
{ name = "agent-framework-devui", marker = "extra == 'all'", editable = "packages/devui" },
|
||||
{ name = "agent-framework-lab", marker = "extra == 'all'", editable = "packages/lab" },
|
||||
{ name = "agent-framework-mem0", marker = "extra == 'all'", editable = "packages/mem0" },
|
||||
@@ -335,6 +338,31 @@ requires-dist = [
|
||||
]
|
||||
provides-extras = ["all"]
|
||||
|
||||
[[package]]
|
||||
name = "agent-framework-declarative"
|
||||
version = "1.0.0b251028"
|
||||
source = { editable = "packages/declarative" }
|
||||
dependencies = [
|
||||
{ name = "agent-framework-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "powerfx", marker = "(python_full_version < '3.14' and sys_platform == 'darwin') or (python_full_version < '3.14' and sys_platform == 'linux') or (python_full_version < '3.14' and sys_platform == 'win32')" },
|
||||
{ name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "types-pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "agent-framework-core", editable = "packages/core" },
|
||||
{ name = "powerfx", marker = "python_full_version < '3.14'", specifier = ">=0.0.31" },
|
||||
{ name = "pyyaml", specifier = ">=6.0,<7.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "types-pyyaml" }]
|
||||
|
||||
[[package]]
|
||||
name = "agent-framework-devui"
|
||||
version = "1.0.0b251114"
|
||||
@@ -1240,6 +1268,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clr-loader"
|
||||
version = "0.2.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/88/9e0a80d59b28d394aad5d736bd47e5aa5883cf1d3674b313ba93e2a353e4/clr_loader-0.2.8.tar.gz", hash = "sha256:b4cd3a2ee5f0489885ef07ffd87eb38b2cee24ca65dcacea97b34e7b59913814", size = 61502, upload-time = "2025-10-20T21:03:16.548Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/2d/748c97ed6a4e8ae38666fd2c42967296222b7902321cff939f60d5a72b55/clr_loader-0.2.8-py3-none-any.whl", hash = "sha256:2cd76904e2f68fecab1ad1d158fb2905b5173a2b0cd54606d548518642bfbce9", size = 56412, upload-time = "2025-10-20T21:02:47.476Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
@@ -4206,6 +4246,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/0c/8b6b20b0be71725e6e8a32dcd460cdbf62fe6df9bc656a650150dc98fedd/posthog-7.0.1-py3-none-any.whl", hash = "sha256:efe212d8d88a9ba80a20c588eab4baf4b1a5e90e40b551160a5603bb21e96904", size = 145234, upload-time = "2025-11-15T12:44:21.247Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfx"
|
||||
version = "0.0.31"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pythonnet", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/56/1d/40228886242df10c10ed69faf27e973d020c586aa723a51afbe48542d535/powerfx-0.0.31.tar.gz", hash = "sha256:fa9637f315d71163dd900d16f97fce562d550049713d2fc358f8d446bb23906f", size = 3235618, upload-time = "2025-09-16T15:10:13.159Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/45/fdc98dc8a3e38a3cde464e18624a4851785bf7cc63f207d04279e0a1db4f/powerfx-0.0.31-py3-none-any.whl", hash = "sha256:616dcff4950624d3c63dd72c01daea60b0838e217b0c5533dd2c40677444a340", size = 3481524, upload-time = "2025-09-16T15:10:10.393Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.4.0"
|
||||
@@ -4863,6 +4915,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/a0/4ed6632b70a52de845df056654162acdebaf97c20e3212c559ac43e7216e/python_ulid-3.1.0-py3-none-any.whl", hash = "sha256:e2cdc979c8c877029b4b7a38a6fba3bc4578e4f109a308419ff4d3ccf0a46619", size = 11577, upload-time = "2025-08-18T16:09:25.047Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pythonnet"
|
||||
version = "3.0.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "clr-loader", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9a/d6/1afd75edd932306ae9bd2c2d961d603dc2b52fcec51b04afea464f1f6646/pythonnet-3.0.5.tar.gz", hash = "sha256:48e43ca463941b3608b32b4e236db92d8d40db4c58a75ace902985f76dac21cf", size = 239212, upload-time = "2024-12-13T08:30:44.393Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/f1/bfb6811df4745f92f14c47a29e50e89a36b1533130fcc56452d4660bd2d6/pythonnet-3.0.5-py3-none-any.whl", hash = "sha256:f6702d694d5d5b163c9f3f5cc34e0bed8d6857150237fae411fefb883a656d20", size = 297506, upload-time = "2024-12-13T08:30:40.661Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
@@ -6104,6 +6168,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/dd/5cbf31f402f1cc0ab087c94d4669cfa55bd1e818688b910631e131d74e75/typer_slim-0.20.0-py3-none-any.whl", hash = "sha256:f42a9b7571a12b97dddf364745d29f12221865acef7a2680065f9bb29c7dc89d", size = 47087, upload-time = "2025-10-20T17:03:44.546Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.12.20250915"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.32.4.20250913"
|
||||
|
||||
Reference in New Issue
Block a user