Spaces:
Sleeping
Sleeping
| """ | |
| Data models for LLM conversation traces. | |
| TraceEntry – a single message or reasoning step. | |
| TraceSession – an ordered collection of entries from one conversation. | |
| """ | |
| from __future__ import annotations | |
| import time | |
| from dataclasses import dataclass, field | |
| from typing import Dict, Optional, List | |
| import numpy as np | |
| class TraceEntry: | |
| """A single message or reasoning step in a conversation. | |
| Attributes: | |
| text: The raw text content. | |
| role: One of "user", "assistant", "system", "reasoning", "tool", "entry". | |
| step_index: Position in the conversation (0-based, path-local for multi-path). | |
| session_id: ID of the owning TraceSession. | |
| embedding: High-dim embedding vector (set after embedding step). | |
| model_name: Which embedding model produced the embedding. | |
| timestamp: Unix timestamp (optional). | |
| path_id: Which path this entry belongs to (None = single-path mode). | |
| metadata: Arbitrary extra data (token counts, tool calls, etc.). | |
| scores: Named numeric scores for this entry (e.g. {"emotion_valence": 0.8, | |
| "error_rate": 0.1}). Fully optional — used for score-based coloring | |
| in visualization when present. | |
| """ | |
| text: str | |
| role: str | |
| step_index: int | |
| session_id: str | |
| embedding: Optional[np.ndarray] = None | |
| model_name: Optional[str] = None | |
| timestamp: Optional[float] = None | |
| path_id: Optional[int] = None | |
| metadata: dict = field(default_factory=dict) | |
| scores: Dict[str, float] = field(default_factory=dict) | |
| def has_embedding(self) -> bool: | |
| return self.embedding is not None | |
| def to_dict(self) -> dict: | |
| d = { | |
| "text": self.text, | |
| "role": self.role, | |
| "step_index": self.step_index, | |
| "session_id": self.session_id, | |
| "model_name": self.model_name, | |
| "timestamp": self.timestamp, | |
| "path_id": self.path_id, | |
| "metadata": self.metadata, | |
| } | |
| if self.scores: | |
| d["scores"] = self.scores | |
| return d | |
| def from_dict(cls, d: dict) -> "TraceEntry": | |
| return cls( | |
| text=d["text"], | |
| role=d["role"], | |
| step_index=d["step_index"], | |
| session_id=d["session_id"], | |
| model_name=d.get("model_name"), | |
| timestamp=d.get("timestamp"), | |
| path_id=d.get("path_id"), | |
| metadata=d.get("metadata", {}), | |
| scores=d.get("scores", {}), | |
| ) | |
| class TraceSession: | |
| """An ordered collection of TraceEntry objects from one conversation. | |
| Attributes: | |
| session_id: Unique identifier for this session. | |
| label: Human-readable label (e.g. "GPT-4o coding chat"). | |
| entries: Ordered list of conversation entries. | |
| source_format: How the data was imported ("openai", "anthropic", | |
| "plain_text", "reasoning"). | |
| llm_model: The LLM that generated this conversation (optional). | |
| created_at: Unix timestamp of import time. | |
| path_scores: Per-path aggregate scores, keyed by path_id. | |
| E.g. {0: {"success": 1.0, "cost": 0.05}, 1: {"success": 0.0}}. | |
| Fully optional — used for score-based path coloring. | |
| """ | |
| session_id: str | |
| label: str | |
| entries: List[TraceEntry] | |
| source_format: str | |
| llm_model: Optional[str] = None | |
| created_at: float = field(default_factory=time.time) | |
| path_scores: Dict[int, Dict[str, float]] = field(default_factory=dict) | |
| def texts(self) -> List[str]: | |
| return [e.text for e in self.entries] | |
| def roles(self) -> List[str]: | |
| return [e.role for e in self.entries] | |
| def embeddings_matrix(self) -> Optional[np.ndarray]: | |
| """Return (N, D) array of embeddings, or None if not all entries are embedded.""" | |
| if not all(e.has_embedding() for e in self.entries): | |
| return None | |
| return np.array([e.embedding for e in self.entries]) | |
| def score_channels(self) -> List[str]: | |
| """Return sorted list of all score channel names found across entries and paths.""" | |
| channels = set() | |
| for e in self.entries: | |
| channels.update(e.scores.keys()) | |
| for ps in self.path_scores.values(): | |
| channels.update(ps.keys()) | |
| return sorted(channels) | |
| def to_dict(self) -> dict: | |
| d = { | |
| "session_id": self.session_id, | |
| "label": self.label, | |
| "entries": [e.to_dict() for e in self.entries], | |
| "source_format": self.source_format, | |
| "llm_model": self.llm_model, | |
| "created_at": self.created_at, | |
| } | |
| if self.path_scores: | |
| # JSON keys must be strings | |
| d["path_scores"] = {str(k): v for k, v in self.path_scores.items()} | |
| return d | |
| def from_dict(cls, d: dict) -> "TraceSession": | |
| entries = [TraceEntry.from_dict(e) for e in d["entries"]] | |
| raw_ps = d.get("path_scores", {}) | |
| path_scores = {int(k): v for k, v in raw_ps.items()} if raw_ps else {} | |
| return cls( | |
| session_id=d["session_id"], | |
| label=d["label"], | |
| entries=entries, | |
| source_format=d["source_format"], | |
| llm_model=d.get("llm_model"), | |
| created_at=d.get("created_at", 0.0), | |
| path_scores=path_scores, | |
| ) | |
| def __len__(self) -> int: | |
| return len(self.entries) | |
| def __iter__(self): | |
| return iter(self.entries) | |