Trouble Disabling Streamed Chunks

I can’t seem to disable the stream from agents. I’ve tried different combinations of streaming: false, disableStreaming: true, and tags: [“nostream”]. The final messages of this agent are not saved to the messages state, since I return an empty state update, but the message chunks are still being streamed to the frontend. This leads to a weird chat behavior where messages appear for a second (as chunks) and then disappear once the stream finishes.

// graph.ts
import {
  END,
MessagesAnnotation,
START,
StateGraph,
} from "@langchain/langgraph";
import { autonamer } from "./agents/autonamer.js";
import { supervisor } from "./agents/supervisor.js";

const workflow = new StateGraph(MessagesAnnotation)
  // Define nodes
  .addNode("supervisor", supervisor)
  .addNode("autonamer", autonamer)
  // Define edges
  .addEdge(START, "supervisor")
  .addEdge("supervisor", "autonamer")
  .addEdge("autonamer", END);

export const graph = workflow.compile();
// agents/autonamer.ts
import { RunnableConfig } from "@langchain/core/runnables";
import { Command, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { createAgent } from "langchain";
import { z } from "zod";
import chatServiceClient from "../clients/chatServiceClient.js";
import env from "../constants/env.js";
import { ensureConfiguration } from "../state/configuration.js";
import { State } from "../state/state.js";

export const SYSTEM_PROMPT = `
You are an autonamer AI that generates short, descriptive titles for chat threads based on their content.
Your titles should be concise (3-5 words), relevant to the main topic of the conversation, and engaging.
Focus on capturing the essence of the discussion in a way that is easy to understand at a glance.
Avoid generic titles; instead, aim for specificity and clarity.
`;

const ResponseFormat = z.object({
  name: z.string().describe("The name of the generated title"),
});

const autonamerAgent = createAgent({
  systemPrompt: SYSTEM_PROMPT,
  name: "Autonamer",
  model: new ChatOpenAI({
    model: "gpt-4.1-mini",
    openAIApiKey: env.OPENAI_API_KEY,
    temperature: 0.7,
    streaming: false,
    disableStreaming: true,
    tags: ["nostream"],
  }),
  responseFormat: ResponseFormat,
});


export async function autonamer(
  state: State,
  config: RunnableConfig,
): Promise<StateUpdate> {

  const configuration = ensureConfiguration(config);
  const messages = state.messages;

  // Check if only one user message exists
  const userMessages = messages.filter((m) => m.type === "human");

  if (userMessages.length % 5 === 1) {
    const response = await autonamerAgent.invoke({
      messages: messages,
    });

    console.log("Autonamer generated name:", response.structuredResponse.name);
    console.log("Full response:", response);

    let thread = await chatServiceClient.threads.get(configuration.thread_id);
    await chatServiceClient.threads.update(configuration.thread_id, {
      ...thread,
      metadata: { ...thread.metadata, name: response.structuredResponse.name },
    });

    console.log("Thread name updated successfully.");
  }

  return {};
}

Here’s the package versions I’m using:

"@langchain/core": "^1.0.4",
"@langchain/langgraph": "^1.0.2",
"@langchain/langgraph-checkpoint-postgres": "^1.0.0",
"@langchain/mcp-adapters": "^1.0.0",
"@langchain/openai": "^1.1.0",
"@langchain/tavily": "^1.0.1",
"langchain": "^1.0.4",

Client Side

On the client-side I’m using useStream. I’ve tried changing the streamMode, but haven’t seen any changes.

const thread = useStream<{ messages: Message[] }>({
  apiUrl: import.meta.env.VITE_CHAT_SERVICE_URL,
  defaultHeaders: {
    Authorization: `Bearer ${token}`,
  },
  assistantId: "assistant",
  threadId,
});


const sendPrompt = async (message: string) => {
  thread.submit(
    { messages: [{ type: "human", content: message }] },
  );
};

I’ve considered filtering messages on the frontend, but I can’t find any distinguishing key to use. I’ve named each of the agents, but the name doesn’t appear on the message chunks.

[
    {
        "content": "Hello! What's your name?",
        "additional_kwargs": {},
        "response_metadata": {},
        "id": "d9b2a6d1-6f65-4f96-8c8a-9a13a9fc95f9",
        "type": "human"
    },
    {
        "content": "Hello! My name is Helpy. I’m here to help. How can I help?",
        "additional_kwargs": {},
        "tool_call_chunks": [],
        "id": "chatcmpl-CleZLNJvsMp2BLMjNLyoIqAKtchMQ",
        "tool_calls": [],
        "invalid_tool_calls": [],
        "name": "Helpy",
        "type": "ai"
    },
    {
        "id": "chatcmpl-CleZMJVPnpGqg7gkRry9Emsg1gmVH",
        "content": "{\"name\":\"Introduction and Greeting\"}",
        "additional_kwargs": {},
        "type": "ai",
        "tool_calls": [],
        "invalid_tool_calls": [],
        "tool_call_chunks": []
    }
]

I wonder if you could just tag the autonamer’s output in response.additional_kwargs with
source_agent: “autonamer” and filter it on your frontend?

 response.additional_kwargs = {
 ...response.additional_kwargs,
source_agent: "autonamer",
 };

Thanks for the suggestion! However, the trouble I run into with this is that these additional_kwargs are only being updated for the final/complete message. additional_kwargs is empty for all of the intermediate streamed chunks.

For example, I’ll get a series of updates that look like this:

{
    "id": "chatcmpl-CleZMJVPnpGqg7gkRry9Emsg1gmVH",
    "content": "{\"name\":\"I",
    "additional_kwargs": {},
    "type": "ai",
    "tool_calls": [],
    "invalid_tool_calls": [],
    "tool_call_chunks": []
}
{
    "id": "chatcmpl-CleZMJVPnpGqg7gkRry9Emsg1gmVH",
    "content": "{\"name\":\"Intro"}",
    "additional_kwargs": {},
    "type": "ai",
    "tool_calls": [],
    "invalid_tool_calls": [],
    "tool_call_chunks": []
}
{
    "id": "chatcmpl-CleZMJVPnpGqg7gkRry9Emsg1gmVH",
    "content": "{\"name\":\"Introduction and"}",
    "additional_kwargs": {},
    "type": "ai",
    "tool_calls": [],
    "invalid_tool_calls": [],
    "tool_call_chunks": []
}
{
    "id": "chatcmpl-CleZMJVPnpGqg7gkRry9Emsg1gmVH",
    "content": "{\"name\":\"Introduction and Greeting\"}",
    "additional_kwargs": {},
    "type": "ai",
    "tool_calls": [],
    "invalid_tool_calls": [],
    "tool_call_chunks": []
}

And then only in the final message, the additional_kwargs are update:

{
    "id": "chatcmpl-CleZMJVPnpGqg7gkRry9Emsg1gmVH",
    "content": "{\"name\":\"Introduction and Greeting\"}",
    "additional_kwargs": {
        "source_agent": "autonamer",
    },
    "type": "ai",
    "tool_calls": [],
    "invalid_tool_calls": [],
    "tool_call_chunks": []
}