From 56c74aaa8bb4c19bf80f21a9f1c8d01f9d08cf12 Mon Sep 17 00:00:00 2001 From: Carey Payette Date: Wed, 18 Dec 2024 21:41:37 -0500 Subject: [PATCH] Integrated external workflow loading into langchain agent --- src/python/PythonSDK/PythonSDK.pyproj | 3 +- .../langchain_knowledge_management_agent.py | 39 ++++++++++- .../langchain/common/__init__.py | 1 + .../common/foundationallm_workflow_base.py | 68 +++++++++++++++++++ .../langchain/workflows/__init__.py | 2 +- .../workflows/external_workflow_factory.py | 56 +++++++++++++++ .../langchain/workflows/workflow_factory.py | 49 ------------- .../workflows/workflow_plugin_manager_base.py | 25 ++++++- 8 files changed, 189 insertions(+), 54 deletions(-) create mode 100644 src/python/PythonSDK/foundationallm/langchain/common/foundationallm_workflow_base.py create mode 100644 src/python/PythonSDK/foundationallm/langchain/workflows/external_workflow_factory.py delete mode 100644 src/python/PythonSDK/foundationallm/langchain/workflows/workflow_factory.py diff --git a/src/python/PythonSDK/PythonSDK.pyproj b/src/python/PythonSDK/PythonSDK.pyproj index 234fb01165..b69214c071 100644 --- a/src/python/PythonSDK/PythonSDK.pyproj +++ b/src/python/PythonSDK/PythonSDK.pyproj @@ -30,8 +30,9 @@ + - + diff --git a/src/python/PythonSDK/foundationallm/langchain/agents/langchain_knowledge_management_agent.py b/src/python/PythonSDK/foundationallm/langchain/agents/langchain_knowledge_management_agent.py index e19faacec9..7aa6bd54e8 100644 --- a/src/python/PythonSDK/foundationallm/langchain/agents/langchain_knowledge_management_agent.py +++ b/src/python/PythonSDK/foundationallm/langchain/agents/langchain_knowledge_management_agent.py @@ -8,7 +8,8 @@ from foundationallm.langchain.agents import LangChainAgentBase from foundationallm.langchain.exceptions import LangChainException from foundationallm.langchain.retrievers import RetrieverFactory, ContentArtifactRetrievalBase -from foundationallm.models.agents import AzureOpenAIAssistantsAgentWorkflow, LangGraphReactAgentWorkflow +from foundationallm.langchain.workflows import ExternalWorkflowFactory +from foundationallm.models.agents import AzureOpenAIAssistantsAgentWorkflow, ExternalAgentWorkflow, LangGraphReactAgentWorkflow from foundationallm.models.constants import ( AgentCapabilityCategories, ResourceObjectIdPropertyNames, @@ -499,6 +500,42 @@ async def invoke_async(self, request: KnowledgeManagementCompletionRequest) -> C ) # End LangGraph ReAct Agent workflow implementation + # Start External Agent workflow implementation + if (agent.workflow is not None and isinstance(agent.workflow, ExternalAgentWorkflow)): + # prepare tools + tool_factory = ToolFactory(self.plugin_manager) + tools = [] + + parsed_user_prompt = request.user_prompt + + explicit_tool = next((tool for tool in agent.tools if parsed_user_prompt.startswith(f'[{tool.name}]:')), None) + if explicit_tool is not None: + tools.append(tool_factory.get_tool(explicit_tool, request.objects, self.user_identity, self.config)) + parsed_user_prompt = parsed_user_prompt.split(':', 1)[1].strip() + else: + # Populate tools list from agent configuration + for tool in agent.tools: + tools.append(tool_factory.get_tool(tool, request.objects, self.user_identity, self.config)) + + # create the workflow + workflow_factory = ExternalWorkflowFactory(self.plugin_manager) + workflow = workflow_factory.create_workflow( + agent.workflow, + request.objects, + tools, + self.user_identity, + self.config) + + # Get message history + messages = self._build_conversation_history_message_list(request.message_history, agent.conversation_history_settings.max_history) + + response = await workflow.invoke_async( + user_prompt=parsed_user_prompt, + message_history=messages + ) + return response + # End External Agent workflow implementation + # Start LangChain Expression Language (LCEL) implementation # Get the vector document retriever, if it exists. diff --git a/src/python/PythonSDK/foundationallm/langchain/common/__init__.py b/src/python/PythonSDK/foundationallm/langchain/common/__init__.py index 108e083d0d..81fe4eb03e 100644 --- a/src/python/PythonSDK/foundationallm/langchain/common/__init__.py +++ b/src/python/PythonSDK/foundationallm/langchain/common/__init__.py @@ -1 +1,2 @@ from .foundationallm_tool_base import FoundationaLLMToolBase +from .foundationallm_workflow_base import FoundationaLLMWorkflowBase diff --git a/src/python/PythonSDK/foundationallm/langchain/common/foundationallm_workflow_base.py b/src/python/PythonSDK/foundationallm/langchain/common/foundationallm_workflow_base.py new file mode 100644 index 0000000000..d275ec2ad9 --- /dev/null +++ b/src/python/PythonSDK/foundationallm/langchain/common/foundationallm_workflow_base.py @@ -0,0 +1,68 @@ +""" +Class: FoundationaLLMWorkflowBase +Description: FoundationaLLM base class for tools that uses the agent workflow model for its configuration. +""" +from abc import ABC, abstractmethod +from azure.identity import DefaultAzureCredential +from langchain_core.messages import BaseMessage +from pydantic import BaseModel +from typing import List +from foundationallm.config import Configuration, UserIdentity +from foundationallm.models.agents import AgentTool, ExternalAgentWorkflow +from foundationallm.models.orchestration import CompletionResponse +from foundationallm.telemetry import Telemetry + +class FoundationaLLMWorkflowBase(BaseModel, ABC): + """ + FoundationaLLM base class for workflows that uses the agent workflow model for its configuration. + """ + def __init__(self, + workflow_config: ExternalAgentWorkflow, + objects: dict, + tools: List[AgentTool], + user_identity: UserIdentity, + config: Configuration): + """ + Initializes the FoundationaLLMWorkflowBase class with the workflow configuration. + + Parameters + ---------- + workflow_config : ExternalAgentWorkflow + The workflow assigned to the agent. + objects : dict + The exploded objects assigned from the agent. + tools : List[AgentTool] + The tools assigned to the agent. + user_identity : UserIdentity + The user identity of the user initiating the request. + config : Configuration + The application configuration for FoundationaLLM. + """ + self.workflow_config = workflow_config + self.objects = objects + self.tools = tools if tools is not None else [] + self.user_identity = user_identity + self.config = config + self.logger = Telemetry.get_logger(self.workflow_config.name) + self.tracer = Telemetry.get_tracer(self.workflow_config.name) + self.default_credential = DefaultAzureCredential(exclude_environment_credential=True) + + @abstractmethod + async def invoke_async(self, + user_prompt:str, + message_history: List[BaseMessage])-> CompletionResponse: + """ + Invokes the workflow asynchronously. + + Parameters + ---------- + user_prompt : str + The user prompt message. + message_history : List[BaseMessage] + The message history. + """ + pass + + class Config: + """ Pydantic configuration for FoundationaLLMWorkflowBase. """ + extra = "allow" diff --git a/src/python/PythonSDK/foundationallm/langchain/workflows/__init__.py b/src/python/PythonSDK/foundationallm/langchain/workflows/__init__.py index b178742436..51cfc6d779 100644 --- a/src/python/PythonSDK/foundationallm/langchain/workflows/__init__.py +++ b/src/python/PythonSDK/foundationallm/langchain/workflows/__init__.py @@ -1 +1 @@ -from .workflow_factory import WorkflowFactory +from .external_workflow_factory import ExternalWorkflowFactory diff --git a/src/python/PythonSDK/foundationallm/langchain/workflows/external_workflow_factory.py b/src/python/PythonSDK/foundationallm/langchain/workflows/external_workflow_factory.py new file mode 100644 index 0000000000..a239aeebb0 --- /dev/null +++ b/src/python/PythonSDK/foundationallm/langchain/workflows/external_workflow_factory.py @@ -0,0 +1,56 @@ +""" +Class: WorkflowFactory +Description: Factory class for creating an external workflow instance based on the Agent workflow configuration. +""" +from typing import List +from foundationallm.config import Configuration, UserIdentity +from foundationallm.langchain.common import FoundationaLLMWorkflowBase +from foundationallm.langchain.exceptions import LangChainException +from foundationallm.models.agents import AgentTool, ExternalAgentWorkflow +from foundationallm.plugins import PluginManager + +class ExternalWorkflowFactory: + """ + Factory class for creating an external agent workflow instance based on the Agent workflow configuration. + """ + def __init__(self, plugin_manager: PluginManager): + """ + Initializes the workflow factory. + + Parameters + ---------- + plugin_manager : PluginManager + The plugin manager object used to load external workflows. + """ + self.plugin_manager = plugin_manager + + def get_workflow( + self, + workflow_config: ExternalAgentWorkflow, + objects: dict, + tools: List[AgentTool], + user_identity: UserIdentity, + config: Configuration + ) -> FoundationaLLMWorkflowBase: + """ + Creates an instance of an external agent workflow based on the agent workflow configuration. + + Parameters + ---------- + workflow_config : ExternalAgentWorkflow + The workflow assigned to the agent. + objects : dict + The exploded objects assigned from the agent. + tools : List[AgentTool] + The tools assigned to the agent. + user_identity : UserIdentity + The user identity of the user initiating the request. + config : Configuration + The application configuration for FoundationaLLM. + """ + workflow_plugin_manager = None + if workflow_config.package_name in self.plugin_manager.external_modules: + workflow_plugin_manager = self.plugin_manager.external_modules[workflow_config.package_name].plugin_manager + return workflow_plugin_manager.create_workflow(workflow_config, objects, tools, user_identity, config) + else: + raise LangChainException(f"Package {workflow_config.package_name} not found in the list of external modules loaded by the package manager.") diff --git a/src/python/PythonSDK/foundationallm/langchain/workflows/workflow_factory.py b/src/python/PythonSDK/foundationallm/langchain/workflows/workflow_factory.py deleted file mode 100644 index 380e770827..0000000000 --- a/src/python/PythonSDK/foundationallm/langchain/workflows/workflow_factory.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Class: WorkflowFactory -Description: Factory class for creating workflows based on the Agent workflow configuration. -""" -from typing import Any -from foundationallm.config import Configuration, UserIdentity -from foundationallm.langchain.exceptions import LangChainException -from foundationallm.plugins import PluginManager - -class WorkflowFactory: - """ - Factory class for creating workflows based on the Agent workflow configuration. - """ - FLLM_PACKAGE_NAME = "FoundationaLLM" - - def __init__(self, plugin_manager: PluginManager): - """ - Initializes the workflow factory. - - Parameters - ---------- - plugin_manager : PluginManager - The plugin manager object used to load external workflows. - """ - self.plugin_manager = plugin_manager - - def get_workflow( - self, - workflow_config, - objects: dict, - user_identity: UserIdentity, - config: Configuration - ) -> Any: - """ - Creates an instance of a workflow based on the agent workflow configuration. - """ - if workflow_config.package_name == self.FLLM_PACKAGE_NAME: - # internal workflow - return workflow_config - else: - workflow_plugin_manager = None - if workflow_config.package_name in self.plugin_manager.external_modules: - workflow_plugin_manager = self.plugin_manager.external_modules[workflow_config.package_name].plugin_manager - return workflow_plugin_manager.create_workflow(workflow_config, user_identity, config) - else: - raise LangChainException(f"Package {workflow_config.package_name} not found in the list of external modules loaded by the package manager.") - - raise LangChainException(f"Workflow {workflow_config.name} not found in package {workflow_config.package_name}") - diff --git a/src/python/PythonSDK/foundationallm/plugins/workflows/workflow_plugin_manager_base.py b/src/python/PythonSDK/foundationallm/plugins/workflows/workflow_plugin_manager_base.py index 9e7348d2c5..74c739cc02 100644 --- a/src/python/PythonSDK/foundationallm/plugins/workflows/workflow_plugin_manager_base.py +++ b/src/python/PythonSDK/foundationallm/plugins/workflows/workflow_plugin_manager_base.py @@ -1,17 +1,38 @@ from abc import ABC, abstractmethod +from typing import List from foundationallm.config import Configuration, UserIdentity from foundationallm.langchain.common import FoundationaLLMToolBase -from foundationallm.models.agents import AgentWorkflowBase +from foundationallm.models.agents import AgentTool, ExternalAgentWorkflow class WorkflowPluginManagerBase(ABC): + """ + The base class for all workflow plugin managers. + """ def __init__(self): pass @abstractmethod def create_workflow(self, - workflow_config: AgentWorkflowBase, + workflow_config: ExternalAgentWorkflow, + objects: dict, + tools: List[AgentTool], user_identity: UserIdentity, config: Configuration) -> FoundationaLLMToolBase: + """ + Create a workflow instance based on the given configuration and tools. + Parameters + ---------- + workflow_config : ExternalAgentWorkflow + The workflow assigned to the agent. + objects : dict + The exploded objects assigned from the agent. + tools : List[AgentTool] + The tools assigned to the agent. + user_identity : UserIdentity + The user identity of the user initiating the request. + config : Configuration + The application configuration for FoundationaLLM. + """ pass @abstractmethod