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 from dataclasses import dataclass
import logging
from typing import List, Protocol from typing import List, Protocol
from autogen_agentchat.agents._base_chat_agent import ChatMessage 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 @dataclass

View File

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

View File

@ -1,12 +1,13 @@
import sys import logging
from typing import List from typing import List
from autogen_core.base import MessageContext, TopicId from autogen_core.base import MessageContext, TopicId
from autogen_core.components import event from autogen_core.components import event
from ...agents import MultiModalMessage, StopMessage, TextMessage from ...agents import StopMessage, TextMessage, ChatMessage
from ._events import ContentPublishEvent, ContentRequestEvent from ._events import ContentPublishEvent, ContentRequestEvent
from ._sequential_routed_agent import SequentialRoutedAgent from ._sequential_routed_agent import SequentialRoutedAgent
from ...logging import EVENT_LOGGER_NAME
class BaseGroupChatManager(SequentialRoutedAgent): 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.") 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_topic_types = participant_topic_types
self._participant_descriptions = participant_descriptions 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 @event
async def handle_content_publish(self, message: ContentPublishEvent, ctx: MessageContext) -> None: 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) group_chat_topic_id = TopicId(type=self._group_topic_type, source=ctx.topic_id.source)
# TODO: use something else other than print. # 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. # Process event from parent.
if ctx.topic_id.type == self._parent_topic_type: 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) participant_topic_id = TopicId(type=speaker_topic_type, source=ctx.topic_id.source)
await self.publish_message(ContentRequestEvent(), topic_id=participant_topic_id) 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 """Select a speaker from the participants and return the
topic type of the selected speaker.""" topic type of the selected speaker."""
raise NotImplementedError("Method not implemented") raise NotImplementedError("Method not implemented")

View File

@ -1,6 +1,7 @@
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
from ...agents import MultiModalMessage, StopMessage, TextMessage from ...agents import ChatMessage
class ContentPublishEvent(BaseModel): class ContentPublishEvent(BaseModel):
@ -9,8 +10,9 @@ class ContentPublishEvent(BaseModel):
content of the event. content of the event.
""" """
agent_message: TextMessage | MultiModalMessage | StopMessage agent_message: ChatMessage
"""The message published by the agent.""" """The message published by the agent."""
source: Optional[str] = None
class ContentRequestEvent(BaseModel): 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.tool_agent import ToolAgent
from autogen_core.components.tools import Tool from autogen_core.components.tools import Tool
from ...agents import BaseChatAgent, TextMessage from ...agents import BaseChatAgent, TextMessage
from .._base_team import BaseTeam, TeamRunResult from .._base_team import BaseTeam, TeamRunResult
from ._base_chat_agent_container import BaseChatAgentContainer from ._base_chat_agent_container import BaseChatAgentContainer

View File

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