Skip to content

feat: add Human-in-the-Loop (HITL) deferred tool calls support#481

Closed
tchardonnens wants to merge 1 commit intomainfrom
feat/hitl-deferred-tool-calls
Closed

feat: add Human-in-the-Loop (HITL) deferred tool calls support#481
tchardonnens wants to merge 1 commit intomainfrom
feat/hitl-deferred-tool-calls

Conversation

@tchardonnens
Copy link
Copy Markdown
Contributor

Summary

  • Add DeferredToolCallsException and supporting classes to enable human confirmation flows in run_async and run_stream_async
  • When a tool is registered with requires_confirmation=True or the server returns a function call with confirmation_status="pending", run_async raises DeferredToolCallsException
  • The user catches the exception, calls .confirm() or .reject() on each deferred call, and passes the responses back to run_async to resume the conversation

Files changed

File Change
extra/exceptions.py Add DeferralReason, DeferredToolCallEntry, DeferredToolCallConfirmation, DeferredToolCallRejection, DeferredToolCallsException with serialization
extra/run/deferred.py New module: helpers for processing deferred responses (client-side execution, server-side ToolCallConfirmation)
extra/run/context.py Add requires_confirmation(), _tool_configurations, requires_confirmation param on register_func() and register_mcp_client(), tool include/exclude filtering, FunctionResultEntry pass-through in _validate_run
client/conversations.py Update run_async and run_stream_async to partition function calls, process DeferredToolCallResponse inputs, pass tool_confirmations to append_async/append_stream_async

Test plan

Unit tests

  • All 101 existing unit tests pass (src/mistralai/extra/tests/) — 0 regressions
  • 2 pre-existing failures in test_otel_tracing.py (Python 3.14 asyncio.get_event_loop() deprecation) — unrelated to this PR

Manual integration tests (all run against live API)

Client-side HITL (local functions with requires_confirmation=True):

  • Confirm flow: register_func(get_weather, requires_confirmation=True)DeferredToolCallsException raised → .confirm() → resumed → model responds with weather ✅
  • Reject flow: Same setup → .reject("User does not want weather info") → model responds gracefully without weather ✅
  • Mixed flow: get_weather (requires confirmation) + get_time (auto-execute) → DeferredToolCallsException with executed_results=[get_time result] → pass executed_results + confirmations → model responds with both weather and time ✅

Server-side HITL (MCP connectors with requires_confirmation in ToolConfiguration):

  • Deepwiki low-level API: CustomConnector(connector_id='deepwiki', tool_configuration=ToolConfiguration(requires_confirmation=[...]))function.call with confirmation_status=pendingToolCallConfirmation(confirmation="allow") → search results returned ✅
  • Deepwiki high-level RunContext: Same connector → DeferredToolCallsException raised with reason=SERVER_SIDE_CONFIRMATION_REQUIRED.confirm() → resumed → model responds with wiki structure ✅
  • Notion low-level API: CustomConnector(connector_id='notion', ...)notion_notion-search with confirmation_status=pending → approved → Notion search results returned ✅
  • Notion high-level RunContext: Same → DeferredToolCallsException.confirm() → Notion meeting notes returned ✅

🤖 Generated with Claude Code

Add DeferredToolCallsException and supporting classes to enable
human confirmation flows in run_async and run_stream_async.

When a tool is registered with requires_confirmation=True or the
server returns a function call with confirmation_status="pending",
run_async raises DeferredToolCallsException. The user catches it,
calls .confirm() or .reject() on each deferred call, and passes
the responses back to run_async to resume the conversation.

Changes:
- extra/exceptions.py: Add DeferralReason, DeferredToolCallEntry,
  DeferredToolCallConfirmation, DeferredToolCallRejection,
  DeferredToolCallsException with serialization support
- extra/run/deferred.py: New module with helpers for processing
  deferred responses (client-side execution, server-side
  ToolCallConfirmation)
- extra/run/context.py: Add requires_confirmation() method,
  _tool_configurations dict, requires_confirmation param on
  register_func() and register_mcp_client(), tool include/exclude
  filtering, FunctionResultEntry pass-through in _validate_run
- client/conversations.py: Update run_async and run_stream_async
  to partition function calls into deferred vs executable, process
  DeferredToolCallResponse inputs, and pass tool_confirmations to
  append_async/append_stream_async

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tchardonnens tchardonnens force-pushed the feat/hitl-deferred-tool-calls branch from 2e1af21 to 87fdfa4 Compare April 10, 2026 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant