Skip to content

Commit

Permalink
Fix bug in logger.chat; Add speak interface within agent;
Browse files Browse the repository at this point in the history
  • Loading branch information
DavdGao committed Feb 5, 2024
1 parent 70f6d8c commit 7a0642f
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 66 deletions.
6 changes: 6 additions & 0 deletions src/agentscope/agents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ def __call__(self, *args: Any, **kwargs: Any) -> dict:

return res

def speak(
self,
content: Union[str, dict]) -> None:
"""Speak out the content generated by the agent."""
logger.chat(content)

def observe(self, x: Union[dict, Sequence[dict]]) -> None:
"""Observe the input, store it in memory without response to it.
Expand Down
6 changes: 4 additions & 2 deletions src/agentscope/agents/dialog_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ def reply(self, x: dict = None) -> dict:
response = self.model(prompt)
msg = Msg(self.name, response)

# logging and record the message in memory
logger.chat(msg)
# Print/speak the message in this agent's voice
self.speak(msg)

# Record the message in memory
self.memory.add(msg)

return msg
4 changes: 2 additions & 2 deletions src/agentscope/agents/dict_dialog_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ def reply(self, x: dict = None) -> dict:
else:
msg = Msg(self.name, response)

# logging the message
logger.chat(msg)
# Print/speak the message in this agent's voice
self.speak(msg)

# record to memory
self.memory.add(msg)
Expand Down
103 changes: 41 additions & 62 deletions src/agentscope/utils/logging_utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
"""Logging utilities."""
import json
import os
import sys
from typing import Optional, Literal, Union, Any

from loguru import logger

from agentscope.constants import MSG_TOKEN

LOG_LEVEL = Literal[
"TRACE",
"DEBUG",
Expand All @@ -18,6 +17,9 @@
"CRITICAL",
]

LEVEL_CHAT_LOG = "CHAT_LOG"
LEVEL_CHAT_SAVE = "CHAT_SAVE"


class _Stream:
"""Redirect stderr to logging"""
Expand Down Expand Up @@ -85,72 +87,40 @@ def _chat(message: Union[str, dict], *args: Any, **kwargs: Any) -> None:
"content" keys, and the message will be logged as "<name/role>:
<content>".
"""
# Save message into file
logger.log(LEVEL_CHAT_SAVE, json.dumps(message), *args, **kwargs)

# Print message in terminal with specific format
if isinstance(message, dict):
contain_name_or_role = "name" in message or "role" in message
contain_content = "content" in message
contain_url = "url" in message

# print content if contain name or role and contain content
if contain_name_or_role and contain_content:
if contain_name_or_role:
speaker = message.get("name", None) or message.get("role", None)
content = message["content"]
(m1, m2) = _get_speaker_color(speaker)
logger.log(
"CHAT",
f"{m1}<b>{speaker}</b>{m2}: {content}".replace(
"{",
"{{",
).replace("}", "}}"),
*args,
**kwargs,
)

print_str = []
if contain_content:
print_str.append(f"{m1}<b>{speaker}</b>{m2}: {message['content']}")

if contain_url:
# print url if contain name or role and contain url
url = message["url"]
(m1, m2) = _get_speaker_color(speaker)
# print url one by one if url is a list
if isinstance(url, list):
for each_url in url:
logger.log(
"CHAT",
f"{m1}<b>{speaker}</b>{m2}: {each_url}",
*args,
**kwargs,
)
else:
logger.log(
"CHAT",
f"{m1}<b>{speaker}</b>{m2}: {url}",
*args,
**kwargs,
)

# print raw message if not contain name
if not contain_name_or_role or not contain_content:
logger.log("CHAT", str(message), *args, **kwargs)
else:
# print other types of message directly
logger.log("CHAT", message, *args, **kwargs)
print_str.append(f"{m1}<b>{speaker}</b>{m2}: {message['url']}")

