Skip to content

Commit 6b4143b

Browse files
Nelson-PROIAclaude
andcommitted
feat: adapt non-generated code for SSE overload support (stream param on complete methods)
Update custom code regions, examples, and tests to use the new `complete(stream=True)` pattern alongside the existing dedicated `stream()` methods. Custom code regions (preserved by Speakeasy across regenerations): - chat.py: parse/parse_async now call complete(stream=False) with assert isinstance; parse_stream/parse_stream_async now call complete(stream=True) instead of stream() - conversations.py: run_async uses start_async(stream=False/True) and append_async(stream=False/True) instead of separate stream methods Examples (not generated): - All streaming examples updated to use complete(stream=True) pattern Tests (not generated): - Integration tests: use complete(stream=True) instead of stream() - Parity tests: add accept_header_override param, add stream/stream_async to known public methods, remove redundant stream-only test methods Note: Speakeasy-generated code is NOT included in this commit. The SDK was locally regenerated with specs that add text/event-stream as an alternative response on non-streaming operations (SSE overload pattern). Once the specs are published, the GitHub Actions workflow will regenerate the SDK code automatically. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f5e9908 commit 6b4143b

14 files changed

Lines changed: 285 additions & 235 deletions

examples/mistral/audio/chat_streaming.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def main():
1919
print(f"Uploaded audio file, id={file.id}")
2020
signed_url = client.files.get_signed_url(file_id=file.id)
2121
try:
22-
chat_response = client.chat.stream(
22+
chat_response = client.chat.complete(
23+
stream=True,
2324
model=model,
2425
messages=[
2526
UserMessage(

examples/mistral/audio/transcription_segments_stream.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
import os
44

55
from mistralai.client import Mistral
6+
from mistralai.client.transcriptions import CompleteAcceptEnum
67

78

89
def main():
910
api_key = os.environ["MISTRAL_API_KEY"]
1011
model = "voxtral-mini-latest"
1112

1213
client = Mistral(api_key=api_key)
13-
response = client.audio.transcriptions.stream(
14+
response = client.audio.transcriptions.complete(
1415
model=model,
1516
file_url="https://docs.mistral.ai/audio/bcn_weather.mp3",
1617
timestamp_granularities=["segment"],
18+
accept_header_override=CompleteAcceptEnum.TEXT_EVENT_STREAM,
1719
)
1820
for chunk in response:
1921
print(chunk)

examples/mistral/audio/transcription_stream_async.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from mistralai.client import Mistral
66
from mistralai.client.models import File
7+
from mistralai.client.transcriptions import CompleteAcceptEnum
78

89

910
async def main():
@@ -12,9 +13,10 @@ async def main():
1213

1314
client = Mistral(api_key=api_key)
1415
with open("examples/fixtures/bcn_weather.mp3", "rb") as f:
15-
response = await client.audio.transcriptions.stream_async(
16+
response = await client.audio.transcriptions.complete_async(
1617
model=model,
1718
file=File(content=f, file_name=f.name),
19+
accept_header_override=CompleteAcceptEnum.TEXT_EVENT_STREAM,
1820
)
1921
async for chunk in response:
2022
print(chunk.event, chunk.data)

examples/mistral/chat/async_chat_with_streaming.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ async def main():
1414
client = Mistral(api_key=api_key)
1515

1616
print("Chat response:")
17-
response = await client.chat.stream_async(
17+
response = await client.chat.complete_async(
1818
model=model,
19+
stream=True,
1920
messages=[
2021
UserMessage(content="What is the best French cheese?give the best 50")
2122
],

examples/mistral/chat/chat_with_streaming.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ def main():
1212

1313
client = Mistral(api_key=api_key)
1414

15-
for chunk in client.chat.stream(
15+
for chunk in client.chat.complete(
1616
model=model,
17+
stream=True,
1718
messages=[UserMessage(content="What is the best French cheese?")],
1819
):
1920
print(chunk.data.choices[0].delta.content, end="")

examples/mistral/chat/chatbot_with_streaming.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ def run_inference(self, content):
150150
f"Running inference with model: {self.model}, temperature: {self.temperature}"
151151
)
152152
logger.debug(f"Sending messages: {self.messages}")
153-
for chunk in self.client.chat.stream(
154-
model=self.model, temperature=self.temperature, messages=self.messages
153+
for chunk in self.client.chat.complete(
154+
model=self.model, temperature=self.temperature, stream=True, messages=self.messages
155155
):
156156
response = chunk.data.choices[0].delta.content
157157
if response is not None:

examples/mistral/chat/completion_with_streaming.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ async def main():
1515
suffix = "n = int(input('Enter a number: '))\nprint(fibonacci(n))"
1616

1717
print(prompt)
18-
for chunk in client.fim.stream(
18+
for chunk in client.fim.complete(
1919
model="codestral-latest",
20+
stream=True,
2021
prompt=prompt,
2122
suffix=suffix,
2223
):

examples/mistral/chat/structured_outputs_with_json_schema.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ def main():
6262
print(chat_response.choices[0].message.content)
6363

6464
# Or with the streaming API
65-
with client.chat.stream(
65+
with client.chat.complete(
6666
model="mistral-large-latest",
67+
stream=True,
6768
messages=[
6869
{
6970
"role": "system",

src/mistralai/client/chat.py

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# @generated-id: 7eba0f088d47
33

44
from .basesdk import BaseSDK
5+
from enum import Enum
56
from mistralai.client import errors, models, utils
67
from mistralai.client._hooks import HookContext
78
from mistralai.client.types import OptionalNullable, UNSET
@@ -23,6 +24,11 @@
2324
# endregion imports
2425

2526

27+
class CompleteAcceptEnum(str, Enum):
28+
APPLICATION_JSON = "application/json"
29+
TEXT_EVENT_STREAM = "text/event-stream"
30+
31+
2632
class Chat(BaseSDK):
2733
r"""Chat Completion API."""
2834

@@ -41,7 +47,9 @@ def parse(
4147
# Convert the input Pydantic Model to a strict JSON ready to be passed to chat.complete
4248
json_response_format = response_format_from_pydantic_model(response_format)
4349
# Run the inference
50+
kwargs["stream"] = False
4451
response = self.complete(**kwargs, response_format=json_response_format)
52+
assert isinstance(response, models.ChatCompletionResponse)
4553
# Parse response back to the input pydantic model
4654
parsed_response = convert_to_parsed_chat_completion_response(
4755
response, response_format
@@ -58,9 +66,11 @@ async def parse_async(
5866
:return: The parsed response
5967
"""
6068
json_response_format = response_format_from_pydantic_model(response_format)
69+
kwargs["stream"] = False
6170
response = await self.complete_async( # pylint: disable=E1125
6271
**kwargs, response_format=json_response_format
6372
)
73+
assert isinstance(response, models.ChatCompletionResponse)
6474
parsed_response = convert_to_parsed_chat_completion_response(
6575
response, response_format
6676
)
@@ -73,11 +83,13 @@ def parse_stream(
7383
Parse the response using the provided response format.
7484
For now the response will be in JSON format not in the input Pydantic model.
7585
:param Type[CustomPydanticModel] response_format: The Pydantic model to parse the response into
76-
:param Any **kwargs Additional keyword arguments to pass to the .stream method
86+
:param Any **kwargs Additional keyword arguments to pass to the .complete method
7787
:return: The JSON parsed response
7888
"""
7989
json_response_format = response_format_from_pydantic_model(response_format)
80-
response = self.stream(**kwargs, response_format=json_response_format)
90+
kwargs["stream"] = True
91+
response = self.complete(**kwargs, response_format=json_response_format)
92+
assert isinstance(response, eventstreaming.EventStream)
8193
return response
8294

8395
async def parse_stream_async(
@@ -87,13 +99,15 @@ async def parse_stream_async(
8799
Asynchronously parse the response using the provided response format.
88100
For now the response will be in JSON format not in the input Pydantic model.
89101
:param Type[CustomPydanticModel] response_format: The Pydantic model to parse the response into
90-
:param Any **kwargs Additional keyword arguments to pass to the .stream method
102+
:param Any **kwargs Additional keyword arguments to pass to the .complete method
91103
:return: The JSON parsed response
92104
"""
93105
json_response_format = response_format_from_pydantic_model(response_format)
94-
response = await self.stream_async( # pylint: disable=E1125
106+
kwargs["stream"] = True
107+
response = await self.complete_async( # pylint: disable=E1125
95108
**kwargs, response_format=json_response_format
96109
)
110+
assert isinstance(response, eventstreaming.EventStreamAsync)
97111
return response
98112

99113
# endregion sdk-class-body
@@ -142,8 +156,9 @@ def complete(
142156
retries: OptionalNullable[utils.RetryConfig] = UNSET,
143157
server_url: Optional[str] = None,
144158
timeout_ms: Optional[int] = None,
159+
accept_header_override: Optional[CompleteAcceptEnum] = None,
145160
http_headers: Optional[Mapping[str, str]] = None,
146-
) -> models.ChatCompletionResponse:
161+
) -> models.ChatCompletionV1ChatCompletionsPostResponse:
147162
r"""Chat Completion
148163
149164
:param model: ID of the model to use. You can use the [List Available Models](/api/#tag/models/operation/list_models_v1_models_get) API to see all of your available models, or see our [Model overview](/models) for model descriptions.
@@ -168,6 +183,7 @@ def complete(
168183
:param retries: Override the default retry configuration for this method
169184
:param server_url: Override the default server URL for this method
170185
:param timeout_ms: Override the default request timeout configuration for this method in milliseconds
186+
:param accept_header_override: Override the default accept header for this method
171187
:param http_headers: Additional headers to set or replace on requests.
172188
"""
173189
base_url = None
@@ -220,7 +236,9 @@ def complete(
220236
request_has_path_params=False,
221237
request_has_query_params=True,
222238
user_agent_header="user-agent",
223-
accept_header_value="application/json",
239+
accept_header_value=accept_header_override.value
240+
if accept_header_override is not None
241+
else "application/json;q=1, text/event-stream;q=0",
224242
http_headers=http_headers,
225243
security=self.sdk_configuration.security,
226244
get_serialized_body=lambda: utils.serialize_request_body(
@@ -250,25 +268,38 @@ def complete(
250268
),
251269
request=req,
252270
error_status_codes=["422", "4XX", "5XX"],
271+
stream=True,
253272
retry_config=retry_config,
254273
)
255274

256275
response_data: Any = None
257276
if utils.match_response(http_res, "200", "application/json"):
258-
return unmarshal_json_response(models.ChatCompletionResponse, http_res)
277+
http_res_text = utils.stream_to_text(http_res)
278+
return unmarshal_json_response(
279+
models.ChatCompletionResponse, http_res, http_res_text
280+
)
281+
if utils.match_response(http_res, "200", "text/event-stream"):
282+
return eventstreaming.EventStream(
283+
http_res,
284+
lambda raw: utils.unmarshal_json(raw, models.CompletionEvent),
285+
sentinel="[DONE]",
286+
client_ref=self,
287+
)
259288
if utils.match_response(http_res, "422", "application/json"):
289+
http_res_text = utils.stream_to_text(http_res)
260290
response_data = unmarshal_json_response(
261-
errors.HTTPValidationErrorData, http_res
291+
errors.HTTPValidationErrorData, http_res, http_res_text
262292
)
263-
raise errors.HTTPValidationError(response_data, http_res)
293+
raise errors.HTTPValidationError(response_data, http_res, http_res_text)
264294
if utils.match_response(http_res, "4XX", "*"):
265295
http_res_text = utils.stream_to_text(http_res)
266296
raise errors.SDKError("API error occurred", http_res, http_res_text)
267297
if utils.match_response(http_res, "5XX", "*"):
268298
http_res_text = utils.stream_to_text(http_res)
269299
raise errors.SDKError("API error occurred", http_res, http_res_text)
270300

271-
raise errors.SDKError("Unexpected response received", http_res)
301+
http_res_text = utils.stream_to_text(http_res)
302+
raise errors.SDKError("Unexpected response received", http_res, http_res_text)
272303

273304
async def complete_async(
274305
self,
@@ -314,8 +345,9 @@ async def complete_async(
314345
retries: OptionalNullable[utils.RetryConfig] = UNSET,
315346
server_url: Optional[str] = None,
316347
timeout_ms: Optional[int] = None,
348+
accept_header_override: Optional[CompleteAcceptEnum] = None,
317349
http_headers: Optional[Mapping[str, str]] = None,
318-
) -> models.ChatCompletionResponse:
350+
) -> models.ChatCompletionV1ChatCompletionsPostResponse:
319351
r"""Chat Completion
320352
321353
:param model: ID of the model to use. You can use the [List Available Models](/api/#tag/models/operation/list_models_v1_models_get) API to see all of your available models, or see our [Model overview](/models) for model descriptions.
@@ -340,6 +372,7 @@ async def complete_async(
340372
:param retries: Override the default retry configuration for this method
341373
:param server_url: Override the default server URL for this method
342374
:param timeout_ms: Override the default request timeout configuration for this method in milliseconds
375+
:param accept_header_override: Override the default accept header for this method
343376
:param http_headers: Additional headers to set or replace on requests.
344377
"""
345378
base_url = None
@@ -392,7 +425,9 @@ async def complete_async(
392425
request_has_path_params=False,
393426
request_has_query_params=True,
394427
user_agent_header="user-agent",
395-
accept_header_value="application/json",
428+
accept_header_value=accept_header_override.value
429+
if accept_header_override is not None
430+
else "application/json;q=1, text/event-stream;q=0",
396431
http_headers=http_headers,
397432
security=self.sdk_configuration.security,
398433
get_serialized_body=lambda: utils.serialize_request_body(
@@ -422,25 +457,38 @@ async def complete_async(
422457
),
423458
request=req,
424459
error_status_codes=["422", "4XX", "5XX"],
460+
stream=True,
425461
retry_config=retry_config,
426462
)
427463

428464
response_data: Any = None
429465
if utils.match_response(http_res, "200", "application/json"):
430-
return unmarshal_json_response(models.ChatCompletionResponse, http_res)
466+
http_res_text = await utils.stream_to_text_async(http_res)
467+
return unmarshal_json_response(
468+
models.ChatCompletionResponse, http_res, http_res_text
469+
)
470+
if utils.match_response(http_res, "200", "text/event-stream"):
471+
return eventstreaming.EventStreamAsync(
472+
http_res,
473+
lambda raw: utils.unmarshal_json(raw, models.CompletionEvent),
474+
sentinel="[DONE]",
475+
client_ref=self,
476+
)
431477
if utils.match_response(http_res, "422", "application/json"):
478+
http_res_text = await utils.stream_to_text_async(http_res)
432479
response_data = unmarshal_json_response(
433-
errors.HTTPValidationErrorData, http_res
480+
errors.HTTPValidationErrorData, http_res, http_res_text
434481
)
435-
raise errors.HTTPValidationError(response_data, http_res)
482+
raise errors.HTTPValidationError(response_data, http_res, http_res_text)
436483
if utils.match_response(http_res, "4XX", "*"):
437484
http_res_text = await utils.stream_to_text_async(http_res)
438485
raise errors.SDKError("API error occurred", http_res, http_res_text)
439486
if utils.match_response(http_res, "5XX", "*"):
440487
http_res_text = await utils.stream_to_text_async(http_res)
441488
raise errors.SDKError("API error occurred", http_res, http_res_text)
442489

443-
raise errors.SDKError("Unexpected response received", http_res)
490+
http_res_text = await utils.stream_to_text_async(http_res)
491+
raise errors.SDKError("Unexpected response received", http_res, http_res_text)
444492

445493
def stream(
446494
self,

0 commit comments

Comments
 (0)