oracle-ads 2.12.8__py3-none-any.whl → 2.12.10rc0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. ads/aqua/__init__.py +4 -4
  2. ads/aqua/app.py +12 -2
  3. ads/aqua/common/enums.py +3 -0
  4. ads/aqua/common/utils.py +62 -2
  5. ads/aqua/data.py +2 -19
  6. ads/aqua/evaluation/entities.py +6 -0
  7. ads/aqua/evaluation/evaluation.py +25 -3
  8. ads/aqua/extension/deployment_handler.py +8 -4
  9. ads/aqua/extension/finetune_handler.py +8 -14
  10. ads/aqua/extension/model_handler.py +25 -6
  11. ads/aqua/extension/ui_handler.py +13 -1
  12. ads/aqua/finetuning/constants.py +5 -2
  13. ads/aqua/finetuning/entities.py +70 -17
  14. ads/aqua/finetuning/finetuning.py +79 -82
  15. ads/aqua/model/entities.py +4 -1
  16. ads/aqua/model/model.py +95 -29
  17. ads/aqua/modeldeployment/deployment.py +13 -1
  18. ads/aqua/modeldeployment/entities.py +7 -4
  19. ads/aqua/ui.py +24 -2
  20. ads/common/auth.py +9 -9
  21. ads/llm/autogen/__init__.py +2 -0
  22. ads/llm/autogen/constants.py +15 -0
  23. ads/llm/autogen/reports/__init__.py +2 -0
  24. ads/llm/autogen/reports/base.py +67 -0
  25. ads/llm/autogen/reports/data.py +103 -0
  26. ads/llm/autogen/reports/session.py +526 -0
  27. ads/llm/autogen/reports/templates/chat_box.html +13 -0
  28. ads/llm/autogen/reports/templates/chat_box_lt.html +5 -0
  29. ads/llm/autogen/reports/templates/chat_box_rt.html +6 -0
  30. ads/llm/autogen/reports/utils.py +56 -0
  31. ads/llm/autogen/v02/__init__.py +4 -0
  32. ads/llm/autogen/{client_v02.py → v02/client.py} +23 -10
  33. ads/llm/autogen/v02/log_handlers/__init__.py +2 -0
  34. ads/llm/autogen/v02/log_handlers/oci_file_handler.py +83 -0
  35. ads/llm/autogen/v02/loggers/__init__.py +6 -0
  36. ads/llm/autogen/v02/loggers/metric_logger.py +320 -0
  37. ads/llm/autogen/v02/loggers/session_logger.py +580 -0
  38. ads/llm/autogen/v02/loggers/utils.py +86 -0
  39. ads/llm/autogen/v02/runtime_logging.py +163 -0
  40. ads/llm/guardrails/base.py +6 -5
  41. ads/llm/langchain/plugins/chat_models/oci_data_science.py +46 -20
  42. ads/llm/langchain/plugins/llms/oci_data_science_model_deployment_endpoint.py +38 -11
  43. ads/model/__init__.py +11 -13
  44. ads/model/artifact.py +47 -8
  45. ads/model/extractor/embedding_onnx_extractor.py +80 -0
  46. ads/model/framework/embedding_onnx_model.py +438 -0
  47. ads/model/generic_model.py +26 -24
  48. ads/model/model_metadata.py +8 -7
  49. ads/opctl/config/merger.py +13 -14
  50. ads/opctl/operator/common/operator_config.py +4 -4
  51. ads/opctl/operator/lowcode/common/transformations.py +12 -5
  52. ads/opctl/operator/lowcode/common/utils.py +11 -5
  53. ads/opctl/operator/lowcode/forecast/const.py +3 -0
  54. ads/opctl/operator/lowcode/forecast/model/arima.py +19 -13
  55. ads/opctl/operator/lowcode/forecast/model/automlx.py +129 -36
  56. ads/opctl/operator/lowcode/forecast/model/autots.py +1 -0
  57. ads/opctl/operator/lowcode/forecast/model/base_model.py +58 -17
  58. ads/opctl/operator/lowcode/forecast/model/neuralprophet.py +10 -3
  59. ads/opctl/operator/lowcode/forecast/model/prophet.py +25 -18
  60. ads/opctl/operator/lowcode/forecast/model_evaluator.py +3 -2
  61. ads/opctl/operator/lowcode/forecast/schema.yaml +13 -0
  62. ads/opctl/operator/lowcode/forecast/utils.py +8 -6
  63. ads/telemetry/base.py +18 -11
  64. ads/telemetry/client.py +33 -13
  65. ads/templates/schemas/openapi.json +1740 -0
  66. ads/templates/score_embedding_onnx.jinja2 +202 -0
  67. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10rc0.dist-info}/METADATA +9 -10
  68. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10rc0.dist-info}/RECORD +71 -50
  69. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10rc0.dist-info}/LICENSE.txt +0 -0
  70. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10rc0.dist-info}/WHEEL +0 -0
  71. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10rc0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,526 @@
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
+ """Module for building session report."""
4
+ import copy
5
+ import json
6
+ import logging
7
+ from dataclasses import dataclass
8
+ from typing import List, Optional
9
+
10
+ import fsspec
11
+ import pandas as pd
12
+ import plotly.express as px
13
+ import report_creator as rc
14
+
15
+ from ads.common.auth import default_signer
16
+ from ads.llm.autogen.constants import Events
17
+ from ads.llm.autogen.reports.base import BaseReport
18
+ from ads.llm.autogen.reports.data import (
19
+ AgentData,
20
+ LLMCompletionData,
21
+ LogRecord,
22
+ ToolCallData,
23
+ )
24
+ from ads.llm.autogen.reports.utils import escape_html, get_duration, is_json_string
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ @dataclass
30
+ class AgentInvocation:
31
+ """Represents an agent invocation."""
32
+
33
+ log: LogRecord
34
+ header: str = ""
35
+ description: str = ""
36
+ duration: Optional[float] = None
37
+
38
+
39
+ class SessionReport(BaseReport):
40
+ """Class for building session report from session log file."""
41
+
42
+ def __init__(self, log_file: str, auth: Optional[dict] = None) -> None:
43
+ """Initialize the session report with log file.
44
+ It is assumed that the file contains logs for a single session.
45
+
46
+ Parameters
47
+ ----------
48
+ log_file : str
49
+ Path or URI of the log file.
50
+ auth : dict, optional
51
+ Authentication signer/config for OCI, by default None
52
+ """
53
+ self.log_file: str = log_file
54
+ if self.log_file.startswith("oci://"):
55
+ auth = auth or default_signer()
56
+ with fsspec.open(self.log_file, mode="r", **auth) as f:
57
+ self.log_lines = f.readlines()
58
+ else:
59
+ with open(self.log_file, encoding="utf-8") as f:
60
+ self.log_lines = f.readlines()
61
+ self.logs: List[LogRecord] = self._parse_logs()
62
+
63
+ # Parse logs to get entities for building the report
64
+ # Agents
65
+ self.agents: List[AgentData] = self._parse_agents()
66
+ self.managers: List[AgentData] = self._parse_managers()
67
+ # Events
68
+ self.start_event: LogRecord = self._parse_start_event()
69
+ self.session_id: str = self.start_event.session_id
70
+ self.llm_calls: List[AgentInvocation] = self._parse_llm_calls()
71
+ self.tool_calls: List[AgentInvocation] = self._parse_tool_calls()
72
+ self.invocations: List[AgentInvocation] = self._parse_invocations()
73
+
74
+ self.received_message_logs = self._parse_received_messages()
75
+
76
+ def _parse_logs(self) -> List[LogRecord]:
77
+ """Parses the logs form strings into LogRecord objects."""
78
+ logs = []
79
+ for i, log in enumerate(self.log_lines):
80
+ try:
81
+ logs.append(LogRecord.from_dict(json.loads(log)))
82
+ except Exception as e:
83
+ logger.error(
84
+ "Error when parsing log record at line %s:\n%s", str(i + 1), str(e)
85
+ )
86
+ continue
87
+ # Sort the logs by timestamp
88
+ logs = sorted(logs, key=lambda x: x.timestamp)
89
+ return logs
90
+
91
+ def _parse_agents(self) -> List[AgentData]:
92
+ """Parses the logs to identify unique agents.
93
+ AutoGen may have new_agent multiple times.
94
+ Here we identify the agents by the unique tuple of (name, module, class).
95
+ """
96
+ new_agent_logs = self.filter_by_event(Events.NEW_AGENT)
97
+ agents = {}
98
+ for log in new_agent_logs:
99
+ agent: AgentData = log.data
100
+ agents[(agent.agent_name, agent.agent_module, agent.agent_class)] = agent
101
+ return list(agents.values())
102
+
103
+ def _parse_managers(self) -> List[AgentData]:
104
+ """Parses the logs to get chat managers."""
105
+ managers = []
106
+ for agent in self.agents:
107
+ if agent.is_manager:
108
+ managers.append(agent)
109
+ return managers
110
+
111
+ def _parse_start_event(self) -> LogRecord:
112
+ """Parses the logs to get the first logging_session_start event log."""
113
+ records = self.filter_by_event(event_name=Events.SESSION_START)
114
+ if not records:
115
+ raise ValueError("logging_session_start event is not found in the logs.")
116
+ records = sorted(records, key=lambda x: x.timestamp)
117
+ return records[0]
118
+
119
+ def _parse_llm_calls(self) -> List[AgentInvocation]:
120
+ """Parses the logs to get the LLM calls."""
121
+ records = self.filter_by_event(Events.LLM_CALL)
122
+ invocations = []
123
+ for record in records:
124
+ log_data: LLMCompletionData = record.data
125
+ source_name = record.source_name
126
+ request = log_data.request
127
+ # If there is no request, the log is invalid.
128
+ if not request:
129
+ continue
130
+
131
+ header = f"{source_name} invoking {request.get('model')}"
132
+ if log_data.is_cached:
133
+ header += " (Cached)"
134
+ invocations.append(
135
+ AgentInvocation(
136
+ header=header,
137
+ log=record,
138
+ duration=get_duration(log_data.start_time, log_data.end_time),
139
+ )
140
+ )
141
+ return invocations
142
+
143
+ def _parse_tool_calls(self) -> List[AgentInvocation]:
144
+ """Parses the logs to get the tool calls."""
145
+ records = self.filter_by_event(Events.TOOL_CALL)
146
+ invocations = []
147
+ for record in records:
148
+ log_data: ToolCallData = record.data
149
+ source_name = record.source_name
150
+ invocations.append(
151
+ AgentInvocation(
152
+ log=record,
153
+ header=f"{source_name} invoking {log_data.tool_name}",
154
+ duration=get_duration(log_data.start_time, log_data.end_time),
155
+ )
156
+ )
157
+ return invocations
158
+
159
+ def _parse_invocations(self) -> List[AgentInvocation]:
160
+ """Add numbering to the combined list of LLM and tool calls."""
161
+ invocations = self.llm_calls + self.tool_calls
162
+ invocations = sorted(invocations, key=lambda x: x.log.data.start_time)
163
+ for i, invocation in enumerate(invocations):
164
+ invocation.header = f"{str(i + 1)} {invocation.header}"
165
+ return invocations
166
+
167
+ def _parse_received_messages(self) -> List[LogRecord]:
168
+ """Parses the logs to get the received_message events."""
169
+ managers = [manager.agent_name for manager in self.managers]
170
+ logs = self.filter_by_event(Events.RECEIVED_MESSAGE)
171
+ if not logs:
172
+ return []
173
+ logs = sorted(logs, key=lambda x: x.timestamp)
174
+ logs = [log for log in logs if log.kwargs.get("sender") not in managers]
175
+ return logs
176
+
177
+ def filter_by_event(self, event_name: str) -> List[LogRecord]:
178
+ """Filters the logs by event name.
179
+
180
+ Parameters
181
+ ----------
182
+ event_name : str
183
+ Name of the event.
184
+
185
+ Returns
186
+ -------
187
+ List[LogRecord]
188
+ A list of LogRecord objects for the event.
189
+ """
190
+ filtered_logs = []
191
+ for log in self.logs:
192
+ if log.event_name == event_name:
193
+ filtered_logs.append(log)
194
+ return filtered_logs
195
+
196
+ def _build_flowchart(self):
197
+ """Builds the flowchart of agent chats."""
198
+ senders = []
199
+ for log in self.received_message_logs:
200
+ sender = log.kwargs.get("sender")
201
+ senders.append(sender)
202
+
203
+ diagram_src = "graph LR\n"
204
+ prev_sender = None
205
+ links = []
206
+ # Conversation Flow
207
+ for sender in senders:
208
+ if prev_sender is None:
209
+ link = f"START([START]) --> {sender}"
210
+ else:
211
+ link = f"{prev_sender} --> {sender}"
212
+ if link not in links:
213
+ links.append(link)
214
+ prev_sender = sender
215
+ links.append(f"{prev_sender} --> END([END])")
216
+ # Tool Calls
217
+ for invocation in self.tool_calls:
218
+ tool = invocation.log.data.tool_name
219
+ agent = invocation.log.data.agent_name
220
+ if tool and agent:
221
+ link = f"{agent} <--> {tool}[[{tool}]]"
222
+ if link not in links:
223
+ links.append(link)
224
+
225
+ diagram_src += "\n".join(links)
226
+ return rc.Diagram(src=diagram_src, label="Flowchart")
227
+
228
+ def _build_timeline_tab(self):
229
+ """Builds the plotly timeline chart."""
230
+ if not self.invocations:
231
+ return rc.Text("No LLM or Tool Calls.", label="Timeline")
232
+ invocations = []
233
+ for invocation in self.invocations:
234
+ invocations.append(
235
+ {
236
+ "start_time": invocation.log.data.start_time,
237
+ "end_time": invocation.log.data.end_time,
238
+ "header": invocation.header,
239
+ "duration": invocation.duration,
240
+ }
241
+ )
242
+ df = pd.DataFrame(invocations)
243
+ fig = px.timeline(
244
+ df,
245
+ x_start="start_time",
246
+ x_end="end_time",
247
+ y="header",
248
+ labels={"header": "Invocation"},
249
+ color="duration",
250
+ color_continuous_scale="rdylgn_r",
251
+ height=max(len(df.index) * 50, 500),
252
+ )
253
+ fig.update_layout(showlegend=False)
254
+ fig.update_yaxes(autorange="reversed")
255
+ return rc.Block(
256
+ rc.Widget(fig, label="Timeline"), self._build_flowchart(), label="Timeline"
257
+ )
258
+
259
+ def _format_messages(self, messages: List[dict]):
260
+ """Formats the LLM call messages to be displayed in the report."""
261
+ text = ""
262
+ for message in messages:
263
+ text += f"**{message.get('role')}**:\n{message.get('content')}\n\n"
264
+ return text
265
+
266
+ def _build_llm_call(self, invocation: AgentInvocation):
267
+ """Builds the LLM call details."""
268
+ log_data: LLMCompletionData = invocation.log.data
269
+ request = log_data.request
270
+ response = log_data.response
271
+
272
+ start_date, start_time = self._parse_date_time(log_data.start_time)
273
+
274
+ request_value = f"{str(len(request.get('messages')))} messages"
275
+ tools = request.get("tools", [])
276
+ if tools:
277
+ request_value += f", {str(len(tools))} tools"
278
+
279
+ response_message = response.get("choices")[0].get("message")
280
+ response_text = response_message.get("content") or ""
281
+ tool_calls = response_message.get("tool_calls")
282
+ if tool_calls:
283
+ response_text += "\n\n**Tool Calls**:"
284
+ for tool_call in tool_calls:
285
+ func = tool_call.get("function")
286
+ response_text += f"\n\n`{func.get('name')}(**{func.get('arguments')})`"
287
+
288
+ metrics = [
289
+ rc.Metric(heading="Time", value=start_time, label=start_date),
290
+ rc.Metric(
291
+ heading="Messages",
292
+ value=len(request.get("messages", [])),
293
+ ),
294
+ rc.Metric(heading="Tools", value=len(tools)),
295
+ rc.Metric(heading="Duration", value=invocation.duration, unit="s"),
296
+ rc.Metric(
297
+ heading="Cached",
298
+ value="Yes" if log_data.is_cached else "No",
299
+ ),
300
+ rc.Metric(heading="Cost", value=log_data.cost),
301
+ ]
302
+
303
+ usage = response.get("usage")
304
+ if isinstance(usage, dict):
305
+ for k, v in usage.items():
306
+ if not v:
307
+ continue
308
+ metrics.append(
309
+ rc.Metric(heading=str(k).replace("_", " ").title(), value=v)
310
+ )
311
+
312
+ return rc.Block(
313
+ rc.Block(rc.Group(*metrics, label=invocation.header)),
314
+ rc.Group(
315
+ rc.Block(
316
+ rc.Markdown(
317
+ self._format_messages(request.get("messages")), label="Request"
318
+ ),
319
+ rc.Collapse(
320
+ rc.Json(request),
321
+ label="JSON",
322
+ ),
323
+ ),
324
+ rc.Block(
325
+ rc.Markdown(response_text, label="Response"),
326
+ rc.Collapse(
327
+ rc.Json(response),
328
+ label="JSON",
329
+ ),
330
+ ),
331
+ ),
332
+ )
333
+
334
+ def _build_tool_call(self, invocation: AgentInvocation):
335
+ """Builds the tool call details."""
336
+ log_data: ToolCallData = invocation.log.data
337
+ request = log_data.to_dict()
338
+ response = request.pop("returns", {})
339
+
340
+ start_date, start_time = self._parse_date_time(log_data.start_time)
341
+ tool_call_args = log_data.input_args
342
+ if is_json_string(tool_call_args):
343
+ tool_call_args = self.format_json_string(tool_call_args)
344
+
345
+ if is_json_string(response):
346
+ response = self.format_json_string(response)
347
+
348
+ metrics = [
349
+ rc.Metric(heading="Time", value=start_time, label=start_date),
350
+ rc.Metric(heading="Duration", value=invocation.duration, unit="s"),
351
+ ]
352
+
353
+ return rc.Block(
354
+ rc.Block(rc.Group(*metrics, label=invocation.header)),
355
+ rc.Group(
356
+ rc.Block(
357
+ rc.Markdown(
358
+ (log_data.tool_name or "") + "\n\n" + tool_call_args,
359
+ label="Request",
360
+ ),
361
+ rc.Collapse(
362
+ rc.Json(request),
363
+ label="JSON",
364
+ ),
365
+ ),
366
+ rc.Block(rc.Text("", label="Response"), rc.Markdown(response)),
367
+ ),
368
+ )
369
+
370
+ def _build_invocations_tab(self) -> rc.Block:
371
+ """Builds the invocations tab."""
372
+ blocks = []
373
+ for invocation in self.invocations:
374
+ event_name = invocation.log.event_name
375
+ if event_name == Events.LLM_CALL:
376
+ blocks.append(self._build_llm_call(invocation))
377
+ elif event_name == Events.TOOL_CALL:
378
+ blocks.append(self._build_tool_call(invocation))
379
+ return rc.Block(
380
+ *blocks,
381
+ label="Invocations",
382
+ )
383
+
384
+ def _build_chat_tab(self) -> rc.Block:
385
+ """Builds the chat tab."""
386
+ if not self.received_message_logs:
387
+ return rc.Text("No messages received in this session.", label="Chats")
388
+ # The agent sending the first message will be placed on the right.
389
+ # All other agents will be placed on the left
390
+ host = self.received_message_logs[0].kwargs.get("sender")
391
+ blocks = []
392
+
393
+ for log in self.received_message_logs:
394
+ context = copy.deepcopy(log.kwargs)
395
+ context.update(log.to_dict())
396
+ sender = context.get("sender")
397
+ message = context.get("message", "")
398
+ # Content
399
+ if isinstance(message, dict) and "content" in message:
400
+ content = message.get("content", "")
401
+ if is_json_string(content):
402
+ context["json_content"] = json.dumps(json.loads(content), indent=2)
403
+ context["content"] = content
404
+ else:
405
+ context["content"] = message
406
+ if context["content"] is None:
407
+ context["content"] = ""
408
+ # Tool call
409
+ if isinstance(message, dict) and "tool_calls" in message:
410
+ tool_calls = message.get("tool_calls")
411
+ if tool_calls:
412
+ tool_call_signatures = []
413
+ for tool_call in tool_calls:
414
+ func = tool_call.get("function")
415
+ if not func:
416
+ continue
417
+ tool_call_signatures.append(
418
+ f'{func.get("name")}(**{func.get("arguments", "{}")})'
419
+ )
420
+ context["tool_calls"] = tool_call_signatures
421
+ if sender == host:
422
+ html = self._render_template("chat_box_rt.html", **context)
423
+ else:
424
+ html = self._render_template("chat_box_lt.html", **context)
425
+ blocks.append(rc.Html(html))
426
+
427
+ return rc.Block(
428
+ *blocks,
429
+ label="Chats",
430
+ )
431
+
432
+ def _build_logs_tab(self) -> rc.Block:
433
+ """Builds the logs tab."""
434
+ blocks = []
435
+ for log_line in self.log_lines:
436
+ if is_json_string(log_line):
437
+ log = json.loads(log_line)
438
+ label = log.get(
439
+ "event_name", self._preview_message(log.get("message", ""))
440
+ )
441
+ blocks.append(rc.Collapse(rc.Json(escape_html(log)), label=label))
442
+ else:
443
+ log = log_line
444
+ blocks.append(
445
+ rc.Collapse(rc.Text(log), label=self._preview_message(log_line))
446
+ )
447
+
448
+ return rc.Block(
449
+ *blocks,
450
+ label="Logs",
451
+ )
452
+
453
+ def _build_errors_tab(self) -> Optional[rc.Block]:
454
+ """Builds the error tab to show exception."""
455
+ errors = self.filter_by_event(Events.EXCEPTION)
456
+ if not errors:
457
+ return None
458
+ blocks = []
459
+ for error in errors:
460
+ label = f'{error.kwargs.get("exc_type", "")} - {error.kwargs.get("exc_value", "")}'
461
+ variables: dict = error.kwargs.get("locals", {})
462
+ table = "| Variable | Value |\n|---|---|\n"
463
+ table += "\n".join([f"| {k} | {v} |" for k, v in variables.items()])
464
+ blocks += [
465
+ rc.Unformatted(text=error.kwargs.get("traceback", ""), label=label),
466
+ rc.Markdown(table),
467
+ ]
468
+ return rc.Block(*blocks, label="Error")
469
+
470
+ def build(self, output_file: str):
471
+ """Builds the session report.
472
+
473
+ Parameters
474
+ ----------
475
+ output_file : str
476
+ Local path or OCI object storage URI to save the report HTML file.
477
+ """
478
+
479
+ if not self.managers:
480
+ agent_label = ""
481
+ elif len(self.managers) == 1:
482
+ agent_label = "+1 chat manager"
483
+ else:
484
+ agent_label = f"+{str(len(self.managers))} chat managers"
485
+
486
+ blocks = [
487
+ self._build_timeline_tab(),
488
+ self._build_invocations_tab(),
489
+ self._build_chat_tab(),
490
+ self._build_logs_tab(),
491
+ ]
492
+
493
+ error_block = self._build_errors_tab()
494
+ if error_block:
495
+ blocks.append(error_block)
496
+
497
+ with rc.ReportCreator(
498
+ title=f"AutoGen Session: {self.session_id}",
499
+ description=f"Started at {self.start_event.timestamp}",
500
+ footer="Created with ❤️ by Oracle ADS",
501
+ ) as report:
502
+
503
+ view = rc.Block(
504
+ rc.Group(
505
+ rc.Metric(
506
+ heading="Agents",
507
+ value=len(self.agents) - len(self.managers),
508
+ label=agent_label,
509
+ ),
510
+ rc.Metric(
511
+ heading="Events",
512
+ value=len(self.logs),
513
+ ),
514
+ rc.Metric(
515
+ heading="LLM Calls",
516
+ value=len(self.llm_calls),
517
+ ),
518
+ rc.Metric(
519
+ heading="Tool Calls",
520
+ value=len(self.tool_calls),
521
+ ),
522
+ ),
523
+ rc.Select(blocks=blocks),
524
+ )
525
+
526
+ report.save(view, output_file)
@@ -0,0 +1,13 @@
1
+ <p><strong>{{ sender }}</strong><br /><small><i>to {{ source_name }}</i></small></p>
2
+ <p><small><i>{{ timestamp }}</i></small></p>
3
+ <hr />
4
+ {% if json_content %}
5
+ <pre><code style="background-color: white; text-align: left;">{{ json_content }}</code></pre>
6
+ {% else%}
7
+ <p>{{ content }}</p>
8
+ {% endif %}
9
+ {% if tool_calls %}
10
+ {% for tool_call in tool_calls %}
11
+ <pre><code style="background-color: white; text-align: left;">{{ tool_call }}</code></pre>
12
+ {% endfor %}
13
+ {% endif %}
@@ -0,0 +1,5 @@
1
+ <div style="padding-right: 20%;">
2
+ <div class="metric" style="padding-left: 25px; margin-right: auto; margin-left: 0; width: fit-content;">
3
+ {% include "chat_box.html" %}
4
+ </div>
5
+ </div>
@@ -0,0 +1,6 @@
1
+ <div style="padding-left: 20%;">
2
+ <div class="metric"
3
+ style="padding-right: 25px; margin-right: 0; margin-left: auto; width: fit-content;text-align: right;">
4
+ {% include "chat_box.html" %}
5
+ </div>
6
+ </div>
@@ -0,0 +1,56 @@
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 html
4
+ import json
5
+ from datetime import datetime
6
+
7
+
8
+ def parse_datetime(s):
9
+ return datetime.strptime(s, "%Y-%m-%d %H:%M:%S.%f")
10
+
11
+
12
+ def get_duration(start_time: str, end_time: str) -> float:
13
+ """Gets the duration in seconds between `start_time` and `end_time`.
14
+ Each of the value should be a time in string format of
15
+ `%Y-%m-%d %H:%M:%S.%f`
16
+
17
+ The duration is calculated by parsing the two strings,
18
+ then subtracting the `end_time` from `start_time`.
19
+
20
+ If either `start_time` or `end_time` is not presented,
21
+ 0 will be returned.
22
+
23
+ Parameters
24
+ ----------
25
+ start_time : str
26
+ The start time.
27
+ end_time : str
28
+ The end time.
29
+
30
+ Returns
31
+ -------
32
+ float
33
+ Duration in seconds.
34
+ """
35
+ if not start_time or not end_time:
36
+ return 0
37
+ return (parse_datetime(end_time) - parse_datetime(start_time)).total_seconds()
38
+
39
+
40
+ def is_json_string(s):
41
+ """Checks if a string contains valid JSON."""
42
+ try:
43
+ json.loads(s)
44
+ except Exception:
45
+ return False
46
+ return True
47
+
48
+
49
+ def escape_html(obj):
50
+ if isinstance(obj, dict):
51
+ return {k: escape_html(v) for k, v in obj.items()}
52
+ elif isinstance(obj, list):
53
+ return [escape_html(v) for v in obj]
54
+ elif isinstance(obj, str):
55
+ return html.escape(obj)
56
+ return html.escape(str(obj))
@@ -0,0 +1,4 @@
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
+ from ads.llm.autogen.v02.client import LangChainModelClient, register_custom_client