Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies = [
"llama-index-embeddings-azure-openai>=0.3.8",
"llama-index-llms-azure-openai>=0.3.2",
"openinference-instrumentation-llama-index>=4.3.0",
"uipath>=2.1.10, <2.2.0",
"uipath>=2.1.28.dev1005170000,<2.1.28.dev1005180000",
]
classifiers = [
"Development Status :: 3 - Alpha",
Expand Down Expand Up @@ -91,3 +91,5 @@ url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true

[tool.uv.sources]
uipath = { index = "testpypi" }
85 changes: 5 additions & 80 deletions src/uipath_llamaindex/_cli/_tracing/_oteladapter.py
Original file line number Diff line number Diff line change
@@ -1,101 +1,26 @@
import json
import logging
from typing import Any, Dict

from opentelemetry.sdk.trace.export import (
SpanExportResult,
)
from uipath.tracing import LlmOpsHttpExporter
from uipath.tracing import CommonSpanProcessor, LlmOpsHttpExporter

logger = logging.getLogger(__name__)


class LlamaIndexExporter(LlmOpsHttpExporter):
# Mapping of old attribute names to new attribute names or (new name, function)
ATTRIBUTE_MAPPING = {
"input.value": ("input", lambda s: json.loads(s)),
"output.value": ("output", lambda s: json.loads(s)),
"llm.model_name": "model",
}

# Mapping of span types
SPAN_TYPE_MAPPING = {
"LLM": "completion",
# Add more mappings as needed
}

def __init__(self, **kwargs):
super().__init__(**kwargs)
self._processor = CommonSpanProcessor()

def _send_with_retries(
self, url: str, payload: list[Dict[str, Any]], max_retries: int = 4
) -> SpanExportResult:
# Transform attributes in each span's payload before sending
for span_data in payload:
if "Attributes" in span_data and isinstance(span_data["Attributes"], str):
try:
# Parse the JSON string to a dictionary
attributes = json.loads(span_data["Attributes"])

if "openinference.span.kind" in attributes:
# Remove the span kind attribute
span_type = attributes["openinference.span.kind"]
# Map span type using SPAN_TYPE_MAPPING
span_data["SpanType"] = self.SPAN_TYPE_MAPPING.get(
span_type, span_type
)
del attributes["openinference.span.kind"]

# Apply the transformation logic
for old_key, mapping in self.ATTRIBUTE_MAPPING.items():
if old_key in attributes:
if isinstance(mapping, tuple):
new_key, func = mapping
try:
attributes[new_key] = func(attributes[old_key])
except Exception:
attributes[new_key] = attributes[old_key]
else:
new_key = mapping
attributes[new_key] = attributes[old_key]
del attributes[old_key]

# Transform token usage data if present
token_keys = {
"llm.token_count.prompt": "promptTokens",
"llm.token_count.completion": "completionTokens",
"llm.token_count.total": "totalTokens",
}

# Check if any token count keys exist
if any(key in attributes for key in token_keys):
usage = {}
for old_key, new_key in token_keys.items():
if old_key in attributes:
usage[new_key] = attributes[old_key]
del attributes[old_key]

# Add default values for BYO execution fields
usage["isByoExecution"] = False
# usage["executionDeploymentType"] = "PAYGO"

# Add usage to attributes
attributes["usage"] = usage

# Clean up any other token count attributes
keys_to_remove = [
k for k in attributes if k.startswith("llm.token_count.")
]
for key in keys_to_remove:
del attributes[key]

# Convert back to JSON string
span_data["attributes"] = json.dumps(attributes)
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse attributes JSON: {e}")

processed_payload = [self._processor.process_span(span) for span in payload]
return super()._send_with_retries(
url=url,
payload=payload,
payload=processed_payload,
max_retries=max_retries,
)

Loading
Loading