Skip to content

Commit b614175

Browse files
authored
Merge pull request #6 from pko89403/refactor/yagni-cleanup
YAGNI cleanup and internal module renames
2 parents 2787719 + 1332a9b commit b614175

91 files changed

Lines changed: 401 additions & 1072 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.ko.md

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ Strategy를 설정한 뒤 `AzureOpenAIReranker`에 전달합니다.
7878
from ranksmith import AzureOpenAIReranker, ListwiseStrategy
7979

8080
strategy = ListwiseStrategy(
81-
algorithm="rankgpt_sliding_window",
8281
window_size=20,
8382
stride=10,
8483
max_document_chars=4000,
@@ -116,7 +115,7 @@ reranker = AzureOpenAIReranker(
116115
api_key="...",
117116
azure_endpoint="https://example.openai.azure.com",
118117
azure_deployment="gpt-4o-mini",
119-
strategy=TourRankStrategy(rounds=2, group_parallelism=1),
118+
strategy=TourRankStrategy(rounds=2),
120119
)
121120
```
122121

@@ -145,7 +144,6 @@ reranker = AzureOpenAIReranker(
145144
target_rank=10,
146145
window_size=20,
147146
max_adaptive_reranker_calls=20, # 선택적 adaptive phase budget cap.
148-
batch_parallelism=2, # 선택 사항. provider thread-safety가 불확실하면 1 유지.
149147
),
150148
)
151149
```
@@ -157,14 +155,14 @@ prior로 사용합니다. score가 전혀 없으면 standard TrueSkill prior를
157155
후보 수가 작을 때는 `target_rank`를 문서 수로 자동 제한합니다.
158156
`max_adaptive_reranker_calls`는 adaptive refinement phase만 제한하며, 선택적
159157
initial pass 호출은 결과 metadata에서 별도로 함께 집계됩니다.
160-
`batch_parallelism`은 같은 AcuRank iteration 안의 독립 batch를 병렬 호출하되,
161-
posterior update는 deterministic batch order로 적용합니다.
158+
`AsyncAcuRankStrategy``batch_parallelism`은 같은 iteration 안의 독립 batch를
159+
동시에 호출하되, posterior update는 deterministic batch order로 적용합니다.
162160

163-
> **참고**: `strategy`를 명시하지 않으면 기본적으로 `ListwiseStrategy(algorithm="rankgpt_sliding_window")`가 자동으로 적용됩니다. Pairwise PRP, Setwise, TourRank-r, AcuRank는 기본 listwise보다 LLM 호출 수가 많을 수 있으므로 live benchmark 전 호출 수를 확인해야 합니다.
161+
> **참고**: `strategy`를 명시하지 않으면 기본적으로 `ListwiseStrategy()`(RankGPT sliding window)가 자동으로 적용됩니다. Pairwise PRP, Setwise, TourRank-r, AcuRank는 기본 listwise보다 LLM 호출 수가 많을 수 있으므로 live benchmark 전 호출 수를 확인해야 합니다.
164162
165163
## 커스텀 Strategy
166164

167-
커스텀 reranking 메소드는 `ListwiseStrategy.algorithm`에 새 문자열 값을 추가하는
165+
커스텀 reranking 메소드는 내장 Strategy 클래스를 수정하는
168166
방식보다, 새 Strategy 클래스로 구현하는 방식을 권장합니다. Strategy는 정규화된
169167
`Document` 목록, model client, 선택적 `top_k`를 받아 `RerankResult` 목록을 반환합니다.
170168

@@ -253,9 +251,6 @@ reranker = AzureOpenAIReranker(
253251
)
254252
```
255253

256-
`OpenAIProvider`, `AnthropicProvider`, `GeminiProvider`는 향후 SDK 구현을 위한
257-
public stub입니다. 호출하면 `RerankProviderError`로 fast fail 합니다.
258-
259254
## 비동기 지원 (Async Support)
260255

261256
대규모 트래픽이나 FastAPI 같은 비동기 웹 프레임워크를 위해 async reranker를

README.md

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ Configure a strategy and pass it to `AzureOpenAIReranker`.
8080
from ranksmith import AzureOpenAIReranker, ListwiseStrategy
8181

8282
strategy = ListwiseStrategy(
83-
algorithm="rankgpt_sliding_window",
8483
window_size=20,
8584
stride=10,
8685
max_document_chars=4000,
@@ -118,7 +117,7 @@ reranker = AzureOpenAIReranker(
118117
api_key="...",
119118
azure_endpoint="https://example.openai.azure.com",
120119
azure_deployment="gpt-4o-mini",
121-
strategy=TourRankStrategy(rounds=2, group_parallelism=1),
120+
strategy=TourRankStrategy(rounds=2),
122121
)
123122
```
124123

@@ -147,7 +146,6 @@ reranker = AzureOpenAIReranker(
147146
target_rank=10,
148147
window_size=20,
149148
max_adaptive_reranker_calls=20, # Optional adaptive-phase budget cap.
150-
batch_parallelism=2, # Optional; keep 1 if your provider is not thread-safe.
151149
),
152150
)
153151
```
@@ -158,17 +156,17 @@ TrueSkill prior. Partial score metadata and boolean score values fail fast.
158156

159157
For small candidate sets, `target_rank` is clipped to the number of documents.
160158
`max_adaptive_reranker_calls` limits only the adaptive refinement phase; the
161-
optional initial pass is counted separately in result metadata.
162-
`batch_parallelism` parallelizes independent batches within the same AcuRank
163-
iteration, while posterior updates are still applied in deterministic batch
164-
order.
159+
optional initial pass is counted separately in result metadata. On
160+
`AsyncAcuRankStrategy`, `batch_parallelism` runs independent batches within the
161+
same iteration concurrently, while posterior updates are still applied in
162+
deterministic batch order.
165163

166-
> **Note**: If `strategy` is not provided, it defaults to `ListwiseStrategy(algorithm="rankgpt_sliding_window")`. Pairwise PRP, Setwise, TourRank-r, and AcuRank can use more LLM calls than basic listwise reranking, so check call estimates before live benchmarks.
164+
> **Note**: If `strategy` is not provided, it defaults to `ListwiseStrategy()` (RankGPT sliding window). Pairwise PRP, Setwise, TourRank-r, and AcuRank can use more LLM calls than basic listwise reranking, so check call estimates before live benchmarks.
167165
168166
## Custom Strategies
169167

170168
Custom reranking methods should be implemented as new strategy classes instead
171-
of adding new string values to `ListwiseStrategy.algorithm`. A strategy receives
169+
of patching the built-in strategy classes. A strategy receives
172170
the normalized `Document` objects, a model client, and optional `top_k`, then
173171
returns `RerankResult` objects.
174172

@@ -257,10 +255,6 @@ reranker = AzureOpenAIReranker(
257255
)
258256
```
259257

