From c27271f3ae50f47f5225885fff76188f468220d6 Mon Sep 17 00:00:00 2001 From: ccurme Date: Thu, 9 Oct 2025 09:15:27 -0400 Subject: [PATCH] fix(openai): update file index key name (#33350) --- .../langchain_openai/chat_models/_compat.py | 10 ++-- .../langchain_openai/chat_models/base.py | 31 ++++++++++-- .../tests/cassettes/test_file_search.yaml.gz | Bin 0 -> 26640 bytes .../chat_models/test_responses_api.py | 46 ++++++++++++++++-- 4 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 libs/partners/openai/tests/cassettes/test_file_search.yaml.gz diff --git a/libs/partners/openai/langchain_openai/chat_models/_compat.py b/libs/partners/openai/langchain_openai/chat_models/_compat.py index 6ff3b932b9b..0c664ac0bad 100644 --- a/libs/partners/openai/langchain_openai/chat_models/_compat.py +++ b/libs/partners/openai/langchain_openai/chat_models/_compat.py @@ -187,15 +187,19 @@ def _convert_annotation_from_v1(annotation: types.Annotation) -> dict[str, Any]: new_ann["title"] = annotation["title"] new_ann["type"] = "url_citation" new_ann["url"] = annotation["url"] + + if extra_fields := annotation.get("extras"): + new_ann.update(dict(extra_fields.items())) else: # Document citation new_ann["type"] = "file_citation" + + if extra_fields := annotation.get("extras"): + new_ann.update(dict(extra_fields.items())) + if "title" in annotation: new_ann["filename"] = annotation["title"] - if extra_fields := annotation.get("extras"): - new_ann.update(dict(extra_fields.items())) - return new_ann if annotation["type"] == "non_standard_annotation": diff --git a/libs/partners/openai/langchain_openai/chat_models/base.py b/libs/partners/openai/langchain_openai/chat_models/base.py index 2d4cd988bbf..8fffd34e6ae 100644 --- a/libs/partners/openai/langchain_openai/chat_models/base.py +++ b/libs/partners/openai/langchain_openai/chat_models/base.py @@ -3589,6 +3589,24 @@ def _construct_responses_api_payload( return payload +def _format_annotation_to_lc(annotation: dict[str, Any]) -> dict[str, Any]: + # langchain-core reserves the `"index"` key for streaming aggregation. + # Here we re-name. + if annotation.get("type") == "file_citation" and "index" in annotation: + new_annotation = annotation.copy() + new_annotation["file_index"] = new_annotation.pop("index") + return new_annotation + return annotation + + +def _format_annotation_from_lc(annotation: dict[str, Any]) -> dict[str, Any]: + if annotation.get("type") == "file_citation" and "file_index" in annotation: + new_annotation = annotation.copy() + new_annotation["index"] = new_annotation.pop("file_index") + return new_annotation + return annotation + + def _convert_chat_completions_blocks_to_responses( block: dict[str, Any], ) -> dict[str, Any]: @@ -3775,7 +3793,10 @@ def _construct_responses_api_input(messages: Sequence[BaseMessage]) -> list: new_block = { "type": "output_text", "text": block["text"], - "annotations": block.get("annotations") or [], + "annotations": [ + _format_annotation_from_lc(annotation) + for annotation in block.get("annotations") or [] + ], } elif block_type == "refusal": new_block = { @@ -3951,7 +3972,7 @@ def _construct_lc_result_from_responses_api( "type": "text", "text": content.text, "annotations": [ - annotation.model_dump() + _format_annotation_to_lc(annotation.model_dump()) for annotation in content.annotations ] if isinstance(content.annotations, list) @@ -4142,7 +4163,11 @@ def _convert_responses_chunk_to_generation_chunk( annotation = chunk.annotation.model_dump(exclude_none=True, mode="json") content.append( - {"type": "text", "annotations": [annotation], "index": current_index} + { + "type": "text", + "annotations": [_format_annotation_to_lc(annotation)], + "index": current_index, + } ) elif chunk.type == "response.output_text.done": content.append({"type": "text", "id": chunk.item_id, "index": current_index}) diff --git a/libs/partners/openai/tests/cassettes/test_file_search.yaml.gz b/libs/partners/openai/tests/cassettes/test_file_search.yaml.gz new file mode 100644 index 0000000000000000000000000000000000000000..4c896356533d8e5b6ac63cc4568b6bafb40bc032 GIT binary patch literal 26640 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 literal 0 HcmV?d00001 diff --git a/libs/partners/openai/tests/integration_tests/chat_models/test_responses_api.py b/libs/partners/openai/tests/integration_tests/chat_models/test_responses_api.py index a7df4ef7ef7..8310adc6ed8 100644 --- a/libs/partners/openai/tests/integration_tests/chat_models/test_responses_api.py +++ b/libs/partners/openai/tests/integration_tests/chat_models/test_responses_api.py @@ -34,7 +34,7 @@ def _check_response(response: BaseMessage | None) -> None: if annotation["type"] == "file_citation": assert all( key in annotation - for key in ["file_id", "filename", "index", "type"] + for key in ["file_id", "filename", "file_index", "type"] ) elif annotation["type"] == "web_search": assert all( @@ -374,9 +374,17 @@ def test_computer_calls() -> None: assert response.additional_kwargs["tool_outputs"] -def test_file_search() -> None: - pytest.skip() # TODO: set up infra - llm = ChatOpenAI(model=MODEL_NAME, use_responses_api=True) +@pytest.mark.default_cassette("test_file_search.yaml.gz") +@pytest.mark.vcr +@pytest.mark.parametrize("output_version", ["responses/v1", "v1"]) +def test_file_search( + output_version: Literal["responses/v1", "v1"], +) -> None: + llm = ChatOpenAI( + model=MODEL_NAME, + use_responses_api=True, + output_version=output_version, + ) tool = { "type": "file_search", "vector_store_ids": [os.environ["OPENAI_VECTOR_STORE_ID"]], @@ -386,16 +394,44 @@ def test_file_search() -> None: response = llm.invoke([input_message], tools=[tool]) _check_response(response) - full: BaseMessageChunk | None = None + if output_version == "v1": + assert [block["type"] for block in response.content] == [ # type: ignore[index] + "server_tool_call", + "server_tool_result", + "text", + ] + else: + assert [block["type"] for block in response.content] == [ # type: ignore[index] + "file_search_call", + "text", + ] + + full: AIMessageChunk | None = None for chunk in llm.stream([input_message], tools=[tool]): assert isinstance(chunk, AIMessageChunk) full = chunk if full is None else full + chunk assert isinstance(full, AIMessageChunk) _check_response(full) + if output_version == "v1": + assert [block["type"] for block in full.content] == [ # type: ignore[index] + "server_tool_call", + "server_tool_result", + "text", + ] + else: + assert [block["type"] for block in full.content] == ["file_search_call", "text"] # type: ignore[index] + next_message = {"role": "user", "content": "Thank you."} _ = llm.invoke([input_message, full, next_message]) + for message in [response, full]: + assert [block["type"] for block in message.content_blocks] == [ + "server_tool_call", + "server_tool_result", + "text", + ] + @pytest.mark.default_cassette("test_stream_reasoning_summary.yaml.gz") @pytest.mark.vcr