if len(print_str) > 0:
print_str = "\n".join(print_str).replace("{", "{{").replace("}", "}}")
logger.log(LEVEL_CHAT_LOG, print_str, *args, **kwargs)
return

def _level_format(record: dict) -> str:
"""Format the log record."""
if record["level"].name == "CHAT":
return record["message"] + "\n"
else:
return (
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{"
"level: <8}</level> | <cyan>{name}</cyan>:<cyan>{"
"function}</cyan>:<cyan>{line}</cyan> - <level>{"
"message}</level>\n"
)
message = str(message).replace("{", "{{").replace("}", "}}")
logger.log(LEVEL_CHAT_LOG, message, *args, **kwargs)


def _level_format_with_special_tokens(record: dict) -> str:
def _level_format(record: dict) -> str:
"""Format the log record."""
if record["level"].name == "CHAT":
return MSG_TOKEN + record["message"] + MSG_TOKEN + "\n"
if record["level"].name == LEVEL_CHAT_LOG:
return record["message"] + "\n"
else:
return (
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{"
Expand Down Expand Up @@ -182,33 +152,42 @@ def setup_logger(
sys.stderr = _Stream()

# add chat function for logger
logger.level("CHAT", no=21, color="<yellow>")
logger.level(LEVEL_CHAT_LOG, no=21)
logger.level(LEVEL_CHAT_SAVE, no=0)
logger.chat = _chat

# set logging level
logger.remove()
# standard output for all logging except chat
logger.add(sys.stdout, format=_level_format, enqueue=True, level=level)
logger.add(
sys.stdout,
filter=lambda record: record["level"].name != LEVEL_CHAT_SAVE,
format=_level_format,
enqueue=True,
level=level)

if path_log is not None:
if not os.path.exists(path_log):
os.makedirs(path_log)
path_log_file = os.path.join(path_log, "all.log")
path_log_file_only_chat = os.path.join(
path_log_file = os.path.join(path_log, "logging.log")
path_chat_file = os.path.join(
path_log,
"chat.log",
"logging.chat",
)

# save all logging into file
logger.add(
path_log_file,
format=_level_format_with_special_tokens,
filter=lambda record: record["level"].name != LEVEL_CHAT_SAVE,
format=_level_format,
enqueue=True,
level=level,
)

logger.add(
path_log_file_only_chat,
format=_level_format,
path_chat_file,
filter=lambda record: record["level"].name == LEVEL_CHAT_SAVE,
format="{message}",
enqueue=True,
level="CHAT",
level=LEVEL_CHAT_SAVE
)
56 changes: 56 additions & 0 deletions tests/logger_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
""" Unit test for logger chat"""
import shutil
import time
import unittest

from loguru import logger

from agentscope.utils import setup_logger


class LoggerTest(unittest.TestCase):
"""
Unit test for logger.
"""

def test_logger_chat(self) -> None:
"""Logger chat."""

setup_logger("./runs/", level="INFO")

# str with "\n"
logger.chat("Test\nChat\n\nMessage\n\n")

# dict with "\n"
logger.chat({"name": "Alice", "content": "Hi!\n", "url":
"https://xxx.png"})

# dict without content
logger.chat({"name": "Alice", "url": "https://xxx.png"})

# dict
logger.chat({"abc": 1})

# To avoid that logging is not finished before the file is read
time.sleep(3)

with open("./runs/logging.chat", "r", encoding="utf-8") as file:
lines = file.readlines()

ground_truth = [
'"Test\\nChat\\n\\nMessage\\n\\n"\n',
'{"name": "Alice", "content": "Hi!\\n", "url": "https://xxx.png"}\n',
'{"name": "Alice", "url": "https://xxx.png"}\n',
'{"abc": 1}\n'
]

self.assertListEqual(lines, ground_truth)

def tearDown(self):
"""Tear down for LoggerTest."""
shutil.rmtree("./runs/")


if __name__ == "__main__":
unittest.main()

0 comments on commit 7a0642f

Please sign in to comment.