Skip to content

Commit 4e69044

Browse files
authored
Add Mistral batch finish reasons (#717)
1 parent 3e0f6a8 commit 4e69044

3 files changed

Lines changed: 80 additions & 4 deletions

File tree

‎src/bespokelabs/curator/request_processor/batch/gemini_batch_request_processor.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ def parse_api_specific_response(
299299
token_usage = None
300300
cost = None
301301
response_message = None
302-
finish_reason = "unkown"
302+
finish_reason = "unknown"
303303

304304
response_message_raw = ""
305305
if result_type == "succeeded":

‎src/bespokelabs/curator/request_processor/batch/mistral_batch_request_processor.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import instructor
66
import litellm
77
from litellm import model_cost
8+
from litellm.litellm_core_utils.core_helpers import map_finish_reason
89
from mistralai import Mistral
910
from mistralai.models import BatchJobOut, UploadFileOutTypedDict
1011

@@ -174,24 +175,26 @@ def parse_api_specific_response(self, raw_response: dict, generic_request: Gener
174175
175176
Reference: Mistral API request/response format: https://docs.mistral.ai/api/#tag/chat
176177
"""
178+
finish_reason = "unknown"
177179
if raw_response["response"]["status_code"] != 200:
178180
response_message = None
179181
response_errors = raw_response["detail"]["msg"]
180182
token_usage = None
181183
cost = None
182184
else:
183-
response_message = raw_response["response"]["body"]["choices"][0]["message"]["content"]
185+
choice = raw_response["response"]["body"]["choices"][0]
186+
response_message = choice["message"]["content"]
184187
response_errors = None
188+
finish_reason = choice.get("finish_reason", "unknown")
185189
token_usage = _TokenUsage(
186190
input=int(raw_response["response"]["body"]["usage"]["prompt_tokens"]),
187191
output=int(raw_response["response"]["body"]["usage"]["completion_tokens"]),
188192
total=int(raw_response["response"]["body"]["usage"]["total_tokens"]),
189193
)
190-
print("[DEBUG] self.tracker.input_cost_per_million:", self.tracker.input_cost_per_million)
191-
print("[DEBUG] self.tracker.output_cost_per_million:", self.tracker.output_cost_per_million)
192194

193195
cost = self._cost_processor.cost(model="mistral/" + self.config.model, prompt=str(generic_request.messages), completion=response_message)
194196

197+
finish_reason = map_finish_reason(finish_reason)
195198
generic_response = GenericResponse(
196199
response_message=response_message,
197200
response_errors=response_errors,
@@ -202,6 +205,7 @@ def parse_api_specific_response(self, raw_response: dict, generic_request: Gener
202205
finished_at=batch.finished_at,
203206
token_usage=token_usage,
204207
response_cost=cost,
208+
finish_reason=finish_reason,
205209
)
206210
return generic_response
207211

‎tests/unittests/test_batch.py‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,84 @@
66
from pydantic import BaseModel, Field
77

88
from bespokelabs import curator
9+
from bespokelabs.curator.request_processor.batch.mistral_batch_request_processor import MistralBatchRequestProcessor
10+
from bespokelabs.curator.request_processor.config import BatchRequestProcessorConfig
11+
from bespokelabs.curator.types.generic_batch import GenericBatch, GenericBatchRequestCounts, GenericBatchStatus
12+
from bespokelabs.curator.types.generic_request import GenericRequest
913

1014

1115
class Answer(BaseModel):
1216
answer: int = Field(description="The answer to the question")
1317

1418

19+
class _CostProcessorStub:
20+
def cost(self, **kwargs):
21+
return 0.0
22+
23+
24+
def _generic_request() -> GenericRequest:
25+
return GenericRequest(
26+
model="mistral-small-latest",
27+
messages=[{"role": "user", "content": "hello"}],
28+
original_row={},
29+
original_row_idx=0,
30+
)
31+
32+
33+
def _generic_batch() -> GenericBatch:
34+
now = pd.Timestamp("2025-01-01T00:00:00Z").to_pydatetime()
35+
return GenericBatch(
36+
request_file="requests.jsonl",
37+
id="batch-id",
38+
created_at=now,
39+
finished_at=now,
40+
status=GenericBatchStatus.FINISHED.value,
41+
api_key_suffix="test",
42+
request_counts=GenericBatchRequestCounts(
43+
failed=0,
44+
succeeded=1,
45+
total=1,
46+
raw_request_counts_object={},
47+
),
48+
raw_batch={},
49+
raw_status="SUCCESS",
50+
)
51+
52+
53+
def test_mistral_batch_response_includes_normalized_finish_reason() -> None:
54+
"""Mistral batch responses expose finish_reason for invalid-finish retries."""
55+
processor = MistralBatchRequestProcessor.__new__(MistralBatchRequestProcessor)
56+
processor.config = BatchRequestProcessorConfig(model="mistral-small-latest")
57+
processor._cost_processor = _CostProcessorStub()
58+
59+
raw_response = {
60+
"response": {
61+
"status_code": 200,
62+
"body": {
63+
"choices": [
64+
{
65+
"message": {"content": "hello"},
66+
"finish_reason": "length",
67+
}
68+
],
69+
"usage": {
70+
"prompt_tokens": 1,
71+
"completion_tokens": 2,
72+
"total_tokens": 3,
73+
},
74+
},
75+
}
76+
}
77+
78+
response = processor.parse_api_specific_response(
79+
raw_response=raw_response,
80+
generic_request=_generic_request(),
81+
batch=_generic_batch(),
82+
)
83+
84+
assert response.finish_reason == "length"
85+
86+
1587
def batch_call(model_name, prompts):
1688
dataset: Dataset = Dataset.from_dict({"prompt": prompts})
1789
llm = curator.LLM(

0 commit comments

Comments
 (0)