Merge branch 'dev' of https://github.com/logspace-ai/langflow into NodeModal

This commit is contained in:
Gabriel Almeida 2023-04-19 14:25:20 -03:00
commit 72bb4e45c5
47 changed files with 1319 additions and 516 deletions

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Browser and Version**
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

9
.gitignore vendored
View file

@ -9,6 +9,11 @@ lerna-debug.log*
# Mac
.DS_Store
# VSCode
.vscode
.chroma
.ruff_cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
@ -233,5 +238,5 @@ venv.bak/
.dmypy.json
dmypy.json
# Poetry
.testenv/*
# Poetry
.testenv/*

View file

@ -3,7 +3,7 @@ FROM python:3.10-slim
WORKDIR /app
# Install Poetry
RUN apt-get update && apt-get install gcc g++ curl build-essential -y
RUN apt-get update && apt-get install gcc g++ curl build-essential postgresql-server-dev-all -y
RUN curl -sSL https://install.python-poetry.org | python3 -
# # Add Poetry to PATH
ENV PATH="${PATH}:/root/.local/bin"

895
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "langflow"
version = "0.0.55"
version = "0.0.57"
description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"]
maintainers = [
@ -38,6 +38,15 @@ chromadb = "^0.3.21"
huggingface-hub = "^0.13.3"
rich = "^13.3.3"
llama-cpp-python = "0.1.23"
networkx = "^3.1"
unstructured = "^0.5.11"
pypdf = "^3.7.1"
lxml = "^4.9.2"
pysrt = "^1.1.2"
fake-useragent = "^1.1.3"
docstring-parser = "^0.15"
psycopg2-binary = "^2.9.6"
pyarrow = "^11.0.0"
[tool.poetry.group.dev.dependencies]
black = "^23.1.0"

View file

@ -1,3 +1,4 @@
import base64
import contextlib
import functools
import hashlib
@ -10,6 +11,19 @@ from pathlib import Path
import dill # type: ignore
def create_cache_folder(func):
def wrapper(*args, **kwargs):
# Get the destination folder
cache_path = Path(tempfile.gettempdir()) / PREFIX
# Create the destination folder if it doesn't exist
os.makedirs(cache_path, exist_ok=True)
return func(*args, **kwargs)
return wrapper
def memoize_dict(maxsize=128):
cache = OrderedDict()
@ -39,9 +53,10 @@ def memoize_dict(maxsize=128):
PREFIX = "langflow_cache"
@create_cache_folder
def clear_old_cache_files(max_cache_size: int = 3):
cache_dir = Path(tempfile.gettempdir())
cache_files = list(cache_dir.glob(f"{PREFIX}_*.dill"))
cache_dir = Path(tempfile.gettempdir()) / PREFIX
cache_files = list(cache_dir.glob("*.dill"))
if len(cache_files) > max_cache_size:
cache_files_sorted_by_mtime = sorted(
@ -84,8 +99,40 @@ def filter_json(json_data):
return filtered_data
@create_cache_folder
def save_binary_file(content: str, file_name: str, accepted_types: list[str]) -> str:
"""
Save a binary file to the specified folder.
Args:
content: The content of the file as a bytes object.
file_name: The name of the file, including its extension.
Returns:
The path to the saved file.
"""
if not any(file_name.endswith(suffix) for suffix in accepted_types):
raise ValueError(f"File {file_name} is not accepted")
# Get the destination folder
cache_path = Path(tempfile.gettempdir()) / PREFIX
data = content.split(",")[1]
decoded_bytes = base64.b64decode(data)
# Create the full file path
file_path = os.path.join(cache_path, file_name)
# Save the binary content to the file
with open(file_path, "wb") as file:
file.write(decoded_bytes)
return file_path
@create_cache_folder
def save_cache(hash_val: str, chat_data, clean_old_cache_files: bool):
cache_path = Path(tempfile.gettempdir()) / f"{PREFIX}_{hash_val}.dill"
cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill"
with cache_path.open("wb") as cache_file:
dill.dump(chat_data, cache_file)
@ -93,8 +140,9 @@ def save_cache(hash_val: str, chat_data, clean_old_cache_files: bool):
clear_old_cache_files()
@create_cache_folder
def load_cache(hash_val):
cache_path = Path(tempfile.gettempdir()) / f"{PREFIX}_{hash_val}.dill"
cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill"
if cache_path.exists():
with cache_path.open("rb") as cache_file:
return dill.load(cache_file)

View file

@ -14,6 +14,7 @@ agents:
- initialize_agent
- VectorStoreAgent
- VectorStoreRouterAgent
- SQLAgent
prompts:
- PromptTemplate
@ -61,8 +62,31 @@ vectorstores:
- Chroma
documentloaders:
- AirbyteJSONLoader
- CoNLLULoader
- CSVLoader
- UnstructuredEmailLoader
- EverNoteLoader
- FacebookChatLoader
- GutenbergLoader
- BSHTMLLoader
- UnstructuredHTMLLoader
# - UnstructuredImageLoader # Issue with Python 3.11 (https://github.com/Unstructured-IO/unstructured-inference/issues/83)
- UnstructuredMarkdownLoader
- PyPDFLoader
- UnstructuredPowerPointLoader
- SRTLoader
- TelegramChatLoader
- TextLoader
- UnstructuredWordDocumentLoader
- WebBaseLoader
- AZLyricsLoader
- CollegeConfidentialLoader
- HNLoader
- IFixitLoader
- IMSDbLoader
- GitbookLoader
- ReadTheDocsLoader
textsplitters:
- CharacterTextSplitter

View file

@ -10,6 +10,7 @@ CUSTOM_NODES = {
"initialize_agent": nodes.InitializeAgentNode(),
"VectorStoreAgent": nodes.VectorStoreAgentNode(),
"VectorStoreRouterAgent": nodes.VectorStoreRouterAgentNode(),
"SQLAgent": nodes.SQLAgentNode(),
},
}

View file

@ -3,13 +3,14 @@
# - Defer prompts building to the last moment or when they have all the tools
# - Build each inner agent first, then build the outer agent
import contextlib
import types
import warnings
from copy import deepcopy
from typing import Any, Dict, List, Optional
from langflow.cache import utils as cache_utils
from langflow.graph.constants import DIRECT_TYPES
from langflow.graph.utils import load_file
from langflow.interface import loading
from langflow.interface.listing import ALL_TYPES_DICT
from langflow.utils.logger import logger
@ -88,11 +89,12 @@ class Node:
file_name = value.get("value")
content = value.get("content")
type_to_load = value.get("suffixes")
loaded_dict = load_file(file_name, content, type_to_load)
params[key] = loaded_dict
file_path = cache_utils.save_binary_file(
content=content, file_name=file_name, accepted_types=type_to_load
)
params[key] = file_path
# We should check if the type is in something not
# the opposite
elif value.get("type") not in DIRECT_TYPES:
# Get the edge that connects to this node
edges = [
@ -126,6 +128,9 @@ class Node:
new_value = value.get("value")
if new_value is None:
warnings.warn(f"Value for {key} in {self.node_type} is None. ")
if value.get("type") == "int":
with contextlib.suppress(TypeError, ValueError):
new_value = int(new_value) # type: ignore
params[key] = new_value
# Add _type to params

View file

@ -18,16 +18,16 @@ from langflow.graph.nodes import (
)
from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator
from langflow.interface.documentLoaders.base import documentloader_creator
from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator
from langflow.interface.textSplitters.base import textsplitter_creator
from langflow.interface.text_splitters.base import textsplitter_creator
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator
from langflow.interface.tools.constants import FILE_TOOLS
from langflow.interface.vectorStore.base import vectorstore_creator
from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator
from langflow.utils import payload
@ -62,7 +62,12 @@ class Graph:
if isinstance(node, ToolkitNode):
node.params["llm"] = llm_node
# remove invalid nodes
self.nodes = [node for node in self.nodes if self._validate_node(node)]
self.nodes = [
node
for node in self.nodes
if self._validate_node(node)
or (len(self.nodes) == 1 and len(self.edges) == 0)
]
def _validate_node(self, node: Node) -> bool:
# All nodes that do not have edges are invalid

View file

@ -33,8 +33,8 @@ class AgentNode(Node):
self._build()
#! Cannot deepcopy VectorStore
if self.node_type in ["VectorStoreAgent", "VectorStoreRouterAgent"]:
#! Cannot deepcopy VectorStore, VectorStoreRouter, or SQL agents
if self.node_type in ["VectorStoreAgent", "VectorStoreRouterAgent", "SQLAgent"]:
return self._built_object
return deepcopy(self._built_object)

View file

@ -1,48 +1,4 @@
import base64
import csv
import io
import json
import re
from typing import Any
import yaml
def load_file(file_name, file_content, accepted_types) -> Any:
"""Load a file from a string."""
# Check if the file is accepted
if not any(file_name.endswith(suffix) for suffix in accepted_types):
raise ValueError(f"File {file_name} is not accepted")
# Get the suffix
suffix = file_name.split(".")[-1]
# file_content == 'data:application/x-yaml;base64,b3BlbmFwaTogIjMuMC4wIg...'
data = file_content.split(",")[1]
decoded_bytes = base64.b64decode(data)
# Convert the bytes object to a string
decoded_string = decoded_bytes.decode("utf-8")
if suffix == "json":
# Return the json content
return json.loads(decoded_string)
elif suffix in ["yaml", "yml"]:
# Return the yaml content
loaded_yaml = yaml.load(decoded_string, Loader=yaml.FullLoader)
try:
from langchain.agents.agent_toolkits.openapi.spec import reduce_openapi_spec # type: ignore
return reduce_openapi_spec(loaded_yaml)
except ImportError:
return loaded_yaml
elif suffix == "csv":
# Load the csv content
csv_reader = csv.DictReader(io.StringIO(decoded_string))
return list(csv_reader)
elif suffix == "txt":
# Return the text content
return decoded_string
else:
raise ValueError(f"File {file_name} is not accepted")
def validate_prompt(prompt: str):

View file

@ -6,6 +6,7 @@ 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.utils.logger import logger
from langflow.utils.util import build_template_from_class
@ -31,6 +32,9 @@ class AgentCreator(LangChainTypeCreator):
)
except ValueError as exc:
raise ValueError("Agent not found") from exc
except AttributeError as exc:
logger.error(f"Agent {name} not loaded: {exc}")
return None
# Now this is a generator
def to_list(self) -> List[str]:

View file

@ -1,8 +1,14 @@
from typing import Any, List, Optional
from langchain import LLMChain
from langchain.agents import AgentExecutor, Tool, ZeroShotAgent, initialize_agent
from langchain.agents import (
AgentExecutor,
Tool,
ZeroShotAgent,
initialize_agent,
)
from langchain.agents.agent_toolkits import (
SQLDatabaseToolkit,
VectorStoreInfo,
VectorStoreRouterToolkit,
VectorStoreToolkit,
@ -11,6 +17,7 @@ from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX
from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit
from langchain.agents.agent_toolkits.pandas.prompt import PREFIX as PANDAS_PREFIX
from langchain.agents.agent_toolkits.pandas.prompt import SUFFIX as PANDAS_SUFFIX
from langchain.agents.agent_toolkits.sql.prompt import SQL_PREFIX, SQL_SUFFIX
from langchain.agents.agent_toolkits.vectorstore.prompt import (
PREFIX as VECTORSTORE_PREFIX,
)
@ -18,10 +25,13 @@ from langchain.agents.agent_toolkits.vectorstore.prompt import (
ROUTER_PREFIX as VECTORSTORE_ROUTER_PREFIX,
)
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS as SQL_FORMAT_INSTRUCTIONS
from langchain.llms.base import BaseLLM
from langchain.memory.chat_memory import BaseChatMemory
from langchain.schema import BaseLanguageModel
from langchain.sql_database import SQLDatabase
from langchain.tools.python.tool import PythonAstREPLTool
from langchain.tools.sql_database.prompt import QUERY_CHECKER
class JsonAgent(AgentExecutor):
@ -77,7 +87,7 @@ class CSVAgent(AgentExecutor):
@classmethod
def from_toolkit_and_llm(
cls,
path: dict,
path: str,
llm: BaseLanguageModel,
pandas_kwargs: Optional[dict] = None,
**kwargs: Any
@ -85,7 +95,7 @@ class CSVAgent(AgentExecutor):
import pandas as pd # type: ignore
_kwargs = pandas_kwargs or {}
df = pd.DataFrame.from_dict(path, **_kwargs)
df = pd.read_csv(path, **_kwargs)
tools = [PythonAstREPLTool(locals={"df": df})] # type: ignore
prompt = ZeroShotAgent.create_prompt(
@ -146,6 +156,76 @@ class VectorStoreAgent(AgentExecutor):
return super().run(*args, **kwargs)
class SQLAgent(AgentExecutor):
"""SQL agent"""
@staticmethod
def function_name():
return "SQLAgent"
@classmethod
def initialize(cls, *args, **kwargs):
return cls.from_toolkit_and_llm(*args, **kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@classmethod
def from_toolkit_and_llm(cls, llm: BaseLLM, database_uri: str, **kwargs: Any):
"""Construct a sql agent from an LLM and tools."""
db = SQLDatabase.from_uri(database_uri)
toolkit = SQLDatabaseToolkit(db=db)
# The right code should be this, but there is a problem with tools = toolkit.get_tools()
# related to `OPENAI_API_KEY`
# return create_sql_agent(llm=llm, toolkit=toolkit, verbose=True)
from langchain.prompts import PromptTemplate
from langchain.tools.sql_database.tool import (
InfoSQLDatabaseTool,
ListSQLDatabaseTool,
QueryCheckerTool,
QuerySQLDataBaseTool,
)
llmchain = LLMChain(
llm=llm,
prompt=PromptTemplate(
template=QUERY_CHECKER, input_variables=["query", "dialect"]
),
)
tools = [
QuerySQLDataBaseTool(db=db), # type: ignore
InfoSQLDatabaseTool(db=db), # type: ignore
ListSQLDatabaseTool(db=db), # type: ignore
QueryCheckerTool(db=db, llm_chain=llmchain), # type: ignore
]
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
prompt = ZeroShotAgent.create_prompt(
tools=tools, # type: ignore
prefix=prefix,
suffix=SQL_SUFFIX,
format_instructions=SQL_FORMAT_INSTRUCTIONS,
)
llm_chain = LLMChain(
llm=llm,
prompt=prompt,
)
tool_names = [tool.name for tool in tools] # type: ignore
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs)
return AgentExecutor.from_agent_and_tools(
agent=agent,
tools=tools, # type: ignore
verbose=True,
max_iterations=15,
early_stopping_method="force",
)
def run(self, *args, **kwargs):
return super().run(*args, **kwargs)
class VectorStoreRouterAgent(AgentExecutor):
"""Vector Store Router Agent"""
@ -218,4 +298,5 @@ CUSTOM_AGENTS = {
"initialize_agent": InitializeAgent,
"VectorStoreAgent": VectorStoreAgent,
"VectorStoreRouterAgent": VectorStoreRouterAgent,
"SQLAgent": SQLAgent,
}

View file

@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Type, Union
from pydantic import BaseModel
from langflow.template.base import FrontendNode, Template, TemplateField
from langflow.utils.logger import logger
# Assuming necessary imports for Field, Template, and FrontendNode classes
@ -39,14 +40,18 @@ class LangChainTypeCreator(BaseModel, ABC):
# frontend_node.to_dict() returns a dict with the following structure:
# {name: {template: {fields}, description: str}}
# so we should update the result dict
result[self.type_name].update(self.frontend_node(name).to_dict())
node = self.frontend_node(name)
if node is not None:
node = node.to_dict()
result[self.type_name].update(node)
return result
def frontend_node(self, name) -> FrontendNode:
def frontend_node(self, name) -> Union[FrontendNode, None]:
signature = self.get_signature(name)
if signature is None:
raise ValueError(f"{name} not found")
logger.error(f"Node {name} not loaded")
return None
if isinstance(signature, FrontendNode):
return signature
fields = [

View file

@ -5,6 +5,7 @@ from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import chain_type_to_cls_dict
from langflow.settings import settings
from langflow.template.nodes import ChainFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
# Assuming necessary imports for Field, Template, and FrontendNode classes
@ -39,6 +40,9 @@ class ChainCreator(LangChainTypeCreator):
return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc:
raise ValueError("Chain not found") from exc
except AttributeError as exc:
logger.error(f"Chain {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
custom_chains = list(get_custom_nodes("chains").keys())

View file

@ -1,64 +0,0 @@
from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
from langflow.interface.documentLoaders.custom import CUSTOM_DOCUMENTLOADERS
from langflow.settings import settings
from langflow.utils.util import build_template_from_class
class DocumentLoaderCreator(LangChainTypeCreator):
type_name: str = "documentloaders"
@property
def type_to_loader_dict(self) -> Dict:
types = documentloaders_type_to_cls_dict
# Drop some types that are reimplemented with the same name
types.pop("TextLoader")
for name, documentloader in CUSTOM_DOCUMENTLOADERS.items():
types[name] = documentloader
return types
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a document loader."""
try:
signature = build_template_from_class(
name, documentloaders_type_to_cls_dict
)
if name == "TextLoader":
signature["template"]["file"] = {
"type": "file",
"required": True,
"show": True,
"name": "path",
"value": "",
"suffixes": [".txt"],
"fileTypes": ["txt"],
}
elif name == "WebBaseLoader":
signature["template"]["web_path"] = {
"type": "str",
"required": True,
"show": True,
"name": "web_path",
"value": "",
"display_name": "Web Path",
}
return signature
except ValueError as exc:
raise ValueError(f"Documment Loader {name} not found") from exc
def to_list(self) -> List[str]:
return [
documentloader.__name__
for documentloader in self.type_to_loader_dict.values()
if documentloader.__name__ in settings.documentloaders or settings.dev
]
documentloader_creator = DocumentLoaderCreator()

View file

@ -1,22 +0,0 @@
"""Load text files."""
from typing import List
from langchain.docstore.document import Document
from langchain.document_loaders.base import BaseLoader
class TextLoader(BaseLoader):
"""Load Text files."""
def __init__(self, file: str):
"""Initialize with file path."""
self.file = file
def load(self) -> List[Document]:
"""Load from file path."""
return [Document(page_content=self.file, metadata={"source": "loaded"})]
CUSTOM_DOCUMENTLOADERS = {
"TextLoader": TextLoader,
}

View file

@ -0,0 +1,146 @@
from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import documentloaders_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
def build_file_path_template(
suffixes: list, fileTypes: list, name: str = "file_path"
) -> Dict:
"""Build a file path template for a document loader."""
return {
"type": "file",
"required": True,
"show": True,
"name": name,
"value": "",
"suffixes": suffixes,
"fileTypes": fileTypes,
}
class DocumentLoaderCreator(LangChainTypeCreator):
type_name: str = "documentloaders"
@property
def type_to_loader_dict(self) -> Dict:
return documentloaders_type_to_cls_dict
def get_signature(self, name: str) -> Optional[Dict]:
"""Get the signature of a document loader."""
try:
signature = build_template_from_class(
name, documentloaders_type_to_cls_dict
)
file_path_templates = {
"AirbyteJSONLoader": build_file_path_template(
suffixes=[".json"], fileTypes=["json"]
),
"CoNLLULoader": build_file_path_template(
suffixes=[".csv"], fileTypes=["csv"]
),
"CSVLoader": build_file_path_template(
suffixes=[".csv"], fileTypes=["csv"]
),
"UnstructuredEmailLoader": build_file_path_template(
suffixes=[".eml"], fileTypes=["eml"]
),
"EverNoteLoader": build_file_path_template(
suffixes=[".xml"], fileTypes=["xml"]
),
"FacebookChatLoader": build_file_path_template(
suffixes=[".json"], fileTypes=["json"]
),
"GutenbergLoader": build_file_path_template(
suffixes=[".txt"], fileTypes=["txt"]
),
"BSHTMLLoader": build_file_path_template(
suffixes=[".html"], fileTypes=["html"]
),
"UnstructuredHTMLLoader": build_file_path_template(
suffixes=[".html"], fileTypes=["html"]
),
"UnstructuredImageLoader": build_file_path_template(
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
),
"UnstructuredMarkdownLoader": build_file_path_template(
suffixes=[".md"], fileTypes=["md"]
),
"PyPDFLoader": build_file_path_template(
suffixes=[".pdf"], fileTypes=["pdf"]
),
"UnstructuredPowerPointLoader": build_file_path_template(
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
),
"SRTLoader": build_file_path_template(
suffixes=[".srt"], fileTypes=["srt"]
),
"TelegramChatLoader": build_file_path_template(
suffixes=[".json"], fileTypes=["json"]
),
"TextLoader": build_file_path_template(
suffixes=[".txt"], fileTypes=["txt"]
),
"UnstructuredWordDocumentLoader": build_file_path_template(
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
),
}
if name in file_path_templates:
signature["template"]["file_path"] = file_path_templates[name]
elif name in {
"WebBaseLoader",
"AZLyricsLoader",
"CollegeConfidentialLoader",
"HNLoader",
"IFixitLoader",
"IMSDbLoader",
}:
signature["template"]["web_path"] = {
"type": "str",
"required": True,
"show": True,
"name": "web_path",
"value": "",
"display_name": "Web Page",
}
elif name in {"GitbookLoader"}:
signature["template"]["web_page"] = {
"type": "str",
"required": True,
"show": True,
"name": "web_page",
"value": "",
"display_name": "Web Page",
}
elif name in {"ReadTheDocsLoader"}:
signature["template"]["path"] = {
"type": "str",
"required": True,
"show": True,
"name": "path",
"value": "",
"display_name": "Web Page",
}
return signature
except ValueError as exc:
raise ValueError(f"Documment Loader {name} not found") from exc
except AttributeError as exc:
logger.error(f"Documment Loader {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [
documentloader.__name__
for documentloader in self.type_to_loader_dict.values()
if documentloader.__name__ in settings.documentloaders or settings.dev
]
documentloader_creator = DocumentLoaderCreator()

View file

@ -3,6 +3,7 @@ from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import embedding_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
@ -20,6 +21,10 @@ class EmbeddingCreator(LangChainTypeCreator):
except ValueError as exc:
raise ValueError(f"Embedding {name} not found") from exc
except AttributeError as exc:
logger.error(f"Embedding {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [
embedding.__name__

View file

@ -10,7 +10,6 @@ from langchain.chat_models.base import BaseChatModel
from langchain.llms.base import BaseLLM
from langchain.tools import BaseTool
from langflow.interface.documentLoaders.custom import CUSTOM_DOCUMENTLOADERS
from langflow.interface.tools.util import get_tool_by_name
@ -132,8 +131,6 @@ def import_vectorstore(vectorstore: str) -> Any:
def import_documentloader(documentloader: str) -> Any:
"""Import documentloader from documentloader name"""
if documentloader in CUSTOM_DOCUMENTLOADERS:
return CUSTOM_DOCUMENTLOADERS[documentloader]
return import_class(f"langchain.document_loaders.{documentloader}")

View file

@ -1,14 +1,14 @@
from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator
from langflow.interface.documentLoaders.base import documentloader_creator
from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator
from langflow.interface.textSplitters.base import textsplitter_creator
from langflow.interface.text_splitters.base import textsplitter_creator
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator
from langflow.interface.vectorStore.base import vectorstore_creator
from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator

View file

@ -4,6 +4,7 @@ from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import llm_type_to_cls_dict
from langflow.settings import settings
from langflow.template.nodes import LLMFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
@ -27,6 +28,10 @@ class LLMCreator(LangChainTypeCreator):
except ValueError as exc:
raise ValueError("LLM not found") from exc
except AttributeError as exc:
logger.error(f"LLM {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [
llm.__name__

View file

@ -22,6 +22,7 @@ from langflow.interface.agents.custom import CUSTOM_AGENTS
from langflow.interface.importing.utils import import_by_type
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.types import get_type_list
from langflow.interface.utils import load_file_into_dict
from langflow.utils import util, validate
@ -36,21 +37,27 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
if base_type == "agents":
# We need to initialize it differently
return load_agent_executor(class_object, params)
elif node_type == "ZeroShotPrompt":
if "tools" not in params:
params["tools"] = []
return ZeroShotAgent.create_prompt(**params)
elif node_type == "PythonFunction":
# If the node_type is "PythonFunction"
# we need to get the function from the params
# which will be a str containing a python function
# and then we need to compile it and return the function
# as the instance
function_string = params["code"]
if isinstance(function_string, str):
return validate.eval_function(function_string)
raise ValueError("Function should be a string")
elif base_type == "prompts":
if node_type == "ZeroShotPrompt":
if "tools" not in params:
params["tools"] = []
return ZeroShotAgent.create_prompt(**params)
elif base_type == "tools":
if node_type == "JsonSpec":
params["dict_"] = load_file_into_dict(params.pop("path"))
return class_object(**params)
elif node_type == "PythonFunction":
# If the node_type is "PythonFunction"
# we need to get the function from the params
# which will be a str containing a python function
# and then we need to compile it and return the function
# as the instance
function_string = params["code"]
if isinstance(function_string, str):
return validate.eval_function(function_string)
raise ValueError("Function should be a string")
elif node_type.lower() == "tool":
return class_object(**params)
elif base_type == "toolkits":
loaded_toolkit = class_object(**params)
# Check if node_type has a loader
@ -68,8 +75,8 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
documents = params.pop("documents")
text_splitter = class_object(**params)
return text_splitter.split_documents(documents)
else:
return class_object(**params)
return class_object(**params)
def load_flow_from_json(path: str):

View file

@ -5,6 +5,7 @@ from langflow.interface.custom_lists import memory_type_to_cls_dict
from langflow.settings import settings
from langflow.template.base import FrontendNode
from langflow.template.nodes import MemoryFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
@ -28,6 +29,9 @@ class MemoryCreator(LangChainTypeCreator):
return build_template_from_class(name, memory_type_to_cls_dict)
except ValueError as exc:
raise ValueError("Memory not found") from exc
except AttributeError as exc:
logger.error(f"Memory {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [

View file

@ -7,6 +7,7 @@ from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.settings import settings
from langflow.template.nodes import PromptFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
@ -43,7 +44,11 @@ class PromptCreator(LangChainTypeCreator):
return get_custom_nodes(self.type_name)[name]
return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc:
raise ValueError("Prompt not found") from exc
# raise ValueError("Prompt not found") from exc
logger.error(f"Prompt {name} not found: {exc}")
except AttributeError as exc:
logger.error(f"Prompt {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
custom_prompts = get_custom_nodes("prompts")

View file

@ -170,10 +170,12 @@ def fix_memory_inputs(langchain_object):
if langchain_object.memory.memory_key in langchain_object.input_variables:
return
except AttributeError:
if (
langchain_object.memory.memory_key
in langchain_object.prompt.input_variables
):
input_variables = (
langchain_object.prompt.input_variables
if hasattr(langchain_object, "prompt")
else langchain_object.input_keys
)
if langchain_object.memory.memory_key in input_variables:
return
possible_new_mem_key = get_memory_key(langchain_object)
@ -191,9 +193,12 @@ def get_result_and_thought_using_graph(langchain_object, message: str):
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
memory_key = langchain_object.memory.memory_key
for key in langchain_object.input_keys:
if key not in [memory_key, "chat_history"]:
chat_input = {key: message}
if hasattr(langchain_object, "input_keys"):
for key in langchain_object.input_keys:
if key not in [memory_key, "chat_history"]:
chat_input = {key: message}
else:
chat_input = message # type: ignore
if hasattr(langchain_object, "return_intermediate_steps"):
# https://github.com/hwchase17/langchain/issues/2068

View file

@ -1,3 +0,0 @@
from langflow.interface.textSplitters.base import TextSplitterCreator
__all__ = ["TextSplitterCreator"]

View file

@ -0,0 +1,3 @@
from langflow.interface.text_splitters.base import TextSplitterCreator
__all__ = ["TextSplitterCreator"]

View file

@ -3,6 +3,7 @@ from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import textsplitter_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
@ -25,9 +26,39 @@ class TextSplitterCreator(LangChainTypeCreator):
"name": "documents",
}
signature["template"]["separator"] = {
"type": "str",
"required": True,
"show": True,
"value": ".",
"name": "separator",
"display_name": "Separator",
}
signature["template"]["chunk_size"] = {
"type": "int",
"required": True,
"show": True,
"value": 4000,
"name": "chunk_size",
"display_name": "Chunk Size",
}
signature["template"]["chunk_overlap"] = {
"type": "int",
"required": True,
"show": True,
"value": 200,
"name": "chunk_overlap",
"display_name": "Chunk Overlap",
}
return signature
except ValueError as exc:
raise ValueError(f"Text Splitter {name} not found") from exc
except AttributeError as exc:
logger.error(f"Text Splitter {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [

View file

@ -5,6 +5,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.logger import logger
from langflow.utils.util import build_template_from_class
@ -44,6 +45,9 @@ class ToolkitCreator(LangChainTypeCreator):
return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc:
raise ValueError("Prompt not found") from exc
except AttributeError as exc:
logger.error(f"Prompt {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return list(self.type_to_loader_dict.keys())

View file

@ -47,12 +47,14 @@ TOOL_INPUTS = {
value="",
multiline=True,
),
"dict_": TemplateField(
"path": TemplateField(
field_type="file",
required=True,
is_list=False,
show=True,
value="",
suffixes=[".json", ".yaml", ".yml"],
fileTypes=["json", "yaml", "yml"],
),
}
@ -104,8 +106,8 @@ class ToolCreator(LangChainTypeCreator):
n_dict = {val[0]: val[1] for val in _EXTRA_OPTIONAL_TOOLS.values()} # type: ignore
extra_keys = n_dict[all_tools[tool_type]["fcn"]]
params = extra_keys
elif tool_type == "Tool":
params = ["name", "description", "func"]
# elif tool_type == "Tool":
# params = ["name", "description", "func"]
elif tool_type in CUSTOM_TOOLS:
# Get custom tool params
params = all_tools[name]["params"] # type: ignore
@ -114,6 +116,8 @@ class ToolCreator(LangChainTypeCreator):
return node
elif tool_type in FILE_TOOLS:
params = all_tools[name]["params"] # type: ignore
if tool_type == "JsonSpec":
params["path"] = params.pop("dict_") # type: ignore
base_classes += [name]
else:
params = []

View file

@ -1,14 +1,14 @@
from langflow.interface.agents.base import agent_creator
from langflow.interface.chains.base import chain_creator
from langflow.interface.documentLoaders.base import documentloader_creator
from langflow.interface.document_loaders.base import documentloader_creator
from langflow.interface.embeddings.base import embedding_creator
from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator
from langflow.interface.textSplitters.base import textsplitter_creator
from langflow.interface.text_splitters.base import textsplitter_creator
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator
from langflow.interface.vectorStore.base import vectorstore_creator
from langflow.interface.vector_store.base import vectorstore_creator
from langflow.interface.wrappers.base import wrapper_creator

View file

@ -0,0 +1,22 @@
import json
import os
import yaml
def load_file_into_dict(file_path: str) -> dict:
if not os.path.exists(file_path):
raise FileNotFoundError(f"File not found: {file_path}")
file_extension = os.path.splitext(file_path)[1].lower()
if file_extension == ".json":
with open(file_path, "r") as json_file:
data = json.load(json_file)
elif file_extension in [".yaml", ".yml"]:
with open(file_path, "r") as yaml_file:
data = yaml.safe_load(yaml_file)
else:
raise ValueError("Unsupported file type. Please provide a JSON or YAML file.")
return data

View file

@ -3,6 +3,7 @@ from typing import Dict, List, Optional
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import vectorstores_type_to_cls_dict
from langflow.settings import settings
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
@ -39,6 +40,9 @@ class VectorstoreCreator(LangChainTypeCreator):
except ValueError as exc:
raise ValueError(f"Vector Store {name} not found") from exc
except AttributeError as exc:
logger.error(f"Vector Store {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return [

View file

@ -3,6 +3,7 @@ from typing import Dict, List, Optional
from langchain import requests
from langflow.interface.base import LangChainTypeCreator
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class
@ -22,6 +23,9 @@ class WrapperCreator(LangChainTypeCreator):
return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc:
raise ValueError("Wrapper not found") from exc
except AttributeError as exc:
logger.error(f"Wrapper {name} not loaded: {exc}")
return None
def to_list(self) -> List[str]:
return list(self.type_to_loader_dict.keys())

View file

@ -104,7 +104,7 @@ class PythonFunctionNode(FrontendNode):
class ToolNode(FrontendNode):
name: str = "Tool"
template: Template = Template(
type_name="tool",
type_name="Tool",
fields=[
TemplateField(
field_type="str",
@ -127,19 +127,27 @@ class ToolNode(FrontendNode):
name="description",
),
TemplateField(
field_type="str",
name="func",
field_type="function",
required=True,
is_list=False,
show=True,
multiline=True,
),
TemplateField(
field_type="bool",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=True,
value="",
name="func",
multiline=False,
value=False,
name="return_direct",
),
],
)
description: str = "Tool to be used in the flow."
base_classes: list[str] = ["BaseTool"]
base_classes: list[str] = ["Tool"]
def to_dict(self):
return super().to_dict()
@ -304,6 +312,37 @@ class VectorStoreRouterAgentNode(FrontendNode):
return super().to_dict()
class SQLAgentNode(FrontendNode):
name: str = "SQLAgent"
template: Template = Template(
type_name="sql_agent",
fields=[
TemplateField(
field_type="str",
required=True,
placeholder="",
is_list=False,
show=True,
multiline=False,
value="",
name="database_uri",
),
TemplateField(
field_type="BaseLanguageModel",
required=True,
show=True,
name="llm",
display_name="LLM",
),
],
)
description: str = """Construct an agent from a Vector Store Router."""
base_classes: list[str] = ["AgentExecutor"]
def to_dict(self):
return super().to_dict()
class PromptFrontendNode(FrontendNode):
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
@ -348,6 +387,7 @@ class MemoryFrontendNode(FrontendNode):
field.field_type = "int"
field.value = 10
field.display_name = "Memory Size"
field.password = False
class ChainFrontendNode(FrontendNode):
@ -358,6 +398,14 @@ class ChainFrontendNode(FrontendNode):
if "key" in field.name:
field.password = False
field.show = False
if field.name in ["input_key", "output_key"]:
field.required = True
field.show = True
# Separated for possible future changes
if field.name == "prompt":
# if no prompt is provided, use the default prompt
field.required = False
field.show = True
class LLMFrontendNode(FrontendNode):

View file

@ -33,6 +33,10 @@ def get_root_node(graph):
Returns the root node of the template.
"""
incoming_edges = {edge.source for edge in graph.edges}
if not incoming_edges and len(graph.nodes) == 1:
return graph.nodes[0]
return next((node for node in graph.nodes if node not in incoming_edges), None)

View file

@ -3,6 +3,8 @@ import inspect
import re
from typing import Dict, Optional
from docstring_parser import parse # type: ignore
from langflow.template.constants import FORCE_SHOW_FIELDS
from langflow.utils import constants
@ -65,7 +67,8 @@ def build_template_from_function(
if v.__annotations__["return"].__name__ == name:
_class = v.__annotations__["return"]
docs = get_class_doc(_class)
# Get the docstring
docs = parse(_class.__doc__)
variables = {"_type": _type}
for class_field_items, value in _class.__fields__.items():
@ -86,8 +89,8 @@ def build_template_from_function(
variables[class_field_items][name_] = value_
variables[class_field_items]["placeholder"] = (
docs["Attributes"][class_field_items]
if class_field_items in docs["Attributes"]
docs.params[class_field_items]
if class_field_items in docs.params
else ""
)
# Adding function to base classes to allow
@ -98,7 +101,7 @@ def build_template_from_function(
return {
"template": format_dict(variables, name),
"description": docs["Description"],
"description": docs.short_description or "",
"base_classes": base_classes,
}
@ -117,7 +120,7 @@ def build_template_from_class(
_class = v
# Get the docstring
docs = get_class_doc(_class)
docs = parse(_class.__doc__)
variables = {"_type": _type}
@ -140,8 +143,8 @@ def build_template_from_class(
variables[class_field_items][name_] = value_
variables[class_field_items]["placeholder"] = (
docs["Attributes"][class_field_items]
if class_field_items in docs["Attributes"]
docs.params[class_field_items]
if class_field_items in docs.params
else ""
)
base_classes = get_base_classes(_class)
@ -151,7 +154,7 @@ def build_template_from_class(
base_classes.append("function")
return {
"template": format_dict(variables, name),
"description": docs["Description"],
"description": docs.short_description or "",
"base_classes": base_classes,
}
@ -188,60 +191,6 @@ def get_default_factory(module: str, function: str):
return None
def get_class_doc(class_name):
"""
Extracts information from the docstring of a given class.
Args:
class_name: the class to extract information from
Returns:
A dictionary containing the extracted information, with keys
for 'Description', 'Parameters', 'Attributes', and 'Returns'.
"""
# Template
data = {
"Description": "",
"Parameters": {},
"Attributes": {},
"Example": [],
"Returns": {},
}
# Get the class docstring
docstring = class_name.__doc__
if not docstring:
return data
# Parse the docstring to extract information
lines = docstring.split("\n")
current_section = "Description"
for line in lines:
line = line.strip()
if not line:
continue
if (
line.startswith(tuple(data.keys()))
and len(line.split()) == 1
and line.endswith(":")
):
current_section = line[:-1]
continue
if current_section in ["Description", "Example"]:
data[current_section] += line
else:
param, desc = line.split(":")
data[current_section][param.strip()] = desc.strip()
return data
def format_dict(d, name: Optional[str] = None):
"""
Formats a dictionary by removing certain keys and modifying the

View file

@ -59,7 +59,12 @@ def eval_function(function_string: str):
# Execute the code string in the new namespace
exec(function_string, namespace)
function_object = next(
(obj for name, obj in namespace.items() if isinstance(obj, types.FunctionType)),
(
obj
for name, obj in namespace.items()
if isinstance(obj, types.FunctionType)
and obj.__code__.co_filename == "<string>"
),
None,
)
if function_object is None:

View file

@ -206,7 +206,7 @@
"type": "JsonSpec",
"node": {
"template": {
"dict_": {
"path": {
"required": true,
"placeholder": "",
"show": true,
@ -218,7 +218,7 @@
".yml"
],
"password": false,
"name": "dict_",
"name": "path",
"type": "file",
"list": false,
"fileTypes": [

View file

@ -49,7 +49,7 @@ def test_cache_creation(basic_data_graph):
)
save_cache(computed_hash, langchain_object, is_first_message)
# Check if the cache file exists
cache_file = Path(tempfile.gettempdir()) / f"{PREFIX}_{computed_hash}.dill"
cache_file = Path(tempfile.gettempdir()) / f"{PREFIX}/{computed_hash}.dill"
assert cache_file.exists()

View file

@ -53,9 +53,9 @@ def test_conversation_chain(client: TestClient):
"list": False,
}
assert template["input_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "input",
"password": False,
@ -64,9 +64,9 @@ def test_conversation_chain(client: TestClient):
"list": False,
}
assert template["output_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "response",
"password": False,
@ -125,9 +125,9 @@ def test_llm_chain(client: TestClient):
"list": False,
}
assert template["output_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "text",
"password": False,
@ -179,9 +179,9 @@ def test_llm_checker_chain(client: TestClient):
"list": False,
}
assert template["input_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "query",
"password": False,
@ -190,9 +190,9 @@ def test_llm_checker_chain(client: TestClient):
"list": False,
}
assert template["output_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "result",
"password": False,
@ -251,9 +251,9 @@ def test_llm_math_chain(client: TestClient):
"list": False,
}
assert template["input_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "question",
"password": False,
@ -262,9 +262,9 @@ def test_llm_math_chain(client: TestClient):
"list": False,
}
assert template["output_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "answer",
"password": False,
@ -338,9 +338,9 @@ def test_series_character_chain(client: TestClient):
"list": False,
}
assert template["input_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "input",
"password": False,
@ -349,9 +349,9 @@ def test_series_character_chain(client: TestClient):
"list": False,
}
assert template["output_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "response",
"password": False,
@ -462,7 +462,7 @@ def test_mid_journey_prompt_chain(client: TestClient):
assert template["prompt"] == {
"required": False,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": {
"input_variables": ["history", "input"],
@ -489,9 +489,9 @@ def test_mid_journey_prompt_chain(client: TestClient):
"list": False,
}
assert template["output_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "response",
"password": False,
@ -500,9 +500,9 @@ def test_mid_journey_prompt_chain(client: TestClient):
"list": False,
}
assert template["input_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "input",
"password": False,
@ -590,7 +590,7 @@ def test_time_travel_guide_chain(client: TestClient):
assert template["prompt"] == {
"required": False,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": {
"input_variables": ["history", "input"],
@ -617,9 +617,9 @@ def test_time_travel_guide_chain(client: TestClient):
"list": False,
}
assert template["output_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "response",
"password": False,
@ -629,9 +629,9 @@ def test_time_travel_guide_chain(client: TestClient):
}
assert template["input_key"] == {
"required": False,
"required": True,
"placeholder": "",
"show": False,
"show": True,
"multiline": False,
"value": "input",
"password": False,

View file

@ -440,7 +440,7 @@ def test_chat_open_ai(client: TestClient):
assert template["_type"] == "ChatOpenAI"
assert (
model["description"]
== "Wrapper around OpenAI Chat large language models.To use, you should have the ``openai`` python package installed, and theenvironment variable ``OPENAI_API_KEY`` set with your API key.Any parameters that are valid to be passed to the openai.create call can be passedin, even if not explicitly saved on this class." # noqa E501
== "Wrapper around OpenAI Chat large language models." # noqa E501
)
assert set(model["base_classes"]) == {
"BaseChatModel",

View file

@ -8,7 +8,6 @@ from langflow.utils.util import (
build_template_from_function,
format_dict,
get_base_classes,
get_class_doc,
get_default_factory,
)
from pydantic import BaseModel
@ -280,12 +279,3 @@ def test_get_default_factory():
default_value = get_default_factory(module_name, function_repr)
assert default_value == "default_value"
# Test get_class_doc
def test_get_class_doc():
class_doc_parent = get_class_doc(Parent)
class_doc_child = get_class_doc(Child)
assert class_doc_parent["Description"] == "Parent Class"
assert class_doc_child["Description"] == "Child Class"