embedpick 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- embedpick/__init__.py +7 -0
- embedpick/__main__.py +3 -0
- embedpick/benchmark.py +97 -0
- embedpick/cli.py +111 -0
- embedpick/data.py +69 -0
- embedpick/metrics.py +54 -0
- embedpick/retrievers.py +150 -0
- embedpick/sample_data/corpus.csv +73 -0
- embedpick/sample_data/queries.csv +27 -0
- embedpick-0.1.0.dist-info/METADATA +239 -0
- embedpick-0.1.0.dist-info/RECORD +15 -0
- embedpick-0.1.0.dist-info/WHEEL +5 -0
- embedpick-0.1.0.dist-info/entry_points.txt +2 -0
- embedpick-0.1.0.dist-info/licenses/LICENSE +21 -0
- embedpick-0.1.0.dist-info/top_level.txt +1 -0
embedpick/__init__.py
ADDED
embedpick/__main__.py
ADDED
embedpick/benchmark.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Benchmark akışını yürütür ve sonuç tablosunu üretir."""
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from .metrics import consensus_failures, recall_at_k, score_all
|
|
6
|
+
from .retrievers import BM25Retriever, EmbeddingRetriever
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_benchmark(
|
|
10
|
+
corpus,
|
|
11
|
+
queries,
|
|
12
|
+
model_names,
|
|
13
|
+
k=5,
|
|
14
|
+
repeats=5,
|
|
15
|
+
include_baseline=True,
|
|
16
|
+
use_presets=True,
|
|
17
|
+
verbose=False,
|
|
18
|
+
):
|
|
19
|
+
retrievers = []
|
|
20
|
+
if include_baseline:
|
|
21
|
+
retrievers.append(BM25Retriever())
|
|
22
|
+
|
|
23
|
+
for name in model_names:
|
|
24
|
+
if use_presets:
|
|
25
|
+
retrievers.append(EmbeddingRetriever(name))
|
|
26
|
+
else:
|
|
27
|
+
# Önekler kapalı: modelin belgelenmiş kullanım kuralı yok sayılıyor.
|
|
28
|
+
# Yanlış yapılandırmanın maliyetini ölçmek için.
|
|
29
|
+
retrievers.append(EmbeddingRetriever(name, query_prefix="", doc_prefix=""))
|
|
30
|
+
|
|
31
|
+
satirlar = []
|
|
32
|
+
sorgu_bazli = {}
|
|
33
|
+
|
|
34
|
+
for retriever in retrievers:
|
|
35
|
+
print(f"\n-> {retriever.label}")
|
|
36
|
+
maliyet = retriever.index(corpus, repeats)
|
|
37
|
+
bulunanlar = retriever.search(queries["query"].tolist(), k)
|
|
38
|
+
|
|
39
|
+
recall, mrr = score_all(bulunanlar, queries, k, verbose=verbose)
|
|
40
|
+
|
|
41
|
+
sorgu_bazli[retriever.label] = {
|
|
42
|
+
q: recall_at_k(bulunan, dogru, k)
|
|
43
|
+
for q, bulunan, dogru in zip(
|
|
44
|
+
queries["query"], bulunanlar, queries["relevant_ids"]
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
satirlar.append(
|
|
49
|
+
{
|
|
50
|
+
"model": retriever.label,
|
|
51
|
+
f"recall@{k}": round(recall, 3),
|
|
52
|
+
f"mrr@{k}": round(mrr, 3),
|
|
53
|
+
"docs_per_sec": round(maliyet["docs_per_sec"], 1),
|
|
54
|
+
"dim": maliyet["dim"],
|
|
55
|
+
"index_mb": round(maliyet["index_mb"], 2),
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
_add_relative_speed(satirlar)
|
|
60
|
+
return pd.DataFrame(satirlar), sorgu_bazli
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _add_relative_speed(satirlar):
|
|
64
|
+
"""Mutlak hız makineye ve o anki işlemci durumuna göre kat kat oynar.
|
|
65
|
+
Aynı çalıştırma içindeki oranlar ise sabit kalır."""
|
|
66
|
+
sinirsel = [r for r in satirlar if r["dim"] > 0]
|
|
67
|
+
if not sinirsel:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
en_hizli = max(r["docs_per_sec"] for r in sinirsel)
|
|
71
|
+
for r in satirlar:
|
|
72
|
+
r["rel_speed"] = f"{r['docs_per_sec'] / en_hizli:.2f}x" if r["dim"] > 0 else "-"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def report(df, sorgu_bazli, k):
|
|
76
|
+
print()
|
|
77
|
+
print(df.to_string(index=False))
|
|
78
|
+
|
|
79
|
+
print(
|
|
80
|
+
"\nNot: docs_per_sec makineye ve o anki işlemci durumuna göre değişir."
|
|
81
|
+
"\n Makineler arası kıyas için rel_speed sütununu kullanın"
|
|
82
|
+
"\n (en hızlı sinirsel model = 1.00x)."
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Tek yöntem varken "hiçbiri bulamadı" demek anlamsız; uyarının değeri
|
|
86
|
+
# birden fazla yöntemin aynı sorguda batmasından geliyor.
|
|
87
|
+
ortak = consensus_failures(sorgu_bazli) if len(sorgu_bazli) > 1 else []
|
|
88
|
+
if ortak:
|
|
89
|
+
print(
|
|
90
|
+
f"\nHiçbir yöntemin ilk {k} sonuçta bulamadığı sorgular:"
|
|
91
|
+
)
|
|
92
|
+
for q in ortak:
|
|
93
|
+
print(f" - {q}")
|
|
94
|
+
print(
|
|
95
|
+
" Bütün yöntemler aynı sorguda batıyorsa sorun genelde modelde değil,"
|
|
96
|
+
"\n etiketlerdedir. Bu sorguların relevant_ids değerlerini gözden geçirin."
|
|
97
|
+
)
|
embedpick/cli.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""embedpick komut satırı arayüzü."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from importlib import resources
|
|
6
|
+
|
|
7
|
+
from .benchmark import report, run_benchmark
|
|
8
|
+
from .data import load_corpus, load_queries, validate
|
|
9
|
+
from .retrievers import DEFAULT_MODELS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def sample_path(name):
|
|
13
|
+
"""Pakete gömülü örnek veri dosyasının yolu.
|
|
14
|
+
|
|
15
|
+
Paket pip ile kurulduğunda çalışma dizininde data/ klasörü olmaz,
|
|
16
|
+
o yüzden varsayılan veri kurulum dizininden okunuyor.
|
|
17
|
+
"""
|
|
18
|
+
return str(resources.files("embedpick") / "sample_data" / name)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_parser():
|
|
22
|
+
p = argparse.ArgumentParser(
|
|
23
|
+
prog="embedpick",
|
|
24
|
+
description=(
|
|
25
|
+
"Kendi verinizde embedding modellerini kıyaslayın. "
|
|
26
|
+
"Kalite, hız ve bellek maliyetini tek tabloda gösterir."
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
p.add_argument(
|
|
30
|
+
"--corpus",
|
|
31
|
+
help="Aranacak dokümanlar (sütunlar: id,text). "
|
|
32
|
+
"Verilmezse pakete gömülü örnek veri kullanılır.",
|
|
33
|
+
)
|
|
34
|
+
p.add_argument(
|
|
35
|
+
"--queries",
|
|
36
|
+
help="Sorgular ve doğru cevaplar (sütunlar: query,relevant_ids). "
|
|
37
|
+
"Verilmezse pakete gömülü örnek veri kullanılır.",
|
|
38
|
+
)
|
|
39
|
+
p.add_argument(
|
|
40
|
+
"--models",
|
|
41
|
+
nargs="+",
|
|
42
|
+
default=DEFAULT_MODELS,
|
|
43
|
+
help="Hugging Face model adları, boşlukla ayrılmış",
|
|
44
|
+
)
|
|
45
|
+
p.add_argument("-k", type=int, default=5, help="İlk kaç sonuç değerlendirilsin")
|
|
46
|
+
p.add_argument(
|
|
47
|
+
"--repeats", type=int, default=5, help="Hız ölçümü kaç kez tekrarlansın"
|
|
48
|
+
)
|
|
49
|
+
p.add_argument(
|
|
50
|
+
"--no-baseline",
|
|
51
|
+
action="store_true",
|
|
52
|
+
help="BM25 kelime bazlı baseline'ı atla (önerilmez)",
|
|
53
|
+
)
|
|
54
|
+
p.add_argument(
|
|
55
|
+
"--no-presets",
|
|
56
|
+
action="store_true",
|
|
57
|
+
help=(
|
|
58
|
+
"Bilinen model öneklerini uygulama. e5 gibi önek bekleyen modeller "
|
|
59
|
+
"düşük skor alır; yanlış yapılandırmanın maliyetini ölçmek için."
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
p.add_argument(
|
|
63
|
+
"--verbose",
|
|
64
|
+
action="store_true",
|
|
65
|
+
help="Her sorgunun sonucunu tek tek yazdır",
|
|
66
|
+
)
|
|
67
|
+
p.add_argument("--out", help="Sonuç tablosunu CSV olarak kaydet")
|
|
68
|
+
return p
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def main(argv=None):
|
|
72
|
+
args = build_parser().parse_args(argv)
|
|
73
|
+
|
|
74
|
+
corpus_path = args.corpus or sample_path("corpus.csv")
|
|
75
|
+
queries_path = args.queries or sample_path("queries.csv")
|
|
76
|
+
|
|
77
|
+
if args.corpus is None and args.queries is None:
|
|
78
|
+
print("Kendi veriniz verilmedi, pakete gömülü örnek veri kullanılıyor.")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
corpus = load_corpus(corpus_path)
|
|
82
|
+
queries = load_queries(queries_path)
|
|
83
|
+
validate(corpus, queries)
|
|
84
|
+
except (FileNotFoundError, ValueError) as exc:
|
|
85
|
+
print(f"Hata: {exc}", file=sys.stderr)
|
|
86
|
+
return 1
|
|
87
|
+
|
|
88
|
+
print(f"{len(corpus)} doküman, {len(queries)} sorgu, k={args.k}")
|
|
89
|
+
|
|
90
|
+
df, sorgu_bazli = run_benchmark(
|
|
91
|
+
corpus,
|
|
92
|
+
queries,
|
|
93
|
+
model_names=args.models,
|
|
94
|
+
k=args.k,
|
|
95
|
+
repeats=args.repeats,
|
|
96
|
+
include_baseline=not args.no_baseline,
|
|
97
|
+
use_presets=not args.no_presets,
|
|
98
|
+
verbose=args.verbose,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
report(df, sorgu_bazli, args.k)
|
|
102
|
+
|
|
103
|
+
if args.out:
|
|
104
|
+
df.to_csv(args.out, index=False, encoding="utf-8")
|
|
105
|
+
print(f"\nTablo kaydedildi: {args.out}")
|
|
106
|
+
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
raise SystemExit(main())
|
embedpick/data.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Korpus ve sorgu dosyalarını okur, formatlarını doğrular."""
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load_corpus(path):
|
|
7
|
+
df = pd.read_csv(path, encoding="utf-8")
|
|
8
|
+
|
|
9
|
+
missing = {"id", "text"} - set(df.columns)
|
|
10
|
+
if missing:
|
|
11
|
+
raise ValueError(
|
|
12
|
+
f"{path} dosyasında eksik sütun: {sorted(missing)}. "
|
|
13
|
+
"Beklenen başlık satırı: id,text"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if df["id"].duplicated().any():
|
|
17
|
+
tekrar = df.loc[df["id"].duplicated(), "id"].tolist()
|
|
18
|
+
raise ValueError(f"{path} içinde tekrar eden id değerleri var: {tekrar}")
|
|
19
|
+
|
|
20
|
+
df["text"] = df["text"].astype(str).str.strip()
|
|
21
|
+
return df
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_queries(path):
|
|
25
|
+
df = pd.read_csv(path, encoding="utf-8")
|
|
26
|
+
|
|
27
|
+
missing = {"query", "relevant_ids"} - set(df.columns)
|
|
28
|
+
if missing:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"{path} dosyasında eksik sütun: {sorted(missing)}. "
|
|
31
|
+
"Beklenen başlık satırı: query,relevant_ids"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
df["query"] = df["query"].astype(str).str.strip()
|
|
35
|
+
df["relevant_ids"] = df["relevant_ids"].apply(_parse_ids)
|
|
36
|
+
return df
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _parse_ids(value):
|
|
40
|
+
parts = [p.strip() for p in str(value).split(";") if p.strip()]
|
|
41
|
+
if not parts:
|
|
42
|
+
raise ValueError(f"Boş relevant_ids değeri bulundu: {value!r}")
|
|
43
|
+
try:
|
|
44
|
+
return [int(p) for p in parts]
|
|
45
|
+
except ValueError as exc:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"relevant_ids sayı olmalı, alınan: {value!r}. "
|
|
48
|
+
"Birden fazla id noktalı virgülle ayrılır, örnek: 3;11"
|
|
49
|
+
) from exc
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def validate(corpus, queries):
|
|
53
|
+
"""Sorgu etiketlerinin korpusta gerçekten var olduğunu kontrol eder.
|
|
54
|
+
|
|
55
|
+
Etiket hataları benchmark sonuçlarını sessizce bozar: olmayan bir id'yi
|
|
56
|
+
hiçbir model bulamaz, sen de modeli suçlarsın. Baştan yakalamak daha iyi.
|
|
57
|
+
"""
|
|
58
|
+
bilinen = set(corpus["id"])
|
|
59
|
+
sorunlar = []
|
|
60
|
+
|
|
61
|
+
for q, ids in zip(queries["query"], queries["relevant_ids"]):
|
|
62
|
+
eksik = [i for i in ids if i not in bilinen]
|
|
63
|
+
if eksik:
|
|
64
|
+
sorunlar.append(f' "{q}" -> korpusta olmayan id: {eksik}')
|
|
65
|
+
|
|
66
|
+
if sorunlar:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"Sorgu etiketleri korpusla uyuşmuyor:\n" + "\n".join(sorunlar)
|
|
69
|
+
)
|
embedpick/metrics.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Erişim metrikleri: recall@k ve MRR@k."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def recall_at_k(retrieved_ids, relevant_ids, k):
|
|
7
|
+
"""İlk k sonuçta doğru dokümanların ne kadarı yakalandı."""
|
|
8
|
+
hits = len(set(retrieved_ids[:k]) & set(relevant_ids))
|
|
9
|
+
return hits / len(relevant_ids)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def reciprocal_rank(retrieved_ids, relevant_ids, k):
|
|
13
|
+
"""İlk doğru sonucun sırasının tersi. 1. sıra -> 1.0, 2. sıra -> 0.5."""
|
|
14
|
+
for rank, doc_id in enumerate(retrieved_ids[:k], start=1):
|
|
15
|
+
if doc_id in relevant_ids:
|
|
16
|
+
return 1.0 / rank
|
|
17
|
+
return 0.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def score_all(all_retrieved, queries, k, verbose=False):
|
|
21
|
+
"""Tüm sorguları puanlar, ortalama recall ve MRR döndürür."""
|
|
22
|
+
recalls = []
|
|
23
|
+
rrs = []
|
|
24
|
+
|
|
25
|
+
for q, retrieved_ids, relevant in zip(
|
|
26
|
+
queries["query"], all_retrieved, queries["relevant_ids"]
|
|
27
|
+
):
|
|
28
|
+
r = recall_at_k(retrieved_ids, relevant, k)
|
|
29
|
+
rr = reciprocal_rank(retrieved_ids, relevant, k)
|
|
30
|
+
recalls.append(r)
|
|
31
|
+
rrs.append(rr)
|
|
32
|
+
if verbose:
|
|
33
|
+
print(
|
|
34
|
+
f" [r={r:.2f} rr={rr:.2f}] {q}"
|
|
35
|
+
f" -> {retrieved_ids} | doğru: {relevant}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return float(np.mean(recalls)), float(np.mean(rrs))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def consensus_failures(per_model_scores, threshold=0.0):
|
|
42
|
+
"""Hiçbir modelin bulamadığı sorguları döndürür.
|
|
43
|
+
|
|
44
|
+
Bütün modeller aynı sorguda batıyorsa suçlu genelde model değil, etikettir.
|
|
45
|
+
"""
|
|
46
|
+
if not per_model_scores:
|
|
47
|
+
return []
|
|
48
|
+
|
|
49
|
+
sorgular = list(per_model_scores.values())[0].keys()
|
|
50
|
+
return [
|
|
51
|
+
q
|
|
52
|
+
for q in sorgular
|
|
53
|
+
if all(skorlar[q] <= threshold for skorlar in per_model_scores.values())
|
|
54
|
+
]
|
embedpick/retrievers.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Arama yöntemleri: kelime bazlı BM25 baseline ve embedding tabanlı arama."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
# Bazı modeller metinlerin başına önek bekler. Önek konmazsa model kötü
|
|
9
|
+
# çalışır ve kullanıcı bunu "model kötüymüş" diye yorumlar. Bilinenleri
|
|
10
|
+
# burada tutuyoruz ki varsayılan davranış doğru olsun.
|
|
11
|
+
MODEL_PRESETS = {
|
|
12
|
+
"intfloat/multilingual-e5-small": {"query": "query: ", "doc": "passage: "},
|
|
13
|
+
"intfloat/multilingual-e5-base": {"query": "query: ", "doc": "passage: "},
|
|
14
|
+
"intfloat/multilingual-e5-large": {"query": "query: ", "doc": "passage: "},
|
|
15
|
+
"BAAI/bge-m3": {"query": "", "doc": ""},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
DEFAULT_MODELS = [
|
|
19
|
+
"sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
|
|
20
|
+
"intfloat/multilingual-e5-small",
|
|
21
|
+
"trmteb/turkish-embedding-model",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_prefixes(model_name):
|
|
26
|
+
if model_name in MODEL_PRESETS:
|
|
27
|
+
return MODEL_PRESETS[model_name]
|
|
28
|
+
|
|
29
|
+
# Tanımadığımız bir e5 varyantı gelirse yine de uyaralım.
|
|
30
|
+
if "e5" in model_name.lower():
|
|
31
|
+
print(
|
|
32
|
+
f" uyarı: {model_name} bir e5 varyantı gibi görünüyor. "
|
|
33
|
+
"e5 modelleri 'query: ' / 'passage: ' öneki bekler, "
|
|
34
|
+
"önek olmadan skorlar düşük çıkar."
|
|
35
|
+
)
|
|
36
|
+
return {"query": "", "doc": ""}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def tr_tokenize(text):
|
|
40
|
+
"""Türkçe farkındalıklı basit kelime ayırıcı.
|
|
41
|
+
|
|
42
|
+
Python'un lower() metodu Türkçe bilmez: "I" -> "i" yapar, oysa "ı" olmalı.
|
|
43
|
+
"İ" ise "i" artı ayrı bir birleşen nokta karakterine bölünür.
|
|
44
|
+
"""
|
|
45
|
+
text = text.replace("I", "ı").replace("İ", "i")
|
|
46
|
+
return re.findall(r"\w+", text.lower(), flags=re.UNICODE)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BM25Retriever:
|
|
50
|
+
"""Kelime bazlı baseline. Embedding modelleri bunu geçemiyorsa
|
|
51
|
+
harcanan hesap gücü boşa gidiyor demektir."""
|
|
52
|
+
|
|
53
|
+
label = "BM25 (baseline)"
|
|
54
|
+
dim = 0
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
self._bm25 = None
|
|
58
|
+
self._ids = None
|
|
59
|
+
|
|
60
|
+
def index(self, corpus, repeats):
|
|
61
|
+
from rank_bm25 import BM25Okapi
|
|
62
|
+
|
|
63
|
+
texts = corpus["text"].tolist()
|
|
64
|
+
timings = []
|
|
65
|
+
for _ in range(repeats):
|
|
66
|
+
start = time.perf_counter()
|
|
67
|
+
tokenized = [tr_tokenize(t) for t in texts]
|
|
68
|
+
bm25 = BM25Okapi(tokenized)
|
|
69
|
+
timings.append(time.perf_counter() - start)
|
|
70
|
+
|
|
71
|
+
self._bm25 = bm25
|
|
72
|
+
self._ids = corpus["id"].tolist()
|
|
73
|
+
|
|
74
|
+
index_sec = float(np.median(timings))
|
|
75
|
+
return {
|
|
76
|
+
"docs_per_sec": len(texts) / index_sec,
|
|
77
|
+
"index_mb": 0.0,
|
|
78
|
+
"dim": 0,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
def search(self, queries, k):
|
|
82
|
+
sonuclar = []
|
|
83
|
+
for q in queries:
|
|
84
|
+
scores = self._bm25.get_scores(tr_tokenize(q))
|
|
85
|
+
top = np.argsort(-scores)[:k]
|
|
86
|
+
sonuclar.append([self._ids[i] for i in top])
|
|
87
|
+
return sonuclar
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class EmbeddingRetriever:
|
|
91
|
+
"""sentence-transformers modeli + FAISS iç çarpım indeksi.
|
|
92
|
+
|
|
93
|
+
Vektörler normalize edildiği için iç çarpım = kosinüs benzerliği.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, model_name, query_prefix=None, doc_prefix=None):
|
|
97
|
+
self.model_name = model_name
|
|
98
|
+
self.label = model_name.split("/")[-1]
|
|
99
|
+
|
|
100
|
+
onekler = get_prefixes(model_name)
|
|
101
|
+
self.query_prefix = onekler["query"] if query_prefix is None else query_prefix
|
|
102
|
+
self.doc_prefix = onekler["doc"] if doc_prefix is None else doc_prefix
|
|
103
|
+
|
|
104
|
+
self._model = None
|
|
105
|
+
self._index = None
|
|
106
|
+
self._ids = None
|
|
107
|
+
self.dim = 0
|
|
108
|
+
|
|
109
|
+
def index(self, corpus, repeats):
|
|
110
|
+
from sentence_transformers import SentenceTransformer
|
|
111
|
+
|
|
112
|
+
self._model = SentenceTransformer(self.model_name)
|
|
113
|
+
texts = [self.doc_prefix + t for t in corpus["text"].tolist()]
|
|
114
|
+
|
|
115
|
+
# Isınma: tüm korpusu bir kez geçiyoruz. Kısa bir ısınma turu
|
|
116
|
+
# işlemciyi düşük frekanstan çıkarmaya yetmiyor ve ilk ölçümler
|
|
117
|
+
# yanıltıcı derecede yavaş çıkıyor.
|
|
118
|
+
self._encode(texts)
|
|
119
|
+
|
|
120
|
+
# Medyan alıyoruz; ortalama tek bir tepe değerden bozulur.
|
|
121
|
+
timings = []
|
|
122
|
+
for _ in range(repeats):
|
|
123
|
+
start = time.perf_counter()
|
|
124
|
+
emb = self._encode(texts)
|
|
125
|
+
timings.append(time.perf_counter() - start)
|
|
126
|
+
|
|
127
|
+
import faiss
|
|
128
|
+
|
|
129
|
+
self.dim = int(emb.shape[1])
|
|
130
|
+
self._index = faiss.IndexFlatIP(self.dim)
|
|
131
|
+
self._index.add(np.asarray(emb, dtype="float32"))
|
|
132
|
+
self._ids = corpus["id"].tolist()
|
|
133
|
+
|
|
134
|
+
encode_sec = float(np.median(timings))
|
|
135
|
+
return {
|
|
136
|
+
"docs_per_sec": len(texts) / encode_sec,
|
|
137
|
+
"index_mb": emb.nbytes / (1024 * 1024),
|
|
138
|
+
"dim": self.dim,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def search(self, queries, k):
|
|
142
|
+
texts = [self.query_prefix + q for q in queries]
|
|
143
|
+
emb = self._encode(texts)
|
|
144
|
+
_, positions = self._index.search(np.asarray(emb, dtype="float32"), k)
|
|
145
|
+
return [[self._ids[p] for p in row] for row in positions]
|
|
146
|
+
|
|
147
|
+
def _encode(self, texts):
|
|
148
|
+
return self._model.encode(
|
|
149
|
+
texts, normalize_embeddings=True, show_progress_bar=False
|
|
150
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
id,text
|
|
2
|
+
1,Kargom hala elime ulaşmadı
|
|
3
|
+
2,Sipariş takip numaram sistemde görünmüyor
|
|
4
|
+
3,Paketim yanlış adrese teslim edilmiş
|
|
5
|
+
4,Kurye kapıyı çalmadan teslim edilemedi olarak işaretlemiş
|
|
6
|
+
5,Teslimat tarihi sürekli ileriye atılıyor
|
|
7
|
+
6,Kargo firmasını değiştirebilir miyim
|
|
8
|
+
7,Siparişim iki ayrı paket halinde mi gelecek
|
|
9
|
+
8,Şubeden teslim alma seçeneği var mı
|
|
10
|
+
9,Gönderim ücreti neden bu kadar yüksek
|
|
11
|
+
10,Yurt dışına gönderim yapıyor musunuz
|
|
12
|
+
11,İade sürecini nasıl başlatabilirim
|
|
13
|
+
12,Ürünü kaç gün içinde geri gönderebilirim
|
|
14
|
+
13,Beden değişimi yapmak mümkün mü
|
|
15
|
+
14,İade kargo ücretini kim karşılıyor
|
|
16
|
+
15,Geri gönderdiğim ürün elinize ulaştı mı
|
|
17
|
+
16,Paramı ne zaman geri alacağım
|
|
18
|
+
17,Kullanılmış ürün iade edilebilir mi
|
|
19
|
+
18,Hediye olarak aldığım ürünü değiştirebilir miyim
|
|
20
|
+
19,İade formunu nereden indirebilirim
|
|
21
|
+
20,Farklı renk ile değişim yapmak istiyorum
|
|
22
|
+
21,Kredi kartımdan iki kez çekim yapılmış
|
|
23
|
+
22,Fatura adresimi değiştirmek istiyorum
|
|
24
|
+
23,Ödemem onaylandı ama sipariş görünmüyor
|
|
25
|
+
24,Taksit seçenekleri neden çıkmıyor
|
|
26
|
+
25,Kapıda ödeme yapabilir miyim
|
|
27
|
+
26,Kurumsal fatura kesilmesini istiyorum
|
|
28
|
+
27,Havale ile ödeme yaptım ama onaylanmadı
|
|
29
|
+
28,Kartımdan çekilen tutar sipariş tutarından fazla
|
|
30
|
+
29,E-arşiv faturama nereden ulaşırım
|
|
31
|
+
30,Ödeme sayfasında hata alıyorum
|
|
32
|
+
31,Şifremi unuttum giriş yapamıyorum
|
|
33
|
+
32,Hesabım askıya alınmış neden
|
|
34
|
+
33,Üyeliğimi tamamen silmek istiyorum
|
|
35
|
+
34,E-posta adresimi güncellemek istiyorum
|
|
36
|
+
35,Doğrulama kodu telefonuma gelmiyor
|
|
37
|
+
36,Aynı e-posta ile ikinci hesap açabilir miyim
|
|
38
|
+
37,Hesabıma başkası girmiş olabilir
|
|
39
|
+
38,Telefon numaramı değiştiremiyorum
|
|
40
|
+
39,Bildirim tercihlerimi nereden ayarlarım
|
|
41
|
+
40,Üyelik avantajları nelerdir
|
|
42
|
+
41,Ürün hasarlı geldi kutusu ezilmişti
|
|
43
|
+
42,Ürün açıklamadaki renkten farklı geldi
|
|
44
|
+
43,Gelen üründe eksik parça var
|
|
45
|
+
44,Ürünün orijinal olmadığını düşünüyorum
|
|
46
|
+
45,Ambalaj açılmış görünüyor
|
|
47
|
+
46,Ürün ilk kullanımda bozuldu
|
|
48
|
+
47,Beden tablosu gerçeği yansıtmıyor
|
|
49
|
+
48,Ürün fotoğraftakinden çok daha küçük
|
|
50
|
+
49,Kumaş kalitesi beklediğim gibi değil
|
|
51
|
+
50,Son kullanma tarihi geçmiş ürün gönderilmiş
|
|
52
|
+
51,Kampanya kodu sepette geçerli olmadı
|
|
53
|
+
52,İndirimli aldığım ürün sipariş sonrası zamlandı
|
|
54
|
+
53,Hediye çekimi nasıl kullanırım
|
|
55
|
+
54,Bedava gönderim kampanyası devam ediyor mu
|
|
56
|
+
55,Sepetimdeki ürünün fiyatı değişti
|
|
57
|
+
56,Bu ürün iki al bir öde kampanyasına dahil mi
|
|
58
|
+
57,Puanlarım hesabıma yansımadı
|
|
59
|
+
58,Öğrenci indirimi var mı
|
|
60
|
+
59,Garanti kapsamında tamir talebi oluşturmak istiyorum
|
|
61
|
+
60,Garanti belgesi paketten çıkmadı
|
|
62
|
+
61,En yakın yetkili servis nerede
|
|
63
|
+
62,Garanti süresi ne kadar
|
|
64
|
+
63,Tamire gönderdiğim ürün ne zaman döner
|
|
65
|
+
64,Garanti kapsamı dışında olduğu söylenip ücret istendi
|
|
66
|
+
65,Ürünün yedek parçasını nereden alabilirim
|
|
67
|
+
66,Kullanım kılavuzuna nereden ulaşabilirim
|
|
68
|
+
67,Siparişimi iptal etmek istiyorum
|
|
69
|
+
68,Tükenen ürün tekrar gelecek mi
|
|
70
|
+
69,Teslimat adresimi güncelleyebilir miyim
|
|
71
|
+
70,Siparişime ürün ekleyebilir miyim
|
|
72
|
+
71,Ön sipariş verdiğim ürün ne zaman gönderilir
|
|
73
|
+
72,Müşteri hizmetlerine nasıl ulaşabilirim
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
query,relevant_ids
|
|
2
|
+
kargo gecikmesi,1;5
|
|
3
|
+
paketim nerede,1;2
|
|
4
|
+
yanlış adrese teslimat,3
|
|
5
|
+
paramı geri istiyorum,16;11
|
|
6
|
+
ürünü geri göndermek istiyorum,11;12
|
|
7
|
+
kıyafet küçük geldi,47;48
|
|
8
|
+
çift çekim yapıldı,21;28
|
|
9
|
+
fatura bilgilerimi güncelleme,22;29
|
|
10
|
+
hesabıma giremiyorum,31;35
|
|
11
|
+
güvenlik ihlali şüphesi,37
|
|
12
|
+
bozuk ürün geldi,46;41
|
|
13
|
+
kutu ezilmiş halde geldi,41;45
|
|
14
|
+
renk farklı,42;20
|
|
15
|
+
indirim kodu çalışmıyor,51;53
|
|
16
|
+
ücretsiz kargo,54;9
|
|
17
|
+
tamir ve servis,59;61;63
|
|
18
|
+
siparişi durdurmak istiyorum,67
|
|
19
|
+
adres değiştirme,69;22
|
|
20
|
+
ürün ne zaman stoklara girecek,68
|
|
21
|
+
size nasıl ulaşabilirim,72
|
|
22
|
+
taksitli alışveriş,24
|
|
23
|
+
ödeme yaparken sorun,30;27
|
|
24
|
+
üyelikten çıkmak istiyorum,33
|
|
25
|
+
sahte ürün şüphesi,44
|
|
26
|
+
yurt dışı gönderim,10
|
|
27
|
+
garanti süresi,62;60
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: embedpick
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Benchmark embedding models on your own data, with a keyword-search baseline
|
|
5
|
+
Author: Mehmet Alper Tuğtekin
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Mehmetalpertugtekin/embedpick
|
|
8
|
+
Project-URL: Repository, https://github.com/Mehmetalpertugtekin/embedpick
|
|
9
|
+
Project-URL: Issues, https://github.com/Mehmetalpertugtekin/embedpick/issues
|
|
10
|
+
Keywords: embeddings,benchmark,semantic-search,information-retrieval,sentence-transformers,turkish-nlp
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Text Processing :: Indexing
|
|
22
|
+
Classifier: Natural Language :: Turkish
|
|
23
|
+
Classifier: Natural Language :: English
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: pandas>=1.3
|
|
28
|
+
Requires-Dist: numpy>=1.21
|
|
29
|
+
Requires-Dist: rank-bm25>=0.2
|
|
30
|
+
Requires-Dist: sentence-transformers>=2.2
|
|
31
|
+
Requires-Dist: faiss-cpu>=1.7
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
34
|
+
Provides-Extra: ui
|
|
35
|
+
Requires-Dist: gradio>=4.0; extra == "ui"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# embedpick
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
Benchmark embedding models on **your own data**, not on someone else's leaderboard.
|
|
42
|
+
|
|
43
|
+
[Türkçe README](README.tr.md)
|
|
44
|
+
|
|
45
|
+
## Why
|
|
46
|
+
|
|
47
|
+
Picking an embedding model usually goes like this: open Hugging Face, sort by
|
|
48
|
+
downloads, take the top result. The scores on the model card were measured on
|
|
49
|
+
general-purpose English benchmarks. Your data is not that.
|
|
50
|
+
|
|
51
|
+
embedpick answers a narrower and more useful question: *given my documents and
|
|
52
|
+
my queries, which model actually finds the right thing, and what does it cost
|
|
53
|
+
me in time and memory?*
|
|
54
|
+
|
|
55
|
+
It reports quality, speed and memory side by side, and — unlike most benchmark
|
|
56
|
+
tooling — it puts a plain keyword-search baseline in the same table.
|
|
57
|
+
|
|
58
|
+
## The baseline is the point
|
|
59
|
+
|
|
60
|
+
Every run includes BM25, a classic keyword-matching algorithm with no neural
|
|
61
|
+
network involved. If an embedding model cannot beat it, running that model is
|
|
62
|
+
wasted compute.
|
|
63
|
+
|
|
64
|
+
Results on the included Turkish customer-support dataset (72 documents,
|
|
65
|
+
26 queries, k=5):
|
|
66
|
+
|
|
67
|
+
| Model | recall@5 | MRR@5 | docs/sec | dim | rel. speed |
|
|
68
|
+
|---|---|---|---|---|---|
|
|
69
|
+
| BM25 (baseline) | 0.564 | 0.564 | ~226,000 | — | — |
|
|
70
|
+
| paraphrase-multilingual-MiniLM-L12-v2 | 0.615 | 0.596 | 340 | 384 | 1.00x |
|
|
71
|
+
| multilingual-e5-small | 0.692 | 0.708 | 304 | 384 | 0.89x |
|
|
72
|
+
| trmteb/turkish-embedding-model | **0.846** | **0.865** | 103 | 768 | 0.30x |
|
|
73
|
+
|
|
74
|
+
Read that first two rows carefully. MiniLM buys a 5-point recall gain over
|
|
75
|
+
keyword matching, and pays roughly 600x the indexing time for it. On this
|
|
76
|
+
dataset that trade is hard to justify.
|
|
77
|
+
|
|
78
|
+
The Turkish-specific model is a different story: +28 points over the baseline
|
|
79
|
+
is a real jump, and worth the 3x slowdown against the other neural models.
|
|
80
|
+
|
|
81
|
+
So the lesson is not "embeddings are overrated." It is: **a badly chosen
|
|
82
|
+
embedding model is not better than keyword search, and nobody checks.**
|
|
83
|
+
|
|
84
|
+
## Findings from the sample dataset
|
|
85
|
+
|
|
86
|
+
**Documented configuration is not always the right configuration.** The e5
|
|
87
|
+
model card specifies `query: ` and `passage: ` prefixes on input text. Applying
|
|
88
|
+
them on this dataset *lowered* recall@5 from 0.750 to 0.692 and MRR from 0.792
|
|
89
|
+
to 0.708 — same model, same data, same hardware.
|
|
90
|
+
|
|
91
|
+
A plausible reason: these documents are short, five to eight words each, so an
|
|
92
|
+
English prefix takes up a large share of every one of them. Whatever the cause,
|
|
93
|
+
the recommended setting cost about six points here, and nothing short of
|
|
94
|
+
measuring on your own data would have shown it.
|
|
95
|
+
|
|
96
|
+
embedpick applies known prefixes by default, following the model authors'
|
|
97
|
+
instructions, and `--no-presets` turns them off so you can check. Across 26
|
|
98
|
+
queries the gap is worth roughly one and a half queries, so read the direction
|
|
99
|
+
as suggestive rather than settled.
|
|
100
|
+
|
|
101
|
+
**BM25 and embeddings fail on different queries.** On `ödeme yaparken sorun`
|
|
102
|
+
("problem while paying") BM25 scored 1.00 and MiniLM scored 0.00. On
|
|
103
|
+
`sahte ürün şüphesi` ("suspected counterfeit") it was the reverse. They are
|
|
104
|
+
complementary, which is the empirical case for hybrid retrieval.
|
|
105
|
+
|
|
106
|
+
**Small corpora hide everything.** On a 16-document pilot all three models
|
|
107
|
+
scored 1.0 and looked identical. At 72 documents a 23-point spread appeared.
|
|
108
|
+
A benchmark that cannot separate models tells you nothing about them.
|
|
109
|
+
|
|
110
|
+
**Absolute timings are unreliable; ratios are not.** Across repeated runs on
|
|
111
|
+
the same machine, `docs/sec` varied by up to 4x while the ratio between models
|
|
112
|
+
stayed within about 20%. Report `rel_speed` when comparing across machines.
|
|
113
|
+
|
|
114
|
+
## Install
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
git clone https://github.com/Mehmetalpertugtekin/embedpick.git
|
|
118
|
+
cd embedpick
|
|
119
|
+
python -m venv .venv
|
|
120
|
+
.venv\Scripts\activate # Windows
|
|
121
|
+
source .venv/bin/activate # macOS / Linux
|
|
122
|
+
pip install -r requirements.txt
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Python 3.9+.
|
|
126
|
+
|
|
127
|
+
## Usage
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# run with the bundled sample dataset
|
|
131
|
+
python -m embedpick
|
|
132
|
+
|
|
133
|
+
# your own data
|
|
134
|
+
python -m embedpick --corpus my_docs.csv --queries my_queries.csv
|
|
135
|
+
|
|
136
|
+
# pick models, evaluate top 10, show per-query detail
|
|
137
|
+
python -m embedpick --models intfloat/multilingual-e5-base -k 10 --verbose
|
|
138
|
+
|
|
139
|
+
# save the table
|
|
140
|
+
python -m embedpick --out results.csv
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`python -m embedpick --help` lists every flag.
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pip install -r requirements-dev.txt
|
|
149
|
+
python -m pytest
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Tests do not download any models, so they run in seconds. Make sure your
|
|
153
|
+
virtual environment is active first — a missing `rank_bm25` in the test output
|
|
154
|
+
usually means you installed into the system Python by mistake.
|
|
155
|
+
|
|
156
|
+
## Data format
|
|
157
|
+
|
|
158
|
+
**corpus.csv** — the documents to search over:
|
|
159
|
+
|
|
160
|
+
```csv
|
|
161
|
+
id,text
|
|
162
|
+
1,Kargom hala elime ulaşmadı
|
|
163
|
+
2,Sipariş takip numaram sistemde görünmüyor
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**queries.csv** — queries and their correct answers:
|
|
167
|
+
|
|
168
|
+
```csv
|
|
169
|
+
query,relevant_ids
|
|
170
|
+
kargo gecikmesi,1;5
|
|
171
|
+
paketim nerede,1;2
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Separate multiple correct answers with `;`. A query can have any number of
|
|
175
|
+
them. Files must be UTF-8.
|
|
176
|
+
|
|
177
|
+
Twenty to thirty queries is usually enough to see real differences. Include
|
|
178
|
+
queries that share no words with their correct answer — those are the ones
|
|
179
|
+
that test semantic matching rather than lucky keyword overlap.
|
|
180
|
+
|
|
181
|
+
## Metrics
|
|
182
|
+
|
|
183
|
+
**recall@k** — of the correct documents, what fraction appeared in the top k.
|
|
184
|
+
Use this when you show the user a list of results.
|
|
185
|
+
|
|
186
|
+
**MRR@k** — 1.0 if the first correct answer is at rank 1, 0.5 at rank 2, 0.33
|
|
187
|
+
at rank 3, averaged over queries. Use this when you show one answer, or feed
|
|
188
|
+
the top hit to an LLM.
|
|
189
|
+
|
|
190
|
+
These can disagree, and the disagreement is informative. In an earlier run
|
|
191
|
+
e5 had higher recall than MiniLM but lower MRR: it found more, but ranked
|
|
192
|
+
worse. Which model is "better" depends on what you are building.
|
|
193
|
+
|
|
194
|
+
**docs/sec** — indexing throughput. Machine-dependent, see the caveat above.
|
|
195
|
+
|
|
196
|
+
**dim** and **index_mb** — vector width and index size. A 768-dimensional
|
|
197
|
+
model needs twice the memory of a 384-dimensional one at the same corpus size.
|
|
198
|
+
|
|
199
|
+
## Label checking
|
|
200
|
+
|
|
201
|
+
Two things happen automatically:
|
|
202
|
+
|
|
203
|
+
- If `relevant_ids` points at a document id that is not in the corpus, the run
|
|
204
|
+
stops and names the query. Otherwise that query silently scores zero forever
|
|
205
|
+
and you blame the model.
|
|
206
|
+
- If *no* method — including BM25 — finds a query's answer, embedpick flags it.
|
|
207
|
+
When everything fails on the same query, the label is usually wrong. This
|
|
208
|
+
caught a mislabelled query during development.
|
|
209
|
+
|
|
210
|
+
## Sample dataset
|
|
211
|
+
|
|
212
|
+
`embedpick/sample_data/` contains 72 synthetic Turkish customer-support messages across nine
|
|
213
|
+
themes (shipping, returns, payment, account, product quality, promotions,
|
|
214
|
+
warranty, order management, support) and 26 labelled queries. It is written,
|
|
215
|
+
not scraped, so it carries no licensing or privacy constraints.
|
|
216
|
+
|
|
217
|
+
Several queries deliberately share no vocabulary with their answers — for
|
|
218
|
+
example `güvenlik ihlali şüphesi` ("suspected security breach") maps to
|
|
219
|
+
*"Hesabıma başkası girmiş olabilir"* ("someone else may have accessed my
|
|
220
|
+
account"). Keyword search cannot solve these; that is the point.
|
|
221
|
+
|
|
222
|
+
## Caveats
|
|
223
|
+
|
|
224
|
+
- Timing on a 72-document corpus is close to measurement noise, particularly
|
|
225
|
+
for BM25. Treat the throughput column as indicative.
|
|
226
|
+
- BM25 and neural encoders scale differently. The ratio at 72 documents is not
|
|
227
|
+
the ratio at 100,000.
|
|
228
|
+
- Results are from one machine, CPU only, no GPU.
|
|
229
|
+
|
|
230
|
+
## Roadmap
|
|
231
|
+
|
|
232
|
+
- Hybrid retrieval (BM25 + embedding score fusion) as a fourth row
|
|
233
|
+
- Hub detection: flag documents that surface for nearly every query
|
|
234
|
+
- Markdown report export
|
|
235
|
+
- Optional Gradio interface
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
embedpick/__init__.py,sha256=Ybzv5pEthoIrzGby5H69tBPsRknzd1BEzeGiFoBaOWE,258
|
|
2
|
+
embedpick/__main__.py,sha256=k1ocEWawweo1qCJWNFAAvyxz3tcY13dzvCenHszij30,48
|
|
3
|
+
embedpick/benchmark.py,sha256=uqh4T39jcjis86TeMkXCMkumjXx9GFRS2P3B_WGTAw4,3157
|
|
4
|
+
embedpick/cli.py,sha256=RihVIlj2WWrwbqfdiQ_Bqw2ZTKHhWnnxVv6YBXt8RZo,3312
|
|
5
|
+
embedpick/data.py,sha256=1pNyTzmngcADcRf8BGSvNW9wx0GHdQpiRx5MJMcPXCg,2183
|
|
6
|
+
embedpick/metrics.py,sha256=T0S-8fs-lrl3Bef6txDpLQxyT6J2tUS8Rglz7rjfw1I,1676
|
|
7
|
+
embedpick/retrievers.py,sha256=Xe30WabS75qPcCb16Re86D3d3T7o6fxLGyVf8gS5IzY,5004
|
|
8
|
+
embedpick/sample_data/corpus.csv,sha256=Uqzp1qWWFvKPH4xLXFBCqIgrKe0fKKk08MKxy1AqSSI,3236
|
|
9
|
+
embedpick/sample_data/queries.csv,sha256=XdyfiATgpvsMwmGuX2FijQhhqKnkfi_AloqcSyHNJAg,775
|
|
10
|
+
embedpick-0.1.0.dist-info/licenses/LICENSE,sha256=TmjrxRWdTFitR2wsHBSyM7Jp4XH-PGzc7JMTmZj9-aM,1100
|
|
11
|
+
embedpick-0.1.0.dist-info/METADATA,sha256=shJNskBoGeHmIxgcd2KSfVCDfIhQSxJ9e3yyujS-tMg,9261
|
|
12
|
+
embedpick-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
13
|
+
embedpick-0.1.0.dist-info/entry_points.txt,sha256=OuhrM3J7vIydF3ZEHW-MmgCna4RTBQoVsiw0Z5WnZWw,49
|
|
14
|
+
embedpick-0.1.0.dist-info/top_level.txt,sha256=QErZwZoHszm4YRgjfBVg0b-v8hKDQ55A_kIwjYXlhbs,10
|
|
15
|
+
embedpick-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mehmet Alper Tuğtekin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
embedpick
|