惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Blog of Author Tim Ferriss
S
Securelist
D
Docker
The Register - Security
The Register - Security
GbyAI
GbyAI
Recorded Future
Recorded Future
Engineering at Meta
Engineering at Meta
Stack Overflow Blog
Stack Overflow Blog
云风的 BLOG
云风的 BLOG
P
Proofpoint News Feed
罗磊的独立博客
博客园 - 【当耐特】
F
Full Disclosure
WordPress大学
WordPress大学
腾讯CDC
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
D
DataBreaches.Net
SecWiki News
SecWiki News
L
Lohrmann on Cybersecurity
I
InfoQ
MyScale Blog
MyScale Blog
量子位
Cyberwarzone
Cyberwarzone
博客园 - 三生石上(FineUI控件)
The Hacker News
The Hacker News
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Jina AI
Jina AI
博客园_首页
H
Help Net Security
K
Kaspersky official blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Webroot Blog
Webroot Blog
Blog — PlanetScale
Blog — PlanetScale
V
Vulnerabilities – Threatpost
Y
Y Combinator Blog
The Cloudflare Blog
P
Proofpoint News Feed
V
Visual Studio Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tailwind CSS Blog
爱范儿
爱范儿
P
Privacy International News Feed
Security Archives - TechRepublic
Security Archives - TechRepublic
The GitHub Blog
The GitHub Blog
C
Cybersecurity and Infrastructure Security Agency CISA
B
Blog RSS Feed

LangChain Forum - Topics tagged intro-to-langgraph

Null-drift: A bare-metal O(1) Memory Store for continuous LangGraph agents Interrupt does not work correctly in LangGraph Pre-interrupt() code re-runs on resume — anti-pattern, or is there a sanctioned way to detect resume? Would pre-inference routing help long-context agent workflows? Improving citation accuracy and reducing hallucinations in custom Parent-Child RAG pipeline (Gemma3:4B + FAISS+BM25 + Cross-encoder reranker) WikipediaLoader endup in JSONDecodeError Using LangGraph interrupt for multi-step wizards with branching — right tool or wrong abstraction? Built NORNR for spend governance in agent workflows LangGraph + PostgreSQL: Chat history and summarization best practice Discussion about why LangGraph JS ToolNode doesn’t inject ToolRuntime.state like Python does, and what the correct workaround or intended design pattern is. No cost displayed in LangSmith when using LiteLLM + LangGraph LiteLLM Router in LangChain: Missing Model Name and Cost in LangSmith Traces I find langgraph chat in documents useful, how can I hook it up as mcp or skill or a tool for the Claude Code? Can I get User IDs and Bot IDs of bots used in Slack channels for personal projects [Docs/Cookbook] Request to add a design pattern for "Package-Skill" with internal retries Using the useStream frontend API with custom FastAPI backend When is it actually a failure? Diagnosing agent behavior beyond LangGraph traces Can the input and output of deep_agent's sub-agents be output in streaming mode? Complete context compression through middleware Feedback Wanted: My Structured Multi-Agent Research Assistant (Inspired by LangGraph Academy) Problems encountered when study is interrupted Can you provide a learning roadmap for LangChain and LangGraph suitable for beginners? What is the return type of StateGraph.compile()? Does the cache param do anything when using create_agent? Issues about the entry points of RAG knowledge Is the way RAG stores retrieved information in the state also just by directly concatenating it like historical memory? Regarding whether the knowledge base recall can be excluded from the context and used as a node-level system prompt for the response model Extending LangSmith Auto Cost Tracking to Include Agent API Calls Best Practice for Assigning MCP Tools to Sub-Agents in a Multi-Tool MCP Server? Reducing Latency in GPT-5: Controlling Reasoning in LangChain Advice for implementing complex Customer AI HealthCare Assistant Pattern for Persisting Structured Agent Outputs in LangGraph State (Instead of ToolMessage History) Langchiain deep_agent very slowly Stopping endpoint for deep agents Clarification on Official Container Images, Supported Python Versions, and the Recommended LangGraph Deployment Path Create_agent : Tool Invocation Failure in LangGraph Agent Are dynamic tool lists allowed when using create_agent? Files in Agent Chat Make a llm.with_structured_output call a tool Seeking help with some merge message issues when LangGraph is called in parallel - LangGraph - LangChain Forum
Chat model returns empty content when I inject a delayed ToolMessage from a scheduled callback
vicky70 · 2025-10-26 · via LangChain Forum - Topics tagged intro-to-langgraph

October 26, 2025, 8:20am 1

Hey!

I have a scheduler that triggers a tool function later (outside the original user–model exchange).
When the scheduled time comes, it sends a ToolMessage back into the chat model — but the model returns an empty message (content="") instead of responding.

This is the flow of messages:

[HumanMessage] → [AIMessage] → [ToolMessage] → [AIMessage] → … → [ToolMessage (from scheduled function)]

The final ToolMessage is the one created when the scheduled callback fires.
That’s where the issue happens — the model simply returns an empty string.

The Scheduled Callback:

def _task_reminder_callback(thread_id: str, task_description: str):
    print(f"\n\n === AGENT: TASK REMINDER! (for thread: {thread_id}) === ")
    print(f" TASK: {task_description} ")
    print(" ======================================================= \n\n")

    config = {"configurable": {"thread_id": thread_id, "user_id": "user_123"}}
    workflow_instance = app_state.get("WORKFLOW")

    state = workflow_instance.get_state(config)

    # Find the most recent tool call for schedule_reminder
    tool_call_id = None
    if state and state.values and "messages" in state.values:
        for msg in reversed(state.values["messages"]):
            if hasattr(msg, 'tool_calls') and msg.tool_calls:
                for tool_call in msg.tool_calls:
                    if tool_call.get('name') == 'schedule_reminder':
                        tool_call_id = tool_call.get('id')
                        break
            if tool_call_id:
                break

    print(f'[DEBUG] tool_call_id: {tool_call_id}')

    if workflow_instance:
        try:
            reminder_message = ToolMessage(
                content=(
                    f"REMINDER: generate a natural sounding message to remind user "
                    f"that it's time to do this task:\n\n {task_description}.\n\n"
                ),
                tool_call_id=tool_call_id
            )

            result = workflow_instance.invoke({"messages": reminder_message}, config=config)

            print(f" === Successfully invoked workflow for reminder in thread {thread_id} === \n\n {result} === \n\n")
        except Exception as e:
            print(f" !!! ERROR in _task_reminder_callback for thread {thread_id}: {e} !!! \n\n")
    else:
        print(f" !!! ERROR: WORKFLOW not initialized. Cannot run reminder for thread {thread_id} !!!")

Problem
When this scheduled function runs and I call workflow_instance.invoke() with a ToolMessage,
the chat model returns an empty content message AIMessage(content="") — it doesn’t “react” like it normally would when the tool message comes directly during conversation flow.
Expected behavior:
The model should interpret the tool message and respond naturally (e.g., “Hey, it’s time to do your scheduled task!”).

My Question:
How can I correctly re-inject a delayed ToolMessage into the workflow so that the model responds like it does during normal tool use?

Is there something special about how the tool_call_id or message history needs to be linked so the model knows what to do?
Or do I need to send a different type of message (like SystemMessage or AssistantMessage) when reactivating the model after a delay?

Hi @vicky70

have you tried this?

# 1) Build the ToolMessage with the SAME tool_call_id produced earlier by the model
reminder_message = ToolMessage(
    content=(
        f"REMINDER: generate a natural sounding message to remind the user "
        f"that it's time to do this task:\n\n{task_description}.\n\n"
    ),
    tool_call_id=tool_call_id,
    name="schedule_reminder",  # include tool name for clarity
)

# 2) Append it to the thread state AS IF it came from the 'tools' node
next_config = workflow_instance.update_state(
    config,
    {"messages": [reminder_message]},
    as_node="tools",  # drives the graph to the agent/model next
)

# 3) Resume the graph so the model reacts to the tool output
result = workflow_instance.invoke(None, config=next_config)

1 Like

vinishiru February 19, 2026, 7:11pm 3

Hello @vicky70 , have you successfully implemented this behavior?
I also have a tool that returns a partial result from the agent call, but in a later moment I try to inject a ToolMessage (with same tool_call_id), but the model generates a empty content response.

I have tried the @pawel-twardziak approach, but the model also generates an empty response, even when updating the graph state as it was resuming the result from the tool node.

I have also used a time-travel approach, search for the state when the tool was originally called, overwriting the tool result, as if it was completed as a direct call, but this way I lose any message exchanged between the user and the agent.

I had better results creating the tool message and a HumanMessage that instructs the model to continue the conversation, both on the same input message array, but oddly the model seens to ignore the ToolMessage data and generate a response that is not present on the appended ToolMessage.

Example:

messages = [
                    ToolMessage(
                        content=ai_agent_prompt_dto.prompt,
                        tool_call_id=ai_agent_prompt_dto.context[
                            "tool_call_id"
                        ], 
                    ),
                    HumanMessage(content="You have received the tool delayed result. You must continue with the conversation."),
                ]

Hey @vicky70

This usually happens because the graph isn’t actually being resumed in the same execution context the model expects.

When you inject a delayed ToolMessage, the model will only react if:

  1. The tool_call_id matches exactly the one from the original AIMessage.tool_calls.

  2. The message is appended as coming from the "tools" node.

  3. The graph is resumed from the correct next edge (not restarted with a fresh invoke).

In LangGraph, a ToolMessage by itself doesn’t trigger reasoning — it only works if the graph state machine thinks it just finished executing a tool. If that edge isn’t active anymore (because time passed or the flow completed), the model can legitimately return AIMessage(content="").

That empty response usually means:

“There is no pending tool result expected in this state.”

Why your HumanMessage workaround behaves oddly

When you add:

HumanMessage("You have received the tool delayed result...")

you’re effectively starting a new reasoning turn.
So the model ignores the tool linkage and just treats it like fresh input.

That’s why it doesn’t behave like native tool completion.

What works more reliably in production

Instead of trying to “resume” the original tool call hours later:

:white_check_mark: Treat the reminder as a new user event
→ Inject a HumanMessage like:

“System reminder: It’s time to do X.”

and let the agent respond normally.

This avoids fighting the internal state machine.

Why this happens (architecturally)

LangGraph’s execution model assumes:

AI → tool_call → ToolMessage → AI (immediate continuation)

It is not designed for:

AI → tool_call → [hours later] → ToolMessage → AI

Once the run is complete, there is no suspended edge waiting for that tool result.

In short:
You’re not doing it wrong — you’re hitting a lifecycle limitation.
Delayed tool reinjection only works if the graph is still in a waiting state. Otherwise, treat it as a new conversational turn.