|
6 | 6 | from pydantic import BaseModel, Field |
7 | 7 |
|
8 | 8 | 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 |
9 | 13 |
|
10 | 14 |
|
11 | 15 | class Answer(BaseModel): |
12 | 16 | answer: int = Field(description="The answer to the question") |
13 | 17 |
|
14 | 18 |
|
| 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 | + |
15 | 87 | def batch_call(model_name, prompts): |
16 | 88 | dataset: Dataset = Dataset.from_dict({"prompt": prompts}) |
17 | 89 | llm = curator.LLM( |
|
0 commit comments