feat: migrate agents and toolkits to Component syntax (#2579)

* feat: migrate agents and toolkits to Component syntax

* fix mypy

* fix mypy

* [autofix.ci] apply automated fixes

* fix mypy

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Nicolò Boschi 2024-07-10 14:09:14 +02:00 • committed by GitHub
commit 05044a3434
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 365 additions and 706 deletions

View file

@ -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.agents.agent import RunnableAgent
from langchain_core.messages import BaseMessage
from langchain.agents import AgentExecutor
from langchain_core.runnables import Runnable from langchain_core.runnables import Runnable
from langflow.base.agents.utils import data_to_messages, get_agents_list from langflow.custom import Component
from langflow.custom import CustomComponent from langflow.inputs import BoolInput, IntInput, HandleInput
from langflow.field_typing import Text, Tool from langflow.inputs.inputs import InputTypes
from langflow.schema import Data from langflow.template import Output
class LCAgentComponent(CustomComponent): class LCAgentComponent(Component):
def get_agents_list(self): trace_type = "agent"
return get_agents_list() _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,
),
]
def build_config(self): outputs = [
return { Output(display_name="Agent", name="agent", method="build_agent"),
"lc": { ]
"display_name": "LangChain",
"info": "The LangChain to interact with.", def _validate_outputs(self):
}, required_output_methods = ["build_agent"]
"handle_parsing_errors": { output_names = [output.name for output in self.outputs]
"display_name": "Handle Parsing Errors", for method_name in required_output_methods:
"info": "If True, the agent will handle parsing errors. If False, the agent will raise an error.", if method_name not in output_names:
"advanced": True, raise ValueError(f"Output with name '{method_name}' must be defined.")
}, elif not hasattr(self, method_name):
"output_key": { raise ValueError(f"Method '{method_name}' must be defined.")
"display_name": "Output Key",
"info": "The key to use to get the output from the agent.", def get_agent_kwargs(self, flatten: bool = False) -> dict:
"advanced": True, base = {
}, "handle_parsing_errors": self.handle_parsing_errors,
"memory": { "verbose": self.verbose,
"display_name": "Memory", "allow_dangerous_code": True,
"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.",
},
} }
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}
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'.")
return cast(str, result.get("output")) 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),
]
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),
)
@abstractmethod
def creat_agent_runnable(self) -> Runnable:
"""Create the agent."""
pass

View file

@ -1,4 +1,4 @@
from typing import List from typing import List, cast
from langchain_core.documents import Document from langchain_core.documents import Document
from loguru import logger from loguru import logger
@ -23,11 +23,16 @@ class LCVectorStoreComponent(Component):
name="search_results", name="search_results",
method="search_documents", method="search_documents",
), ),
Output(
display_name="Vector Store",
name="vector_store",
method="cast_vector_store",
),
] ]
def _validate_outputs(self): def _validate_outputs(self):
# At least these three outputs must be defined # 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] output_names = [output.name for output in self.outputs]
for method_name in required_output_methods: for method_name in required_output_methods:
if method_name not in output_names: if method_name not in output_names:
@ -67,6 +72,9 @@ class LCVectorStoreComponent(Component):
self.status = data self.status = data
return data return data
def cast_vector_store(self) -> VectorStore:
return cast(VectorStore, self.build_vector_store())
def build_vector_store(self) -> VectorStore: def build_vector_store(self) -> VectorStore:
""" """
Builds the Vector Store object.c Builds the Vector Store object.c

View file

