Skip to content

Commit a5a327a

Browse files
committed
fix #20333 when store=False
1 parent 74b1101 commit a5a327a

File tree

4 files changed

+111
-91
lines changed

4 files changed

+111
-91
lines changed

llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/responses.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ def _chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse:
536536
messages,
537537
model=self.model,
538538
is_responses_api=True,
539+
kwargs_dict=kwargs_dict,
539540
)
540541

541542
response: Response = self._client.responses.create(

llama-index-integrations/llms/llama-index-llms-openai/llama_index/llms/openai/utils.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ def to_openai_message_dict(
352352
message: ChatMessage,
353353
drop_none: bool = False,
354354
model: Optional[str] = None,
355+
kwargs: Optional[Dict[str, Any]] = None,
355356
) -> ChatCompletionMessageParam:
356357
"""Convert a ChatMessage to an OpenAI message dict."""
357358
content = []
@@ -533,6 +534,7 @@ def to_openai_responses_message_dict(
533534
message: ChatMessage,
534535
drop_none: bool = False,
535536
model: Optional[str] = None,
537+
kwargs: Optional[Dict[str, Any]] = None,
536538
) -> Union[str, Dict[str, Any], List[Dict[str, Any]]]:
537539
"""Convert a ChatMessage to an OpenAI message dict."""
538540
content = []
@@ -582,10 +584,21 @@ def to_openai_responses_message_dict(
582584
}
583585
)
584586

