Merge pull request #3606 from microsoft/agentchat_logging_vd

add logging to agentchat
This commit is contained in:
Eric Zhu 2024-10-03 17:30:01 -07:00 committed by GitHub
commit fdb8f8a256
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 124 additions and 15 deletions

View File

@ -0,0 +1,93 @@
from datetime import datetime
import json
import logging
import sys
from typing import Union, List, Dict, Any, Sequence
from dataclasses import asdict, is_dataclass
from .teams.group_chat._events import ContentPublishEvent
from .agents import ChatMessage, TextMessage, MultiModalMessage, ToolCallMessage, ToolCallResultMessage, StopMessage
from autogen_core.components import FunctionCall, Image
from autogen_core.components.models import FunctionExecutionResult
EVENT_LOGGER_NAME = "autogen_agentchat.events"
ContentType = Union[str, List[Union[str, Image]], List[FunctionCall], List[FunctionExecutionResult]]
class BaseLogHandler(logging.Handler):
def serialize_content(
self, content: Union[ContentType, Sequence[ChatMessage], ChatMessage]
) -> Union[List[Any], Dict[str, Any], str]:
if isinstance(content, (str, list)):
return content
elif isinstance(content, (TextMessage, MultiModalMessage, ToolCallMessage, ToolCallResultMessage, StopMessage)):
return asdict(content)
elif isinstance(content, Image):
return {"type": "image", "data": content.data_uri}
elif isinstance(content, FunctionCall):
return {"type": "function_call", "name": content.name, "arguments": content.arguments}
elif isinstance(content, FunctionExecutionResult):
return {"type": "function_execution_result", "content": content.content}
return str(content)
@staticmethod
def json_serializer(obj: Any) -> Any:
if is_dataclass(obj) and not isinstance(obj, type):
return asdict(obj)
elif isinstance(obj, type):
return str(obj)
return str(obj)
class ConsoleLogHandler(BaseLogHandler):
def emit(self, record: logging.LogRecord) -> None:
try:
ts = datetime.fromtimestamp(record.created).isoformat()
if isinstance(record.msg, ContentPublishEvent):
console_message = (
f"\n{'-'*75} \n"
f"\033[91m[{ts}], {record.msg.agent_message.source}:\033[0m\n"
f"\n{self.serialize_content(record.msg.agent_message.content)}"
)
sys.stdout.write(console_message)
sys.stdout.flush()
except Exception:
self.handleError(record)
class FileLogHandler(BaseLogHandler):
def __init__(self, filename: str) -> None:
super().__init__()
self.filename = filename
self.file_handler = logging.FileHandler(filename)
def emit(self, record: logging.LogRecord) -> None:
try:
ts = datetime.fromtimestamp(record.created).isoformat()
if isinstance(record.msg, ContentPublishEvent):
log_entry = json.dumps(
{
"timestamp": ts,
"source": record.msg.agent_message.source,
"message": self.serialize_content(record.msg.agent_message.content),
"type": "OrchestrationEvent",
},
default=self.json_serializer,
)
file_record = logging.LogRecord(
name=record.name,
level=record.levelno,
pathname=record.pathname,
lineno=record.lineno,
msg=log_entry,
args=(),
exc_info=record.exc_info,
)
self.file_handler.emit(file_record)
except Exception:
self.handleError(record)
def close(self) -> None:
self.file_handler.close()
super().close()

View File

@ -1,7 +1,16 @@
from dataclasses import dataclass
import logging
from typing import List, Protocol
from autogen_agentchat.agents._base_chat_agent import ChatMessage
from autogen_core.application.logging import EVENT_LOGGER_NAME
from ..logging import ConsoleLogHandler, EVENT_LOGGER_NAME
logger = logging.getLogger(EVENT_LOGGER_NAME)
logger.setLevel(logging.INFO)
console_handler = ConsoleLogHandler()
logger.addHandler(console_handler)
@dataclass

View File

