Missing `structuredResponse` when retrieving agent state via `.getState` & other questions

Good to know

This is the package version i’m using:

Link to reproduction: errorpad/repro-langgraphjs-createagent-state at main · tanshunyuan/errorpad · GitHub

The Issue

When using createAgent with a structured responseFormat, the agent correctly executes the tools and generates the structured response.

However, the final structured output is not available in the graph state via .getState(). ( CompiledGraph | LangGraph.js API Reference )

The agent is created with a Zod schema in agent.ts:

const responseSchema = z.object({
  status: z.enum(["completed", "error"]),
  summary: z.string().describe("A summary of the findings"),
});

export const agent = createAgent({
  model: llm,
  tools: [mockResearchTool],
  systemPrompt: new SystemMessage(
    "You are a research assistant. Use the mock_research tool to gather data, then return a structured response.",
  ),
  responseFormat: responseSchema,
  checkpointer: new MemorySaver(),
});

However, when we use .getState() in index.ts to inspect the result:

const finalState = await agent.getState(config);
const values = finalState.values;
const structuredResponse = values.structuredResponse;

Expected Behavior

I expected values.structuredResponse to contain the structured object matching the responseSchema.

// Expected Output:
// {
//   status: "completed",
//   summary: "Mock research data for 'minimal reproduction' has been retrieved..."
// }

Actual Behavior

Instead, the structuredResponse key doesn’t exist, and I have to manually dig through the values.messages array to find it.

The structured output is buried inside the content of a ToolMessage (specifically the one with name: “extract-2”), which is the second last message.

Note: To see the full raw log of the finalState object, refer to index.ts starting at line 30.

values: {
  messages: [
    // trimmed object
    ToolMessage {
      "id": "8fa99e6b-bf2f-4e79-9253-102218c40651",
      "content": "{\"summary\":\"Mock research data for 'minimal reproduction' has been retrieved, containing useful insights.\",\"status\":\"completed\"}",
      "name": "extract-2",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_call_id": "c4085f98-c686-451d-bffa-bf1b2118225f"
    },
    AIMessage {
      "id": "8e3fa287-c98e-4c45-a47e-a9f864c0059b",
      "content": "Returning structured response: {\"summary\":\"Mock research data for 'minimal reproduction' has been retrieved, containing useful insights.\",\"status\":\"completed\"}",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_calls": [],
      "invalid_tool_calls": []
    }
  ]
}

:thinking: Other Questions

While working on this reproduction, I noticed a few things about the LangGraph.js API that raised questions:

  1. How to properly type .getState()?
    TypeScript types the values as never or any, forcing manual casts. Can the state schema be inferred automatically from createAgent?

  2. Is .getState() an internal API?
    If getState is meant for internal use only, what is the recommended public API for retrieving the final state after streaming?

  3. Is .streamEvents() an internal API?
    streamEvents is used UI updates. If it’s internal, what is the stable alternative for streaming tokens and tool events?

Here’s an answer I got from the slack community

You’re not doing anything wrong here. With createAgent(), structured output is not written into state.values. When you pass responseFormat, LangGraph just forces the model to respond via a tool call and stores the parsed result as a ToolMessage in the message history. That’s why values.structuredResponse is undefined and you only see the data when you inspect the last tool message.

The reason .getState() shows values as never is simply because createAgent() builds a dynamic, prebuilt graph and doesn’t expose a typed state schema. TypeScript can’t infer what’s inside, so the typing collapses. The method itself is public and safe to use, but it’s meant more for inspection and debugging than for consuming outputs.
For actually reading structured output, .streamEvents() is stable and is the intended approach. If you really want something like values.structuredResponse, you’d need to build a custom graph and explicitly write that tool output into state yourself.