2020from langchain_core .tools import BaseTool
2121from langgraph .graph import END , START
2222from langgraph .graph .state import CompiledStateGraph , StateGraph
23- from pydantic import BaseModel , create_model
23+ from pydantic import BaseModel , ConfigDict , create_model
2424from uipath .core .chat import UiPathConversationMessageData
2525
2626from uipath_langchain ._utils import get_unique_model_field_name
2727from uipath_langchain .agent .react .job_attachments import get_job_attachment_paths
28+ from uipath_langchain .runtime .messages import UiPathChatMessagesMapper
2829
29- from .types import AdvancedAgentGraphState , ConversationalAdvancedAgentGraphState
30+ from .types import (
31+ AdvancedAgentGraphState ,
32+ ConversationalAdvancedAgentGraphState ,
33+ _ConversationalAdvancedAgentGraphInput ,
34+ )
3035from .utils import (
3136 MEMORY_INDEX_VIRTUAL_PATH ,
3237 create_state_with_input ,
@@ -213,45 +218,132 @@ def transform_output(state: BaseModel) -> dict[str, Any]:
213218def create_conversational_advanced_agent_graph (
214219 model : BaseChatModel ,
215220 tools : Sequence [BaseTool ],
216- system_prompt : str ,
221+ system_prompt : str | Callable [[ dict [ str , Any ]], str ] ,
217222 backend : BackendProtocol | BackendFactory | None ,
223+ input_schema : type [BaseModel ] | None = None ,
218224) -> StateGraph [Any , Any , Any , Any ]:
219225 """Wrap the advanced agent in a parent graph that speaks the conversational contract.
220226
221227 Conversational agents receive the full conversation history in the
222228 ``messages`` input each exchange and must output the newly produced
223229 messages as ``uipath__agent_response_messages``. The deepagent already
224- operates on ``messages``, so the wrapper only records the incoming history
225- size and maps the new messages to the conversational output field.
230+ operates on ``messages``. Callable system prompts are resolved once from
231+ the exchange input and applied to every main-agent model request in that
232+ invocation.
226233 """
227- # deferred: avoids a circular import (runtime.messages imports agent modules)
228- from uipath_langchain .runtime .messages import UiPathChatMessagesMapper
229-
230234 memory_sources = (
231235 [MEMORY_INDEX_VIRTUAL_PATH ] if isinstance (backend , FilesystemBackend ) else []
232236 )
237+ if callable (system_prompt ):
238+ build_system_prompt = system_prompt
239+ static_system_prompt = None
240+ else :
241+ build_system_prompt = None
242+ static_system_prompt = system_prompt
243+ initial_message_count_key = get_unique_model_field_name (
244+ "initial_message_count" ,
245+ _ConversationalAdvancedAgentGraphInput ,
246+ input_schema ,
247+ )
248+ runtime_system_prompt_key = (
249+ get_unique_model_field_name (
250+ "uipath__system_prompt" ,
251+ _ConversationalAdvancedAgentGraphInput ,
252+ input_schema ,
253+ )
254+ if build_system_prompt is not None
255+ else None
256+ )
233257
234258 inner_graph = create_advanced_agent (
235259 model = model ,
236260 tools = tools ,
237- system_prompt = system_prompt ,
261+ system_prompt = static_system_prompt ,
238262 backend = backend ,
239263 memory = memory_sources ,
264+ middleware = (
265+ [_RuntimeSystemPromptMiddleware (runtime_system_prompt_key )]
266+ if runtime_system_prompt_key is not None
267+ else []
268+ ),
240269 )
241270
242271 class ConversationalAdvancedAgentOutput (BaseModel ):
243272 uipath__agent_response_messages : list [UiPathConversationMessageData ] = []
244273
245- def capture_exchange_start (
246- state : ConversationalAdvancedAgentGraphState ,
247- ) -> dict [str , Any ]:
248- return {"initial_message_count" : len (state .messages )}
274+ graph_input : type [BaseModel ] = _ConversationalAdvancedAgentGraphInput
275+ wrapper_input = graph_input
276+ if input_schema is not None :
277+ wrapper_input = type (
278+ "CompleteConversationalAdvancedAgentInput" ,
279+ (_ConversationalAdvancedAgentGraphInput , input_schema ),
280+ {
281+ "model_config" : ConfigDict (
282+ validate_by_alias = True ,
283+ validate_by_name = True ,
284+ )
285+ },
286+ )
287+ wrapper_input .model_rebuild ()
288+ graph_input = (
289+ input_schema if "messages" in input_schema .model_fields else wrapper_input
290+ )
291+ initial_count_field : dict [str , Any ] = {
292+ initial_message_count_key : (int | None , None )
293+ }
294+ base_wrapper_state = cast (
295+ type [BaseModel ],
296+ create_model (
297+ "ConversationalAdvancedAgentGraphState" ,
298+ __base__ = wrapper_input ,
299+ ** initial_count_field ,
300+ ),
301+ )
302+ if runtime_system_prompt_key is not None :
303+ runtime_state_field : dict [str , Any ] = {
304+ runtime_system_prompt_key : (str | None , None )
305+ }
306+ wrapper_state = cast (
307+ type [BaseModel ],
308+ create_model (
309+ "RuntimeConversationalAdvancedAgentGraphState" ,
310+ __base__ = base_wrapper_state ,
311+ ** runtime_state_field ,
312+ ),
313+ )
314+ else :
315+ wrapper_state = base_wrapper_state
316+
317+ internal_fields = set (_ConversationalAdvancedAgentGraphInput .model_fields )
318+ internal_fields .add (initial_message_count_key )
319+ if runtime_system_prompt_key is not None :
320+ internal_fields .add (runtime_system_prompt_key )
249321
250- def transform_output (
251- state : ConversationalAdvancedAgentGraphState ,
252- ) -> dict [str , Any ]:
253- initial_count = state .initial_message_count or 0
254- new_messages = state .messages [initial_count :]
322+ def capture_exchange_start (state : BaseModel ) -> dict [str , Any ]:
323+ conversation_state = cast (ConversationalAdvancedAgentGraphState , state )
324+ update : dict [str , Any ] = {
325+ initial_message_count_key : len (conversation_state .messages )
326+ }
327+ if build_system_prompt is not None and runtime_system_prompt_key is not None :
328+ input_args = (
329+ input_schema .model_construct (
330+ ** {
331+ field_name : getattr (state , field_name )
332+ for field_name in input_schema .model_fields
333+ if field_name not in internal_fields
334+ }
335+ ).model_dump (by_alias = True , exclude_unset = True )
336+ if input_schema is not None
337+ else {}
338+ )
339+ update [runtime_system_prompt_key ] = build_system_prompt (input_args )
340+ return update
341+
342+ def transform_output (state : BaseModel ) -> dict [str , Any ]:
343+ initial_count = getattr (state , initial_message_count_key ) or 0
344+ new_messages = cast (ConversationalAdvancedAgentGraphState , state ).messages [
345+ initial_count :
346+ ]
255347 converted = (
256348 UiPathChatMessagesMapper .map_langchain_messages_to_uipath_message_data_list (
257349 messages = new_messages , include_tool_results = False
@@ -262,7 +354,8 @@ def transform_output(
262354 return {"uipath__agent_response_messages" : converted }
263355
264356 wrapper : StateGraph [Any , Any , Any , Any ] = StateGraph (
265- ConversationalAdvancedAgentGraphState ,
357+ wrapper_state ,
358+ input_schema = graph_input ,
266359 output_schema = ConversationalAdvancedAgentOutput ,
267360 )
268361 wrapper .add_node ("capture_exchange_start" , capture_exchange_start )
0 commit comments