@ -1,7 +1,8 @@
import logging
import asyncio
import sys
from typing import List
from autogen_core.base import AgentId, AgentType, MessageContext
from autogen_core.components import DefaultTopicId, event
from autogen_core.components.models import FunctionExecutionResult
@ -10,6 +11,7 @@ from autogen_core.components.tool_agent import ToolException
from ...agents import BaseChatAgent, MultiModalMessage, StopMessage, TextMessage, ToolCallMessage, ToolCallResultMessage
from ._events import ContentPublishEvent, ContentRequestEvent
from ._sequential_routed_agent import SequentialRoutedAgent
from ...logging import EVENT_LOGGER_NAME
class BaseChatAgentContainer(SequentialRoutedAgent):
@ -29,6 +31,7 @@ class BaseChatAgentContainer(SequentialRoutedAgent):
self._agent = agent
self._message_buffer: List[TextMessage | MultiModalMessage | StopMessage] = []
self._tool_agent_id = AgentId(type=tool_agent_type, key=self.id.key)
self._logger = self.logger = logging.getLogger(EVENT_LOGGER_NAME)
@event
async def handle_content_publish(self, message: ContentPublishEvent, ctx: MessageContext) -> None:
@ -48,9 +51,8 @@ class BaseChatAgentContainer(SequentialRoutedAgent):
# Handle tool calls.
while isinstance(response, ToolCallMessage):
# TODO: use logging instead of print
sys.stdout.write(f"{'-'*80}\n{self._agent.name}:\n{response.content}\n")
# Execute functions called by the model by sending messages to tool agent.
self._logger.info(ContentPublishEvent(agent_message=response))
results: List[FunctionExecutionResult | BaseException] = await asyncio.gather(
*[
self.send_message(
@ -73,8 +75,7 @@ class BaseChatAgentContainer(SequentialRoutedAgent):
# Create a new tool call result message.
feedback = ToolCallResultMessage(content=function_results, source=self._tool_agent_id.type)
# TODO: use logging instead of print
sys.stdout.write(f"{'-'*80}\n{self._tool_agent_id.type}:\n{feedback.content}\n")
# Forward the feedback to the agent.
self._logger.info(ContentPublishEvent(agent_message=feedback, source=self._tool_agent_id.type))
response = await self._agent.on_messages([feedback], ctx.cancellation_token)
# Publish the response.

View File

@ -1,12 +1,13 @@
import sys
import logging
from typing import List
from autogen_core.base import MessageContext, TopicId
from autogen_core.components import event
from ...agents import MultiModalMessage, StopMessage, TextMessage
from ...agents import StopMessage, TextMessage, ChatMessage
from ._events import ContentPublishEvent, ContentRequestEvent
from ._sequential_routed_agent import SequentialRoutedAgent
from ...logging import EVENT_LOGGER_NAME
class BaseGroupChatManager(SequentialRoutedAgent):
@ -48,7 +49,8 @@ class BaseGroupChatManager(SequentialRoutedAgent):
raise ValueError("The group topic type must not be the same as the parent topic type.")
self._participant_topic_types = participant_topic_types
self._participant_descriptions = participant_descriptions
self._message_thread: List[TextMessage | MultiModalMessage | StopMessage] = []
self._message_thread: List[ChatMessage] = []
self._logger = self.logger = logging.getLogger(EVENT_LOGGER_NAME + ".agentchatchat")
@event
async def handle_content_publish(self, message: ContentPublishEvent, ctx: MessageContext) -> None:
@ -62,7 +64,8 @@ class BaseGroupChatManager(SequentialRoutedAgent):
group_chat_topic_id = TopicId(type=self._group_topic_type, source=ctx.topic_id.source)
# TODO: use something else other than print.
sys.stdout.write(f"{'-'*80}\n{message.agent_message.source}:\n{message.agent_message.content}\n")
self._logger.info(ContentPublishEvent(agent_message=message.agent_message))
# Process event from parent.
if ctx.topic_id.type == self._parent_topic_type:
@ -105,7 +108,7 @@ class BaseGroupChatManager(SequentialRoutedAgent):
participant_topic_id = TopicId(type=speaker_topic_type, source=ctx.topic_id.source)
await self.publish_message(ContentRequestEvent(), topic_id=participant_topic_id)
async def select_speaker(self, thread: List[TextMessage | MultiModalMessage | StopMessage]) -> str:
async def select_speaker(self, thread: List[ChatMessage]) -> str:
"""Select a speaker from the participants and return the
topic type of the selected speaker."""
raise NotImplementedError("Method not implemented")

View File

@ -1,6 +1,7 @@
from typing import Optional
from pydantic import BaseModel
from ...agents import MultiModalMessage, StopMessage, TextMessage
from ...agents import ChatMessage
class ContentPublishEvent(BaseModel):
@ -9,8 +10,9 @@ class ContentPublishEvent(BaseModel):
content of the event.
"""
agent_message: TextMessage | MultiModalMessage | StopMessage
agent_message: ChatMessage
"""The message published by the agent."""
source: Optional[str] = None
class ContentRequestEvent(BaseModel):

View File

@ -8,6 +8,7 @@ from autogen_core.components import ClosureAgent, TypeSubscription
from autogen_core.components.tool_agent import ToolAgent
from autogen_core.components.tools import Tool
from ...agents import BaseChatAgent, TextMessage
from .._base_team import BaseTeam, TeamRunResult
from ._base_chat_agent_container import BaseChatAgentContainer

View File

@ -1,6 +1,6 @@
from typing import List
from ...agents import MultiModalMessage, StopMessage, TextMessage
from ...agents import ChatMessage
from ._base_group_chat_manager import BaseGroupChatManager
@ -22,7 +22,7 @@ class RoundRobinGroupChatManager(BaseGroupChatManager):
)
self._next_speaker_index = 0
async def select_speaker(self, thread: List[TextMessage | MultiModalMessage | StopMessage]) -> str:
async def select_speaker(self, thread: List[ChatMessage]) -> str:
"""Select a speaker from the participants in a round-robin fashion."""
current_speaker_index = self._next_speaker_index
self._next_speaker_index = (current_speaker_index + 1) % len(self._participant_topic_types)