585-
# Omit reasoning items from the conversation history
587+
# Omit reasoning items when store is set to False
586588
elif isinstance(block, ThinkingBlock):
587-
continue
588-
589+
if kwargs is None:
590+
continue
591+
elif kwargs["store"]:
592+
if block.content and "id" in block.additional_information:
593+
reasoning.append(
594+
{
595+
"type": "reasoning",
596+
"id": block.additional_information["id"],
597+
"summary": [
598+
{"type": "summary_text", "text": block.content or ""}
599+
],
600+
}
601+
)
589602
elif isinstance(block, ToolCallBlock):
590603
tool_calls.extend(
591604
[
@@ -692,6 +705,7 @@ def to_openai_message_dicts(
692705
drop_none: bool = False,
693706
model: Optional[str] = None,
694707
is_responses_api: bool = False,
708+
kwargs: Optional[Dict[str, Any]] = None,
695709
) -> Union[List[ChatCompletionMessageParam], str]:
696710
"""Convert generic messages to OpenAI message dicts."""
697711
if is_responses_api:
@@ -701,6 +715,7 @@ def to_openai_message_dicts(
701715
message,
702716
drop_none=drop_none,
703717
model="o3-mini", # hardcode to ensure developer messages are used
718+
kwargs=kwargs,
704719
)
705720
if isinstance(message_dicts, list):
706721
final_message_dicts.extend(message_dicts)
@@ -712,9 +727,8 @@ def to_openai_message_dicts(
712727
# If there is only one message, and it is a user message, return the content string directly
713728
if (
714729
len(final_message_dicts) == 1
715-
and isinstance(final_message_dicts[0], dict)
716-
and final_message_dicts[0].get("role") == "user"
717-
and isinstance(final_message_dicts[0].get("content"), str)
730+
and final_message_dicts[0]["role"] == "user"
731+
and isinstance(final_message_dicts[0]["content"], str)
718732
):
719733
return final_message_dicts[0]["content"]
720734

@@ -725,6 +739,7 @@ def to_openai_message_dicts(
725739
message,
726740
drop_none=drop_none,
727741
model=model,
742+
kwargs=kwargs,
728743
)
729744
for message in messages
730745
]

llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_responses.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,95 @@ def test_messages_to_openai_responses_messages():
826826
assert openai_messages[6]["content"][0]["text"] == messages[6].blocks[1].text
827827

828828

829+
def test_messages_to_openai_responses_messages_with_store():
830+
messages = [
831+
ChatMessage(role=MessageRole.SYSTEM, content="You are a helpful assistant."),
832+
ChatMessage(role=MessageRole.USER, content="What is the capital of France?"),
833+
ChatMessage(
834+
role=MessageRole.ASSISTANT,
835+
blocks=[
836+
ToolCallBlock(
837+
tool_call_id="1",
838+
tool_name="get_capital_city_by_state",
839+
tool_kwargs="{'state': 'France'}",
840+
)
841+
],
842+
),
843+
ChatMessage(role=MessageRole.ASSISTANT, content="Paris"),
844+
ChatMessage(role=MessageRole.USER, content="What is the capital of Germany?"),
845+
ChatMessage(
846+
role=MessageRole.ASSISTANT,
847+
blocks=[
848+
ToolCallBlock(
849+
tool_call_id="2",
850+
tool_name="get_capital_city_by_state",
851+
tool_kwargs="{'state': 'Germany'}",
852+
)
853+
],
854+
),
855+
ChatMessage(
856+
role=MessageRole.ASSISTANT,
857+
blocks=[
858+
ThinkingBlock(
859+
content="The user is asking a simple question related to the capital of Germany, I should answer it concisely",
860+
additional_information={"id": "123456789"},
861+
),
862+
TextBlock(text="Berlin"),
863+
],
864+
),
865+
]
866+
867+
kwargs = {
868+
"model": "fake-model",
869+
"include": None,
870+
"instructions": None,
871+
"max_output_tokens": 100,
872+
"metadata": {},
873+
"previous_response_id": None,
874+
"store": True,
875+
"temperature": 0.0,
876+
"tools": [],
877+
"top_p": 1.0,
878+
"truncation": None,
879+
"user": None,
880+
}
881+
882+
openai_messages = to_openai_message_dicts(
883+
messages, is_responses_api=True, kwargs=kwargs
884+
)
885+
assert len(openai_messages) == 8
886+
assert openai_messages[0]["role"] == "developer"
887+
assert openai_messages[0]["content"] == "You are a helpful assistant."
888+
assert openai_messages[1]["role"] == "user"
889+
assert openai_messages[1]["content"] == "What is the capital of France?"
890+
assert openai_messages[2] == {
891+
"type": "function_call",
892+
"arguments": "{'state': 'France'}",
893+
"call_id": "1",
894+
"name": "get_capital_city_by_state",
895+
}
896+
assert openai_messages[3]["role"] == "assistant"
897+
assert openai_messages[3]["content"] == "Paris"
898+
assert openai_messages[4]["role"] == "user"
899+
assert openai_messages[4]["content"] == "What is the capital of Germany?"
900+
assert openai_messages[5] == {
901+
"type": "function_call",
902+
"arguments": "{'state': 'Germany'}",
903+
"call_id": "2",
904+
"name": "get_capital_city_by_state",
905+
}
906+
907+
assert openai_messages[6]["type"] == "reasoning"
908+
assert (
909+
openai_messages[6]["id"] == messages[6].blocks[0].additional_information["id"]
910+
)
911+
assert openai_messages[6]["summary"][0]["text"] == messages[6].blocks[0].content
912+
913+
assert openai_messages[7]["role"] == "assistant"
914+
assert len(openai_messages[7]["content"]) == 1
915+
assert openai_messages[7]["content"][0]["text"] == messages[6].blocks[1].text
916+
917+
829918
@pytest.fixture()
830919
def response_output() -> List[ResponseOutputItem]:
831920
return [

llama-index-integrations/llms/llama-index-llms-openai/tests/test_openai_utils.py

Lines changed: 0 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
MessageRole,
2222
TextBlock,
2323
ToolCallBlock,
24-
ThinkingBlock,
2524
)
2625
from llama_index.core.bridge.pydantic import BaseModel
2726
from llama_index.llms.openai import OpenAI
@@ -502,87 +501,3 @@ def test_gpt_5_chat_model_support() -> None:
502501
)
503502

504503
assert model_name in CHAT_MODELS, f"{model_name} should be in CHAT_MODELS"
505-
506-
507-
def test_to_openai_message_dicts_responses_api_drops_reasoning_before_tool_call() -> (
508-
None
509-
):
510-
"""
511-
Test that 'reasoning items' are not included when converting a ChatMessage to an OpenAI message dict.
512-
(they are internal model thinking and should not be included in conversational history)
513-
"""
514-
msg = ChatMessage(
515-
role=MessageRole.ASSISTANT,
516-
blocks=[
517-
ThinkingBlock(
518-
content="I will call the tool now.",
519-
additional_information={"id": "rs_dummy_reasoning_id"},
520-
),
521-
ToolCallBlock(
522-
block_type="tool_call",
523-
tool_call_id="call_123",
524-
tool_name="search_hotels",
525-
tool_kwargs='{"location":"Rome","max_price":200}',
526-
),
527-
],
528-
)
529-
530-
out = to_openai_message_dicts([msg], is_responses_api=True)
531-
532-
assert isinstance(out, list)
533-
534-
# Must not include any reasoning items in the serialized input
535-
assert not any(
536-
isinstance(item, dict) and item.get("type") == "reasoning" for item in out
537-
)
538-
539-
# Must still include the tool/function call item
540-
assert any(
541-
isinstance(item, dict)
542-
and item.get("type") == "function_call"
543-
and item.get("name") == "search_hotels"
544-
and item.get("call_id") == "call_123"
545-
for item in out
546-
)
547-
548-
549-
def test_to_openai_message_dicts_responses_api_drops_reasoning_before_tool_call() -> (
550-
None
551-
):
552-
"""
553-
Test that 'reasoning items' are not included when converting a ChatMessage to an OpenAI message dict.
554-
(they are internal model thinking and should not be included in conversational history)
555-
"""
556-
msg = ChatMessage(
557-
role=MessageRole.ASSISTANT,
558-
blocks=[
559-
ThinkingBlock(
560-
content="I will call the tool now.",
561-
additional_information={"id": "rs_dummy_reasoning_id"},
562-
),
563-
ToolCallBlock(
564-
block_type="tool_call",
565-
tool_call_id="call_123",
566-
tool_name="search_hotels",
567-
tool_kwargs='{"location":"Rome","max_price":200}',
568-
),
569-
],
570-
)
571-
572-
out = to_openai_message_dicts([msg], is_responses_api=True)
573-
574-
assert isinstance(out, list)
575-
576-
# Must not include any reasoning items in the serialized input
577-
assert not any(
578-
isinstance(item, dict) and item.get("type") == "reasoning" for item in out
579-
)
580-
581-
# Must still include the tool/function call item
582-
assert any(
583-
isinstance(item, dict)
584-
and item.get("type") == "function_call"
585-
and item.get("name") == "search_hotels"
586-
and item.get("call_id") == "call_123"
587-
for item in out
588-
)

0 commit comments

Comments
 (0)