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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions examples/a2a-test/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

import asyncio
import logging
import re
import uuid
from os import getenv
Expand All @@ -24,14 +25,20 @@
from microsoft_teams.ai import ChatPrompt, Function, ModelMessage
from microsoft_teams.api import MessageActivity, TypingActivityInput
from microsoft_teams.apps import ActivityContext, App, PluginBase
from microsoft_teams.common import ConsoleLogger, ConsoleLoggerOptions
from microsoft_teams.common import ConsoleFormatter
from microsoft_teams.devtools import DevToolsPlugin
from microsoft_teams.openai.completions_model import OpenAICompletionsAIModel
from pydantic import BaseModel

logger = ConsoleLogger().create_logger("a2a", ConsoleLoggerOptions(level="debug"))
PORT = getenv("PORT", "4000")

# Setup logging
logging.getLogger().setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(ConsoleFormatter())
logging.getLogger().addHandler(stream_handler)
logger = logging.getLogger(__name__)


# Setup AI
def get_required_env(key: str) -> str:
Expand Down Expand Up @@ -146,7 +153,7 @@ class LocationParams(BaseModel):

# Setup the A2A Server Plugin
plugins: List[PluginBase] = [A2APlugin(A2APluginOptions(agent_card=agent_card)), DevToolsPlugin()]
app = App(logger=logger, plugins=plugins)
app = App(plugins=plugins)


# A2A Server Event Handler
Expand Down
13 changes: 9 additions & 4 deletions examples/dialogs/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"""

import asyncio
import logging
import os
from logging import Logger
from typing import Any, Optional

from microsoft_teams.api import (
Expand All @@ -25,10 +25,15 @@
from microsoft_teams.apps import ActivityContext, App
from microsoft_teams.apps.events.types import ErrorEvent
from microsoft_teams.cards import AdaptiveCard, SubmitAction, SubmitActionData, TaskFetchSubmitActionData, TextBlock
from microsoft_teams.common.logging import ConsoleLogger
from microsoft_teams.common import ConsoleFormatter

# Setup logging
logging.getLogger().setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(ConsoleFormatter())
logging.getLogger().addHandler(stream_handler)
logger = logging.getLogger(__name__)

logger_instance = ConsoleLogger()
logger: Logger = logger_instance.create_logger("@apps/dialogs")

if not os.getenv("BOT_ENDPOINT"):
logger.warning("No remote endpoint detected. Using webpages for dialog will not work as expected")
Expand Down
16 changes: 11 additions & 5 deletions examples/graph/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@
from azure.core.exceptions import ClientAuthenticationError
from microsoft_teams.api import MessageActivity
from microsoft_teams.apps import ActivityContext, App, AppOptions, ErrorEvent, SignInEvent
from microsoft_teams.common import ConsoleFormatter
from microsoft_teams.graph import get_graph_client
from msgraph.generated.users.item.messages.messages_request_builder import ( # type: ignore
MessagesRequestBuilder,
)

# Setup logging
logging.getLogger().setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(ConsoleFormatter())
logging.getLogger().addHandler(stream_handler)
logger = logging.getLogger(__name__)

app_options = AppOptions(default_connection_name=os.getenv("CONNECTION_NAME", "graph"))
Expand All @@ -39,7 +45,7 @@ async def get_authenticated_graph_client(ctx: ActivityContext[MessageActivity]):
return get_graph_client(ctx.user_token)

except Exception as e:
ctx.logger.error(f"Failed to create Graph client: {e}")
logger.error(f"Failed to create Graph client: {e}")
await ctx.send("🔐 Failed to create authenticated client. Please try signing in again.")
await ctx.sign_in()
return None
Expand Down Expand Up @@ -90,12 +96,12 @@ async def handle_profile_command(ctx: ActivityContext[MessageActivity]):
await ctx.send("❌ Could not retrieve your profile information.")

except ClientAuthenticationError as e:
ctx.logger.error(f"Authentication error: {e}")
logger.error(f"Authentication error: {e}")
await ctx.send("🔐 Authentication failed. Please try signing in again.")
await ctx.sign_in()

except Exception as e:
ctx.logger.error(f"Error getting profile: {e}")
logger.error(f"Error getting profile: {e}")
await ctx.send(f"❌ Failed to get your profile: {str(e)}")


Expand Down Expand Up @@ -138,11 +144,11 @@ async def handle_emails_command(ctx: ActivityContext[MessageActivity]):
await ctx.send("📪 No recent emails found.")

except ClientAuthenticationError as e:
ctx.logger.error(f"Authentication error: {e}")
logger.error(f"Authentication error: {e}")
await ctx.send("🔐 Authentication failed. You may need additional permissions to read emails.")

except Exception as e:
ctx.logger.error(f"Error getting emails: {e}")
logger.error(f"Error getting emails: {e}")
await ctx.send(f"❌ Failed to get your emails: {str(e)}")


Expand Down
11 changes: 10 additions & 1 deletion examples/oauth/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@
"""