260-
`OpenAIProvider`, `AnthropicProvider`, and `GeminiProvider` are reserved public
261-
stubs for future SDK-backed implementations. Calling them fails fast with
262-
`RerankProviderError`.
263-
264258
## Async Support
265259

266260
`ranksmith` provides first-class asynchronous support for high-throughput
@@ -368,9 +362,8 @@ Runnable examples live in the `examples/` directory.
368362
This repo ships a [Claude Code](https://code.claude.com/docs) plugin,
369363
`ranksmith-advisor`, that helps you choose a reranking strategy for your use
370364
case and returns working, CI-verified snippets. It encodes ranksmith-specific
371-
guardrails, so the suggested code follows the library's real contracts (no
372-
calls into unimplemented providers, no `algorithm` string hacks, no treating
373-
`confidence` as a reranker).
365+
guardrails, so the suggested code follows the library's real contracts (Azure
366+
is the only bundled provider, and `confidence` is not a reranker).
374367

375368
Use it from Claude Code:
376369

benchmarks/__init__.py

Whitespace-only changes.
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from pathlib import Path
99
from typing import Literal, cast
1010

11-
from ranksmith._metrics import mrr_at_k, ndcg_at_k, recall_at_k
11+
from benchmarks.metrics import mrr_at_k, ndcg_at_k, recall_at_k
1212

