Skip to content

Conversation

@zhangzhefang-github
Copy link

@zhangzhefang-github zhangzhefang-github commented Nov 3, 2025

Description

Fixes a critical bug in the Human-in-the-Loop middleware where the agent's final response referenced the original tool call parameters instead of the edited ones.

The Problem

When a user edited a tool call via HITL middleware:

  1. ✅ The edited tool would execute correctly with the new parameters
  2. ✅ The AIMessage.tool_calls would be updated in state
  3. But the agent's final response would still reference the ORIGINAL parameters from the user's request

Example:

User: "Send email to [email protected]"
[Human edits to [email protected]]
Tool: Email sent to [email protected] ✓
Agent: "Email sent to [email protected]" ✗  ← References original, not edited!

Root Causes (Fixed in 3 iterations)

This PR contains three progressive fixes addressing different layers of the problem:

Fix 1: Data Persistence (Commit 69d4f40)

Problem: Direct mutations to AIMessage.tool_calls weren't persisting in LangGraph's state.

Solution: Create a new AIMessage instance instead of mutating:

# Before: Direct mutation (doesn't persist)
last_ai_msg.tool_calls = revised_tool_calls# After: Create new message (persists correctly)
updated_ai_msg = AIMessage(
    tool_calls=revised_tool_calls,
    id=last_ai_msg.id,  # Same ID ensures replacement
    ...
)  ✅

Fix 2: Pre-execution Context (Commit ce9892f)

Problem: Even with persisted edits, the AI didn't know the tool call had been edited.

Solution: Add a [System Note] message before tool execution:

HumanMessage(
    "[System Note] The user edited the proposed tool call..."
)

Fix 3: Post-execution Reminder (Commit 4d4039f) ← Critical Fix

Problem: The pre-execution context was too far from the AI's final response generation. The AI would "forget" the edit and summarize based on the user's original request.

Solution: Add a before_model() hook that injects a reminder immediately before the AI generates its final response:

def before_model(self, state, runtime):
    """Inject context messages after tool execution for edited tool calls."""
    # When we detect a ToolMessage for an edited tool call...
    return {"messages": [HumanMessage(
        f"[System Reminder] The tool '{tool_name}' was executed with "
        f"edited parameters: {edited_args}. When responding to the user, "
        f"reference the edited parameters, not the original request."
    )]}

Message Flow After All Fixes

1. HumanMessage: "Send email to [email protected]"
2. AIMessage: [tool_call(to="[email protected]")]        ← Fix 1: Persisted correctly
3. HumanMessage: "[System Note] Will execute with [email protected]"  ← Fix 2
4. ToolMessage: "Email sent to [email protected]"
5. HumanMessage: "[System Reminder] Executed with [email protected]"  ← Fix 3 (KEY!)
6. AIMessage: "Email sent to [email protected]" ✓        ← Correct response!

Changes

  • Added _pending_edit_contexts: Dictionary to track edited tool calls across middleware hooks
  • Added before_model() hook: Injects post-execution reminder messages
  • Updated after_model(): Records edit information for later use in before_model()
  • Updated test expectations: Validates two context messages (pre and post execution)
  • Added type guard: if tool_call_id: to satisfy mypy

Testing

✅ All 16 HITL middleware tests pass
✅ New test test_human_in_the_loop_middleware_edit_actually_executes_with_edited_args validates:

  • Tool executes with edited parameters
  • Two context messages present (pre and post execution)
  • Both context messages reference edited parameters
  • AI's final response correctly references edited parameters
    ✅ Linting passes
    ✅ Type checking passes

Issue

Fixes #33787
Fixes #33784

Dependencies

No new dependencies added.

@github-actions github-actions bot added fix langchain Related to the package `langchain` v1 Issue specific to LangChain 1.0 and removed fix labels Nov 3, 2025
@zhangzhefang-github zhangzhefang-github force-pushed the fix/hitl-edit-persistence branch 2 times, most recently from 1c13e07 to 5bd39be Compare November 3, 2025 02:01
@zhangzhefang-github zhangzhefang-github changed the title fix(agents): ensure HITL middleware edits persist correctly fix(langchain_v1): ensure HITL middleware edits persist correctly Nov 3, 2025
@github-actions github-actions bot added the fix label Nov 3, 2025
Fix issues langchain-ai#33787 and langchain-ai#33784 where Human-in-the-Loop middleware edits
were not persisting correctly in the agent's message history.

The problem occurred because the middleware was directly mutating the
AIMessage.tool_calls attribute, but LangGraph's state management doesn't
properly persist direct object mutations. This caused the agent to see
the original (unedited) tool calls in subsequent model invocations,
leading to duplicate or incorrect tool executions.