@ -1,35 +1,27 @@
from langchain_experimental.agents.agent_toolkits.csv.base import create_csv_agent from langchain_experimental.agents.agent_toolkits.csv.base import create_csv_agent
from langflow.custom import CustomComponent from langflow.base.agents.agent import LCAgentComponent
from langflow.field_typing import AgentExecutor, LanguageModel from langflow.field_typing import AgentExecutor
from langflow.inputs import HandleInput, FileInput, DropdownInput
class CSVAgentComponent(CustomComponent): class CSVAgentComponent(LCAgentComponent):
display_name = "CSVAgent" display_name = "CSVAgent"
description = "Construct a CSV agent from a CSV and tools." description = "Construct a CSV agent from a CSV and tools."
documentation = "https://python.langchain.com/docs/modules/agents/toolkits/csv" documentation = "https://python.langchain.com/docs/modules/agents/toolkits/csv"
name = "CSVAgent" name = "CSVAgent"
def build_config(self): inputs = LCAgentComponent._base_inputs + [
return { FileInput(name="path", display_name="File Path", file_types=["csv"], required=True),
"llm": {"display_name": "LLM", "type": LanguageModel}, HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
"path": {"display_name": "Path", "field_type": "file", "suffixes": [".csv"], "file_types": [".csv"]}, DropdownInput(
"handle_parsing_errors": {"display_name": "Handle Parse Errors", "advanced": True}, name="agent_type",
"agent_type": { display_name="Agent Type",
"display_name": "Agent Type", advanced=True,
"options": ["zero-shot-react-description", "openai-functions", "openai-tools"], options=["zero-shot-react-description", "openai-functions", "openai-tools"],
"advanced": True, value="openai-tools",
}, ),
} ]
def build( def build_agent(self) -> AgentExecutor:
self, llm: LanguageModel, path: str, handle_parsing_errors: bool = True, agent_type: str = "openai-tools" return create_csv_agent(llm=self.llm, path=self.path, agent_type=self.agent_type, **self.get_agent_kwargs())
) -> 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),
)

View file

@ -1,25 +1,31 @@
from pathlib import Path
import yaml
from langchain.agents import AgentExecutor from langchain.agents import AgentExecutor
from langchain_community.agent_toolkits import create_json_agent from langchain_community.agent_toolkits import create_json_agent
from langchain_community.agent_toolkits.json.toolkit import JsonToolkit from langchain_community.agent_toolkits.json.toolkit import JsonToolkit
from langchain_community.tools.json.tool import JsonSpec
from langflow.custom import CustomComponent from langflow.base.agents.agent import LCAgentComponent
from langflow.field_typing import LanguageModel from langflow.inputs import HandleInput, FileInput
class JsonAgentComponent(CustomComponent): class JsonAgentComponent(LCAgentComponent):
display_name = "JsonAgent" display_name = "JsonAgent"
description = "Construct a json agent from an LLM and tools." description = "Construct a json agent from an LLM and tools."
name = "JsonAgent" name = "JsonAgent"
def build_config(self): inputs = LCAgentComponent._base_inputs + [
return { FileInput(name="path", display_name="File Path", file_types=["json", "yaml", "yml"], required=True),
"llm": {"display_name": "LLM"}, HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
"toolkit": {"display_name": "Toolkit"}, ]
}
def build( def build_agent(self) -> AgentExecutor:
self, if self.path.endswith("yaml") or self.path.endswith("yml"):
llm: LanguageModel, yaml_dict = yaml.load(open(self.path, "r"), Loader=yaml.FullLoader)
toolkit: JsonToolkit, spec = JsonSpec(dict_=yaml_dict)
) -> AgentExecutor: else:
return create_json_agent(llm=llm, toolkit=toolkit) spec = JsonSpec.from_file(Path(self.path))
toolkit = JsonToolkit(spec=spec)
return create_json_agent(llm=self.llm, toolkit=toolkit, **self.get_agent_kwargs())

View file

@ -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)

View file

@ -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)

View file

@ -1,32 +1,26 @@
from typing import Callable, Union
from langchain.agents import AgentExecutor from langchain.agents import AgentExecutor
from langchain_community.agent_toolkits import SQLDatabaseToolkit from langchain_community.agent_toolkits import SQLDatabaseToolkit
from langchain_community.agent_toolkits.sql.base import create_sql_agent from langchain_community.agent_toolkits.sql.base import create_sql_agent
from langchain_community.utilities import SQLDatabase from langchain_community.utilities import SQLDatabase
from langflow.custom import CustomComponent from langflow.base.agents.agent import LCAgentComponent
from langflow.field_typing import LanguageModel from langflow.inputs import MessageTextInput, HandleInput
class SQLAgentComponent(CustomComponent): class SQLAgentComponent(LCAgentComponent):
display_name = "SQLAgent" display_name = "SQLAgent"
description = "Construct an SQL agent from an LLM and tools." description = "Construct an SQL agent from an LLM and tools."
name = "SQLAgent" name = "SQLAgent"
def build_config(self): inputs = LCAgentComponent._base_inputs + [
return { MessageTextInput(name="database_uri", display_name="Database URI", required=True),
"llm": {"display_name": "LLM"}, HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
"database_uri": {"display_name": "Database URI"}, ]
"verbose": {"display_name": "Verbose", "value": False, "advanced": True},
}
def build( def build_agent(self) -> AgentExecutor:
self, db = SQLDatabase.from_uri(self.database_uri)
llm: LanguageModel, toolkit = SQLDatabaseToolkit(db=db, llm=self.llm)
database_uri: str, agent_args = self.get_agent_kwargs()
verbose: bool = False, agent_args["max_iterations"] = agent_args["agent_executor_kwargs"]["max_iterations"]
) -> Union[AgentExecutor, Callable]: del agent_args["agent_executor_kwargs"]["max_iterations"]
db = SQLDatabase.from_uri(database_uri) return create_sql_agent(llm=self.llm, toolkit=toolkit, **agent_args)
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
return create_sql_agent(llm=llm, toolkit=toolkit)

