Merge branch 'form_io' into python_custom_node_component
This commit is contained in:
commit
320989870e
51 changed files with 1012 additions and 606 deletions
|
|
@ -71,30 +71,68 @@ def validate_prompt(template: str):
|
|||
except Exception as exc:
|
||||
raise ValueError(str(exc)) from exc
|
||||
|
||||
# if len(input_variables) > 1:
|
||||
# # If there's more than one input variable
|
||||
|
||||
return input_variables
|
||||
|
||||
|
||||
def check_input_variables(input_variables: list):
|
||||
invalid_chars = []
|
||||
fixed_variables = []
|
||||
wrong_variables = []
|
||||
empty_variables = []
|
||||
for variable in input_variables:
|
||||
new_var = variable
|
||||
for char in INVALID_CHARACTERS:
|
||||
if char in variable:
|
||||
invalid_chars.append(char)
|
||||
new_var = new_var.replace(char, "")
|
||||
|
||||
# if variable is empty, then we should add that to the wrong variables
|
||||
if not variable:
|
||||
empty_variables.append(variable)
|
||||
continue
|
||||
|
||||
# if variable starts with a number we should add that to the invalid chars
|
||||
# and wrong variables
|
||||
if variable[0].isdigit():
|
||||
invalid_chars.append(variable[0])
|
||||
new_var = new_var.replace(variable[0], "")
|
||||
wrong_variables.append(variable)
|
||||
else:
|
||||
for char in INVALID_CHARACTERS:
|
||||
if char in variable:
|
||||
invalid_chars.append(char)
|
||||
new_var = new_var.replace(char, "")
|
||||
wrong_variables.append(variable)
|
||||
fixed_variables.append(new_var)
|
||||
if new_var != variable:
|
||||
input_variables.remove(variable)
|
||||
input_variables.append(new_var)
|
||||
# If any of the input_variables is not in the fixed_variables, then it means that
|
||||
# there are invalid characters in the input_variables
|
||||
if any(var not in fixed_variables for var in input_variables):
|
||||
raise ValueError(
|
||||
f"Invalid input variables: {input_variables}. Please, use something like {fixed_variables} instead."
|
||||
)
|
||||
|
||||
if any(var not in fixed_variables for var in input_variables):
|
||||
error_message = build_error_message(
|
||||
input_variables,
|
||||
invalid_chars,
|
||||
wrong_variables,
|
||||
fixed_variables,
|
||||
empty_variables,
|
||||
)
|
||||
raise ValueError(error_message)
|
||||
return input_variables
|
||||
|
||||
|
||||
def build_error_message(
|
||||
input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables
|
||||
):
|
||||
input_variables_str = ", ".join([f"'{var}'" for var in input_variables])
|
||||
error_string = f"Invalid input variables: {input_variables_str}. "
|
||||
|
||||
if wrong_variables and invalid_chars:
|
||||
# fix the wrong variables replacing invalid chars and find them in the fixed variables
|
||||
error_string_vars = "You can fix them by replacing the invalid characters: "
|
||||
wvars = wrong_variables.copy()
|
||||
for i, wrong_var in enumerate(wvars):
|
||||
for char in invalid_chars:
|
||||
wrong_var = wrong_var.replace(char, "")
|
||||
if wrong_var in fixed_variables:
|
||||
error_string_vars += f"'{wrong_variables[i]}' -> '{wrong_var}'"
|
||||
error_string += error_string_vars
|
||||
elif empty_variables:
|
||||
error_string += f" There are {len(empty_variables)} empty variable{'s' if len(empty_variables) > 1 else ''}."
|
||||
elif len(set(fixed_variables)) != len(fixed_variables):
|
||||
error_string += "There are duplicate variables."
|
||||
return error_string
|
||||
|
|
|
|||
|
|
@ -1,22 +1,107 @@
|
|||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
|
||||
|
||||
from langflow.api.v1.schemas import ChatResponse
|
||||
|
||||
|
||||
from typing import Any, Dict, List, Union
|
||||
from fastapi import WebSocket
|
||||
|
||||
|
||||
from langchain.schema import AgentAction, LLMResult, AgentFinish
|
||||
|
||||
|
||||
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
|
||||
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||
"""Callback handler for streaming LLM responses."""
|
||||
|
||||
def __init__(self, websocket):
|
||||
def __init__(self, websocket: WebSocket):
|
||||
self.websocket = websocket
|
||||
|
||||
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
||||
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||
await self.websocket.send_json(resp.dict())
|
||||
|
||||
async def on_llm_start(
|
||||
self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any
|
||||
) -> Any:
|
||||
"""Run when LLM starts running."""
|
||||
|
||||
async def on_llm_end(self, response: LLMResult, **kwargs: Any) -> Any:
|
||||
"""Run when LLM ends running."""
|
||||
|
||||
async def on_llm_error(
|
||||
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
|
||||
) -> Any:
|
||||
"""Run when LLM errors."""
|
||||
|
||||
async def on_chain_start(
|
||||
self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any
|
||||
) -> Any:
|
||||
"""Run when chain starts running."""
|
||||
|
||||
async def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> Any:
|
||||
"""Run when chain ends running."""
|
||||
|
||||
async def on_chain_error(
|
||||
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
|
||||
) -> Any:
|
||||
"""Run when chain errors."""
|
||||
|
||||
async def on_tool_start(
|
||||
self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
|
||||
) -> Any:
|
||||
"""Run when tool starts running."""
|
||||
resp = ChatResponse(
|
||||
message="",
|
||||
type="stream",
|
||||
intermediate_steps=f"Tool input: {input_str}",
|
||||
)
|
||||
await self.websocket.send_json(resp.dict())
|
||||
|
||||
async def on_tool_end(self, output: str, **kwargs: Any) -> Any:
|
||||
"""Run when tool ends running."""
|
||||
resp = ChatResponse(
|
||||
message="",
|
||||
type="stream",
|
||||
intermediate_steps=f"Tool output: {output}",
|
||||
)
|
||||
await self.websocket.send_json(resp.dict())
|
||||
|
||||
async def on_tool_error(
|
||||
self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any
|
||||
) -> Any:
|
||||
"""Run when tool errors."""
|
||||
|
||||
async def on_text(self, text: str, **kwargs: Any) -> Any:
|
||||
"""Run on arbitrary text."""
|
||||
# This runs when first sending the prompt
|
||||
# to the LLM, adding it will send the final prompt
|
||||
# to the frontend
|
||||
|
||||
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
|
||||
log = f"Thought: {action.log}"
|
||||
# if there are line breaks, split them and send them
|
||||
# as separate messages
|
||||
if "\n" in log:
|
||||
logs = log.split("\n")
|
||||
for log in logs:
|
||||
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||
await self.websocket.send_json(resp.dict())
|
||||
else:
|
||||
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||
await self.websocket.send_json(resp.dict())
|
||||
|
||||
async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any:
|
||||
"""Run on agent end."""
|
||||
resp = ChatResponse(
|
||||
message="",
|
||||
type="stream",
|
||||
intermediate_steps=finish.log,
|
||||
)
|
||||
await self.websocket.send_json(resp.dict())
|
||||
|
||||
|
||||
class StreamingLLMCallbackHandler(BaseCallbackHandler):
|
||||
"""Callback handler for streaming LLM responses."""
|
||||
|
|
|
|||
|
|
@ -160,7 +160,13 @@ async def stream_build(flow_id: str):
|
|||
input_keys_response = build_input_keys_response(
|
||||
langchain_object, artifacts
|
||||
)
|
||||
yield str(StreamData(event="message", data=input_keys_response))
|
||||
else:
|
||||
input_keys_response = {
|
||||
"input_keys": {},
|
||||
"memory_keys": [],
|
||||
"handle_keys": [],
|
||||
}
|
||||
yield str(StreamData(event="message", data=input_keys_response))
|
||||
|
||||
chat_manager.set_cache(flow_id, langchain_object)
|
||||
# We need to reset the chat history
|
||||
|
|
|
|||
|
|
@ -73,12 +73,14 @@ def add_new_variables_to_template(input_variables, prompt_request):
|
|||
advanced=False,
|
||||
multiline=True,
|
||||
input_types=["Document", "BaseOutputParser"],
|
||||
value="", # Set the value to empty string
|
||||
)
|
||||
if variable in prompt_request.frontend_node.template:
|
||||
# Set the new field with the old value
|
||||
template_field.value = prompt_request.frontend_node.template[variable][
|
||||
"value"
|
||||
]
|
||||
|
||||
prompt_request.frontend_node.template[variable] = template_field.to_dict()
|
||||
|
||||
# Check if variable is not already in the list before appending
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ async def process_graph(
|
|||
try:
|
||||
logger.debug("Generating result and thought")
|
||||
result, intermediate_steps = await get_result_and_steps(
|
||||
langchain_object, chat_inputs.message or "", websocket=websocket
|
||||
langchain_object, chat_inputs.message, websocket=websocket
|
||||
)
|
||||
logger.debug("Generated result and intermediate_steps")
|
||||
return result, intermediate_steps
|
||||
|
|
|
|||
|
|
@ -164,8 +164,6 @@ memories:
|
|||
prompts:
|
||||
PromptTemplate:
|
||||
documentation: "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/"
|
||||
ZeroShotPrompt:
|
||||
documentation: "https://python.langchain.com/docs/modules/agents/how_to/custom_mrkl_agent"
|
||||
textsplitters:
|
||||
CharacterTextSplitter:
|
||||
documentation: "https://python.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/character_text_splitter"
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ from langflow.template import frontend_node
|
|||
|
||||
# These should always be instantiated
|
||||
CUSTOM_NODES = {
|
||||
"prompts": {
|
||||
"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(),
|
||||
},
|
||||
# "prompts": {
|
||||
# "ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode(),
|
||||
# },
|
||||
"tools": {
|
||||
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
|
||||
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
|
||||
|
|
|
|||
|
|
@ -183,6 +183,8 @@ class Vertex:
|
|||
# and return the instance
|
||||
|
||||
try:
|
||||
if self.base_type is None:
|
||||
raise ValueError(f"Base type for node {self.vertex_type} not found")
|
||||
result = loading.instantiate_class(
|
||||
node_type=self.vertex_type,
|
||||
base_type=self.base_type,
|
||||
|
|
@ -224,4 +226,5 @@ class Vertex:
|
|||
return id(self)
|
||||
|
||||
def _built_object_repr(self):
|
||||
return repr(self._built_object)
|
||||
# Add a message with an emoji, stars for sucess,
|
||||
return "Built sucessfully ✨" if self._built_object else "Failed to build 😵💫"
|
||||
|
|
|
|||
|
|
@ -201,6 +201,15 @@ class PromptVertex(Vertex):
|
|||
self._build()
|
||||
return self._built_object
|
||||
|
||||
def _built_object_repr(self):
|
||||
if self.artifacts and hasattr(self._built_object, "format"):
|
||||
# We'll build the prompt with the artifacts
|
||||
# to show the user what the prompt looks like
|
||||
# with the variables filled in
|
||||
return self._built_object.format(**self.artifacts)
|
||||
else:
|
||||
return super()._built_object_repr()
|
||||
|
||||
|
||||
class OutputParserVertex(Vertex):
|
||||
def __init__(self, data: Dict):
|
||||
|
|
|
|||
|
|
@ -6,13 +6,20 @@ from langflow.custom.customs import get_custom_nodes
|
|||
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
||||
from langflow.interface.base import LangChainTypeCreator
|
||||
from langflow.settings import settings
|
||||
from langflow.template.frontend_node.agents import AgentFrontendNode
|
||||
from langflow.utils.logger import logger
|
||||
from langflow.utils.util import build_template_from_class
|
||||
from langflow.utils.util import build_template_from_class, build_template_from_method
|
||||
|
||||
|
||||
class AgentCreator(LangChainTypeCreator):
|
||||
type_name: str = "agents"
|
||||
|
||||
from_method_nodes = {"ZeroShotAgent": "from_llm_and_tools"}
|
||||
|
||||
@property
|
||||
def frontend_node_class(self) -> type[AgentFrontendNode]:
|
||||
return AgentFrontendNode
|
||||
|
||||
@property
|
||||
def type_to_loader_dict(self) -> Dict:
|
||||
if self.type_dict is None:
|
||||
|
|
@ -27,6 +34,13 @@ class AgentCreator(LangChainTypeCreator):
|
|||
try:
|
||||
if name in get_custom_nodes(self.type_name).keys():
|
||||
return get_custom_nodes(self.type_name)[name]
|
||||
elif name in self.from_method_nodes:
|
||||
return build_template_from_method(
|
||||
name,
|
||||
type_to_cls_dict=self.type_to_loader_dict,
|
||||
add_function=True,
|
||||
method_name=self.from_method_nodes[name],
|
||||
)
|
||||
return build_template_from_class(
|
||||
name, self.type_to_loader_dict, add_function=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ from langflow.interface.importing.utils import (
|
|||
import_by_type,
|
||||
)
|
||||
from langflow.interface.custom_lists import CUSTOM_NODES
|
||||
from langflow.interface.importing.utils import get_function, import_by_type
|
||||
from langflow.interface.agents.base import agent_creator
|
||||
from langflow.interface.toolkits.base import toolkits_creator
|
||||
from langflow.interface.chains.base import chain_creator
|
||||
from langflow.interface.output_parsers.base import output_parser_creator
|
||||
|
|
@ -65,7 +67,7 @@ def convert_kwargs(params):
|
|||
|
||||
def instantiate_based_on_type(class_object, base_type, node_type, params):
|
||||
if base_type == "agents":
|
||||
return instantiate_agent(class_object, params)
|
||||
return instantiate_agent(node_type, class_object, params)
|
||||
elif base_type == "prompts":
|
||||
return instantiate_prompt(node_type, class_object, params)
|
||||
elif base_type == "tools":
|
||||
|
|
@ -122,6 +124,12 @@ def instantiate_llm(node_type, class_object, params: Dict):
|
|||
|
||||
|
||||
def instantiate_memory(node_type, class_object, params):
|
||||
# process input_key and output_key to remove them if
|
||||
# they are empty strings
|
||||
for key in ["input_key", "output_key"]:
|
||||
if key in params and not params[key]:
|
||||
params.pop(key)
|
||||
|
||||
try:
|
||||
if "retriever" in params and hasattr(params["retriever"], "as_retriever"):
|
||||
params["retriever"] = params["retriever"].as_retriever()
|
||||
|
|
@ -164,7 +172,16 @@ def instantiate_chains(node_type, class_object: Type[Chain], params: Dict):
|
|||
return class_object(**params)
|
||||
|
||||
|
||||
def instantiate_agent(class_object: Type[agent_module.Agent], params: Dict):
|
||||
def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict):
|
||||
if node_type in agent_creator.from_method_nodes:
|
||||
method = agent_creator.from_method_nodes[node_type]
|
||||
if class_method := getattr(class_object, method, None):
|
||||
agent = class_method(**params)
|
||||
tools = params.get("tools", [])
|
||||
return AgentExecutor.from_agent_and_tools(
|
||||
agent=agent,
|
||||
tools=tools,
|
||||
)
|
||||
return load_agent_executor(class_object, params)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class ToolCreator(LangChainTypeCreator):
|
|||
def get_signature(self, name: str) -> Optional[Dict]:
|
||||
"""Get the signature of a tool."""
|
||||
|
||||
base_classes = ["Tool"]
|
||||
base_classes = ["Tool", "BaseTool"]
|
||||
fields = []
|
||||
params = []
|
||||
tool_params = {}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,17 @@ NON_CHAT_AGENTS = {
|
|||
}
|
||||
|
||||
|
||||
class AgentFrontendNode(FrontendNode):
|
||||
@staticmethod
|
||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||
if field.name in ["suffix", "prefix"]:
|
||||
field.show = True
|
||||
if field.name == "Tools" and name == "ZeroShotAgent":
|
||||
# field.
|
||||
field.type_name = "BaseTool"
|
||||
field.is_list = True
|
||||
|
||||
|
||||
class SQLAgentNode(FrontendNode):
|
||||
name: str = "SQLAgent"
|
||||
template: Template = Template(
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class ToolNode(FrontendNode):
|
|||
],
|
||||
)
|
||||
description: str = "Converts a chain, agent or function into a tool."
|
||||
base_classes: list[str] = ["Tool"]
|
||||
base_classes: list[str] = ["Tool", "BaseTool"]
|
||||
|
||||
def to_dict(self):
|
||||
return super().to_dict()
|
||||
|
|
|
|||
|
|
@ -243,7 +243,11 @@ def format_dict(d, name: Optional[str] = None):
|
|||
|
||||
# Check for list type
|
||||
if "List" in _type or "Sequence" in _type or "Set" in _type:
|
||||
_type = _type.replace("List[", "")[:-1]
|
||||
_type = (
|
||||
_type.replace("List[", "")
|
||||
.replace("Sequence[", "")
|
||||
.replace("Set[", "")[:-1]
|
||||
)
|
||||
value["list"] = True
|
||||
else:
|
||||
value["list"] = False
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue