Merge branch 'toolkits' of https://github.com/logspace-ai/langflow into toolkits
This commit is contained in:
commit
0b760003b1
17 changed files with 1310 additions and 87 deletions
0
src/backend/langflow/cache/__init__.py
vendored
Normal file
0
src/backend/langflow/cache/__init__.py
vendored
Normal file
49
src/backend/langflow/cache/utils.py
vendored
Normal file
49
src/backend/langflow/cache/utils.py
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import contextlib
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import dill
|
||||
|
||||
PREFIX = "langflow_cache"
|
||||
|
||||
|
||||
def clear_old_cache_files(max_cache_size: int = 10):
|
||||
cache_dir = Path(tempfile.gettempdir())
|
||||
cache_files = list(cache_dir.glob(f"{PREFIX}_*.dill"))
|
||||
|
||||
if len(cache_files) > max_cache_size:
|
||||
cache_files_sorted_by_mtime = sorted(
|
||||
cache_files, key=lambda x: x.stat().st_mtime, reverse=True
|
||||
)
|
||||
|
||||
for cache_file in cache_files_sorted_by_mtime[max_cache_size:]:
|
||||
with contextlib.suppress(OSError):
|
||||
os.remove(cache_file)
|
||||
|
||||
|
||||
def remove_position_info(node):
|
||||
node.pop("position", None)
|
||||
|
||||
|
||||
def compute_hash(graph_data):
|
||||
for node in graph_data["nodes"]:
|
||||
remove_position_info(node)
|
||||
|
||||
cleaned_graph_json = json.dumps(graph_data, sort_keys=True)
|
||||
return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def save_cache(hash_val, chat_data):
|
||||
cache_path = Path(tempfile.gettempdir()) / f"{PREFIX}_{hash_val}.dill"
|
||||
with cache_path.open("wb") as cache_file:
|
||||
dill.dump(chat_data, cache_file)
|
||||
|
||||
|
||||
def load_cache(hash_val):
|
||||
cache_path = Path(tempfile.gettempdir()) / f"{PREFIX}_{hash_val}.dill"
|
||||
if cache_path.exists():
|
||||
with cache_path.open("rb") as cache_file:
|
||||
return dill.load(cache_file)
|
||||
return None
|
||||
|
|
@ -29,4 +29,8 @@ tools:
|
|||
wrappers:
|
||||
- RequestsWrapper
|
||||
|
||||
toolkits:
|
||||
- OpenAPIToolkit
|
||||
- JsonToolkit
|
||||
|
||||
dev: false
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ class PromptNode(Node):
|
|||
)
|
||||
self.params["tools"] = tools
|
||||
prompt_params = [
|
||||
key for key, value in self.params.items() if value["type"] == "str"
|
||||
key
|
||||
for key, value in self.params.items()
|
||||
if isinstance(value, str) and key != "format_instructions"
|
||||
]
|
||||
else:
|
||||
prompt_params = ["template"]
|
||||
|
|
|
|||
|
|
@ -2,30 +2,48 @@ import contextlib
|
|||
import io
|
||||
import re
|
||||
from typing import Any, Dict
|
||||
from langflow.cache.utils import compute_hash, load_cache, save_cache
|
||||
|
||||
from langflow.graph.graph import Graph
|
||||
from langflow.interface import loading
|
||||
from langflow.utils import payload
|
||||
|
||||
|
||||
def load_langchain_object(data_graph):
|
||||
computed_hash = compute_hash(data_graph)
|
||||
|
||||
# Load langchain_object from cache if it exists
|
||||
langchain_object = load_cache(computed_hash)
|
||||
if langchain_object is None:
|
||||
nodes = data_graph["nodes"]
|
||||
# Add input variables
|
||||
nodes = payload.extract_input_variables(nodes)
|
||||
# Nodes, edges and root node
|
||||
edges = data_graph["edges"]
|
||||
graph = Graph(nodes, edges)
|
||||
|
||||
langchain_object = graph.build()
|
||||
|
||||
return computed_hash, langchain_object
|
||||
|
||||
|
||||
def process_graph(data_graph: Dict[str, Any]):
|
||||
"""
|
||||
Process graph by extracting input variables and replacing ZeroShotPrompt
|
||||
with PromptTemplate,then run the graph and return the result and thought.
|
||||
"""
|
||||
nodes = data_graph["nodes"]
|
||||
# Add input variables
|
||||
# ? Is this necessary?
|
||||
nodes = payload.extract_input_variables(nodes)
|
||||
# Nodes, edges and root node
|
||||
edges = data_graph["edges"]
|
||||
graph = Graph(nodes, edges)
|
||||
|
||||
langchain_object = graph.build()
|
||||
# Load langchain object
|
||||
computed_hash, langchain_object = load_langchain_object(data_graph)
|
||||
message = data_graph["message"]
|
||||
# Process json
|
||||
|
||||
# Generate result and thought
|
||||
result, thought = get_result_and_thought_using_graph(langchain_object, message)
|
||||
|
||||
# Save langchain_object to cache
|
||||
# We have to save it here because if the
|
||||
# memory is updated we need to keep the new values
|
||||
save_cache(computed_hash, langchain_object)
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"thought": re.sub(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from langchain.agents import agent_toolkits
|
|||
|
||||
from langflow.interface.base import LangChainTypeCreator
|
||||
from langflow.interface.importing.utils import import_class, import_module
|
||||
from langflow.settings import settings
|
||||
from langflow.utils.util import build_template_from_class
|
||||
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ class ToolkitCreator(LangChainTypeCreator):
|
|||
)
|
||||
# if toolkit_name is not lower case it is a class
|
||||
for toolkit_name in agent_toolkits.__all__
|
||||
if not toolkit_name.islower()
|
||||
if not toolkit_name.islower() and toolkit_name in settings.toolkits
|
||||
}
|
||||
|
||||
return self.type_dict
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ class ToolCreator(LangChainTypeCreator):
|
|||
# Copy the field and add the name
|
||||
fields = []
|
||||
for param in params:
|
||||
field = TOOL_INPUTS.get(param, TOOL_INPUTS["str"])
|
||||
field = TOOL_INPUTS.get(param, TOOL_INPUTS["str"]).copy()
|
||||
field.name = param
|
||||
if param == "aiosession":
|
||||
field.show = False
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class Settings(BaseSettings):
|
|||
tools: List[str] = []
|
||||
memories: List[str] = []
|
||||
wrappers: List[str] = []
|
||||
toolkits: List[str] = []
|
||||
dev: bool = False
|
||||
|
||||
class Config:
|
||||
|
|
@ -35,6 +36,7 @@ class Settings(BaseSettings):
|
|||
self.tools = new_settings.tools or []
|
||||
self.memories = new_settings.memories or []
|
||||
self.wrappers = new_settings.wrappers or []
|
||||
self.toolkits = new_settings.toolkits or []
|
||||
self.dev = new_settings.dev or False
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from abc import ABC
|
||||
from typing import Any, Union
|
||||
from typing import Any, Optional, Union, Dict
|
||||
from langflow.utils import constants
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ class TemplateFieldCreator(BaseModel, ABC):
|
|||
file_types: list[str] = []
|
||||
content: Union[str, None] = None
|
||||
password: bool = False
|
||||
options: list[str] = []
|
||||
# _name will be used to store the name of the field
|
||||
# in the template
|
||||
name: str = ""
|
||||
|
|
@ -37,6 +39,88 @@ class TemplateFieldCreator(BaseModel, ABC):
|
|||
result["content"] = self.content
|
||||
return result
|
||||
|
||||
def process_field(
|
||||
self, key: str, value: Dict[str, Any], name: Optional[str] = None
|
||||
) -> None:
|
||||
_type = value["type"]
|
||||
|
||||
# Remove 'Optional' wrapper
|
||||
if "Optional" in _type:
|
||||
_type = _type.replace("Optional[", "")[:-1]
|
||||
|
||||
# Check for list type
|
||||
if "List" in _type:
|
||||
_type = _type.replace("List[", "")[:-1]
|
||||
self.is_list = True
|
||||
else:
|
||||
self.is_list = False
|
||||
|
||||
# Replace 'Mapping' with 'dict'
|
||||
if "Mapping" in _type:
|
||||
_type = _type.replace("Mapping", "dict")
|
||||
|
||||
# Change type from str to Tool
|
||||
self.field_type = "Tool" if key in ["allowed_tools"] else _type
|
||||
|
||||
self.field_type = "int" if key in ["max_value_length"] else self.field_type
|
||||
|
||||
# Show or not field
|
||||
self.show = bool(
|
||||
(self.required and key not in ["input_variables"])
|
||||
or key
|
||||
in [
|
||||
"allowed_tools",
|
||||
"memory",
|
||||
"prefix",
|
||||
"examples",
|
||||
"temperature",
|
||||
"model_name",
|
||||
"headers",
|
||||
"max_value_length",
|
||||
]
|
||||
or "api_key" in key
|
||||
)
|
||||
|
||||
# Add password field
|
||||
self.password = any(
|
||||
text in key.lower() for text in ["password", "token", "api", "key"]
|
||||
)
|
||||
|
||||
# Add multline
|
||||
self.multiline = key in [
|
||||
"suffix",
|
||||
"prefix",
|
||||
"template",
|
||||
"examples",
|
||||
"code",
|
||||
"headers",
|
||||
]
|
||||
|
||||
# Replace dict type with str
|
||||
if "dict" in self.field_type.lower():
|
||||
self.field_type = "code"
|
||||
|
||||
if key == "dict_":
|
||||
self.field_type = "file"
|
||||
self.suffixes = [".json", ".yaml", ".yml"]
|
||||
self.file_types = ["json", "yaml", "yml"]
|
||||
|
||||
# Replace default value with actual value
|
||||
if "default" in value:
|
||||
self.value = value["default"]
|
||||
|
||||
if key == "headers":
|
||||
self.value = """{'Authorization':
|
||||
'Bearer <token>'}"""
|
||||
|
||||
# Add options to openai
|
||||
if name == "OpenAI" and key == "model_name":
|
||||
self.options = constants.OPENAI_MODELS
|
||||
self.is_list = True
|
||||
elif name == "OpenAIChat" and key == "model_name":
|
||||
self.options = constants.CHAT_OPENAI_MODELS
|
||||
self.is_list = True
|
||||
|
||||
|
||||
class TemplateField(TemplateFieldCreator):
|
||||
pass
|
||||
|
|
@ -46,7 +130,13 @@ class Template(BaseModel):
|
|||
type_name: str
|
||||
fields: list[TemplateField]
|
||||
|
||||
def process_fields(self, name: Optional[str] = None) -> None:
|
||||
for field in self.fields:
|
||||
signature = field.to_dict()
|
||||
field.process_field(field.name, signature, name)
|
||||
|
||||
def to_dict(self):
|
||||
self.process_fields(self.type_name)
|
||||
result = {field.name: field.to_dict() for field in self.fields}
|
||||
result["_type"] = self.type_name # type: ignore
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ def build_template_from_function(
|
|||
return {
|
||||
"template": format_dict(variables, name),
|
||||
"description": docs["Description"],
|
||||
"base_classes": get_base_classes(_class),
|
||||
"base_classes": base_classes,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ def get_base_classes(cls):
|
|||
result = [cls.__name__]
|
||||
if not result:
|
||||
result = [cls.__name__]
|
||||
return list(set(result))
|
||||
return list(set(result + [cls.__name__]))
|
||||
|
||||
|
||||
def get_default_factory(module: str, function: str):
|
||||
|
|
@ -333,8 +333,10 @@ def format_dict(d, name: Optional[str] = None):
|
|||
# Add options to openai
|
||||
if name == "OpenAI" and key == "model_name":
|
||||
value["options"] = constants.OPENAI_MODELS
|
||||
value["list"] = True
|
||||
elif name == "OpenAIChat" and key == "model_name":
|
||||
value["options"] = constants.CHAT_OPENAI_MODELS
|
||||
value["list"] = True
|
||||
|
||||
return d
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue