Skip to content

Commit dabd59d

Browse files
liuzhengyang699codelion
authored andcommitted
fix: exclude truncated and blank best-of-n candidates
1 parent 265eaac commit dabd59d

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

‎optillm/bon.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ def best_of_n_sampling(system_prompt: str, initial_query: str, client, model: st
3737
if response is None or not response.choices:
3838
raise Exception("Response is None or has no choices")
3939

40-
completions = [choice.message.content for choice in response.choices if choice.message.content is not None]
40+
completions = [
41+
choice.message.content for choice in response.choices
42+
if choice.message.content and choice.message.content.strip()
43+
and choice.finish_reason != "length"
44+
]
4145
logger.info(f"Generated {len(completions)} initial completions using n parameter. Tokens used: {response.usage.completion_tokens}")
4246
bon_completion_tokens += response.usage.completion_tokens
4347

@@ -68,7 +72,8 @@ def best_of_n_sampling(system_prompt: str, initial_query: str, client, model: st
6872
# Check for valid response with None-checking
6973
if (response is None or
7074
not response.choices or
71-
response.choices[0].message.content is None or
75+
not response.choices[0].message.content or
76+
not response.choices[0].message.content.strip() or
7277
response.choices[0].finish_reason == "length"):
7378
logger.warning(f"Completion {i+1}/{n} truncated or empty, skipping")
7479
continue
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from types import SimpleNamespace
2+
from unittest.mock import Mock
3+
4+
import pytest
5+
6+
from optillm.bon import best_of_n_sampling
7+
8+
9+
def response(*choices):
10+
return SimpleNamespace(
11+
choices=[
12+
SimpleNamespace(message=SimpleNamespace(content=text), finish_reason=reason)
13+
for text, reason in choices
14+
],
15+
usage=SimpleNamespace(completion_tokens=1),
16+
)
17+
18+
19+
@pytest.mark.parametrize(
20+
"invalid_text, reason", [("incomplete answer", "length"), (" ", "stop")]
21+
)
22+
def test_batched_invalid_candidates_are_never_sent_to_judge(invalid_text, reason):
23+
rated = []
24+
25+
def create(**kwargs):
26+
if kwargs.get("n") == 2:
27+
return response((invalid_text, reason), ("complete answer", "stop"))
28+
candidate = kwargs["messages"][-2]["content"]
29+
rated.append(candidate)
30+
return response(("10" if candidate == invalid_text else "8", "stop"))
31+
32+
client = SimpleNamespace(
33+
chat=SimpleNamespace(completions=SimpleNamespace(create=create))
34+
)
35+
answer, tokens = best_of_n_sampling("system", "question", client, "model", n=2)
36+
37+
assert answer == "complete answer"
38+
assert rated == ["complete answer"]
39+
assert tokens == 2
40+
41+
42+
def test_all_invalid_batch_uses_existing_sequential_fallback():
43+
create = Mock(
44+
side_effect=[
45+
response(("partial", "length")),
46+
response(("complete answer", "stop")),
47+
response(("9", "stop")),
48+
]
49+
)
50+
client = SimpleNamespace(
51+
chat=SimpleNamespace(completions=SimpleNamespace(create=create))
52+
)
53+
answer, tokens = best_of_n_sampling("system", "question", client, "model", n=1)
54+
55+
assert answer == "complete answer"
56+
assert "n" not in create.call_args_list[1].kwargs
57+
assert tokens == 3

0 commit comments

Comments
 (0)