1313
SCHEMA_VERSION = 1
1414
CandidateStrategy = Literal["candidate_file", "oracle_plus_random"]
@@ -81,7 +81,6 @@ def load_beir_cases(
8181
seed: int = 13,
8282
dataset_name: str = "BEIR/SciFact",
8383
fixture_prefix: str = "beir-scifact",
84-
license_text: str = "See upstream dataset license metadata.",
8584
) -> list[BenchmarkCase]:
8685
_validate_positive("candidate_count", candidate_count)
8786
if max_cases is not None:
@@ -132,7 +131,7 @@ def load_beir_cases(
132131
fixture_id=f"{fixture_prefix}-{split}-{query_id}",
133132
dataset=f"{dataset_name} {split}",
134133
source=f"cache:{cache_dir}",
135-
license=license_text,
134+
license="See upstream dataset license metadata.",
136135
query_id=query_id,
137136
query=queries[query_id],
138137
documents=documents,
Lines changed: 5 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from __future__ import annotations
22

33
import hashlib
4-
import json
54
import math
6-
from collections.abc import Mapping, Sequence
5+
import statistics
6+
from collections.abc import Sequence
77
from dataclasses import dataclass
8-
from pathlib import Path
9-
from typing import Any, Literal, TypeVar
8+
from typing import Literal, TypeVar
109

11-
from ranksmith._metrics import map_score, mrr_at_k, ndcg_at_k, recall_at_k
10+
from benchmarks.metrics import map_score, mrr_at_k, ndcg_at_k, recall_at_k
1211
from ranksmith.strategies import TourRankStageConfig
1312

1413
T = TypeVar("T")
@@ -30,13 +29,6 @@ class MtebRerankingSample:
3029
candidates: tuple[MtebRerankingCandidate, ...]
3130

3231

33-
@dataclass(frozen=True)
34-
class ParsedRanking:
35-
ranking: tuple[int, ...]
36-
valid: bool
37-
failure_type: str | None
38-
39-
4032
@dataclass(frozen=True)
4133
class PriceConfig:
4234
input_token_price_per_1m: float
@@ -141,68 +133,6 @@ def _parse_tourrank_method(method: str) -> MethodConfig:
141133
)
142134

143135

