11import functools
22import inspect
3+ import json
34from inspect import Parameter
4- from typing import Annotated , Any , Callable
5+ from typing import Annotated , Any , Callable , NamedTuple
56
7+ from langchain_core .messages .tool import ToolCall , ToolMessage
68from langchain_core .tools import BaseTool , InjectedToolCallId
79from langchain_core .tools import tool as langchain_tool
810from langgraph .types import interrupt
911from uipath .core .chat import (
1012 UiPathConversationToolCallConfirmationValue ,
1113)
1214
13- _CANCELLED_MESSAGE = "Cancelled by user"
15+ CANCELLED_MESSAGE = "Cancelled by user"
16+
17+ CONVERSATIONAL_APPROVED_TOOL_ARGS = "conversational_approved_tool_args"
18+ REQUIRE_CONVERSATIONAL_CONFIRMATION = "require_conversational_confirmation"
19+
20+
21+ class ConfirmationResult (NamedTuple ):
22+ """Result of a tool confirmation check."""
23+
24+ cancelled : ToolMessage | None # ToolMessage if cancelled, None if approved
25+ args_modified : bool
26+ approved_args : dict [str , Any ] | None = None
27+
28+ def annotate_result (self , output : dict [str , Any ] | Any ) -> None :
29+ """Apply confirmation metadata to a tool result message."""
30+ msg = None
31+ if isinstance (output , dict ):
32+ messages = output .get ("messages" )
33+ if messages :
34+ msg = messages [0 ]
35+ if msg is None :
36+ return
37+ if self .approved_args is not None :
38+ msg .response_metadata [CONVERSATIONAL_APPROVED_TOOL_ARGS ] = (
39+ self .approved_args
40+ )
41+ if self .args_modified :
42+ try :
43+ result_value = json .loads (msg .content )
44+ except (json .JSONDecodeError , TypeError ):
45+ result_value = msg .content
46+ msg .content = json .dumps (
47+ {
48+ "meta" : {
49+ "args_modified_by_user" : True ,
50+ "executed_args" : self .approved_args ,
51+ },
52+ "result" : result_value ,
53+ }
54+ )
1455
1556
1657def _patch_span_input (approved_args : dict [str , Any ]) -> None :
@@ -53,7 +94,7 @@ def _patch_span_input(approved_args: dict[str, Any]) -> None:
5394 pass
5495
5596
56- def _request_approval (
97+ def request_approval (
5798 tool_args : dict [str , Any ],
5899 tool : BaseTool ,
59100) -> dict [str , Any ] | None :
@@ -89,7 +130,41 @@ def _request_approval(
89130 if not confirmation .get ("approved" , True ):
90131 return None
91132
92- return confirmation .get ("input" ) or tool_args
133+ return (
134+ confirmation .get ("input" )
135+ if confirmation .get ("input" ) is not None
136+ else tool_args
137+ )
138+
139+
140+ def request_tool_confirmation (
141+ call : ToolCall , tool : BaseTool
142+ ) -> ConfirmationResult | None :
143+ """Check whether a tool requires user confirmation and request approval"""
144+ if not (tool .metadata and tool .metadata .get (REQUIRE_CONVERSATIONAL_CONFIRMATION )):
145+ return None
146+
147+ original_args = call ["args" ]
148+ approved_args = request_approval (
149+ {** original_args , "tool_call_id" : call ["id" ]}, tool
150+ )
151+ if approved_args is None :
152+ cancelled_msg = ToolMessage (
153+ content = CANCELLED_MESSAGE ,
154+ name = call ["name" ],
155+ tool_call_id = call ["id" ],
156+ )
157+ cancelled_msg .response_metadata [CONVERSATIONAL_APPROVED_TOOL_ARGS ] = (
158+ original_args
159+ )
160+ return ConfirmationResult (cancelled = cancelled_msg , args_modified = False )
161+ # Mutate call args so the tool executes with the approved values
162+ call ["args" ] = approved_args
163+ return ConfirmationResult (
164+ cancelled = None ,
165+ args_modified = approved_args != original_args ,
166+ approved_args = approved_args ,
167+ )
93168
94169
95170def requires_approval (
@@ -107,9 +182,9 @@ def decorator(fn: Callable[..., Any]) -> BaseTool:
107182 # wrap the tool/function
108183 @functools .wraps (fn )
109184 def wrapper (** tool_args : Any ) -> Any :
110- approved_args = _request_approval (tool_args , _created_tool [0 ])
185+ approved_args = request_approval (tool_args , _created_tool [0 ])
111186 if approved_args is None :
112- return _CANCELLED_MESSAGE
187+ return { "meta" : CANCELLED_MESSAGE }
113188 _patch_span_input (approved_args )
114189 return fn (** approved_args )
115190
0 commit comments