View file

@ -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 langchain.agents import AgentExecutor, BaseSingleActionAgent from langflow.base.agents.agent import LCToolsAgentComponent
from langchain.agents.tool_calling_agent.base import create_tool_calling_agent from langflow.inputs import MultilineInput
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" 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" icon = "LangChain"
beta = True beta = True
name = "ToolCallingAgent" name = "ToolCallingAgent"
inputs = [ inputs = LCToolsAgentComponent._base_inputs + [
MessageTextInput( MultilineInput(
name="system_prompt", name="system_prompt",
display_name="System Prompt", display_name="System Prompt",
info="System prompt for the agent.", info="System prompt for the agent.",
value="You are a helpful assistant", value="You are a helpful assistant",
), ),
MessageTextInput( MultilineInput(
name="input_value", name="user_prompt", display_name="Prompt", info="This prompt must contain 'input' key.", value="{input}"
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"],
), ),
] ]
outputs = [ def creat_agent_runnable(self):
Output(display_name="Text", name="text_output", method="run_agent"),
]
async def run_agent(self) -> Message:
if "input" not in self.user_prompt: if "input" not in self.user_prompt:
raise ValueError("Prompt must contain 'input' key.") raise ValueError("Prompt must contain 'input' key.")
messages = [ messages = [
("system", self.system_prompt), ("system", self.system_prompt),
( HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=["input"], template=self.user_prompt)),
"placeholder",
"{chat_history}",
),
("human", self.user_prompt),
("placeholder", "{agent_scratchpad}"), ("placeholder", "{agent_scratchpad}"),
] ]
prompt = ChatPromptTemplate.from_messages(messages) prompt = ChatPromptTemplate.from_messages(messages)
agent = create_tool_calling_agent(self.llm, self.tools, prompt) return 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

View file

@ -1,26 +1,19 @@
from typing import Callable, Union
from langchain.agents import AgentExecutor, create_vectorstore_agent from langchain.agents import AgentExecutor, create_vectorstore_agent
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreToolkit from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreToolkit
from langflow.base.agents.agent import LCAgentComponent
from langflow.custom import CustomComponent from langflow.inputs import HandleInput
from langflow.field_typing import LanguageModel
class VectorStoreAgentComponent(CustomComponent): class VectorStoreAgentComponent(LCAgentComponent):
display_name = "VectorStoreAgent" display_name = "VectorStoreAgent"
description = "Construct an agent from a Vector Store." description = "Construct an agent from a Vector Store."
name = "VectorStoreAgent" name = "VectorStoreAgent"
def build_config(self): inputs = LCAgentComponent._base_inputs + [
return { HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
"llm": {"display_name": "LLM"}, HandleInput(name="vectorstore", display_name="Vector Store", input_types=["VectorStoreInfo"], required=True),
"vector_store_toolkit": {"display_name": "Vector Store Info"}, ]
}
def build( def build_agent(self) -> AgentExecutor:
self, toolkit = VectorStoreToolkit(vectorstore_info=self.vectorstore, llm=self.llm)
llm: LanguageModel, return create_vectorstore_agent(llm=self.llm, toolkit=toolkit, **self.get_agent_kwargs())
vector_store_toolkit: VectorStoreToolkit,
) -> Union[AgentExecutor, Callable]:
return create_vectorstore_agent(llm=llm, toolkit=vector_store_toolkit)

View file

@ -1,22 +1,27 @@
from typing import Callable
from langchain.agents import create_vectorstore_router_agent from langchain.agents import create_vectorstore_router_agent
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreRouterToolkit 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" display_name = "VectorStoreRouterAgent"
description = "Construct an agent from a Vector Store Router." description = "Construct an agent from a Vector Store Router."
name = "VectorStoreRouterAgent" name = "VectorStoreRouterAgent"
def build_config(self): inputs = LCAgentComponent._base_inputs + [
return { HandleInput(name="llm", display_name="Language Model", input_types=["LanguageModel"], required=True),
"llm": {"display_name": "LLM"}, HandleInput(
"vectorstoreroutertoolkit": {"display_name": "Vector Store Router Toolkit"}, name="vectorstores",
} display_name="Vector Stores",
input_types=["VectorStoreInfo"],
is_list=True,
required=True,
),
]
def build(self, llm: LanguageModel, vectorstoreroutertoolkit: VectorStoreRouterToolkit) -> Callable: def build_agent(self) -> AgentExecutor:
return create_vectorstore_router_agent(llm=llm, toolkit=vectorstoreroutertoolkit) toolkit = VectorStoreRouterToolkit(vectorstores=self.vectorstores, llm=self.llm)
return create_vectorstore_router_agent(llm=self.llm, toolkit=toolkit, **self.get_agent_kwargs())

View file

@ -1,111 +1,52 @@
from typing import List, Optional
from langchain.agents import create_xml_agent 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.base.agents.agent import LCToolsAgentComponent
from langflow.field_typing import LanguageModel, Text, Tool from langflow.inputs import MultilineInput
from langflow.schema import Data
class XMLAgentComponent(LCAgentComponent): class XMLAgentComponent(LCToolsAgentComponent):
display_name = "XMLAgent" display_name: str = "XML Agent"
description = "Construct an XML agent from an LLM and tools." description: str = "Agent that uses tools formatting instructions as xml to the Language Model."
icon = "LangChain"
beta = True
name = "XMLAgent" name = "XMLAgent"
def build_config(self): inputs = LCToolsAgentComponent._base_inputs + [
return { MultilineInput(
"llm": {"display_name": "LLM"}, name="user_prompt",
"tools": {"display_name": "Tools"}, display_name="Prompt",
"user_prompt": { value="""
"display_name": "Prompt", You are a helpful assistant. Help the user answer any questions.
"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: You have access to the following tools:
{tools} {tools}
In order to use a tool, you can use <tool></tool> and <tool_input></tool_input> tags. You will then get back a response in the form <observation></observation> In order to use a tool, you can use <tool></tool> and <tool_input></tool_input> tags. You will then get back a response in the form <observation></observation>
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:
<tool>search</tool><tool_input>weather in SF</tool_input> 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:
<observation>64 degrees</observation>
When you are done, respond with a final answer between <final_answer></final_answer>. For example: <tool>search</tool><tool_input>weather in SF</tool_input>
<final_answer>The weather in SF is 64 degrees</final_answer> <observation>64 degrees</observation>
Begin! When you are done, respond with a final answer between <final_answer></final_answer>. For example:
Previous Conversation: <final_answer>The weather in SF is 64 degrees</final_answer>
{chat_history}
Question: {input} Begin!
{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( Question: {input}
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): {agent_scratchpad}
return "\n".join( """,
[tool_template.format(name=tool.name, description=tool.description, args=tool.args) for tool in tools] ),
) ]
def creat_agent_runnable(self):
messages = [ messages = [
("system", system_message), HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=["input"], template=self.user_prompt))
(
"placeholder",
"{chat_history}",
),
("human", user_prompt),
("placeholder", "{agent_scratchpad}"),
] ]
prompt = ChatPromptTemplate.from_messages(messages) prompt = ChatPromptTemplate.from_messages(messages)
agent = create_xml_agent(llm, tools, prompt, tools_renderer=render_tool_description) return create_xml_agent(self.llm, self.tools, prompt)
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

View file

@ -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

View file

