mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Added CopilotStudioAgent implementation
This commit is contained in:
@@ -17,3 +17,8 @@ AGENT_FRAMEWORK_ENABLE_SENSITIVE_DATA=true
|
||||
AGENT_FRAMEWORK_WORKFLOW_ENABLE_OTEL=true
|
||||
# Mem0
|
||||
MEM0_API_KEY=""
|
||||
# Copilot Studio
|
||||
COPILOTSTUDIOAGENT__ENVIRONMENTID=""
|
||||
COPILOTSTUDIOAGENT__SCHEMANAME=""
|
||||
COPILOTSTUDIOAGENT__TENANTID=""
|
||||
COPILOTSTUDIOAGENT__AGENTAPPID=""
|
||||
|
||||
@@ -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,13 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import importlib.metadata
|
||||
|
||||
from ._acquire_token import acquire_token
|
||||
from ._agent import CopilotStudioAgent
|
||||
|
||||
try:
|
||||
__version__ = importlib.metadata.version(__name__)
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
__version__ = "0.0.0" # Fallback for development mode
|
||||
|
||||
__all__ = ["CopilotStudioAgent", "__version__", "acquire_token"]
|
||||
@@ -0,0 +1,94 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
# pyright: reportUnknownMemberType = false
|
||||
# pyright: reportUnknownVariableType = false
|
||||
# pyright: reportUnknownArgumentType = false
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from agent_framework.exceptions import ServiceException
|
||||
from msal import PublicClientApplication
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Default scopes for Power Platform API
|
||||
DEFAULT_SCOPES = ["https://api.powerplatform.com/.default"]
|
||||
|
||||
|
||||
def acquire_token(
|
||||
client_id: str,
|
||||
tenant_id: str,
|
||||
username: str | None = None,
|
||||
token_cache: Any | None = None,
|
||||
scopes: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Acquire an authentication token using MSAL Public Client Application.
|
||||
|
||||
This function attempts to acquire a token silently first (using cached tokens),
|
||||
and falls back to interactive authentication if needed.
|
||||
|
||||
Args:
|
||||
client_id: The client ID of the application.
|
||||
tenant_id: The tenant ID for authentication.
|
||||
username: Optional username to filter accounts.
|
||||
token_cache: Optional token cache for storing tokens.
|
||||
scopes: Optional list of scopes. Defaults to Power Platform API scopes.
|
||||
|
||||
Returns:
|
||||
The access token string.
|
||||
|
||||
Raises:
|
||||
ServiceException: If authentication token cannot be acquired.
|
||||
"""
|
||||
if not client_id:
|
||||
raise ServiceException("Client ID is required for token acquisition.")
|
||||
|
||||
if not tenant_id:
|
||||
raise ServiceException("Tenant ID is required for token acquisition.")
|
||||
|
||||
authority = f"https://login.microsoftonline.com/{tenant_id}"
|
||||
target_scopes = scopes or DEFAULT_SCOPES
|
||||
|
||||
pca = PublicClientApplication(client_id=client_id, authority=authority, token_cache=token_cache)
|
||||
|
||||
accounts = pca.get_accounts(username=username)
|
||||
|
||||
token: str | None = None
|
||||
|
||||
# Try silent token acquisition first if we have cached accounts
|
||||
if accounts:
|
||||
try:
|
||||
logger.debug("Attempting silent token acquisition")
|
||||
response = pca.acquire_token_silent(scopes=target_scopes, account=accounts[0])
|
||||
if response and "access_token" in response:
|
||||
token = str(response["access_token"]) # type: ignore[assignment]
|
||||
logger.debug("Successfully acquired token silently")
|
||||
elif response and "error" in response:
|
||||
logger.warning(
|
||||
"Silent token acquisition failed: %s - %s", response.get("error"), response.get("error_description")
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.warning("Silent token acquisition failed with exception: %s", ex)
|
||||
|
||||
# Fall back to interactive authentication if silent acquisition failed
|
||||
if not token:
|
||||
try:
|
||||
logger.debug("Attempting interactive token acquisition")
|
||||
response = pca.acquire_token_interactive(scopes=target_scopes)
|
||||
if response and "access_token" in response:
|
||||
token = str(response["access_token"]) # type: ignore[assignment]
|
||||
logger.debug("Successfully acquired token interactively")
|
||||
elif response and "error" in response:
|
||||
logger.error(
|
||||
"Interactive token acquisition failed: %s - %s",
|
||||
response.get("error"),
|
||||
response.get("error_description"),
|
||||
)
|
||||
except Exception as ex:
|
||||
logger.error("Interactive token acquisition failed with exception: %s", ex)
|
||||
raise ServiceException(f"Failed to acquire authentication token: {ex}") from ex
|
||||
|
||||
if not token:
|
||||
raise ServiceException("Authentication token cannot be acquired.")
|
||||
|
||||
return token
|
||||
@@ -0,0 +1,282 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from collections.abc import AsyncIterable, MutableSequence
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from agent_framework import (
|
||||
AgentRunResponse,
|
||||
AgentRunResponseUpdate,
|
||||
AgentThread,
|
||||
BaseAgent,
|
||||
ChatMessage,
|
||||
Contents,
|
||||
Role,
|
||||
TextContent,
|
||||
TextReasoningContent,
|
||||
)
|
||||
from agent_framework._pydantic import AFBaseSettings
|
||||
from agent_framework.exceptions import ServiceException, ServiceInitializationError
|
||||
from microsoft_agents.activity import Activity
|
||||
from microsoft_agents.copilotstudio.client import AgentType, ConnectionSettings, CopilotClient, PowerPlatformCloud
|
||||
from pydantic import ValidationError
|
||||
|
||||
from ._acquire_token import acquire_token
|
||||
|
||||
|
||||
class CopilotStudioSettings(AFBaseSettings):
|
||||
"""Copilot Studio model settings.
|
||||
|
||||
The settings are first loaded from environment variables with the prefix 'COPILOTSTUDIOAGENT__'.
|
||||
If the environment variables are not found, the settings can be loaded from a .env file
|
||||
with the encoding 'utf-8'. If the settings are not found in the .env file, the settings
|
||||
are ignored; however, validation will fail alerting that the settings are missing.
|
||||
|
||||
Attributes:
|
||||
environment_id: Environment ID of environment with the Copilot Studio App..
|
||||
(Env var COPILOTSTUDIOAGENT__ENVIRONMENTID)
|
||||
agent_identifier: The agent identifier or schema name of the Copilot to use.
|
||||
(Env var COPILOTSTUDIOAGENT__SCHEMANAME)
|
||||
client_id: The app ID of the App Registration used to login.
|
||||
(Env var COPILOTSTUDIOAGENT__AGENTAPPID)
|
||||
tenant_id: The tenant ID of the App Registration used to login.
|
||||
(Env var COPILOTSTUDIOAGENT__TENANTID)
|
||||
Parameters:
|
||||
env_file_path: If provided, the .env settings are read from this file path location.
|
||||
env_file_encoding: The encoding of the .env file, defaults to 'utf-8'.
|
||||
"""
|
||||
|
||||
env_prefix: ClassVar[str] = "COPILOTSTUDIOAGENT__"
|
||||
|
||||
environment_id: str | None = None
|
||||
agent_identifier: str | None = None
|
||||
client_id: str | None = None
|
||||
tenant_id: str | None = None
|
||||
|
||||
|
||||
class CopilotStudioAgent(BaseAgent):
|
||||
"""A Copilot Studio Agent."""
|
||||
|
||||
client: CopilotClient
|
||||
settings: ConnectionSettings | None
|
||||
environment_id: str | None
|
||||
agent_identifier: str | None
|
||||
client_id: str | None
|
||||
tenant_id: str | None
|
||||
token: str | None
|
||||
cloud: PowerPlatformCloud | None
|
||||
agent_type: AgentType | None
|
||||
custom_power_platform_cloud: str | None
|
||||
username: str | None
|
||||
token_cache: Any | None
|
||||
scopes: list[str] | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: CopilotClient | None = None,
|
||||
settings: ConnectionSettings | None = None,
|
||||
environment_id: str | None = None,
|
||||
agent_identifier: str | None = None,
|
||||
client_id: str | None = None,
|
||||
tenant_id: str | None = None,
|
||||
token: str | None = None,
|
||||
cloud: PowerPlatformCloud | None = None,
|
||||
agent_type: AgentType | None = None,
|
||||
custom_power_platform_cloud: str | None = None,
|
||||
username: str | None = None,
|
||||
token_cache: Any | None = None,
|
||||
scopes: list[str] | None = None,
|
||||
env_file_path: str | None = None,
|
||||
env_file_encoding: str | None = None,
|
||||
) -> None:
|
||||
if not client:
|
||||
try:
|
||||
copilot_studio_settings = CopilotStudioSettings(
|
||||
environment_id=environment_id,
|
||||
agent_identifier=agent_identifier,
|
||||
client_id=client_id,
|
||||
tenant_id=tenant_id,
|
||||
env_file_path=env_file_path,
|
||||
env_file_encoding=env_file_encoding,
|
||||
)
|
||||
except ValidationError as ex:
|
||||
raise ServiceInitializationError("Failed to create Copilot Studio settings.", ex) from ex
|
||||
|
||||
if not settings:
|
||||
if not copilot_studio_settings.environment_id:
|
||||
raise ServiceInitializationError(
|
||||
"Copilot Studio environment ID is required. Set via 'environment_id' parameter "
|
||||
"or 'COPILOTSTUDIOAGENT__ENVIRONMENTID' environment variable."
|
||||
)
|
||||
if not copilot_studio_settings.agent_identifier:
|
||||
raise ServiceInitializationError(
|
||||
"Copilot Studio agent identifier/schema name is required. Set via 'agent_identifier' parameter "
|
||||
"or 'COPILOTSTUDIOAGENT__SCHEMANAME' environment variable."
|
||||
)
|
||||
|
||||
settings = ConnectionSettings(
|
||||
environment_id=copilot_studio_settings.environment_id,
|
||||
agent_identifier=copilot_studio_settings.agent_identifier,
|
||||
cloud=cloud,
|
||||
copilot_agent_type=agent_type,
|
||||
custom_power_platform_cloud=custom_power_platform_cloud,
|
||||
)
|
||||
|
||||
if not token:
|
||||
if not copilot_studio_settings.client_id:
|
||||
raise ServiceInitializationError(
|
||||
"Copilot Studio client ID is required. Set via 'client_id' parameter "
|
||||
"or 'COPILOTSTUDIOAGENT__AGENTAPPID' environment variable."
|
||||
)
|
||||
|
||||
if not copilot_studio_settings.tenant_id:
|
||||
raise ServiceInitializationError(
|
||||
"Copilot Studio tenant ID is required. Set via 'tenant_id' parameter "
|
||||
"or 'COPILOTSTUDIOAGENT__TENANTID' environment variable."
|
||||
)
|
||||
|
||||
token = acquire_token(
|
||||
client_id=copilot_studio_settings.client_id,
|
||||
tenant_id=copilot_studio_settings.tenant_id,
|
||||
username=username,
|
||||
token_cache=token_cache,
|
||||
scopes=scopes,
|
||||
)
|
||||
|
||||
client = CopilotClient(settings=settings, token=token)
|
||||
|
||||
super().__init__(
|
||||
client=client, # type: ignore[reportCallIssue]
|
||||
settings=settings, # type: ignore[reportCallIssue]
|
||||
environment_id=copilot_studio_settings.environment_id, # type: ignore[reportCallIssue]
|
||||
agent_identifier=copilot_studio_settings.agent_identifier, # type: ignore[reportCallIssue]
|
||||
client_id=copilot_studio_settings.client_id, # type: ignore[reportCallIssue]
|
||||
tenant_id=copilot_studio_settings.tenant_id, # type: ignore[reportCallIssue]
|
||||
token=token, # type: ignore[reportCallIssue]
|
||||
cloud=cloud, # type: ignore[reportCallIssue]
|
||||
agent_type=agent_type, # type: ignore[reportCallIssue]
|
||||
custom_power_platform_cloud=custom_power_platform_cloud, # type: ignore[reportCallIssue]
|
||||
username=username, # type: ignore[reportCallIssue]
|
||||
token_cache=token_cache, # type: ignore[reportCallIssue]
|
||||
scopes=scopes, # type: ignore[reportCallIssue]
|
||||
)
|
||||
|
||||
async def run(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
*,
|
||||
thread: AgentThread | None = None,
|
||||
**kwargs: Any,
|
||||
) -> AgentRunResponse:
|
||||
"""Get a response from the agent.
|
||||
|
||||
This method returns the final result of the agent's execution
|
||||
as a single AgentRunResponse object. The caller is blocked until
|
||||
the final result is available.
|
||||
|
||||
Note: For streaming responses, use the run_stream method, which returns
|
||||
intermediate steps and the final result as a stream of AgentRunResponseUpdate
|
||||
objects. Streaming only the final result is not feasible because the timing of
|
||||
the final result's availability is unknown, and blocking the caller until then
|
||||
is undesirable in streaming scenarios.
|
||||
|
||||
Args:
|
||||
messages: The message(s) to send to the agent.
|
||||
thread: The conversation thread associated with the message(s).
|
||||
kwargs: Additional keyword arguments.
|
||||
|
||||
Returns:
|
||||
An agent response item.
|
||||
"""
|
||||
if not thread:
|
||||
thread = self.get_new_thread()
|
||||
thread.service_thread_id = await self._start_new_conversation()
|
||||
|
||||
input_messages = self._normalize_messages(messages)
|
||||
|
||||
question = "\n".join([message.text for message in input_messages])
|
||||
|
||||
activities = self.client.ask_question(question, thread.service_thread_id)
|
||||
response_messages: list[ChatMessage] = []
|
||||
response_id: str | None = None
|
||||
|
||||
async for message in self._process_activities(activities):
|
||||
response_messages.append(message)
|
||||
if not response_id:
|
||||
response_id = message.message_id
|
||||
|
||||
return AgentRunResponse(messages=response_messages, response_id=response_id)
|
||||
|
||||
async def run_stream(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
*,
|
||||
thread: AgentThread | None = None,
|
||||
**kwargs: Any,
|
||||
) -> AsyncIterable[AgentRunResponseUpdate]:
|
||||
"""Run the agent as a stream.
|
||||
|
||||
This method will return the intermediate steps and final results of the
|
||||
agent's execution as a stream of AgentRunResponseUpdate objects to the caller.
|
||||
|
||||
Note: An AgentRunResponseUpdate object contains a chunk of a message.
|
||||
|
||||
Args:
|
||||
messages: The message(s) to send to the agent.
|
||||
thread: The conversation thread associated with the message(s).
|
||||
kwargs: Additional keyword arguments.
|
||||
|
||||
Yields:
|
||||
An agent response item.
|
||||
"""
|
||||
if not thread:
|
||||
thread = self.get_new_thread()
|
||||
thread.service_thread_id = await self._start_new_conversation()
|
||||
|
||||
input_messages = self._normalize_messages(messages)
|
||||
|
||||
question = "\n".join([message.text for message in input_messages])
|
||||
|
||||
activities = self.client.ask_question(question, thread.service_thread_id)
|
||||
|
||||
async for message in self._process_activities(activities):
|
||||
yield AgentRunResponseUpdate(
|
||||
role=message.role,
|
||||
contents=message.contents,
|
||||
additional_properties=message.additional_properties,
|
||||
author_name=message.author_name,
|
||||
raw_representation=message.raw_representation,
|
||||
response_id=message.message_id,
|
||||
message_id=message.message_id,
|
||||
)
|
||||
|
||||
async def _start_new_conversation(self) -> str:
|
||||
conversation_id: str | None = None
|
||||
|
||||
async for activity in self.client.start_conversation(emit_start_conversation_event=True):
|
||||
if activity and activity.conversation and activity.conversation.id:
|
||||
conversation_id = activity.conversation.id
|
||||
|
||||
if not conversation_id:
|
||||
raise ServiceException("Failed to start a new conversation.")
|
||||
|
||||
return conversation_id
|
||||
|
||||
async def _process_activities(self, activities: AsyncIterable[Activity]) -> AsyncIterable[ChatMessage]:
|
||||
async for activity in activities:
|
||||
if activity.type == "message":
|
||||
yield self._create_chat_message_from_activity(activity, [TextContent(activity.text)])
|
||||
break
|
||||
elif activity.type == "typing" or activity.type == "event":
|
||||
yield self._create_chat_message_from_activity(activity, [TextReasoningContent(activity.text)])
|
||||
break
|
||||
|
||||
def _create_chat_message_from_activity(
|
||||
self, activity: Activity, contents: MutableSequence[Contents]
|
||||
) -> ChatMessage:
|
||||
return ChatMessage(
|
||||
role=Role.ASSISTANT,
|
||||
contents=contents,
|
||||
author_name=activity.from_property.name if activity.from_property else None,
|
||||
message_id=activity.id,
|
||||
raw_representation=activity,
|
||||
)
|
||||
@@ -0,0 +1,94 @@
|
||||
[project]
|
||||
name = "agent-framework-copilotstudio"
|
||||
description = "Copilot Studio integration for Microsoft Agent Framework."
|
||||
authors = [{ name = "Microsoft", email = "SK-Support@microsoft.com"}]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
version = "0.1.0b1"
|
||||
license-files = ["LICENSE"]
|
||||
urls.homepage = "https://learn.microsoft.com/en-us/semantic-kernel/overview/"
|
||||
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 :: 5 - Production/Stable",
|
||||
"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",
|
||||
"Framework :: Pydantic :: 2",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework",
|
||||
"microsoft-agents-copilotstudio-client>=0.3.1",
|
||||
]
|
||||
|
||||
[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]
|
||||
extend = "../../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
|
||||
disallow_any_unimported = true
|
||||
|
||||
[tool.bandit]
|
||||
targets = ["agent_framework_copilotstudio"]
|
||||
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_copilotstudio"
|
||||
test = "pytest --cov=agent_framework_copilotstudio --cov-report=term-missing:skip-covered tests"
|
||||
|
||||
[tool.uv.build-backend]
|
||||
module-name = "agent_framework_copilotstudio"
|
||||
module-root = ""
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.8.2,<0.9.0"]
|
||||
build-backend = "uv_build"
|
||||
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
|
||||
def test_self_through_main() -> None:
|
||||
try:
|
||||
from agent_framework.copilotstudio import __version__
|
||||
except ImportError:
|
||||
__version__ = None
|
||||
|
||||
assert __version__ is not None
|
||||
|
||||
|
||||
def test_self() -> None:
|
||||
try:
|
||||
from agent_framework_copilotstudio import __version__
|
||||
except ImportError:
|
||||
__version__ = None
|
||||
|
||||
assert __version__ is not None
|
||||
|
||||
|
||||
def test_agent_framework() -> None:
|
||||
try:
|
||||
from agent_framework import __version__
|
||||
except ImportError:
|
||||
__version__ = None
|
||||
|
||||
assert __version__ is not None
|
||||
@@ -170,6 +170,21 @@ class BaseAgent(AFBaseModel):
|
||||
await deserialize_thread_state(thread, serialized_thread, **kwargs)
|
||||
return thread
|
||||
|
||||
def _normalize_messages(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str] | Sequence[ChatMessage] | None = None,
|
||||
) -> list[ChatMessage]:
|
||||
if messages is None:
|
||||
return []
|
||||
|
||||
if isinstance(messages, str):
|
||||
return [ChatMessage(role=Role.USER, text=messages)]
|
||||
|
||||
if isinstance(messages, ChatMessage):
|
||||
return [messages]
|
||||
|
||||
return [ChatMessage(role=Role.USER, text=msg) if isinstance(msg, str) else msg for msg in messages]
|
||||
|
||||
|
||||
# region ChatAgent
|
||||
|
||||
@@ -669,21 +684,6 @@ class ChatAgent(BaseAgent):
|
||||
messages.extend(input_messages or [])
|
||||
return thread, messages
|
||||
|
||||
def _normalize_messages(
|
||||
self,
|
||||
messages: str | ChatMessage | Sequence[str] | Sequence[ChatMessage] | None = None,
|
||||
) -> list[ChatMessage]:
|
||||
if messages is None:
|
||||
return []
|
||||
|
||||
if isinstance(messages, str):
|
||||
return [ChatMessage(role=Role.USER, text=messages)]
|
||||
|
||||
if isinstance(messages, ChatMessage):
|
||||
return [messages]
|
||||
|
||||
return [ChatMessage(role=Role.USER, text=msg) if isinstance(msg, str) else msg for msg in messages]
|
||||
|
||||
def _get_agent_name(self) -> str:
|
||||
return self.name or "UnnamedAgent"
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
PACKAGE_NAME = "agent_framework_copilotstudio"
|
||||
PACKAGE_EXTRA = "copilotstudio"
|
||||
_IMPORTS = ["CopilotStudioAgent", "__version__", "acquire_token"]
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name in _IMPORTS:
|
||||
try:
|
||||
return getattr(importlib.import_module(PACKAGE_NAME), name)
|
||||
except ModuleNotFoundError as exc:
|
||||
raise ModuleNotFoundError(
|
||||
f"The '{PACKAGE_EXTRA}' extra is not installed, "
|
||||
f"please do `pip install agent-framework[{PACKAGE_EXTRA}]`"
|
||||
) from exc
|
||||
raise AttributeError(f"Module {PACKAGE_NAME} has no attribute {name}.")
|
||||
|
||||
|
||||
def __dir__() -> list[str]:
|
||||
return _IMPORTS
|
||||
@@ -0,0 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from agent_framework_copilotstudio import CopilotStudioAgent, __version__, acquire_token
|
||||
|
||||
__all__ = ["CopilotStudioAgent", "__version__", "acquire_token"]
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from collections.abc import AsyncIterable
|
||||
from collections.abc import AsyncIterable, Sequence
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast
|
||||
|
||||
@@ -199,7 +199,7 @@ class WorkflowAgent(BaseAgent):
|
||||
|
||||
def _normalize_messages(
|
||||
self,
|
||||
messages: str | ChatMessage | list[str] | list[ChatMessage] | None = None,
|
||||
messages: str | ChatMessage | Sequence[str] | Sequence[ChatMessage] | None = None,
|
||||
) -> list[ChatMessage]:
|
||||
"""Normalize input messages to a list of ChatMessage objects."""
|
||||
if messages is None:
|
||||
@@ -211,7 +211,7 @@ class WorkflowAgent(BaseAgent):
|
||||
if isinstance(messages, ChatMessage):
|
||||
return [messages]
|
||||
|
||||
normalized = []
|
||||
normalized: list[ChatMessage] = []
|
||||
for msg in messages:
|
||||
if isinstance(msg, str):
|
||||
normalized.append(ChatMessage(role=Role.USER, contents=[TextContent(text=msg)]))
|
||||
|
||||
@@ -6,6 +6,7 @@ requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"agent-framework",
|
||||
"agent-framework-azure",
|
||||
"agent-framework-copilotstudio",
|
||||
"agent-framework-foundry",
|
||||
"agent-framework-mem0",
|
||||
"agent-framework-workflow",
|
||||
@@ -63,6 +64,7 @@ exclude = [ "packages/agent_framework_project.egg-info" ]
|
||||
[tool.uv.sources]
|
||||
agent-framework = { workspace = true }
|
||||
agent-framework-azure = { workspace = true }
|
||||
agent-framework-copilotstudio = { workspace = true }
|
||||
agent-framework-foundry = { workspace = true }
|
||||
agent-framework-mem0 = { workspace = true }
|
||||
agent-framework-runtime = { workspace = true }
|
||||
@@ -193,7 +195,7 @@ build = "python run_tasks_in_packages_if_exists.py build"
|
||||
# combined checks
|
||||
check = ["fmt", "lint", "pyright", "mypy", "test", "markdown-code-lint", "samples-code-check"]
|
||||
pre-commit-check = ["fmt", "lint", "pyright", "markdown-code-lint", "samples-code-check"]
|
||||
all-tests = "pytest --import-mode=importlib --cov=agent_framework --cov=agent_framework_azure --cov=agent_framework_foundry --cov=agent_framework_mem0 --cov=agent_framework_workflow --cov-report=term-missing:skip-covered packages/azure/tests packages/foundry/tests packages/main/tests packages/mem0/tests packages/workflow/tests"
|
||||
all-tests = "pytest --import-mode=importlib --cov=agent_framework --cov=agent_framework_azure --cov=agent_framework_foundry --cov=agent_framework_mem0 --cov=agent_framework_workflow --cov-report=term-missing:skip-covered packages/azure/tests packages/copilotstudio/tests packages/foundry/tests packages/main/tests packages/mem0/tests packages/workflow/tests"
|
||||
|
||||
[tool.poe.tasks.venv]
|
||||
cmd = "uv venv --clear --python $python"
|
||||
|
||||
Generated
+66
-5
@@ -25,6 +25,7 @@ supported-markers = [
|
||||
members = [
|
||||
"agent-framework",
|
||||
"agent-framework-azure",
|
||||
"agent-framework-copilotstudio",
|
||||
"agent-framework-foundry",
|
||||
"agent-framework-mem0",
|
||||
"agent-framework-project",
|
||||
@@ -51,7 +52,7 @@ source = { editable = "packages/main" }
|
||||
dependencies = [
|
||||
{ name = "azure-monitor-opentelemetry", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "azure-monitor-opentelemetry-exporter", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "mcp", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "mcp", extra = ["ws"], marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "openai", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "opentelemetry-api", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-grpc", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
@@ -59,7 +60,6 @@ dependencies = [
|
||||
{ name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "pydantic-settings", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "websockets", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
@@ -84,7 +84,7 @@ requires-dist = [
|
||||
{ name = "agent-framework-workflow", marker = "extra == 'workflow'", editable = "packages/workflow" },
|
||||
{ name = "azure-monitor-opentelemetry", specifier = ">=1.7.0" },
|
||||
{ name = "azure-monitor-opentelemetry-exporter", specifier = ">=1.0.0b41" },
|
||||
{ name = "mcp", specifier = ">=1.12" },
|
||||
{ name = "mcp", extras = ["ws"], specifier = ">=1.13" },
|
||||
{ name = "openai", specifier = ">=1.99.0" },
|
||||
{ name = "opentelemetry-api", specifier = "~=1.24" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.36.0" },
|
||||
@@ -92,7 +92,6 @@ requires-dist = [
|
||||
{ name = "pydantic", specifier = ">=2.11.7" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.10.1" },
|
||||
{ name = "typing-extensions", specifier = ">=4.14.0" },
|
||||
{ name = "websockets", specifier = ">=15.0.1" },
|
||||
]
|
||||
provides-extras = ["azure", "foundry", "workflow", "runtime"]
|
||||
|
||||
@@ -111,6 +110,21 @@ requires-dist = [
|
||||
{ name = "azure-identity", specifier = ">=1.13" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent-framework-copilotstudio"
|
||||
version = "0.1.0b1"
|
||||
source = { editable = "packages/copilotstudio" }
|
||||
dependencies = [
|
||||
{ name = "agent-framework", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "microsoft-agents-copilotstudio-client", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "agent-framework", editable = "packages/main" },
|
||||
{ name = "microsoft-agents-copilotstudio-client", specifier = ">=0.3.1" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent-framework-foundry"
|
||||
version = "0.1.0b1"
|
||||
@@ -152,6 +166,7 @@ source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "agent-framework", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "agent-framework-azure", 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-foundry", 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'" },
|
||||
{ name = "agent-framework-workflow", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
@@ -196,6 +211,7 @@ dev = [
|
||||
requires-dist = [
|
||||
{ name = "agent-framework", editable = "packages/main" },
|
||||
{ name = "agent-framework-azure", editable = "packages/azure" },
|
||||
{ name = "agent-framework-copilotstudio", editable = "packages/copilotstudio" },
|
||||
{ name = "agent-framework-foundry", editable = "packages/foundry" },
|
||||
{ name = "agent-framework-mem0", editable = "packages/mem0" },
|
||||
{ name = "agent-framework-workflow", editable = "packages/workflow" },
|
||||
@@ -1052,7 +1068,7 @@ name = "exceptiongroup"
|
||||
version = "1.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "(python_full_version < '3.13' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform == 'win32')" },
|
||||
{ name = "typing-extensions", marker = "(python_full_version < '3.11' and sys_platform == 'darwin') or (python_full_version < '3.11' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform == 'win32')" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 }
|
||||
wheels = [
|
||||
@@ -1860,6 +1876,11 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/19/3f/d085c7f49ade6d273b185d61ec9405e672b6433f710ea64a90135a8dd445/mcp-1.13.1-py3-none-any.whl", hash = "sha256:c314e7c8bd477a23ba3ef472ee5a32880316c42d03e06dcfa31a1cc7a73b65df", size = 161494 },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
ws = [
|
||||
{ name = "websockets", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdit-py-plugins"
|
||||
version = "0.5.0"
|
||||
@@ -1899,6 +1920,46 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/f4/7fb9974bb1f81afb363a6b5a74f64609bf8e021d0cfe486909c6db6a8f3b/mem0ai-0.1.117-py3-none-any.whl", hash = "sha256:12159d6b9fef22a155f9b8b305999c52dfad20a46e7ccbced68677b6f48d4b57", size = 213063 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-agents-activity"
|
||||
version = "0.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/6a/dfc2fc0316b7dc4f6d24792b4a31a873b026be76792af1e0c3e65f843ef0/microsoft_agents_activity-0.3.1.tar.gz", hash = "sha256:c7567fc30f8e6f2a2d74cd65a1f7f31ade0d7ec9dd94531677d0d7b0648c77ee", size = 44886 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/25/8b/50ce2243e2900e94358f37009121145bb8224a388d95d704856aa2686667/microsoft_agents_activity-0.3.1-py3-none-any.whl", hash = "sha256:d7fc2e9cf2843ec8d6d42608b808b159a12cbb61e1fc7d7b1aaf29899f20746a", size = 111904 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-agents-copilotstudio-client"
|
||||
version = "0.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "microsoft-agents-hosting-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e9/a5/2381ffd14d6a584f9f7ab80c7b6c634f658ea651b38702eb403c930d8396/microsoft_agents_copilotstudio_client-0.3.1.tar.gz", hash = "sha256:c529209241c9d11b7a6e8696f96a3d43121c10b49e44f00e5066f9cf5256f4f3", size = 5024 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/97/35/8b4e9c691f2ce89653007f358519bbadff1fe0d495c3723c9dbbfa962a33/microsoft_agents_copilotstudio_client-0.3.1-py3-none-any.whl", hash = "sha256:cac7485405325b990202452c9c14848cbdb25d13e6cdaf7bd3eca3a5c1fb3989", size = 7420 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "microsoft-agents-hosting-core"
|
||||
version = "0.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "azure-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "isodate", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "microsoft-agents-activity", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "pyjwt", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
{ name = "python-dotenv", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/14/a1365e0bab1486c2d16aabeb192ca90715794edf4e68be4815c245884420/microsoft_agents_hosting_core-0.3.1.tar.gz", hash = "sha256:0b76bda10e7a54ff3c86e56cbabaad5ac7a4c2a076c9833af3b2f4c86fa85e89", size = 81137 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/1b/543ddaa2daf8593911a02a07a6a78366d4a6a0053ec86a557c19fa97b60e/microsoft_agents_hosting_core-0.3.1-py3-none-any.whl", hash = "sha256:a4b41556b15321b74f539c5a0a89f70955459b7ec57e9e4b24e61bba27f1cbbc", size = 94573 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msal"
|
||||
version = "1.33.0"
|
||||
|
||||
Reference in New Issue
Block a user