Skip to content
Merged
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d743c47
Do not pop message from params.
qiuosier Nov 14, 2024
2bd2b65
Add logger for autogen.
qiuosier Nov 15, 2024
1928c21
Add report generation.
qiuosier Nov 15, 2024
ee0b154
Generate report when stop logging.
qiuosier Nov 15, 2024
2888b8d
Add multi-threading support.
qiuosier Nov 18, 2024
838c13b
Format tool call args if it is a valid JSON.
qiuosier Nov 18, 2024
c87704b
Support OCI object storage for logging and reports.
qiuosier Nov 19, 2024
004def7
Refactor logging.
qiuosier Nov 20, 2024
38b02aa
Update logic for creating new logging session.
qiuosier Nov 20, 2024
7683e0d
Support multiple AutoGen loggers.
qiuosier Nov 20, 2024
45caba9
Refactor code into ads.llm.autogen.v02
qiuosier Nov 21, 2024
82b067d
Update logger repr.
qiuosier Nov 21, 2024
209b5fa
Disable chat tab.
qiuosier Nov 21, 2024
c2aa1da
Add Chat tab.
qiuosier Nov 22, 2024
8c608ef
Handle chat rendering error.
qiuosier Nov 25, 2024
cc7f186
Add logs tab.
qiuosier Nov 25, 2024
f35a995
Log library versions.
qiuosier Nov 25, 2024
4c0ca6f
Create report_dir when it does not exist.
qiuosier Nov 25, 2024
ab64b62
Fix error when there is no LLM call.
qiuosier Nov 25, 2024
4328487
Fix error when starting multiple loggers.
qiuosier Dec 2, 2024
cd600a4
Add usage property to client response.
qiuosier Dec 2, 2024
57e60c4
Fix log_new_client() bug in session_logger.
qiuosier Dec 2, 2024
67c59e1
Print error instead raising exception when failed to create report.
qiuosier Dec 3, 2024
2e30362
Update copyright and sort imports.
qiuosier Dec 5, 2024
ee2df37
Add method to get all existing loggers.
qiuosier Dec 5, 2024
2aa919a
Catch logging exception and log traceback.
qiuosier Dec 5, 2024
9d13d8f
Use space to replace empty message for OCI GenAI LLM call.
qiuosier Dec 5, 2024
1430005
Include recipient in chat.
qiuosier Dec 5, 2024
bd239a0
Update session logger serialization.
qiuosier Dec 5, 2024
def985c
Ignore message from chat manager in chat tab.
qiuosier Dec 5, 2024
76a201c
Update Chat box template.
qiuosier Dec 6, 2024
696065d
Update response serialization.
qiuosier Dec 6, 2024
af5de13
Add HTML escape for displaying raw logs.
qiuosier Dec 6, 2024
728eeb5
Update timeline header and show whether LLM call is cached.
qiuosier Dec 6, 2024
9f89d0f
Show more metrics in invocation tab.
qiuosier Dec 6, 2024
9937b32
Count unique agents and chat managers.
qiuosier Dec 8, 2024
b4384e8
Align left within code block.
qiuosier Dec 9, 2024
d9a11fc
Do logging only if logger is started.
qiuosier Dec 9, 2024
20bccba
Remove loggers from LoggerManager once stopped.
qiuosier Dec 9, 2024
ef7876a
Skip logging new clients and new wrappers.
qiuosier Dec 9, 2024
cf1f152
Update Request/Response block in invocations.
qiuosier Dec 9, 2024
014f69f
Update new client logging.
qiuosier Dec 10, 2024
a0ffcd2
Show flow chat with timeline.
qiuosier Dec 10, 2024
a3ff197
Move SessionLogger into ads.llm.autogen.v02.loggers.
qiuosier Dec 12, 2024
3da1519
Show empty message as empty instead of None in chat tab.
qiuosier Dec 16, 2024
9750e06
Update copyrights.
qiuosier Dec 17, 2024
3c16972
Merge remote-tracking branch 'origin/main' into feature/autogen
qiuosier Dec 18, 2024
519508b
Refactor logging and report generation.
qiuosier Dec 19, 2024
faa2ca5
Add context manager for session logger.
qiuosier Dec 19, 2024
d84c178
Fix chat manager parsing.
qiuosier Dec 19, 2024
277e3b2
Log and show exception in report.
qiuosier Dec 19, 2024
fbb4257
Add OCI monitoring logger.
qiuosier Dec 19, 2024
d3ef8e7
Move functions to utils.py
qiuosier Dec 20, 2024
085249c
Update metric logger.
qiuosier Dec 20, 2024
6ddf9bf
Fix error in session logger.
qiuosier Dec 20, 2024
c63e6cf
Make dimensions optional in metric logger.
qiuosier Dec 20, 2024
389193d
Update default settings for metric logger.
qiuosier Dec 20, 2024
d95e4ea
Update docs.
qiuosier Dec 20, 2024
ec6dcdb
Fix typo in docs.
qiuosier Dec 20, 2024
277e3cf
Merge branch 'main' into feature/autogen
qiuosier Jan 6, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ads/llm/autogen/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
15 changes: 15 additions & 0 deletions ads/llm/autogen/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/


class Events:
KEY = "event_name"

EXCEPTION = "exception"
LLM_CALL = "llm_call"
TOOL_CALL = "tool_call"
NEW_AGENT = "new_agent"
NEW_CLIENT = "new_client"
RECEIVED_MESSAGE = "received_message"
SESSION_START = "logging_session_start"
SESSION_STOP = "logging_session_stop"
2 changes: 2 additions & 0 deletions ads/llm/autogen/reports/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
67 changes: 67 additions & 0 deletions ads/llm/autogen/reports/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
import json
import logging
import os

from jinja2 import Environment, FileSystemLoader

logger = logging.getLogger(__name__)


class BaseReport:
"""Base class containing utilities for generating reports."""

@staticmethod
def format_json_string(s) -> str:
"""Formats the JSON string in markdown."""
return f"```json\n{json.dumps(json.loads(s), indent=2)}\n```"

@staticmethod
def _parse_date_time(datetime_string: str):
"""Parses a datetime string in the logs into date and time.
Keeps only the seconds in the time.
"""
date_str, time_str = datetime_string.split(" ", 1)
time_str = time_str.split(".", 1)[0]
return date_str, time_str

@staticmethod
def _preview_message(message: str, max_length=30) -> str:
"""Shows the beginning part of a string message."""
# Return the entire string if it is less than the max_length
if len(message) <= max_length:
return message
# Go backward until we find the first whitespace
idx = 30
while not message[idx].isspace() and idx > 0:
idx -= 1
# If we found a whitespace
if idx > 0:
return message[:idx] + "..."
# If we didn't find a whitespace
return message[:30] + "..."

@classmethod
def _render_template(cls, template_path, **kwargs) -> str:
"""Render Jinja template with kwargs."""
template_dir = os.path.join(os.path.dirname(__file__), "templates")
environment = Environment(
loader=FileSystemLoader(template_dir), autoescape=True
)
template = environment.get_template(template_path)
try:
html = template.render(**kwargs)
except Exception:
logger.error(
"Unable to render template %s with data:\n%s",
template_path,
str(kwargs),
)
return cls._render_template(
template_path=template_path,
sender=kwargs.get("sender", "N/A"),
content="TEMPLATE RENDER ERROR",
timestamp=kwargs.get("timestamp", ""),
)
return html
103 changes: 103 additions & 0 deletions ads/llm/autogen/reports/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env python
# Copyright (c) 2024 Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
"""Contains the data structure for logging and reporting."""
import copy
import json
from dataclasses import asdict, dataclass, field
from typing import Optional, Union

from ads.llm.autogen.constants import Events


@dataclass
class LogData:
"""Base class for the data field of LogRecord."""

def to_dict(self):
"""Convert the log data to dictionary."""
return asdict(self)


@dataclass
class LogRecord:
"""Represents a log record.

The `data` field is for pre-defined structured data, which should be an instance of LogData.
The `kwargs` field is for freeform key value pairs.
"""

session_id: str
thread_id: int
timestamp: str
event_name: str
source_id: Optional[int] = None
source_name: Optional[str] = None
# Structured data for specific type of logs
data: Optional[LogData] = None
# Freeform data
kwargs: dict = field(default_factory=dict)

def to_dict(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it better to use the built in protocol for conversion to dict? __dict__

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the data attribute is another data class, the build in __dict__ does not seem to convert it to a dict, while the asdict() function will convert it to dict recursively.

"""Convert the log record to dictionary."""
return asdict(self)

def to_string(self):
"""Serialize the log record to JSON string."""
return json.dumps(self.to_dict(), default=str)

@classmethod
def from_dict(cls, data: dict) -> "LogRecord":
"""Initializes a LogRecord object from dictionary."""
event_mapping = {
Events.NEW_AGENT: AgentData,
Events.TOOL_CALL: ToolCallData,
Events.LLM_CALL: LLMCompletionData,
}
if Events.KEY not in data:
raise KeyError("event_name not found in data.")

data = copy.deepcopy(data)

event_name = data["event_name"]
if event_name in event_mapping and data.get("data"):
data["data"] = event_mapping[event_name](**data.pop("data"))

return cls(**data)


@dataclass
class AgentData(LogData):
"""Represents agent log Data."""

agent_name: str
agent_class: str
agent_module: Optional[str] = None
is_manager: Optional[bool] = None


@dataclass
class LLMCompletionData(LogData):
"""Represents LLM completion log data."""

invocation_id: str
request: dict
response: dict
start_time: str
end_time: str
cost: Optional[float] = None
is_cached: Optional[bool] = None


@dataclass
class ToolCallData(LogData):
"""Represents tool call log data."""

tool_name: str
start_time: str
end_time: str
agent_name: str
agent_class: str
agent_module: Optional[str] = None
input_args: dict = field(default_factory=dict)
returns: Optional[Union[str, list, dict, tuple]] = None
Loading
Loading