144-
def parse_ranking_with_failure_type(
145-
raw_response: str,
146-
expected_count: int,
147-
) -> ParsedRanking:
148-
try:
149-
data = json.loads(raw_response)
150-
except json.JSONDecodeError:
151-
return ParsedRanking((), False, "json_parse_failure")
152-
if not isinstance(data, dict) or "ranking" not in data:
153-
return ParsedRanking((), False, "missing_ranking")
154-
ranking = data["ranking"]
155-
if not isinstance(ranking, list):
156-
return ParsedRanking((), False, "missing_ranking")
157-
if not all(type(item) is int for item in ranking):
158-
return ParsedRanking((), False, "non_integer_rank")
159-
parsed = tuple(ranking)
160-
if len(parsed) != expected_count:
161-
return ParsedRanking(parsed, False, "length_mismatch")
162-
if any(rank < 1 or rank > expected_count for rank in parsed):
163-
return ParsedRanking(parsed, False, "out_of_range_rank")
164-
if len(set(parsed)) != len(parsed):
165-
return ParsedRanking(parsed, False, "duplicate_rank")
166-
return ParsedRanking(parsed, True, None)
167-
168-
169-
def apply_integer_permutation(
170-
items: Sequence[T],
171-
ranking: Sequence[int],
172-
) -> tuple[T, ...]:
173-
return tuple(items[index - 1] for index in ranking)
174-
175-
176-
def rankgpt_window_ranges(
177-
*,
178-
document_count: int,
179-
rank_start: int,
180-
rank_end: int,
181-
window_size: int,
182-
step: int,
183-
) -> tuple[tuple[int, int], ...]:
184-
if document_count < 1:
185-
return ()
186-
if rank_start < 0 or rank_end < 1 or window_size < 1 or step < 1:
187-
raise ValueError("rank_start, rank_end, window_size, and step are invalid")
188-
if step > window_size:
189-
raise ValueError("step must be less than or equal to window_size")
190-
if rank_end <= rank_start:
191-
raise ValueError("rank_end must be greater than rank_start")
192-
193-
effective_end = min(rank_end, document_count)
194-
ranges: list[tuple[int, int]] = []
195-
end = effective_end
196-
max_iterations = (effective_end - rank_start) // step + 2
197-
for _ in range(max_iterations):
198-
start = max(rank_start, end - window_size)
199-
ranges.append((start, end))
200-
if start == rank_start:
201-
return tuple(ranges)
202-
end -= step
203-
raise RuntimeError("rankgpt_window_ranges exceeded iteration cap")
204-
205-
206136
def compute_query_metrics(
207137
*,
208138
sample: MtebRerankingSample,
@@ -225,9 +155,7 @@ def compute_query_metrics(
225155

226156

227157
def mean(values: Sequence[float]) -> float:
228-
if not values:
229-
return 0.0
230-
return sum(values) / len(values)
158+
return statistics.fmean(values) if values else 0.0
231159

232160

233161
def percentile(values: Sequence[float], percentile_value: float) -> float:
@@ -259,32 +187,6 @@ def estimate_cost(
259187
)
260188

261189

262-
def write_jsonl(path: Path, rows: Sequence[Mapping[str, Any]]) -> None:
263-
path.parent.mkdir(parents=True, exist_ok=True)
264-
with path.open("w", encoding="utf-8") as handle:
265-
for row in rows:
266-
handle.write(json.dumps(dict(row), sort_keys=True) + "\n")
267-
268-
269-
def completed_result_keys(path: Path) -> set[tuple[str, str, str, str]]:
270-
if not path.exists():
271-
return set()
272-
completed: set[tuple[str, str, str, str]] = set()
273-
for line in path.read_text(encoding="utf-8").splitlines():
274-
if line.strip() == "":
275-
continue
276-
row = json.loads(line)
277-
completed.add(
278-
(
279-
str(row["task"]),
280-
str(row["split"]),
281-
str(row["query_id"]),
282-
str(row["method"]),
283-
)
284-
)
285-
return completed
286-
287-
288190
def tourrank_stage_configs_for_candidate_count(
289191
candidate_count: int,
290192
) -> tuple[TourRankStageConfig, ...]:

docs/wiki/00_context.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@ LLM 기반 reranking을 위한 작고 신뢰성 있는 Python 패키지를 만
3333
- `AsyncAzureOpenAIReranker`
3434
- `AzureAOAIProvider`
3535
- `AsyncAzureAOAIProvider`
36-
- `OpenAIProvider`
37-
- `AsyncOpenAIProvider`
38-
- `AnthropicProvider`
39-
- `AsyncAnthropicProvider`
40-
- `GeminiProvider`
41-
- `AsyncGeminiProvider`
4236
- `ModelClient`
4337
- `AsyncModelClient`
4438
- `ModelProvider`
@@ -92,7 +86,7 @@ LLM 기반 reranking을 위한 작고 신뢰성 있는 Python 패키지를 만
9286
- `tolerance`: `0.01`
9387
- `uncertain_threshold`: `10`
9488
- `initial_pass`: `True`
95-
- `score_metadata_key`: `score`
89+
- score prior metadata key: `score` (고정)
9690

9791
## Codex 읽기 순서
9892
1. `docs/wiki/00_context.md`

docs/wiki/02_architecture.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,23 @@ src/ranksmith/
2020
parsing.py # LLM response contract parser
2121
strategies/
2222
__init__.py # public strategy exports
23-
_common.py # shared validation/capability guards
24-
_listwise.py
25-
_pairwise.py
26-
_setwise.py
27-
_tourrank.py
28-
_acurank.py
23+
common.py # shared validation/capability guards
24+
listwise.py
25+
pairwise.py
26+
setwise.py
27+
tourrank.py
28+
acurank.py
2929
providers/
3030
__init__.py # public provider exports
31-
_azure.py # Azure OpenAI implementation
32-
_stubs.py # unimplemented provider stubs
33-
_providers.py # backward-compatible re-export layer
31+
azure.py # Azure OpenAI implementation
3432
```
3533

3634
외부 사용자는 root import 또는 `ranksmith.strategies`, `ranksmith.providers`의 public export를 사용한다.
37-
`strategies/_*.py`, `providers/_*.py`는 내부 구현 모듈로 취급한다.
35+
strategies/, providers/ 하위 개별 모듈은 내부 구현으로 취급한다.
3836

3937
## ModelProvider
4038
실제 SDK 호출은 Azure OpenAI만 구현한다.
41-
OpenAI, Anthropic, Gemini provider는 향후 구현을 위한 public stub이며 호출 시 fast fail 한다.
39+
다른 vendor는 사용자 정의 `ModelProvider` 구현으로 연결한다.
4240

4341
Provider는 `ModelRequest`를 받아 `ModelResponse`를 반환한다.
4442
Provider는 ranking 도메인 prompt의 의미를 알지 않는다.

examples/acurank.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ def main() -> None:
8484
target_rank=3,
8585
window_size=3,
8686
max_adaptive_reranker_calls=1,
87-
batch_parallelism=1,
8887
),
8988
)
9089

examples/rankgpt_async.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ async def async_main() -> None:
4848
azure_deployment=deployment,
4949
api_version="2024-10-21", # 필요한 API 버전에 맞게 수정
5050
strategy=AsyncListwiseStrategy(
51-
algorithm="rankgpt_sliding_window",
5251
window_size=10,
5352
stride=5,
5453
),

0 commit comments

Comments
 (0)