import asyncio
import logging

from microsoft_teams.api import MessageActivity
from microsoft_teams.api.activities.invoke.sign_in import SignInFailureInvokeActivity
from microsoft_teams.apps import ActivityContext, App, SignInEvent
from microsoft_teams.apps.events.types import ErrorEvent
from microsoft_teams.common import ConsoleFormatter

# Setup logging
logging.getLogger().setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(ConsoleFormatter())
logging.getLogger().addHandler(stream_handler)
logger = logging.getLogger(__name__)

app = App()

Expand All @@ -19,7 +28,7 @@ async def handle_message(ctx: ActivityContext[MessageActivity]):
print(f"[GENERATED onMessage] Message received: {ctx.activity.text}")
print(f"[GENERATED onMessage] From: {ctx.activity.from_}")

ctx.logger.info("User requested sign-in.")
logger.info("User requested sign-in.")
if ctx.is_signed_in:
await ctx.send("You are already signed in. Logging you out.")
await ctx.sign_out()
Expand Down
4 changes: 1 addition & 3 deletions examples/tab/Web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import * as teamsJs from '@microsoft/teams-js';
import * as client from '@microsoft/teams.client';
import * as endpoints from '@microsoft/teams.graph-endpoints';
import { ConsoleLogger } from '@microsoft/teams.common';

import './App.css';

