Skip to content

Commit 2de141a

Browse files
authored
Merge branch 'main' into qq/telemetry
2 parents 3c07c7b + ef20d9a commit 2de141a

File tree

22 files changed

+2141
-11
lines changed

22 files changed

+2141
-11
lines changed

ads/llm/autogen/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/

ads/llm/autogen/constants.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
4+
5+
class Events:
6+
KEY = "event_name"
7+
8+
EXCEPTION = "exception"
9+
LLM_CALL = "llm_call"
10+
TOOL_CALL = "tool_call"
11+
NEW_AGENT = "new_agent"
12+
NEW_CLIENT = "new_client"
13+
RECEIVED_MESSAGE = "received_message"
14+
SESSION_START = "logging_session_start"
15+
SESSION_STOP = "logging_session_stop"

ads/llm/autogen/reports/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/

ads/llm/autogen/reports/base.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
import json
4+
import logging
5+
import os
6+
7+
from jinja2 import Environment, FileSystemLoader
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class BaseReport:
13+
"""Base class containing utilities for generating reports."""
14+
15+
@staticmethod
16+
def format_json_string(s) -> str:
17+
"""Formats the JSON string in markdown."""
18+
return f"```json\n{json.dumps(json.loads(s), indent=2)}\n```"
19+
20+
@staticmethod
21+
def _parse_date_time(datetime_string: str):
22+
"""Parses a datetime string in the logs into date and time.
23+
Keeps only the seconds in the time.
24+
"""
25+
date_str, time_str = datetime_string.split(" ", 1)
26+
time_str = time_str.split(".", 1)[0]
27+
return date_str, time_str
28+
29+
@staticmethod
30+
def _preview_message(message: str, max_length=30) -> str:
31+
"""Shows the beginning part of a string message."""
32+
# Return the entire string if it is less than the max_length
33+
if len(message) <= max_length:
34+
return message
35+
# Go backward until we find the first whitespace
36+
idx = 30
37+
while not message[idx].isspace() and idx > 0:
38+
idx -= 1
39+
# If we found a whitespace
40+
if idx > 0:
41+
return message[:idx] + "..."
42+
# If we didn't find a whitespace
43+
return message[:30] + "..."
44+
45+
@classmethod
46+
def _render_template(cls, template_path, **kwargs) -> str:
47+
"""Render Jinja template with kwargs."""
48+
template_dir = os.path.join(os.path.dirname(__file__), "templates")
49+
environment = Environment(
50+
loader=FileSystemLoader(template_dir), autoescape=True
51+
)
52+
template = environment.get_template(template_path)
53+
try:
54+
html = template.render(**kwargs)
55+
except Exception:
56+
logger.error(
57+
"Unable to render template %s with data:\n%s",
58+
template_path,
59+
str(kwargs),
60+
)
61+
return cls._render_template(
62+
template_path=template_path,
63+
sender=kwargs.get("sender", "N/A"),
64+
content="TEMPLATE RENDER ERROR",
65+
timestamp=kwargs.get("timestamp", ""),
66+
)
67+
return html

ads/llm/autogen/reports/data.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/usr/bin/env python
2+
# Copyright (c) 2024 Oracle and/or its affiliates.
3+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
4+
"""Contains the data structure for logging and reporting."""
5+
import copy
6+
import json
7+
from dataclasses import asdict, dataclass, field
8+
from typing import Optional, Union
9+
10+
from ads.llm.autogen.constants import Events
11+
12+
13+
@dataclass
14+
class LogData:
15+
"""Base class for the data field of LogRecord."""
16+
17+
def to_dict(self):
18+
"""Convert the log data to dictionary."""
19+
return asdict(self)
20+
21+
22+
@dataclass
23+
class LogRecord:
24+
"""Represents a log record.
25+
26+
The `data` field is for pre-defined structured data, which should be an instance of LogData.
27+
The `kwargs` field is for freeform key value pairs.
28+
"""
29+
30+
session_id: str
31+
thread_id: int
32+
timestamp: str
33+
event_name: str
34+
source_id: Optional[int] = None
35+
source_name: Optional[str] = None
36+
# Structured data for specific type of logs
37+
data: Optional[LogData] = None
38+
# Freeform data
39+
kwargs: dict = field(default_factory=dict)
40+
41+
def to_dict(self):
42+
"""Convert the log record to dictionary."""
43+
return asdict(self)
44+
45+
def to_string(self):
46+
"""Serialize the log record to JSON string."""
47+
return json.dumps(self.to_dict(), default=str)
48+
49+
@classmethod
50+
def from_dict(cls, data: dict) -> "LogRecord":
51+
"""Initializes a LogRecord object from dictionary."""
52+
event_mapping = {
53+
Events.NEW_AGENT: AgentData,
54+
Events.TOOL_CALL: ToolCallData,
55+
Events.LLM_CALL: LLMCompletionData,
56+
}
57+
if Events.KEY not in data:
58+
raise KeyError("event_name not found in data.")
59+
60+
data = copy.deepcopy(data)
61+
62+
event_name = data["event_name"]
63+
if event_name in event_mapping and data.get("data"):
64+
data["data"] = event_mapping[event_name](**data.pop("data"))
65+
66+
return cls(**data)
67+
68+
69+
@dataclass
70+
class AgentData(LogData):
71+
"""Represents agent log Data."""
72+
73+
agent_name: str
74+
agent_class: str
75+
agent_module: Optional[str] = None
76+
is_manager: Optional[bool] = None
77+
78+
79+
@dataclass
80+
class LLMCompletionData(LogData):
81+
"""Represents LLM completion log data."""
82+
83+
invocation_id: str
84+
request: dict
85+
response: dict
86+
start_time: str
87+
end_time: str
88+
cost: Optional[float] = None
89+
is_cached: Optional[bool] = None
90+
91+
92+
@dataclass
93+
class ToolCallData(LogData):
94+
"""Represents tool call log data."""
95+
96+
tool_name: str
97+
start_time: str
98+
end_time: str
99+
agent_name: str
100+
agent_class: str
101+
agent_module: Optional[str] = None
102+
input_args: dict = field(default_factory=dict)
103+
returns: Optional[Union[str, list, dict, tuple]] = None

0 commit comments

Comments
 (0)