@ -1,4 +1,3 @@
from .AgentComponent import AgentComponent
from .ExtractKeyFromData import ExtractKeyFromDataComponent from .ExtractKeyFromData import ExtractKeyFromDataComponent
from .ListFlows import ListFlowsComponent from .ListFlows import ListFlowsComponent
from .MergeData import MergeDataComponent from .MergeData import MergeDataComponent
@ -6,7 +5,6 @@ from .SelectivePassThrough import SelectivePassThroughComponent
from .SubFlow import SubFlowComponent from .SubFlow import SubFlowComponent
__all__ = [ __all__ = [
"AgentComponent",
"ConditionalRouterComponent", "ConditionalRouterComponent",
"ExtractKeyFromDataComponent", "ExtractKeyFromDataComponent",
"FlowToolComponent", "FlowToolComponent",

View file

@ -1,43 +1,44 @@
from langchain_core.runnables import Runnable from langflow.custom import Component
from langflow.inputs import HandleInput, MessageTextInput
from langflow.custom import CustomComponent from langflow.schema.message import Message
from langflow.field_typing import Text from langflow.template import Output
class RunnableExecComponent(CustomComponent): class RunnableExecComponent(Component):
description = "Execute a runnable. It will try to guess the input and output keys." description = "Execute a runnable. It will try to guess the input and output keys."
display_name = "Runnable Executor" display_name = "Runnable Executor"
name = "RunnableExecutor" name = "RunnableExecutor"
beta: bool = True beta: bool = True
field_order = [
"input_key", inputs = [
"output_key", MessageTextInput(name="input_value", display_name="Input", required=True),
"input_value", HandleInput(
"runnable", 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): outputs = [
return { Output(
"input_key": { display_name="Text",
"display_name": "Input Key", name="text",
"info": "The key to use for the input.", method="build_executor",
"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,
},
}
def get_output(self, result, input_key, output_key): def get_output(self, result, input_key, output_key):
""" """
@ -107,16 +108,10 @@ class RunnableExecComponent(CustomComponent):
status = f"Warning: The input key is not '{input_key}'. The input key is '{runnable.input_keys}'." status = f"Warning: The input key is not '{input_key}'. The input key is '{runnable.input_keys}'."
return input_dict, status return input_dict, status
def build( def build_executor(self) -> Message:
self, input_dict, status = self.get_input_dict(self.runnable, self.input_key, self.input_value)
input_value: Text, result = self.runnable.invoke(input_dict)
runnable: Runnable, result_value, _status = self.get_output(result, self.input_key, self.output_key)
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)
status += _status status += _status
status += f"\n\nOutput: {result_value}\n\nRaw Output: {result}" status += f"\n\nOutput: {result_value}\n\nRaw Output: {result}"
self.status = status self.status = status

View file

@ -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)

View file

@ -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,
)

View file

@ -1,25 +1,44 @@
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo 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.custom import CustomComponent from langflow.template import Output
class VectorStoreInfoComponent(CustomComponent): class VectorStoreInfoComponent(Component):
display_name = "VectorStoreInfo" display_name = "VectorStoreInfo"
description = "Information about a VectorStore" description = "Information about a VectorStore"
name = "VectorStoreInfo" name = "VectorStoreInfo"
def build_config(self): inputs = [
return { MessageTextInput(
"vectorstore": {"display_name": "VectorStore"}, name="vectorstore_name",
"description": {"display_name": "Description", "multiline": True}, display_name="Name",
"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( outputs = [
self, Output(display_name="Vector Store Info", name="info", method="build_info"),
vectorstore: VectorStore, ]
description: str,
name: str, def build_info(self) -> VectorStoreInfo:
) -> VectorStoreInfo: self.status = {
return VectorStoreInfo(vectorstore=vectorstore, description=description, name=name) "name": self.vectorstore_name,
"description": self.vectorstore_description,
}
return VectorStoreInfo(
vectorstore=self.input_vectorstore, description=self.vectorstore_description, name=self.vectorstore_name
)

View file

@ -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)

View file

@ -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)

View file

@ -1,15 +1,7 @@
from .JsonToolkit import JsonToolkitComponent
from .Metaphor import MetaphorToolkit from .Metaphor import MetaphorToolkit
from .OpenAPIToolkit import OpenAPIToolkitComponent
from .VectorStoreInfo import VectorStoreInfoComponent from .VectorStoreInfo import VectorStoreInfoComponent
from .VectorStoreRouterToolkit import VectorStoreRouterToolkitComponent
from .VectorStoreToolkit import VectorStoreToolkitComponent
__all__ = [ __all__ = [
"JsonToolkitComponent",
"MetaphorToolkit", "MetaphorToolkit",
"OpenAPIToolkitComponent",
"VectorStoreInfoComponent", "VectorStoreInfoComponent",
"VectorStoreRouterToolkitComponent",
"VectorStoreToolkitComponent",
] ]