opik 1.9.5__py3-none-any.whl → 1.9.39__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.
- opik/__init__.py +10 -3
- opik/anonymizer/__init__.py +5 -0
- opik/anonymizer/anonymizer.py +12 -0
- opik/anonymizer/factory.py +80 -0
- opik/anonymizer/recursive_anonymizer.py +64 -0
- opik/anonymizer/rules.py +56 -0
- opik/anonymizer/rules_anonymizer.py +35 -0
- opik/api_objects/dataset/rest_operations.py +5 -0
- opik/api_objects/experiment/experiment.py +46 -49
- opik/api_objects/experiment/helpers.py +34 -10
- opik/api_objects/local_recording.py +8 -3
- opik/api_objects/opik_client.py +230 -48
- opik/api_objects/opik_query_language.py +9 -0
- opik/api_objects/prompt/__init__.py +11 -3
- opik/api_objects/prompt/base_prompt.py +69 -0
- opik/api_objects/prompt/base_prompt_template.py +29 -0
- opik/api_objects/prompt/chat/__init__.py +1 -0
- opik/api_objects/prompt/chat/chat_prompt.py +193 -0
- opik/api_objects/prompt/chat/chat_prompt_template.py +350 -0
- opik/api_objects/prompt/{chat_content_renderer_registry.py → chat/content_renderer_registry.py} +37 -35
- opik/api_objects/prompt/client.py +101 -30
- opik/api_objects/prompt/text/__init__.py +1 -0
- opik/api_objects/prompt/text/prompt.py +174 -0
- opik/api_objects/prompt/{prompt_template.py → text/prompt_template.py} +10 -6
- opik/api_objects/prompt/types.py +1 -1
- opik/cli/export.py +6 -2
- opik/cli/usage_report/charts.py +39 -10
- opik/cli/usage_report/cli.py +164 -45
- opik/cli/usage_report/pdf.py +14 -1
- opik/config.py +0 -5
- opik/decorator/base_track_decorator.py +37 -40
- opik/decorator/context_manager/span_context_manager.py +9 -0
- opik/decorator/context_manager/trace_context_manager.py +5 -0
- opik/dict_utils.py +3 -3
- opik/evaluation/__init__.py +13 -2
- opik/evaluation/engine/engine.py +195 -223
- opik/evaluation/engine/helpers.py +8 -7
- opik/evaluation/engine/metrics_evaluator.py +237 -0
- opik/evaluation/evaluation_result.py +35 -1
- opik/evaluation/evaluator.py +318 -30
- opik/evaluation/models/litellm/util.py +78 -6
- opik/evaluation/models/model_capabilities.py +33 -0
- opik/evaluation/report.py +14 -2
- opik/evaluation/rest_operations.py +36 -33
- opik/evaluation/test_case.py +2 -2
- opik/evaluation/types.py +9 -1
- opik/exceptions.py +17 -0
- opik/hooks/__init__.py +17 -1
- opik/hooks/anonymizer_hook.py +36 -0
- opik/id_helpers.py +18 -0
- opik/integrations/adk/helpers.py +16 -7
- opik/integrations/adk/legacy_opik_tracer.py +7 -4
- opik/integrations/adk/opik_tracer.py +3 -1
- opik/integrations/adk/patchers/adk_otel_tracer/opik_adk_otel_tracer.py +7 -3
- opik/integrations/adk/recursive_callback_injector.py +1 -6
- opik/integrations/dspy/callback.py +1 -4
- opik/integrations/haystack/opik_connector.py +2 -2
- opik/integrations/haystack/opik_tracer.py +2 -4
- opik/integrations/langchain/opik_tracer.py +273 -82
- opik/integrations/llama_index/callback.py +110 -108
- opik/integrations/openai/agents/opik_tracing_processor.py +1 -2
- opik/integrations/openai/opik_tracker.py +1 -1
- opik/message_processing/batching/batchers.py +11 -7
- opik/message_processing/encoder_helpers.py +79 -0
- opik/message_processing/messages.py +25 -1
- opik/message_processing/online_message_processor.py +23 -8
- opik/opik_context.py +7 -7
- opik/rest_api/__init__.py +188 -12
- opik/rest_api/client.py +3 -0
- opik/rest_api/dashboards/__init__.py +4 -0
- opik/rest_api/dashboards/client.py +462 -0
- opik/rest_api/dashboards/raw_client.py +648 -0
- opik/rest_api/datasets/client.py +893 -89
- opik/rest_api/datasets/raw_client.py +1328 -87
- opik/rest_api/experiments/client.py +30 -2
- opik/rest_api/experiments/raw_client.py +26 -0
- opik/rest_api/feedback_definitions/types/find_feedback_definitions_request_type.py +1 -1
- opik/rest_api/optimizations/client.py +302 -0
- opik/rest_api/optimizations/raw_client.py +463 -0
- opik/rest_api/optimizations/types/optimization_update_status.py +3 -1
- opik/rest_api/prompts/__init__.py +2 -2
- opik/rest_api/prompts/client.py +34 -4
- opik/rest_api/prompts/raw_client.py +32 -2
- opik/rest_api/prompts/types/__init__.py +3 -1
- opik/rest_api/prompts/types/create_prompt_version_detail_template_structure.py +5 -0
- opik/rest_api/prompts/types/prompt_write_template_structure.py +5 -0
- opik/rest_api/spans/__init__.py +0 -2
- opik/rest_api/spans/client.py +148 -64
- opik/rest_api/spans/raw_client.py +210 -83
- opik/rest_api/spans/types/__init__.py +0 -2
- opik/rest_api/traces/client.py +241 -73
- opik/rest_api/traces/raw_client.py +344 -90
- opik/rest_api/types/__init__.py +200 -15
- opik/rest_api/types/aggregation_data.py +1 -0
- opik/rest_api/types/alert_trigger_config_public_type.py +6 -1
- opik/rest_api/types/alert_trigger_config_type.py +6 -1
- opik/rest_api/types/alert_trigger_config_write_type.py +6 -1
- opik/rest_api/types/automation_rule_evaluator.py +23 -1
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_llm_as_judge_write.py +2 -0
- opik/rest_api/types/{automation_rule_evaluator_object_public.py → automation_rule_evaluator_object_object_public.py} +32 -10
- opik/rest_api/types/automation_rule_evaluator_page_public.py +2 -2
- opik/rest_api/types/automation_rule_evaluator_public.py +23 -1
- opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_public.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_span_llm_as_judge_write.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_llm_as_judge_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_trace_thread_user_defined_metric_python_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update.py +23 -1
- opik/rest_api/types/automation_rule_evaluator_update_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update_span_llm_as_judge.py +22 -0
- opik/rest_api/types/automation_rule_evaluator_update_trace_thread_llm_as_judge.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update_trace_thread_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_update_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_public.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_user_defined_metric_python_write.py +2 -0
- opik/rest_api/types/automation_rule_evaluator_write.py +23 -1
- opik/rest_api/types/boolean_feedback_definition.py +25 -0
- opik/rest_api/types/boolean_feedback_definition_create.py +20 -0
- opik/rest_api/types/boolean_feedback_definition_public.py +25 -0
- opik/rest_api/types/boolean_feedback_definition_update.py +20 -0
- opik/rest_api/types/boolean_feedback_detail.py +29 -0
- opik/rest_api/types/boolean_feedback_detail_create.py +29 -0
- opik/rest_api/types/boolean_feedback_detail_public.py +29 -0
- opik/rest_api/types/boolean_feedback_detail_update.py +29 -0
- opik/rest_api/types/dashboard_page_public.py +24 -0
- opik/rest_api/types/dashboard_public.py +30 -0
- opik/rest_api/types/dataset.py +2 -0
- opik/rest_api/types/dataset_item.py +2 -0
- opik/rest_api/types/dataset_item_compare.py +2 -0
- opik/rest_api/types/dataset_item_filter.py +23 -0
- opik/rest_api/types/dataset_item_filter_operator.py +21 -0
- opik/rest_api/types/dataset_item_page_compare.py +1 -0
- opik/rest_api/types/dataset_item_page_public.py +1 -0
- opik/rest_api/types/dataset_item_public.py +2 -0
- opik/rest_api/types/dataset_item_update.py +39 -0
- opik/rest_api/types/dataset_item_write.py +1 -0
- opik/rest_api/types/dataset_public.py +2 -0
- opik/rest_api/types/dataset_public_status.py +5 -0
- opik/rest_api/types/dataset_status.py +5 -0
- opik/rest_api/types/dataset_version_diff.py +22 -0
- opik/rest_api/types/dataset_version_diff_stats.py +24 -0
- opik/rest_api/types/dataset_version_page_public.py +23 -0
- opik/rest_api/types/dataset_version_public.py +49 -0
- opik/rest_api/types/experiment.py +2 -0
- opik/rest_api/types/experiment_public.py +2 -0
- opik/rest_api/types/experiment_score.py +20 -0
- opik/rest_api/types/experiment_score_public.py +20 -0
- opik/rest_api/types/experiment_score_write.py +20 -0
- opik/rest_api/types/feedback.py +20 -1
- opik/rest_api/types/feedback_create.py +16 -1
- opik/rest_api/types/feedback_object_public.py +22 -1
- opik/rest_api/types/feedback_public.py +20 -1
- opik/rest_api/types/feedback_score_public.py +4 -0
- opik/rest_api/types/feedback_update.py +16 -1
- opik/rest_api/types/image_url.py +20 -0
- opik/rest_api/types/image_url_public.py +20 -0
- opik/rest_api/types/image_url_write.py +20 -0
- opik/rest_api/types/llm_as_judge_message.py +5 -1
- opik/rest_api/types/llm_as_judge_message_content.py +24 -0
- opik/rest_api/types/llm_as_judge_message_content_public.py +24 -0
- opik/rest_api/types/llm_as_judge_message_content_write.py +24 -0
- opik/rest_api/types/llm_as_judge_message_public.py +5 -1
- opik/rest_api/types/llm_as_judge_message_write.py +5 -1
- opik/rest_api/types/llm_as_judge_model_parameters.py +2 -0
- opik/rest_api/types/llm_as_judge_model_parameters_public.py +2 -0
- opik/rest_api/types/llm_as_judge_model_parameters_write.py +2 -0
- opik/rest_api/types/optimization.py +2 -0
- opik/rest_api/types/optimization_public.py +2 -0
- opik/rest_api/types/optimization_public_status.py +3 -1
- opik/rest_api/types/optimization_status.py +3 -1
- opik/rest_api/types/optimization_studio_config.py +27 -0
- opik/rest_api/types/optimization_studio_config_public.py +27 -0
- opik/rest_api/types/optimization_studio_config_write.py +27 -0
- opik/rest_api/types/optimization_studio_log.py +22 -0
- opik/rest_api/types/optimization_write.py +2 -0
- opik/rest_api/types/optimization_write_status.py +3 -1
- opik/rest_api/types/prompt.py +6 -0
- opik/rest_api/types/prompt_detail.py +6 -0
- opik/rest_api/types/prompt_detail_template_structure.py +5 -0
- opik/rest_api/types/prompt_public.py +6 -0
- opik/rest_api/types/prompt_public_template_structure.py +5 -0
- opik/rest_api/types/prompt_template_structure.py +5 -0
- opik/rest_api/types/prompt_version.py +2 -0
- opik/rest_api/types/prompt_version_detail.py +2 -0
- opik/rest_api/types/prompt_version_detail_template_structure.py +5 -0
- opik/rest_api/types/prompt_version_public.py +2 -0
- opik/rest_api/types/prompt_version_public_template_structure.py +5 -0
- opik/rest_api/types/prompt_version_template_structure.py +5 -0
- opik/rest_api/types/score_name.py +1 -0
- opik/rest_api/types/service_toggles_config.py +6 -0
- opik/rest_api/types/span_enrichment_options.py +31 -0
- opik/rest_api/types/span_filter.py +23 -0
- opik/rest_api/types/span_filter_operator.py +21 -0
- opik/rest_api/types/span_filter_write.py +23 -0
- opik/rest_api/types/span_filter_write_operator.py +21 -0
- opik/rest_api/types/span_llm_as_judge_code.py +27 -0
- opik/rest_api/types/span_llm_as_judge_code_public.py +27 -0
- opik/rest_api/types/span_llm_as_judge_code_write.py +27 -0
- opik/rest_api/types/span_update.py +46 -0
- opik/rest_api/types/studio_evaluation.py +20 -0
- opik/rest_api/types/studio_evaluation_public.py +20 -0
- opik/rest_api/types/studio_evaluation_write.py +20 -0
- opik/rest_api/types/studio_llm_model.py +21 -0
- opik/rest_api/types/studio_llm_model_public.py +21 -0
- opik/rest_api/types/studio_llm_model_write.py +21 -0
- opik/rest_api/types/studio_message.py +20 -0
- opik/rest_api/types/studio_message_public.py +20 -0
- opik/rest_api/types/studio_message_write.py +20 -0
- opik/rest_api/types/studio_metric.py +21 -0
- opik/rest_api/types/studio_metric_public.py +21 -0
- opik/rest_api/types/studio_metric_write.py +21 -0
- opik/rest_api/types/studio_optimizer.py +21 -0
- opik/rest_api/types/studio_optimizer_public.py +21 -0
- opik/rest_api/types/studio_optimizer_write.py +21 -0
- opik/rest_api/types/studio_prompt.py +20 -0
- opik/rest_api/types/studio_prompt_public.py +20 -0
- opik/rest_api/types/studio_prompt_write.py +20 -0
- opik/rest_api/types/trace.py +6 -0
- opik/rest_api/types/trace_public.py +6 -0
- opik/rest_api/types/trace_thread_filter_write.py +23 -0
- opik/rest_api/types/trace_thread_filter_write_operator.py +21 -0
- opik/rest_api/types/trace_thread_update.py +19 -0
- opik/rest_api/types/trace_update.py +39 -0
- opik/rest_api/types/value_entry.py +2 -0
- opik/rest_api/types/value_entry_compare.py +2 -0
- opik/rest_api/types/value_entry_experiment_item_bulk_write_view.py +2 -0
- opik/rest_api/types/value_entry_public.py +2 -0
- opik/rest_api/types/video_url.py +19 -0
- opik/rest_api/types/video_url_public.py +19 -0
- opik/rest_api/types/video_url_write.py +19 -0
- opik/synchronization.py +5 -6
- opik/{decorator/tracing_runtime_config.py → tracing_runtime_config.py} +6 -7
- {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/METADATA +5 -4
- {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/RECORD +246 -151
- opik/api_objects/prompt/chat_prompt_template.py +0 -164
- opik/api_objects/prompt/prompt.py +0 -131
- /opik/rest_api/{spans/types → types}/span_update_type.py +0 -0
- {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/WHEEL +0 -0
- {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/entry_points.txt +0 -0
- {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/licenses/LICENSE +0 -0
- {opik-1.9.5.dist-info → opik-1.9.39.dist-info}/top_level.txt +0 -0
opik/cli/usage_report/cli.py
CHANGED
|
@@ -7,11 +7,15 @@ import sys
|
|
|
7
7
|
import traceback
|
|
8
8
|
import webbrowser
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from typing import Optional
|
|
10
|
+
from typing import List, Optional
|
|
11
11
|
|
|
12
12
|
import click
|
|
13
13
|
from rich.console import Console
|
|
14
14
|
|
|
15
|
+
import opik.config as config
|
|
16
|
+
import opik.url_helpers as url_helpers
|
|
17
|
+
import opik.httpx_client as httpx_client
|
|
18
|
+
|
|
15
19
|
from .extraction import extract_project_data
|
|
16
20
|
from .pdf import create_pdf_report
|
|
17
21
|
from .statistics import calculate_statistics
|
|
@@ -20,7 +24,7 @@ console = Console()
|
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
@click.command(name="usage-report")
|
|
23
|
-
@click.argument("
|
|
27
|
+
@click.argument("workspaces", nargs=-1, type=str)
|
|
24
28
|
@click.option(
|
|
25
29
|
"--start-date",
|
|
26
30
|
type=str,
|
|
@@ -50,35 +54,52 @@ console = Console()
|
|
|
50
54
|
is_flag=True,
|
|
51
55
|
help="Automatically open the generated PDF report in the default viewer.",
|
|
52
56
|
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--from-json",
|
|
59
|
+
"from_json",
|
|
60
|
+
is_flag=True,
|
|
61
|
+
help="Load data from existing JSON files instead of extracting from API. "
|
|
62
|
+
"JSON files should match the output pattern (e.g., opik_usage_report.json or opik_usage_report_{workspace}.json).",
|
|
63
|
+
)
|
|
53
64
|
@click.pass_context
|
|
54
65
|
def usage_report(
|
|
55
66
|
ctx: click.Context,
|
|
56
|
-
|
|
67
|
+
workspaces: tuple,
|
|
57
68
|
start_date: Optional[str],
|
|
58
69
|
end_date: Optional[str],
|
|
59
70
|
unit: str,
|
|
60
71
|
output: str,
|
|
61
72
|
open_pdf: bool,
|
|
73
|
+
from_json: bool,
|
|
62
74
|
) -> None:
|
|
63
75
|
"""
|
|
64
|
-
Extract Opik usage data for
|
|
76
|
+
Extract Opik usage data for one or more workspaces.
|
|
65
77
|
|
|
66
|
-
This command extracts project-level metrics from Opik for
|
|
67
|
-
- Loops through all projects in
|
|
78
|
+
This command extracts project-level metrics from Opik for specified workspace(s):
|
|
79
|
+
- Loops through all projects in each workspace
|
|
68
80
|
- Gets trace count, cost, and token count
|
|
69
81
|
- Gets experiment and dataset counts (workspace-level)
|
|
70
82
|
- Aggregates data by the specified time unit (month, week, day, or hour)
|
|
71
83
|
- Saves data to a JSON file
|
|
72
84
|
- Generates a PDF report with charts and statistics
|
|
73
85
|
|
|
74
|
-
|
|
86
|
+
WORKSPACES: Zero or more workspace names to extract data from.
|
|
87
|
+
If no workspaces are provided, all workspaces will be processed.
|
|
75
88
|
|
|
76
89
|
Examples:
|
|
77
90
|
|
|
78
|
-
Extract data with auto-detected date range
|
|
91
|
+
Extract data for a single workspace with auto-detected date range:
|
|
79
92
|
|
|
80
93
|
opik usage-report my-workspace
|
|
81
94
|
|
|
95
|
+
Extract data for multiple workspaces:
|
|
96
|
+
|
|
97
|
+
opik usage-report workspace1 workspace2 workspace3
|
|
98
|
+
|
|
99
|
+
Extract data for all workspaces (no workspace specified):
|
|
100
|
+
|
|
101
|
+
opik usage-report
|
|
102
|
+
|
|
82
103
|
Extract data aggregated by week:
|
|
83
104
|
|
|
84
105
|
opik usage-report my-workspace --unit week
|
|
@@ -90,6 +111,10 @@ def usage_report(
|
|
|
90
111
|
Extract data and automatically open the PDF report:
|
|
91
112
|
|
|
92
113
|
opik usage-report my-workspace --open
|
|
114
|
+
|
|
115
|
+
Generate PDF from existing JSON file (skip data extraction):
|
|
116
|
+
|
|
117
|
+
opik usage-report my-workspace --from-json
|
|
93
118
|
"""
|
|
94
119
|
try:
|
|
95
120
|
# Get API key from context (set by main CLI)
|
|
@@ -104,52 +129,146 @@ def usage_report(
|
|
|
104
129
|
if end_date:
|
|
105
130
|
end_date_obj = datetime.datetime.strptime(end_date, "%Y-%m-%d")
|
|
106
131
|
|
|
107
|
-
|
|
132
|
+
# Determine which workspaces to process
|
|
133
|
+
workspaces_list: List[str] = list(workspaces) if workspaces else []
|
|
108
134
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
# If no workspaces provided, fetch all workspaces
|
|
136
|
+
if not workspaces_list:
|
|
137
|
+
console.print(
|
|
138
|
+
"[blue]No workspaces specified. Fetching all workspaces...[/blue]"
|
|
139
|
+
)
|
|
140
|
+
cfg = config.OpikConfig()
|
|
141
|
+
# Use API key from context if available, otherwise from config
|
|
142
|
+
api_key_to_use = api_key or cfg.api_key
|
|
143
|
+
with httpx_client.get(
|
|
144
|
+
workspace=None, # No workspace needed when fetching workspace list
|
|
145
|
+
api_key=api_key_to_use,
|
|
146
|
+
check_tls_certificate=cfg.check_tls_certificate,
|
|
147
|
+
compress_json_requests=cfg.enable_json_request_compression,
|
|
148
|
+
) as client:
|
|
149
|
+
base_url = url_helpers.get_base_url(cfg.url_override)
|
|
150
|
+
workspace_list_url = url_helpers.get_workspace_list_url(base_url)
|
|
151
|
+
response = client.get(workspace_list_url)
|
|
152
|
+
workspaces_list = response.json().get("workspaceNames", [])
|
|
112
153
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
data["statistics"] = stats
|
|
154
|
+
if not workspaces_list:
|
|
155
|
+
console.print("[yellow]No workspaces found.[/yellow]")
|
|
156
|
+
return
|
|
117
157
|
|
|
118
|
-
|
|
119
|
-
console.print(f"\n[cyan]{'='*60}[/cyan]")
|
|
120
|
-
console.print(f"[blue]Saving data to {output}...[/blue]")
|
|
121
|
-
with open(output, "w") as f:
|
|
122
|
-
json.dump(data, f, indent=2, default=str)
|
|
158
|
+
console.print(f"[green]Found {len(workspaces_list)} workspace(s)[/green]\n")
|
|
123
159
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
160
|
+
# Process each workspace
|
|
161
|
+
for idx, workspace in enumerate(workspaces_list, 1):
|
|
162
|
+
console.print(f"\n[cyan]{'='*60}[/cyan]")
|
|
163
|
+
console.print(
|
|
164
|
+
f"[blue]Processing workspace {idx}/{len(workspaces_list)}: {workspace}[/blue]"
|
|
165
|
+
)
|
|
166
|
+
console.print(f"[cyan]{'='*60}[/cyan]\n")
|
|
127
167
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
output_dir = output_path.parent if output_path.parent != Path(".") else "."
|
|
134
|
-
pdf_filename = create_pdf_report(data, output_dir=str(output_dir))
|
|
135
|
-
console.print(f"[green]PDF report saved to {pdf_filename}[/green]")
|
|
136
|
-
|
|
137
|
-
# Open PDF if --open flag is set
|
|
138
|
-
if open_pdf:
|
|
139
|
-
pdf_path = os.path.abspath(pdf_filename)
|
|
140
|
-
if os.path.exists(pdf_path):
|
|
141
|
-
webbrowser.open(f"file://{pdf_path}")
|
|
142
|
-
console.print("[green]Opened PDF in default viewer[/green]")
|
|
168
|
+
try:
|
|
169
|
+
# Generate output filename for this workspace
|
|
170
|
+
if len(workspaces_list) == 1:
|
|
171
|
+
# Single workspace: use the provided output filename
|
|
172
|
+
workspace_output = output
|
|
143
173
|
else:
|
|
174
|
+
# Multiple workspaces: append workspace name to output filename
|
|
175
|
+
output_path = Path(output)
|
|
176
|
+
workspace_output = str(
|
|
177
|
+
output_path.parent
|
|
178
|
+
/ f"{output_path.stem}_{workspace}{output_path.suffix}"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Load from JSON or extract from API
|
|
182
|
+
if from_json:
|
|
144
183
|
console.print(
|
|
145
|
-
f"[
|
|
184
|
+
f"[green]Loading data from {workspace_output}...[/green]\n"
|
|
146
185
|
)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
186
|
+
if not os.path.exists(workspace_output):
|
|
187
|
+
console.print(
|
|
188
|
+
f"[red]Error: JSON file not found: {workspace_output}[/red]"
|
|
189
|
+
)
|
|
190
|
+
console.print(
|
|
191
|
+
f"[yellow]Expected file: {workspace_output}[/yellow]"
|
|
192
|
+
)
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
with open(workspace_output, "r") as f:
|
|
196
|
+
data = json.load(f)
|
|
197
|
+
|
|
198
|
+
# Verify workspace matches
|
|
199
|
+
if data.get("workspace") != workspace:
|
|
200
|
+
console.print(
|
|
201
|
+
f"[yellow]Warning: JSON file workspace '{data.get('workspace')}' "
|
|
202
|
+
f"does not match expected workspace '{workspace}'[/yellow]"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Calculate statistics if not present in JSON
|
|
206
|
+
if "statistics" not in data:
|
|
207
|
+
console.print("[blue]Calculating summary statistics...[/blue]")
|
|
208
|
+
stats = calculate_statistics(data)
|
|
209
|
+
data["statistics"] = stats
|
|
210
|
+
|
|
211
|
+
console.print(f"[green]Loaded data from {workspace_output}[/green]")
|
|
212
|
+
else:
|
|
213
|
+
console.print("[green]Starting Opik data extraction...[/green]\n")
|
|
214
|
+
|
|
215
|
+
data = extract_project_data(
|
|
216
|
+
workspace, api_key, start_date_obj, end_date_obj, unit
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Calculate and add summary statistics to the data
|
|
220
|
+
console.print("[blue]Calculating summary statistics...[/blue]")
|
|
221
|
+
stats = calculate_statistics(data)
|
|
222
|
+
data["statistics"] = stats
|
|
223
|
+
|
|
224
|
+
# Save to JSON file
|
|
225
|
+
console.print(f"\n[cyan]{'='*60}[/cyan]")
|
|
226
|
+
console.print(f"[blue]Saving data to {workspace_output}...[/blue]")
|
|
227
|
+
with open(workspace_output, "w") as f:
|
|
228
|
+
json.dump(data, f, indent=2, default=str)
|
|
229
|
+
|
|
230
|
+
console.print(
|
|
231
|
+
f"[green]Data extraction complete! Results saved to {workspace_output}[/green]"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Generate PDF report
|
|
235
|
+
console.print(f"\n[cyan]{'='*60}[/cyan]")
|
|
236
|
+
console.print("[blue]Generating PDF report...[/blue]")
|
|
237
|
+
try:
|
|
238
|
+
output_path = Path(workspace_output)
|
|
239
|
+
output_dir = (
|
|
240
|
+
output_path.parent if output_path.parent != Path(".") else "."
|
|
241
|
+
)
|
|
242
|
+
pdf_filename = create_pdf_report(data, output_dir=str(output_dir))
|
|
243
|
+
console.print(f"[green]PDF report saved to {pdf_filename}[/green]")
|
|
244
|
+
|
|
245
|
+
# Open PDF if --open flag is set (only for the last workspace)
|
|
246
|
+
if open_pdf and idx == len(workspaces_list):
|
|
247
|
+
pdf_path = os.path.abspath(pdf_filename)
|
|
248
|
+
if os.path.exists(pdf_path):
|
|
249
|
+
webbrowser.open(f"file://{pdf_path}")
|
|
250
|
+
console.print("[green]Opened PDF in default viewer[/green]")
|
|
251
|
+
else:
|
|
252
|
+
console.print(
|
|
253
|
+
f"[yellow]Warning: PDF file not found: {pdf_path}[/yellow]"
|
|
254
|
+
)
|
|
255
|
+
except Exception as e:
|
|
256
|
+
console.print(
|
|
257
|
+
f"[yellow]Warning: Could not generate PDF report: {e}[/yellow]"
|
|
258
|
+
)
|
|
259
|
+
traceback.print_exc()
|
|
260
|
+
|
|
261
|
+
except Exception as e:
|
|
262
|
+
console.print(f"[red]Error processing workspace {workspace}: {e}[/red]")
|
|
263
|
+
traceback.print_exc()
|
|
264
|
+
# Continue with next workspace instead of exiting
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
console.print(
|
|
268
|
+
f"\n[green]Completed processing {len(workspaces_list)} workspace(s)[/green]"
|
|
269
|
+
)
|
|
152
270
|
|
|
153
271
|
except Exception as e:
|
|
154
272
|
console.print(f"[red]Error: {e}[/red]")
|
|
273
|
+
traceback.print_exc()
|
|
155
274
|
sys.exit(1)
|
opik/cli/usage_report/pdf.py
CHANGED
|
@@ -184,7 +184,20 @@ def create_pdf_report(data: Dict[str, Any], output_dir: str = ".") -> str:
|
|
|
184
184
|
)
|
|
185
185
|
continue
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
# All charts are exactly 14x8 inches (4200x2400 pixels at 300 DPI)
|
|
188
|
+
# Scale to fit page with margins
|
|
189
|
+
# Aspect ratio: 14/8 = 1.75 (always wider than tall)
|
|
190
|
+
max_width = 7.5 * inch # Leave margin
|
|
191
|
+
chart_aspect_ratio = 14.0 / 8.0 # 1.75
|
|
192
|
+
|
|
193
|
+
# Charts are always wider than tall, so always scale by width
|
|
194
|
+
display_width = max_width
|
|
195
|
+
display_height = max_width / chart_aspect_ratio
|
|
196
|
+
|
|
197
|
+
# All charts use the same dimensions, so use fixed scaling
|
|
198
|
+
img = Image(
|
|
199
|
+
chart_path, width=display_width, height=display_height
|
|
200
|
+
)
|
|
188
201
|
story.append(img)
|
|
189
202
|
story.append(Spacer(1, 0.1 * inch))
|
|
190
203
|
story.append(PageBreak())
|
opik/config.py
CHANGED
|
@@ -7,7 +7,6 @@ import pathlib
|
|
|
7
7
|
import urllib.parse
|
|
8
8
|
from typing import Any, Dict, Final, List, Literal, Optional, Tuple, Type, Union
|
|
9
9
|
|
|
10
|
-
import opik.decorator.tracing_runtime_config as tracing_runtime_config
|
|
11
10
|
import pydantic
|
|
12
11
|
import pydantic_settings
|
|
13
12
|
from pydantic_settings import BaseSettings, InitSettingsSource
|
|
@@ -257,10 +256,6 @@ class OpikConfig(pydantic_settings.BaseSettings):
|
|
|
257
256
|
def guardrails_backend_host(self) -> str:
|
|
258
257
|
return url_helpers.get_base_url(self.url_override) + "guardrails/"
|
|
259
258
|
|
|
260
|
-
@property
|
|
261
|
-
def runtime(self) -> tracing_runtime_config.TracingRuntimeConfig:
|
|
262
|
-
return tracing_runtime_config.runtime_cfg
|
|
263
|
-
|
|
264
259
|
@pydantic.model_validator(mode="after")
|
|
265
260
|
def _set_url_override_from_api_key(self) -> "OpikConfig":
|
|
266
261
|
url_was_not_provided = (
|
|
@@ -14,7 +14,7 @@ from typing import (
|
|
|
14
14
|
NamedTuple,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
|
-
from .. import context_storage, logging_messages
|
|
17
|
+
from .. import context_storage, logging_messages, tracing_runtime_config
|
|
18
18
|
from ..api_objects import opik_client, span, trace
|
|
19
19
|
from ..types import DistributedTraceHeadersDict, ErrorInfoDict, SpanType
|
|
20
20
|
from . import (
|
|
@@ -24,7 +24,6 @@ from . import (
|
|
|
24
24
|
inspect_helpers,
|
|
25
25
|
opik_args,
|
|
26
26
|
span_creation_handler,
|
|
27
|
-
tracing_runtime_config,
|
|
28
27
|
)
|
|
29
28
|
|
|
30
29
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -337,25 +336,24 @@ class BaseTrackDecorator(abc.ABC):
|
|
|
337
336
|
)
|
|
338
337
|
error_info = error_info_collector.collect(exception)
|
|
339
338
|
func_exception = exception
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
return result
|
|
339
|
+
|
|
340
|
+
stream_or_stream_manager = self._streams_handler(
|
|
341
|
+
result,
|
|
342
|
+
track_options.capture_output,
|
|
343
|
+
track_options.generations_aggregator,
|
|
344
|
+
)
|
|
345
|
+
if stream_or_stream_manager is not None:
|
|
346
|
+
return stream_or_stream_manager
|
|
347
|
+
|
|
348
|
+
self._after_call(
|
|
349
|
+
output=result,
|
|
350
|
+
error_info=error_info,
|
|
351
|
+
capture_output=track_options.capture_output,
|
|
352
|
+
flush=track_options.flush,
|
|
353
|
+
)
|
|
354
|
+
if func_exception is not None:
|
|
355
|
+
raise func_exception
|
|
356
|
+
return result
|
|
359
357
|
|
|
360
358
|
wrapper.opik_tracked = True # type: ignore
|
|
361
359
|
|
|
@@ -391,25 +389,24 @@ class BaseTrackDecorator(abc.ABC):
|
|
|
391
389
|
)
|
|
392
390
|
error_info = error_info_collector.collect(exception)
|
|
393
391
|
func_exception = exception
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
return result
|
|
392
|
+
|
|
393
|
+
stream_or_stream_manager = self._streams_handler(
|
|
394
|
+
result,
|
|
395
|
+
track_options.capture_output,
|
|
396
|
+
track_options.generations_aggregator,
|
|
397
|
+
)
|
|
398
|
+
if stream_or_stream_manager is not None:
|
|
399
|
+
return stream_or_stream_manager
|
|
400
|
+
|
|
401
|
+
self._after_call(
|
|
402
|
+
output=result,
|
|
403
|
+
error_info=error_info,
|
|
404
|
+
capture_output=track_options.capture_output,
|
|
405
|
+
flush=track_options.flush,
|
|
406
|
+
)
|
|
407
|
+
if func_exception is not None:
|
|
408
|
+
raise func_exception
|
|
409
|
+
return result
|
|
413
410
|
|
|
414
411
|
wrapper.opik_tracked = True # type: ignore
|
|
415
412
|
return wrapper
|
|
@@ -4,6 +4,7 @@ from typing import Optional, Dict, Any, List, Generator
|
|
|
4
4
|
|
|
5
5
|
from opik.api_objects import span, opik_client
|
|
6
6
|
from opik.types import SpanType
|
|
7
|
+
from opik import context_storage
|
|
7
8
|
from .. import arguments_helpers, base_track_decorator, error_info_collector
|
|
8
9
|
|
|
9
10
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -110,5 +111,13 @@ def start_as_current_span(
|
|
|
110
111
|
)
|
|
111
112
|
client.trace(**span_creation_result.trace_data.as_parameters)
|
|
112
113
|
|
|
114
|
+
# Clean up span and trace from context
|
|
115
|
+
opik_context_storage = context_storage.get_current_context_instance()
|
|
116
|
+
opik_context_storage.pop_span_data(ensure_id=span_creation_result.span_data.id)
|
|
117
|
+
if span_creation_result.trace_data is not None:
|
|
118
|
+
opik_context_storage.pop_trace_data(
|
|
119
|
+
ensure_id=span_creation_result.trace_data.id
|
|
120
|
+
)
|
|
121
|
+
|
|
113
122
|
if flush:
|
|
114
123
|
client.flush()
|
|
@@ -4,6 +4,7 @@ from typing import Any, Generator, Optional, Dict, List
|
|
|
4
4
|
|
|
5
5
|
from opik import datetime_helpers
|
|
6
6
|
from opik.api_objects import trace, opik_client, helpers
|
|
7
|
+
from opik import context_storage
|
|
7
8
|
from .. import base_track_decorator, error_info_collector
|
|
8
9
|
|
|
9
10
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -75,5 +76,9 @@ def start_as_current_trace(
|
|
|
75
76
|
client = opik_client.get_client_cached()
|
|
76
77
|
client.trace(**trace_data.init_end_time().as_parameters)
|
|
77
78
|
|
|
79
|
+
# Clean up trace from context
|
|
80
|
+
opik_context_storage = context_storage.get_current_context_instance()
|
|
81
|
+
opik_context_storage.pop_trace_data(ensure_id=trace_data.id)
|
|
82
|
+
|
|
78
83
|
if flush:
|
|
79
84
|
client.flush()
|
opik/dict_utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import logging
|
|
3
|
-
from typing import Any, Dict,
|
|
3
|
+
from typing import Any, Dict, Optional, List, Tuple, TypeVar, Type
|
|
4
4
|
|
|
5
5
|
from . import logging_messages
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@ def flatten_dict(
|
|
|
11
11
|
d: Dict[str, Any], parent_key: str, delim: str = "."
|
|
12
12
|
) -> Dict[str, Any]:
|
|
13
13
|
"""
|
|
14
|
-
|
|
14
|
+
The current implementation does not have max depth restrictions or cyclic references handling!
|
|
15
15
|
"""
|
|
16
16
|
items = [] # type: ignore
|
|
17
17
|
|
|
@@ -60,7 +60,7 @@ def deepmerge(
|
|
|
60
60
|
return merged
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def remove_none_from_dict(original:
|
|
63
|
+
def remove_none_from_dict(original: Dict[str, Optional[Any]]) -> Dict[str, Any]:
|
|
64
64
|
new: Dict[str, Any] = {}
|
|
65
65
|
|
|
66
66
|
for key, value in original.items():
|
opik/evaluation/__init__.py
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
from .evaluator import
|
|
1
|
+
from .evaluator import (
|
|
2
|
+
evaluate,
|
|
3
|
+
evaluate_prompt,
|
|
4
|
+
evaluate_experiment,
|
|
5
|
+
evaluate_on_dict_items,
|
|
6
|
+
)
|
|
2
7
|
from .threads.evaluator import evaluate_threads
|
|
3
8
|
|
|
4
|
-
__all__ = [
|
|
9
|
+
__all__ = [
|
|
10
|
+
"evaluate",
|
|
11
|
+
"evaluate_prompt",
|
|
12
|
+
"evaluate_experiment",
|
|
13
|
+
"evaluate_on_dict_items",
|
|
14
|
+
"evaluate_threads",
|
|
15
|
+
]
|