fix recall trace visualization

This commit is contained in:
Nicolò Boschi 2025-12-12 14:38:37 +01:00
parent d6b7b9b398
commit 922164e25c
14 changed files with 1328 additions and 427 deletions

View file

@ -1203,49 +1203,57 @@ class MemoryEngine:
temporal_info = f" | temporal_range={start_dt.strftime('%Y-%m-%d')} to {end_dt.strftime('%Y-%m-%d')}"
log_buffer.append(f" [2] {total_retrievals}-way retrieval ({len(fact_type)} fact_types): {', '.join(timing_parts)} in {step_duration:.3f}s{temporal_info}")
# Record retrieval results for tracer (convert typed results to old format)
# Record retrieval results for tracer - per fact type
if tracer:
# Convert RetrievalResult to old tuple format for tracer
def to_tuple_format(results):
return [(r.id, r.__dict__) for r in results]
# Add semantic retrieval results
tracer.add_retrieval_results(
method_name="semantic",
results=to_tuple_format(semantic_results),
duration_seconds=aggregated_timings["semantic"],
score_field="similarity",
metadata={"limit": thinking_budget}
)
# Add retrieval results per fact type (to show parallel execution in UI)
for idx, (ft_semantic, ft_bm25, ft_graph, ft_temporal, ft_timings, _) in enumerate(all_retrievals):
ft_name = fact_type[idx] if idx < len(fact_type) else "unknown"
# Add BM25 retrieval results
tracer.add_retrieval_results(
method_name="bm25",
results=to_tuple_format(bm25_results),
duration_seconds=aggregated_timings["bm25"],
score_field="bm25_score",
metadata={"limit": thinking_budget}
)
# Add graph retrieval results
tracer.add_retrieval_results(
method_name="graph",
results=to_tuple_format(graph_results),
duration_seconds=aggregated_timings["graph"],
score_field="similarity", # Graph uses similarity for activation
metadata={"budget": thinking_budget}
)
# Add temporal retrieval results if present
if temporal_results:
# Add semantic retrieval results for this fact type
tracer.add_retrieval_results(
method_name="temporal",
results=to_tuple_format(temporal_results),
duration_seconds=aggregated_timings["temporal"],
score_field="temporal_score",
metadata={"budget": thinking_budget}
method_name="semantic",
results=to_tuple_format(ft_semantic),
duration_seconds=ft_timings.get("semantic", 0.0),
score_field="similarity",
metadata={"limit": thinking_budget},
fact_type=ft_name
)
# Add BM25 retrieval results for this fact type
tracer.add_retrieval_results(
method_name="bm25",
results=to_tuple_format(ft_bm25),
duration_seconds=ft_timings.get("bm25", 0.0),
score_field="bm25_score",
metadata={"limit": thinking_budget},
fact_type=ft_name
)
# Add graph retrieval results for this fact type
tracer.add_retrieval_results(
method_name="graph",
results=to_tuple_format(ft_graph),
duration_seconds=ft_timings.get("graph", 0.0),
score_field="activation",
metadata={"budget": thinking_budget},
fact_type=ft_name
)
# Add temporal retrieval results for this fact type (even if empty, to show it ran)
if ft_temporal is not None:
tracer.add_retrieval_results(
method_name="temporal",
results=to_tuple_format(ft_temporal),
duration_seconds=ft_timings.get("temporal", 0.0),
score_field="temporal_score",
metadata={"budget": thinking_budget},
fact_type=ft_name
)
# Record entry points (from semantic results) for legacy graph view
for rank, retrieval in enumerate(semantic_results[:10], start=1): # Top 10 as entry points
tracer.add_entry_point(retrieval.id, retrieval.text, retrieval.similarity or 0.0, rank)
@ -1287,31 +1295,24 @@ class MemoryEngine:
step_duration = time.time() - step_start
log_buffer.append(f" [4] Reranking: {len(scored_results)} candidates scored in {step_duration:.3f}s")
if tracer:
# Convert to old format for tracer
results_dict = [sr.to_dict() for sr in scored_results]
tracer_merged = [(mc.id, mc.retrieval.__dict__, {"rrf_score": mc.rrf_score, **mc.source_ranks})
for mc in merged_candidates]
tracer.add_reranked(results_dict, tracer_merged)
tracer.add_phase_metric("reranking", step_duration, {
"reranker_type": "cross-encoder",
"candidates_reranked": len(scored_results)
})
# Step 4.5: Combine cross-encoder score with retrieval signals
# This preserves retrieval work (RRF, temporal, recency) instead of pure cross-encoder ranking
if scored_results:
# Normalize RRF scores to [0, 1] range
# Normalize RRF scores to [0, 1] range using min-max normalization
rrf_scores = [sr.candidate.rrf_score for sr in scored_results]
max_rrf = max(rrf_scores) if rrf_scores else 1.0
max_rrf = max(rrf_scores) if rrf_scores else 0.0
min_rrf = min(rrf_scores) if rrf_scores else 0.0
rrf_range = max_rrf - min_rrf if max_rrf > min_rrf else 1.0
rrf_range = max_rrf - min_rrf # Don't force to 1.0, let fallback handle it
# Calculate recency based on occurred_start (more recent = higher score)
now = utcnow()
for sr in scored_results:
# Normalize RRF score
sr.rrf_normalized = (sr.candidate.rrf_score - min_rrf) / rrf_range if rrf_range > 0 else 0.5
# Normalize RRF score (0-1 range, 0.5 if all same)
if rrf_range > 0:
sr.rrf_normalized = (sr.candidate.rrf_score - min_rrf) / rrf_range
else:
# All RRF scores are the same, use neutral value
sr.rrf_normalized = 0.5
# Calculate recency (decay over 365 days, minimum 0.1)
sr.recency = 0.5 # default for missing dates
@ -1343,6 +1344,17 @@ class MemoryEngine:
scored_results.sort(key=lambda x: x.weight, reverse=True)
log_buffer.append(f" [4.6] Combined scoring: cross_encoder(0.6) + rrf(0.2) + temporal(0.1) + recency(0.1)")
# Add reranked results to tracer AFTER combined scoring (so normalized values are included)
if tracer:
results_dict = [sr.to_dict() for sr in scored_results]
tracer_merged = [(mc.id, mc.retrieval.__dict__, {"rrf_score": mc.rrf_score, **mc.source_ranks})
for mc in merged_candidates]
tracer.add_reranked(results_dict, tracer_merged)
tracer.add_phase_metric("reranking", step_duration, {
"reranker_type": "cross-encoder",
"candidates_reranked": len(scored_results)
})
# Step 5: Truncate to thinking_budget * 2 for token filtering
rerank_limit = thinking_budget * 2
top_scored = scored_results[:rerank_limit]

View file

@ -3,13 +3,23 @@ Search module for memory retrieval.
Provides modular search architecture:
- Retrieval: 4-way parallel (semantic + BM25 + graph + temporal)
- Graph retrieval: Pluggable strategies (BFS, PPR)
- Reranking: Pluggable strategies (heuristic, cross-encoder)
"""
from .retrieval import retrieve_parallel
from .retrieval import (
retrieve_parallel,
get_default_graph_retriever,
set_default_graph_retriever,
)
from .graph_retrieval import GraphRetriever, BFSGraphRetriever
from .reranking import CrossEncoderReranker
__all__ = [
"retrieve_parallel",
"get_default_graph_retriever",
"set_default_graph_retriever",
"GraphRetriever",
"BFSGraphRetriever",
"CrossEncoderReranker",
]

View file

@ -0,0 +1,225 @@
"""
Graph retrieval strategies for memory recall.
This module provides an abstraction for graph-based memory retrieval,
allowing different algorithms (BFS spreading activation, PPR, etc.) to be
swapped without changing the rest of the recall pipeline.
"""
from abc import ABC, abstractmethod
from typing import List, Optional
from datetime import datetime
import logging
from .types import RetrievalResult
from ..db_utils import acquire_with_retry
logger = logging.getLogger(__name__)
class GraphRetriever(ABC):
"""
Abstract base class for graph-based memory retrieval.
Implementations traverse the memory graph (entity links, temporal links,
causal links) to find relevant facts that might not be found by
semantic or keyword search alone.
"""
@property
@abstractmethod
def name(self) -> str:
"""Return identifier for this retrieval strategy (e.g., 'bfs', 'ppr')."""
pass
@abstractmethod
async def retrieve(
self,
pool,
query_embedding_str: str,
bank_id: str,
fact_type: str,
budget: int,
query_text: Optional[str] = None,
) -> List[RetrievalResult]:
"""
Retrieve relevant facts via graph traversal.
Args:
pool: Database connection pool
query_embedding_str: Query embedding as string (for finding entry points)
bank_id: Memory bank identifier
fact_type: Fact type to filter ('world', 'experience', 'opinion', 'observation')
budget: Maximum number of nodes to explore/return
query_text: Original query text (optional, for some strategies)
Returns:
List of RetrievalResult objects with activation scores set
"""
pass
class BFSGraphRetriever(GraphRetriever):
"""
Graph retrieval using BFS-style spreading activation.
Starting from semantic entry points, spreads activation through
the memory graph (entity, temporal, causal links) using breadth-first
traversal with decaying activation.
This is the original Hindsight graph retrieval algorithm.
"""
def __init__(
self,
entry_point_limit: int = 5,
entry_point_threshold: float = 0.5,
activation_decay: float = 0.8,
min_activation: float = 0.1,
batch_size: int = 20,
):
"""
Initialize BFS graph retriever.
Args:
entry_point_limit: Maximum number of entry points to start from
entry_point_threshold: Minimum semantic similarity for entry points
activation_decay: Decay factor per hop (activation *= decay)
min_activation: Minimum activation to continue spreading
batch_size: Number of nodes to process per batch (for neighbor fetching)
"""
self.entry_point_limit = entry_point_limit
self.entry_point_threshold = entry_point_threshold
self.activation_decay = activation_decay
self.min_activation = min_activation
self.batch_size = batch_size
@property
def name(self) -> str:
return "bfs"
async def retrieve(
self,
pool,
query_embedding_str: str,
bank_id: str,
fact_type: str,
budget: int,
query_text: Optional[str] = None,
) -> List[RetrievalResult]:
"""
Retrieve facts using BFS spreading activation.
Algorithm:
1. Find entry points (top semantic matches above threshold)
2. BFS traversal: visit neighbors, propagate decaying activation
3. Boost causal links (causes, enables, prevents)
4. Return visited nodes up to budget
"""
async with acquire_with_retry(pool) as conn:
return await self._retrieve_with_conn(
conn, query_embedding_str, bank_id, fact_type, budget
)
async def _retrieve_with_conn(
self,
conn,
query_embedding_str: str,
bank_id: str,
fact_type: str,
budget: int,
) -> List[RetrievalResult]:
"""Internal implementation with connection."""
# Step 1: Find entry points
entry_points = await conn.fetch(
"""
SELECT id, text, context, event_date, occurred_start, occurred_end,
mentioned_at, access_count, embedding, fact_type, document_id, chunk_id,
1 - (embedding <=> $1::vector) AS similarity
FROM memory_units
WHERE bank_id = $2
AND embedding IS NOT NULL
AND fact_type = $3
AND (1 - (embedding <=> $1::vector)) >= $4
ORDER BY embedding <=> $1::vector
LIMIT $5
""",
query_embedding_str, bank_id, fact_type,
self.entry_point_threshold, self.entry_point_limit
)
if not entry_points:
return []
# Step 2: BFS spreading activation
visited = set()
results = []
queue = [
(RetrievalResult.from_db_row(dict(r)), r["similarity"])
for r in entry_points
]
budget_remaining = budget
while queue and budget_remaining > 0:
# Collect a batch of nodes to process
batch_nodes = []
batch_activations = {}
while queue and len(batch_nodes) < self.batch_size and budget_remaining > 0:
current, activation = queue.pop(0)
unit_id = current.id
if unit_id not in visited:
visited.add(unit_id)
budget_remaining -= 1
current.activation = activation
results.append(current)
batch_nodes.append(current.id)
batch_activations[unit_id] = activation
# Batch fetch neighbors
if batch_nodes and budget_remaining > 0:
max_neighbors = len(batch_nodes) * 20
neighbors = await conn.fetch(
"""
SELECT mu.id, mu.text, mu.context, mu.occurred_start, mu.occurred_end,
mu.mentioned_at, mu.access_count, mu.embedding, mu.fact_type,
mu.document_id, mu.chunk_id,
ml.weight, ml.link_type, ml.from_unit_id
FROM memory_links ml
JOIN memory_units mu ON ml.to_unit_id = mu.id
WHERE ml.from_unit_id = ANY($1::uuid[])
AND ml.weight >= $2
AND mu.fact_type = $3
ORDER BY ml.weight DESC
LIMIT $4
""",
batch_nodes, self.min_activation, fact_type, max_neighbors
)
for n in neighbors:
neighbor_id = str(n["id"])
if neighbor_id not in visited:
parent_id = str(n["from_unit_id"])
parent_activation = batch_activations.get(parent_id, 0.5)
# Boost causal links
link_type = n["link_type"]
base_weight = n["weight"]
if link_type in ("causes", "caused_by"):
causal_boost = 2.0
elif link_type in ("enables", "prevents"):
causal_boost = 1.5
else:
causal_boost = 1.0
effective_weight = base_weight * causal_boost
new_activation = parent_activation * effective_weight * self.activation_decay
if new_activation > self.min_activation:
neighbor_result = RetrievalResult.from_db_row(dict(n))
queue.append((neighbor_result, new_activation))
return results

View file

@ -4,7 +4,7 @@ Retrieval module for 4-way parallel search.
Implements:
1. Semantic retrieval (vector similarity)
2. BM25 retrieval (keyword/full-text search)
3. Graph retrieval (spreading activation)
3. Graph retrieval (via pluggable GraphRetriever interface)
4. Temporal retrieval (time-aware search with spreading)
"""
@ -13,6 +13,24 @@ from datetime import datetime
import asyncio
from ..db_utils import acquire_with_retry
from .types import RetrievalResult
from .graph_retrieval import GraphRetriever, BFSGraphRetriever
# Default graph retriever instance (can be overridden)
_default_graph_retriever: Optional[GraphRetriever] = None
def get_default_graph_retriever() -> GraphRetriever:
"""Get or create the default graph retriever."""
global _default_graph_retriever
if _default_graph_retriever is None:
_default_graph_retriever = BFSGraphRetriever()
return _default_graph_retriever
def set_default_graph_retriever(retriever: GraphRetriever) -> None:
"""Set the default graph retriever (for configuration/testing)."""
global _default_graph_retriever
_default_graph_retriever = retriever
async def retrieve_semantic(
@ -105,121 +123,6 @@ async def retrieve_bm25(
return [RetrievalResult.from_db_row(dict(r)) for r in results]
async def retrieve_graph(
conn,
query_emb_str: str,
bank_id: str,
fact_type: str,
budget: int
) -> List[RetrievalResult]:
"""
Graph retrieval via spreading activation.
Args:
conn: Database connection
query_emb_str: Query embedding as string
agent_id: bank ID
fact_type: Fact type to filter
budget: Node budget for graph traversal
Returns:
List of RetrievalResult objects
"""
# Find entry points
entry_points = await conn.fetch(
"""
SELECT id, text, context, event_date, occurred_start, occurred_end, mentioned_at, access_count, embedding, fact_type, document_id, chunk_id,
1 - (embedding <=> $1::vector) AS similarity
FROM memory_units
WHERE bank_id = $2
AND embedding IS NOT NULL
AND fact_type = $3
AND (1 - (embedding <=> $1::vector)) >= 0.5
ORDER BY embedding <=> $1::vector
LIMIT 5
""",
query_emb_str, bank_id, fact_type
)
if not entry_points:
return []
# BFS-style spreading activation with batched neighbor fetching
visited = set()
results = []
queue = [(RetrievalResult.from_db_row(dict(r)), r["similarity"]) for r in entry_points]
budget_remaining = budget
# Process nodes in batches to reduce DB roundtrips
batch_size = 20 # Fetch neighbors for up to 20 nodes at once
while queue and budget_remaining > 0:
# Collect a batch of nodes to process
batch_nodes = []
batch_activations = {}
while queue and len(batch_nodes) < batch_size and budget_remaining > 0:
current, activation = queue.pop(0)
unit_id = current.id
if unit_id not in visited:
visited.add(unit_id)
budget_remaining -= 1
results.append(current)
batch_nodes.append(current.id)
batch_activations[unit_id] = activation
# Batch fetch neighbors for all nodes in this batch
# Fetch top weighted neighbors (batch_size * 20 = ~400 for good distribution)
if batch_nodes and budget_remaining > 0:
max_neighbors = len(batch_nodes) * 20
neighbors = await conn.fetch(
"""
SELECT mu.id, mu.text, mu.context, mu.occurred_start, mu.occurred_end, mu.mentioned_at,
mu.access_count, mu.embedding, mu.fact_type, mu.document_id, mu.chunk_id,
ml.weight, ml.link_type, ml.from_unit_id
FROM memory_links ml
JOIN memory_units mu ON ml.to_unit_id = mu.id
WHERE ml.from_unit_id = ANY($1::uuid[])
AND ml.weight >= 0.1
AND mu.fact_type = $2
ORDER BY ml.weight DESC
LIMIT $3
""",
batch_nodes, fact_type, max_neighbors
)
for n in neighbors:
neighbor_id = str(n["id"])
if neighbor_id not in visited:
# Get parent activation
parent_id = str(n["from_unit_id"])
activation = batch_activations.get(parent_id, 0.5)
# Boost activation for causal links (they're high-value relationships)
link_type = n["link_type"]
base_weight = n["weight"]
# Causal links get 1.5-2.0x boost depending on type
if link_type in ("causes", "caused_by"):
# Direct causation - very strong relationship
causal_boost = 2.0
elif link_type in ("enables", "prevents"):
# Conditional causation - strong but not as direct
causal_boost = 1.5
else:
# Temporal, semantic, entity links - standard weight
causal_boost = 1.0
effective_weight = base_weight * causal_boost
new_activation = activation * effective_weight * 0.8
if new_activation > 0.1:
neighbor_result = RetrievalResult.from_db_row(dict(n))
queue.append((neighbor_result, new_activation))
return results
async def retrieve_temporal(
conn,
query_emb_str: str,
@ -419,7 +322,8 @@ async def retrieve_parallel(
fact_type: str,
thinking_budget: int,
question_date: Optional[datetime] = None,
query_analyzer: Optional["QueryAnalyzer"] = None
query_analyzer: Optional["QueryAnalyzer"] = None,
graph_retriever: Optional[GraphRetriever] = None,
) -> Tuple[List[RetrievalResult], List[RetrievalResult], List[RetrievalResult], Optional[List[RetrievalResult]], Dict[str, float], Optional[Tuple[datetime, datetime]]]:
"""
Run 3-way or 4-way parallel retrieval (adds temporal if detected).
@ -428,11 +332,12 @@ async def retrieve_parallel(
pool: Database connection pool
query_text: Query text
query_embedding_str: Query embedding as string
agent_id: bank ID
bank_id: Bank ID
fact_type: Fact type to filter
thinking_budget: Budget for graph traversal and retrieval limits
question_date: Optional date when question was asked (for temporal filtering)
query_analyzer: Query analyzer to use (defaults to TransformerQueryAnalyzer)
graph_retriever: Graph retrieval strategy (defaults to BFSGraphRetriever)
Returns:
Tuple of (semantic_results, bm25_results, graph_results, temporal_results, timings, temporal_constraint)
@ -449,6 +354,9 @@ async def retrieve_parallel(
query_text, reference_date=question_date, analyzer=query_analyzer
)
# Use provided graph retriever or default
retriever = graph_retriever or get_default_graph_retriever()
# Wrapper to track timing for each retrieval method
async def timed_retrieval(name: str, coro):
start = time.time()
@ -465,8 +373,14 @@ async def retrieve_parallel(
return await retrieve_bm25(conn, query_text, bank_id, fact_type, limit=thinking_budget)
async def run_graph():
async with acquire_with_retry(pool) as conn:
return await retrieve_graph(conn, query_embedding_str, bank_id, fact_type, budget=thinking_budget)
return await retriever.retrieve(
pool=pool,
query_embedding_str=query_embedding_str,
bank_id=bank_id,
fact_type=fact_type,
budget=thinking_budget,
query_text=query_text,
)
async def run_temporal(start_date, end_date):
async with acquire_with_retry(pool) as conn:

View file

@ -108,6 +108,7 @@ class RetrievalResult(BaseModel):
class RetrievalMethodResults(BaseModel):
"""Results from a single retrieval method."""
method_name: Literal["semantic", "bm25", "graph", "temporal"] = Field(description="Name of retrieval method")
fact_type: Optional[str] = Field(default=None, description="Fact type this retrieval was for (world, experience, opinion)")
results: List[RetrievalResult] = Field(description="Retrieved results with ranks")
duration_seconds: float = Field(description="Time taken for this retrieval")
metadata: Dict[str, Any] = Field(default_factory=dict, description="Method-specific metadata")

View file

@ -289,7 +289,8 @@ class SearchTracer:
results: List[tuple], # List of (doc_id, data) tuples
duration_seconds: float,
score_field: str, # e.g., "similarity", "bm25_score"
metadata: Optional[Dict[str, Any]] = None
metadata: Optional[Dict[str, Any]] = None,
fact_type: Optional[str] = None
):
"""
Record results from a single retrieval method.
@ -300,6 +301,7 @@ class SearchTracer:
duration_seconds: Time taken for this retrieval
score_field: Field name containing the score in data dict
metadata: Optional metadata about this retrieval method
fact_type: Fact type this retrieval was for (world, experience, opinion)
"""
retrieval_results = []
for rank, (doc_id, data) in enumerate(results, start=1):
@ -313,7 +315,7 @@ class SearchTracer:
text=data.get("text", ""),
context=data.get("context", ""),
event_date=data.get("event_date"),
fact_type=data.get("fact_type"),
fact_type=data.get("fact_type") or fact_type,
score=score,
score_name=score_field,
)
@ -322,6 +324,7 @@ class SearchTracer:
self.retrieval_results.append(
RetrievalMethodResults(
method_name=method_name,
fact_type=fact_type,
results=retrieval_results,
duration_seconds=duration_seconds,
metadata=metadata or {},
@ -367,8 +370,10 @@ class SearchTracer:
rank_change = rrf_rank - rank # Positive = moved up
# Extract score components (only include non-None values)
# Keys from ScoredResult.to_dict(): cross_encoder_score, cross_encoder_score_normalized,
# rrf_normalized, temporal, recency, combined_score, weight
score_components = {}
for key in ["semantic_similarity", "bm25_score", "rrf_score", "recency_normalized", "frequency_normalized", "cross_encoder_score", "cross_encoder_score_normalized"]:
for key in ["cross_encoder_score", "cross_encoder_score_normalized", "rrf_score", "rrf_normalized", "temporal", "recency", "combined_score"]:
if key in result and result[key] is not None:
score_components[key] = result[key]

View file

@ -31,8 +31,9 @@ class RetrievalResult:
embedding: Optional[List[float]] = None
# Retrieval-specific scores (only one will be set depending on retrieval method)
similarity: Optional[float] = None # Semantic/graph retrieval
similarity: Optional[float] = None # Semantic retrieval
bm25_score: Optional[float] = None # BM25 retrieval
activation: Optional[float] = None # Graph retrieval (spreading activation)
temporal_score: Optional[float] = None # Temporal retrieval
temporal_proximity: Optional[float] = None # Temporal retrieval
@ -54,6 +55,7 @@ class RetrievalResult:
embedding=row.get("embedding"),
similarity=row.get("similarity"),
bm25_score=row.get("bm25_score"),
activation=row.get("activation"),
temporal_score=row.get("temporal_score"),
temporal_proximity=row.get("temporal_proximity"),
)
@ -152,6 +154,7 @@ class ScoredResult:
result["cross_encoder_score"] = self.cross_encoder_score
result["cross_encoder_score_normalized"] = self.cross_encoder_score_normalized
result["rrf_normalized"] = self.rrf_normalized
result["temporal"] = self.temporal
result["recency"] = self.recency
result["combined_score"] = self.combined_score
result["weight"] = self.weight

View file

@ -0,0 +1,323 @@
"""
Tests for combined scoring functionality.
Verifies that:
1. RRF scores are properly normalized to [0, 1] range
2. Combined scoring formula is applied correctly
3. Tracer captures normalized values (not raw values)
"""
import pytest
from datetime import datetime, timezone
from hindsight_api.engine.search.types import RetrievalResult, MergedCandidate, ScoredResult
from hindsight_api.engine.memory_engine import Budget
class TestRRFNormalization:
"""Test that RRF scores are properly normalized."""
def test_rrf_normalized_range(self):
"""RRF normalized values should be in [0, 1] range, not raw [0.04, 0.06]."""
# Simulate RRF scores like what we get from actual retrieval
raw_rrf_scores = [0.0607, 0.0550, 0.0480, 0.0390]
max_rrf = max(raw_rrf_scores)
min_rrf = min(raw_rrf_scores)
rrf_range = max_rrf - min_rrf
normalized = []
for score in raw_rrf_scores:
if rrf_range > 0:
norm = (score - min_rrf) / rrf_range
else:
norm = 0.5
normalized.append(norm)
# Verify normalized values are in [0, 1]
for i, norm in enumerate(normalized):
assert 0.0 <= norm <= 1.0, f"Normalized RRF {norm} not in [0, 1] for raw {raw_rrf_scores[i]}"
# Highest raw should be 1.0
assert normalized[0] == 1.0, f"Highest RRF should normalize to 1.0, got {normalized[0]}"
# Lowest raw should be 0.0
assert normalized[-1] == 0.0, f"Lowest RRF should normalize to 0.0, got {normalized[-1]}"
def test_rrf_all_same_scores(self):
"""When all RRF scores are the same, normalized should be 0.5 (neutral)."""
raw_rrf_scores = [0.0500, 0.0500, 0.0500]
max_rrf = max(raw_rrf_scores)
min_rrf = min(raw_rrf_scores)
rrf_range = max_rrf - min_rrf
normalized = []
for score in raw_rrf_scores:
if rrf_range > 0:
norm = (score - min_rrf) / rrf_range
else:
norm = 0.5 # Neutral value when all same
normalized.append(norm)
# All should be 0.5 when scores are identical
for norm in normalized:
assert norm == 0.5, f"Expected 0.5 for identical scores, got {norm}"
class TestCombinedScoringFormula:
"""Test that the combined scoring formula is applied correctly."""
def test_combined_score_calculation(self):
"""Verify the weighted combination: 0.6*CE + 0.2*RRF + 0.1*temporal + 0.1*recency."""
# Test case 1: All components at 1.0
ce_norm = 1.0
rrf_norm = 1.0
temporal = 1.0
recency = 1.0
expected = 0.6 * ce_norm + 0.2 * rrf_norm + 0.1 * temporal + 0.1 * recency
assert expected == 1.0, f"All 1.0 should give 1.0, got {expected}"
# Test case 2: All components at 0.0
ce_norm = 0.0
rrf_norm = 0.0
temporal = 0.0
recency = 0.0
expected = 0.6 * ce_norm + 0.2 * rrf_norm + 0.1 * temporal + 0.1 * recency
assert expected == 0.0, f"All 0.0 should give 0.0, got {expected}"
# Test case 3: High CE, low RRF (cross-encoder finds something retrieval missed)
ce_norm = 0.999
rrf_norm = 0.0 # Lowest in set
temporal = 0.5
recency = 0.5
expected = 0.6 * ce_norm + 0.2 * rrf_norm + 0.1 * temporal + 0.1 * recency
# 0.5994 + 0.0 + 0.05 + 0.05 = 0.6994
assert abs(expected - 0.6994) < 0.001, f"Expected ~0.6994, got {expected}"
# Test case 4: Medium CE, high RRF (retrieval consensus)
ce_norm = 0.8
rrf_norm = 1.0 # Highest in set
temporal = 0.5
recency = 0.5
expected = 0.6 * ce_norm + 0.2 * rrf_norm + 0.1 * temporal + 0.1 * recency
# 0.48 + 0.2 + 0.05 + 0.05 = 0.78
assert abs(expected - 0.78) < 0.001, f"Expected ~0.78, got {expected}"
def test_rrf_contribution_is_significant(self):
"""Verify RRF actually contributes to the final score (not negligible)."""
# Same CE, different RRF
ce_norm = 0.8
temporal = 0.5
recency = 0.5
# Low RRF
score_low_rrf = 0.6 * ce_norm + 0.2 * 0.0 + 0.1 * temporal + 0.1 * recency
# High RRF
score_high_rrf = 0.6 * ce_norm + 0.2 * 1.0 + 0.1 * temporal + 0.1 * recency
# Difference should be 0.2 (20% contribution)
diff = score_high_rrf - score_low_rrf
assert abs(diff - 0.2) < 0.001, f"RRF should contribute 0.2 difference, got {diff}"
@pytest.mark.asyncio
async def test_trace_has_normalized_rrf(memory):
"""Integration test: verify trace contains normalized RRF values, not raw."""
bank_id = f"test_scoring_{datetime.now(timezone.utc).timestamp()}"
try:
# Store multiple memories to ensure different RRF scores
await memory.retain_async(
bank_id=bank_id,
content="Python is a programming language created by Guido van Rossum",
context="tech facts",
)
await memory.retain_async(
bank_id=bank_id,
content="JavaScript was created by Brendan Eich at Netscape",
context="tech facts",
)
await memory.retain_async(
bank_id=bank_id,
content="The Eiffel Tower is located in Paris, France",
context="geography facts",
)
await memory.retain_async(
bank_id=bank_id,
content="Mount Everest is the tallest mountain on Earth",
context="geography facts",
)
# Search with tracing
result = await memory.recall_async(
bank_id=bank_id,
query="programming languages",
fact_type=["world"],
budget=Budget.LOW,
max_tokens=1024,
enable_trace=True,
)
assert result.trace is not None, "Trace should be present"
trace = result.trace
# Check reranked results have proper score_components
assert "reranked" in trace, "Trace should have reranked results"
assert len(trace["reranked"]) > 0, "Should have reranked results"
has_valid_rrf = False
has_valid_temporal = False
has_valid_recency = False
for r in trace["reranked"]:
sc = r.get("score_components", {})
# Check RRF normalized is present and in valid range
if "rrf_normalized" in sc:
rrf_norm = sc["rrf_normalized"]
assert 0.0 <= rrf_norm <= 1.0, f"rrf_normalized {rrf_norm} should be in [0, 1]"
# Should NOT be raw RRF score (which would be ~0.04-0.06)
# A normalized value of exactly 0.0 or 1.0 is valid (min/max of set)
# But raw scores like 0.0607 should never appear as normalized
if rrf_norm > 0.1: # Any value > 0.1 is likely properly normalized
has_valid_rrf = True
# Check temporal is present and in valid range
if "temporal" in sc:
temporal = sc["temporal"]
assert 0.0 <= temporal <= 1.0, f"temporal {temporal} should be in [0, 1]"
has_valid_temporal = True
# Check recency is present and in valid range
if "recency" in sc:
recency = sc["recency"]
assert 0.0 <= recency <= 1.0, f"recency {recency} should be in [0, 1]"
has_valid_recency = True
# At least some results should have these components
# (might not have rrf > 0.1 if all scores are same, which is fine)
assert has_valid_temporal, "Should have temporal scores in trace"
assert has_valid_recency, "Should have recency scores in trace"
print("\n✓ Combined scoring trace test passed!")
print(f" - Reranked results: {len(trace['reranked'])}")
if trace["reranked"]:
sc = trace["reranked"][0].get("score_components", {})
print(f" - First result score components: {sc}")
finally:
await memory.delete_bank(bank_id)
@pytest.mark.asyncio
async def test_rrf_normalized_not_raw_in_trace(memory):
"""Verify that raw RRF scores (0.04-0.06 range) don't appear as normalized values."""
bank_id = f"test_rrf_raw_{datetime.now(timezone.utc).timestamp()}"
try:
# Store enough memories to get varied RRF scores
for i in range(5):
await memory.retain_async(
bank_id=bank_id,
content=f"Test fact number {i} about various topics",
context="test context",
)
result = await memory.recall_async(
bank_id=bank_id,
query="test fact",
fact_type=["world"],
budget=Budget.LOW,
max_tokens=512,
enable_trace=True,
)
trace = result.trace
assert trace is not None
# Check that rrf_normalized values are NOT in the raw range
raw_rrf_range = (0.01, 0.08) # Raw RRF scores are typically in this range
for r in trace.get("reranked", []):
sc = r.get("score_components", {})
if "rrf_normalized" in sc and "rrf_score" in sc:
rrf_norm = sc["rrf_normalized"]
rrf_raw = sc["rrf_score"]
# Raw should be in the typical range
assert raw_rrf_range[0] <= rrf_raw <= raw_rrf_range[1], \
f"Raw RRF {rrf_raw} should be in typical range {raw_rrf_range}"
# Normalized should either be:
# - 0.0 (min in set)
# - 1.0 (max in set)
# - 0.5 (all same)
# - Something in between (0.0 to 1.0)
# But NOT the same as raw (which would indicate no normalization)
if len(trace["reranked"]) > 1:
# If we have multiple results, normalized should differ from raw
# (unless by coincidence, which is very unlikely)
assert rrf_norm != rrf_raw, \
f"Normalized RRF ({rrf_norm}) should differ from raw ({rrf_raw})"
print("\n✓ RRF raw vs normalized test passed!")
finally:
await memory.delete_bank(bank_id)
@pytest.mark.asyncio
async def test_combined_score_matches_components(memory):
"""Verify the final score actually equals the weighted sum of components."""
bank_id = f"test_combined_{datetime.now(timezone.utc).timestamp()}"
try:
await memory.retain_async(
bank_id=bank_id,
content="The quick brown fox jumps over the lazy dog",
context="test",
)
await memory.retain_async(
bank_id=bank_id,
content="A quick test of the emergency broadcast system",
context="test",
)
result = await memory.recall_async(
bank_id=bank_id,
query="quick test",
fact_type=["world"],
budget=Budget.LOW,
max_tokens=512,
enable_trace=True,
)
trace = result.trace
assert trace is not None
for r in trace.get("reranked", []):
sc = r.get("score_components", {})
final_score = r.get("rerank_score", 0)
# Get components (use defaults if missing)
ce = sc.get("cross_encoder_score_normalized", 0)
rrf = sc.get("rrf_normalized", 0.5)
tmp = sc.get("temporal", 0.5)
rec = sc.get("recency", 0.5)
# Calculate expected score
expected = 0.6 * ce + 0.2 * rrf + 0.1 * tmp + 0.1 * rec
# Allow small floating point difference
assert abs(final_score - expected) < 0.01, \
f"Final score {final_score} doesn't match expected {expected} from components"
print("\n✓ Combined score verification test passed!")
finally:
await memory.delete_bank(bank_id)

View file

@ -18,7 +18,7 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<body className="bg-background text-foreground">
<ThemeProvider>
<BankProvider>
{children}

View file

@ -193,7 +193,7 @@ export function DocumentsView() {
{/* Document ID */}
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Document ID</div>
<div className="text-sm font-mono break-all">{selectedDocument.id}</div>
<code className="text-sm font-mono break-all text-foreground">{selectedDocument.id}</code>
</div>
{/* Created & Memory Units */}
@ -201,11 +201,11 @@ export function DocumentsView() {
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Created</div>
<div className="text-sm font-medium">{new Date(selectedDocument.created_at).toLocaleString()}</div>
<div className="text-sm font-medium text-foreground">{new Date(selectedDocument.created_at).toLocaleString()}</div>
</div>
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Memory Units</div>
<div className="text-sm font-medium">{selectedDocument.memory_unit_count}</div>
<div className="text-sm font-medium text-foreground">{selectedDocument.memory_unit_count}</div>
</div>
</div>
)}
@ -214,7 +214,7 @@ export function DocumentsView() {
{selectedDocument.original_text && (
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Text Length</div>
<div className="text-sm font-medium">{selectedDocument.original_text.length.toLocaleString()} characters</div>
<div className="text-sm font-medium text-foreground">{selectedDocument.original_text.length.toLocaleString()} characters</div>
</div>
)}
@ -244,7 +244,7 @@ export function DocumentsView() {
<div>
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Original Text</div>
<div className="p-4 bg-muted/50 rounded-lg border border-border max-h-[400px] overflow-y-auto">
<pre className="text-sm whitespace-pre-wrap font-mono leading-relaxed">{selectedDocument.original_text}</pre>
<pre className="text-sm whitespace-pre-wrap font-mono leading-relaxed text-foreground">{selectedDocument.original_text}</pre>
</div>
</div>
)}

View file

@ -92,9 +92,9 @@ export function EntitiesView() {
};
return (
<div className="flex gap-4">
<div>
{/* Entity List */}
<div className="flex-1">
<div>
{loading ? (
<div className="flex items-center justify-center py-20">
<div className="text-center">
@ -111,7 +111,6 @@ export function EntitiesView() {
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Mentions</TableHead>
<TableHead>First Seen</TableHead>
@ -123,11 +122,10 @@ export function EntitiesView() {
<TableRow
key={entity.id}
onClick={() => loadEntityDetail(entity.id)}
className={`cursor-pointer ${
selectedEntity?.id === entity.id ? 'bg-accent' : ''
className={`cursor-pointer hover:bg-muted/50 ${
selectedEntity?.id === entity.id ? 'bg-primary/10' : ''
}`}
>
<TableCell className="text-xs text-muted-foreground font-mono" title={entity.id}>{entity.id.slice(0, 8)}...</TableCell>
<TableCell className="font-medium">{entity.canonical_name}</TableCell>
<TableCell>{entity.mention_count}</TableCell>
<TableCell>{formatDate(entity.first_seen)}</TableCell>
@ -149,60 +147,81 @@ export function EntitiesView() {
)}
</div>
{/* Entity Detail Panel */}
{/* Entity Detail Panel - Fixed overlay */}
{selectedEntity && (
<div className="w-96 bg-card border-2 border-primary rounded-lg p-4">
<div className="flex justify-between items-start mb-4">
<h3 className="text-lg font-bold text-card-foreground">{selectedEntity.canonical_name}</h3>
<Button
variant="ghost"
size="sm"
onClick={() => setSelectedEntity(null)}
>
X
</Button>
</div>
<div className="text-sm text-muted-foreground mb-4">
<div className="font-mono text-xs mb-1" title={selectedEntity.id}>ID: {selectedEntity.id}</div>
<div>Mentions: {selectedEntity.mention_count}</div>
<div>First seen: {formatDate(selectedEntity.first_seen)}</div>
<div>Last seen: {formatDate(selectedEntity.last_seen)}</div>
</div>
<div className="mb-4">
<div className="flex justify-between items-center mb-2">
<h4 className="font-bold text-card-foreground">Observations</h4>
<div className="fixed right-0 top-0 h-screen w-[420px] bg-card border-l-2 border-primary shadow-2xl z-50 overflow-y-auto animate-in slide-in-from-right duration-300 ease-out">
<div className="p-5">
{/* Header */}
<div className="flex justify-between items-center mb-6 pb-4 border-b border-border">
<div>
<h3 className="text-xl font-bold text-foreground">{selectedEntity.canonical_name}</h3>
<p className="text-sm text-muted-foreground mt-1">Entity details</p>
</div>
<Button
onClick={regenerateObservations}
disabled={regenerating}
variant="secondary"
variant="ghost"
size="sm"
onClick={() => setSelectedEntity(null)}
className="h-8 w-8 p-0"
>
{regenerating ? 'Regenerating...' : 'Regenerate'}
<span className="text-lg">×</span>
</Button>
</div>
{loadingDetail ? (
<div className="text-muted-foreground text-sm">Loading observations...</div>
) : selectedEntity.observations && selectedEntity.observations.length > 0 ? (
<ul className="space-y-2">
{selectedEntity.observations.map((obs, idx) => (
<li key={idx} className="p-2 bg-muted rounded text-sm">
<div>{obs.text}</div>
{obs.mentioned_at && (
<div className="text-xs text-muted-foreground mt-1">
{formatDate(obs.mentioned_at)}
</div>
)}
</li>
))}
</ul>
) : (
<div className="text-muted-foreground text-sm">
No observations yet. Click &quot;Regenerate&quot; to generate observations from facts.
<div className="space-y-5">
{/* Entity Info */}
<div className="grid grid-cols-2 gap-4">
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Mentions</div>
<div className="text-lg font-semibold text-foreground">{selectedEntity.mention_count}</div>
</div>
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">First Seen</div>
<div className="text-sm font-medium text-foreground">{formatDate(selectedEntity.first_seen)}</div>
</div>
</div>
)}
{/* ID */}
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Entity ID</div>
<code className="text-xs font-mono break-all text-muted-foreground">{selectedEntity.id}</code>
</div>
{/* Observations */}
<div>
<div className="flex justify-between items-center mb-3">
<div className="text-xs font-bold text-muted-foreground uppercase">Observations</div>
<Button
onClick={regenerateObservations}
disabled={regenerating}
variant="outline"
size="sm"
>
{regenerating ? 'Regenerating...' : 'Regenerate'}
</Button>
</div>
{loadingDetail ? (
<div className="text-muted-foreground text-sm">Loading observations...</div>
) : selectedEntity.observations && selectedEntity.observations.length > 0 ? (
<ul className="space-y-2">
{selectedEntity.observations.map((obs, idx) => (
<li key={idx} className="p-3 bg-muted/50 rounded-lg">
<div className="text-sm text-foreground">{obs.text}</div>
{obs.mentioned_at && (
<div className="text-xs text-muted-foreground mt-2">
{formatDate(obs.mentioned_at)}
</div>
)}
</li>
))}
</ul>
) : (
<div className="text-muted-foreground text-sm p-4 bg-muted/50 rounded-lg">
No observations yet. Click &quot;Regenerate&quot; to generate observations from facts.
</div>
)}
</div>
</div>
</div>
</div>
)}

View file

@ -49,6 +49,9 @@ export function MemoryDetailPanel({
if (!memory) return null;
// Handle both 'id' and 'node_id' (trace results use node_id)
const memoryId = memory.id || memory.node_id;
const labelSize = compact ? 'text-[10px]' : 'text-xs';
const textSize = compact ? 'text-xs' : 'text-sm';
@ -129,24 +132,26 @@ export function MemoryDetailPanel({
)}
{/* ID */}
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Memory ID</div>
<div className="flex items-center gap-2">
<code className="text-xs font-mono break-all flex-1 text-muted-foreground">{memory.id}</code>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 flex-shrink-0"
onClick={() => copyToClipboard(memory.id)}
>
{copiedId === memory.id ? (
<Check className="h-4 w-4 text-green-600" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
{memoryId && (
<div className="p-4 bg-muted/50 rounded-lg">
<div className="text-xs font-bold text-muted-foreground uppercase mb-2">Memory ID</div>
<div className="flex items-center gap-2">
<code className="text-xs font-mono break-all flex-1 text-muted-foreground">{memoryId}</code>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 flex-shrink-0"
onClick={() => copyToClipboard(memoryId)}
>
{copiedId === memoryId ? (
<Check className="h-4 w-4 text-green-600" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</div>
</div>
</div>
)}
{/* Document/Chunk buttons */}
{(memory.document_id || memory.chunk_id) && (
@ -267,24 +272,26 @@ export function MemoryDetailPanel({
)}
{/* ID */}
<div className={`${compact ? 'p-2' : 'p-3'} bg-muted rounded-lg`}>
<div className={`${labelSize} font-bold text-muted-foreground uppercase mb-1`}>Memory ID</div>
<div className="flex items-center gap-2">
<span className={`${compact ? 'text-[10px]' : 'text-sm'} font-mono break-all`}>{memory.id}</span>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 flex-shrink-0"
onClick={() => copyToClipboard(memory.id)}
>
{copiedId === memory.id ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
</Button>
{memoryId && (
<div className={`${compact ? 'p-2' : 'p-3'} bg-muted rounded-lg`}>
<div className={`${labelSize} font-bold text-muted-foreground uppercase mb-1`}>Memory ID</div>
<div className="flex items-center gap-2">
<span className={`${compact ? 'text-[10px]' : 'text-sm'} font-mono break-all`}>{memoryId}</span>
<Button
variant="ghost"
size="sm"
className="h-6 w-6 p-0 flex-shrink-0"
onClick={() => copyToClipboard(memoryId)}
>
{copiedId === memoryId ? (
<Check className="h-3 w-3 text-green-600" />
) : (
<Copy className="h-3 w-3" />
)}
</Button>
</div>
</div>
</div>
)}
{/* Document/Chunk buttons */}
{(memory.document_id || memory.chunk_id) && (

View file

@ -9,7 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Search, Clock, Zap, ChevronRight, Database, FileText, Users } from 'lucide-react';
import { Search, Clock, Zap, ChevronRight, ChevronDown, Database, FileText, Users, ArrowDown } from 'lucide-react';
import JsonView from 'react18-json-view';
import 'react18-json-view/src/style.css';
import { MemoryDetailPanel } from './memory-detail-panel';
@ -38,6 +38,34 @@ export function SearchDebugView() {
const [loading, setLoading] = useState(false);
const [viewMode, setViewMode] = useState<ViewMode>('results');
const [selectedMemory, setSelectedMemory] = useState<any | null>(null);
const [expandedSteps, setExpandedSteps] = useState<Set<string>>(new Set());
const [expandedResults, setExpandedResults] = useState<Set<string>>(new Set());
const toggleStep = (step: string) => {
setExpandedSteps(prev => {
const next = new Set(prev);
if (next.has(step)) {
next.delete(step);
} else {
next.add(step);
}
return next;
});
};
const toggleExpandResults = (key: string) => {
setExpandedResults(prev => {
const next = new Set(prev);
if (next.has(key)) {
next.delete(key);
} else {
next.add(key);
}
return next;
});
};
const INITIAL_RESULTS_COUNT = 5;
const runSearch = async () => {
if (!currentBank) {
@ -316,55 +344,431 @@ export function SearchDebugView() {
{/* Trace View */}
{viewMode === 'trace' && trace && (
<Card>
<CardHeader>
<CardTitle className="text-lg">Recall Trace</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* Retrieval Methods */}
{trace.retrieval_results && (
<div className="space-y-4">
{/* Parallel Retrieval Methods - Grouped by Fact Type */}
{trace.retrieval_results && trace.retrieval_results.length > 0 && (() => {
// Group retrieval results by fact type
const factTypeGroups: Record<string, any[]> = {};
trace.retrieval_results.forEach((method: any) => {
const ft = method.fact_type || 'all';
if (!factTypeGroups[ft]) factTypeGroups[ft] = [];
factTypeGroups[ft].push(method);
});
const factTypes = Object.keys(factTypeGroups);
return (
<div>
<h4 className="font-semibold mb-3">Retrieval Methods</h4>
<div className="grid grid-cols-2 gap-4">
{trace.retrieval_results.map((method: any, idx: number) => (
<div key={idx} className="p-4 rounded-lg bg-muted/50">
<div className="flex items-center justify-between mb-2">
<span className="font-medium capitalize">{method.method_name}</span>
<span className="text-sm text-muted-foreground">
{method.duration_seconds?.toFixed(3)}s
</span>
<div className="text-xs font-medium text-muted-foreground mb-3 flex items-center gap-2">
<div className="flex-1 h-px bg-border" />
<span>PARALLEL RETRIEVAL</span>
<div className="flex-1 h-px bg-border" />
</div>
{/* Fact type lanes */}
<div className="space-y-2">
{factTypes.map((factType, ftIdx) => {
const methods = factTypeGroups[factType];
const laneKey = `lane-${factType}`;
const isLaneExpanded = expandedSteps.has(laneKey);
const totalResults = methods.reduce((sum: number, m: any) => sum + (m.results?.length || 0), 0);
const totalDuration = Math.max(...methods.map((m: any) => m.duration_seconds || 0));
// Color coding for fact types
const ftColors: Record<string, { bg: string; text: string; border: string }> = {
world: { bg: 'bg-blue-500/10', text: 'text-blue-500', border: 'border-blue-500/30' },
experience: { bg: 'bg-green-500/10', text: 'text-green-500', border: 'border-green-500/30' },
opinion: { bg: 'bg-purple-500/10', text: 'text-purple-500', border: 'border-purple-500/30' },
all: { bg: 'bg-gray-500/10', text: 'text-gray-500', border: 'border-gray-500/30' },
};
const colors = ftColors[factType] || ftColors.all;
return (
<Card
key={laneKey}
className={`transition-colors ${isLaneExpanded ? 'border-primary' : colors.border}`}
>
<CardContent className="py-3 px-4">
{/* Lane Header */}
<div
className="flex items-center gap-3 cursor-pointer"
onClick={() => toggleStep(laneKey)}
>
<div className={`w-8 h-8 rounded-lg ${colors.bg} flex items-center justify-center`}>
<span className={`text-sm font-bold ${colors.text} capitalize`}>
{factType.charAt(0).toUpperCase()}
</span>
</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-semibold text-foreground capitalize">{factType}</span>
<span className="text-xs text-muted-foreground">
{methods.length} methods
</span>
</div>
{/* Method summary pills */}
<div className="flex gap-1.5 mt-1">
{methods.map((m: any, mIdx: number) => (
<span
key={mIdx}
className="text-[10px] px-2 py-0.5 rounded-full bg-muted text-muted-foreground capitalize"
>
{m.method_name}: {m.results?.length || 0}
</span>
))}
</div>
</div>
<div className="text-right">
<div className="text-2xl font-bold text-foreground">{totalResults}</div>
<div className="text-[10px] text-muted-foreground">{totalDuration.toFixed(2)}s</div>
</div>
{isLaneExpanded ? (
<ChevronDown className="h-5 w-5 text-muted-foreground" />
) : (
<ChevronRight className="h-5 w-5 text-muted-foreground" />
)}
</div>
{/* Expanded: Show methods grid */}
{isLaneExpanded && (
<div className="mt-4 pt-4 border-t border-border">
<div className={`grid gap-3 ${
methods.length === 1 ? 'grid-cols-1' :
methods.length === 2 ? 'grid-cols-2' :
methods.length === 3 ? 'grid-cols-3' :
'grid-cols-4'
}`}>
{methods.map((method: any, mIdx: number) => {
const methodKey = `${laneKey}-method-${mIdx}`;
const isMethodExpanded = expandedSteps.has(methodKey);
const methodResults = method.results || [];
return (
<div key={methodKey} className="flex flex-col">
<div
className={`p-3 rounded-lg cursor-pointer transition-colors ${
isMethodExpanded ? 'bg-primary/10 border border-primary' : 'bg-muted/50 hover:bg-muted'
}`}
onClick={(e) => {
e.stopPropagation();
toggleStep(methodKey);
}}
>
<div className="flex items-center justify-between mb-1">
<span className="font-medium text-sm text-foreground capitalize">{method.method_name}</span>
{isMethodExpanded ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" />
)}
</div>
<div className="flex items-end justify-between">
<div className="text-2xl font-bold text-foreground">{methodResults.length}</div>
<div className="text-[10px] text-muted-foreground">{method.duration_seconds?.toFixed(2)}s</div>
</div>
</div>
{/* Method Results */}
{isMethodExpanded && methodResults.length > 0 && (() => {
const resultsKey = `results-${methodKey}`;
const showAll = expandedResults.has(resultsKey);
const displayResults = showAll ? methodResults : methodResults.slice(0, INITIAL_RESULTS_COUNT);
const hasMore = methodResults.length > INITIAL_RESULTS_COUNT;
return (
<div className="mt-2 space-y-1.5 max-h-[300px] overflow-y-auto">
{displayResults.map((r: any, rIdx: number) => (
<div
key={rIdx}
className="p-2 bg-background rounded cursor-pointer hover:bg-muted/50 transition-colors border border-border"
onClick={(e) => {
e.stopPropagation();
setSelectedMemory(r);
}}
>
<div className="flex items-start gap-2">
<span className="text-[10px] font-mono text-muted-foreground mt-0.5">{rIdx + 1}</span>
<div className="flex-1 min-w-0">
<p className="text-xs text-foreground line-clamp-2">{r.text}</p>
<div className="flex items-center gap-2 mt-1">
<span className="text-[10px] text-muted-foreground">
{(r.score || r.similarity || 0).toFixed(4)}
</span>
</div>
</div>
</div>
</div>
))}
{hasMore && (
<button
className="w-full text-[10px] text-primary hover:text-primary/80 py-1.5 hover:bg-muted/50 rounded transition-colors"
onClick={(e) => {
e.stopPropagation();
toggleExpandResults(resultsKey);
}}
>
{showAll ? `Show less` : `View all ${methodResults.length} results`}
</button>
)}
</div>
);
})()}
</div>
);
})}
</div>
</div>
)}
</CardContent>
</Card>
);
})}
</div>
{/* Parallel indicator - vertical lines showing all run together */}
<div className="flex justify-center py-2">
<div className="flex items-center gap-2">
{factTypes.map((ft, i) => {
const ftColors: Record<string, string> = {
world: 'bg-blue-500',
experience: 'bg-green-500',
opinion: 'bg-purple-500',
all: 'bg-gray-500',
};
return (
<div key={i} className="flex flex-col items-center">
<div className={`w-1 h-4 ${ftColors[ft] || ftColors.all} rounded-full opacity-50`} />
</div>
);
})}
</div>
</div>
<div className="flex justify-center">
<ArrowDown className="h-5 w-5 text-muted-foreground/50" />
</div>
</div>
);
})()}
{/* Step 2: RRF Merge */}
{trace.rrf_merged && (() => {
const stepKey = 'rrf-merge';
const isExpanded = expandedSteps.has(stepKey);
return (
<div>
<Card
className={`cursor-pointer transition-colors ${isExpanded ? 'border-primary' : 'hover:border-primary/50'}`}
onClick={() => toggleStep(stepKey)}
>
<CardContent className="py-4">
<div className="flex items-center gap-4">
<div className="flex-shrink-0 w-10 h-10 rounded-full bg-purple-500/10 flex items-center justify-center">
<span className="text-sm font-bold text-purple-500"></span>
</div>
<div className="text-2xl font-bold">{method.results?.length || 0}</div>
<div className="text-xs text-muted-foreground">results</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-semibold text-foreground">RRF Fusion</span>
<span className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground">merge</span>
</div>
<div className="text-sm text-muted-foreground mt-0.5">
Reciprocal Rank Fusion of all retrieval results
</div>
</div>
<div className="text-2xl font-bold text-foreground">{trace.rrf_merged.length}</div>
{isExpanded ? (
<ChevronDown className="h-5 w-5 text-muted-foreground" />
) : (
<ChevronRight className="h-5 w-5 text-muted-foreground" />
)}
</div>
))}
</div>
</div>
)}
</CardContent>
</Card>
{/* RRF Merge */}
{trace.rrf_merged && (
<div>
<h4 className="font-semibold mb-3">RRF Merge</h4>
<div className="p-4 rounded-lg bg-muted/50">
<div className="text-2xl font-bold">{trace.rrf_merged.length}</div>
<div className="text-xs text-muted-foreground">candidates after fusion</div>
</div>
</div>
)}
{/* Expanded Results */}
{isExpanded && trace.rrf_merged.length > 0 && (() => {
const resultsKey = 'results-rrf';
const showAll = expandedResults.has(resultsKey);
const displayResults = showAll ? trace.rrf_merged : trace.rrf_merged.slice(0, INITIAL_RESULTS_COUNT);
const hasMore = trace.rrf_merged.length > INITIAL_RESULTS_COUNT;
{/* Reranking */}
{trace.reranked && (
<div>
<h4 className="font-semibold mb-3">Reranking</h4>
<div className="p-4 rounded-lg bg-muted/50">
<div className="text-2xl font-bold">{trace.reranked.length}</div>
<div className="text-xs text-muted-foreground">results after cross-encoder</div>
return (
<div className="ml-6 mt-2 space-y-2 border-l-2 border-muted pl-4 max-h-[400px] overflow-y-auto">
{displayResults.map((r: any, rIdx: number) => (
<div
key={rIdx}
className="p-3 bg-muted/30 rounded-lg cursor-pointer hover:bg-muted/50 transition-colors"
onClick={(e) => {
e.stopPropagation();
setSelectedMemory(r);
}}
>
<div className="flex items-start gap-3">
<span className="text-xs font-mono text-muted-foreground">{rIdx + 1}</span>
<div className="flex-1 min-w-0">
<p className="text-sm text-foreground line-clamp-2">{r.text}</p>
<div className="text-xs text-muted-foreground mt-1">
RRF Score: {(r.rrf_score || r.score || 0).toFixed(4)}
</div>
</div>
</div>
</div>
))}
{hasMore && (
<button
className="w-full text-xs text-primary hover:text-primary/80 py-2 hover:bg-muted/50 rounded transition-colors"
onClick={(e) => {
e.stopPropagation();
toggleExpandResults(resultsKey);
}}
>
{showAll ? `Show less` : `View all ${trace.rrf_merged.length} results`}
</button>
)}
</div>
);
})()}
{/* Arrow */}
<div className="flex justify-center py-2">
<ArrowDown className="h-4 w-4 text-muted-foreground/50" />
</div>
</div>
)}
</CardContent>
</Card>
);
})()}
{/* Step 3: Combined Scoring */}
{trace.reranked && (() => {
const stepKey = 'reranking';
const isExpanded = expandedSteps.has(stepKey);
return (
<div>
<Card
className={`cursor-pointer transition-colors ${isExpanded ? 'border-primary' : 'hover:border-primary/50'}`}
onClick={() => toggleStep(stepKey)}
>
<CardContent className="py-4">
<div className="flex items-center gap-4">
<div className="flex-shrink-0 w-10 h-10 rounded-full bg-amber-500/10 flex items-center justify-center">
<span className="text-sm font-bold text-amber-500"></span>
</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-semibold text-foreground">Combined Scoring</span>
<span className="text-xs px-2 py-0.5 rounded bg-muted text-muted-foreground">rerank</span>
</div>
<div className="text-sm text-muted-foreground mt-0.5">
<span className="font-mono text-xs">0.6×cross_encoder + 0.2×rrf + 0.1×temporal + 0.1×recency</span>
</div>
</div>
<div className="text-2xl font-bold text-foreground">{trace.reranked.length}</div>
{isExpanded ? (
<ChevronDown className="h-5 w-5 text-muted-foreground" />
) : (
<ChevronRight className="h-5 w-5 text-muted-foreground" />
)}
</div>
</CardContent>
</Card>
{/* Expanded Results */}
{isExpanded && trace.reranked.length > 0 && (() => {
const resultsKey = 'results-rerank';
const showAll = expandedResults.has(resultsKey);
const displayResults = showAll ? trace.reranked : trace.reranked.slice(0, INITIAL_RESULTS_COUNT);
const hasMore = trace.reranked.length > INITIAL_RESULTS_COUNT;
return (
<div className="ml-6 mt-2 space-y-2 border-l-2 border-muted pl-4 max-h-[400px] overflow-y-auto">
{displayResults.map((r: any, rIdx: number) => {
const sc = r.score_components || {};
return (
<div
key={rIdx}
className="p-3 bg-muted/30 rounded-lg cursor-pointer hover:bg-muted/50 transition-colors"
onClick={(e) => {
e.stopPropagation();
setSelectedMemory(r);
}}
>
<div className="flex items-start gap-3">
<span className="text-xs font-mono text-muted-foreground">{rIdx + 1}</span>
<div className="flex-1 min-w-0">
<p className="text-sm text-foreground line-clamp-2">{r.text}</p>
<div className="flex flex-wrap gap-x-3 gap-y-1 mt-2 text-[10px] text-muted-foreground font-mono">
<span className="font-semibold text-foreground">
= {(r.rerank_score || r.score || 0).toFixed(4)}
</span>
{sc.cross_encoder_score_normalized !== undefined && (
<span title="Cross-encoder (60%)">
CE: {sc.cross_encoder_score_normalized.toFixed(3)}
</span>
)}
{sc.rrf_normalized !== undefined && (
<span title={`RRF normalized (20%) - raw: ${sc.rrf_score?.toFixed(4) || 'N/A'}`}>
RRF: {sc.rrf_normalized.toFixed(3)}
</span>
)}
{sc.temporal !== undefined && (
<span title="Temporal proximity (10%)">
Tmp: {sc.temporal.toFixed(3)}
</span>
)}
{sc.recency !== undefined && (
<span title="Recency (10%)">
Rec: {sc.recency.toFixed(3)}
</span>
)}
</div>
</div>
</div>
</div>
);
})}
{hasMore && (
<button
className="w-full text-xs text-primary hover:text-primary/80 py-2 hover:bg-muted/50 rounded transition-colors"
onClick={(e) => {
e.stopPropagation();
toggleExpandResults(resultsKey);
}}
>
{showAll ? `Show less` : `View all ${trace.reranked.length} results`}
</button>
)}
</div>
);
})()}
{/* Arrow */}
<div className="flex justify-center py-2">
<ArrowDown className="h-4 w-4 text-muted-foreground/50" />
</div>
</div>
);
})()}
{/* Final: Results */}
<Card className="border-primary bg-primary/5">
<CardContent className="py-4">
<div className="flex items-center gap-4">
<div className="flex-shrink-0 w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center">
<span className="text-sm font-bold text-primary"></span>
</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-semibold text-foreground">Final Results</span>
<span className="text-xs px-2 py-0.5 rounded bg-primary/20 text-primary">output</span>
</div>
<div className="text-sm text-muted-foreground mt-0.5">
Top results after all processing steps
</div>
</div>
<div className="text-2xl font-bold text-primary">{results?.length || 0}</div>
</div>
</CardContent>
</Card>
</div>
)}
{/* JSON View */}

212
uv.lock
View file

@ -1141,7 +1141,7 @@ wheels = [
[[package]]
name = "hindsight-all"
version = "0.1.3"
version = "0.1.4"
source = { editable = "hindsight" }
dependencies = [
{ name = "hindsight-api" },
@ -1165,7 +1165,7 @@ provides-extras = ["test"]
[[package]]
name = "hindsight-api"
version = "0.1.3"
version = "0.1.4"
source = { editable = "hindsight-api" }
dependencies = [
{ name = "alembic" },
@ -1243,11 +1243,11 @@ requires-dist = [
{ name = "python-dateutil", specifier = ">=2.8.0" },
{ name = "python-dotenv", specifier = ">=1.0.0" },
{ name = "rich", specifier = ">=13.0.0" },
{ name = "sentence-transformers", specifier = ">=3.0.0" },
{ name = "sentence-transformers", specifier = ">=3.0.0,<3.3.0" },
{ name = "sqlalchemy", specifier = ">=2.0.44" },
{ name = "tiktoken", specifier = ">=0.12.0" },
{ name = "torch", specifier = ">=2.0.0" },
{ name = "transformers", specifier = ">=4.30.0" },
{ name = "torch", specifier = ">=2.0.0,<2.6.0" },
{ name = "transformers", specifier = ">=4.30.0,<4.46.0" },
{ name = "uvicorn", specifier = ">=0.38.0" },
{ name = "wsproto", specifier = ">=1.0.0" },
]
@ -1265,7 +1265,7 @@ dev = [
[[package]]
name = "hindsight-client"
version = "0.1.3"
version = "0.1.4"
source = { editable = "hindsight-clients/python" }
dependencies = [
{ name = "aiohttp" },
@ -1297,7 +1297,7 @@ provides-extras = ["test"]
[[package]]
name = "hindsight-dev"
version = "0.1.3"
version = "0.1.4"
source = { editable = "hindsight-dev" }
dependencies = [
{ name = "hindsight-api" },
@ -2095,77 +2095,69 @@ wheels = [
[[package]]
name = "nvidia-cublas-cu12"
version = "12.8.4.1"
version = "12.4.5.8"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921 },
{ url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805 },
]
[[package]]
name = "nvidia-cuda-cupti-cu12"
version = "12.8.90"
version = "12.4.127"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621 },
{ url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957 },
]
[[package]]
name = "nvidia-cuda-nvrtc-cu12"
version = "12.8.93"
version = "12.4.127"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029 },
{ url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306 },
]
[[package]]
name = "nvidia-cuda-runtime-cu12"
version = "12.8.90"
version = "12.4.127"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765 },
{ url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737 },
]
[[package]]
name = "nvidia-cudnn-cu12"
version = "9.10.2.21"
version = "9.1.0.70"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas-cu12" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467 },
{ url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741 },
]
[[package]]
name = "nvidia-cufft-cu12"
version = "11.3.3.83"
version = "11.2.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink-cu12" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695 },
]
[[package]]
name = "nvidia-cufile-cu12"
version = "1.13.1.3"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834 },
{ url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117 },
]
[[package]]
name = "nvidia-curand-cu12"
version = "10.3.9.90"
version = "10.3.5.147"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976 },
{ url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206 },
]
[[package]]
name = "nvidia-cusolver-cu12"
version = "11.7.3.90"
version = "11.6.1.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-cublas-cu12" },
@ -2173,58 +2165,42 @@ dependencies = [
{ name = "nvidia-nvjitlink-cu12" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905 },
{ url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057 },
]
[[package]]
name = "nvidia-cusparse-cu12"
version = "12.5.8.93"
version = "12.3.1.170"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "nvidia-nvjitlink-cu12" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466 },
]
[[package]]
name = "nvidia-cusparselt-cu12"
version = "0.7.1"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691 },
{ url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763 },
]
[[package]]
name = "nvidia-nccl-cu12"
version = "2.27.5"
version = "2.21.5"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229 },
{ url = "https://files.pythonhosted.org/packages/df/99/12cd266d6233f47d00daf3a72739872bdc10267d0383508b0b9c84a18bb6/nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0", size = 188654414 },
]
[[package]]
name = "nvidia-nvjitlink-cu12"
version = "12.8.93"
version = "12.4.127"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836 },
]
[[package]]
name = "nvidia-nvshmem-cu12"
version = "3.3.20"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/6c/99acb2f9eb85c29fc6f3a7ac4dccfd992e22666dd08a642b303311326a97/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5", size = 124657145 },
{ url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810 },
]
[[package]]
name = "nvidia-nvtx-cu12"
version = "12.8.90"
version = "12.4.127"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954 },
{ url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 },
]
[[package]]
@ -3830,7 +3806,7 @@ wheels = [
[[package]]
name = "sentence-transformers"
version = "5.1.2"
version = "3.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "huggingface-hub" },
@ -3840,11 +3816,10 @@ dependencies = [
{ name = "torch" },
{ name = "tqdm" },
{ name = "transformers" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0f/96/f3f3409179d14dbfdbea8622e2e9eaa3c8836ddcaecd2cd5ff0a11731d20/sentence_transformers-5.1.2.tar.gz", hash = "sha256:0f6c8bd916a78dc65b366feb8d22fd885efdb37432e7630020d113233af2b856", size = 375185 }
sdist = { url = "https://files.pythonhosted.org/packages/de/61/708b20dedf26c460b416beb0acd5474c190dbca13e93b40858e99f17ac46/sentence_transformers-3.2.1.tar.gz", hash = "sha256:9fc38e620e5e1beba31d538a451778c9ccdbad77119d90f59f5bce49c4148e79", size = 202527 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bb/a6/a607a737dc1a00b7afe267b9bfde101b8cee2529e197e57471d23137d4e5/sentence_transformers-5.1.2-py3-none-any.whl", hash = "sha256:724ce0ea62200f413f1a5059712aff66495bc4e815a1493f7f9bca242414c333", size = 488009 },
{ url = "https://files.pythonhosted.org/packages/45/18/1ec591befcbdb2c97192a40fbe7c43a8b8a8b3c89b1fa101d3eeed4d79a4/sentence_transformers-3.2.1-py3-none-any.whl", hash = "sha256:c507e069eea33d15f1f2c72f74d7ea93abef298152cc235ab5af5e3a7584f738", size = 255758 },
]
[[package]]
@ -4007,14 +3982,14 @@ wheels = [
[[package]]
name = "sympy"
version = "1.14.0"
version = "1.13.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mpmath" },
]
sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921 }
sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353 },
{ url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177 },
]
[[package]]
@ -4091,27 +4066,49 @@ wheels = [
[[package]]
name = "tokenizers"
version = "0.22.1"
version = "0.20.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "huggingface-hub" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1c/46/fb6854cec3278fbfa4a75b50232c77622bc517ac886156e6afbfa4d8fc6e/tokenizers-0.22.1.tar.gz", hash = "sha256:61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9", size = 363123 }
sdist = { url = "https://files.pythonhosted.org/packages/da/25/b1681c1c30ea3ea6e584ae3fffd552430b12faa599b558c4c4783f56d7ff/tokenizers-0.20.3.tar.gz", hash = "sha256:2278b34c5d0dd78e087e1ca7f9b1dcbf129d80211afa645f214bd6e051037539", size = 340513 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/33/f4b2d94ada7ab297328fc671fed209368ddb82f965ec2224eb1892674c3a/tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73", size = 3069318 },
{ url = "https://files.pythonhosted.org/packages/1c/58/2aa8c874d02b974990e89ff95826a4852a8b2a273c7d1b4411cdd45a4565/tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc", size = 2926478 },
{ url = "https://files.pythonhosted.org/packages/1e/3b/55e64befa1e7bfea963cf4b787b2cea1011362c4193f5477047532ce127e/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a", size = 3256994 },
{ url = "https://files.pythonhosted.org/packages/71/0b/fbfecf42f67d9b7b80fde4aabb2b3110a97fac6585c9470b5bff103a80cb/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7", size = 3153141 },
{ url = "https://files.pythonhosted.org/packages/17/a9/b38f4e74e0817af8f8ef925507c63c6ae8171e3c4cb2d5d4624bf58fca69/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21", size = 3508049 },
{ url = "https://files.pythonhosted.org/packages/d2/48/dd2b3dac46bb9134a88e35d72e1aa4869579eacc1a27238f1577270773ff/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214", size = 3710730 },
{ url = "https://files.pythonhosted.org/packages/93/0e/ccabc8d16ae4ba84a55d41345207c1e2ea88784651a5a487547d80851398/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f", size = 3412560 },
{ url = "https://files.pythonhosted.org/packages/d0/c6/dc3a0db5a6766416c32c034286d7c2d406da1f498e4de04ab1b8959edd00/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4", size = 3250221 },
{ url = "https://files.pythonhosted.org/packages/d7/a6/2c8486eef79671601ff57b093889a345dd3d576713ef047776015dc66de7/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879", size = 9345569 },
{ url = "https://files.pythonhosted.org/packages/6b/16/32ce667f14c35537f5f605fe9bea3e415ea1b0a646389d2295ec348d5657/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446", size = 9271599 },
{ url = "https://files.pythonhosted.org/packages/51/7c/a5f7898a3f6baa3fc2685c705e04c98c1094c523051c805cdd9306b8f87e/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a", size = 9533862 },
{ url = "https://files.pythonhosted.org/packages/36/65/7e75caea90bc73c1dd8d40438adf1a7bc26af3b8d0a6705ea190462506e1/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390", size = 9681250 },
{ url = "https://files.pythonhosted.org/packages/30/2c/959dddef581b46e6209da82df3b78471e96260e2bc463f89d23b1bf0e52a/tokenizers-0.22.1-cp39-abi3-win32.whl", hash = "sha256:b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82", size = 2472003 },
{ url = "https://files.pythonhosted.org/packages/b3/46/e33a8c93907b631a99377ef4c5f817ab453d0b34f93529421f42ff559671/tokenizers-0.22.1-cp39-abi3-win_amd64.whl", hash = "sha256:65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138", size = 2674684 },
{ url = "https://files.pythonhosted.org/packages/c6/93/6742ef9206409d5ce1fdf44d5ca1687cdc3847ba0485424e2c731e6bcf67/tokenizers-0.20.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:585b51e06ca1f4839ce7759941e66766d7b060dccfdc57c4ca1e5b9a33013a90", size = 2674224 },
{ url = "https://files.pythonhosted.org/packages/aa/14/e75ece72e99f6ef9ae07777ca9fdd78608f69466a5cecf636e9bd2f25d5c/tokenizers-0.20.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61cbf11954f3b481d08723ebd048ba4b11e582986f9be74d2c3bdd9293a4538d", size = 2558991 },
{ url = "https://files.pythonhosted.org/packages/46/54/033b5b2ba0c3ae01e026c6f7ced147d41a2fa1c573d00a66cb97f6d7f9b3/tokenizers-0.20.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef820880d5e4e8484e2fa54ff8d297bb32519eaa7815694dc835ace9130a3eea", size = 2892476 },
{ url = "https://files.pythonhosted.org/packages/e6/b0/cc369fb3297d61f3311cab523d16d48c869dc2f0ba32985dbf03ff811041/tokenizers-0.20.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:67ef4dcb8841a4988cd00dd288fb95dfc8e22ed021f01f37348fd51c2b055ba9", size = 2802775 },
{ url = "https://files.pythonhosted.org/packages/1a/74/62ad983e8ea6a63e04ed9c5be0b605056bf8aac2f0125f9b5e0b3e2b89fa/tokenizers-0.20.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff1ef8bd47a02b0dc191688ccb4da53600df5d4c9a05a4b68e1e3de4823e78eb", size = 3086138 },
{ url = "https://files.pythonhosted.org/packages/6b/ac/4637ba619db25094998523f9e6f5b456e1db1f8faa770a3d925d436db0c3/tokenizers-0.20.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:444d188186eab3148baf0615b522461b41b1f0cd58cd57b862ec94b6ac9780f1", size = 3098076 },
{ url = "https://files.pythonhosted.org/packages/58/ce/9793f2dc2ce529369807c9c74e42722b05034af411d60f5730b720388c7d/tokenizers-0.20.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37c04c032c1442740b2c2d925f1857885c07619224a533123ac7ea71ca5713da", size = 3379650 },
{ url = "https://files.pythonhosted.org/packages/50/f6/2841de926bc4118af996eaf0bdf0ea5b012245044766ffc0347e6c968e63/tokenizers-0.20.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:453c7769d22231960ee0e883d1005c93c68015025a5e4ae56275406d94a3c907", size = 2994005 },
{ url = "https://files.pythonhosted.org/packages/a3/b2/00915c4fed08e9505d37cf6eaab45b12b4bff8f6719d459abcb9ead86a4b/tokenizers-0.20.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4bb31f7b2847e439766aaa9cc7bccf7ac7088052deccdb2275c952d96f691c6a", size = 8977488 },
{ url = "https://files.pythonhosted.org/packages/e9/ac/1c069e7808181ff57bcf2d39e9b6fbee9133a55410e6ebdaa89f67c32e83/tokenizers-0.20.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:843729bf0f991b29655a069a2ff58a4c24375a553c70955e15e37a90dd4e045c", size = 9294935 },
{ url = "https://files.pythonhosted.org/packages/50/47/722feb70ee68d1c4412b12d0ea4acc2713179fd63f054913990f9e259492/tokenizers-0.20.3-cp311-none-win32.whl", hash = "sha256:efcce3a927b1e20ca694ba13f7a68c59b0bd859ef71e441db68ee42cf20c2442", size = 2197175 },
{ url = "https://files.pythonhosted.org/packages/75/68/1b4f928b15a36ed278332ac75d66d7eb65d865bf344d049c452c18447bf9/tokenizers-0.20.3-cp311-none-win_amd64.whl", hash = "sha256:88301aa0801f225725b6df5dea3d77c80365ff2362ca7e252583f2b4809c4cc0", size = 2381616 },
{ url = "https://files.pythonhosted.org/packages/07/00/92a08af2a6b0c88c50f1ab47d7189e695722ad9714b0ee78ea5e1e2e1def/tokenizers-0.20.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:49d12a32e190fad0e79e5bdb788d05da2f20d8e006b13a70859ac47fecf6ab2f", size = 2667951 },
{ url = "https://files.pythonhosted.org/packages/ec/9a/e17a352f0bffbf415cf7d73756f5c73a3219225fc5957bc2f39d52c61684/tokenizers-0.20.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:282848cacfb9c06d5e51489f38ec5aa0b3cd1e247a023061945f71f41d949d73", size = 2555167 },
{ url = "https://files.pythonhosted.org/packages/27/37/d108df55daf4f0fcf1f58554692ff71687c273d870a34693066f0847be96/tokenizers-0.20.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abe4e08c7d0cd6154c795deb5bf81d2122f36daf075e0c12a8b050d824ef0a64", size = 2898389 },
{ url = "https://files.pythonhosted.org/packages/b2/27/32f29da16d28f59472fa7fb38e7782069748c7e9ab9854522db20341624c/tokenizers-0.20.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca94fc1b73b3883c98f0c88c77700b13d55b49f1071dfd57df2b06f3ff7afd64", size = 2795866 },
{ url = "https://files.pythonhosted.org/packages/29/4e/8a9a3c89e128c4a40f247b501c10279d2d7ade685953407c4d94c8c0f7a7/tokenizers-0.20.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef279c7e239f95c8bdd6ff319d9870f30f0d24915b04895f55b1adcf96d6c60d", size = 3085446 },
{ url = "https://files.pythonhosted.org/packages/b4/3b/a2a7962c496ebcd95860ca99e423254f760f382cd4bd376f8895783afaf5/tokenizers-0.20.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16384073973f6ccbde9852157a4fdfe632bb65208139c9d0c0bd0176a71fd67f", size = 3094378 },
{ url = "https://files.pythonhosted.org/packages/1f/f4/a8a33f0192a1629a3bd0afcad17d4d221bbf9276da4b95d226364208d5eb/tokenizers-0.20.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:312d522caeb8a1a42ebdec87118d99b22667782b67898a76c963c058a7e41d4f", size = 3385755 },
{ url = "https://files.pythonhosted.org/packages/9e/65/c83cb3545a65a9eaa2e13b22c93d5e00bd7624b354a44adbdc93d5d9bd91/tokenizers-0.20.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2b7cb962564785a83dafbba0144ecb7f579f1d57d8c406cdaa7f32fe32f18ad", size = 2997679 },
{ url = "https://files.pythonhosted.org/packages/55/e9/a80d4e592307688a67c7c59ab77e03687b6a8bd92eb5db763a2c80f93f57/tokenizers-0.20.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:124c5882ebb88dadae1fc788a582299fcd3a8bd84fc3e260b9918cf28b8751f5", size = 8989296 },
{ url = "https://files.pythonhosted.org/packages/90/af/60c957af8d2244321124e893828f1a4817cde1a2d08d09d423b73f19bd2f/tokenizers-0.20.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2b6e54e71f84c4202111a489879005cb14b92616a87417f6c102c833af961ea2", size = 9303621 },
{ url = "https://files.pythonhosted.org/packages/be/a9/96172310ee141009646d63a1ca267c099c462d747fe5ef7e33f74e27a683/tokenizers-0.20.3-cp312-none-win32.whl", hash = "sha256:83d9bfbe9af86f2d9df4833c22e94d94750f1d0cd9bfb22a7bb90a86f61cdb1c", size = 2188979 },
{ url = "https://files.pythonhosted.org/packages/bd/68/61d85ae7ae96dde7d0974ff3538db75d5cdc29be2e4329cd7fc51a283e22/tokenizers-0.20.3-cp312-none-win_amd64.whl", hash = "sha256:44def74cee574d609a36e17c8914311d1b5dbcfe37c55fd29369d42591b91cf2", size = 2380725 },
{ url = "https://files.pythonhosted.org/packages/07/19/36e9eaafb229616cb8502b42030fa7fe347550e76cb618de71b498fc3222/tokenizers-0.20.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0b630e0b536ef0e3c8b42c685c1bc93bd19e98c0f1543db52911f8ede42cf84", size = 2666813 },
{ url = "https://files.pythonhosted.org/packages/b9/c7/e2ce1d4f756c8a62ef93fdb4df877c2185339b6d63667b015bf70ea9d34b/tokenizers-0.20.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a02d160d2b19bcbfdf28bd9a4bf11be4cb97d0499c000d95d4c4b1a4312740b6", size = 2555354 },
{ url = "https://files.pythonhosted.org/packages/7c/cf/5309c2d173a6a67f9ec8697d8e710ea32418de6fd8541778032c202a1c3e/tokenizers-0.20.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e3d80d89b068bc30034034b5319218c7c0a91b00af19679833f55f3becb6945", size = 2897745 },
{ url = "https://files.pythonhosted.org/packages/2c/e5/af3078e32f225e680e69d61f78855880edb8d53f5850a1834d519b2b103f/tokenizers-0.20.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:174a54910bed1b089226512b4458ea60d6d6fd93060254734d3bc3540953c51c", size = 2794385 },
{ url = "https://files.pythonhosted.org/packages/0b/a7/bc421fe46650cc4eb4a913a236b88c243204f32c7480684d2f138925899e/tokenizers-0.20.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:098b8a632b8656aa5802c46689462c5c48f02510f24029d71c208ec2c822e771", size = 3084580 },
{ url = "https://files.pythonhosted.org/packages/c6/22/97e1e95ee81f75922c9f569c23cb2b1fdc7f5a7a29c4c9fae17e63f751a6/tokenizers-0.20.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78c8c143e3ae41e718588281eb3e212c2b31623c9d6d40410ec464d7d6221fb5", size = 3093581 },
{ url = "https://files.pythonhosted.org/packages/d5/14/f0df0ee3b9e516121e23c0099bccd7b9f086ba9150021a750e99b16ce56f/tokenizers-0.20.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b26b0aadb18cd8701077362ba359a06683662d5cafe3e8e8aba10eb05c037f1", size = 3385934 },
{ url = "https://files.pythonhosted.org/packages/66/52/7a171bd4929e3ffe61a29b4340fe5b73484709f92a8162a18946e124c34c/tokenizers-0.20.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07d7851a72717321022f3774e84aa9d595a041d643fafa2e87fbc9b18711dac0", size = 2997311 },
{ url = "https://files.pythonhosted.org/packages/7c/64/f1993bb8ebf775d56875ca0d50a50f2648bfbbb143da92fe2e6ceeb4abd5/tokenizers-0.20.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bd44e48a430ada902c6266a8245f5036c4fe744fcb51f699999fbe82aa438797", size = 8988601 },
{ url = "https://files.pythonhosted.org/packages/d6/3f/49fa63422159bbc2f2a4ac5bfc597d04d4ec0ad3d2ef46649b5e9a340e37/tokenizers-0.20.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a4c186bb006ccbe1f5cc4e0380d1ce7806f5955c244074fd96abc55e27b77f01", size = 9303950 },
{ url = "https://files.pythonhosted.org/packages/66/11/79d91aeb2817ad1993ef61c690afe73e6dbedbfb21918b302ef5a2ba9bfb/tokenizers-0.20.3-cp313-none-win32.whl", hash = "sha256:6e19e0f1d854d6ab7ea0c743d06e764d1d9a546932be0a67f33087645f00fe13", size = 2188941 },
{ url = "https://files.pythonhosted.org/packages/c2/ff/ac8410f868fb8b14b5e619efa304aa119cb8a40bd7df29fc81a898e64f99/tokenizers-0.20.3-cp313-none-win_amd64.whl", hash = "sha256:d50ede425c7e60966a9680d41b58b3a0950afa1bb570488e2972fa61662c4273", size = 2380269 },
]
[[package]]
@ -4125,7 +4122,7 @@ wheels = [
[[package]]
name = "torch"
version = "2.9.0"
version = "2.5.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filelock" },
@ -4138,45 +4135,27 @@ dependencies = [
{ name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "setuptools", marker = "python_full_version >= '3.12'" },
{ name = "sympy" },
{ name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'" },
{ name = "typing-extensions" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/fe/334225e6330e672b36aef23d77451fa906ea12881570c08638a91331a212/torch-2.9.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c596708b5105d0b199215acf0c9be7c1db5f1680d88eddadf4b75a299259a677", size = 104230578 },
{ url = "https://files.pythonhosted.org/packages/05/cc/49566caaa218872ec9a2912456f470ff92649894a4bc2e5274aa9ef87c4a/torch-2.9.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:51de31219c97c51cf4bf2be94d622e3deb5dcc526c6dc00e97c17eaec0fc1d67", size = 899815990 },
{ url = "https://files.pythonhosted.org/packages/74/25/e9ab21d5925b642d008f139d4a3c9664fc9ee1faafca22913c080cc4c0a5/torch-2.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd515c70059afd95f48b8192733764c08ca37a1d19803af6401b5ecad7c8676e", size = 109313698 },
{ url = "https://files.pythonhosted.org/packages/b3/b7/205ef3e94de636feffd64b28bb59a0dfac0771221201b9871acf9236f5ca/torch-2.9.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:614a185e4986326d526a91210c8fc1397e76e8cfafa78baf6296a790e53a9eec", size = 74463678 },
{ url = "https://files.pythonhosted.org/packages/d1/d3/3985739f3b8e88675127bf70f82b3a48ae083e39cda56305dbd90398fec0/torch-2.9.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e5f7af1dc4c0a7c4a260c2534f41ddaf209714f7c89145e644c44712fbd6b642", size = 104107898 },
{ url = "https://files.pythonhosted.org/packages/a5/4b/f4bb2e6c25d0272f798cd6d7a04ed315da76cec68c602d87040c7847287f/torch-2.9.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:01cff95ecd9a212ea2f141db28acccdceb6a4c54f64e6c51091146f5e2a772c6", size = 899738273 },
{ url = "https://files.pythonhosted.org/packages/66/11/c1c5ba6691cda6279087c35bd626536e4fd29521fe740abf5008377a9a02/torch-2.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:4582b162f541651f0cb184d3e291c05c2f556c7117c64a9873e2ee158d40062b", size = 109280887 },
{ url = "https://files.pythonhosted.org/packages/dd/5f/b85bd8c05312d71de9402bf5868d217c38827cfd09d8f8514e5be128a52b/torch-2.9.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:33f58e9a102a91259af289d50525c30323b5c9ae1d31322b6447c0814da68695", size = 74478983 },
{ url = "https://files.pythonhosted.org/packages/c2/1c/90eb13833cdf4969ea9707586d7b57095c3b6e2b223a7256bf111689bcb8/torch-2.9.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c30a17fc83eeab346913e237c64b15b5ba6407fff812f6c541e322e19bc9ea0e", size = 104111330 },
{ url = "https://files.pythonhosted.org/packages/0e/21/2254c54b8d523592c25ef4434769aa23e29b1e6bf5f4c0ad9e27bf442927/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8f25033b8667b57857dfd01458fbf2a9e6a6df1f8def23aef0dc46292f6aa642", size = 899750243 },
{ url = "https://files.pythonhosted.org/packages/b7/a5/5cb94fa4fd1e78223455c23c200f30f6dc10c6d4a2bcc8f6e7f2a2588370/torch-2.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:d037f1b4ffd25013be4a7bf3651a0a910c68554956c7b2c92ebe87c76475dece", size = 109284513 },
{ url = "https://files.pythonhosted.org/packages/66/e8/fc414d8656250ee46120b44836ffbb3266343db424b3e18ca79ebbf69d4f/torch-2.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e4e5b5cba837a2a8d1a497ba9a58dae46fa392593eaa13b871c42f71847503a5", size = 74830362 },
{ url = "https://files.pythonhosted.org/packages/ed/5f/9474c98fc5ae0cd04b9466035428cd360e6611a86b8352a0fc2fa504acdc/torch-2.9.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:64693568f5dc4dbd5f880a478b1cea0201cc6b510d91d1bc54fea86ac5d1a637", size = 104144940 },
{ url = "https://files.pythonhosted.org/packages/2d/5a/8e0c1cf57830172c109d4bd6be2708cabeaf550983eee7029291322447a0/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:f8ed31ddd7d10bfb3fbe0b9fe01b1243577f13d75e6f4a0839a283915ce3791e", size = 899744054 },
{ url = "https://files.pythonhosted.org/packages/6d/28/82c28b30fcb4b7c9cdd995763d18bbb830d6521356712faebbad92ffa61d/torch-2.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:eff527d4e4846e6f70d2afd8058b73825761203d66576a7e04ea2ecfebcb4ab8", size = 109517546 },
{ url = "https://files.pythonhosted.org/packages/ff/c3/a91f96ec74347fa5fd24453fa514bc61c61ecc79196fa760b012a1873d96/torch-2.9.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:f8877779cf56d1ce431a7636703bdb13307f5960bb1af49716d8b179225e0e6a", size = 74480732 },
{ url = "https://files.pythonhosted.org/packages/5c/73/9f70af34b334a7e0ef496ceec96b7ec767bd778ea35385ce6f77557534d1/torch-2.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7e614fae699838038d888729f82b687c03413c5989ce2a9481f9a7e7a396e0bb", size = 74433037 },
{ url = "https://files.pythonhosted.org/packages/b7/84/37cf88625901934c97109e583ecc21777d21c6f54cda97a7e5bbad1ee2f2/torch-2.9.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:dfb5b8cd310ba3436c7e14e8b7833ef658cf3045e50d2bdaed23c8fc517065eb", size = 104116482 },
{ url = "https://files.pythonhosted.org/packages/56/8e/ca8b17866943a8d4f4664d402ea84210aa274588b4c5d89918f5caa24eec/torch-2.9.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b3d29524993a478e46f5d598b249cd824b7ed98d7fba538bd9c4cde6c803948f", size = 899746916 },
{ url = "https://files.pythonhosted.org/packages/43/65/3b17c0fbbdab6501c5b320a52a648628d0d44e7379f64e27d9eef701b6bf/torch-2.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:71c7578984f5ec0eb645eb4816ac8435fcf3e3e2ae1901bcd2f519a9cafb5125", size = 109275151 },
{ url = "https://files.pythonhosted.org/packages/83/36/74f8c051f785500396e42f93542422422dfd874a174f21f8d955d36e5d64/torch-2.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:71d9309aee457bbe0b164bce2111cd911c4ed4e847e65d5077dbbcd3aba6befc", size = 74823353 },
{ url = "https://files.pythonhosted.org/packages/62/51/dc3b4e2f9ba98ae27238f0153ca098bf9340b2dafcc67fde645d496dfc2a/torch-2.9.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c08fb654d783899e204a32cca758a7ce8a45b2d78eeb89517cc937088316f78e", size = 104140340 },
{ url = "https://files.pythonhosted.org/packages/c0/8d/b00657f8141ac16af7bb6cda2e67de18499a3263b78d516b9a93fcbc98e3/torch-2.9.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ec8feb0099b2daa5728fbc7abb0b05730fd97e0f359ff8bda09865aaa7bd7d4b", size = 899731750 },
{ url = "https://files.pythonhosted.org/packages/fc/29/bd361e0cbb2c79ce6450f42643aaf6919956f89923a50571b0ebfe92d142/torch-2.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:695ba920f234ad4170c9c50e28d56c848432f8f530e6bc7f88fcb15ddf338e75", size = 109503850 },
{ url = "https://files.pythonhosted.org/packages/d1/35/e8b2daf02ce933e4518e6f5682c72fd0ed66c15910ea1fb4168f442b71c4/torch-2.5.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:de5b7d6740c4b636ef4db92be922f0edc425b65ed78c5076c43c42d362a45457", size = 906474467 },
{ url = "https://files.pythonhosted.org/packages/40/04/bd91593a4ca178ece93ca55f27e2783aa524aaccbfda66831d59a054c31e/torch-2.5.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:340ce0432cad0d37f5a31be666896e16788f1adf8ad7be481196b503dad675b9", size = 91919450 },
{ url = "https://files.pythonhosted.org/packages/0d/4a/e51420d46cfc90562e85af2fee912237c662ab31140ab179e49bd69401d6/torch-2.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:603c52d2fe06433c18b747d25f5c333f9c1d58615620578c326d66f258686f9a", size = 203098237 },
{ url = "https://files.pythonhosted.org/packages/d0/db/5d9cbfbc7968d79c5c09a0bc0bc3735da079f2fd07cc10498a62b320a480/torch-2.5.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:31f8c39660962f9ae4eeec995e3049b5492eb7360dd4f07377658ef4d728fa4c", size = 63884466 },
{ url = "https://files.pythonhosted.org/packages/8b/5c/36c114d120bfe10f9323ed35061bc5878cc74f3f594003854b0ea298942f/torch-2.5.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:ed231a4b3a5952177fafb661213d690a72caaad97d5824dd4fc17ab9e15cec03", size = 906389343 },
{ url = "https://files.pythonhosted.org/packages/6d/69/d8ada8b6e0a4257556d5b4ddeb4345ea8eeaaef3c98b60d1cca197c7ad8e/torch-2.5.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:3f4b7f10a247e0dcd7ea97dc2d3bfbfc90302ed36d7f3952b0008d0df264e697", size = 91811673 },
{ url = "https://files.pythonhosted.org/packages/5f/ba/607d013b55b9fd805db2a5c2662ec7551f1910b4eef39653eeaba182c5b2/torch-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:73e58e78f7d220917c5dbfad1a40e09df9929d3b95d25e57d9f8558f84c9a11c", size = 203046841 },
{ url = "https://files.pythonhosted.org/packages/57/6c/bf52ff061da33deb9f94f4121fde7ff3058812cb7d2036c97bc167793bd1/torch-2.5.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:8c712df61101964eb11910a846514011f0b6f5920c55dbf567bff8a34163d5b1", size = 63858109 },
{ url = "https://files.pythonhosted.org/packages/69/72/20cb30f3b39a9face296491a86adb6ff8f1a47a897e4d14667e6cf89d5c3/torch-2.5.1-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:9b61edf3b4f6e3b0e0adda8b3960266b9009d02b37555971f4d1c8f7a05afed7", size = 906393265 },
]
[[package]]
@ -4212,7 +4191,7 @@ wheels = [
[[package]]
name = "transformers"
version = "4.57.1"
version = "4.45.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filelock" },
@ -4226,22 +4205,21 @@ dependencies = [
{ name = "tokenizers" },
{ name = "tqdm" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d6/68/a39307bcc4116a30b2106f2e689130a48de8bd8a1e635b5e1030e46fcd9e/transformers-4.57.1.tar.gz", hash = "sha256:f06c837959196c75039809636cd964b959f6604b75b8eeec6fdfc0440b89cc55", size = 10142511 }
sdist = { url = "https://files.pythonhosted.org/packages/4b/4c/3862b2dd6cdf83b187897bd351da0f7fb74d0df642b03c6f5d06353a3ca0/transformers-4.45.2.tar.gz", hash = "sha256:72bc390f6b203892561f05f86bbfaa0e234aab8e927a83e62b9d92ea7e3ae101", size = 8478357 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/71/d3/c16c3b3cf7655a67db1144da94b021c200ac1303f82428f2beef6c2e72bb/transformers-4.57.1-py3-none-any.whl", hash = "sha256:b10d05da8fa67dc41644dbbf9bc45a44cb86ae33da6f9295f5fbf5b7890bd267", size = 11990925 },
{ url = "https://files.pythonhosted.org/packages/f9/9d/030cc1b3e88172967e22ee1d012e0d5e0384eb70d2a098d1669d549aea29/transformers-4.45.2-py3-none-any.whl", hash = "sha256:c551b33660cfc815bae1f9f097ecfd1e65be623f13c6ee0dda372bd881460210", size = 9881312 },
]
[[package]]
name = "triton"
version = "3.5.0"
version = "3.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "filelock" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/78/949a04391c21956c816523678f0e5fa308eb5b1e7622d88c4e4ef5fceca0/triton-3.5.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f34bfa21c5b3a203c0f0eab28dcc1e49bd1f67d22724e77fb6665a659200a4ec", size = 170433488 },
{ url = "https://files.pythonhosted.org/packages/f5/3a/e991574f3102147b642e49637e0281e9bb7c4ba254edb2bab78247c85e01/triton-3.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9e71db82261c4ffa3921cd050cd5faa18322d2d405c30eb56084afaff3b0833", size = 170476535 },
{ url = "https://files.pythonhosted.org/packages/6c/29/10728de8a6e932e517c10773486b8e99f85d1b1d9dd87d9a9616e1fef4a1/triton-3.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e6bb9aa5519c084a333acdba443789e50012a4b851cd486c54f0b8dc2a8d3a12", size = 170487289 },
{ url = "https://files.pythonhosted.org/packages/5c/38/db80e48b9220c9bce872b0f616ad0446cdf554a40b85c7865cbca99ab3c2/triton-3.5.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c83f2343e1a220a716c7b3ab9fccfcbe3ad4020d189549200e2d2e8d5868bed9", size = 170577179 },
{ url = "https://files.pythonhosted.org/packages/ff/60/1810655d1d856c9a4fcc90ee8966d85f552d98c53a6589f95ab2cbe27bb8/triton-3.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da0fa67ccd76c3dcfb0bffe1b1c57c685136a6bd33d141c24d9655d4185b1289", size = 170487949 },
{ url = "https://files.pythonhosted.org/packages/fb/b7/1dec8433ac604c061173d0589d99217fe7bf90a70bdc375e745d044b8aad/triton-3.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:317fe477ea8fd4524a6a8c499fb0a36984a56d0b75bf9c9cb6133a1c56d5a6e7", size = 170580176 },
{ url = "https://files.pythonhosted.org/packages/86/17/d9a5cf4fcf46291856d1e90762e36cbabd2a56c7265da0d1d9508c8e3943/triton-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f34f6e7885d1bf0eaaf7ba875a5f0ce6f3c13ba98f9503651c1e6dc6757ed5c", size = 209506424 },
{ url = "https://files.pythonhosted.org/packages/78/eb/65f5ba83c2a123f6498a3097746607e5b2f16add29e36765305e4ac7fdd8/triton-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8182f42fd8080a7d39d666814fa36c5e30cc00ea7eeeb1a2983dbb4c99a0fdc", size = 209551444 },
]
[[package]]