Skip to content

Commit d980703

Browse files
Pouyanpitgasser-nv
authored andcommitted
refactor(llm): remove LLMs isolation for actions (#1408)
* refactor(llm): remove isolated LLMs for actions Removes the logic for creating isolated LLM instances for actions that require an 'llm' parameter in `LLMRails`. This includes deleting the `_create_isolated_llms_for_actions`, `_detect_llm_requiring_actions`, `_get_action_function`, and `_create_action_llm_copy` methods, as well as removing their invocation from the class initialization. Also deletes all related tests, including unit, integration, and e2e tests for LLM isolation and model_kwargs handling. This simplifies the codebase and test suite by eliminating support for per-action LLM isolation. * Revert tests/utils.py to state before #1336
1 parent 848dbb1 commit d980703

File tree

6 files changed

+13
-1619
lines changed

6 files changed

+13
-1619
lines changed

nemoguardrails/rails/llm/llmrails.py

Lines changed: 0 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,6 @@ def __init__(
286286
# We also register the kb as a parameter that can be passed to actions.
287287
self.runtime.register_action_param("kb", self.kb)
288288

289-
# detect actions that need isolated LLM instances and create them
290-
self._create_isolated_llms_for_actions()
291289
# Reference to the general ExplainInfo object.
292290
self.explain_info = None
293291

@@ -511,147 +509,6 @@ def _init_llms(self):
511509

512510
self.runtime.register_action_param("llms", llms)
513511

514-
def _create_isolated_llms_for_actions(self):
515-
"""Create isolated LLM copies for all actions that accept 'llm' parameter."""
516-
if not self.llm:
517-
log.debug("No main LLM available for creating isolated copies")
518-
return
519-
520-
try:
521-
actions_needing_llms = self._detect_llm_requiring_actions()
522-
log.info(
523-
"%d actions requiring isolated LLMs: %s",
524-
len(actions_needing_llms),
525-
list(actions_needing_llms),
526-
)
527-
528-
created_count = 0
529-
530-
configured_actions_names = []
531-
try:
532-
if self.config.flows:
533-
get_action_details = partial(
534-
get_action_details_from_flow_id, flows=self.config.flows
535-
)
536-
for flow_id in self.config.rails.input.flows:
537-
action_name, _ = get_action_details(flow_id)
538-
configured_actions_names.append(action_name)
539-
for flow_id in self.config.rails.output.flows:
540-
action_name, _ = get_action_details(flow_id)
541-
configured_actions_names.append(action_name)
542-
else:
543-
# for configurations without flow definitions, use all actions that need LLMs
544-
log.info(
545-
"No flow definitions found, creating isolated LLMs for all actions requiring them"
546-
)
547-
configured_actions_names = list(actions_needing_llms)
548-
except Exception as e:
549-
# if flow matching fails, fall back to all actions that need LLMs
550-
log.info(
551-
"Flow matching failed (%s), creating isolated LLMs for all actions requiring them",
552-
e,
553-
)
554-
configured_actions_names = list(actions_needing_llms)
555-
556-
for action_name in configured_actions_names:
557-
if action_name not in actions_needing_llms:
558-
continue
559-
if f"{action_name}_llm" not in self.runtime.registered_action_params:
560-
isolated_llm = self._create_action_llm_copy(self.llm, action_name)
561-
if isolated_llm:
562-
self.runtime.register_action_param(
563-
f"{action_name}_llm", isolated_llm
564-
)
565-
created_count += 1
566-
log.debug("Created isolated LLM for action: %s", action_name)
567-
else:
568-
log.debug(
569-
"Action %s already has dedicated LLM, skipping isolation",
570-
action_name,
571-
)
572-
573-
log.info("Created %d isolated LLM instances for actions", created_count)
574-
575-
except Exception as e:
576-
log.warning("Failed to create isolated LLMs for actions: %s", e)
577-
578-
def _detect_llm_requiring_actions(self):
579-
"""Auto-detect actions that have 'llm' parameter."""
580-
import inspect
581-
582-
actions_needing_llms = set()
583-
584-
if (
585-
not hasattr(self.runtime, "action_dispatcher")
586-
or not self.runtime.action_dispatcher
587-
):
588-
log.debug("Action dispatcher not available")
589-
return actions_needing_llms
590-
591-
for (
592-
action_name,
593-
action_info,
594-
) in self.runtime.action_dispatcher.registered_actions.items():
595-
action_func = self._get_action_function(action_info)
596-
if not action_func:
597-
continue
598-
599-
try:
600-
sig = inspect.signature(action_func)
601-
if "llm" in sig.parameters:
602-
actions_needing_llms.add(action_name)
603-
log.debug("Action %s has 'llm' parameter", action_name)
604-
605-
except Exception as e:
606-
log.debug("Could not inspect action %s: %s", action_name, e)
607-
608-
return actions_needing_llms
609-
610-
def _get_action_function(self, action_info):
611-
"""Extract the actual function from action info."""
612-
return action_info if callable(action_info) else None
613-
614-
def _create_action_llm_copy(
615-
self, main_llm: Union[BaseLLM, BaseChatModel], action_name: str
616-
) -> Optional[Union[BaseLLM, BaseChatModel]]:
617-
"""Create an isolated copy of main LLM for a specific action."""
618-
import copy
619-
620-
try:
621-
# shallow copy to preserve HTTP clients, credentials, etc.
622-
# but create new instance to avoid shared state
623-
isolated_llm = copy.copy(main_llm)
624-
625-
# isolate model_kwargs to prevent shared mutable state
626-
if (
627-
hasattr(isolated_llm, "model_kwargs")
628-
and isolated_llm.model_kwargs is not None
629-
):
630-
isolated_llm.model_kwargs = isolated_llm.model_kwargs.copy()
631-
632-
log.debug(
633-
"Successfully created isolated LLM copy for action: %s", action_name
634-
)
635-
return isolated_llm
636-
637-
except Exception as e:
638-
error_msg = (
639-
"Failed to create isolated LLM instance for action '%s'. "
640-
"This is required to prevent parameter contamination between different actions. "
641-
"\n\nPossible solutions:"
642-
"\n1. If using a custom LLM class, ensure it supports copy.copy() operation"
643-
"\n2. Check that your LLM configuration doesn't contain non-copyable objects"
644-
"\n3. Consider using a dedicated LLM configuration for action '%s'"
645-
"\n\nOriginal error: %s"
646-
"\n\nTo use a dedicated LLM for this action, add to your config:"
647-
"\nmodels:"
648-
"\n - type: %s"
649-
"\n engine: <your_engine>"
650-
"\n model: <your_model>"
651-
) % (action_name, action_name, e, action_name)
652-
log.error(error_msg)
653-
raise RuntimeError(error_msg)
654-
655512
def _get_embeddings_search_provider_instance(
656513
self, esp_config: Optional[EmbeddingSearchProvider] = None
657514
) -> EmbeddingsIndex:

tests/runnable_rails/test_runnable_rails.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -633,11 +633,11 @@ def test_chat_prompt_template_with_runnable_rails_fixed():
633633

634634
llm = FakeLLM(
635635
responses=[
636-
"Hi!",
636+
"no",
637637
"express greeting",
638+
"no",
638639
"Welcome to our clinic! I'm so glad you're here.",
639-
"Additional response",
640-
"Another response",
640+
"no",
641641
]
642642
)
643643

0 commit comments

Comments
 (0)