Checked other resources
Example Code
import { ChatOpenAI } from "@langchain/openai";
import { concat } from "@langchain/core/utils/stream";
const chunk = (delta, finish = null) =>
`data: ${JSON.stringify({
id: "c",
object: "chat.completion.chunk",
created: 1,
model: "m",
choices: [{ index: 0, delta, finish_reason: finish }],
})}\n\n`;
// The model was cut off mid-argument, so the stream ends on finish_reason "length".
const sse =
chunk({
role: "assistant",
tool_calls: [
{
index: 0,
id: "c1",
type: "function",
function: { name: "write", arguments: '{"path":"/etc/hosts","content":"line1' },
},
],
}) +
chunk({}, "length") +
"data: [DONE]\n\n";
const model = new ChatOpenAI({
model: "m",
apiKey: "x",
configuration: {
baseURL: "https://example.invalid/v1",
fetch: async () =>
new Response(sse, {
status: 200,
headers: { "content-type": "text/event-stream" },
}),
},
});
let message;
for await (const c of await model.stream("hi")) {
message = message ? concat(message, c) : c;
}
console.log("tool_calls ", JSON.stringify(message.tool_calls));
console.log("invalid_tool_calls", JSON.stringify(message.invalid_tool_calls));
console.log("finish_reason ", message.response_metadata.finish_reason);
Output:
tool_calls [{"name":"write","args":{"path":"/etc/hosts","content":"line1"},"id":"c1","type":"tool_call"}]
invalid_tool_calls []
finish_reason length
Error Message and Stack Trace (if applicable)
No error is raised.
Description
When a stream is cut short mid-argument, the partial JSON is repaired and the result lands in tool_calls as a complete call, with invalid_tool_calls empty. In the example above "content":"line1 becomes content: "line1", which is byte-identical to what a legitimately finished call would produce. A tool bound to that message would write a truncated file with nothing to indicate the input was cut off; finish_reason: "length" is the only signal, and it is on the message rather than the call.
The repair is only wrong at the end of the stream. Partial parsing is what makes tool_call_chunks useful while arguments are still arriving, but once the stream terminates the repaired value should not be presented as a finished call. Other truncation points degrade differently and just as quietly: {"content": and {"cont both yield {}, and {"cfg":{"a":1,"b": yields {"cfg":{"a":1}}, silently dropping a key. For contrast, arguments that cannot be repaired at all are handled correctly — {"a": } and hello world both go to invalid_tool_calls with "Malformed args." — so the classification path exists and truncated input simply never reaches it.
System Info
@langchain/openai 1.5.8
@langchain/core 1.2.9
node v20.19.5
linux x64
Checked other resources
Example Code
Output:
Error Message and Stack Trace (if applicable)
No error is raised.
Description
When a stream is cut short mid-argument, the partial JSON is repaired and the result lands in
tool_callsas a complete call, withinvalid_tool_callsempty. In the example above"content":"line1becomescontent: "line1", which is byte-identical to what a legitimately finished call would produce. A tool bound to that message would write a truncated file with nothing to indicate the input was cut off;finish_reason: "length"is the only signal, and it is on the message rather than the call.The repair is only wrong at the end of the stream. Partial parsing is what makes
tool_call_chunksuseful while arguments are still arriving, but once the stream terminates the repaired value should not be presented as a finished call. Other truncation points degrade differently and just as quietly:{"content":and{"contboth yield{}, and{"cfg":{"a":1,"b":yields{"cfg":{"a":1}}, silently dropping a key. For contrast, arguments that cannot be repaired at all are handled correctly —{"a": }andhello worldboth go toinvalid_tool_callswith"Malformed args."— so the classification path exists and truncated input simply never reaches it.System Info