Changes:
- Create new AIMessage instance instead of mutating the original
- Ensure message has an ID (generate UUID if needed) so add_messages
  reducer properly replaces instead of appending
- Add comprehensive test case that reproduces and verifies the fix
@zhangzhefang-github
Copy link
Author

@sydney-runkle Hi! I've investigated the CI failure and found:

The failing test is unrelated to my PR:

  • My PR only modifies libs/langchain_v1 code (HITL middleware)
  • The failing test is in libs/core/tests/unit_tests/runnables/test_runnable.py
  • I didn't modify any libs/core files

The test only fails on Python 3.12:

  • ✅ Passes on Python 3.10 (both master and this PR branch)
  • ❌ Fails on Python 3.12 (CI environment)
  • Error: ValueError('generator already executing')

All my HITL tests pass:

This appears to be a Python 3.12-specific issue in libs/core, possibly related to recent tracing changes (commit 76dd656). Could you please re-run CI or advise how to proceed?

@zhangzhefang-github
Copy link
Author

Hi @sydney-runkle,

I've reverted the master merge that was causing CI failures. Here's what happened:

Timeline:

  • Nov 3: Original commit (69d4f40) - ✅ All tests passed
  • Nov 4: GitHub suggested updating the branch, so I merged master
  • Result: ❌ CI failed on libs/core tests (unrelated to my changes)

Analysis:

  • The failing test test_runnable_lambda_context_config is in libs/core
  • My PR only modifies libs/langchain_v1 files
  • The test failure is Python 3.12-specific, likely from recent master changes
  • Error: ValueError('generator already executing')

Resolution:

The PR is ready for review. I can merge master again after the core test issue is resolved.

Enhances the fix for issues langchain-ai#33787 and langchain-ai#33784 by adding a HumanMessage
that informs the AI when a tool call has been edited by a human operator.

This ensures that the AI's subsequent responses reference the edited
parameters rather than the original request parameters.

Changes:
- Modified _process_decision to create a HumanMessage on edit
- The message informs the AI about the edited tool call arguments
- Uses HumanMessage instead of ToolMessage to avoid interfering with
  actual tool execution
- Updated all affected tests to expect the context message
- All 70 middleware agent tests pass

This complements the previous fix that ensured tool calls persist
correctly in state by also providing context to the AI about the edit.
- Updated _process_decision return type to allow HumanMessage
- Updated artificial_tool_messages list type annotation
- Removed unused BaseMessage import
@zhangzhefang-github zhangzhefang-github changed the title fix(langchain_v1): ensure HITL middleware edits persist correctly fix(langchain_v1): ensure HITL middleware edit decisions persist in agent state Nov 6, 2025
@github-actions github-actions bot added fix and removed fix labels Nov 6, 2025
@zhangzhefang-github zhangzhefang-github changed the title fix(langchain_v1): ensure HITL middleware edit decisions persist in agent state fix(langchain): ensure HITL middleware edit decisions persist in agent state Nov 6, 2025
@github-actions github-actions bot added fix and removed fix labels Nov 6, 2025
This commit adds a before_model hook to inject a reminder message after
tool execution for edited tool calls. This ensures the AI's final response
references the edited parameters rather than the original user request.

The fix addresses issue langchain-ai#33787 where the AI would generate a final response
referencing the original parameters despite the tool being executed with
edited parameters. Now a [System Reminder] message is injected after tool
execution to provide context about the edited parameters.

Changes:
- Added _pending_edit_contexts dict to track edited tool calls
- Added before_model hook to inject post-execution reminder messages
- Updated test to expect two context messages (pre and post execution)
- Added type guard for tool_call_id to satisfy mypy

Fixes langchain-ai#33787
@github-actions github-actions bot added fix and removed fix labels Nov 7, 2025
@zhangzhefang-github
Copy link
Author

CI Failure Analysis

The failing test in is unrelated to this PR's changes.

Details:

  • This PR modifies: HITL middleware
  • Failing test in:
  • All langchain_v1 tests: ✅ PASSING (16/16 HITL tests pass)

Root Cause:

This is a flaky timing-sensitive test:

Expected delta_time: ~0ms (±20ms tolerance)
Actual delta_time: 90ms

The test expects producer/consumer to run in parallel with minimal delay, but CI machine load caused 90ms delay, exceeding the 20ms tolerance.

Evidence This is Unrelated:

  1. ✅ All tests in the modified package () pass
  2. ✅ Lint/type checking passes
  3. ✅ Extended tests pass
  4. ❌ Only one timing-sensitive test in a different package fails

Request: Could a maintainer please re-run the failed CI job? This appears to be a transient infrastructure issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix langchain Related to the package `langchain` v1 Issue specific to LangChain 1.0

Projects

None yet

2 participants