arize-phoenix 10.0.4__py3-none-any.whl → 12.28.1__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.
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/METADATA +124 -72
- arize_phoenix-12.28.1.dist-info/RECORD +499 -0
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/WHEEL +1 -1
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/IP_NOTICE +1 -1
- phoenix/__generated__/__init__.py +0 -0
- phoenix/__generated__/classification_evaluator_configs/__init__.py +20 -0
- phoenix/__generated__/classification_evaluator_configs/_document_relevance_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_hallucination_classification_evaluator_config.py +17 -0
- phoenix/__generated__/classification_evaluator_configs/_models.py +18 -0
- phoenix/__generated__/classification_evaluator_configs/_tool_selection_classification_evaluator_config.py +17 -0
- phoenix/__init__.py +5 -4
- phoenix/auth.py +39 -2
- phoenix/config.py +1763 -91
- phoenix/datetime_utils.py +120 -2
- phoenix/db/README.md +595 -25
- phoenix/db/bulk_inserter.py +145 -103
- phoenix/db/engines.py +140 -33
- phoenix/db/enums.py +3 -12
- phoenix/db/facilitator.py +302 -35
- phoenix/db/helpers.py +1000 -65
- phoenix/db/iam_auth.py +64 -0
- phoenix/db/insertion/dataset.py +135 -2
- phoenix/db/insertion/document_annotation.py +9 -6
- phoenix/db/insertion/evaluation.py +2 -3
- phoenix/db/insertion/helpers.py +17 -2
- phoenix/db/insertion/session_annotation.py +176 -0
- phoenix/db/insertion/span.py +15 -11
- phoenix/db/insertion/span_annotation.py +3 -4
- phoenix/db/insertion/trace_annotation.py +3 -4
- phoenix/db/insertion/types.py +50 -20
- phoenix/db/migrations/versions/01a8342c9cdf_add_user_id_on_datasets.py +40 -0
- phoenix/db/migrations/versions/0df286449799_add_session_annotations_table.py +105 -0
- phoenix/db/migrations/versions/272b66ff50f8_drop_single_indices.py +119 -0
- phoenix/db/migrations/versions/58228d933c91_dataset_labels.py +67 -0
- phoenix/db/migrations/versions/699f655af132_experiment_tags.py +57 -0
- phoenix/db/migrations/versions/735d3d93c33e_add_composite_indices.py +41 -0
- phoenix/db/migrations/versions/a20694b15f82_cost.py +196 -0
- phoenix/db/migrations/versions/ab513d89518b_add_user_id_on_dataset_versions.py +40 -0
- phoenix/db/migrations/versions/d0690a79ea51_users_on_experiments.py +40 -0
- phoenix/db/migrations/versions/deb2c81c0bb2_dataset_splits.py +139 -0
- phoenix/db/migrations/versions/e76cbd66ffc3_add_experiments_dataset_examples.py +87 -0
- phoenix/db/models.py +669 -56
- phoenix/db/pg_config.py +10 -0
- phoenix/db/types/model_provider.py +4 -0
- phoenix/db/types/token_price_customization.py +29 -0
- phoenix/db/types/trace_retention.py +23 -15
- phoenix/experiments/evaluators/utils.py +3 -3
- phoenix/experiments/functions.py +160 -52
- phoenix/experiments/tracing.py +2 -2
- phoenix/experiments/types.py +1 -1
- phoenix/inferences/inferences.py +1 -2
- phoenix/server/api/auth.py +38 -7
- phoenix/server/api/auth_messages.py +46 -0
- phoenix/server/api/context.py +100 -4
- phoenix/server/api/dataloaders/__init__.py +79 -5
- phoenix/server/api/dataloaders/annotation_configs_by_project.py +31 -0
- phoenix/server/api/dataloaders/annotation_summaries.py +60 -8
- phoenix/server/api/dataloaders/average_experiment_repeated_run_group_latency.py +50 -0
- phoenix/server/api/dataloaders/average_experiment_run_latency.py +17 -24
- phoenix/server/api/dataloaders/cache/two_tier_cache.py +1 -2
- phoenix/server/api/dataloaders/dataset_dataset_splits.py +52 -0
- phoenix/server/api/dataloaders/dataset_example_revisions.py +0 -1
- phoenix/server/api/dataloaders/dataset_example_splits.py +40 -0
- phoenix/server/api/dataloaders/dataset_examples_and_versions_by_experiment_run.py +47 -0
- phoenix/server/api/dataloaders/dataset_labels.py +36 -0
- phoenix/server/api/dataloaders/document_evaluation_summaries.py +2 -2
- phoenix/server/api/dataloaders/document_evaluations.py +6 -9
- phoenix/server/api/dataloaders/experiment_annotation_summaries.py +88 -34
- phoenix/server/api/dataloaders/experiment_dataset_splits.py +43 -0
- phoenix/server/api/dataloaders/experiment_error_rates.py +21 -28
- phoenix/server/api/dataloaders/experiment_repeated_run_group_annotation_summaries.py +77 -0
- phoenix/server/api/dataloaders/experiment_repeated_run_groups.py +57 -0
- phoenix/server/api/dataloaders/experiment_runs_by_experiment_and_example.py +44 -0
- phoenix/server/api/dataloaders/last_used_times_by_generative_model_id.py +35 -0
- phoenix/server/api/dataloaders/latency_ms_quantile.py +40 -8
- phoenix/server/api/dataloaders/record_counts.py +37 -10
- phoenix/server/api/dataloaders/session_annotations_by_session.py +29 -0
- phoenix/server/api/dataloaders/span_cost_by_span.py +24 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_generative_model.py +56 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_project_session.py +57 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_span.py +43 -0
- phoenix/server/api/dataloaders/span_cost_detail_summary_entries_by_trace.py +56 -0
- phoenix/server/api/dataloaders/span_cost_details_by_span_cost.py +27 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment.py +57 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_repeated_run_group.py +64 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_experiment_run.py +58 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_generative_model.py +55 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project.py +152 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_project_session.py +56 -0
- phoenix/server/api/dataloaders/span_cost_summary_by_trace.py +55 -0
- phoenix/server/api/dataloaders/span_costs.py +29 -0
- phoenix/server/api/dataloaders/table_fields.py +2 -2
- phoenix/server/api/dataloaders/token_prices_by_model.py +30 -0
- phoenix/server/api/dataloaders/trace_annotations_by_trace.py +27 -0
- phoenix/server/api/dataloaders/types.py +29 -0
- phoenix/server/api/exceptions.py +11 -1
- phoenix/server/api/helpers/dataset_helpers.py +5 -1
- phoenix/server/api/helpers/playground_clients.py +1243 -292
- phoenix/server/api/helpers/playground_registry.py +2 -2
- phoenix/server/api/helpers/playground_spans.py +8 -4
- phoenix/server/api/helpers/playground_users.py +26 -0
- phoenix/server/api/helpers/prompts/conversions/aws.py +83 -0
- phoenix/server/api/helpers/prompts/conversions/google.py +103 -0
- phoenix/server/api/helpers/prompts/models.py +205 -22
- phoenix/server/api/input_types/{SpanAnnotationFilter.py → AnnotationFilter.py} +22 -14
- phoenix/server/api/input_types/ChatCompletionInput.py +6 -2
- phoenix/server/api/input_types/CreateProjectInput.py +27 -0
- phoenix/server/api/input_types/CreateProjectSessionAnnotationInput.py +37 -0
- phoenix/server/api/input_types/DatasetFilter.py +17 -0
- phoenix/server/api/input_types/ExperimentRunSort.py +237 -0
- phoenix/server/api/input_types/GenerativeCredentialInput.py +9 -0
- phoenix/server/api/input_types/GenerativeModelInput.py +5 -0
- phoenix/server/api/input_types/ProjectSessionSort.py +161 -1
- phoenix/server/api/input_types/PromptFilter.py +14 -0
- phoenix/server/api/input_types/PromptVersionInput.py +52 -1
- phoenix/server/api/input_types/SpanSort.py +44 -7
- phoenix/server/api/input_types/TimeBinConfig.py +23 -0
- phoenix/server/api/input_types/UpdateAnnotationInput.py +34 -0
- phoenix/server/api/input_types/UserRoleInput.py +1 -0
- phoenix/server/api/mutations/__init__.py +10 -0
- phoenix/server/api/mutations/annotation_config_mutations.py +8 -8
- phoenix/server/api/mutations/api_key_mutations.py +19 -23
- phoenix/server/api/mutations/chat_mutations.py +154 -47
- phoenix/server/api/mutations/dataset_label_mutations.py +243 -0
- phoenix/server/api/mutations/dataset_mutations.py +21 -16
- phoenix/server/api/mutations/dataset_split_mutations.py +351 -0
- phoenix/server/api/mutations/experiment_mutations.py +2 -2
- phoenix/server/api/mutations/export_events_mutations.py +3 -3
- phoenix/server/api/mutations/model_mutations.py +210 -0
- phoenix/server/api/mutations/project_mutations.py +49 -10
- phoenix/server/api/mutations/project_session_annotations_mutations.py +158 -0
- phoenix/server/api/mutations/project_trace_retention_policy_mutations.py +8 -4
- phoenix/server/api/mutations/prompt_label_mutations.py +74 -65
- phoenix/server/api/mutations/prompt_mutations.py +65 -129
- phoenix/server/api/mutations/prompt_version_tag_mutations.py +11 -8
- phoenix/server/api/mutations/span_annotations_mutations.py +15 -10
- phoenix/server/api/mutations/trace_annotations_mutations.py +14 -10
- phoenix/server/api/mutations/trace_mutations.py +47 -3
- phoenix/server/api/mutations/user_mutations.py +66 -41
- phoenix/server/api/queries.py +768 -293
- phoenix/server/api/routers/__init__.py +2 -2
- phoenix/server/api/routers/auth.py +154 -88
- phoenix/server/api/routers/ldap.py +229 -0
- phoenix/server/api/routers/oauth2.py +369 -106
- phoenix/server/api/routers/v1/__init__.py +24 -4
- phoenix/server/api/routers/v1/annotation_configs.py +23 -31
- phoenix/server/api/routers/v1/annotations.py +481 -17
- phoenix/server/api/routers/v1/datasets.py +395 -81
- phoenix/server/api/routers/v1/documents.py +142 -0
- phoenix/server/api/routers/v1/evaluations.py +24 -31
- phoenix/server/api/routers/v1/experiment_evaluations.py +19 -8
- phoenix/server/api/routers/v1/experiment_runs.py +337 -59
- phoenix/server/api/routers/v1/experiments.py +479 -48
- phoenix/server/api/routers/v1/models.py +7 -0
- phoenix/server/api/routers/v1/projects.py +18 -49
- phoenix/server/api/routers/v1/prompts.py +54 -40
- phoenix/server/api/routers/v1/sessions.py +108 -0
- phoenix/server/api/routers/v1/spans.py +1091 -81
- phoenix/server/api/routers/v1/traces.py +132 -78
- phoenix/server/api/routers/v1/users.py +389 -0
- phoenix/server/api/routers/v1/utils.py +3 -7
- phoenix/server/api/subscriptions.py +305 -88
- phoenix/server/api/types/Annotation.py +90 -23
- phoenix/server/api/types/ApiKey.py +13 -17
- phoenix/server/api/types/AuthMethod.py +1 -0
- phoenix/server/api/types/ChatCompletionSubscriptionPayload.py +1 -0
- phoenix/server/api/types/CostBreakdown.py +12 -0
- phoenix/server/api/types/Dataset.py +226 -72
- phoenix/server/api/types/DatasetExample.py +88 -18
- phoenix/server/api/types/DatasetExperimentAnnotationSummary.py +10 -0
- phoenix/server/api/types/DatasetLabel.py +57 -0
- phoenix/server/api/types/DatasetSplit.py +98 -0
- phoenix/server/api/types/DatasetVersion.py +49 -4
- phoenix/server/api/types/DocumentAnnotation.py +212 -0
- phoenix/server/api/types/Experiment.py +264 -59
- phoenix/server/api/types/ExperimentComparison.py +5 -10
- phoenix/server/api/types/ExperimentRepeatedRunGroup.py +155 -0
- phoenix/server/api/types/ExperimentRepeatedRunGroupAnnotationSummary.py +9 -0
- phoenix/server/api/types/ExperimentRun.py +169 -65
- phoenix/server/api/types/ExperimentRunAnnotation.py +158 -39
- phoenix/server/api/types/GenerativeModel.py +245 -3
- phoenix/server/api/types/GenerativeProvider.py +70 -11
- phoenix/server/api/types/{Model.py → InferenceModel.py} +1 -1
- phoenix/server/api/types/ModelInterface.py +16 -0
- phoenix/server/api/types/PlaygroundModel.py +20 -0
- phoenix/server/api/types/Project.py +1278 -216
- phoenix/server/api/types/ProjectSession.py +188 -28
- phoenix/server/api/types/ProjectSessionAnnotation.py +187 -0
- phoenix/server/api/types/ProjectTraceRetentionPolicy.py +1 -1
- phoenix/server/api/types/Prompt.py +119 -39
- phoenix/server/api/types/PromptLabel.py +42 -25
- phoenix/server/api/types/PromptVersion.py +11 -8
- phoenix/server/api/types/PromptVersionTag.py +65 -25
- phoenix/server/api/types/ServerStatus.py +6 -0
- phoenix/server/api/types/Span.py +167 -123
- phoenix/server/api/types/SpanAnnotation.py +189 -42
- phoenix/server/api/types/SpanCostDetailSummaryEntry.py +10 -0
- phoenix/server/api/types/SpanCostSummary.py +10 -0
- phoenix/server/api/types/SystemApiKey.py +65 -1
- phoenix/server/api/types/TokenPrice.py +16 -0
- phoenix/server/api/types/TokenUsage.py +3 -3
- phoenix/server/api/types/Trace.py +223 -51
- phoenix/server/api/types/TraceAnnotation.py +149 -50
- phoenix/server/api/types/User.py +137 -32
- phoenix/server/api/types/UserApiKey.py +73 -26
- phoenix/server/api/types/node.py +10 -0
- phoenix/server/api/types/pagination.py +11 -2
- phoenix/server/app.py +290 -45
- phoenix/server/authorization.py +38 -3
- phoenix/server/bearer_auth.py +34 -24
- phoenix/server/cost_tracking/cost_details_calculator.py +196 -0
- phoenix/server/cost_tracking/cost_model_lookup.py +179 -0
- phoenix/server/cost_tracking/helpers.py +68 -0
- phoenix/server/cost_tracking/model_cost_manifest.json +3657 -830
- phoenix/server/cost_tracking/regex_specificity.py +397 -0
- phoenix/server/cost_tracking/token_cost_calculator.py +57 -0
- phoenix/server/daemons/__init__.py +0 -0
- phoenix/server/daemons/db_disk_usage_monitor.py +214 -0
- phoenix/server/daemons/generative_model_store.py +103 -0
- phoenix/server/daemons/span_cost_calculator.py +99 -0
- phoenix/server/dml_event.py +17 -0
- phoenix/server/dml_event_handler.py +5 -0
- phoenix/server/email/sender.py +56 -3
- phoenix/server/email/templates/db_disk_usage_notification.html +19 -0
- phoenix/server/email/types.py +11 -0
- phoenix/server/experiments/__init__.py +0 -0
- phoenix/server/experiments/utils.py +14 -0
- phoenix/server/grpc_server.py +11 -11
- phoenix/server/jwt_store.py +17 -15
- phoenix/server/ldap.py +1449 -0
- phoenix/server/main.py +26 -10
- phoenix/server/oauth2.py +330 -12
- phoenix/server/prometheus.py +66 -6
- phoenix/server/rate_limiters.py +4 -9
- phoenix/server/retention.py +33 -20
- phoenix/server/session_filters.py +49 -0
- phoenix/server/static/.vite/manifest.json +55 -51
- phoenix/server/static/assets/components-BreFUQQa.js +6702 -0
- phoenix/server/static/assets/{index-E0M82BdE.js → index-CTQoemZv.js} +140 -56
- phoenix/server/static/assets/pages-DBE5iYM3.js +9524 -0
- phoenix/server/static/assets/vendor-BGzfc4EU.css +1 -0
- phoenix/server/static/assets/vendor-DCE4v-Ot.js +920 -0
- phoenix/server/static/assets/vendor-codemirror-D5f205eT.js +25 -0
- phoenix/server/static/assets/vendor-recharts-V9cwpXsm.js +37 -0
- phoenix/server/static/assets/vendor-shiki-Do--csgv.js +5 -0
- phoenix/server/static/assets/vendor-three-CmB8bl_y.js +3840 -0
- phoenix/server/templates/index.html +40 -6
- phoenix/server/thread_server.py +1 -2
- phoenix/server/types.py +14 -4
- phoenix/server/utils.py +74 -0
- phoenix/session/client.py +56 -3
- phoenix/session/data_extractor.py +5 -0
- phoenix/session/evaluation.py +14 -5
- phoenix/session/session.py +45 -9
- phoenix/settings.py +5 -0
- phoenix/trace/attributes.py +80 -13
- phoenix/trace/dsl/helpers.py +90 -1
- phoenix/trace/dsl/query.py +8 -6
- phoenix/trace/projects.py +5 -0
- phoenix/utilities/template_formatters.py +1 -1
- phoenix/version.py +1 -1
- arize_phoenix-10.0.4.dist-info/RECORD +0 -405
- phoenix/server/api/types/Evaluation.py +0 -39
- phoenix/server/cost_tracking/cost_lookup.py +0 -255
- phoenix/server/static/assets/components-DULKeDfL.js +0 -4365
- phoenix/server/static/assets/pages-Cl0A-0U2.js +0 -7430
- phoenix/server/static/assets/vendor-WIZid84E.css +0 -1
- phoenix/server/static/assets/vendor-arizeai-Dy-0mSNw.js +0 -649
- phoenix/server/static/assets/vendor-codemirror-DBtifKNr.js +0 -33
- phoenix/server/static/assets/vendor-oB4u9zuV.js +0 -905
- phoenix/server/static/assets/vendor-recharts-D-T4KPz2.js +0 -59
- phoenix/server/static/assets/vendor-shiki-BMn4O_9F.js +0 -5
- phoenix/server/static/assets/vendor-three-C5WAXd5r.js +0 -2998
- phoenix/utilities/deprecation.py +0 -31
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/entry_points.txt +0 -0
- {arize_phoenix-10.0.4.dist-info → arize_phoenix-12.28.1.dist-info}/licenses/LICENSE +0 -0
phoenix/settings.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Optional
|
|
3
4
|
|
|
4
5
|
from phoenix.config import LoggingMode
|
|
5
6
|
|
|
@@ -18,6 +19,10 @@ class _Settings:
|
|
|
18
19
|
db_logging_level: int = field(default=logging.WARNING)
|
|
19
20
|
# By default, migrations are enabled
|
|
20
21
|
disable_migrations: bool = field(default=False)
|
|
22
|
+
# FullStory organization ID for web analytics tracking
|
|
23
|
+
fullstory_org: Optional[str] = field(default=None)
|
|
24
|
+
# Scarf.sh pixel ID for open-source analytics and usage
|
|
25
|
+
scarf_sh_pixel_id: Optional[str] = field(default=None)
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
# Singleton instance of the settings
|
phoenix/trace/attributes.py
CHANGED
|
@@ -1,18 +1,63 @@
|
|
|
1
1
|
"""
|
|
2
|
+
OpenTelemetry Span Attribute Flattening/Unflattening for Phoenix
|
|
3
|
+
|
|
4
|
+
This module handles the conversion between flattened dot-separated key-value pairs
|
|
5
|
+
(as received from OpenTelemetry protobuf) and nested dictionary structures (as used
|
|
6
|
+
internally by Phoenix).
|
|
7
|
+
|
|
8
|
+
Basic Behavior
|
|
9
|
+
--------------
|
|
2
10
|
Span attribute keys have a special relationship with the `.` separator. When
|
|
3
11
|
a span attribute is ingested from protobuf, it's in the form of a key value
|
|
4
|
-
pair such as `("llm.token_count.completion", 123)`.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
pair such as `("llm.token_count.completion", 123)`. We split the key by the `.`
|
|
13
|
+
separator and turn it into a nested dictionary:
|
|
14
|
+
{"llm": {"token_count": {"completion": 123}}}
|
|
15
|
+
|
|
16
|
+
Array Creation Rule
|
|
17
|
+
-------------------
|
|
18
|
+
Numeric keys are treated specially to support array-like structures in OpenTelemetry
|
|
19
|
+
semantic conventions. A numeric key becomes an array index ONLY when:
|
|
20
|
+
1. The numeric key has additional segments after it (e.g., "documents.0.content")
|
|
21
|
+
2. Those segments lead to mappings (dictionaries), not scalar values
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
("documents.0.content", "A"), ("documents.1.content", "B")
|
|
25
|
+
→ {"documents": [{"content": "A"}, {"content": "B"}]} # Array created
|
|
26
|
+
|
|
27
|
+
("tags.0", "python"), ("tags.1", "ai")
|
|
28
|
+
→ {"tags": {"0": "python", "1": "ai"}} # Dict with string keys, NOT array
|
|
29
|
+
|
|
30
|
+
Rationale: In OpenTelemetry semantic conventions, arrays typically contain structured
|
|
31
|
+
objects (like documents or events), not primitive values. This rule ensures that only
|
|
32
|
+
semantically meaningful arrays are created, avoiding ambiguity with numeric string keys.
|
|
33
|
+
|
|
34
|
+
Terminal Value Node Behavior
|
|
35
|
+
----------------------------
|
|
36
|
+
When a path receives an explicit value (typically from pre-nested input), that node
|
|
37
|
+
becomes "terminal" and cannot have children added via flattened keys. Instead,
|
|
38
|
+
attempted extensions become separate dotted keys:
|
|
39
|
+
|
|
40
|
+
("a", {"b": 1}), ("a.c", 2)
|
|
41
|
+
→ {"a": {"b": 1}, "a.c": 2} # "a.c" becomes dotted key, not nested
|
|
42
|
+
|
|
43
|
+
This preserves all data during OpenTelemetry ingestion where:
|
|
44
|
+
- Pre-nested values (dicts/arrays) come from the OTEL data model
|
|
45
|
+
- Flattened keys come from custom instrumentation
|
|
46
|
+
- Both must be preserved to avoid data loss
|
|
47
|
+
|
|
48
|
+
Edge Cases
|
|
49
|
+
----------
|
|
50
|
+
- None values: Skipped entirely during processing
|
|
51
|
+
- Leading zeros: Normalized ("00" → "0", treated as same key)
|
|
52
|
+
- Negative numbers: Treated as string keys, not array indices
|
|
53
|
+
- Empty key segments: Ignored ("a..b" → "a.b")
|
|
54
|
+
- Alphanumeric keys: "0a", "1x" are string keys, not array indices
|
|
55
|
+
- Whitespace: Stripped from key segments (" key " → "key")
|
|
56
|
+
- Empty string key: Valid key, preserved as-is
|
|
57
|
+
- Duplicate keys: Last write wins
|
|
58
|
+
|
|
59
|
+
These edge cases are handled consistently to ensure reliable round-tripping
|
|
60
|
+
between flattened and nested representations.
|
|
16
61
|
"""
|
|
17
62
|
|
|
18
63
|
import inspect
|
|
@@ -107,6 +152,13 @@ def has_mapping(sequence: Iterable[Any]) -> bool:
|
|
|
107
152
|
only contain primitive types, such as strings, integers, etc. Conversely,
|
|
108
153
|
we'll only un-flatten digit sub-keys if it can be interpreted the index of
|
|
109
154
|
an array of dictionaries.
|
|
155
|
+
|
|
156
|
+
This is the key function that implements the "arrays only for mappings" rule.
|
|
157
|
+
In OpenTelemetry semantic conventions, arrays typically contain structured
|
|
158
|
+
objects (e.g., retrieval.documents[0], llm.messages[1]) not primitive arrays
|
|
159
|
+
like ["tag1", "tag2"]. This check ensures semantic correctness during round-
|
|
160
|
+
trip conversions: primitive arrays stay as-is, only structured arrays are
|
|
161
|
+
flattened/unflattened with numeric indices.
|
|
110
162
|
"""
|
|
111
163
|
for item in sequence:
|
|
112
164
|
if isinstance(item, Mapping):
|
|
@@ -189,7 +241,9 @@ class _Trie(defaultdict[Union[str, int], "_Trie"]):
|
|
|
189
241
|
|
|
190
242
|
def set_value(self, value: Any) -> None:
|
|
191
243
|
self.value = value
|
|
192
|
-
# value and indices must not coexist
|
|
244
|
+
# value and indices must not coexist - convert indices to branches
|
|
245
|
+
# This handles the case where a numeric key ends a path (scalar value)
|
|
246
|
+
# vs. continues a path (array index). Example: "a.0" vs "a.0.b"
|
|
193
247
|
self.branches.update(self.indices)
|
|
194
248
|
self.indices.clear()
|
|
195
249
|
|
|
@@ -230,8 +284,14 @@ def _build_trie(
|
|
|
230
284
|
separator,
|
|
231
285
|
prefix_exclusions,
|
|
232
286
|
)
|
|
287
|
+
# Strip whitespace from key segments for cleaner attribute keys
|
|
288
|
+
prefix = prefix.strip()
|
|
233
289
|
if prefix.isdigit():
|
|
234
290
|
index = int(prefix)
|
|
291
|
+
# Key decision: numeric key with suffix → array index (add_index)
|
|
292
|
+
# numeric key without suffix → dict key (add_branch)
|
|
293
|
+
# This ensures arrays only contain mappings, not scalar values,
|
|
294
|
+
# matching OpenTelemetry semantic conventions.
|
|
235
295
|
t = t.add_index(index) if suffix else t.add_branch(index)
|
|
236
296
|
else:
|
|
237
297
|
t = t.add_branch(prefix)
|
|
@@ -253,8 +313,15 @@ def _walk(
|
|
|
253
313
|
yield the prefix and the value. If the Trie node has indices, then yield the
|
|
254
314
|
prefix and a list of dictionaries. If the Trie node has branches, then yield
|
|
255
315
|
the prefix and a dictionary.
|
|
316
|
+
|
|
317
|
+
Conflict Resolution: When a node has both a value and child nodes, both are
|
|
318
|
+
yielded. The value is yielded with its current prefix, and children create
|
|
319
|
+
additional dotted keys. This preserves all data from mixed flattened/nested
|
|
320
|
+
input, avoiding data loss during OpenTelemetry span ingestion.
|
|
256
321
|
"""
|
|
257
322
|
if trie.value is not None:
|
|
323
|
+
# Yield the value first - if there are also branches, those will become
|
|
324
|
+
# separate dotted keys (e.g., "a" and "a.b" coexist)
|
|
258
325
|
yield prefix, trie.value
|
|
259
326
|
elif prefix and trie.indices:
|
|
260
327
|
yield (
|
phoenix/trace/dsl/helpers.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import warnings
|
|
2
3
|
from datetime import datetime
|
|
3
|
-
from typing import Optional, Protocol, Union, cast
|
|
4
|
+
from typing import Any, Iterable, Mapping, Optional, Protocol, Union, cast
|
|
4
5
|
|
|
5
6
|
import pandas as pd
|
|
6
7
|
from openinference.semconv.trace import DocumentAttributes, SpanAttributes
|
|
@@ -13,11 +14,16 @@ DOCUMENT_SCORE = DocumentAttributes.DOCUMENT_SCORE
|
|
|
13
14
|
INPUT_VALUE = SpanAttributes.INPUT_VALUE
|
|
14
15
|
OUTPUT_VALUE = SpanAttributes.OUTPUT_VALUE
|
|
15
16
|
RETRIEVAL_DOCUMENTS = SpanAttributes.RETRIEVAL_DOCUMENTS
|
|
17
|
+
LLM_FUNCTION_CALL = SpanAttributes.LLM_FUNCTION_CALL
|
|
18
|
+
LLM_INPUT_MESSAGES = SpanAttributes.LLM_INPUT_MESSAGES
|
|
19
|
+
LLM_OUTPUT_MESSAGES = SpanAttributes.LLM_OUTPUT_MESSAGES
|
|
20
|
+
|
|
16
21
|
|
|
17
22
|
INPUT = {"input": INPUT_VALUE}
|
|
18
23
|
OUTPUT = {"output": OUTPUT_VALUE}
|
|
19
24
|
IO = {**INPUT, **OUTPUT}
|
|
20
25
|
|
|
26
|
+
|
|
21
27
|
IS_ROOT = "parent_id is None"
|
|
22
28
|
IS_LLM = "span_kind == 'LLM'"
|
|
23
29
|
IS_RETRIEVER = "span_kind == 'RETRIEVER'"
|
|
@@ -125,3 +131,86 @@ def get_qa_with_reference(
|
|
|
125
131
|
df_ref = pd.DataFrame({"reference": ref})
|
|
126
132
|
df_qa_ref = pd.concat([df_qa, df_ref], axis=1, join="inner").set_index("context.span_id")
|
|
127
133
|
return df_qa_ref
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def get_called_tools(
|
|
137
|
+
obj: CanQuerySpans,
|
|
138
|
+
*,
|
|
139
|
+
start_time: Optional[datetime] = None,
|
|
140
|
+
end_time: Optional[datetime] = None,
|
|
141
|
+
project_name: Optional[str] = None,
|
|
142
|
+
timeout: Optional[int] = DEFAULT_TIMEOUT_IN_SECONDS,
|
|
143
|
+
function_name_only: bool = False,
|
|
144
|
+
) -> Optional[pd.DataFrame]:
|
|
145
|
+
"""Retrieve tool calls made by LLM spans within a specified time range.
|
|
146
|
+
|
|
147
|
+
This function queries LLM spans and extracts tool calls from their output messages.
|
|
148
|
+
It can return either just the function names or full function calls with arguments.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
obj: An object that implements the CanQuerySpans protocol for querying spans.
|
|
152
|
+
start_time: Optional start time to filter spans. If None, no start time filter is applied.
|
|
153
|
+
end_time: Optional end time to filter spans. If None, no end time filter is applied.
|
|
154
|
+
project_name: Optional project name to filter spans. If None, uses the environment project name.
|
|
155
|
+
timeout: Optional timeout in seconds for the query. Defaults to DEFAULT_TIMEOUT_IN_SECONDS.
|
|
156
|
+
function_name_only: If True, returns only function names. If False, returns full function calls
|
|
157
|
+
with arguments. Defaults to False.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
A pandas DataFrame containing the tool calls, or None if no spans are found.
|
|
161
|
+
The DataFrame includes columns for input messages, output messages, and tool calls.
|
|
162
|
+
""" # noqa: E501
|
|
163
|
+
project_name = project_name or get_env_project_name()
|
|
164
|
+
|
|
165
|
+
def extract_tool_calls(outputs: list[dict[str, Any]]) -> Optional[list[str]]:
|
|
166
|
+
if not isinstance(outputs, list) or not outputs:
|
|
167
|
+
return None
|
|
168
|
+
ans = []
|
|
169
|
+
if isinstance(message := outputs[0].get("message"), Mapping) and isinstance(
|
|
170
|
+
tool_calls := message.get("tool_calls"), Iterable
|
|
171
|
+
):
|
|
172
|
+
for tool_call in tool_calls:
|
|
173
|
+
if not isinstance(tool_call, Mapping):
|
|
174
|
+
continue
|
|
175
|
+
if not isinstance(tc := tool_call.get("tool_call"), Mapping):
|
|
176
|
+
continue
|
|
177
|
+
if not isinstance(function := tc.get("function"), Mapping):
|
|
178
|
+
continue
|
|
179
|
+
if not isinstance(name := function.get("name"), str):
|
|
180
|
+
continue
|
|
181
|
+
if function_name_only:
|
|
182
|
+
ans.append(name)
|
|
183
|
+
continue
|
|
184
|
+
kwargs = {}
|
|
185
|
+
if isinstance(arguments := function.get("arguments"), str):
|
|
186
|
+
try:
|
|
187
|
+
kwargs = json.loads(arguments)
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
kwargs_str = "" if not kwargs else ", ".join(f"{k}={v}" for k, v in kwargs.items())
|
|
191
|
+
ans.append(f"{name}({kwargs_str})")
|
|
192
|
+
return ans or None
|
|
193
|
+
|
|
194
|
+
df_qa = cast(
|
|
195
|
+
pd.DataFrame,
|
|
196
|
+
obj.query_spans(
|
|
197
|
+
SpanQuery()
|
|
198
|
+
.where(IS_LLM)
|
|
199
|
+
.select(
|
|
200
|
+
input=LLM_INPUT_MESSAGES,
|
|
201
|
+
output=LLM_OUTPUT_MESSAGES,
|
|
202
|
+
),
|
|
203
|
+
start_time=start_time,
|
|
204
|
+
end_time=end_time,
|
|
205
|
+
project_name=project_name,
|
|
206
|
+
timeout=timeout,
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if df_qa is None:
|
|
211
|
+
print("No spans found.")
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
df_qa["tool_call"] = df_qa["output"].apply(extract_tool_calls)
|
|
215
|
+
|
|
216
|
+
return df_qa
|
phoenix/trace/dsl/query.py
CHANGED
|
@@ -456,16 +456,16 @@ class SpanQuery(_HasTmpSuffix):
|
|
|
456
456
|
return replace(self, _filter=_filter)
|
|
457
457
|
|
|
458
458
|
def explode(self, key: str, **kwargs: str) -> "SpanQuery":
|
|
459
|
-
assert (
|
|
460
|
-
|
|
461
|
-
)
|
|
459
|
+
assert isinstance(key, str) and key, (
|
|
460
|
+
"The field name for explosion must be a non-empty string."
|
|
461
|
+
)
|
|
462
462
|
_explode = Explosion(key=key, kwargs=kwargs, primary_index_key=self._index.key)
|
|
463
463
|
return replace(self, _explode=_explode)
|
|
464
464
|
|
|
465
465
|
def concat(self, key: str, **kwargs: str) -> "SpanQuery":
|
|
466
|
-
assert (
|
|
467
|
-
|
|
468
|
-
)
|
|
466
|
+
assert isinstance(key, str) and key, (
|
|
467
|
+
"The field name for concatenation must be a non-empty string."
|
|
468
|
+
)
|
|
469
469
|
_concat = (
|
|
470
470
|
Concatenation(key=key, kwargs=kwargs, separator=self._concat.separator)
|
|
471
471
|
if self._concat
|
|
@@ -807,6 +807,8 @@ def _get_spans_dataframe(
|
|
|
807
807
|
stmt = stmt.where(start_time <= models.Span.start_time)
|
|
808
808
|
if end_time:
|
|
809
809
|
stmt = stmt.where(models.Span.start_time < end_time)
|
|
810
|
+
# Default newest-first ordering by start_time, with id as a stable tiebreaker
|
|
811
|
+
stmt = stmt.order_by(models.Span.start_time.desc(), models.Span.id.desc())
|
|
810
812
|
if root_spans_only:
|
|
811
813
|
# A root span is either a span with no parent_id or an orphan span
|
|
812
814
|
# (a span whose parent_id references a span that doesn't exist in the database)
|
phoenix/trace/projects.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Any, Optional
|
|
|
5
5
|
from openinference.semconv.resource import ResourceAttributes
|
|
6
6
|
from opentelemetry.sdk import trace
|
|
7
7
|
from opentelemetry.sdk.resources import Resource
|
|
8
|
+
from typing_extensions import deprecated
|
|
8
9
|
from wrapt import wrap_function_wrapper
|
|
9
10
|
|
|
10
11
|
|
|
@@ -27,6 +28,10 @@ def project_override_wrapper(project_name: str) -> Callable[..., None]:
|
|
|
27
28
|
return wrapper
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
@deprecated(
|
|
32
|
+
"This decorator has been moved to openinference-instrumentation via dangerously_using_project"
|
|
33
|
+
" in version 0.1.38 and will be removed in an upcoming major release"
|
|
34
|
+
)
|
|
30
35
|
class using_project:
|
|
31
36
|
"""
|
|
32
37
|
A context manager that switches the project for all spans created within the context.
|
|
@@ -85,7 +85,7 @@ class MustacheTemplateFormatter(TemplateFormatter):
|
|
|
85
85
|
for variable_name in variable_names:
|
|
86
86
|
template = re.sub(
|
|
87
87
|
pattern=rf"(?<!\\){{{{\s*{variable_name}\s*}}}}",
|
|
88
|
-
repl=variables[variable_name],
|
|
88
|
+
repl=str(variables[variable_name]),
|
|
89
89
|
string=template,
|
|
90
90
|
)
|
|
91
91
|
return template
|
phoenix/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "12.28.1"
|