diff --git a/src/backend/base/langflow/base/agents/agent.py b/src/backend/base/langflow/base/agents/agent.py index b24cf1984b3..5475e77e88e 100644 --- a/src/backend/base/langflow/base/agents/agent.py +++ b/src/backend/base/langflow/base/agents/agent.py @@ -1,78 +1,91 @@ -from typing import List, Optional, Union, cast +from abc import abstractmethod +from typing import List -from langchain.agents import AgentExecutor, BaseMultiActionAgent, BaseSingleActionAgent -from langchain_core.messages import BaseMessage +from langchain.agents.agent import RunnableAgent + +from langchain.agents import AgentExecutor from langchain_core.runnables import Runnable -from langflow.base.agents.utils import data_to_messages, get_agents_list -from langflow.custom import CustomComponent -from langflow.field_typing import Text, Tool -from langflow.schema import Data +from langflow.custom import Component +from langflow.inputs import BoolInput, IntInput, HandleInput +from langflow.inputs.inputs import InputTypes +from langflow.template import Output + + +class LCAgentComponent(Component): + trace_type = "agent" + _base_inputs: List[InputTypes] = [ + BoolInput( + name="handle_parsing_errors", + display_name="Handle Parse Errors", + value=True, + advanced=True, + ), + BoolInput( + name="verbose", + display_name="Verbose", + value=True, + advanced=True, + ), + IntInput( + name="max_iterations", + display_name="Max Iterations", + value=15, + advanced=True, + ), + ] + outputs = [ + Output(display_name="Agent", name="agent", method="build_agent"), + ] -class LCAgentComponent(CustomComponent): - def get_agents_list(self): - return get_agents_list() + def _validate_outputs(self): + required_output_methods = ["build_agent"] + output_names = [output.name for output in self.outputs] + for method_name in required_output_methods: + if method_name not in output_names: + raise ValueError(f"Output with name '{method_name}' must be defined.") + elif not hasattr(self, method_name): + raise ValueError(f"Method '{method_name}' must be defined.") - def build_config(self): - return { - "lc": { - "display_name": "LangChain", - "info": "The LangChain to interact with.", - }, - "handle_parsing_errors": { - "display_name": "Handle Parsing Errors", - "info": "If True, the agent will handle parsing errors. If False, the agent will raise an error.", - "advanced": True, - }, - "output_key": { - "display_name": "Output Key", - "info": "The key to use to get the output from the agent.", - "advanced": True, - }, - "memory": { - "display_name": "Memory", - "info": "Memory to use for the agent.", - }, - "tools": { - "display_name": "Tools", - "info": "Tools the agent can use.", - }, - "input_value": { - "display_name": "Input", - "info": "Input text to pass to the agent.", - }, + def get_agent_kwargs(self, flatten: bool = False) -> dict: + base = { + "handle_parsing_errors": self.handle_parsing_errors, + "verbose": self.verbose, + "allow_dangerous_code": True, } + agent_kwargs = { + "handle_parsing_errors": self.handle_parsing_errors, + "max_iterations": self.max_iterations, + } + if flatten: + return { + **base, + **agent_kwargs, + } + return {**base, "agent_executor_kwargs": agent_kwargs} + + +class LCToolsAgentComponent(LCAgentComponent): + _base_inputs = LCAgentComponent._base_inputs + [ + HandleInput( + name="tools", + display_name="Tools", + input_types=["Tool"], + is_list=True, + ), + HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), + ] - async def run_agent( - self, - agent: Union[Runnable, BaseSingleActionAgent, BaseMultiActionAgent, AgentExecutor], - inputs: str, - tools: List[Tool], - message_history: Optional[List[Data]] = None, - handle_parsing_errors: bool = True, - output_key: str = "output", - ) -> Text: - if isinstance(agent, AgentExecutor): - runnable = agent - else: - runnable = AgentExecutor.from_agent_and_tools( - agent=agent, # type: ignore - tools=tools, - verbose=True, - handle_parsing_errors=handle_parsing_errors, - ) - input_dict: dict[str, str | list[BaseMessage]] = {"input": inputs} - if message_history: - input_dict["chat_history"] = data_to_messages(message_history) - result = await runnable.ainvoke(input_dict) - self.status = result - if output_key in result: - return cast(str, result.get(output_key)) - elif "output" not in result: - if output_key != "output": - raise ValueError(f"Output key not found in result. Tried '{output_key}' and 'output'.") - else: - raise ValueError("Output key not found in result. Tried 'output'.") + def build_agent(self) -> AgentExecutor: + agent = self.creat_agent_runnable() + return AgentExecutor.from_agent_and_tools( + agent=RunnableAgent(runnable=agent, input_keys_arg=["input"], return_keys_arg=["output"]), + tools=self.tools, + **self.get_agent_kwargs(flatten=True), + ) - return cast(str, result.get("output")) + @abstractmethod + def creat_agent_runnable(self) -> Runnable: + """Create the agent.""" + pass diff --git a/src/backend/base/langflow/base/vectorstores/model.py b/src/backend/base/langflow/base/vectorstores/model.py index a00b56e99c4..b989c0ecf6b 100644 --- a/src/backend/base/langflow/base/vectorstores/model.py +++ b/src/backend/base/langflow/base/vectorstores/model.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, cast from langchain_core.documents import Document from loguru import logger @@ -23,11 +23,16 @@ class LCVectorStoreComponent(Component): name="search_results", method="search_documents", ), + Output( + display_name="Vector Store", + name="vector_store", + method="cast_vector_store", + ), ] def _validate_outputs(self): # At least these three outputs must be defined - required_output_methods = ["build_base_retriever", "search_documents"] + required_output_methods = ["build_base_retriever", "search_documents", "build_vector_store"] output_names = [output.name for output in self.outputs] for method_name in required_output_methods: if method_name not in output_names: @@ -67,6 +72,9 @@ def search_with_vector_store( self.status = data return data + def cast_vector_store(self) -> VectorStore: + return cast(VectorStore, self.build_vector_store()) + def build_vector_store(self) -> VectorStore: """ Builds the Vector Store object.c diff --git a/src/backend/base/langflow/components/agents/CSVAgent.py b/src/backend/base/langflow/components/agents/CSVAgent.py index b336045703a..c8868552116 100644 --- a/src/backend/base/langflow/components/agents/CSVAgent.py +++ b/src/backend/base/langflow/components/agents/CSVAgent.py @@ -1,35 +1,27 @@ from langchain_experimental.agents.agent_toolkits.csv.base import create_csv_agent -from langflow.custom import CustomComponent -from langflow.field_typing import AgentExecutor, LanguageModel +from langflow.base.agents.agent import LCAgentComponent +from langflow.field_typing import AgentExecutor +from langflow.inputs import HandleInput, FileInput, DropdownInput -class CSVAgentComponent(CustomComponent): +class CSVAgentComponent(LCAgentComponent): display_name = "CSVAgent" description = "Construct a CSV agent from a CSV and tools." documentation = "https://python.langchain.com/docs/modules/agents/toolkits/csv" name = "CSVAgent" - def build_config(self): - return { - "llm": {"display_name": "LLM", "type": LanguageModel}, - "path": {"display_name": "Path", "field_type": "file", "suffixes": [".csv"], "file_types": [".csv"]}, - "handle_parsing_errors": {"display_name": "Handle Parse Errors", "advanced": True}, - "agent_type": { - "display_name": "Agent Type", - "options": ["zero-shot-react-description", "openai-functions", "openai-tools"], - "advanced": True, - }, - } + inputs = LCAgentComponent._base_inputs + [ + FileInput(name="path", display_name="File Path", file_types=["csv"], required=True), + HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), + DropdownInput( + name="agent_type", + display_name="Agent Type", + advanced=True, + options=["zero-shot-react-description", "openai-functions", "openai-tools"], + value="openai-tools", + ), + ] - def build( - self, llm: LanguageModel, path: str, handle_parsing_errors: bool = True, agent_type: str = "openai-tools" - ) -> AgentExecutor: - # Instantiate and return the CSV agent class with the provided llm and path - return create_csv_agent( - llm=llm, - path=path, - agent_type=agent_type, - verbose=True, - agent_executor_kwargs=dict(handle_parsing_errors=handle_parsing_errors), - ) + def build_agent(self) -> AgentExecutor: + return create_csv_agent(llm=self.llm, path=self.path, agent_type=self.agent_type, **self.get_agent_kwargs()) diff --git a/src/backend/base/langflow/components/agents/JsonAgent.py b/src/backend/base/langflow/components/agents/JsonAgent.py index 3746e989d51..f79d2dba5e0 100644 --- a/src/backend/base/langflow/components/agents/JsonAgent.py +++ b/src/backend/base/langflow/components/agents/JsonAgent.py @@ -1,25 +1,31 @@ +from pathlib import Path + +import yaml from langchain.agents import AgentExecutor from langchain_community.agent_toolkits import create_json_agent from langchain_community.agent_toolkits.json.toolkit import JsonToolkit +from langchain_community.tools.json.tool import JsonSpec -from langflow.custom import CustomComponent -from langflow.field_typing import LanguageModel +from langflow.base.agents.agent import LCAgentComponent +from langflow.inputs import HandleInput, FileInput -class JsonAgentComponent(CustomComponent): +class JsonAgentComponent(LCAgentComponent): display_name = "JsonAgent" description = "Construct a json agent from an LLM and tools." name = "JsonAgent" - def build_config(self): - return { - "llm": {"display_name": "LLM"}, - "toolkit": {"display_name": "Toolkit"}, - } + inputs = LCAgentComponent._base_inputs + [ + FileInput(name="path", display_name="File Path", file_types=["json", "yaml", "yml"], required=True), + HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), + ] + + def build_agent(self) -> AgentExecutor: + if self.path.endswith("yaml") or self.path.endswith("yml"): + yaml_dict = yaml.load(open(self.path, "r"), Loader=yaml.FullLoader) + spec = JsonSpec(dict_=yaml_dict) + else: + spec = JsonSpec.from_file(Path(self.path)) + toolkit = JsonToolkit(spec=spec) - def build( - self, - llm: LanguageModel, - toolkit: JsonToolkit, - ) -> AgentExecutor: - return create_json_agent(llm=llm, toolkit=toolkit) + return create_json_agent(llm=self.llm, toolkit=toolkit, **self.get_agent_kwargs()) diff --git a/src/backend/base/langflow/components/agents/OpenAIToolsAgent.py b/src/backend/base/langflow/components/agents/OpenAIToolsAgent.py new file mode 100644 index 00000000000..45eafc430e7 --- /dev/null +++ b/src/backend/base/langflow/components/agents/OpenAIToolsAgent.py @@ -0,0 +1,36 @@ +from langchain.agents import create_openai_tools_agent +from langchain_core.prompts import ChatPromptTemplate, PromptTemplate, HumanMessagePromptTemplate + +from langflow.base.agents.agent import LCToolsAgentComponent +from langflow.inputs import MultilineInput + + +class OpenAIToolsAgentComponent(LCToolsAgentComponent): + display_name: str = "OpenAI Tools Agent" + description: str = "Agent that uses tools via openai-tools." + icon = "LangChain" + beta = True + name = "OpenAIToolsAgent" + + inputs = LCToolsAgentComponent._base_inputs + [ + MultilineInput( + name="system_prompt", + display_name="System Prompt", + info="System prompt for the agent.", + value="You are a helpful assistant", + ), + MultilineInput( + name="user_prompt", display_name="Prompt", info="This prompt must contain 'input' key.", value="{input}" + ), + ] + + def creat_agent_runnable(self): + if "input" not in self.user_prompt: + raise ValueError("Prompt must contain 'input' key.") + messages = [ + ("system", self.system_prompt), + HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=["input"], template=self.user_prompt)), + ("placeholder", "{agent_scratchpad}"), + ] + prompt = ChatPromptTemplate.from_messages(messages) + return create_openai_tools_agent(self.llm, self.tools, prompt) diff --git a/src/backend/base/langflow/components/agents/OpenAPIAgent.py b/src/backend/base/langflow/components/agents/OpenAPIAgent.py new file mode 100644 index 00000000000..f453ada491c --- /dev/null +++ b/src/backend/base/langflow/components/agents/OpenAPIAgent.py @@ -0,0 +1,42 @@ +from pathlib import Path + +import yaml +from langchain.agents import AgentExecutor +from langchain_community.agent_toolkits import create_openapi_agent +from langchain_community.tools.json.tool import JsonSpec +from langchain_community.agent_toolkits.openapi.toolkit import OpenAPIToolkit + +from langflow.base.agents.agent import LCAgentComponent +from langflow.inputs import BoolInput, HandleInput, FileInput +from langchain_community.utilities.requests import TextRequestsWrapper + + +class OpenAPIAgentComponent(LCAgentComponent): + display_name = "OpenAPI Agent" + description = "Agent to interact with OpenAPI API." + name = "OpenAPIAgent" + + inputs = LCAgentComponent._base_inputs + [ + FileInput(name="path", display_name="File Path", file_types=["json", "yaml", "yml"], required=True), + HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), + BoolInput(name="allow_dangerous_requests", display_name="Allow Dangerous Requests", value=False, required=True), + ] + + def build_agent(self) -> AgentExecutor: + if self.path.endswith("yaml") or self.path.endswith("yml"): + yaml_dict = yaml.load(open(self.path, "r"), Loader=yaml.FullLoader) + spec = JsonSpec(dict_=yaml_dict) + else: + spec = JsonSpec.from_file(Path(self.path)) + requests_wrapper = TextRequestsWrapper() + toolkit = OpenAPIToolkit.from_llm( + llm=self.llm, + json_spec=spec, + requests_wrapper=requests_wrapper, + allow_dangerous_requests=self.allow_dangerous_requests, + ) + + agent_args = self.get_agent_kwargs() + agent_args["max_iterations"] = agent_args["agent_executor_kwargs"]["max_iterations"] + del agent_args["agent_executor_kwargs"]["max_iterations"] + return create_openapi_agent(llm=self.llm, toolkit=toolkit, **agent_args) diff --git a/src/backend/base/langflow/components/agents/SQLAgent.py b/src/backend/base/langflow/components/agents/SQLAgent.py index ddc6f49c603..9db94deffde 100644 --- a/src/backend/base/langflow/components/agents/SQLAgent.py +++ b/src/backend/base/langflow/components/agents/SQLAgent.py @@ -1,32 +1,26 @@ -from typing import Callable, Union - from langchain.agents import AgentExecutor from langchain_community.agent_toolkits import SQLDatabaseToolkit from langchain_community.agent_toolkits.sql.base import create_sql_agent from langchain_community.utilities import SQLDatabase -from langflow.custom import CustomComponent -from langflow.field_typing import LanguageModel +from langflow.base.agents.agent import LCAgentComponent +from langflow.inputs import MessageTextInput, HandleInput -class SQLAgentComponent(CustomComponent): +class SQLAgentComponent(LCAgentComponent): display_name = "SQLAgent" description = "Construct an SQL agent from an LLM and tools." name = "SQLAgent" - def build_config(self): - return { - "llm": {"display_name": "LLM"}, - "database_uri": {"display_name": "Database URI"}, - "verbose": {"display_name": "Verbose", "value": False, "advanced": True}, - } + inputs = LCAgentComponent._base_inputs + [ + MessageTextInput(name="database_uri", display_name="Database URI", required=True), + HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), + ] - def build( - self, - llm: LanguageModel, - database_uri: str, - verbose: bool = False, - ) -> Union[AgentExecutor, Callable]: - db = SQLDatabase.from_uri(database_uri) - toolkit = SQLDatabaseToolkit(db=db, llm=llm) - return create_sql_agent(llm=llm, toolkit=toolkit) + def build_agent(self) -> AgentExecutor: + db = SQLDatabase.from_uri(self.database_uri) + toolkit = SQLDatabaseToolkit(db=db, llm=self.llm) + agent_args = self.get_agent_kwargs() + agent_args["max_iterations"] = agent_args["agent_executor_kwargs"]["max_iterations"] + del agent_args["agent_executor_kwargs"]["max_iterations"] + return create_sql_agent(llm=self.llm, toolkit=toolkit, **agent_args) diff --git a/src/backend/base/langflow/components/agents/ToolCallingAgent.py b/src/backend/base/langflow/components/agents/ToolCallingAgent.py index c08c97ca55c..f77d4ac6997 100644 --- a/src/backend/base/langflow/components/agents/ToolCallingAgent.py +++ b/src/backend/base/langflow/components/agents/ToolCallingAgent.py @@ -1,111 +1,35 @@ -from typing import Dict, List, cast +from langchain.agents import create_tool_calling_agent +from langchain_core.prompts import ChatPromptTemplate, PromptTemplate, HumanMessagePromptTemplate +from langflow.base.agents.agent import LCToolsAgentComponent +from langflow.inputs import MultilineInput -from langchain.agents import AgentExecutor, BaseSingleActionAgent -from langchain.agents.tool_calling_agent.base import create_tool_calling_agent -from langchain_core.prompts import ChatPromptTemplate -from langflow.custom import Component -from langflow.io import BoolInput, HandleInput, MessageTextInput, Output -from langflow.schema import Data -from langflow.schema.message import Message - - -class ToolCallingAgentComponent(Component): +class ToolCallingAgentComponent(LCToolsAgentComponent): display_name: str = "Tool Calling Agent" - description: str = "Agent that uses tools. Only models that are compatible with function calling are supported." + description: str = "Agent that uses tools" icon = "LangChain" beta = True name = "ToolCallingAgent" - inputs = [ - MessageTextInput( + inputs = LCToolsAgentComponent._base_inputs + [ + MultilineInput( name="system_prompt", display_name="System Prompt", info="System prompt for the agent.", value="You are a helpful assistant", ), - MessageTextInput( - name="input_value", - display_name="Inputs", - info="Input text to pass to the agent.", - ), - MessageTextInput( - name="user_prompt", - display_name="Prompt", - info="This prompt must contain 'input' key.", - value="{input}", - advanced=True, - ), - BoolInput( - name="handle_parsing_errors", - display_name="Handle Parsing Errors", - info="If True, the agent will handle parsing errors. If False, the agent will raise an error.", - advanced=True, - value=True, - ), - HandleInput( - name="memory", - display_name="Memory", - input_types=["Data"], - info="Memory to use for the agent.", - ), - HandleInput( - name="tools", - display_name="Tools", - input_types=["Tool"], - is_list=True, - ), - HandleInput( - name="llm", - display_name="LLM", - input_types=["LanguageModel"], + MultilineInput( + name="user_prompt", display_name="Prompt", info="This prompt must contain 'input' key.", value="{input}" ), ] - outputs = [ - Output(display_name="Text", name="text_output", method="run_agent"), - ] - - async def run_agent(self) -> Message: + def creat_agent_runnable(self): if "input" not in self.user_prompt: raise ValueError("Prompt must contain 'input' key.") messages = [ ("system", self.system_prompt), - ( - "placeholder", - "{chat_history}", - ), - ("human", self.user_prompt), + HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=["input"], template=self.user_prompt)), ("placeholder", "{agent_scratchpad}"), ] prompt = ChatPromptTemplate.from_messages(messages) - agent = create_tool_calling_agent(self.llm, self.tools, prompt) - - runnable = AgentExecutor.from_agent_and_tools( - agent=cast(BaseSingleActionAgent, agent), - tools=self.tools, - verbose=True, - handle_parsing_errors=self.handle_parsing_errors, - ) - input_dict: dict[str, str | list[Dict[str, str]]] = {"input": self.input_value} - if hasattr(self, "memory") and self.memory: - input_dict["chat_history"] = self.convert_chat_history(self.memory) - result = await runnable.ainvoke(input_dict) - - if "output" not in result: - raise ValueError("Output key not found in result. Tried 'output'.") - - results = result["output"] - if isinstance(results, list): - result_string = "\n".join([r["text"] for r in results if "text" in r and r.get("type") == "text"]) - else: - result_string = results - self.status = result_string - return Message(text=result_string) - - def convert_chat_history(self, chat_history: List[Data]) -> List[Dict[str, str]]: - messages = [] - for item in chat_history: - role = "user" if item.sender == "User" else "assistant" - messages.append({"role": role, "content": item.text}) - return messages + return create_tool_calling_agent(self.llm, self.tools, prompt) diff --git a/src/backend/base/langflow/components/agents/VectorStoreAgent.py b/src/backend/base/langflow/components/agents/VectorStoreAgent.py index 1d3a5e890f9..9a66c08a68b 100644 --- a/src/backend/base/langflow/components/agents/VectorStoreAgent.py +++ b/src/backend/base/langflow/components/agents/VectorStoreAgent.py @@ -1,26 +1,19 @@ -from typing import Callable, Union - from langchain.agents import AgentExecutor, create_vectorstore_agent from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreToolkit - -from langflow.custom import CustomComponent -from langflow.field_typing import LanguageModel +from langflow.base.agents.agent import LCAgentComponent +from langflow.inputs import HandleInput -class VectorStoreAgentComponent(CustomComponent): +class VectorStoreAgentComponent(LCAgentComponent): display_name = "VectorStoreAgent" description = "Construct an agent from a Vector Store." name = "VectorStoreAgent" - def build_config(self): - return { - "llm": {"display_name": "LLM"}, - "vector_store_toolkit": {"display_name": "Vector Store Info"}, - } + inputs = LCAgentComponent._base_inputs + [ + HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), + HandleInput(name="vectorstore", display_name="Vector Store", input_types=["VectorStoreInfo"], required=True), + ] - def build( - self, - llm: LanguageModel, - vector_store_toolkit: VectorStoreToolkit, - ) -> Union[AgentExecutor, Callable]: - return create_vectorstore_agent(llm=llm, toolkit=vector_store_toolkit) + def build_agent(self) -> AgentExecutor: + toolkit = VectorStoreToolkit(vectorstore_info=self.vectorstore, llm=self.llm) + return create_vectorstore_agent(llm=self.llm, toolkit=toolkit, **self.get_agent_kwargs()) diff --git a/src/backend/base/langflow/components/agents/VectorStoreRouterAgent.py b/src/backend/base/langflow/components/agents/VectorStoreRouterAgent.py index 7750b2e01ac..727379fc963 100644 --- a/src/backend/base/langflow/components/agents/VectorStoreRouterAgent.py +++ b/src/backend/base/langflow/components/agents/VectorStoreRouterAgent.py @@ -1,22 +1,27 @@ -from typing import Callable - from langchain.agents import create_vectorstore_router_agent from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreRouterToolkit -from langflow.field_typing import LanguageModel -from langflow.custom import CustomComponent +from langflow.base.agents.agent import LCAgentComponent +from langchain.agents import AgentExecutor +from langflow.inputs import HandleInput -class VectorStoreRouterAgentComponent(CustomComponent): +class VectorStoreRouterAgentComponent(LCAgentComponent): display_name = "VectorStoreRouterAgent" description = "Construct an agent from a Vector Store Router." name = "VectorStoreRouterAgent" - def build_config(self): - return { - "llm": {"display_name": "LLM"}, - "vectorstoreroutertoolkit": {"display_name": "Vector Store Router Toolkit"}, - } + inputs = LCAgentComponent._base_inputs + [ + HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True), + HandleInput( + name="vectorstores", + display_name="Vector Stores", + input_types=["VectorStoreInfo"], + is_list=True, + required=True, + ), + ] - def build(self, llm: LanguageModel, vectorstoreroutertoolkit: VectorStoreRouterToolkit) -> Callable: - return create_vectorstore_router_agent(llm=llm, toolkit=vectorstoreroutertoolkit) + def build_agent(self) -> AgentExecutor: + toolkit = VectorStoreRouterToolkit(vectorstores=self.vectorstores, llm=self.llm) + return create_vectorstore_router_agent(llm=self.llm, toolkit=toolkit, **self.get_agent_kwargs()) diff --git a/src/backend/base/langflow/components/agents/XMLAgent.py b/src/backend/base/langflow/components/agents/XMLAgent.py index 7972f8dfb89..cc8a50e49e5 100644 --- a/src/backend/base/langflow/components/agents/XMLAgent.py +++ b/src/backend/base/langflow/components/agents/XMLAgent.py @@ -1,111 +1,52 @@ -from typing import List, Optional - from langchain.agents import create_xml_agent -from langchain_core.prompts import ChatPromptTemplate +from langchain_core.prompts import ChatPromptTemplate, PromptTemplate, HumanMessagePromptTemplate -from langflow.base.agents.agent import LCAgentComponent -from langflow.field_typing import LanguageModel, Text, Tool -from langflow.schema import Data +from langflow.base.agents.agent import LCToolsAgentComponent +from langflow.inputs import MultilineInput -class XMLAgentComponent(LCAgentComponent): - display_name = "XMLAgent" - description = "Construct an XML agent from an LLM and tools." +class XMLAgentComponent(LCToolsAgentComponent): + display_name: str = "XML Agent" + description: str = "Agent that uses tools formatting instructions as xml to the Language Model." + icon = "LangChain" + beta = True name = "XMLAgent" - def build_config(self): - return { - "llm": {"display_name": "LLM"}, - "tools": {"display_name": "Tools"}, - "user_prompt": { - "display_name": "Prompt", - "multiline": True, - "info": "This prompt must contain 'tools' and 'agent_scratchpad' keys.", - "value": """You are a helpful assistant. Help the user answer any questions. - - You have access to the following tools: - - {tools} - - In order to use a tool, you can use and tags. You will then get back a response in the form - For example, if you have a tool called 'search' that could run a google search, in order to search for the weather in SF you would respond: - - searchweather in SF - 64 degrees - - When you are done, respond with a final answer between . For example: - - The weather in SF is 64 degrees - - Begin! - - Previous Conversation: - {chat_history} - - Question: {input} - {agent_scratchpad}""", - }, - "system_message": { - "display_name": "System Message", - "info": "System message to be passed to the LLM.", - "advanced": True, - }, - "tool_template": { - "display_name": "Tool Template", - "info": "Template for rendering tools in the prompt. Tools have 'name' and 'description' keys.", - "advanced": True, - }, - "handle_parsing_errors": { - "display_name": "Handle Parsing Errors", - "info": "If True, the agent will handle parsing errors. If False, the agent will raise an error.", - "advanced": True, - }, - "message_history": { - "display_name": "Message History", - "info": "Message history to pass to the agent.", - }, - "input_value": { - "display_name": "Inputs", - "info": "Input text to pass to the agent.", - }, - } - - async def build( - self, - input_value: str, - llm: LanguageModel, - tools: List[Tool], - user_prompt: str = "{input}", - system_message: str = "You are a helpful assistant", - message_history: Optional[List[Data]] = None, - tool_template: str = "{name}: {description}", - handle_parsing_errors: bool = True, - ) -> Text: - if "input" not in user_prompt: - raise ValueError("Prompt must contain 'input' key.") - - def render_tool_description(tools): - return "\n".join( - [tool_template.format(name=tool.name, description=tool.description, args=tool.args) for tool in tools] - ) + inputs = LCToolsAgentComponent._base_inputs + [ + MultilineInput( + name="user_prompt", + display_name="Prompt", + value=""" +You are a helpful assistant. Help the user answer any questions. + +You have access to the following tools: + +{tools} + +In order to use a tool, you can use and tags. You will then get back a response in the form + +For example, if you have a tool called 'search' that could run a google search, in order to search for the weather in SF you would respond: + +searchweather in SF + +64 degrees + +When you are done, respond with a final answer between . For example: + +The weather in SF is 64 degrees + +Begin! + +Question: {input} + +{agent_scratchpad} + """, + ), + ] + def creat_agent_runnable(self): messages = [ - ("system", system_message), - ( - "placeholder", - "{chat_history}", - ), - ("human", user_prompt), - ("placeholder", "{agent_scratchpad}"), + HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=["input"], template=self.user_prompt)) ] prompt = ChatPromptTemplate.from_messages(messages) - agent = create_xml_agent(llm, tools, prompt, tools_renderer=render_tool_description) - result = await self.run_agent( - agent=agent, - inputs=input_value, - tools=tools, - message_history=message_history, - handle_parsing_errors=handle_parsing_errors, - ) - self.status = result - return result + return create_xml_agent(self.llm, self.tools, prompt) diff --git a/src/backend/base/langflow/components/deactivated/AgentComponent.py b/src/backend/base/langflow/components/deactivated/AgentComponent.py deleted file mode 100644 index a89213feadc..00000000000 --- a/src/backend/base/langflow/components/deactivated/AgentComponent.py +++ /dev/null @@ -1,186 +0,0 @@ -from typing import Any, List, Optional, cast - -from langchain_core.prompts import ChatPromptTemplate -from langchain_core.prompts.chat import HumanMessagePromptTemplate, SystemMessagePromptTemplate - -from langflow.base.agents.agent import LCAgentComponent -from langflow.base.agents.utils import AGENTS, AgentSpec, get_agents_list -from langflow.field_typing import LanguageModel, Text, Tool -from langflow.schema import Data -from langflow.schema.dotdict import dotdict - - -class AgentComponent(LCAgentComponent): - display_name = "Agent" - description = "Run any LangChain agent using a simplified interface." - field_order = [ - "agent_name", - "llm", - "tools", - "prompt", - "tool_template", - "handle_parsing_errors", - "memory", - "input_value", - ] - name = "AgentComponent" - - def build_config(self): - return { - "agent_name": { - "display_name": "Agent", - "info": "The agent to use.", - "refresh_button": True, - "real_time_refresh": True, - "options": get_agents_list(), - }, - "llm": {"display_name": "LLM"}, - "tools": {"display_name": "Tools"}, - "user_prompt": { - "display_name": "Prompt", - "multiline": True, - "info": "This prompt must contain 'tools' and 'agent_scratchpad' keys.", - }, - "system_message": { - "display_name": "System Message", - "info": "System message to be passed to the LLM.", - "advanced": True, - }, - "tool_template": { - "display_name": "Tool Template", - "info": "Template for rendering tools in the prompt. Tools have 'name' and 'description' keys.", - "advanced": True, - }, - "handle_parsing_errors": { - "display_name": "Handle Parsing Errors", - "info": "If True, the agent will handle parsing errors. If False, the agent will raise an error.", - "advanced": True, - }, - "message_history": { - "display_name": "Message History", - "info": "Message history to pass to the agent.", - }, - "input_value": { - "display_name": "Input", - "info": "Input text to pass to the agent.", - }, - "langchain_hub_api_key": { - "display_name": "LangChain Hub API Key", - "info": "API key to use for LangChain Hub. If provided, prompts will be fetched from LangChain Hub.", - "advanced": True, - }, - } - - def get_system_and_user_message_from_prompt(self, prompt: Any): - """ - Extracts the system message and user prompt from a given prompt object. - - Args: - prompt (Any): The prompt object from which to extract the system message and user prompt. - - Returns: - Tuple[Optional[str], Optional[str]]: A tuple containing the system message and user prompt. - If the prompt object does not have any messages, both values will be None. - """ - if hasattr(prompt, "messages"): - system_message = None - user_prompt = None - for message in prompt.messages: - if isinstance(message, SystemMessagePromptTemplate): - s_prompt = message.prompt - if isinstance(s_prompt, list): - s_template = " ".join([cast(str, s.template) for s in s_prompt if hasattr(s, "template")]) - elif hasattr(s_prompt, "template"): - s_template = s_prompt.template - system_message = s_template - elif isinstance(message, HumanMessagePromptTemplate): - h_prompt = message.prompt - if isinstance(h_prompt, list): - h_template = " ".join([cast(str, h.template) for h in h_prompt if hasattr(h, "template")]) - elif hasattr(h_prompt, "template"): - h_template = h_prompt.template - user_prompt = h_template - return system_message, user_prompt - return None, None - - def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None): - """ - Updates the build configuration based on the provided field value and field name. - - Args: - build_config (dotdict): The build configuration to be updated. - field_value (Any): The value of the field being updated. - field_name (Text | None, optional): The name of the field being updated. Defaults to None. - - Returns: - dotdict: The updated build configuration. - """ - if field_name == "agent": - build_config["agent"]["options"] = get_agents_list() - if field_value in AGENTS: - # if langchain_hub_api_key is provided, fetch the prompt from LangChain Hub - if build_config["langchain_hub_api_key"]["value"] and AGENTS[field_value].hub_repo: - from langchain import hub - - hub_repo: str | None = AGENTS[field_value].hub_repo - if hub_repo: - hub_api_key: str = build_config["langchain_hub_api_key"]["value"] - prompt = hub.pull(hub_repo, api_key=hub_api_key) - system_message, user_prompt = self.get_system_and_user_message_from_prompt(prompt) - if system_message: - build_config["system_message"]["value"] = system_message - if user_prompt: - build_config["user_prompt"]["value"] = user_prompt - - if AGENTS[field_value].prompt: - build_config["user_prompt"]["value"] = AGENTS[field_value].prompt - else: - build_config["user_prompt"]["value"] = "{input}" - fields = AGENTS[field_value].fields - for field in ["llm", "tools", "prompt", "tools_renderer"]: - if field not in fields: - build_config[field]["show"] = False - return build_config - - async def build( - self, - agent_name: str, - input_value: str, - llm: LanguageModel, - tools: List[Tool], - system_message: str = "You are a helpful assistant. Help the user answer any questions.", - user_prompt: str = "{input}", - message_history: Optional[List[Data]] = None, - tool_template: str = "{name}: {description}", - handle_parsing_errors: bool = True, - ) -> Text: - agent_spec: Optional[AgentSpec] = AGENTS.get(agent_name) - if agent_spec is None: - raise ValueError(f"{agent_name} not found.") - - def render_tool_description(tools): - return "\n".join( - [tool_template.format(name=tool.name, description=tool.description, args=tool.args) for tool in tools] - ) - - messages = [ - ("system", system_message), - ( - "placeholder", - "{chat_history}", - ), - ("human", user_prompt), - ("placeholder", "{agent_scratchpad}"), - ] - prompt = ChatPromptTemplate.from_messages(messages) - agent_func = agent_spec.func - agent = agent_func(llm, tools, prompt, render_tool_description, True) - result = await self.run_agent( - agent=agent, - inputs=input_value, - tools=tools, - message_history=message_history, - handle_parsing_errors=handle_parsing_errors, - ) - self.status = result - return result diff --git a/src/backend/base/langflow/components/deactivated/__init__.py b/src/backend/base/langflow/components/deactivated/__init__.py index 51ef2e465b9..8be439d874b 100644 --- a/src/backend/base/langflow/components/deactivated/__init__.py +++ b/src/backend/base/langflow/components/deactivated/__init__.py @@ -1,4 +1,3 @@ -from .AgentComponent import AgentComponent from .ExtractKeyFromData import ExtractKeyFromDataComponent from .ListFlows import ListFlowsComponent from .MergeData import MergeDataComponent @@ -6,7 +5,6 @@ from .SubFlow import SubFlowComponent __all__ = [ - "AgentComponent", "ConditionalRouterComponent", "ExtractKeyFromDataComponent", "FlowToolComponent", diff --git a/src/backend/base/langflow/components/prototypes/RunnableExecutor.py b/src/backend/base/langflow/components/prototypes/RunnableExecutor.py index ab85e1e9c86..8aec1886f95 100644 --- a/src/backend/base/langflow/components/prototypes/RunnableExecutor.py +++ b/src/backend/base/langflow/components/prototypes/RunnableExecutor.py @@ -1,43 +1,44 @@ -from langchain_core.runnables import Runnable +from langflow.custom import Component +from langflow.inputs import HandleInput, MessageTextInput +from langflow.schema.message import Message +from langflow.template import Output -from langflow.custom import CustomComponent -from langflow.field_typing import Text - -class RunnableExecComponent(CustomComponent): +class RunnableExecComponent(Component): description = "Execute a runnable. It will try to guess the input and output keys." display_name = "Runnable Executor" name = "RunnableExecutor" beta: bool = True - field_order = [ - "input_key", - "output_key", - "input_value", - "runnable", + + inputs = [ + MessageTextInput(name="input_value", display_name="Input", required=True), + HandleInput( + name="runnable", + display_name="Agent Executor", + input_types=["Chain", "AgentExecutor", "Agent", "Runnable"], + required=True, + ), + MessageTextInput( + name="input_key", + display_name="Input Key", + value="input", + advanced=True, + ), + MessageTextInput( + name="output_key", + display_name="Output Key", + value="output", + advanced=True, + ), ] - def build_config(self): - return { - "input_key": { - "display_name": "Input Key", - "info": "The key to use for the input.", - "advanced": True, - }, - "input_value": { - "display_name": "Inputs", - "info": "The inputs to pass to the runnable.", - }, - "runnable": { - "display_name": "Runnable", - "info": "The runnable to execute.", - "input_types": ["Chain", "AgentExecutor", "Agent", "Runnable"], - }, - "output_key": { - "display_name": "Output Key", - "info": "The key to use for the output.", - "advanced": True, - }, - } + outputs = [ + Output( + display_name="Text", + name="text", + method="build_executor", + ), + ] def get_output(self, result, input_key, output_key): """ @@ -107,16 +108,10 @@ def get_input_dict(self, runnable, input_key, input_value): status = f"Warning: The input key is not '{input_key}'. The input key is '{runnable.input_keys}'." return input_dict, status - def build( - self, - input_value: Text, - runnable: Runnable, - input_key: str = "input", - output_key: str = "output", - ) -> Text: - input_dict, status = self.get_input_dict(runnable, input_key, input_value) - result = runnable.invoke(input_dict) - result_value, _status = self.get_output(result, input_key, output_key) + def build_executor(self) -> Message: + input_dict, status = self.get_input_dict(self.runnable, self.input_key, self.input_value) + result = self.runnable.invoke(input_dict) + result_value, _status = self.get_output(result, self.input_key, self.output_key) status += _status status += f"\n\nOutput: {result_value}\n\nRaw Output: {result}" self.status = status diff --git a/src/backend/base/langflow/components/toolkits/JsonToolkit.py b/src/backend/base/langflow/components/toolkits/JsonToolkit.py deleted file mode 100644 index 96dc3324853..00000000000 --- a/src/backend/base/langflow/components/toolkits/JsonToolkit.py +++ /dev/null @@ -1,30 +0,0 @@ -from pathlib import Path - -import yaml -from langchain_community.agent_toolkits.json.toolkit import JsonToolkit -from langchain_community.tools.json.tool import JsonSpec - -from langflow.custom import CustomComponent - - -class JsonToolkitComponent(CustomComponent): - display_name = "JsonToolkit" - description = "Toolkit for interacting with a JSON spec." - name = "JsonToolkit" - - def build_config(self): - return { - "path": { - "display_name": "Path", - "field_type": "file", - "file_types": ["json", "yaml", "yml"], - }, - } - - def build(self, path: str) -> JsonToolkit: - if path.endswith("yaml") or path.endswith("yml"): - yaml_dict = yaml.load(open(path, "r"), Loader=yaml.FullLoader) - spec = JsonSpec(dict_=yaml_dict) - else: - spec = JsonSpec.from_file(Path(path)) - return JsonToolkit(spec=spec) diff --git a/src/backend/base/langflow/components/toolkits/OpenAPIToolkit.py b/src/backend/base/langflow/components/toolkits/OpenAPIToolkit.py deleted file mode 100644 index 5a72c7f2f3c..00000000000 --- a/src/backend/base/langflow/components/toolkits/OpenAPIToolkit.py +++ /dev/null @@ -1,35 +0,0 @@ -from pathlib import Path - -import yaml -from langchain_community.agent_toolkits.openapi.toolkit import BaseToolkit, OpenAPIToolkit -from langchain_community.tools.json.tool import JsonSpec -from langchain_community.utilities.requests import TextRequestsWrapper - -from langflow.custom import CustomComponent -from langflow.field_typing import LanguageModel - - -class OpenAPIToolkitComponent(CustomComponent): - display_name = "OpenAPIToolkit" - description = "Toolkit for interacting with an OpenAPI API." - name = "OpenAPIToolkit" - - def build_config(self): - return { - "json_agent": {"display_name": "JSON Agent"}, - "requests_wrapper": {"display_name": "Text Requests Wrapper"}, - } - - def build(self, llm: LanguageModel, path: str, allow_dangerous_requests: bool = False) -> BaseToolkit: - if path.endswith("yaml") or path.endswith("yml"): - yaml_dict = yaml.load(open(path, "r"), Loader=yaml.FullLoader) - spec = JsonSpec(dict_=yaml_dict) - else: - spec = JsonSpec.from_file(Path(path)) - requests_wrapper = TextRequestsWrapper() - return OpenAPIToolkit.from_llm( - llm=llm, - json_spec=spec, - requests_wrapper=requests_wrapper, - allow_dangerous_requests=allow_dangerous_requests, - ) diff --git a/src/backend/base/langflow/components/toolkits/VectorStoreInfo.py b/src/backend/base/langflow/components/toolkits/VectorStoreInfo.py index c1c47e45f02..41ab7ef7364 100644 --- a/src/backend/base/langflow/components/toolkits/VectorStoreInfo.py +++ b/src/backend/base/langflow/components/toolkits/VectorStoreInfo.py @@ -1,25 +1,44 @@ from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo -from langchain_core.vectorstores import VectorStore +from langflow.custom import Component +from langflow.inputs import HandleInput, MultilineInput, MessageTextInput +from langflow.template import Output -from langflow.custom import CustomComponent - -class VectorStoreInfoComponent(CustomComponent): +class VectorStoreInfoComponent(Component): display_name = "VectorStoreInfo" description = "Information about a VectorStore" name = "VectorStoreInfo" - def build_config(self): - return { - "vectorstore": {"display_name": "VectorStore"}, - "description": {"display_name": "Description", "multiline": True}, - "name": {"display_name": "Name"}, - } + inputs = [ + MessageTextInput( + name="vectorstore_name", + display_name="Name", + info="Name of the VectorStore", + required=True, + ), + MultilineInput( + name="vectorstore_description", + display_name="Description", + info="Description of the VectorStore", + required=True, + ), + HandleInput( + name="input_vectorstore", + display_name="Vector Store", + input_types=["VectorStore"], + required=True, + ), + ] - def build( - self, - vectorstore: VectorStore, - description: str, - name: str, - ) -> VectorStoreInfo: - return VectorStoreInfo(vectorstore=vectorstore, description=description, name=name) + outputs = [ + Output(display_name="Vector Store Info", name="info", method="build_info"), + ] + + def build_info(self) -> VectorStoreInfo: + self.status = { + "name": self.vectorstore_name, + "description": self.vectorstore_description, + } + return VectorStoreInfo( + vectorstore=self.input_vectorstore, description=self.vectorstore_description, name=self.vectorstore_name + ) diff --git a/src/backend/base/langflow/components/toolkits/VectorStoreRouterToolkit.py b/src/backend/base/langflow/components/toolkits/VectorStoreRouterToolkit.py deleted file mode 100644 index 0676312a2de..00000000000 --- a/src/backend/base/langflow/components/toolkits/VectorStoreRouterToolkit.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import List, Union - -from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo, VectorStoreRouterToolkit - -from langflow.custom import CustomComponent -from langflow.field_typing import LanguageModel, Tool - - -class VectorStoreRouterToolkitComponent(CustomComponent): - display_name = "VectorStoreRouterToolkit" - description = "Toolkit for routing between Vector Stores." - name = "VectorStoreRouterToolkit" - - def build_config(self): - return { - "vectorstores": {"display_name": "Vector Stores"}, - "llm": {"display_name": "LLM"}, - } - - def build(self, vectorstores: List[VectorStoreInfo], llm: LanguageModel) -> Union[Tool, VectorStoreRouterToolkit]: - print("vectorstores", vectorstores) - print("llm", llm) - return VectorStoreRouterToolkit(vectorstores=vectorstores, llm=llm) diff --git a/src/backend/base/langflow/components/toolkits/VectorStoreToolkit.py b/src/backend/base/langflow/components/toolkits/VectorStoreToolkit.py deleted file mode 100644 index 74fe5d33c26..00000000000 --- a/src/backend/base/langflow/components/toolkits/VectorStoreToolkit.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Union - -from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo, VectorStoreToolkit - -from langflow.custom import CustomComponent -from langflow.field_typing import LanguageModel, Tool - - -class VectorStoreToolkitComponent(CustomComponent): - display_name = "VectorStoreToolkit" - description = "Toolkit for interacting with a Vector Store." - name = "VectorStoreToolkit" - - def build_config(self): - return { - "vectorstore_info": {"display_name": "Vector Store Info"}, - "llm": {"display_name": "LLM"}, - } - - def build( - self, - vectorstore_info: VectorStoreInfo, - llm: LanguageModel, - ) -> Union[Tool, VectorStoreToolkit]: - return VectorStoreToolkit(vectorstore_info=vectorstore_info, llm=llm) diff --git a/src/backend/base/langflow/components/toolkits/__init__.py b/src/backend/base/langflow/components/toolkits/__init__.py index 189b4315995..abded990c63 100644 --- a/src/backend/base/langflow/components/toolkits/__init__.py +++ b/src/backend/base/langflow/components/toolkits/__init__.py @@ -1,15 +1,7 @@ -from .JsonToolkit import JsonToolkitComponent from .Metaphor import MetaphorToolkit -from .OpenAPIToolkit import OpenAPIToolkitComponent from .VectorStoreInfo import VectorStoreInfoComponent -from .VectorStoreRouterToolkit import VectorStoreRouterToolkitComponent -from .VectorStoreToolkit import VectorStoreToolkitComponent __all__ = [ - "JsonToolkitComponent", "MetaphorToolkit", - "OpenAPIToolkitComponent", "VectorStoreInfoComponent", - "VectorStoreRouterToolkitComponent", - "VectorStoreToolkitComponent", ]