Skip to content

Commit 2645dee

Browse files
committed
fix: use raw_response.text instead of .json() for LegacyAPIResponse compat
LegacyAPIResponse (returned by with_raw_response in some openai SDK versions) does not expose a .json() method, causing AttributeError on all non-streaming calls. Switch to .text which is available on both LegacyAPIResponse and APIResponse. Update test mocks to match.
1 parent ce3b93e commit 2645dee

3 files changed

Lines changed: 39 additions & 47 deletions

File tree

integrations/mistral/src/haystack_integrations/components/generators/mistral/chat/chat_generator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ def run(
387387
completions = self._handle_stream_response(chat_completion, streaming_callback)
388388
else:
389389
raw_response = getattr(self.client.chat.completions.with_raw_response, openai_endpoint)(**api_args)
390-
completions = _convert_mistral_response_to_chat_messages(raw_response.json())
390+
completions = _convert_mistral_response_to_chat_messages(raw_response.text)
391391

392392
for message in completions:
393393
_check_finish_reason(message.meta)
@@ -457,7 +457,7 @@ async def run_async(
457457
raw_response = await getattr(self.async_client.chat.completions.with_raw_response, openai_endpoint)(
458458
**api_args
459459
)
460-
completions = _convert_mistral_response_to_chat_messages(raw_response.json())
460+
completions = _convert_mistral_response_to_chat_messages(raw_response.text)
461461

462462
for message in completions:
463463
_check_finish_reason(message.meta)

integrations/mistral/tests/test_mistral_chat_generator.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import json
22
import logging
33
import os
4-
from datetime import datetime
54
from unittest.mock import ANY, patch
65

76
import pytest
8-
import pytz
97
from haystack import Pipeline
108
from haystack.components.generators.utils import print_streaming_chunk
119
from haystack.components.tools import ToolInvoker
@@ -21,8 +19,7 @@
2119
from haystack.tools import Tool, Toolset
2220
from haystack.utils.auth import Secret
2321
from openai import OpenAIError
24-
from openai.types.chat import ChatCompletion, ChatCompletionChunk, ChatCompletionMessage
25-
from openai.types.chat.chat_completion import Choice
22+
from openai.types.chat import ChatCompletionChunk
2623
from openai.types.chat.chat_completion_chunk import Choice as ChoiceChunk
2724
from openai.types.chat.chat_completion_chunk import ChoiceDelta, ChoiceDeltaToolCall, ChoiceDeltaToolCallFunction
2825
from openai.types.completion_usage import CompletionUsage
@@ -103,23 +100,25 @@ def mock_chat_completion():
103100
Mock the OpenAI API completion response and reuse it for tests
104101
"""
105102
with patch("openai.resources.chat.completions.Completions.create") as mock_chat_completion_create:
106-
completion = ChatCompletion(
107-
id="foo",
108-
model="mistral-small-latest",
109-
object="chat.completion",
110-
choices=[
111-
Choice(
112-
finish_reason="stop",
113-
logprobs=None,
114-
index=0,
115-
message=ChatCompletionMessage(content="Hello world!", role="assistant"),
116-
)
117-
],
118-
created=int(datetime.now(tz=pytz.timezone("UTC")).timestamp()),
119-
usage={"prompt_tokens": 57, "completion_tokens": 40, "total_tokens": 97},
103+
mock_response = type("MockRawResponse", (), {})()
104+
mock_response.text = json.dumps(
105+
{
106+
"id": "foo",
107+
"model": "mistral-small-latest",
108+
"object": "chat.completion",
109+
"choices": [
110+
{
111+
"finish_reason": "stop",
112+
"index": 0,
113+
"message": {"role": "assistant", "content": "Hello world!"},
114+
}
115+
],
116+
"created": 1234567890,
117+
"usage": {"prompt_tokens": 57, "completion_tokens": 40, "total_tokens": 97},
118+
}
120119
)
121120

122-
mock_chat_completion_create.return_value = completion
121+
mock_chat_completion_create.return_value = mock_response
123122
yield mock_chat_completion_create
124123

125124

@@ -857,7 +856,7 @@ def mock_reasoning_response():
857856
"""Mock that returns a raw-response-like object with reasoning array content."""
858857
with patch("openai.resources.chat.completions.Completions.create") as mock_create:
859858
mock_response = type("MockRawResponse", (), {})()
860-
mock_response.json = lambda: json.dumps(
859+
mock_response.text = json.dumps(
861860
{
862861
"id": "test-reasoning",
863862
"model": "mistral-small-latest",

integrations/mistral/tests/test_mistral_chat_generator_async.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import json
22
import logging
33
import os
4-
from datetime import datetime
54
from unittest.mock import AsyncMock, patch
65

76
import pytest
8-
import pytz
97
from haystack.dataclasses import (
108
ChatMessage,
119
ChatRole,
1210
StreamingChunk,
1311
)
1412
from haystack.tools import Tool
1513
from openai import AsyncOpenAI
16-
from openai.types.chat import ChatCompletion, ChatCompletionMessage
17-
from openai.types.chat.chat_completion import Choice
1814

1915
from haystack_integrations.components.generators.mistral.chat.chat_generator import (
2016
MistralChatGenerator,
@@ -60,27 +56,24 @@ def mock_async_chat_completion():
6056
"openai.resources.chat.completions.AsyncCompletions.create",
6157
new_callable=AsyncMock,
6258
) as mock_chat_completion_create:
63-
completion = ChatCompletion(
64-
id="foo",
65-
model="mistral-small-latest",
66-
object="chat.completion",
67-
choices=[
68-
Choice(
69-
finish_reason="stop",
70-
logprobs=None,
71-
index=0,
72-
message=ChatCompletionMessage(content="Hello world!", role="assistant"),
73-
)
74-
],
75-
created=int(datetime.now(tz=pytz.timezone("UTC")).timestamp()),
76-
usage={
77-
"prompt_tokens": 57,
78-
"completion_tokens": 40,
79-
"total_tokens": 97,
80-
},
59+
mock_response = type("MockRawResponse", (), {})()
60+
mock_response.text = json.dumps(
61+
{
62+
"id": "foo",
63+
"model": "mistral-small-latest",
64+
"object": "chat.completion",
65+
"choices": [
66+
{
67+
"finish_reason": "stop",
68+
"index": 0,
69+
"message": {"role": "assistant", "content": "Hello world!"},
70+
}
71+
],
72+
"created": 1234567890,
73+
"usage": {"prompt_tokens": 57, "completion_tokens": 40, "total_tokens": 97},
74+
}
8175
)
82-
# For async mocks, the return value should be awaitable
83-
mock_chat_completion_create.return_value = completion
76+
mock_chat_completion_create.return_value = mock_response
8477
yield mock_chat_completion_create
8578

8679

@@ -270,7 +263,7 @@ async def test_run_async_with_reasoning(self, chat_messages, monkeypatch):
270263
monkeypatch.setenv("MISTRAL_API_KEY", "fake-api-key")
271264

272265
mock_response = type("MockRawResponse", (), {})()
273-
mock_response.json = lambda: json.dumps(
266+
mock_response.text = json.dumps(
274267
{
275268
"id": "test-reasoning-async",
276269
"model": "mistral-small-latest",

0 commit comments

Comments
 (0)