Expand All @@ -17,7 +16,6 @@ export default function App() {
try {
// initialize the app and prompt for Graph scope consent, if not already granted
const app = new client.App(clientId, {
logger: new ConsoleLogger('@examples/tab', { level: 'debug' }),
msalOptions: {
prewarmScopes: [
'https://graph.microsoft.com/User.Read',
Expand All @@ -28,7 +26,7 @@ export default function App() {
});

await app.start();
app.log.info('app started');
console.info('app started');
setApp(app);
} catch (err) {
console.error(err);
Expand Down
4 changes: 4 additions & 0 deletions packages/a2aprotocol/src/microsoft_teams/a2a/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
Licensed under the MIT License.
"""

import logging

from . import chat_prompt, server
from .chat_prompt import * # noqa: F403
from .server import * # noqa: F401, F403

logging.getLogger(__name__).addHandler(logging.NullHandler())

# Combine all exports from submodules
__all__: list[str] = []
__all__.extend(chat_prompt.__all__)
Expand Down
23 changes: 11 additions & 12 deletions packages/a2aprotocol/src/microsoft_teams/a2a/chat_prompt/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@
Licensed under the MIT License.
"""

import logging
import re
import uuid
from dataclasses import asdict
from functools import partial
from logging import Logger
from typing import Any, Dict, List, Optional, Union, Unpack, cast

from httpx import AsyncClient
from microsoft_teams.ai import Function as ChatFunction
from microsoft_teams.ai import SystemMessage
from microsoft_teams.ai.plugin import BaseAIPlugin
from microsoft_teams.common.logging.console import ConsoleLogger
from pydantic import BaseModel

from a2a.client import ClientFactory
Expand All @@ -39,14 +38,15 @@
InternalA2AClientPluginOptions,
)

logger = logging.getLogger(__name__)


class A2AClientMessageParams(BaseModel):
message: str
"The message to send to the agent"


class A2AClientPlugin(BaseAIPlugin):
log: Logger
_agent_configs: Dict[str, AgentConfig] = {}
_clients: Dict[str, AgentClientInfo] = {}

Expand All @@ -63,7 +63,6 @@ def __init__(self, **options: Unpack[A2AClientPluginOptions]):
self._build_prompt = self.options.build_prompt
self._build_message_for_agent = self.options.build_message_for_agent
self._build_message_from_agent_response = self.options.build_message_from_agent_response
self.log = self.options.logger if self.options.logger else ConsoleLogger().create_logger(name="a2a:client")

def on_use_plugin(self, args: A2APluginUseParams):
# just store the config, defer client creation to on_build_functions
Expand Down Expand Up @@ -102,7 +101,7 @@ async def _get_agent_card(self, key: str, config: AgentConfig) -> Optional[Agent
self._clients.update({key: client_info})
return card
except Exception as e:
self.log.error(f"Error creating client or fetching agent card for {key}: {e}")
logger.error(f"Error creating client or fetching agent card for {key}: {e}")
return None

def _default_function_metadata(self, card: AgentCard) -> FunctionMetadata:
Expand Down Expand Up @@ -143,7 +142,7 @@ async def on_build_functions(self, functions: list[ChatFunction[Any]]) -> list[C
try:
agent_card = await self._get_agent_card(key, config)
if not agent_card:
self.log.warning(f"Could not retrieve agent card for {key}, continuing to next agent.")
logger.warning(f"Could not retrieve agent card for {key}, continuing to next agent.")
# skip if we couldn't get the agent card
continue

Expand Down Expand Up @@ -183,7 +182,7 @@ async def message_handler(
if not client_info:
raise ValueError(f"Client not found for agent {key}")

self.log.debug(f"Calling agent {agent_card.name} with {message.model_dump_json()}")
logger.debug(f"Calling agent {agent_card.name} with {message.model_dump_json()}")

last_message: Optional[Message] = None
async for event in client_info.client.send_message(message):
Expand All @@ -194,7 +193,7 @@ async def message_handler(
last_message = event

if last_message is not None:
self.log.debug(f"Got response from {agent_card.name}")
logger.debug(f"Got response from {agent_card.name}")
# use custom response builder if provided, otherwise use default
metadata = BuildMessageFromAgentMetadata(
card=agent_card, response=last_message, original_input=agent_message
Expand All @@ -209,7 +208,7 @@ async def message_handler(
)
)
except Exception as e:
self.log.error(e)
logger.error(e)
raise e

message_handler_with_args = partial(message_handler, config=config, agent_card=agent_card, key=key)
Expand All @@ -222,9 +221,9 @@ async def message_handler(
handler=message_handler_with_args,
)
)
self.log.debug(f"Added function in ChatPrompt to call {agent_card.name}")
logger.debug(f"Added function in ChatPrompt to call {agent_card.name}")
except Exception as e:
self.log.info(f"Failed to build function for agent {key}: {e}")
logger.info(f"Failed to build function for agent {key}: {e}")
# Continue with other agents even if one fails

functions = functions + all_functions
Expand All @@ -247,7 +246,7 @@ async def on_build_instructions(self, instructions: Optional[SystemMessage] = No
)
)
except Exception as e:
self.log.info(f"Failed to get agent card for {key}: {e}")
logger.info(f"Failed to get agent card for {key}: {e}")

# use custom build_prompt if provided, otherwise use default
if self._build_prompt:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,26 @@
Licensed under the MIT License.
"""

from logging import Logger
import logging

from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import ASGIApp

logger = logging.getLogger(__name__)


class LoggingMiddleware(BaseHTTPMiddleware):
def __init__(self, app: ASGIApp, logger: Logger) -> None:
def __init__(self, app: ASGIApp) -> None:
super().__init__(app)
self.logger = logger

async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
log_message = f"A2A Request: {request.method} {request.url}"

if request.method == "POST":
body = await request.body()
log_message += f" Body: {body.decode('utf-8')}"
self.logger.debug(log_message)
logger.debug(log_message)
response = await call_next(request)
return response
12 changes: 6 additions & 6 deletions packages/a2aprotocol/src/microsoft_teams/a2a/server/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
Licensed under the MIT License.
"""

from logging import Logger
import logging
from typing import Annotated, Any, Awaitable, Callable, List, Optional

from microsoft_teams.apps import (
DependencyMetadata,
EventMetadata,
HttpPlugin,
LoggerDependencyOptions,
Plugin,
PluginBase,
)
Expand All @@ -26,10 +25,11 @@
from .custom_agent_executor import A2AMessageEvent, CustomAgentExecutor
from .logging_middleware import LoggingMiddleware

logger = logging.getLogger(__name__)


@Plugin(name="a2a", version="0.3.7", description="A2A Server Plugin")
class A2APlugin(PluginBase):
logger: Annotated[Logger, LoggerDependencyOptions()]
http: Annotated[HttpPlugin, DependencyMetadata()]

emit: Annotated[Callable[[str, A2AMessageEvent], Awaitable[None]], EventMetadata(name="custom")]
Expand Down Expand Up @@ -68,13 +68,13 @@ async def on_init(self) -> None:
self.app = a2a_app.build(agent_card_url=self.agent_card_path)

# add the middleware
self.app.add_middleware(LoggingMiddleware, logger=self.logger)
self.app.add_middleware(LoggingMiddleware)
for middleware_info in self._middlewares:
middleware, kwargs = middleware_info
self.app.add_middleware(middleware, **kwargs)

self.logger.info(f"A2A agent set up at {self.agent_card_path}")
self.logger.info(f"A2A agent listening at {self.path}")
logger.info(f"A2A agent set up at {self.agent_card_path}")
logger.info(f"A2A agent listening at {self.path}")

self.http.app.mount(self.path, self.app)

Expand Down
4 changes: 4 additions & 0 deletions packages/ai/src/microsoft_teams/ai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
Licensed under the MIT License.
"""

import logging

from .ai_model import AIModel
from .chat_prompt import ChatPrompt, ChatSendResult
from .function import Function, FunctionCall, FunctionHandler, FunctionHandlers, FunctionHandlerWithNoParams
from .memory import ListMemory, Memory
from .message import FunctionMessage, Message, ModelMessage, SystemMessage, UserMessage

logging.getLogger(__name__).addHandler(logging.NullHandler())

__all__ = [
"ChatSendResult",
"ChatPrompt",
Expand Down
Loading