diff --git a/benchmarks/financebench/evaluate_financebench.py b/benchmarks/financebench/evaluate_financebench.py index 4d3370c..ac05cce 100755 --- a/benchmarks/financebench/evaluate_financebench.py +++ b/benchmarks/financebench/evaluate_financebench.py @@ -1,19 +1,108 @@ -#!/usr/bin/env python3 """ -FinanceBench Evaluation Script -Uses intelligent evaluation similar to VectifyAI/Mafin approach +FinanceBench Evaluation Script - Modular Recall-based Evaluation """ import argparse import json import os -import re +import pickle import time from typing import Optional +import numpy as np import openai + +# Import LEANN modules - this will bring in the modified faiss from leann import LeannChat, LeannSearcher +# Import LEANN's modified faiss directly +from leann_backend_hnsw import faiss + + +class RecallEvaluator: + """Stage 2: Evaluate Recall@3 (searcher vs baseline)""" + + def __init__(self, index_path: str, baseline_dir: str): + self.index_path = index_path + self.baseline_dir = baseline_dir + self.searcher = LeannSearcher(index_path) + + # Load FAISS flat baseline + baseline_index_path = os.path.join(baseline_dir, "faiss_flat.index") + metadata_path = os.path.join(baseline_dir, "metadata.pkl") + + self.faiss_index = faiss.read_index(baseline_index_path) + with open(metadata_path, "rb") as f: + self.passage_ids = pickle.load(f) + print(f"๐Ÿ“š Loaded FAISS flat baseline with {self.faiss_index.ntotal} vectors") + + def evaluate_recall_at_3( + self, queries: list[str], complexity: int = 64, recompute_embeddings: bool = True + ) -> float: + """Evaluate recall@3 for given queries at specified complexity""" + recompute_str = "with recompute" if recompute_embeddings else "no recompute" + print(f"๐Ÿ” Evaluating recall@3 with complexity={complexity} ({recompute_str})...") + + total_recall = 0.0 + num_queries = len(queries) + + for i, query in enumerate(queries): + # Get ground truth: search with FAISS flat + from leann.api import compute_embeddings + + query_embedding = compute_embeddings( + [query], + self.searcher.embedding_model, + mode=self.searcher.embedding_mode, + use_server=False, + ).astype(np.float32) + + # Search FAISS flat for ground truth using LEANN's modified faiss API + n = query_embedding.shape[0] # Number of queries + k = 3 # Number of nearest neighbors + distances = np.zeros((n, k), dtype=np.float32) + labels = np.zeros((n, k), dtype=np.int64) + + self.faiss_index.search( + n, + faiss.swig_ptr(query_embedding), + k, + faiss.swig_ptr(distances), + faiss.swig_ptr(labels), + ) + + # Extract the results + baseline_ids = {self.passage_ids[idx] for idx in labels[0]} + + # Search with LEANN at specified complexity + test_results = self.searcher.search( + query, + top_k=3, + complexity=complexity, + recompute_embeddings=recompute_embeddings, + ) + test_ids = {result.id for result in test_results} + + # Calculate recall@3 = |intersection| / |ground_truth| + intersection = test_ids.intersection(baseline_ids) + recall = len(intersection) / 3.0 # Ground truth size is 3 + total_recall += recall + + if i < 3: # Show first few examples + print(f" Query {i + 1}: '{query[:50]}...' -> Recall@3: {recall:.3f}") + print(f" FAISS ground truth: {list(baseline_ids)}") + print(f" LEANN results (C={complexity}, {recompute_str}): {list(test_ids)}") + print(f" Intersection: {list(intersection)}") + + avg_recall = total_recall / num_queries + print(f"๐Ÿ“Š Average Recall@3: {avg_recall:.3f} ({avg_recall * 100:.1f}%)") + return avg_recall + + def cleanup(self): + """Cleanup resources""" + if hasattr(self, "searcher"): + self.searcher.cleanup() + class FinanceBenchEvaluator: def __init__(self, index_path: str, openai_api_key: Optional[str] = None): @@ -34,310 +123,472 @@ class FinanceBenchEvaluator: print(f"๐Ÿ“Š Loaded {len(data)} FinanceBench examples") return data - def evaluate_retrieval_intelligent(self, data: list[dict], top_k: int = 10) -> dict: - """ - Intelligent retrieval evaluation - Uses semantic similarity instead of strict word overlap - """ - print(f"๐Ÿ” Evaluating retrieval performance (top_k={top_k})...") + def analyze_index_sizes(self) -> dict: + """Analyze index sizes with and without embeddings""" + from pathlib import Path - metrics = { - "total_questions": 0, - "questions_with_relevant_retrieved": 0, - "exact_matches": 0, - "semantic_matches": 0, - "number_matches": 0, - "search_times": [], - "detailed_results": [], + print("๐Ÿ“ Analyzing index sizes...") + + # Get all index-related files + index_path = Path(self.index_path) + index_dir = index_path.parent + index_name = index_path.stem # Remove .leann extension + + sizes = {} + total_with_embeddings = 0 + total_without_embeddings = 0 + + # Core index files + index_file = index_dir / f"{index_name}.index" + meta_file = index_dir / f"{index_path.name}.meta.json" # Keep .leann for meta file + passages_file = index_dir / f"{index_path.name}.passages.jsonl" # Keep .leann for passages + passages_idx_file = index_dir / f"{index_path.name}.passages.idx" # Keep .leann for idx + + for file_path, name in [ + (index_file, "index"), + (meta_file, "metadata"), + (passages_file, "passages_text"), + (passages_idx_file, "passages_index"), + ]: + if file_path.exists(): + size_mb = file_path.stat().st_size / (1024 * 1024) + sizes[name] = size_mb + total_with_embeddings += size_mb + + # For pruned index calculation, exclude the main index file (contains embeddings) + if name != "index": + total_without_embeddings += size_mb + else: + sizes[name] = 0 + + # Estimate pruned index size (approximate) + # When embeddings are removed, the main index file becomes much smaller + # Rough estimate: graph structure is ~10-20% of full index size + estimated_pruned_index_size = sizes["index"] * 0.15 # Conservative estimate + total_without_embeddings += estimated_pruned_index_size + + sizes["total_with_embeddings"] = total_with_embeddings + sizes["total_without_embeddings"] = total_without_embeddings + sizes["estimated_pruned_index"] = estimated_pruned_index_size + sizes["compression_ratio"] = ( + total_without_embeddings / total_with_embeddings if total_with_embeddings > 0 else 0 + ) + + print(f" ๐Ÿ“ Index with embeddings: {total_with_embeddings:.1f} MB") + print(f" ๐Ÿ“ Estimated pruned index: {total_without_embeddings:.1f} MB") + print(f" ๐Ÿ—œ๏ธ Compression ratio: {sizes['compression_ratio']:.2f}x") + + return sizes + + def create_compact_index_for_comparison(self, compact_index_path: str) -> dict: + """Create a compact index for comparison purposes""" + print("๐Ÿ—๏ธ Building compact index from existing passages...") + + # Load existing passages from current index + from pathlib import Path + + from leann import LeannBuilder + + current_index_path = Path(self.index_path) + current_index_dir = current_index_path.parent + current_index_name = current_index_path.name + + # Read metadata to get passage source + meta_path = current_index_dir / f"{current_index_name}.meta.json" + with open(meta_path) as f: + import json + + meta = json.load(f) + + passage_source = meta["passage_sources"][0] + passage_file = passage_source["path"] + + # Convert relative path to absolute + if not Path(passage_file).is_absolute(): + passage_file = current_index_dir / Path(passage_file).name + + print(f"๐Ÿ“„ Loading passages from {passage_file}...") + + # Build compact index with same passages + builder = LeannBuilder( + backend_name="hnsw", + embedding_model=meta["embedding_model"], + embedding_mode=meta.get("embedding_mode", "sentence-transformers"), + is_recompute=True, # Enable recompute (no stored embeddings) + is_compact=True, # Enable compact storage + **meta.get("backend_kwargs", {}), + ) + + # Load all passages + with open(passage_file, encoding="utf-8") as f: + for line in f: + if line.strip(): + data = json.loads(line) + builder.add_text(data["text"], metadata=data.get("metadata", {})) + + print(f"๐Ÿ”จ Building compact index at {compact_index_path}...") + builder.build_index(compact_index_path) + + # Analyze the compact index size + temp_evaluator = FinanceBenchEvaluator(compact_index_path) + compact_sizes = temp_evaluator.analyze_index_sizes() + compact_sizes["index_type"] = "compact" + + return compact_sizes + + def create_non_compact_index_for_comparison(self, non_compact_index_path: str) -> dict: + """Create a non-compact index for comparison purposes""" + print("๐Ÿ—๏ธ Building non-compact index from existing passages...") + + # Load existing passages from current index + from pathlib import Path + + from leann import LeannBuilder + + current_index_path = Path(self.index_path) + current_index_dir = current_index_path.parent + current_index_name = current_index_path.name + + # Read metadata to get passage source + meta_path = current_index_dir / f"{current_index_name}.meta.json" + with open(meta_path) as f: + import json + + meta = json.load(f) + + passage_source = meta["passage_sources"][0] + passage_file = passage_source["path"] + + # Convert relative path to absolute + if not Path(passage_file).is_absolute(): + passage_file = current_index_dir / Path(passage_file).name + + print(f"๐Ÿ“„ Loading passages from {passage_file}...") + + # Build non-compact index with same passages + builder = LeannBuilder( + backend_name="hnsw", + embedding_model=meta["embedding_model"], + embedding_mode=meta.get("embedding_mode", "sentence-transformers"), + is_recompute=False, # Disable recompute (store embeddings) + is_compact=False, # Disable compact storage + **{ + k: v + for k, v in meta.get("backend_kwargs", {}).items() + if k not in ["is_recompute", "is_compact"] + }, + ) + + # Load all passages + with open(passage_file, encoding="utf-8") as f: + for line in f: + if line.strip(): + data = json.loads(line) + builder.add_text(data["text"], metadata=data.get("metadata", {})) + + print(f"๐Ÿ”จ Building non-compact index at {non_compact_index_path}...") + builder.build_index(non_compact_index_path) + + # Analyze the non-compact index size + temp_evaluator = FinanceBenchEvaluator(non_compact_index_path) + non_compact_sizes = temp_evaluator.analyze_index_sizes() + non_compact_sizes["index_type"] = "non_compact" + + return non_compact_sizes + + def compare_index_performance( + self, non_compact_path: str, compact_path: str, test_data: list, complexity: int + ) -> dict: + """Compare performance between non-compact and compact indexes""" + print("โšก Comparing search performance between indexes...") + + import time + + from leann import LeannSearcher + + # Test queries + test_queries = [item["question"] for item in test_data[:5]] + + results = { + "non_compact": {"search_times": []}, + "compact": {"search_times": []}, + "avg_search_times": {}, + "speed_ratio": 0.0, } - for item in data: - question = item["question"] - evidence_texts = [ev["evidence_text"] for ev in item.get("evidence", [])] - expected_answer = item["answer"] + # Test non-compact index (no recompute) + print(" ๐Ÿ” Testing non-compact index (no recompute)...") + non_compact_searcher = LeannSearcher(non_compact_path) - if not evidence_texts: - continue - - metrics["total_questions"] += 1 - - # Search for relevant documents + for query in test_queries: start_time = time.time() - results = self.searcher.search(question, top_k=top_k, complexity=64) + search_results = non_compact_searcher.search( + query, top_k=3, complexity=complexity, recompute_embeddings=False + ) search_time = time.time() - start_time - metrics["search_times"].append(search_time) + results["non_compact"]["search_times"].append(search_time) - # Evaluate retrieved results - found_relevant = False - match_types = [] + # Test compact index (with recompute) + print(" ๐Ÿ” Testing compact index (with recompute)...") + compact_searcher = LeannSearcher(compact_path) - for evidence_text in evidence_texts: - for i, result in enumerate(results): - retrieved_text = result.text - - # Method 1: Exact substring match - if self._has_exact_overlap(evidence_text, retrieved_text): - found_relevant = True - match_types.append(f"exact_match_rank_{i + 1}") - metrics["exact_matches"] += 1 - break - - # Method 2: Key numbers match - elif self._has_number_match(evidence_text, retrieved_text, expected_answer): - found_relevant = True - match_types.append(f"number_match_rank_{i + 1}") - metrics["number_matches"] += 1 - break - - # Method 3: Semantic similarity (word overlap with lower threshold) - elif self._has_semantic_similarity( - evidence_text, retrieved_text, threshold=0.2 - ): - found_relevant = True - match_types.append(f"semantic_match_rank_{i + 1}") - metrics["semantic_matches"] += 1 - break - - if found_relevant: - metrics["questions_with_relevant_retrieved"] += 1 - - # Store detailed result - metrics["detailed_results"].append( - { - "question": question, - "expected_answer": expected_answer, - "found_relevant": found_relevant, - "match_types": match_types, - "search_time": search_time, - "top_results": [ - {"text": r.text[:200] + "...", "score": r.score, "metadata": r.metadata} - for r in results[:3] - ], - } + for query in test_queries: + start_time = time.time() + search_results = compact_searcher.search( + query, top_k=3, complexity=complexity, recompute_embeddings=True ) + search_time = time.time() - start_time + results["compact"]["search_times"].append(search_time) - # Calculate metrics - if metrics["total_questions"] > 0: - metrics["question_coverage"] = ( - metrics["questions_with_relevant_retrieved"] / metrics["total_questions"] + # Calculate averages + results["avg_search_times"]["non_compact"] = sum( + results["non_compact"]["search_times"] + ) / len(results["non_compact"]["search_times"]) + results["avg_search_times"]["compact"] = sum(results["compact"]["search_times"]) / len( + results["compact"]["search_times"] + ) + + # Performance ratio + if results["avg_search_times"]["compact"] > 0: + results["speed_ratio"] = ( + results["avg_search_times"]["non_compact"] / results["avg_search_times"]["compact"] ) - metrics["avg_search_time"] = sum(metrics["search_times"]) / len(metrics["search_times"]) + else: + results["speed_ratio"] = float("inf") - # Match type breakdown - metrics["exact_match_rate"] = metrics["exact_matches"] / metrics["total_questions"] - metrics["number_match_rate"] = metrics["number_matches"] / metrics["total_questions"] - metrics["semantic_match_rate"] = ( - metrics["semantic_matches"] / metrics["total_questions"] - ) + print( + f" Non-compact (no recompute): {results['avg_search_times']['non_compact']:.3f}s avg" + ) + print(f" Compact (with recompute): {results['avg_search_times']['compact']:.3f}s avg") + print(f" Speed ratio: {results['speed_ratio']:.2f}x") - return metrics + # Cleanup + non_compact_searcher.cleanup() + compact_searcher.cleanup() - def evaluate_qa_intelligent(self, data: list[dict], max_samples: Optional[int] = None) -> dict: - """ - Intelligent QA evaluation using LLM-based answer comparison - Similar to VectifyAI/Mafin approach - """ + return results + + def evaluate_timing_breakdown( + self, data: list[dict], max_samples: Optional[int] = None + ) -> dict: + """Evaluate timing breakdown and accuracy by hacking LeannChat.ask() for separated timing""" if not self.chat or not self.openai_client: - print("โš ๏ธ Skipping QA evaluation (no OpenAI API key provided)") - return {"accuracy": 0.0, "total_questions": 0} + print("โš ๏ธ Skipping timing evaluation (no OpenAI API key provided)") + return { + "total_questions": 0, + "avg_search_time": 0.0, + "avg_generation_time": 0.0, + "avg_total_time": 0.0, + "accuracy": 0.0, + } - print("๐Ÿค– Evaluating QA performance...") + print("๐Ÿ”๐Ÿค– Evaluating timing breakdown and accuracy (search + generation)...") if max_samples: data = data[:max_samples] - print(f"๐Ÿ“ Using first {max_samples} samples for QA evaluation") + print(f"๐Ÿ“ Using first {max_samples} samples for timing evaluation") - results = [] + search_times = [] + generation_times = [] + total_times = [] correct_answers = 0 for i, item in enumerate(data): question = item["question"] - expected_answer = item["answer"] - - print(f"Question {i + 1}/{len(data)}: {question[:80]}...") + ground_truth = item["answer"] try: - # Get answer from LEANN - start_time = time.time() + # Hack: Monkey-patch the ask method to capture internal timing + original_ask = self.chat.ask + captured_search_time = None + captured_generation_time = None + + def patched_ask(*args, **kwargs): + nonlocal captured_search_time, captured_generation_time + + # Time the search part + search_start = time.time() + results = self.chat.searcher.search(args[0], top_k=3, complexity=64) + captured_search_time = time.time() - search_start + + # Time the generation part + context = "\n\n".join([r.text for r in results]) + prompt = ( + "Here is some retrieved context that might help answer your question:\n\n" + f"{context}\n\n" + f"Question: {args[0]}\n\n" + "Please provide the best answer you can based on this context and your knowledge." + ) + + generation_start = time.time() + answer = self.chat.llm.ask(prompt) + captured_generation_time = time.time() - generation_start + + return answer + + # Apply the patch + self.chat.ask = patched_ask + + # Time the total QA + total_start = time.time() generated_answer = self.chat.ask(question) - qa_time = time.time() - start_time + total_time = time.time() - total_start - # Intelligent evaluation using LLM - is_correct = self._evaluate_answer_with_llm( - question, expected_answer, generated_answer - ) + # Restore original method + self.chat.ask = original_ask + # Store the timings + search_times.append(captured_search_time) + generation_times.append(captured_generation_time) + total_times.append(total_time) + + # Check accuracy using LLM as judge + is_correct = self._check_answer_accuracy(generated_answer, ground_truth, question) if is_correct: correct_answers += 1 - results.append( - { - "question": question, - "expected_answer": expected_answer, - "generated_answer": generated_answer, - "is_correct": is_correct, - "qa_time": qa_time, - } + status = "โœ…" if is_correct else "โŒ" + print( + f"Question {i + 1}/{len(data)}: {status} Search={captured_search_time:.3f}s, Gen={captured_generation_time:.3f}s, Total={total_time:.3f}s" ) - - print(f" โœ… {'Correct' if is_correct else 'โŒ Incorrect'}") + print(f" GT: {ground_truth}") + print(f" Gen: {generated_answer[:100]}...") except Exception as e: print(f" โŒ Error: {e}") - results.append( - { - "question": question, - "expected_answer": expected_answer, - "generated_answer": f"ERROR: {e}", - "is_correct": False, - "qa_time": 0.0, - } - ) + search_times.append(0.0) + generation_times.append(0.0) + total_times.append(0.0) + + accuracy = correct_answers / len(data) if data else 0.0 metrics = { "total_questions": len(data), + "avg_search_time": sum(search_times) / len(search_times) if search_times else 0.0, + "avg_generation_time": sum(generation_times) / len(generation_times) + if generation_times + else 0.0, + "avg_total_time": sum(total_times) / len(total_times) if total_times else 0.0, + "accuracy": accuracy, "correct_answers": correct_answers, - "accuracy": correct_answers / len(data) if data else 0.0, - "avg_qa_time": sum(r["qa_time"] for r in results) / len(results) if results else 0.0, - "detailed_results": results, + "search_times": search_times, + "generation_times": generation_times, + "total_times": total_times, } return metrics - def _has_exact_overlap(self, evidence_text: str, retrieved_text: str) -> bool: - """Check for exact substring overlap""" - # Check if evidence is contained in retrieved text or vice versa - return ( - evidence_text.lower() in retrieved_text.lower() - or retrieved_text.lower() in evidence_text.lower() - ) - - def _has_number_match( - self, evidence_text: str, retrieved_text: str, expected_answer: str + def _check_answer_accuracy( + self, generated_answer: str, ground_truth: str, question: str ) -> bool: - """Check if key numbers from evidence/answer appear in retrieved text""" - # Extract numbers from evidence and expected answer - evidence_numbers = set(re.findall(r"\$?[\d,]+\.?\d*", evidence_text)) - answer_numbers = set(re.findall(r"\$?[\d,]+\.?\d*", expected_answer)) - retrieved_numbers = set(re.findall(r"\$?[\d,]+\.?\d*", retrieved_text)) - - # Check if any key numbers match - key_numbers = evidence_numbers.union(answer_numbers) - return bool(key_numbers.intersection(retrieved_numbers)) - - def _has_semantic_similarity( - self, evidence_text: str, retrieved_text: str, threshold: float = 0.2 - ) -> bool: - """Check semantic similarity using word overlap""" - words1 = set(evidence_text.lower().split()) - words2 = set(retrieved_text.lower().split()) - - if len(words1) == 0: - return False - - overlap = len(words1.intersection(words2)) - similarity = overlap / len(words1) - - return similarity >= threshold - - def _evaluate_answer_with_llm( - self, question: str, expected_answer: str, generated_answer: str - ) -> bool: - """ - Use LLM to evaluate answer equivalence - Based on VectifyAI/Mafin approach - """ - prompt = f"""You are an expert evaluator for AI-generated responses to financial questions. Your task is to determine whether the AI-generated answer correctly answers the question based on the golden answer provided by a human expert. - -Evaluation Criteria: -- Numerical Accuracy: Rounding differences should be ignored if they don't meaningfully change the conclusion. Numbers like 1.2 and 1.23 are considered similar. -- Fractions, percentages, and numerics could be considered similar. For example: "11 of 14" โ‰ˆ "79%" โ‰ˆ "0.79". -- If the golden answer or any of its equivalence can be inferred from the AI answer, then the AI answer is correct. -- The AI answer is correct if it conveys the same meaning, conclusion, or rationale as the golden answer. -- If the AI answer is a superset of the golden answer, it is also considered correct. -- Subjective judgments are correct as long as they are reasonable and justifiable. + """Check if generated answer matches ground truth using LLM as judge""" + judge_prompt = f"""You are an expert judge evaluating financial question answering. Question: {question} -AI-Generated Answer: {generated_answer} -Golden Answer: {expected_answer} -Your output should be ONLY a boolean value: `True` or `False`, nothing else.""" +Ground Truth Answer: {ground_truth} - # retry in exponential backoff - for i in range(3): - try: - response = self.openai_client.chat.completions.create( - model="gpt-4o", - messages=[{"role": "user", "content": prompt}], - temperature=0, - max_tokens=10, - ) - result = response.choices[0].message.content.strip().lower() - return "true" in result - except Exception as e: - print(f"LLM evaluation error: {e}") - time.sleep(2**i) +Generated Answer: {generated_answer} - return False +Task: Determine if the generated answer is factually correct compared to the ground truth. Focus on: +1. Numerical accuracy (exact values, units, currency) +2. Key financial concepts and terminology +3. Overall factual correctness - def run_evaluation( - self, - dataset_path: str = "data/financebench_merged.jsonl", - top_k: int = 10, - qa_samples: Optional[int] = None, - ) -> dict: - """Run complete FinanceBench evaluation""" - print("๐Ÿฆ FinanceBench Evaluation with LEANN") - print("=" * 50) - print(f"๐Ÿ“ Index: {self.index_path}") - print(f"๐Ÿ” Top-k: {top_k}") - if qa_samples: - print(f"๐Ÿค– QA samples: {qa_samples}") - print() +For financial data, small formatting differences are OK (e.g., "$1,577" vs "1577 million" vs "$1.577 billion"), but the core numerical value must match. - # Load dataset - data = self.load_dataset(dataset_path) +Respond with exactly one word: "CORRECT" if the generated answer is factually accurate, or "INCORRECT" if it's wrong or significantly different.""" - # Run retrieval evaluation - retrieval_metrics = self.evaluate_retrieval_intelligent(data, top_k=top_k) + try: + judge_response = self.openai_client.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "user", "content": judge_prompt}], + max_tokens=10, + temperature=0, + ) + judgment = judge_response.choices[0].message.content.strip().upper() + return judgment == "CORRECT" + except Exception as e: + print(f" โš ๏ธ Judge error: {e}, falling back to string matching") + # Fallback to simple string matching + gen_clean = generated_answer.strip().lower().replace("$", "").replace(",", "") + gt_clean = ground_truth.strip().lower().replace("$", "").replace(",", "") + return gt_clean in gen_clean - # Run QA evaluation - qa_metrics = self.evaluate_qa_intelligent(data, max_samples=qa_samples) - - # Print results - self._print_results(retrieval_metrics, qa_metrics) - - return { - "retrieval": retrieval_metrics, - "qa": qa_metrics, - } - - def _print_results(self, retrieval_metrics: dict, qa_metrics: dict): + def _print_results(self, timing_metrics: dict): """Print evaluation results""" print("\n๐ŸŽฏ EVALUATION RESULTS") print("=" * 50) - print("\n๐Ÿ“Š Retrieval Metrics:") - print(f" Question Coverage: {retrieval_metrics.get('question_coverage', 0):.1%}") - print(f" Exact Match Rate: {retrieval_metrics.get('exact_match_rate', 0):.1%}") - print(f" Number Match Rate: {retrieval_metrics.get('number_match_rate', 0):.1%}") - print(f" Semantic Match Rate: {retrieval_metrics.get('semantic_match_rate', 0):.1%}") - print(f" Avg Search Time: {retrieval_metrics.get('avg_search_time', 0):.3f}s") + # Index comparison analysis + if "current_index" in timing_metrics and "non_compact_index" in timing_metrics: + print("\n๐Ÿ“ Index Comparison Analysis:") + current = timing_metrics["current_index"] + non_compact = timing_metrics["non_compact_index"] - if qa_metrics.get("total_questions", 0) > 0: - print("\n๐Ÿค– QA Metrics:") - print(f" Accuracy: {qa_metrics.get('accuracy', 0):.1%}") - print(f" Questions Evaluated: {qa_metrics.get('total_questions', 0)}") - print(f" Avg QA Time: {qa_metrics.get('avg_qa_time', 0):.3f}s") + print(f" Compact index (current): {current.get('total_with_embeddings', 0):.1f} MB") + print( + f" Non-compact index (with embeddings): {non_compact.get('total_with_embeddings', 0):.1f} MB" + ) + print( + f" Storage saving by compact: {timing_metrics.get('storage_saving_percent', 0):.1f}%" + ) - # Show some example results - print("\n๐Ÿ“ Example Results:") - for i, result in enumerate(retrieval_metrics.get("detailed_results", [])[:3]): - print(f"\n Example {i + 1}:") - print(f" Q: {result['question'][:80]}...") - print(f" Found relevant: {'โœ…' if result['found_relevant'] else 'โŒ'}") - if result["match_types"]: - print(f" Match types: {', '.join(result['match_types'])}") + print(" Component breakdown (non-compact):") + print(f" - Main index: {non_compact.get('index', 0):.1f} MB") + print(f" - Passages text: {non_compact.get('passages_text', 0):.1f} MB") + print(f" - Passages index: {non_compact.get('passages_index', 0):.1f} MB") + print(f" - Metadata: {non_compact.get('metadata', 0):.1f} MB") + + # Performance comparison + if "performance_comparison" in timing_metrics: + perf = timing_metrics["performance_comparison"] + print("\nโšก Performance Comparison:") + print( + f" Non-compact (no recompute): {perf.get('avg_search_times', {}).get('non_compact', 0):.3f}s avg" + ) + print( + f" Compact (with recompute): {perf.get('avg_search_times', {}).get('compact', 0):.3f}s avg" + ) + print(f" Speed ratio: {perf.get('speed_ratio', 0):.2f}x") + + # Legacy single index analysis (fallback) + if "total_with_embeddings" in timing_metrics and "current_index" not in timing_metrics: + print("\n๐Ÿ“ Index Size Analysis:") + print( + f" Index with embeddings: {timing_metrics.get('total_with_embeddings', 0):.1f} MB" + ) + print( + f" Estimated pruned index: {timing_metrics.get('total_without_embeddings', 0):.1f} MB" + ) + print(f" Compression ratio: {timing_metrics.get('compression_ratio', 0):.2f}x") + + print("\n๐Ÿ“Š Accuracy:") + print(f" Accuracy: {timing_metrics.get('accuracy', 0) * 100:.1f}%") + print( + f" Correct Answers: {timing_metrics.get('correct_answers', 0)}/{timing_metrics.get('total_questions', 0)}" + ) + + print("\n๐Ÿ“Š Timing Breakdown:") + print(f" Total Questions: {timing_metrics.get('total_questions', 0)}") + print(f" Avg Search Time: {timing_metrics.get('avg_search_time', 0):.3f}s") + print(f" Avg Generation Time: {timing_metrics.get('avg_generation_time', 0):.3f}s") + print(f" Avg Total Time: {timing_metrics.get('avg_total_time', 0):.3f}s") + + if timing_metrics.get("avg_total_time", 0) > 0: + search_pct = ( + timing_metrics.get("avg_search_time", 0) + / timing_metrics.get("avg_total_time", 1) + * 100 + ) + gen_pct = ( + timing_metrics.get("avg_generation_time", 0) + / timing_metrics.get("avg_total_time", 1) + * 100 + ) + print("\n๐Ÿ“ˆ Time Distribution:") + print(f" Search: {search_pct:.1f}%") + print(f" Generation: {gen_pct:.1f}%") def cleanup(self): """Cleanup resources""" @@ -346,44 +597,299 @@ Your output should be ONLY a boolean value: `True` or `False`, nothing else.""" def main(): - parser = argparse.ArgumentParser(description="Evaluate FinanceBench with LEANN") + parser = argparse.ArgumentParser(description="Modular FinanceBench Evaluation") parser.add_argument("--index", required=True, help="Path to LEANN index") parser.add_argument("--dataset", default="data/financebench_merged.jsonl", help="Dataset path") - parser.add_argument("--top-k", type=int, default=10, help="Number of documents to retrieve") - parser.add_argument("--qa-samples", type=int, default=None, help="Limit QA evaluation samples") - parser.add_argument("--openai-api-key", help="OpenAI API key for QA evaluation") + parser.add_argument( + "--stage", + choices=["2", "3", "4", "all"], + default="all", + help="Which stage to run (2=recall, 3=complexity, 4=generation)", + ) + parser.add_argument("--complexity", type=int, default=None, help="Complexity for search") + parser.add_argument("--baseline-dir", default="baseline", help="Baseline output directory") + parser.add_argument("--openai-api-key", help="OpenAI API key for generation evaluation") parser.add_argument("--output", help="Save results to JSON file") args = parser.parse_args() - # Get OpenAI API key - api_key = args.openai_api_key or os.getenv("OPENAI_API_KEY") - if not api_key and args.qa_samples != 0: - print("โš ๏ธ No OpenAI API key provided. QA evaluation will be skipped.") - print(" Set OPENAI_API_KEY environment variable or use --openai-api-key") - try: - # Run evaluation - evaluator = FinanceBenchEvaluator(args.index, api_key) - results = evaluator.run_evaluation( - dataset_path=args.dataset, top_k=args.top_k, qa_samples=args.qa_samples - ) + # Check if baseline exists + baseline_index_path = os.path.join(args.baseline_dir, "faiss_flat.index") + if not os.path.exists(baseline_index_path): + print(f"โŒ FAISS baseline not found at {baseline_index_path}") + print("๐Ÿ’ก Please run setup_financebench.py first to build the baseline") + exit(1) - # Save results if requested - if args.output: - with open(args.output, "w") as f: - json.dump(results, f, indent=2, default=str) - print(f"\n๐Ÿ’พ Results saved to {args.output}") + if args.stage == "2" or args.stage == "all": + # Stage 2: Recall@3 evaluation + print("๐Ÿš€ Starting Stage 2: Recall@3 evaluation") - evaluator.cleanup() + evaluator = RecallEvaluator(args.index, args.baseline_dir) - print("\nโœ… Evaluation completed!") + # Load FinanceBench queries for testing + print("๐Ÿ“– Loading FinanceBench dataset...") + queries = [] + with open(args.dataset, encoding="utf-8") as f: + for line in f: + if line.strip(): + data = json.loads(line) + queries.append(data["question"]) + # Test with more queries for robust measurement + test_queries = queries[:2000] + print(f"๐Ÿงช Testing with {len(test_queries)} queries") + + # Test with complexity 64 + complexity = 64 + recall = evaluator.evaluate_recall_at_3(test_queries, complexity) + print(f"๐Ÿ“ˆ Recall@3 at complexity {complexity}: {recall * 100:.1f}%") + + evaluator.cleanup() + print("โœ… Stage 2 completed!\n") + + # Shared non-compact index path for Stage 3 and 4 + non_compact_index_path = args.index.replace(".leann", "_noncompact.leann") + complexity = args.complexity + + if args.stage == "3" or args.stage == "all": + # Stage 3: Binary search for 90% recall complexity (using non-compact index for speed) + print("๐Ÿš€ Starting Stage 3: Binary search for 90% recall complexity") + print( + "๐Ÿ’ก Creating non-compact index for fast binary search with recompute_embeddings=False" + ) + + # Create non-compact index for binary search (will be reused in Stage 4) + print("๐Ÿ—๏ธ Creating non-compact index for binary search...") + evaluator = FinanceBenchEvaluator(args.index) + evaluator.create_non_compact_index_for_comparison(non_compact_index_path) + + # Use non-compact index for binary search + binary_search_evaluator = RecallEvaluator(non_compact_index_path, args.baseline_dir) + + # Load queries for testing + print("๐Ÿ“– Loading FinanceBench dataset...") + queries = [] + with open(args.dataset, encoding="utf-8") as f: + for line in f: + if line.strip(): + data = json.loads(line) + queries.append(data["question"]) + + # Use more queries for robust measurement + test_queries = queries[:200] + print(f"๐Ÿงช Testing with {len(test_queries)} queries") + + # Binary search for 90% recall complexity (without recompute for speed) + target_recall = 0.9 + min_complexity, max_complexity = 1, 32 + + print(f"๐Ÿ” Binary search for {target_recall * 100}% recall complexity...") + print(f"Search range: {min_complexity} to {max_complexity}") + + best_complexity = None + best_recall = 0.0 + + while min_complexity <= max_complexity: + mid_complexity = (min_complexity + max_complexity) // 2 + + print( + f"\n๐Ÿงช Testing complexity {mid_complexity} (no recompute, non-compact index)..." + ) + # Use recompute_embeddings=False on non-compact index for fast binary search + recall = binary_search_evaluator.evaluate_recall_at_3( + test_queries, mid_complexity, recompute_embeddings=False + ) + + print( + f" Complexity {mid_complexity}: Recall@3 = {recall:.3f} ({recall * 100:.1f}%)" + ) + + if recall >= target_recall: + best_complexity = mid_complexity + best_recall = recall + max_complexity = mid_complexity - 1 + print(" โœ… Target reached! Searching for lower complexity...") + else: + min_complexity = mid_complexity + 1 + print(" โŒ Below target. Searching for higher complexity...") + + if best_complexity is not None: + print("\n๐ŸŽฏ Optimal complexity found!") + print(f" Complexity: {best_complexity}") + print(f" Recall@3: {best_recall:.3f} ({best_recall * 100:.1f}%)") + + # Test a few complexities around the optimal one for verification + print("\n๐Ÿ”ฌ Verification test around optimal complexity:") + verification_complexities = [ + max(1, best_complexity - 2), + max(1, best_complexity - 1), + best_complexity, + best_complexity + 1, + best_complexity + 2, + ] + + for complexity in verification_complexities: + if complexity <= 512: # reasonable upper bound + recall = binary_search_evaluator.evaluate_recall_at_3( + test_queries, complexity, recompute_embeddings=False + ) + status = "โœ…" if recall >= target_recall else "โŒ" + print(f" {status} Complexity {complexity:3d}: {recall * 100:5.1f}%") + + # Now test the optimal complexity with compact index and recompute for comparison + print( + f"\n๐Ÿ”„ Testing optimal complexity {best_complexity} on compact index WITH recompute..." + ) + compact_evaluator = RecallEvaluator(args.index, args.baseline_dir) + recall_with_recompute = compact_evaluator.evaluate_recall_at_3( + test_queries[:10], best_complexity, recompute_embeddings=True + ) + print( + f" โœ… Complexity {best_complexity} (compact index with recompute): {recall_with_recompute * 100:.1f}%" + ) + complexity = best_complexity + print( + f" ๐Ÿ“Š Recall difference: {abs(best_recall - recall_with_recompute) * 100:.2f}%" + ) + compact_evaluator.cleanup() + else: + print(f"\nโŒ Could not find complexity achieving {target_recall * 100}% recall") + print("All tested complexities were below target.") + + # Cleanup evaluators (keep non-compact index for Stage 4) + binary_search_evaluator.cleanup() + evaluator.cleanup() + + print("โœ… Stage 3 completed! Non-compact index saved for Stage 4.\n") + + if args.stage == "4" or args.stage == "all": + # Stage 4: Comprehensive evaluation with dual index comparison + print("๐Ÿš€ Starting Stage 4: Comprehensive evaluation with dual index comparison") + + # Use FinanceBench evaluator for QA evaluation + evaluator = FinanceBenchEvaluator(args.index, args.openai_api_key) + + print("๐Ÿ“– Loading FinanceBench dataset...") + data = evaluator.load_dataset(args.dataset) + + # Step 1: Analyze current (compact) index + print("\n๐Ÿ“ Analyzing current index (compact, pruned)...") + compact_size_metrics = evaluator.analyze_index_sizes() + compact_size_metrics["index_type"] = "compact" + + # Step 2: Use existing non-compact index or create if needed + from pathlib import Path + + if Path(non_compact_index_path).exists(): + print( + f"\n๐Ÿ“ Using existing non-compact index from Stage 3: {non_compact_index_path}" + ) + temp_evaluator = FinanceBenchEvaluator(non_compact_index_path) + non_compact_size_metrics = temp_evaluator.analyze_index_sizes() + non_compact_size_metrics["index_type"] = "non_compact" + else: + print("\n๐Ÿ—๏ธ Creating non-compact index (with embeddings) for comparison...") + non_compact_size_metrics = evaluator.create_non_compact_index_for_comparison( + non_compact_index_path + ) + + # Step 3: Compare index sizes + print("\n๐Ÿ“Š Index size comparison:") + print( + f" Compact index (current): {compact_size_metrics['total_with_embeddings']:.1f} MB" + ) + print( + f" Non-compact index: {non_compact_size_metrics['total_with_embeddings']:.1f} MB" + ) + size_increase = ( + ( + non_compact_size_metrics["total_with_embeddings"] + - compact_size_metrics["total_with_embeddings"] + ) + / compact_size_metrics["total_with_embeddings"] + * 100 + ) + storage_saving = ( + ( + non_compact_size_metrics["total_with_embeddings"] + - compact_size_metrics["total_with_embeddings"] + ) + / non_compact_size_metrics["total_with_embeddings"] + * 100 + ) + print(f" Storage saving by compact: {storage_saving:.1f}%") + + # Step 4: Performance comparison between the two indexes + if complexity is None: + raise ValueError("Complexity is required for performance comparison") + + print("\nโšก Performance comparison between indexes...") + performance_metrics = evaluator.compare_index_performance( + non_compact_index_path, args.index, data[:10], complexity=complexity + ) + + # Step 5: Timing breakdown evaluation WITH recompute (production mode) + test_samples = 20 + print(f"\n๐Ÿงช Testing with first {test_samples} samples for timing analysis") + print( + "\n๐Ÿ”๐Ÿค– Running timing breakdown evaluation (WITH recompute - production mode)..." + ) + evaluation_start = time.time() + timing_metrics = evaluator.evaluate_timing_breakdown(data[:test_samples]) + evaluation_time = time.time() - evaluation_start + + # Combine all metrics + combined_metrics = { + **timing_metrics, + "total_evaluation_time": evaluation_time, + "current_index": compact_size_metrics, + "non_compact_index": non_compact_size_metrics, + "performance_comparison": performance_metrics, + "storage_saving_percent": storage_saving, + } + + # Print comprehensive results + evaluator._print_results(combined_metrics) + + # Save results if requested + if args.output: + print(f"\n๐Ÿ’พ Saving results to {args.output}...") + with open(args.output, "w") as f: + json.dump(combined_metrics, f, indent=2, default=str) + print(f"โœ… Results saved to {args.output}") + + evaluator.cleanup() + print("โœ… Stage 4 completed!\n") + + if args.stage == "all": + print("๐ŸŽ‰ All evaluation stages completed successfully!") + print("\n๐Ÿ“‹ Summary:") + print(" Stage 2: โœ… Recall@3 evaluation completed") + print(" Stage 3: โœ… Optimal complexity found") + print(" Stage 4: โœ… Generation accuracy & timing evaluation completed") + print("\n๐Ÿ”ง Recommended next steps:") + print(" - Use optimal complexity for best speed/accuracy balance") + print(" - Review accuracy and timing breakdown for performance optimization") + print(" - Run full evaluation on complete dataset if needed") + + # Clean up non-compact index after all stages complete + print("\n๐Ÿงน Cleaning up temporary non-compact index...") + from pathlib import Path + + if Path(non_compact_index_path).exists(): + temp_index_dir = Path(non_compact_index_path).parent + temp_index_name = Path(non_compact_index_path).name + for temp_file in temp_index_dir.glob(f"{temp_index_name}*"): + temp_file.unlink() + print(f"โœ… Cleaned up {non_compact_index_path}") + else: + print("๐Ÿ“ No temporary index to clean up") except KeyboardInterrupt: print("\nโš ๏ธ Evaluation interrupted by user") exit(1) except Exception as e: - print(f"\nโŒ Evaluation failed: {e}") + print(f"\nโŒ Stage {args.stage} failed: {e}") exit(1) diff --git a/benchmarks/financebench/setup_financebench.py b/benchmarks/financebench/setup_financebench.py index e9f6028..36bd0da 100755 --- a/benchmarks/financebench/setup_financebench.py +++ b/benchmarks/financebench/setup_financebench.py @@ -5,6 +5,7 @@ Downloads all PDFs and builds full LEANN datastore """ import argparse +import os import re import time from concurrent.futures import ThreadPoolExecutor, as_completed @@ -139,14 +140,15 @@ class FinanceBenchSetup: start_time = time.time() - # Initialize builder + # Initialize builder with standard compact configuration builder = LeannBuilder( backend_name=backend, embedding_model=embedding_model, embedding_mode="sentence-transformers", graph_degree=32, complexity=64, - is_recompute=False, # Store embeddings for speed + is_recompute=True, # Enable recompute (no stored embeddings) + is_compact=True, # Enable compact storage (pruned) num_threads=4, ) @@ -185,6 +187,87 @@ class FinanceBenchSetup: return str(index_path) + def build_faiss_flat_baseline(self, index_path: str, output_dir: str = "baseline"): + """Build FAISS flat baseline using the same embeddings as LEANN index""" + print("๐Ÿ”จ Building FAISS Flat baseline...") + + import os + import pickle + + import numpy as np + from leann.api import compute_embeddings + from leann_backend_hnsw import faiss + + os.makedirs(output_dir, exist_ok=True) + baseline_path = os.path.join(output_dir, "faiss_flat.index") + metadata_path = os.path.join(output_dir, "metadata.pkl") + + if os.path.exists(baseline_path) and os.path.exists(metadata_path): + print(f"โœ… Baseline already exists at {baseline_path}") + return baseline_path + + # Read metadata from the built index + meta_path = f"{index_path}.meta.json" + with open(meta_path) as f: + import json + + meta = json.loads(f.read()) + + embedding_model = meta["embedding_model"] + passage_source = meta["passage_sources"][0] + passage_file = passage_source["path"] + + # Convert relative path to absolute + if not os.path.isabs(passage_file): + index_dir = os.path.dirname(index_path) + passage_file = os.path.join(index_dir, os.path.basename(passage_file)) + + print(f"๐Ÿ“Š Loading passages from {passage_file}...") + print(f"๐Ÿค– Using embedding model: {embedding_model}") + + # Load all passages for baseline + passages = [] + passage_ids = [] + with open(passage_file, encoding="utf-8") as f: + for line in f: + if line.strip(): + data = json.loads(line) + passages.append(data["text"]) + passage_ids.append(data["id"]) + + print(f"๐Ÿ“„ Loaded {len(passages)} passages") + + # Compute embeddings using the same method as LEANN + print("๐Ÿงฎ Computing embeddings...") + embeddings = compute_embeddings( + passages, + embedding_model, + mode="sentence-transformers", + use_server=False, + ) + + print(f"๐Ÿ“ Embedding shape: {embeddings.shape}") + + # Build FAISS flat index + print("๐Ÿ—๏ธ Building FAISS IndexFlatIP...") + dimension = embeddings.shape[1] + index = faiss.IndexFlatIP(dimension) + + # Add embeddings to flat index + embeddings_f32 = embeddings.astype(np.float32) + index.add(embeddings_f32.shape[0], faiss.swig_ptr(embeddings_f32)) + + # Save index and metadata + faiss.write_index(index, baseline_path) + with open(metadata_path, "wb") as f: + pickle.dump(passage_ids, f) + + print(f"โœ… FAISS baseline saved to {baseline_path}") + print(f"โœ… Metadata saved to {metadata_path}") + print(f"๐Ÿ“Š Total vectors: {index.ntotal}") + + return baseline_path + def extract_pdf_text(self, pdf_path: Path) -> list[dict]: """Extract and chunk text from a PDF file""" chunks = [] @@ -300,6 +383,11 @@ def main(): parser.add_argument("--max-workers", type=int, default=5, help="Parallel download workers") parser.add_argument("--skip-download", action="store_true", help="Skip PDF download") parser.add_argument("--skip-build", action="store_true", help="Skip index building") + parser.add_argument( + "--build-baseline-only", + action="store_true", + help="Only build FAISS baseline from existing index", + ) args = parser.parse_args() @@ -309,6 +397,24 @@ def main(): setup = FinanceBenchSetup(args.data_dir) try: + if args.build_baseline_only: + # Only build baseline from existing index + index_path = setup.index_dir / f"financebench_full_{args.backend}" + index_file = f"{index_path}.index" + meta_file = f"{index_path}.leann.meta.json" + + if not os.path.exists(index_file) or not os.path.exists(meta_file): + print("โŒ Index files not found:") + print(f" Index: {index_file}") + print(f" Meta: {meta_file}") + print("๐Ÿ’ก Run without --build-baseline-only to build the index first") + exit(1) + + print(f"๐Ÿ”จ Building baseline from existing index: {index_path}") + baseline_path = setup.build_faiss_flat_baseline(str(index_path)) + print(f"โœ… Baseline built at {baseline_path}") + return + # Step 1: Download dataset setup.download_dataset() @@ -324,7 +430,12 @@ def main(): backend=args.backend, embedding_model=args.embedding_model ) - # Step 4: Verify setup + # Step 4: Build FAISS flat baseline + print("\n๐Ÿ”จ Building FAISS flat baseline...") + baseline_path = setup.build_faiss_flat_baseline(index_path) + print(f"โœ… Baseline built at {baseline_path}") + + # Step 5: Verify setup setup.verify_setup(index_path) else: print("โญ๏ธ Skipping index building") diff --git a/benchmarks/financebench/verify_recall.py b/benchmarks/financebench/verify_recall.py new file mode 100644 index 0000000..c4f77cb --- /dev/null +++ b/benchmarks/financebench/verify_recall.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.9" +# dependencies = [ +# "faiss-cpu", +# "numpy", +# "sentence-transformers", +# "torch", +# "tqdm", +# ] +# /// + +""" +Independent recall verification script using standard FAISS. +Creates two indexes (HNSW and Flat) and compares recall@3 at different complexities. +""" + +import json +import time +from pathlib import Path + +import faiss +import numpy as np +from sentence_transformers import SentenceTransformer +from tqdm import tqdm + + +def compute_embeddings_direct(chunks: list[str], model_name: str) -> np.ndarray: + """ + Direct embedding computation using sentence-transformers. + Copied logic to avoid dependency issues. + """ + print(f"Loading model: {model_name}") + model = SentenceTransformer(model_name) + + print(f"Computing embeddings for {len(chunks)} chunks...") + embeddings = model.encode( + chunks, + show_progress_bar=True, + batch_size=32, + convert_to_numpy=True, + normalize_embeddings=False, + ) + + return embeddings.astype(np.float32) + + +def load_financebench_queries(dataset_path: str, max_queries: int = 200) -> list[str]: + """Load FinanceBench queries from dataset""" + queries = [] + with open(dataset_path, encoding="utf-8") as f: + for line in f: + if line.strip(): + data = json.loads(line) + queries.append(data["question"]) + if len(queries) >= max_queries: + break + return queries + + +def load_passages_from_leann_index(index_path: str) -> tuple[list[str], list[str]]: + """Load passages from LEANN index structure""" + meta_path = f"{index_path}.meta.json" + with open(meta_path) as f: + meta = json.load(f) + + passage_source = meta["passage_sources"][0] + passage_file = passage_source["path"] + + # Convert relative path to absolute + if not Path(passage_file).is_absolute(): + index_dir = Path(index_path).parent + passage_file = index_dir / Path(passage_file).name + + print(f"Loading passages from {passage_file}") + + passages = [] + passage_ids = [] + with open(passage_file, encoding="utf-8") as f: + for line in tqdm(f, desc="Loading passages"): + if line.strip(): + data = json.loads(line) + passages.append(data["text"]) + passage_ids.append(data["id"]) + + print(f"Loaded {len(passages)} passages") + return passages, passage_ids + + +def build_faiss_indexes(embeddings: np.ndarray) -> tuple[faiss.Index, faiss.Index]: + """Build FAISS indexes: Flat (ground truth) and HNSW""" + dimension = embeddings.shape[1] + + # Build Flat index (ground truth) + print("Building FAISS IndexFlatIP (ground truth)...") + flat_index = faiss.IndexFlatIP(dimension) + flat_index.add(embeddings) + + # Build HNSW index + print("Building FAISS IndexHNSWFlat...") + M = 32 # Same as LEANN default + hnsw_index = faiss.IndexHNSWFlat(dimension, M, faiss.METRIC_INNER_PRODUCT) + hnsw_index.hnsw.efConstruction = 200 # Same as LEANN default + hnsw_index.add(embeddings) + + print(f"Built indexes with {flat_index.ntotal} vectors, dimension {dimension}") + return flat_index, hnsw_index + + +def evaluate_recall_at_k( + query_embeddings: np.ndarray, + flat_index: faiss.Index, + hnsw_index: faiss.Index, + passage_ids: list[str], + k: int = 3, + ef_search: int = 64, +) -> float: + """Evaluate recall@k comparing HNSW vs Flat""" + + # Set search parameters for HNSW + hnsw_index.hnsw.efSearch = ef_search + + total_recall = 0.0 + num_queries = query_embeddings.shape[0] + + for i in range(num_queries): + query = query_embeddings[i : i + 1] # Keep 2D shape + + # Get ground truth from Flat index (standard FAISS API) + flat_distances, flat_indices = flat_index.search(query, k) + ground_truth_ids = {passage_ids[idx] for idx in flat_indices[0]} + + # Get results from HNSW index (standard FAISS API) + hnsw_distances, hnsw_indices = hnsw_index.search(query, k) + hnsw_ids = {passage_ids[idx] for idx in hnsw_indices[0]} + + # Calculate recall + intersection = ground_truth_ids.intersection(hnsw_ids) + recall = len(intersection) / k + total_recall += recall + + if i < 3: # Show first few examples + print(f" Query {i + 1}: Recall@{k} = {recall:.3f}") + print(f" Flat: {list(ground_truth_ids)}") + print(f" HNSW: {list(hnsw_ids)}") + print(f" Intersection: {list(intersection)}") + + avg_recall = total_recall / num_queries + return avg_recall + + +def main(): + # Configuration + dataset_path = "data/financebench_merged.jsonl" + index_path = "data/index/financebench_full_hnsw.leann" + embedding_model = "sentence-transformers/all-mpnet-base-v2" + + print("๐Ÿ” FAISS Recall Verification") + print("=" * 50) + + # Check if files exist + if not Path(dataset_path).exists(): + print(f"โŒ Dataset not found: {dataset_path}") + return + if not Path(f"{index_path}.meta.json").exists(): + print(f"โŒ Index metadata not found: {index_path}.meta.json") + return + + # Load data + print("๐Ÿ“– Loading FinanceBench queries...") + queries = load_financebench_queries(dataset_path, max_queries=50) + print(f"Loaded {len(queries)} queries") + + print("๐Ÿ“„ Loading passages from LEANN index...") + passages, passage_ids = load_passages_from_leann_index(index_path) + + # Compute embeddings + print("๐Ÿงฎ Computing passage embeddings...") + passage_embeddings = compute_embeddings_direct(passages, embedding_model) + + print("๐Ÿงฎ Computing query embeddings...") + query_embeddings = compute_embeddings_direct(queries, embedding_model) + + # Build FAISS indexes + print("๐Ÿ—๏ธ Building FAISS indexes...") + flat_index, hnsw_index = build_faiss_indexes(passage_embeddings) + + # Test different efSearch values (equivalent to LEANN complexity) + print("\n๐Ÿ“Š Evaluating Recall@3 at different efSearch values...") + ef_search_values = [16, 32, 64, 128, 256] + + for ef_search in ef_search_values: + print(f"\n๐Ÿงช Testing efSearch = {ef_search}") + start_time = time.time() + + recall = evaluate_recall_at_k( + query_embeddings, flat_index, hnsw_index, passage_ids, k=3, ef_search=ef_search + ) + + elapsed = time.time() - start_time + print( + f"๐Ÿ“ˆ efSearch {ef_search}: Recall@3 = {recall:.3f} ({recall * 100:.1f}%) in {elapsed:.2f}s" + ) + + print("\nโœ… Verification completed!") + print("\n๐Ÿ“‹ Summary:") + print(" - Built independent FAISS Flat and HNSW indexes") + print(" - Compared recall@3 at different efSearch values") + print(" - Used same embedding model as LEANN") + print(" - This validates LEANN's recall measurements") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index 3267332..961195e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,14 @@ dependencies = [ "pathspec>=0.12.1", "nbconvert>=7.16.6", "gitignore-parser>=0.1.12", + # AST-aware code chunking dependencies + "astchunk>=0.1.0", + "tree-sitter>=0.20.0", + "tree-sitter-python>=0.20.0", + "tree-sitter-java>=0.20.0", + "tree-sitter-c-sharp>=0.20.0", + "tree-sitter-typescript>=0.20.0", + "faiss-cpu>=1.12.0", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index 535aa7c..438e7dd 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.9" resolution-markers = [ "python_full_version >= '3.12'", @@ -198,6 +198,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, ] +[[package]] +name = "astchunk" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyrsistent" }, + { name = "tree-sitter", version = "0.23.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "tree-sitter", version = "0.25.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tree-sitter-c-sharp" }, + { name = "tree-sitter-java" }, + { name = "tree-sitter-python" }, + { name = "tree-sitter-typescript" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/2a/7a35e2fac7d550265ae2ee40651425083b37555f921d1a1b77c3f525e0df/astchunk-0.1.0.tar.gz", hash = "sha256:f4dff0ef8b3b3bcfeac363384db1e153f74d4c825dc2e35864abfab027713be4", size = 18093, upload-time = "2025-06-19T04:37:25.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/84/5433ab0e933b572750cb16fd7edf3d6c7902b069461a22ec670042752a4d/astchunk-0.1.0-py3-none-any.whl", hash = "sha256:33ada9fc3620807fdda5846fa1948af463f281a60e0d43d4f3782b6dbb416d24", size = 15396, upload-time = "2025-06-19T04:37:23.87Z" }, +] + [[package]] name = "asttokens" version = "3.0.0" @@ -1147,6 +1168,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, ] +[[package]] +name = "faiss-cpu" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/80/bb75a7ed6e824dea452a24d3434a72ed799324a688b10b047d441d270185/faiss_cpu-1.12.0.tar.gz", hash = "sha256:2f87cbcd603f3ed464ebceb857971fdebc318de938566c9ae2b82beda8e953c0", size = 69292, upload-time = "2025-08-13T06:07:26.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/3b/42aa7332c2e432fc3af3a26cc49ca8a3ecd23d13bb790e61c1e54a4d16cb/faiss_cpu-1.12.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:be96f9290edd13d56fb3c69b8dd6be487552b4401f2e95b437cabf5309c424ad", size = 8006082, upload-time = "2025-08-13T06:05:33.131Z" }, + { url = "https://files.pythonhosted.org/packages/00/ab/9959c2d9c3a511a5dbfa4e2e2a1d0bdcad5929d410b3abe87bbed74dcb9b/faiss_cpu-1.12.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:0834c547c39d5e5d0b769c90ac5d5ca42e00bcdbba491f3440d2d458058b19d6", size = 3360138, upload-time = "2025-08-13T06:05:35.003Z" }, + { url = "https://files.pythonhosted.org/packages/80/b9/7456f89effe93b7693c7e39cd365065e27aa31794442510c44ad8cce6c4c/faiss_cpu-1.12.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a40830a16d8718b14a462e1a1efaa26660eb3bb8ada22e0712a6ac181092750e", size = 3825459, upload-time = "2025-08-13T06:05:36.546Z" }, + { url = "https://files.pythonhosted.org/packages/f4/0f/02d5d2ae8b53e5629cb03fbd871bbbfbbd647ffc3d09393b34f6347072d7/faiss_cpu-1.12.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7f8732796e3f730556e99327861066ead0ae7e66b5cbf6c0f217be48074e41e", size = 31425823, upload-time = "2025-08-13T06:05:38.778Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/a9fcb0b82727ab2d5509caa7637e5d345c710502f68c7a7e90dd212654ad/faiss_cpu-1.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ded5063e13c3bb6b1b463827f838ae45a0aea4c9aeaf6c938e7e87f3f6ea4126", size = 9751939, upload-time = "2025-08-13T06:05:41.816Z" }, + { url = "https://files.pythonhosted.org/packages/bd/25/7efcb5856f9df4c003716687c4604bb5cfc44819539b79d60e302018962b/faiss_cpu-1.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8e74e71249165757a12fb02feee67ea95df542bcafa21b449fbd2ed0c31b48b4", size = 24160951, upload-time = "2025-08-13T06:05:44.243Z" }, + { url = "https://files.pythonhosted.org/packages/34/d4/1f1cc444708b426b42ec52f01c735d91cb9775fe55cf3d2c64b9a6fd8792/faiss_cpu-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:d04d1cae2a9b66083cd8f48ff391731d81e0a1fdf67ab5c33ae10b3a22a0caae", size = 18169601, upload-time = "2025-08-13T06:05:46.558Z" }, + { url = "https://files.pythonhosted.org/packages/87/ed/83fed257ea410c2e691374f04ac914d5f9414f04a9c7a266bdfbb999eb16/faiss_cpu-1.12.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:fbb63595c7ad43c0d9caaf4d554a38a30ea4edda5e7c3ed38845562776992ba9", size = 8006079, upload-time = "2025-08-13T06:05:48.932Z" }, + { url = "https://files.pythonhosted.org/packages/5b/07/80c248db87ef2e753ad390fca3b0d7dd6092079e904f35b248c7064e791e/faiss_cpu-1.12.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83e74cbde6fa5caceec5bc103c82053d50fde163e3ceabaa58c91508e984142b", size = 3360138, upload-time = "2025-08-13T06:05:50.873Z" }, + { url = "https://files.pythonhosted.org/packages/b9/22/73bd9ed7b11cd14eb0da6e2f2eae763306abaad1b25a5808da8b1fc07665/faiss_cpu-1.12.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6155a5138604b702a32f8f0a63948a539eb7468898554a9911f9ab8c899284fb", size = 3825466, upload-time = "2025-08-13T06:05:52.311Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7f/e1a21337b3cba24b953c760696e3b188a533d724440e050fd60a3c1aa919/faiss_cpu-1.12.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bf4b5f0e9b6bb5a566b1a31e84a93b283f26c2b0155fb2eb5970c32a540a906", size = 31425626, upload-time = "2025-08-13T06:05:54.155Z" }, + { url = "https://files.pythonhosted.org/packages/05/24/f352cf8400f414e6a31385ef12d43d11aac8beb11d573a2fd00ec44b8cb7/faiss_cpu-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60a535b79d3d6225c7c21d7277fb0c6fde80c46a9c1e33632b1b293c1d177f30", size = 9751949, upload-time = "2025-08-13T06:05:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/05/50/a122e3076d7fd95cbe9a0cdf0fc796836f1e4fd399b418c6ba8533c75770/faiss_cpu-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0d1b243468a24564f85a41166f2ca4c92f8f6755da096ffbdcf551675ca739c5", size = 24161021, upload-time = "2025-08-13T06:05:58.776Z" }, + { url = "https://files.pythonhosted.org/packages/72/9f/3344f6fe69f6fbfb19dec298b4dda3d47a87dc31e418911fdcc3a3ace013/faiss_cpu-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:84510079a2efe954e6b89fe5e62f23a98c1ef999756565e056f95f835ff43c5e", size = 18169278, upload-time = "2025-08-13T06:06:01.44Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b1/37d532292c1b3dab690636947a532d3797741b09f2dfb9cb558ffeaff34b/faiss_cpu-1.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:2283f1014f7f86dd56b53bf0ea0d7f848eb4c9c6704b8f4f99a0af02e994e479", size = 8007093, upload-time = "2025-08-13T06:06:03.904Z" }, + { url = "https://files.pythonhosted.org/packages/4a/58/602ed184d35742eb240cbfea237bd214f2ae7f01cb369c39f4dff392f7c9/faiss_cpu-1.12.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:9b54990fcbcf90e37393909d4033520237194263c93ab6dbfae0616ef9af242b", size = 8034413, upload-time = "2025-08-13T06:06:05.564Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/f84c3d0e022cdeb73ff8406a6834a7698829fa242eb8590ddf8a0b09357f/faiss_cpu-1.12.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a5f5bca7e1a3e0a98480d1e2748fc86d12c28d506173e460e6746886ff0e08de", size = 3362034, upload-time = "2025-08-13T06:06:07.091Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a4ba4d285ea4f9b0824bf31ebded3171da08bfcf5376f4771cc5481f72cd/faiss_cpu-1.12.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:016e391f49933875b8d60d47f282f2e93d8ea9f9ffbda82467aa771b11a237db", size = 3834319, upload-time = "2025-08-13T06:06:08.86Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c9/be4e52fd96be601fefb313c26e1259ac2e6b556fb08cc392db641baba8c7/faiss_cpu-1.12.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2e4963c7188f57cfba248f09ebd8a14c76b5ffb87382603ccd4576f2da39d74", size = 31421585, upload-time = "2025-08-13T06:06:10.643Z" }, + { url = "https://files.pythonhosted.org/packages/4b/aa/12c6723ce30df721a6bace21398559c0367c5418c04139babc2d26d8d158/faiss_cpu-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:88bfe134f8c7cd2dda7df34f2619448906624962c8207efdd6eb1647e2f5338b", size = 9762449, upload-time = "2025-08-13T06:06:13.373Z" }, + { url = "https://files.pythonhosted.org/packages/67/15/ed2c9de47c3ebae980d6938f0ec12d739231438958bc5ab2d636b272d913/faiss_cpu-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9243ee4c224a0d74419040503f22bf067462a040281bf6f3f107ab205c97d438", size = 24156525, upload-time = "2025-08-13T06:06:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/6911de6b8fdcfa76144680c2195df6ce7e0cc920a8be8c5bbd2dfe5e3c37/faiss_cpu-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:6b8012353d50d9bc81bcfe35b226d0e5bfad345fdebe0da31848395ebc83816d", size = 18169636, upload-time = "2025-08-13T06:06:17.613Z" }, + { url = "https://files.pythonhosted.org/packages/2f/69/d2b0f434b0ae35344280346b58d2b9a251609333424f3289c54506e60c51/faiss_cpu-1.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:8b4f5b18cbe335322a51d2785bb044036609c35bfac5915bff95eadc10e89ef1", size = 8012423, upload-time = "2025-08-13T06:06:19.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4e/6be5fbd2ceccd87b168c64edeefa469cd11f095bb63b16a61a29296b0fdb/faiss_cpu-1.12.0-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:c9c79b5f28dcf9b2e2557ce51b938b21b7a9d508e008dc1ffea7b8249e7bd443", size = 8034409, upload-time = "2025-08-13T06:06:22.519Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f0/658012a91a690d82f3587fd8e56ea1d9b9698c31970929a9dba17edd211e/faiss_cpu-1.12.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0db6485bc9f32b69aaccf9ad520782371a79904dcfe20b6da5cbfd61a712e85f", size = 3362034, upload-time = "2025-08-13T06:06:24.052Z" }, + { url = "https://files.pythonhosted.org/packages/81/8b/9b355309d448e1a737fac31d45e9b2484ffb0f04f10fba3b544efe6661e4/faiss_cpu-1.12.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6db5532831791d7bac089fc580e741e99869122946bb6a5f120016c83b95d10", size = 3834324, upload-time = "2025-08-13T06:06:25.506Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/d229f6cdb9cbe03020499d69c4b431b705aa19a55aa0fe698c98022b2fef/faiss_cpu-1.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d57ed7aac048b18809af70350c31acc0fb9f00e6c03b6ed1651fd58b174882d", size = 31421590, upload-time = "2025-08-13T06:06:27.601Z" }, + { url = "https://files.pythonhosted.org/packages/26/19/80289ba008f14c95fbb6e94617ea9884e421ca745864fe6b8b90e1c3fc94/faiss_cpu-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:26c29290e7d1c5938e5886594dc0a2272b30728351ca5f855d4ae30704d5a6cc", size = 9762452, upload-time = "2025-08-13T06:06:30.237Z" }, + { url = "https://files.pythonhosted.org/packages/af/e7/6cc03ead5e19275e34992419e2b7d107d0295390ccf589636ff26adb41e2/faiss_cpu-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b43d0c295e93a8e5f1dd30325caaf34d4ecb51f1e3d461c7b0e71bff3a8944b", size = 24156530, upload-time = "2025-08-13T06:06:32.23Z" }, + { url = "https://files.pythonhosted.org/packages/34/90/438865fe737d65e7348680dadf3b2983bdcef7e5b7e852000e74c50a9933/faiss_cpu-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:a7c6156f1309bb969480280906e8865c3c4378eebb0f840c55c924bf06efd8d3", size = 18169604, upload-time = "2025-08-13T06:06:34.884Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/40a1d8d781a70d33c57ef1b4b777486761dd1c502a86d27e90ef6aa8a9f9/faiss_cpu-1.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:0b5fac98a350774a98b904f7a7c6689eb5cf0a593d63c552e705a80c55636d15", size = 8012523, upload-time = "2025-08-13T06:06:37.24Z" }, + { url = "https://files.pythonhosted.org/packages/12/35/01a4a7c179d67bee0d8a027b95c3eae19cb354ae69ef2bc50ac3b93bc853/faiss_cpu-1.12.0-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:ff7db774968210d08cd0331287f3f66a6ffef955a7aa9a7fcd3eb4432a4ce5f5", size = 8036142, upload-time = "2025-08-13T06:06:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/08/23/bac2859490096608c9d527f3041b44c2e43f8df0d4aadd53a4cc5ce678ac/faiss_cpu-1.12.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:220b5bb5439c64e417b35f9ade4c7dc3bf7df683d6123901ba84d6d764ecd486", size = 3363747, upload-time = "2025-08-13T06:06:40.73Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/e18023e1f43a18ec593adcd69d356f1fa94bde20344e38334d5985e5c5cc/faiss_cpu-1.12.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:693d0bf16f79e8d16a1baaeda459f3375f37da0354e97dc032806b48a2a54151", size = 3835232, upload-time = "2025-08-13T06:06:42.172Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2b/1c1fea423d3f550f44c5ec3f14d8400919b49c285c3bd146687c63e40186/faiss_cpu-1.12.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcc6587dee21e17430fb49ddc5200625d6f5e1de2bdf436f14827bad4ca78d19", size = 31432677, upload-time = "2025-08-13T06:06:44.348Z" }, + { url = "https://files.pythonhosted.org/packages/de/d2/3483e92a02f30e2d8491a256f470f54b7f5483266dfe09126d28741d31ec/faiss_cpu-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b80e5965f001822cc99ec65c715169af1b70bdae72eccd573520a2dec485b3ee", size = 9765504, upload-time = "2025-08-13T06:06:46.567Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2f/d97792211a9bd84b8d6b1dcaa1dcd69ac11e026c6ef19c641b6a87e31025/faiss_cpu-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98279f1b4876ef9902695a329b81a99002782ab6e26def472022009df6f1ac68", size = 24169930, upload-time = "2025-08-13T06:06:48.916Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b8/b707ca4d88af472509a053c39d3cced53efd19d096b8dff2fadc18c4b82d/faiss_cpu-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:11670337f9f5ee9ff3490e30683eea80add060c300cf6f6cb0e8faf3155fd20e", size = 18475400, upload-time = "2025-08-13T06:06:51.233Z" }, + { url = "https://files.pythonhosted.org/packages/77/11/42e41ddebde4dfe77e36e92d0110b4f733c8640883abffde54f802482deb/faiss_cpu-1.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:7ac1c8b53609b5c722ab60f1749260a7cb3c72fdfb720a0e3033067e73591da5", size = 8281229, upload-time = "2025-08-13T06:06:53.735Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/8ae5bbeabe70eb673c37fc7c77e2e476746331afb6654b2df97d8b6d380d/faiss_cpu-1.12.0-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:110b21b7bb4c93c4f1a5eb2ffb8ef99dcdb4725f8ab2e5cd161324e4d981f204", size = 8087247, upload-time = "2025-08-13T06:06:55.407Z" }, + { url = "https://files.pythonhosted.org/packages/f4/df/b3d79098860b67b126da351788c04ac243c29718dadc4a678a6f5e7209c0/faiss_cpu-1.12.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:82eb5515ce72be9a43f4cf74447a0d090e014231981df91aff7251204b506fbf", size = 3411043, upload-time = "2025-08-13T06:06:56.983Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2f/b1a2a03dd3cce22ff9fc434aa3c7390125087260c1d1349311da36eaa432/faiss_cpu-1.12.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:754eef89cdf2b35643df6b0923a5a098bdfecf63b5f4bd86c385042ee511b287", size = 3801789, upload-time = "2025-08-13T06:06:58.688Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a8/16ad0c6a966e93d04bfd5248d2be1d8b5849842b0e2611c5ecd26fcaf036/faiss_cpu-1.12.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7285c71c8f5e9c58b55175f5f74c78c518c52c421a88a430263f34e3e31f719c", size = 31231388, upload-time = "2025-08-13T06:07:00.55Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/9c16eca0b8f8b13c32c47a5e4ff7a4bc0ca3e7d263140312088811230871/faiss_cpu-1.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84a50d7a2f711f79cc8b65aa28956dba6435e47b71a38b2daea44c94c9b8e458", size = 9737605, upload-time = "2025-08-13T06:07:03.018Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4a/2c2d615078c9d816a836fb893aaef551ad152f2eb00bc258698273c240c0/faiss_cpu-1.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f3e0a14e4edec6a3959a9f51afccb89e863138f184ff2cc24c13f9ad788740b", size = 23922880, upload-time = "2025-08-13T06:07:05.099Z" }, + { url = "https://files.pythonhosted.org/packages/30/aa/99b8402a4dac678794f13f8f4f29d666c2ef0a91594418147f47034ebc81/faiss_cpu-1.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8b3239cc371df6826ac43c62ac04eec7cc497bedb43f681fcd8ea494f520ddbb", size = 18750661, upload-time = "2025-08-13T06:07:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a2/b546e9a20ba157eb2fbe141289f1752f157ee6d932899f4853df4ded6d4b/faiss_cpu-1.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58b23456db725ee1bd605a6135d2ef55b2ac3e0b6fe873fd99a909e8ef4bd0ff", size = 8302032, upload-time = "2025-08-13T06:07:09.602Z" }, + { url = "https://files.pythonhosted.org/packages/95/5a/1ec3ee947526d95cb5656ef3343d7022f92704eee45f38249ee540378d44/faiss_cpu-1.12.0-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:564c1b707fa9e68e4551cd78cbc6dc89810d740cccfc3076c34a0ba6627119ef", size = 8006142, upload-time = "2025-08-13T06:07:11.476Z" }, + { url = "https://files.pythonhosted.org/packages/a9/94/9f3a0e09e5f8c360dcea076be3c9a30c55e7fb23cb2741c12f70921bc061/faiss_cpu-1.12.0-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:6a80f9223f1f5eea42a853355520ff0efd38d00923dd9e581195b9ecf1095f0e", size = 3359833, upload-time = "2025-08-13T06:07:13.387Z" }, + { url = "https://files.pythonhosted.org/packages/dc/65/0ba324613bd234dc48d51da9e0124b7e97de242251135207f368b473062e/faiss_cpu-1.12.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b9d38691646da1ba12e3c0be8eb910ac52e2bcb0e553b54590c1c7aded9d5a3", size = 3824212, upload-time = "2025-08-13T06:07:15.315Z" }, + { url = "https://files.pythonhosted.org/packages/d3/03/17777b9cb92db034e49804522ec176bbd4898cdca0c7c1ebf483d5817bee/faiss_cpu-1.12.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:433ddfd09f5ef3cd2593b03a39f37e6957b25a91792b17132d3414c99d5ba5dd", size = 31425806, upload-time = "2025-08-13T06:07:17.51Z" }, + { url = "https://files.pythonhosted.org/packages/b6/c9/87a8f360436378238074e9980d876d8e5e1dea86eeaa66fab8e4acb62d79/faiss_cpu-1.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fc98084076d0ceffcc3e306fd77ee22f91cc6143953e471c31c84cf2ddf68bb2", size = 9751334, upload-time = "2025-08-13T06:07:19.777Z" }, + { url = "https://files.pythonhosted.org/packages/5f/89/f9145aee30ab0728abb16ee4374469eb50a65ea004c26321097c70818d4a/faiss_cpu-1.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5867ee3132cdc9f279951b8514ab51fd121acfbea120c9948b06bd1a1e43053", size = 24160811, upload-time = "2025-08-13T06:07:22.123Z" }, + { url = "https://files.pythonhosted.org/packages/62/cf/41c9912052afce0c3de2be92639ba609bd80f7cabde7e1abd0f80664010b/faiss_cpu-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:9bcc6e9eb111fc5b06f1623f4073b5acdae25a5baee1eea32b347a2d810deb35", size = 18169958, upload-time = "2025-08-13T06:07:24.365Z" }, +] + [[package]] name = "fastjsonschema" version = "2.21.1" @@ -2058,7 +2147,7 @@ wheels = [ [[package]] name = "leann-backend-diskann" -version = "0.3.0" +version = "0.3.2" source = { editable = "packages/leann-backend-diskann" } dependencies = [ { name = "leann-core" }, @@ -2070,14 +2159,14 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "leann-core", specifier = "==0.3.0" }, + { name = "leann-core", specifier = "==0.3.2" }, { name = "numpy" }, { name = "protobuf", specifier = ">=3.19.0" }, ] [[package]] name = "leann-backend-hnsw" -version = "0.3.0" +version = "0.3.2" source = { editable = "packages/leann-backend-hnsw" } dependencies = [ { name = "leann-core" }, @@ -2090,7 +2179,7 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "leann-core", specifier = "==0.3.0" }, + { name = "leann-core", specifier = "==0.3.2" }, { name = "msgpack", specifier = ">=1.0.0" }, { name = "numpy" }, { name = "pyzmq", specifier = ">=23.0.0" }, @@ -2098,7 +2187,7 @@ requires-dist = [ [[package]] name = "leann-core" -version = "0.3.0" +version = "0.3.2" source = { editable = "packages/leann-core" } dependencies = [ { name = "accelerate" }, @@ -2164,10 +2253,12 @@ name = "leann-workspace" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "astchunk" }, { name = "boto3" }, { name = "colorama" }, { name = "datasets" }, { name = "evaluate" }, + { name = "faiss-cpu" }, { name = "gitignore-parser" }, { name = "ipykernel" }, { name = "leann-backend-hnsw" }, @@ -2198,6 +2289,12 @@ dependencies = [ { name = "sglang" }, { name = "torch" }, { name = "tqdm" }, + { name = "tree-sitter", version = "0.23.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "tree-sitter", version = "0.25.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "tree-sitter-c-sharp" }, + { name = "tree-sitter-java" }, + { name = "tree-sitter-python" }, + { name = "tree-sitter-typescript" }, { name = "typer" }, ] @@ -2231,12 +2328,14 @@ test = [ [package.metadata] requires-dist = [ + { name = "astchunk", specifier = ">=0.1.0" }, { name = "beautifulsoup4", marker = "extra == 'documents'", specifier = ">=4.13.0" }, { name = "black", marker = "extra == 'dev'", specifier = ">=23.0" }, { name = "boto3" }, { name = "colorama" }, { name = "datasets", specifier = ">=2.15.0" }, { name = "evaluate" }, + { name = "faiss-cpu", specifier = ">=1.12.0" }, { name = "gitignore-parser", specifier = ">=0.1.12" }, { name = "huggingface-hub", marker = "extra == 'dev'", specifier = ">=0.20.0" }, { name = "ipykernel", specifier = "==6.29.5" }, @@ -2280,6 +2379,11 @@ requires-dist = [ { name = "sglang" }, { name = "torch" }, { name = "tqdm" }, + { name = "tree-sitter", specifier = ">=0.20.0" }, + { name = "tree-sitter-c-sharp", specifier = ">=0.20.0" }, + { name = "tree-sitter-java", specifier = ">=0.20.0" }, + { name = "tree-sitter-python", specifier = ">=0.20.0" }, + { name = "tree-sitter-typescript", specifier = ">=0.20.0" }, { name = "typer", specifier = ">=0.12.3" }, ] provides-extras = ["dev", "test", "diskann", "documents"] @@ -4441,6 +4545,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/7a/097801205b991bc3115e8af1edb850d30aeaf0118520b016354cf5ccd3f6/pypdfium2-4.30.0-py3-none-win_arm64.whl", hash = "sha256:119b2969a6d6b1e8d55e99caaf05290294f2d0fe49c12a3f17102d01c441bd29", size = 2752118, upload-time = "2024-05-09T18:33:15.489Z" }, ] +[[package]] +name = "pyrsistent" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/3a/5031723c09068e9c8c2f0bc25c3a9245f2b1d1aea8396c787a408f2b95ca/pyrsistent-0.20.0.tar.gz", hash = "sha256:4c48f78f62ab596c679086084d0dd13254ae4f3d6c72a83ffdf5ebdef8f265a4", size = 103642, upload-time = "2023-10-25T21:06:56.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/19/c343b14061907b629b765444b6436b160e2bd4184d17d4804bbe6381f6be/pyrsistent-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c3aba3e01235221e5b229a6c05f585f344734bd1ad42a8ac51493d74722bbce", size = 83416, upload-time = "2023-10-25T21:06:04.579Z" }, + { url = "https://files.pythonhosted.org/packages/9f/4f/8342079ea331031ef9ed57edd312a9ad283bcc8adfaf268931ae356a09a6/pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1beb78af5423b879edaf23c5591ff292cf7c33979734c99aa66d5914ead880f", size = 118021, upload-time = "2023-10-25T21:06:06.953Z" }, + { url = "https://files.pythonhosted.org/packages/d7/b7/64a125c488243965b7c5118352e47c6f89df95b4ac306d31cee409153d57/pyrsistent-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cc459636983764e692b9eba7144cdd54fdec23ccdb1e8ba392a63666c60c34", size = 117747, upload-time = "2023-10-25T21:06:08.5Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/43c67bd5f80df9e7583042398d12113263ec57f27c0607abe9d78395d18f/pyrsistent-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5ac696f02b3fc01a710427585c855f65cd9c640e14f52abe52020722bb4906b", size = 114524, upload-time = "2023-10-25T21:06:10.728Z" }, + { url = "https://files.pythonhosted.org/packages/8a/98/b382a87e89ca839106d874f7bf78d226b3eedb26735eb6f751f1a3375f21/pyrsistent-0.20.0-cp310-cp310-win32.whl", hash = "sha256:0724c506cd8b63c69c7f883cc233aac948c1ea946ea95996ad8b1380c25e1d3f", size = 60780, upload-time = "2023-10-25T21:06:12.14Z" }, + { url = "https://files.pythonhosted.org/packages/37/8a/23e2193f7adea6901262e3cf39c7fe18ac0c446176c0ff0e19aeb2e9681e/pyrsistent-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:8441cf9616d642c475684d6cf2520dd24812e996ba9af15e606df5f6fd9d04a7", size = 63310, upload-time = "2023-10-25T21:06:13.598Z" }, + { url = "https://files.pythonhosted.org/packages/df/63/7544dc7d0953294882a5c587fb1b10a26e0c23d9b92281a14c2514bac1f7/pyrsistent-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f3b1bcaa1f0629c978b355a7c37acd58907390149b7311b5db1b37648eb6958", size = 83481, upload-time = "2023-10-25T21:06:15.238Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a0/49249bc14d71b1bf2ffe89703acfa86f2017c25cfdabcaea532b8c8a5810/pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdd7ef1ea7a491ae70d826b6cc64868de09a1d5ff9ef8d574250d0940e275b8", size = 120222, upload-time = "2023-10-25T21:06:17.144Z" }, + { url = "https://files.pythonhosted.org/packages/a1/94/9808e8c9271424120289b9028a657da336ad7e43da0647f62e4f6011d19b/pyrsistent-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cae40a9e3ce178415040a0383f00e8d68b569e97f31928a3a8ad37e3fde6df6a", size = 120002, upload-time = "2023-10-25T21:06:18.727Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f6/9ecfb78b2fc8e2540546db0fe19df1fae0f56664a5958c21ff8861b0f8da/pyrsistent-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6288b3fa6622ad8a91e6eb759cfc48ff3089e7c17fb1d4c59a919769314af224", size = 116850, upload-time = "2023-10-25T21:06:20.424Z" }, + { url = "https://files.pythonhosted.org/packages/83/c8/e6d28bc27a0719f8eaae660357df9757d6e9ca9be2691595721de9e8adfc/pyrsistent-0.20.0-cp311-cp311-win32.whl", hash = "sha256:7d29c23bdf6e5438c755b941cef867ec2a4a172ceb9f50553b6ed70d50dfd656", size = 60775, upload-time = "2023-10-25T21:06:21.815Z" }, + { url = "https://files.pythonhosted.org/packages/98/87/c6ef52ff30388f357922d08de012abdd3dc61e09311d88967bdae23ab657/pyrsistent-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:59a89bccd615551391f3237e00006a26bcf98a4d18623a19909a2c48b8e986ee", size = 63306, upload-time = "2023-10-25T21:06:22.874Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/ff2ed52032ac1ce2e7ba19e79bd5b05d152ebfb77956cf08fcd6e8d760ea/pyrsistent-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:09848306523a3aba463c4b49493a760e7a6ca52e4826aa100ee99d8d39b7ad1e", size = 83537, upload-time = "2023-10-25T21:06:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/80/f1/338d0050b24c3132bcfc79b68c3a5f54bce3d213ecef74d37e988b971d8a/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a14798c3005ec892bbada26485c2eea3b54109cb2533713e355c806891f63c5e", size = 122615, upload-time = "2023-10-25T21:06:25.815Z" }, + { url = "https://files.pythonhosted.org/packages/07/3a/e56d6431b713518094fae6ff833a04a6f49ad0fbe25fb7c0dc7408e19d20/pyrsistent-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b14decb628fac50db5e02ee5a35a9c0772d20277824cfe845c8a8b717c15daa3", size = 122335, upload-time = "2023-10-25T21:06:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/4a/bb/5f40a4d5e985a43b43f607250e766cdec28904682c3505eb0bd343a4b7db/pyrsistent-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e2c116cc804d9b09ce9814d17df5edf1df0c624aba3b43bc1ad90411487036d", size = 118510, upload-time = "2023-10-25T21:06:30.718Z" }, + { url = "https://files.pythonhosted.org/packages/1c/13/e6a22f40f5800af116c02c28e29f15c06aa41cb2036f6a64ab124647f28b/pyrsistent-0.20.0-cp312-cp312-win32.whl", hash = "sha256:e78d0c7c1e99a4a45c99143900ea0546025e41bb59ebc10182e947cf1ece9174", size = 60865, upload-time = "2023-10-25T21:06:32.742Z" }, + { url = "https://files.pythonhosted.org/packages/75/ef/2fa3b55023ec07c22682c957808f9a41836da4cd006b5f55ec76bf0fbfa6/pyrsistent-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:4021a7f963d88ccd15b523787d18ed5e5269ce57aa4037146a2377ff607ae87d", size = 63239, upload-time = "2023-10-25T21:06:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/18/0c/289126299fcebf54fd01d385fb5176c328fef2c4233139c23dd48346e992/pyrsistent-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ca52d1ceae015859d16aded12584c59eb3825f7b50c6cfd621d4231a6cc624ce", size = 83379, upload-time = "2023-10-25T21:06:45.585Z" }, + { url = "https://files.pythonhosted.org/packages/4e/45/62639d53ac09eaafc00f2e5845565e70d3eddb2d296337a77637186ca03e/pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b318ca24db0f0518630e8b6f3831e9cba78f099ed5c1d65ffe3e023003043ba0", size = 117740, upload-time = "2023-10-25T21:06:46.918Z" }, + { url = "https://files.pythonhosted.org/packages/ab/12/24b9a6ef7b991b6722756e0aa169a39463af2b8ed0fb526f0a00aae34ea4/pyrsistent-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed2c3216a605dc9a6ea50c7e84c82906e3684c4e80d2908208f662a6cbf9022", size = 117457, upload-time = "2023-10-25T21:06:48.911Z" }, + { url = "https://files.pythonhosted.org/packages/19/3c/ab06510f86bc0934b77ade41948924ff1f33dcd3433f32feca2028218837/pyrsistent-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e14c95c16211d166f59c6611533d0dacce2e25de0f76e4c140fde250997b3ca", size = 114280, upload-time = "2023-10-25T21:06:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b1/1275bbfb929854d20e72aa2bbfb50ea3b1d7d41a95848b353691875e2817/pyrsistent-0.20.0-cp39-cp39-win32.whl", hash = "sha256:f058a615031eea4ef94ead6456f5ec2026c19fb5bd6bfe86e9665c4158cf802f", size = 60764, upload-time = "2023-10-25T21:06:52.093Z" }, + { url = "https://files.pythonhosted.org/packages/28/77/0d7af973c0e3b1b83d8b45943601f77f85b943007e3a4d8744f7102c652b/pyrsistent-0.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:58b8f6366e152092194ae68fefe18b9f0b4f89227dfd86a07770c3d86097aebf", size = 63289, upload-time = "2023-10-25T21:06:53.221Z" }, + { url = "https://files.pythonhosted.org/packages/23/88/0acd180010aaed4987c85700b7cc17f9505f3edb4e5873e4dc67f613e338/pyrsistent-0.20.0-py3-none-any.whl", hash = "sha256:c55acc4733aad6560a7f5f818466631f07efc001fd023f34a6c203f8b6df0f0b", size = 58106, upload-time = "2023-10-25T21:06:54.387Z" }, +] + [[package]] name = "pytest" version = "8.4.1" @@ -5827,6 +5964,165 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/34/4d82dc596764de9d14285f8ed53b50896bf05fbbcd71a82c6d174b3ab8c7/transformers-4.54.0-py3-none-any.whl", hash = "sha256:c96e607f848625965b76c677b2c2576f2c7b7097c1c5292b281919d90675a25e", size = 11176597, upload-time = "2025-07-25T18:58:17.677Z" }, ] +[[package]] +name = "tree-sitter" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/50/fd5fafa42b884f741b28d9e6fd366c3f34e15d2ed3aa9633b34e388379e2/tree-sitter-0.23.2.tar.gz", hash = "sha256:66bae8dd47f1fed7bdef816115146d3a41c39b5c482d7bad36d9ba1def088450", size = 166800, upload-time = "2024-10-24T15:31:02.238Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/04/2068a7b725265ecfcbf63ecdae038f1d4124ebccd55b8a7ce145b70e2b6a/tree_sitter-0.23.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3a937f5d8727bc1c74c4bf2a9d1c25ace049e8628273016ad0d45914ae904e10", size = 139289, upload-time = "2024-10-24T15:29:59.27Z" }, + { url = "https://files.pythonhosted.org/packages/a8/07/a5b943121f674fe1ac77694a698e71ce95353830c1f3f4ce45da7ef3e406/tree_sitter-0.23.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2c7eae7fe2af215645a38660d2d57d257a4c461fe3ec827cca99a79478284e80", size = 132379, upload-time = "2024-10-24T15:30:01.437Z" }, + { url = "https://files.pythonhosted.org/packages/d4/96/fcc72c33d464a2d722db1e95b74a53ced771a47b3cfde60aced29764a783/tree_sitter-0.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a71d607595270b6870eaf778a1032d146b2aa79bfcfa60f57a82a7b7584a4c7", size = 552884, upload-time = "2024-10-24T15:30:02.672Z" }, + { url = "https://files.pythonhosted.org/packages/d0/af/b0e787a52767155b4643a55d6de03c1e4ae77abb61e1dc1629ad983e0a40/tree_sitter-0.23.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fe9b9ea7a0aa23b52fd97354da95d1b2580065bc12a4ac868f9164a127211d6", size = 566561, upload-time = "2024-10-24T15:30:04.073Z" }, + { url = "https://files.pythonhosted.org/packages/65/fd/05e966b5317b1c6679c071c5b0203f28af9d26c9363700cb9682e1bcf343/tree_sitter-0.23.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d74d00a8021719eae14d10d1b1e28649e15d8b958c01c2b2c3dad7a2ebc4dbae", size = 558273, upload-time = "2024-10-24T15:30:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/60/bc/19145efdf3f47711aa3f1bf06f0b50593f97f1108550d38694841fd97b7c/tree_sitter-0.23.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6de18d8d8a7f67ab71f472d1fcb01cc506e080cbb5e13d52929e4b6fdce6bbee", size = 569176, upload-time = "2024-10-24T15:30:07.902Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/3553d8e488ae9284a0762effafb7d2639a306e184963b7f99853923084d6/tree_sitter-0.23.2-cp310-cp310-win_amd64.whl", hash = "sha256:12b60dca70d2282af942b650a6d781be487485454668c7c956338a367b98cdee", size = 117902, upload-time = "2024-10-24T15:30:09.675Z" }, + { url = "https://files.pythonhosted.org/packages/1d/39/836fa485e985c33e8aa1cc3abbf7a84be1c2c382e69547a765631fdd7ce3/tree_sitter-0.23.2-cp310-cp310-win_arm64.whl", hash = "sha256:3346a4dd0447a42aabb863443b0fd8c92b909baf40ed2344fae4b94b625d5955", size = 102644, upload-time = "2024-10-24T15:30:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/55/8d/2d4fb04408772be0919441d66f700673ce7cb76b9ab6682e226d740fb88d/tree_sitter-0.23.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91fda41d4f8824335cc43c64e2c37d8089c8c563bd3900a512d2852d075af719", size = 139142, upload-time = "2024-10-24T15:30:12.627Z" }, + { url = "https://files.pythonhosted.org/packages/32/52/b8a44bfff7b0203256e5dbc8d3a372ee8896128b8ed7d3a89e1ef17b2065/tree_sitter-0.23.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:92b2b489d5ce54b41f94c6f23fbaf592bd6e84dc2877048fd1cb060480fa53f7", size = 132198, upload-time = "2024-10-24T15:30:13.893Z" }, + { url = "https://files.pythonhosted.org/packages/5d/54/746f2ee5acf6191a4a0be7f5843329f0d713bfe5196f5fc6fe2ea69cb44c/tree_sitter-0.23.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64859bd4aa1567d0d6016a811b2b49c59d4a4427d096e3d8c84b2521455f62b7", size = 554303, upload-time = "2024-10-24T15:30:15.334Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5a/3169d9933be813776a9b4b3f2e671d3d50fa27e589dee5578f6ecef7ff6d/tree_sitter-0.23.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:614590611636044e071d3a0b748046d52676dbda3bc9fa431216231e11dd98f7", size = 567626, upload-time = "2024-10-24T15:30:17.12Z" }, + { url = "https://files.pythonhosted.org/packages/32/0d/23f363b3b0bc3fa0e7a4a294bf119957ac1ab02737d57815e1e8b7b3e196/tree_sitter-0.23.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08466953c78ae57be61057188fb88c89791b0a562856010228e0ccf60e2ac453", size = 559803, upload-time = "2024-10-24T15:30:18.921Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b3/1ffba0f17a7ff2c9114d91a1ecc15e0748f217817797564d31fbb61d7458/tree_sitter-0.23.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8a33f03a562de91f7fd05eefcedd8994a06cd44c62f7aabace811ad82bc11cbd", size = 570987, upload-time = "2024-10-24T15:30:21.116Z" }, + { url = "https://files.pythonhosted.org/packages/59/4b/085bcb8a11ea18003aacc4dbc91c301d1536c5e2deedb95393e8ef26f1f7/tree_sitter-0.23.2-cp311-cp311-win_amd64.whl", hash = "sha256:03b70296b569ef64f7b92b42ca5da9bf86d81bee2afd480bea35092687f51dae", size = 117771, upload-time = "2024-10-24T15:30:22.38Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e5/90adc4081f49ccb6bea89a800dc9b0dcc5b6953b0da423e8eff28f63fddf/tree_sitter-0.23.2-cp311-cp311-win_arm64.whl", hash = "sha256:7cb4bb953ea7c0b50eeafc4454783e030357179d2a93c3dd5ebed2da5588ddd0", size = 102555, upload-time = "2024-10-24T15:30:23.534Z" }, + { url = "https://files.pythonhosted.org/packages/07/a7/57e0fe87b49a78c670a7b4483f70e44c000c65c29b138001096b22e7dd87/tree_sitter-0.23.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a014498b6a9e6003fae8c6eb72f5927d62da9dcb72b28b3ce8cd15c6ff6a6572", size = 139259, upload-time = "2024-10-24T15:30:24.941Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b9/bc8513d818ffb54993a017a36c8739300bc5739a13677acf90b54995e7db/tree_sitter-0.23.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04f8699b131d4bcbe3805c37e4ef3d159ee9a82a0e700587625623999ba0ea53", size = 131951, upload-time = "2024-10-24T15:30:26.176Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6a/eab01bb6b1ce3c9acf16d72922ffc29a904af485eb3e60baf3a3e04edd30/tree_sitter-0.23.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4471577df285059c71686ecb208bc50fb472099b38dcc8e849b0e86652891e87", size = 557952, upload-time = "2024-10-24T15:30:27.389Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/f2f73332623cf63200d57800f85273170bc5f99d28ea3f234afd5b0048df/tree_sitter-0.23.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f342c925290dd4e20ecd5787ef7ae8749981597ab364783a1eb73173efe65226", size = 571199, upload-time = "2024-10-24T15:30:28.879Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/bd6e6cfdd0421156e86f5c93848629af1c7323083077e1a95b27d32d5811/tree_sitter-0.23.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a4e9e53d07dd076bede72e4f7d3a0173d7b9ad6576572dd86da008a740a9bb22", size = 562129, upload-time = "2024-10-24T15:30:30.199Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/8a9edcbcf8a76b0bf58e3b927ed291e3598e063d56667367762833cc8709/tree_sitter-0.23.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8caebe65bc358759dac2500d8f8feed3aed939c4ade9a684a1783fe07bc7d5db", size = 574307, upload-time = "2024-10-24T15:30:32.085Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c2/3fb2c6c0ae2f59a7411dc6d3e7945e3cb6f34c8552688708acc8b2b13f83/tree_sitter-0.23.2-cp312-cp312-win_amd64.whl", hash = "sha256:fc5a72eb50d43485000dbbb309acb350467b7467e66dc747c6bb82ce63041582", size = 117858, upload-time = "2024-10-24T15:30:33.353Z" }, + { url = "https://files.pythonhosted.org/packages/e2/18/4ca2c0f4a0c802ebcb3a92264cc436f1d54b394fa24dfa76bf57cdeaca9e/tree_sitter-0.23.2-cp312-cp312-win_arm64.whl", hash = "sha256:a0320eb6c7993359c5f7b371d22719ccd273f440d41cf1bd65dac5e9587f2046", size = 102496, upload-time = "2024-10-24T15:30:34.782Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c6/4ead9ce3113a7c27f37a2bdef163c09757efbaa85adbdfe7b3fbf0317c57/tree_sitter-0.23.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eff630dddee7ba05accb439b17e559e15ce13f057297007c246237ceb6306332", size = 139266, upload-time = "2024-10-24T15:30:35.946Z" }, + { url = "https://files.pythonhosted.org/packages/76/c9/b4197c5b0c1d6ba648202a547846ac910a53163b69a459504b2aa6cdb76e/tree_sitter-0.23.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4780ba8f3894f2dea869fad2995c2aceab3fd5ab9e6a27c45475d2acd7f7e84e", size = 131959, upload-time = "2024-10-24T15:30:37.646Z" }, + { url = "https://files.pythonhosted.org/packages/99/94/0f7c5580d2adff3b57d36f1998725b0caf6cf1af50ceafc00c6cdbc2fef6/tree_sitter-0.23.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0b609460b8e3e256361fb12e94fae5b728cb835b16f0f9d590b5aadbf9d109b", size = 557582, upload-time = "2024-10-24T15:30:39.019Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/f73ff06959d43fd47fc283cbcc4d8efa6550b2cc431d852b184504992447/tree_sitter-0.23.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d070d8eaeaeb36cf535f55e5578fddbfc3bf53c1980f58bf1a99d57466b3b5", size = 570891, upload-time = "2024-10-24T15:30:40.432Z" }, + { url = "https://files.pythonhosted.org/packages/b8/86/bbda5ad09b88051ff7bf3275622a2f79bc4f728b4c283ff8b93b8fcdf36d/tree_sitter-0.23.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878580b2ad5054c410ba3418edca4d34c81cc26706114d8f5b5541688bc2d785", size = 562343, upload-time = "2024-10-24T15:30:43.045Z" }, + { url = "https://files.pythonhosted.org/packages/ca/55/b404fa49cb5c2926ad6fe1cac033dd486ef69f1afeb7828452d21e1e05c1/tree_sitter-0.23.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:29224bdc2a3b9af535b7725e249d3ee291b2e90708e82832e73acc175e40dc48", size = 574407, upload-time = "2024-10-24T15:30:45.018Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/eea2104443ab973091107ef3e730683bd8e6cb51dd025cef853d3fff9dae/tree_sitter-0.23.2-cp313-cp313-win_amd64.whl", hash = "sha256:c58d89348162fbc3aea1fe6511a66ee189fc0e4e4bbe937026f29e4ecef17763", size = 117854, upload-time = "2024-10-24T15:30:47.817Z" }, + { url = "https://files.pythonhosted.org/packages/89/4d/1728d9ce32a1d851081911b7e47830f5e740431f2bb920f54bb8c26175bc/tree_sitter-0.23.2-cp313-cp313-win_arm64.whl", hash = "sha256:0ff2037be5edab7801de3f6a721b9cf010853f612e2008ee454e0e0badb225a6", size = 102492, upload-time = "2024-10-24T15:30:48.892Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ab/b39173a47d498cc6276e303c865f4a222134ceae890bd3c1b29427489805/tree_sitter-0.23.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a5db8e585205faef8bf219da77d8993e2ef04d08eda2e3c8ad7e4df8297ee344", size = 139550, upload-time = "2024-10-24T15:30:50.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/34/fa8f5b862dd7a6014fd5578810178e8f7601830cabb6d65d2aba050c2df1/tree_sitter-0.23.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9dbd110a30cf28be5da734ae4cd0e9031768228dbf6a79f2973962aa51de4ec7", size = 132686, upload-time = "2024-10-24T15:30:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/98/b9/ccdddf35705fc23395caa71557f767e0753d38afe4b5bb99efddbf62bb22/tree_sitter-0.23.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569514b9a996a0fd458b3a891c46ca125298be0c03cf82f2b6f0c13d5d8f25dc", size = 554958, upload-time = "2024-10-24T15:30:53.327Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ba/20ae9079bdfc5cfac28b39d945a6c354c8e1385e73aec8142db6c53b635c/tree_sitter-0.23.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a357ed98a74e47787b812df99a74a2c35c0fe11e55c2095cc01d1cad144ef552", size = 568162, upload-time = "2024-10-24T15:30:54.667Z" }, + { url = "https://files.pythonhosted.org/packages/40/00/b16bf6cf88c47c1b6c8e1cce1eb9e90badb5db9e5252ae0970d858d02592/tree_sitter-0.23.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c2dfb8e8f760f4cc67888d03ef9e2dbd3353245f67f5efba375c2a14d944ac0e", size = 560278, upload-time = "2024-10-24T15:30:56.49Z" }, + { url = "https://files.pythonhosted.org/packages/7a/8f/27ab9b96cc0261af78b080ec8a9846a38e216360ec38774ea27eba35bd3c/tree_sitter-0.23.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3ead958df87a21d706903987e665e9e0e5df7b2c5021ff69ea349826840adc6a", size = 571255, upload-time = "2024-10-24T15:30:58.254Z" }, + { url = "https://files.pythonhosted.org/packages/44/e0/95a3d66a7e5bb229574484ab10c6dc99d1c7a32972b890d194076e30dc4f/tree_sitter-0.23.2-cp39-cp39-win_amd64.whl", hash = "sha256:611cae16be332213c0e6ece72c0bfca202e30ff320a8b309b1526c6cb79ee4ba", size = 118232, upload-time = "2024-10-24T15:30:59.965Z" }, + { url = "https://files.pythonhosted.org/packages/10/b5/9eaf794fc71490573ab14a366affca415bc1ddbf86a14d78e54583db4254/tree_sitter-0.23.2-cp39-cp39-win_arm64.whl", hash = "sha256:b848e0fdd522fbb8888cdb4f4d93f8fad97ae10d70c122fb922e51363c7febcd", size = 102787, upload-time = "2024-10-24T15:31:01.084Z" }, +] + +[[package]] +name = "tree-sitter" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/89/2b/02a642e67605b9dd59986b00d13a076044dede04025a243f0592ac79d68c/tree-sitter-0.25.1.tar.gz", hash = "sha256:cd761ad0e4d1fc88a4b1b8083bae06d4f973acf6f5f29bbf13ea9609c1dec9c1", size = 177874, upload-time = "2025-08-05T17:14:34.193Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/6c/6160ca15926d11a6957d8bee887f477f3c1d9bc5272c863affc0b50b9cff/tree_sitter-0.25.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a15d62ffdb095d509bda8c140c1ddd0cc80f0c67f92b87fcc96cd242dc0c71ea", size = 146692, upload-time = "2025-08-05T17:13:54.559Z" }, + { url = "https://files.pythonhosted.org/packages/81/4a/e5eb39fe73a514a13bf94acee97925de296d673dace00557763cbbdc938f/tree_sitter-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d938f0a1ffad1206a1a569b0501345eeca81cae0a4487bb485e53768b02f24e", size = 141015, upload-time = "2025-08-05T17:13:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/63/22/c8e3ba245e5cdb8c951482028a7ee99d141302047b708dc9d670f0fafd85/tree_sitter-0.25.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba8cea296de5dcb384b9a15cf526985ac8339c81da51c7e29a251d82071f5ee9", size = 599462, upload-time = "2025-08-05T17:13:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/c2/91/c866c3d278ee86354fd81fd055b5d835c510b0e9af07e1cf7e48e2f946b0/tree_sitter-0.25.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:387fd2bd8657d69e877618dc199c18e2d6fe073b8f5c59e23435f3baee4ee10a", size = 627062, upload-time = "2025-08-05T17:13:58.363Z" }, + { url = "https://files.pythonhosted.org/packages/90/96/ac010f72778dae60381ab5fcca9651ac72647d582db0b027ca6c56116920/tree_sitter-0.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:afa49e51f82b58ae2c1291d6b79ca31e0fb36c04bd9a20d89007472edfb70136", size = 623788, upload-time = "2025-08-05T17:13:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/0e/29/190bdfd54a564a2e43a702884ad5679f4578c481a46161f9f335dd390a70/tree_sitter-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:77be45f666adf284914510794b41100decccd71dba88010c03dc2bb0d653acec", size = 127253, upload-time = "2025-08-05T17:14:00.446Z" }, + { url = "https://files.pythonhosted.org/packages/da/60/7daca5ccf65fb204c9f2cc2907db6aeaf1cb42aa605427580c17a38a53b3/tree_sitter-0.25.1-cp310-cp310-win_arm64.whl", hash = "sha256:72badac2de4e81ae0df5efe14ec5003bd4df3e48e7cf84dbd9df3a54599ba371", size = 113930, upload-time = "2025-08-05T17:14:01.623Z" }, + { url = "https://files.pythonhosted.org/packages/17/dc/0dabb75d249108fb9062d6e9e791e4ad8e9ae5c095e06dd8af770bc07902/tree_sitter-0.25.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33a8fbaeb2b5049cf5318306ab8b16ab365828b2b21ee13678c29e0726a1d27a", size = 146696, upload-time = "2025-08-05T17:14:02.408Z" }, + { url = "https://files.pythonhosted.org/packages/da/d0/b7305a05d65dbcfce7a97a93252bf7384f09800866e9de55a625c76e0257/tree_sitter-0.25.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:797bbbc686d8d3722d25ee0108ad979bda6ad3e1025859ce2ee290e517816bd4", size = 141014, upload-time = "2025-08-05T17:14:03.58Z" }, + { url = "https://files.pythonhosted.org/packages/84/d0/d0d8bd13c44ef6379499712a3f5e3930e7db11e5c8eb2af8655e288597a3/tree_sitter-0.25.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:629fc2ae3f5954b0f6a7b42ee3fcd8f34b68ea161e9f02fa5bf709cbbac996d3", size = 604339, upload-time = "2025-08-05T17:14:04.722Z" }, + { url = "https://files.pythonhosted.org/packages/c5/13/22869a6da25ffe2dfff922712605e72a9c3481109a93f4218bea1bc65f35/tree_sitter-0.25.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4257018c42a33a7935a5150d678aac05c6594347d6a6e6dbdf7e2ef4ae985213", size = 631593, upload-time = "2025-08-05T17:14:06.043Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/f4590fc08422768fc57456a85c932888a02e7a13540574859308611be1cf/tree_sitter-0.25.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4027854c9feee2a3bb99642145ba04ce95d75bd17e292911c93a488cb28d0a04", size = 629265, upload-time = "2025-08-05T17:14:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a8/ee9305ce9a7417715cbf038fdcc4fdb6042e30065c9837bdcf36be440388/tree_sitter-0.25.1-cp311-cp311-win_amd64.whl", hash = "sha256:183faaedcee5f0a3ba39257fa81749709d5eb7cf92c2c050b36ff38468d1774c", size = 127210, upload-time = "2025-08-05T17:14:08.331Z" }, + { url = "https://files.pythonhosted.org/packages/48/64/6a39882f534373873ef3dba8a1a8f47dc3bfb39ee63784eac2e789b404c4/tree_sitter-0.25.1-cp311-cp311-win_arm64.whl", hash = "sha256:6a3800235535a2532ce392ed0d8e6f698ee010e73805bdeac2f249da8246bab6", size = 113928, upload-time = "2025-08-05T17:14:09.376Z" }, + { url = "https://files.pythonhosted.org/packages/45/79/6dea0c098879d99f41ba919da1ea46e614fb4bf9c4d591450061aeec6fcb/tree_sitter-0.25.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9362a202144075b54f7c9f07e0b0e44a61eed7ee19e140c506b9e64c1d21ed58", size = 146928, upload-time = "2025-08-05T17:14:10.522Z" }, + { url = "https://files.pythonhosted.org/packages/15/30/8002f4e76c7834a6101895ff7524ea29ab4f1f1da1270260ef52e2319372/tree_sitter-0.25.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:593f22529f34dd04de02f56ea6d7c2c8ec99dfab25b58be893247c1090dedd60", size = 140802, upload-time = "2025-08-05T17:14:11.38Z" }, + { url = "https://files.pythonhosted.org/packages/38/ec/d297ad9d4a4b26f551a5ca49afe48fdbcb20f058c2eff8d8463ad6c0eed1/tree_sitter-0.25.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb6849f76e1cbfa223303fa680da533d452e378d5fe372598e4752838ca7929", size = 606762, upload-time = "2025-08-05T17:14:12.264Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1c/05a623cfb420b10d5f782d4ec064cf00fbfa9c21b8526ca4fd042f80acff/tree_sitter-0.25.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:034d4544bb0f82e449033d76dd083b131c3f9ecb5e37d3475f80ae55e8f382bd", size = 634632, upload-time = "2025-08-05T17:14:13.21Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/f05fd5a2331c16d428efb8eef32dfb80dc6565438146e34e9a235ecd7925/tree_sitter-0.25.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:46a9b721560070f2f980105266e28a17d3149485582cdba14d66dca14692e932", size = 630756, upload-time = "2025-08-05T17:14:14.673Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/79f3c5d53d1721b95ab6cda0368192a4f1d367e3a5ff7ac21d77e9841782/tree_sitter-0.25.1-cp312-cp312-win_amd64.whl", hash = "sha256:9a5c522b1350a626dc1cbc5dc203133caeaa114d3f65e400445e8b02f18b343b", size = 127157, upload-time = "2025-08-05T17:14:15.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/b7/07c4e3f71af0096db6c2ecd83e7d61584e3891c79cb39b208082312d1d60/tree_sitter-0.25.1-cp312-cp312-win_arm64.whl", hash = "sha256:43e7b8e83f9fc29ca62e7d2aa8c38e3fa806ff3fc65e0d501d18588dc1509888", size = 113910, upload-time = "2025-08-05T17:14:16.385Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d3/bfb08aab9c7daed2715f303cc017329e3512bb77678cc28829681decadd2/tree_sitter-0.25.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae1eebc175e6a50b38b0e0385cdc26e92ac0bff9b32ee1c0619bbbf6829d57ea", size = 146920, upload-time = "2025-08-05T17:14:17.483Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/7f897c50489c38665255579646fca8191e1b9e5a29ac9cf11022e42e1e2b/tree_sitter-0.25.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e0ae03c4f132f1bffb2bc40b1bb28742785507da693ab04da8531fe534ada9c", size = 140782, upload-time = "2025-08-05T17:14:18.594Z" }, + { url = "https://files.pythonhosted.org/packages/16/e6/85012113899296b8e0789ae94f562d3971d7d3df989e8bec6128749394e1/tree_sitter-0.25.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acf571758be0a71046a61a0936cb815f15b13e0ae7ec6d08398e4aa1560b371d", size = 607590, upload-time = "2025-08-05T17:14:19.782Z" }, + { url = "https://files.pythonhosted.org/packages/49/93/605b08dc4cf76d08cfacebc30a88467c6526ea5c94592c25240518e38b71/tree_sitter-0.25.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:632910847e3f8ae35841f92cba88a9a1b8bc56ecc1514a5affebf7951fa0fc0a", size = 635553, upload-time = "2025-08-05T17:14:21.107Z" }, + { url = "https://files.pythonhosted.org/packages/ce/27/123667f756bb32168507c940db9040104c606fbb0214397d3c20cf985073/tree_sitter-0.25.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a99ecef7771afb118b2a8435c8ba67ea7a085c60d5d33dc0a4794ed882e5f7df", size = 630844, upload-time = "2025-08-05T17:14:22.078Z" }, + { url = "https://files.pythonhosted.org/packages/2f/53/180b0ed74153a3c9a23967f54774d5930c2e0b67671ae4ca0d4d35ba18ac/tree_sitter-0.25.1-cp313-cp313-win_amd64.whl", hash = "sha256:c1d6393454d1f9d4195c74e40a487640cd4390cd4aee90837485f932a1a0f40c", size = 127159, upload-time = "2025-08-05T17:14:23.061Z" }, + { url = "https://files.pythonhosted.org/packages/32/fb/b8b7b5122ac4a80cd689a5023f2416910e10f9534ace1cdf0020a315d40d/tree_sitter-0.25.1-cp313-cp313-win_arm64.whl", hash = "sha256:c1d2dbf7d12426b71ff49739f599c355f4de338a5c0ab994de2a1d290f6e0b20", size = 113920, upload-time = "2025-08-05T17:14:23.879Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/cb851da552baf4215baf96443e5e9e39095083a95bc05c4444e640fe0fe8/tree_sitter-0.25.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:32cee52264d9ecf98885fcac0185ac63e16251b31dd8b4a3b8d8071173405f8f", size = 146775, upload-time = "2025-08-05T17:14:25.064Z" }, + { url = "https://files.pythonhosted.org/packages/f3/59/002c89df1e8f1664b82023e5d0c06de97fff5c2a2e33dce1a241c8909758/tree_sitter-0.25.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae024d8ccfef51e61c44a81af7a48670601430701c24f450bea10f4b4effd8d1", size = 140787, upload-time = "2025-08-05T17:14:25.914Z" }, + { url = "https://files.pythonhosted.org/packages/39/48/c9e6deb88f3c7f16963ef205e5b8e3ea7f5effd048b4515d09738c7b032b/tree_sitter-0.25.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d025c56c393cea660df9ef33ca60329952a1f8ee6212d21b2b390dfec08a3874", size = 609173, upload-time = "2025-08-05T17:14:26.817Z" }, + { url = "https://files.pythonhosted.org/packages/53/a8/b782576d7ea081a87285d974005155da03b6d0c66283fe1e3a5e0dd4bd98/tree_sitter-0.25.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:044aa23ea14f337809821bea7467f33f4c6d351739dca76ba0cbe4d0154d8662", size = 635994, upload-time = "2025-08-05T17:14:28.343Z" }, + { url = "https://files.pythonhosted.org/packages/70/0a/c5b6c9cdb7bd4bf0c3d2bd494fcf356acc53f8e63007dc2a836d95bbe964/tree_sitter-0.25.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1863d96704eb002df4ad3b738294ae8bd5dcf8cefb715da18bff6cb2d33d978e", size = 630944, upload-time = "2025-08-05T17:14:31.123Z" }, + { url = "https://files.pythonhosted.org/packages/12/2a/d0b097157c2d487f5e6293dae2c106ec9ede792a6bb780249e81432e754d/tree_sitter-0.25.1-cp314-cp314-win_amd64.whl", hash = "sha256:a40a481e28e1afdbc455932d61e49ffd4163aafa83f4a3deb717524a7786197e", size = 130831, upload-time = "2025-08-05T17:14:32.458Z" }, + { url = "https://files.pythonhosted.org/packages/ce/33/3591e7b22dd49f46ae4fdee1db316ecefd0486cae880c5b497a55f0ccb24/tree_sitter-0.25.1-cp314-cp314-win_arm64.whl", hash = "sha256:f7b68f584336b39b2deab9896b629dddc3c784170733d3409f01fe825e9c04eb", size = 117376, upload-time = "2025-08-05T17:14:33.283Z" }, +] + +[[package]] +name = "tree-sitter-c-sharp" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/85/a61c782afbb706a47d990eaee6977e7c2bd013771c5bf5c81c617684f286/tree_sitter_c_sharp-0.23.1.tar.gz", hash = "sha256:322e2cfd3a547a840375276b2aea3335fa6458aeac082f6c60fec3f745c967eb", size = 1317728, upload-time = "2024-11-11T05:25:32.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/04/f6c2df4c53a588ccd88d50851155945cff8cd887bd70c175e00aaade7edf/tree_sitter_c_sharp-0.23.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2b612a6e5bd17bb7fa2aab4bb6fc1fba45c94f09cb034ab332e45603b86e32fd", size = 372235, upload-time = "2024-11-11T05:25:19.424Z" }, + { url = "https://files.pythonhosted.org/packages/99/10/1aa9486f1e28fc22810fa92cbdc54e1051e7f5536a5e5b5e9695f609b31e/tree_sitter_c_sharp-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a8b98f62bc53efcd4d971151950c9b9cd5cbe3bacdb0cd69fdccac63350d83e", size = 419046, upload-time = "2024-11-11T05:25:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/0f/21/13df29f8fcb9ba9f209b7b413a4764b673dfd58989a0dd67e9c7e19e9c2e/tree_sitter_c_sharp-0.23.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:986e93d845a438ec3c4416401aa98e6a6f6631d644bbbc2e43fcb915c51d255d", size = 415999, upload-time = "2024-11-11T05:25:22.359Z" }, + { url = "https://files.pythonhosted.org/packages/ca/72/fc6846795bcdae2f8aa94cc8b1d1af33d634e08be63e294ff0d6794b1efc/tree_sitter_c_sharp-0.23.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8024e466b2f5611c6dc90321f232d8584893c7fb88b75e4a831992f877616d2", size = 402830, upload-time = "2024-11-11T05:25:24.198Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3a/b6028c5890ce6653807d5fa88c72232c027c6ceb480dbeb3b186d60e5971/tree_sitter_c_sharp-0.23.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7f9bf876866835492281d336b9e1f9626ab668737f74e914c31d285261507da7", size = 397880, upload-time = "2024-11-11T05:25:25.937Z" }, + { url = "https://files.pythonhosted.org/packages/47/d2/4facaa34b40f8104d8751746d0e1cd2ddf0beb9f1404b736b97f372bd1f3/tree_sitter_c_sharp-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:ae9a9e859e8f44e2b07578d44f9a220d3fa25b688966708af6aa55d42abeebb3", size = 377562, upload-time = "2024-11-11T05:25:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/d8/88/3cf6bd9959d94d1fec1e6a9c530c5f08ff4115a474f62aedb5fedb0f7241/tree_sitter_c_sharp-0.23.1-cp39-abi3-win_arm64.whl", hash = "sha256:c81548347a93347be4f48cb63ec7d60ef4b0efa91313330e69641e49aa5a08c5", size = 375157, upload-time = "2024-11-11T05:25:30.839Z" }, +] + +[[package]] +name = "tree-sitter-java" +version = "0.23.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/dc/eb9c8f96304e5d8ae1663126d89967a622a80937ad2909903569ccb7ec8f/tree_sitter_java-0.23.5.tar.gz", hash = "sha256:f5cd57b8f1270a7f0438878750d02ccc79421d45cca65ff284f1527e9ef02e38", size = 138121, upload-time = "2024-12-21T18:24:26.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/21/b3399780b440e1567a11d384d0ebb1aea9b642d0d98becf30fa55c0e3a3b/tree_sitter_java-0.23.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:355ce0308672d6f7013ec913dee4a0613666f4cda9044a7824240d17f38209df", size = 58926, upload-time = "2024-12-21T18:24:12.53Z" }, + { url = "https://files.pythonhosted.org/packages/57/ef/6406b444e2a93bc72a04e802f4107e9ecf04b8de4a5528830726d210599c/tree_sitter_java-0.23.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:24acd59c4720dedad80d548fe4237e43ef2b7a4e94c8549b0ca6e4c4d7bf6e69", size = 62288, upload-time = "2024-12-21T18:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6c/74b1c150d4f69c291ab0b78d5dd1b59712559bbe7e7daf6d8466d483463f/tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9401e7271f0b333df39fc8a8336a0caf1b891d9a2b89ddee99fae66b794fc5b7", size = 85533, upload-time = "2024-12-21T18:24:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/29/09/e0d08f5c212062fd046db35c1015a2621c2631bc8b4aae5740d7adb276ad/tree_sitter_java-0.23.5-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:370b204b9500b847f6d0c5ad584045831cee69e9a3e4d878535d39e4a7e4c4f1", size = 84033, upload-time = "2024-12-21T18:24:18.758Z" }, + { url = "https://files.pythonhosted.org/packages/43/56/7d06b23ddd09bde816a131aa504ee11a1bbe87c6b62ab9b2ed23849a3382/tree_sitter_java-0.23.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aae84449e330363b55b14a2af0585e4e0dae75eb64ea509b7e5b0e1de536846a", size = 82564, upload-time = "2024-12-21T18:24:20.493Z" }, + { url = "https://files.pythonhosted.org/packages/da/d6/0528c7e1e88a18221dbd8ccee3825bf274b1fa300f745fd74eb343878043/tree_sitter_java-0.23.5-cp39-abi3-win_amd64.whl", hash = "sha256:1ee45e790f8d31d416bc84a09dac2e2c6bc343e89b8a2e1d550513498eedfde7", size = 60650, upload-time = "2024-12-21T18:24:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/72/57/5bab54d23179350356515526fff3cc0f3ac23bfbc1a1d518a15978d4880e/tree_sitter_java-0.23.5-cp39-abi3-win_arm64.whl", hash = "sha256:402efe136104c5603b429dc26c7e75ae14faaca54cfd319ecc41c8f2534750f4", size = 59059, upload-time = "2024-12-21T18:24:24.934Z" }, +] + +[[package]] +name = "tree-sitter-python" +version = "0.23.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/30/6766433b31be476fda6569a3a374c2220e45ffee0bff75460038a57bf23b/tree_sitter_python-0.23.6.tar.gz", hash = "sha256:354bfa0a2f9217431764a631516f85173e9711af2c13dbd796a8815acfe505d9", size = 155868, upload-time = "2024-12-22T23:09:55.918Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/67/577a02acae5f776007c924ca86ef14c19c12e71de0aa9d2a036f3c248e7b/tree_sitter_python-0.23.6-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:28fbec8f74eeb2b30292d97715e60fac9ccf8a8091ce19b9d93e9b580ed280fb", size = 74361, upload-time = "2024-12-22T23:09:42.37Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a6/194b3625a7245c532ad418130d63077ce6cd241152524152f533e4d6edb0/tree_sitter_python-0.23.6-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:680b710051b144fedf61c95197db0094f2245e82551bf7f0c501356333571f7a", size = 76436, upload-time = "2024-12-22T23:09:43.566Z" }, + { url = "https://files.pythonhosted.org/packages/d0/62/1da112689d6d282920e62c40e67ab39ea56463b0e7167bfc5e81818a770e/tree_sitter_python-0.23.6-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a9dcef55507b6567207e8ee0a6b053d0688019b47ff7f26edc1764b7f4dc0a4", size = 112060, upload-time = "2024-12-22T23:09:44.721Z" }, + { url = "https://files.pythonhosted.org/packages/5d/62/c9358584c96e38318d69b6704653684fd8467601f7b74e88aa44f4e6903f/tree_sitter_python-0.23.6-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29dacdc0cd2f64e55e61d96c6906533ebb2791972bec988450c46cce60092f5d", size = 112338, upload-time = "2024-12-22T23:09:48.323Z" }, + { url = "https://files.pythonhosted.org/packages/1a/58/c5e61add45e34fb8ecbf057c500bae9d96ed7c9ca36edb7985da8ae45526/tree_sitter_python-0.23.6-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7e048733c36f564b379831689006801feb267d8194f9e793fbb395ef1723335d", size = 109382, upload-time = "2024-12-22T23:09:49.49Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f3/9b30893cae9b3811fe652dc6f90aaadfda12ae0b2757f5722fc7266f423c/tree_sitter_python-0.23.6-cp39-abi3-win_amd64.whl", hash = "sha256:a24027248399fb41594b696f929f9956828ae7cc85596d9f775e6c239cd0c2be", size = 75904, upload-time = "2024-12-22T23:09:51.597Z" }, + { url = "https://files.pythonhosted.org/packages/87/cb/ce35a65f83a47b510d8a2f1eddf3bdbb0d57aabc87351c8788caf3309f76/tree_sitter_python-0.23.6-cp39-abi3-win_arm64.whl", hash = "sha256:71334371bd73d5fe080aed39fbff49ed8efb9506edebe16795b0c7567ed6a272", size = 73649, upload-time = "2024-12-22T23:09:53.71Z" }, +] + +[[package]] +name = "tree-sitter-typescript" +version = "0.23.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/fc/bb52958f7e399250aee093751e9373a6311cadbe76b6e0d109b853757f35/tree_sitter_typescript-0.23.2.tar.gz", hash = "sha256:7b167b5827c882261cb7a50dfa0fb567975f9b315e87ed87ad0a0a3aedb3834d", size = 773053, upload-time = "2024-11-11T02:36:11.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/95/4c00680866280e008e81dd621fd4d3f54aa3dad1b76b857a19da1b2cc426/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3cd752d70d8e5371fdac6a9a4df9d8924b63b6998d268586f7d374c9fba2a478", size = 286677, upload-time = "2024-11-11T02:35:58.839Z" }, + { url = "https://files.pythonhosted.org/packages/8f/2f/1f36fda564518d84593f2740d5905ac127d590baf5c5753cef2a88a89c15/tree_sitter_typescript-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:c7cc1b0ff5d91bac863b0e38b1578d5505e718156c9db577c8baea2557f66de8", size = 302008, upload-time = "2024-11-11T02:36:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/96/2d/975c2dad292aa9994f982eb0b69cc6fda0223e4b6c4ea714550477d8ec3a/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b1eed5b0b3a8134e86126b00b743d667ec27c63fc9de1b7bb23168803879e31", size = 351987, upload-time = "2024-11-11T02:36:02.669Z" }, + { url = "https://files.pythonhosted.org/packages/49/d1/a71c36da6e2b8a4ed5e2970819b86ef13ba77ac40d9e333cb17df6a2c5db/tree_sitter_typescript-0.23.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e96d36b85bcacdeb8ff5c2618d75593ef12ebaf1b4eace3477e2bdb2abb1752c", size = 344960, upload-time = "2024-11-11T02:36:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/7f/cb/f57b149d7beed1a85b8266d0c60ebe4c46e79c9ba56bc17b898e17daf88e/tree_sitter_typescript-0.23.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8d4f0f9bcb61ad7b7509d49a1565ff2cc363863644a234e1e0fe10960e55aea0", size = 340245, upload-time = "2024-11-11T02:36:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ab/dd84f0e2337296a5f09749f7b5483215d75c8fa9e33738522e5ed81f7254/tree_sitter_typescript-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:3f730b66396bc3e11811e4465c41ee45d9e9edd6de355a58bbbc49fa770da8f9", size = 278015, upload-time = "2024-11-11T02:36:07.631Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e4/81f9a935789233cf412a0ed5fe04c883841d2c8fb0b7e075958a35c65032/tree_sitter_typescript-0.23.2-cp39-abi3-win_arm64.whl", hash = "sha256:05db58f70b95ef0ea126db5560f3775692f609589ed6f8dd0af84b7f19f1cbb7", size = 274052, upload-time = "2024-11-11T02:36:09.514Z" }, +] + [[package]] name = "triton" version = "3.3.1"