Submission checklist
Package (Required)
Related Issues / PRs
Reproduction Steps / Example Code (Python)
from langchain_openai import ChatOpenAI
# Caller-owned options dict, passed by reference via model_kwargs.
my_text_opts = {"verbosity": "low"}
llm = ChatOpenAI(
model="gpt-4.1",
api_key="test",
use_responses_api=True,
model_kwargs={"text": my_text_opts},
)
print("caller dict BEFORE any call: ", my_text_opts)
# Call 1: a JSON mode / structured output request (what with_structured_output
# with method="json_mode" binds: response_format={"type": "json_object"}).
# Offline: only builds the request payload, never touches the network.
payload1 = llm._get_request_payload(
"give me json", response_format={"type": "json_object"}
)
print("payload1['text'] (expected format): ", payload1["text"])
print("caller dict AFTER structured call: ", my_text_opts)
print("llm.model_kwargs AFTER: ", llm.model_kwargs)
# Call 2: a PLAIN call, no response_format anywhere.
payload2 = llm._get_request_payload("just chat normally")
print("payload2['text'] (plain call!): ", payload2["text"])
assert "format" not in my_text_opts, "caller's dict was mutated in place"
Error Message and Stack Trace (if applicable)
Observed output (langchain-openai 1.3.5 and current master):
caller dict BEFORE any call: {'verbosity': 'low'}
payload1['text'] (expected format): {'verbosity': 'low', 'format': {'type': 'json_object'}}
caller dict AFTER structured call: {'verbosity': 'low', 'format': {'type': 'json_object'}}
llm.model_kwargs AFTER: {'text': {'verbosity': 'low', 'format': {'type': 'json_object'}}}
payload2['text'] (plain call!): {'verbosity': 'low', 'format': {'type': 'json_object'}}
AssertionError: caller's dict was mutated in place
Description
With use_responses_api=True, _construct_responses_api_payload writes the response format and verbosity straight into payload["text"]:
payload["text"]["format"] = {"type": "json_object"} (JSON mode)
payload["text"]["format"] = format_value (json_schema)
payload["text"]["verbosity"] = verbosity
(libs/partners/openai/langchain_openai/chat_models/base.py, around lines 4356-4383 on current master.)
The problem is that payload is built by shallow-merging self.model_kwargs (and per-call kwargs), so payload["text"] is the very same dict object the caller passed as model_kwargs={"text": {...}}. Those writes therefore land in the instance's model_kwargs and in the caller's own dict.
What I expect: request payload construction should not modify the model instance or the caller's inputs. Each call's text.format should apply to that call only.
What happens instead: after one JSON mode or structured output call, model_kwargs["text"] permanently contains format: {"type": "json_object"}, so every later plain call on the same instance is silently forced into JSON mode (see payload2 in the repro, which never asked for any response format). The caller's dict is also corrupted, so sharing one options dict across two ChatOpenAI instances spreads the pollution.
The fix looks small: copy the text dict once during payload construction before writing into it, mirroring what was done for the same bug class in langchain-perplexity (#38840). I have the fix and a regression test ready. I'd like to work on this, could you assign it to me?
System Info
System Information
OS: Windows
OS Version: 10.0.26200
Python Version: 3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)]
Package Information
langchain_core: 1.4.9
langchain: 1.3.13
langsmith: 0.10.5
langchain_openai: 1.3.5
langgraph_sdk: 0.4.2
Also reproduced against current master (commit 7bf8fe2), where the same in-place writes are present in _construct_responses_api_payload.
Submission checklist
Package (Required)
Related Issues / PRs
extra_bodydict in place — per-call params leak into the instance and all later requests #38840 reports the same bug class (in-place mutation of a caller dict on the Responses route) in langchain-perplexity.Reproduction Steps / Example Code (Python)
Error Message and Stack Trace (if applicable)
Description
With use_responses_api=True,
_construct_responses_api_payloadwrites the response format and verbosity straight intopayload["text"]:payload["text"]["format"] = {"type": "json_object"}(JSON mode)payload["text"]["format"] = format_value(json_schema)payload["text"]["verbosity"] = verbosity(
libs/partners/openai/langchain_openai/chat_models/base.py, around lines 4356-4383 on current master.)The problem is that
payloadis built by shallow-mergingself.model_kwargs(and per-call kwargs), sopayload["text"]is the very same dict object the caller passed asmodel_kwargs={"text": {...}}. Those writes therefore land in the instance'smodel_kwargsand in the caller's own dict.What I expect: request payload construction should not modify the model instance or the caller's inputs. Each call's
text.formatshould apply to that call only.What happens instead: after one JSON mode or structured output call,
model_kwargs["text"]permanently containsformat: {"type": "json_object"}, so every later plain call on the same instance is silently forced into JSON mode (see payload2 in the repro, which never asked for any response format). The caller's dict is also corrupted, so sharing one options dict across two ChatOpenAI instances spreads the pollution.The fix looks small: copy the
textdict once during payload construction before writing into it, mirroring what was done for the same bug class in langchain-perplexity (#38840). I have the fix and a regression test ready. I'd like to work on this, could you assign it to me?System Info
System Information
Package Information
Also reproduced against current master (commit 7bf8fe2), where the same in-place writes are present in _construct_responses_api_payload.