Merge fix with dev
This commit is contained in:
commit
eac1f7c922
57 changed files with 2091 additions and 555 deletions
1119
poetry.lock
generated
1119
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langflow"
|
name = "langflow"
|
||||||
version = "0.1.6"
|
version = "0.1.7"
|
||||||
description = "A Python package with a built-in web application"
|
description = "A Python package with a built-in web application"
|
||||||
authors = ["Logspace <contact@logspace.ai>"]
|
authors = ["Logspace <contact@logspace.ai>"]
|
||||||
maintainers = [
|
maintainers = [
|
||||||
|
|
@ -23,14 +23,14 @@ langflow = "langflow.__main__:main"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.9,<3.12"
|
python = ">=3.9,<3.12"
|
||||||
fastapi = "^0.97.0"
|
fastapi = "^0.98.0"
|
||||||
uvicorn = "^0.22.0"
|
uvicorn = "^0.22.0"
|
||||||
beautifulsoup4 = "^4.11.2"
|
beautifulsoup4 = "^4.11.2"
|
||||||
google-search-results = "^2.4.1"
|
google-search-results = "^2.4.1"
|
||||||
google-api-python-client = "^2.79.0"
|
google-api-python-client = "^2.79.0"
|
||||||
typer = "^0.9.0"
|
typer = "^0.9.0"
|
||||||
gunicorn = "^20.1.0"
|
gunicorn = "^20.1.0"
|
||||||
langchain = "^0.0.202"
|
langchain = "^0.0.211"
|
||||||
openai = "^0.27.8"
|
openai = "^0.27.8"
|
||||||
types-pyyaml = "^6.0.12.8"
|
types-pyyaml = "^6.0.12.8"
|
||||||
pandas = "^1.5.3"
|
pandas = "^1.5.3"
|
||||||
|
|
@ -51,12 +51,13 @@ tiktoken = "~0.4.0"
|
||||||
wikipedia = "^1.4.0"
|
wikipedia = "^1.4.0"
|
||||||
langchain-serve = { version = ">0.0.39", optional = true }
|
langchain-serve = { version = ">0.0.39", optional = true }
|
||||||
qdrant-client = "^1.2.0"
|
qdrant-client = "^1.2.0"
|
||||||
websockets = "^11.0.3"
|
websockets = "^10.3"
|
||||||
weaviate-client = "^3.21.0"
|
weaviate-client = "^3.21.0"
|
||||||
jina = "3.15.2"
|
jina = "3.15.2"
|
||||||
sentence-transformers = "^2.2.2"
|
sentence-transformers = "^2.2.2"
|
||||||
ctransformers = "^0.2.2"
|
ctransformers = "^0.2.2"
|
||||||
cohere = "^4.6.0"
|
cohere = "^4.6.0"
|
||||||
|
python-multipart = "^0.0.6"
|
||||||
sqlmodel = "^0.0.8"
|
sqlmodel = "^0.0.8"
|
||||||
faiss-cpu = "^1.7.4"
|
faiss-cpu = "^1.7.4"
|
||||||
anthropic = "^0.2.10"
|
anthropic = "^0.2.10"
|
||||||
|
|
@ -64,6 +65,11 @@ orjson = "^3.9.1"
|
||||||
multiprocess = "^0.70.14"
|
multiprocess = "^0.70.14"
|
||||||
cachetools = "^5.3.1"
|
cachetools = "^5.3.1"
|
||||||
types-cachetools = "^5.3.0.5"
|
types-cachetools = "^5.3.0.5"
|
||||||
|
appdirs = "^1.4.4"
|
||||||
|
pinecone-client = "^2.2.2"
|
||||||
|
supabase = "^1.0.3"
|
||||||
|
pymongo = "^4.4.0"
|
||||||
|
certifi = "^2023.5.7"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
|
@ -78,6 +84,7 @@ requests = "^2.28.0"
|
||||||
pytest-cov = "^4.0.0"
|
pytest-cov = "^4.0.0"
|
||||||
pandas-stubs = "^2.0.0.230412"
|
pandas-stubs = "^2.0.0.230412"
|
||||||
types-pillow = "^9.5.0.2"
|
types-pillow = "^9.5.0.2"
|
||||||
|
types-appdirs = "^1.4.3.5"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow.cache.utils import save_uploaded_file
|
||||||
from langflow.database.models.flow import Flow
|
from langflow.database.models.flow import Flow
|
||||||
from langflow.processing.process import process_graph_cached, process_tweaks
|
from langflow.processing.process import process_graph_cached, process_tweaks
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile
|
||||||
|
|
||||||
from langflow.api.v1.schemas import (
|
from langflow.api.v1.schemas import (
|
||||||
PredictRequest,
|
ProcessResponse,
|
||||||
PredictResponse,
|
UploadFileResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
from langflow.interface.types import build_langchain_types_dict
|
from langflow.interface.types import build_langchain_types_dict
|
||||||
|
|
@ -22,14 +24,17 @@ def get_all():
|
||||||
return build_langchain_types_dict()
|
return build_langchain_types_dict()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/predict/{flow_id}", response_model=PredictResponse)
|
# For backwards compatibility we will keep the old endpoint
|
||||||
async def predict_flow(
|
@router.post("/predict/{flow_id}", response_model=ProcessResponse)
|
||||||
predict_request: PredictRequest,
|
@router.post("/process/{flow_id}", response_model=ProcessResponse)
|
||||||
|
async def process_flow(
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
|
inputs: Optional[dict] = None,
|
||||||
|
tweaks: Optional[dict] = None,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Endpoint to process a message using the flow passed in the bearer token.
|
Endpoint to process an input with a given flow_id.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -40,15 +45,14 @@ async def predict_flow(
|
||||||
if flow.data is None:
|
if flow.data is None:
|
||||||
raise ValueError(f"Flow {flow_id} has no data")
|
raise ValueError(f"Flow {flow_id} has no data")
|
||||||
graph_data = flow.data
|
graph_data = flow.data
|
||||||
if predict_request.tweaks:
|
if tweaks:
|
||||||
try:
|
try:
|
||||||
graph_data = process_tweaks(graph_data, predict_request.tweaks)
|
graph_data = process_tweaks(graph_data, tweaks)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error processing tweaks: {exc}")
|
logger.error(f"Error processing tweaks: {exc}")
|
||||||
response = process_graph_cached(graph_data, predict_request.message)
|
response = process_graph_cached(graph_data, inputs)
|
||||||
return PredictResponse(
|
return ProcessResponse(
|
||||||
result=response.get("result", ""),
|
result=response,
|
||||||
intermediate_steps=response.get("thought", ""),
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log stack trace
|
# Log stack trace
|
||||||
|
|
@ -56,6 +60,21 @@ async def predict_flow(
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upload/{flow_id}", response_model=UploadFileResponse, status_code=201)
|
||||||
|
async def create_upload_file(file: UploadFile, flow_id: str):
|
||||||
|
# Cache file
|
||||||
|
try:
|
||||||
|
file_path = save_uploaded_file(file.file, folder_name=flow_id)
|
||||||
|
|
||||||
|
return UploadFileResponse(
|
||||||
|
flowId=flow_id,
|
||||||
|
file_path=file_path,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error saving file: {exc}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
# get endpoint to return version of langflow
|
# get endpoint to return version of langflow
|
||||||
@router.get("/version")
|
@router.get("/version")
|
||||||
def get_version():
|
def get_version():
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
from langflow.database.models.flow import FlowCreate, FlowRead
|
from langflow.database.models.flow import FlowCreate, FlowRead
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
|
|
@ -20,34 +21,22 @@ class ExportedFlow(BaseModel):
|
||||||
data: GraphData
|
data: GraphData
|
||||||
|
|
||||||
|
|
||||||
class PredictRequest(BaseModel):
|
class InputRequest(BaseModel):
|
||||||
"""Predict request schema."""
|
input: dict
|
||||||
|
|
||||||
message: str
|
|
||||||
|
class TweaksRequest(BaseModel):
|
||||||
tweaks: Optional[Dict[str, Dict[str, str]]] = Field(default_factory=dict)
|
tweaks: Optional[Dict[str, Dict[str, str]]] = Field(default_factory=dict)
|
||||||
|
|
||||||
class Config:
|
|
||||||
schema_extra = {
|
class UpdateTemplateRequest(BaseModel):
|
||||||
"example": {
|
template: dict
|
||||||
"message": "Hello, how are you?",
|
|
||||||
"tweaks": {
|
|
||||||
"dndnode_986363f0-4677-4035-9f38-74b94af5dd78": {
|
|
||||||
"name": "A tool name",
|
|
||||||
"description": "A tool description",
|
|
||||||
},
|
|
||||||
"dndnode_986363f0-4677-4035-9f38-74b94af57378": {
|
|
||||||
"template": "A {template}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PredictResponse(BaseModel):
|
class ProcessResponse(BaseModel):
|
||||||
"""Predict response schema."""
|
"""Process response schema."""
|
||||||
|
|
||||||
result: str
|
result: dict
|
||||||
intermediate_steps: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
class ChatMessage(BaseModel):
|
class ChatMessage(BaseModel):
|
||||||
|
|
@ -104,6 +93,13 @@ class BuiltResponse(BaseModel):
|
||||||
built: bool
|
built: bool
|
||||||
|
|
||||||
|
|
||||||
|
class UploadFileResponse(BaseModel):
|
||||||
|
"""Upload file response schema."""
|
||||||
|
|
||||||
|
flowId: str
|
||||||
|
file_path: Path
|
||||||
|
|
||||||
|
|
||||||
class StreamData(BaseModel):
|
class StreamData(BaseModel):
|
||||||
event: str
|
event: str
|
||||||
data: dict
|
data: dict
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
from langflow.api.v1.base import (
|
from langflow.api.v1.base import (
|
||||||
|
|
@ -9,8 +7,6 @@ from langflow.api.v1.base import (
|
||||||
PromptValidationResponse,
|
PromptValidationResponse,
|
||||||
validate_prompt,
|
validate_prompt,
|
||||||
)
|
)
|
||||||
from langflow.graph.vertex.types import VectorStoreVertex
|
|
||||||
from langflow.graph import Graph
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.validate import validate_code
|
from langflow.utils.validate import validate_code
|
||||||
|
|
||||||
|
|
@ -37,21 +33,3 @@ def post_validate_prompt(prompt: Prompt):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
# validate node
|
|
||||||
@router.post("/node/{node_id}", status_code=200)
|
|
||||||
def post_validate_node(node_id: str, data: dict):
|
|
||||||
try:
|
|
||||||
# build graph
|
|
||||||
graph = Graph.from_payload(data)
|
|
||||||
# validate node
|
|
||||||
node = graph.get_node(node_id)
|
|
||||||
if node is None:
|
|
||||||
raise ValueError(f"Node {node_id} not found")
|
|
||||||
if not isinstance(node, VectorStoreVertex):
|
|
||||||
node.build()
|
|
||||||
return json.dumps({"valid": True, "params": str(node._built_object_repr())})
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
|
||||||
return json.dumps({"valid": False, "params": str(e)})
|
|
||||||
|
|
|
||||||
3
src/backend/langflow/cache/base.py
vendored
3
src/backend/langflow/cache/base.py
vendored
|
|
@ -62,9 +62,6 @@ class BaseCache(abc.ABC):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: The key of the item to retrieve.
|
key: The key of the item to retrieve.
|
||||||
|
|
||||||
Returns:
|
|
||||||
The value associated with the key, or None if the key is not found.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
|
|
||||||
51
src/backend/langflow/cache/utils.py
vendored
51
src/backend/langflow/cache/utils.py
vendored
|
|
@ -8,15 +8,17 @@ import tempfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
from appdirs import user_cache_dir
|
||||||
|
|
||||||
CACHE: Dict[str, Any] = {}
|
CACHE: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
CACHE_DIR = user_cache_dir("langflow", "langflow")
|
||||||
|
|
||||||
|
|
||||||
def create_cache_folder(func):
|
def create_cache_folder(func):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
# Get the destination folder
|
# Get the destination folder
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
cache_path = Path(CACHE_DIR) / PREFIX
|
||||||
|
|
||||||
# Create the destination folder if it doesn't exist
|
# Create the destination folder if it doesn't exist
|
||||||
os.makedirs(cache_path, exist_ok=True)
|
os.makedirs(cache_path, exist_ok=True)
|
||||||
|
|
@ -118,7 +120,7 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
|
||||||
raise ValueError(f"File {file_name} is not accepted")
|
raise ValueError(f"File {file_name} is not accepted")
|
||||||
|
|
||||||
# Get the destination folder
|
# Get the destination folder
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
cache_path = Path(CACHE_DIR) / PREFIX
|
||||||
if not content:
|
if not content:
|
||||||
raise ValueError("Please, reload the file in the loader.")
|
raise ValueError("Please, reload the file in the loader.")
|
||||||
data = content.split(",")[1]
|
data = content.split(",")[1]
|
||||||
|
|
@ -132,3 +134,46 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
|
||||||
file.write(decoded_bytes)
|
file.write(decoded_bytes)
|
||||||
|
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
|
|
||||||
|
@create_cache_folder
|
||||||
|
def save_uploaded_file(file, folder_name):
|
||||||
|
"""
|
||||||
|
Save an uploaded file to the specified folder with a hash of its content as the file name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file: The uploaded file object.
|
||||||
|
folder_name: The name of the folder to save the file in.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The path to the saved file.
|
||||||
|
"""
|
||||||
|
cache_path = Path(CACHE_DIR)
|
||||||
|
folder_path = cache_path / folder_name
|
||||||
|
|
||||||
|
# Create the folder if it doesn't exist
|
||||||
|
if not folder_path.exists():
|
||||||
|
folder_path.mkdir()
|
||||||
|
|
||||||
|
# Create a hash of the file content
|
||||||
|
sha256_hash = hashlib.sha256()
|
||||||
|
# Reset the file cursor to the beginning of the file
|
||||||
|
file.seek(0)
|
||||||
|
# Iterate over the uploaded file in small chunks to conserve memory
|
||||||
|
while chunk := file.read(8192): # Read 8KB at a time (adjust as needed)
|
||||||
|
sha256_hash.update(chunk)
|
||||||
|
|
||||||
|
# Use the hex digest of the hash as the file name
|
||||||
|
hex_dig = sha256_hash.hexdigest()
|
||||||
|
file_name = hex_dig
|
||||||
|
|
||||||
|
# Reset the file cursor to the beginning of the file
|
||||||
|
file.seek(0)
|
||||||
|
|
||||||
|
# Save the file with the hash as its name
|
||||||
|
file_path = folder_path / file_name
|
||||||
|
with open(file_path, "wb") as new_file:
|
||||||
|
while chunk := file.read(8192):
|
||||||
|
new_file.write(chunk)
|
||||||
|
|
||||||
|
return file_path
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,8 @@ class ChatManager:
|
||||||
if isinstance(msg, FileResponse):
|
if isinstance(msg, FileResponse):
|
||||||
if msg.data_type == "image":
|
if msg.data_type == "image":
|
||||||
# Base64 encode the image
|
# Base64 encode the image
|
||||||
|
if isinstance(msg.data, str):
|
||||||
|
continue
|
||||||
msg.data = pil_to_base64(msg.data)
|
msg.data = pil_to_base64(msg.data)
|
||||||
file_responses.append(msg)
|
file_responses.append(msg)
|
||||||
if msg.type == "start":
|
if msg.type == "start":
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ documentloaders:
|
||||||
- ReadTheDocsLoader
|
- ReadTheDocsLoader
|
||||||
- SlackDirectoryLoader
|
- SlackDirectoryLoader
|
||||||
- NotionDirectoryLoader
|
- NotionDirectoryLoader
|
||||||
|
- DirectoryLoader
|
||||||
|
- GitLoader
|
||||||
embeddings:
|
embeddings:
|
||||||
- OpenAIEmbeddings
|
- OpenAIEmbeddings
|
||||||
- HuggingFaceEmbeddings
|
- HuggingFaceEmbeddings
|
||||||
|
|
@ -57,7 +59,7 @@ llms:
|
||||||
# - AzureOpenAI
|
# - AzureOpenAI
|
||||||
# - AzureChatOpenAI
|
# - AzureChatOpenAI
|
||||||
- ChatOpenAI
|
- ChatOpenAI
|
||||||
- LlamaCpp
|
- LlamaCpp
|
||||||
- CTransformers
|
- CTransformers
|
||||||
- Cohere
|
- Cohere
|
||||||
- Anthropic
|
- Anthropic
|
||||||
|
|
@ -129,6 +131,9 @@ vectorstores:
|
||||||
- Qdrant
|
- Qdrant
|
||||||
- Weaviate
|
- Weaviate
|
||||||
- FAISS
|
- FAISS
|
||||||
|
- Pinecone
|
||||||
|
- SupabaseVectorStore
|
||||||
|
- MongoDBAtlasVectorSearch
|
||||||
wrappers:
|
wrappers:
|
||||||
- RequestsWrapper
|
- RequestsWrapper
|
||||||
# - ChatPromptTemplate
|
# - ChatPromptTemplate
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ class Graph:
|
||||||
def generator_build(self) -> Generator:
|
def generator_build(self) -> Generator:
|
||||||
"""Builds each vertex in the graph and yields it."""
|
"""Builds each vertex in the graph and yields it."""
|
||||||
sorted_vertices = self.topological_sort()
|
sorted_vertices = self.topological_sort()
|
||||||
logger.info("Sorted vertices: %s", sorted_vertices)
|
logger.debug("Sorted vertices: %s", sorted_vertices)
|
||||||
yield from sorted_vertices
|
yield from sorted_vertices
|
||||||
|
|
||||||
def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
|
def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,6 @@ from langflow.interface.wrappers.base import wrapper_creator
|
||||||
from typing import Dict, Type
|
from typing import Dict, Type
|
||||||
|
|
||||||
|
|
||||||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
|
||||||
|
|
||||||
|
|
||||||
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
||||||
**{t: PromptVertex for t in prompt_creator.to_list()},
|
**{t: PromptVertex for t in prompt_creator.to_list()},
|
||||||
**{t: AgentVertex for t in agent_creator.to_list()},
|
**{t: AgentVertex for t in agent_creator.to_list()},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from langflow.cache import utils as cache_utils
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
from langflow.graph.vertex.constants import DIRECT_TYPES
|
from langflow.interface.initialize import loading
|
||||||
from langflow.interface import loading
|
|
||||||
from langflow.interface.listing import ALL_TYPES_DICT
|
from langflow.interface.listing import ALL_TYPES_DICT
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import sync_to_async
|
from langflow.utils.util import sync_to_async
|
||||||
|
|
@ -90,12 +89,7 @@ class Vertex:
|
||||||
# Load the type in value.get('suffixes') using
|
# Load the type in value.get('suffixes') using
|
||||||
# what is inside value.get('content')
|
# what is inside value.get('content')
|
||||||
# value.get('value') is the file name
|
# value.get('value') is the file name
|
||||||
file_name = value.get("value")
|
file_path = value.get("file_path")
|
||||||
content = value.get("content")
|
|
||||||
type_to_load = value.get("suffixes")
|
|
||||||
file_path = cache_utils.save_binary_file(
|
|
||||||
content=content, file_name=file_name, accepted_types=type_to_load
|
|
||||||
)
|
|
||||||
|
|
||||||
params[key] = file_path
|
params[key] = file_path
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,6 @@ class VectorStoreVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="vectorstores")
|
super().__init__(data, base_type="vectorstores")
|
||||||
|
|
||||||
def _built_object_repr(self):
|
|
||||||
return "Vector stores can take time to build. It will build on the first query."
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryVertex(Vertex):
|
class MemoryVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
from langchain.tools.sql_database.tool import (
|
from langchain.tools.sql_database.tool import (
|
||||||
InfoSQLDatabaseTool,
|
InfoSQLDatabaseTool,
|
||||||
ListSQLDatabaseTool,
|
ListSQLDatabaseTool,
|
||||||
QueryCheckerTool,
|
QuerySQLCheckerTool,
|
||||||
QuerySQLDataBaseTool,
|
QuerySQLDataBaseTool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -207,7 +207,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
QuerySQLDataBaseTool(db=db), # type: ignore
|
QuerySQLDataBaseTool(db=db), # type: ignore
|
||||||
InfoSQLDatabaseTool(db=db), # type: ignore
|
InfoSQLDatabaseTool(db=db), # type: ignore
|
||||||
ListSQLDatabaseTool(db=db), # type: ignore
|
ListSQLDatabaseTool(db=db), # type: ignore
|
||||||
QueryCheckerTool(db=db, llm_chain=llmchain, llm=llm), # type: ignore
|
QuerySQLCheckerTool(db=db, llm_chain=llmchain, llm=llm), # type: ignore
|
||||||
]
|
]
|
||||||
|
|
||||||
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
|
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
value=value.get("value", None),
|
value=value.get("value", None),
|
||||||
suffixes=value.get("suffixes", []),
|
suffixes=value.get("suffixes", []),
|
||||||
file_types=value.get("fileTypes", []),
|
file_types=value.get("fileTypes", []),
|
||||||
content=value.get("content", None),
|
file_path=value.get("file_path", None),
|
||||||
)
|
)
|
||||||
for key, value in signature["template"].items()
|
for key, value in signature["template"].items()
|
||||||
if key != "_type"
|
if key != "_type"
|
||||||
|
|
|
||||||
0
src/backend/langflow/interface/initialize/__init__.py
Normal file
0
src/backend/langflow/interface/initialize/__init__.py
Normal file
|
|
@ -1,31 +1,21 @@
|
||||||
import json
|
import json
|
||||||
from typing import Any, Callable, Dict, Optional
|
from typing import Any, Callable, Dict, Sequence
|
||||||
|
|
||||||
from langchain.agents import ZeroShotAgent
|
from langchain.agents import ZeroShotAgent
|
||||||
from langchain.agents import agent as agent_module
|
from langchain.agents import agent as agent_module
|
||||||
from langchain.agents.agent import AgentExecutor
|
from langchain.agents.agent import AgentExecutor
|
||||||
from langchain.agents.agent_toolkits.base import BaseToolkit
|
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||||
from langchain.agents.load_tools import (
|
from langchain.agents.tools import BaseTool
|
||||||
_BASE_TOOLS,
|
from langflow.interface.initialize.vector_store import vecstore_initializer
|
||||||
_EXTRA_LLM_TOOLS,
|
|
||||||
_EXTRA_OPTIONAL_TOOLS,
|
|
||||||
_LLM_TOOLS,
|
|
||||||
)
|
|
||||||
from langchain.agents.loading import load_agent_from_config
|
|
||||||
from langchain.agents.tools import Tool
|
|
||||||
from langchain.base_language import BaseLanguageModel
|
|
||||||
from langchain.callbacks.base import BaseCallbackManager
|
|
||||||
from langchain.chains.loading import load_chain_from_config
|
|
||||||
from langchain.llms.loading import load_llm_from_config
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from langflow.interface.custom_lists import CUSTOM_NODES
|
from langflow.interface.custom_lists import CUSTOM_NODES
|
||||||
from langflow.interface.importing.utils import get_function, import_by_type
|
from langflow.interface.importing.utils import get_function, import_by_type
|
||||||
from langflow.interface.toolkits.base import toolkits_creator
|
from langflow.interface.toolkits.base import toolkits_creator
|
||||||
from langflow.interface.chains.base import chain_creator
|
from langflow.interface.chains.base import chain_creator
|
||||||
from langflow.interface.types import get_type_list
|
|
||||||
from langflow.interface.utils import load_file_into_dict
|
from langflow.interface.utils import load_file_into_dict
|
||||||
from langflow.utils import util, validate
|
from langflow.utils import validate
|
||||||
|
|
||||||
|
|
||||||
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||||
|
|
@ -153,24 +143,48 @@ def instantiate_embedding(class_object, params):
|
||||||
|
|
||||||
|
|
||||||
def instantiate_vectorstore(class_object, params):
|
def instantiate_vectorstore(class_object, params):
|
||||||
if len(params.get("documents", [])) == 0:
|
search_kwargs = params.pop("search_kwargs", {})
|
||||||
raise ValueError(
|
if initializer := vecstore_initializer.get(class_object.__name__):
|
||||||
"The source you provided did not load correctly or was empty."
|
vecstore = initializer(class_object, params)
|
||||||
"This may cause an error in the vectorstore."
|
else:
|
||||||
)
|
if "texts" in params:
|
||||||
# Chroma requires all metadata values to not be None
|
params["documents"] = params.pop("texts")
|
||||||
if class_object.__name__ == "Chroma":
|
vecstore = class_object.from_documents(**params)
|
||||||
for doc in params["documents"]:
|
|
||||||
if doc.metadata is None:
|
# ! This might not work. Need to test
|
||||||
doc.metadata = {}
|
if search_kwargs and hasattr(vecstore, "as_retriever"):
|
||||||
for key, value in doc.metadata.items():
|
vecstore = vecstore.as_retriever(search_kwargs=search_kwargs)
|
||||||
if value is None:
|
|
||||||
doc.metadata[key] = ""
|
return vecstore
|
||||||
return class_object.from_documents(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_documentloader(class_object, params):
|
def instantiate_documentloader(class_object, params):
|
||||||
return class_object(**params).load()
|
if "file_filter" in params:
|
||||||
|
# file_filter will be a string but we need a function
|
||||||
|
# that will be used to filter the files using file_filter
|
||||||
|
# like lambda x: x.endswith(".txt") but as we don't know
|
||||||
|
# anything besides the string, we will simply check if the string is
|
||||||
|
# in x and if it is, we will return True
|
||||||
|
file_filter = params.pop("file_filter", None)
|
||||||
|
extensions = file_filter.split(",")
|
||||||
|
params["file_filter"] = lambda x: any(
|
||||||
|
extension.strip() in x for extension in extensions
|
||||||
|
)
|
||||||
|
metadata = params.pop("metadata", None)
|
||||||
|
docs = class_object(**params).load()
|
||||||
|
if metadata:
|
||||||
|
if isinstance(metadata, str):
|
||||||
|
try:
|
||||||
|
metadata = json.loads(metadata)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise ValueError(
|
||||||
|
"The metadata you provided is not a valid JSON string."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
for doc in docs:
|
||||||
|
doc.metadata = metadata
|
||||||
|
|
||||||
|
return docs
|
||||||
|
|
||||||
|
|
||||||
def instantiate_textsplitter(class_object, params):
|
def instantiate_textsplitter(class_object, params):
|
||||||
|
|
@ -207,48 +221,14 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
def load_langchain_type_from_config(config: Dict[str, Any]):
|
|
||||||
"""Load langchain type from config"""
|
|
||||||
# Get type list
|
|
||||||
type_list = get_type_list()
|
|
||||||
if config["_type"] in type_list["agents"]:
|
|
||||||
config = util.update_verbose(config, new_value=False)
|
|
||||||
return load_agent_executor_from_config(config, verbose=True)
|
|
||||||
elif config["_type"] in type_list["chains"]:
|
|
||||||
config = util.update_verbose(config, new_value=False)
|
|
||||||
return load_chain_from_config(config, verbose=True)
|
|
||||||
elif config["_type"] in type_list["llms"]:
|
|
||||||
config = util.update_verbose(config, new_value=True)
|
|
||||||
return load_llm_from_config(config)
|
|
||||||
else:
|
|
||||||
raise ValueError("Type should be either agent, chain or llm")
|
|
||||||
|
|
||||||
|
|
||||||
def load_agent_executor_from_config(
|
|
||||||
config: dict,
|
|
||||||
llm: Optional[BaseLanguageModel] = None,
|
|
||||||
tools: Optional[list[Tool]] = None,
|
|
||||||
callback_manager: Optional[BaseCallbackManager] = None,
|
|
||||||
**kwargs: Any,
|
|
||||||
):
|
|
||||||
tools = load_tools_from_config(config["allowed_tools"])
|
|
||||||
config["allowed_tools"] = [tool.name for tool in tools] if tools else []
|
|
||||||
agent_obj = load_agent_from_config(config, llm, tools, **kwargs)
|
|
||||||
|
|
||||||
return AgentExecutor.from_agent_and_tools(
|
|
||||||
agent=agent_obj,
|
|
||||||
tools=tools,
|
|
||||||
callback_manager=callback_manager,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs):
|
def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs):
|
||||||
"""Load agent executor from agent class, tools and chain"""
|
"""Load agent executor from agent class, tools and chain"""
|
||||||
allowed_tools = params.get("allowed_tools", [])
|
allowed_tools: Sequence[BaseTool] = params.get("allowed_tools", [])
|
||||||
llm_chain = params["llm_chain"]
|
llm_chain = params["llm_chain"]
|
||||||
# if allowed_tools is not a list or set, make it a list
|
# if allowed_tools is not a list or set, make it a list
|
||||||
if not isinstance(allowed_tools, (list, set)):
|
if not isinstance(allowed_tools, (list, set)) and isinstance(
|
||||||
|
allowed_tools, BaseTool
|
||||||
|
):
|
||||||
allowed_tools = [allowed_tools]
|
allowed_tools = [allowed_tools]
|
||||||
tool_names = [tool.name for tool in allowed_tools]
|
tool_names = [tool.name for tool in allowed_tools]
|
||||||
# Agent class requires an output_parser but Agent classes
|
# Agent class requires an output_parser but Agent classes
|
||||||
|
|
@ -267,46 +247,6 @@ def load_toolkits_executor(node_type: str, toolkit: BaseToolkit, params: dict):
|
||||||
return create_function(llm=llm, toolkit=toolkit)
|
return create_function(llm=llm, toolkit=toolkit)
|
||||||
|
|
||||||
|
|
||||||
def load_tools_from_config(tool_list: list[dict]) -> list:
|
|
||||||
"""Load tools based on a config list.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config: config list.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of tools.
|
|
||||||
"""
|
|
||||||
tools = []
|
|
||||||
for tool in tool_list:
|
|
||||||
tool_type = tool.pop("_type")
|
|
||||||
llm_config = tool.pop("llm", None)
|
|
||||||
llm = load_llm_from_config(llm_config) if llm_config else None
|
|
||||||
kwargs = tool
|
|
||||||
if tool_type in _BASE_TOOLS:
|
|
||||||
tools.append(_BASE_TOOLS[tool_type]())
|
|
||||||
elif tool_type in _LLM_TOOLS:
|
|
||||||
if llm is None:
|
|
||||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
|
||||||
tools.append(_LLM_TOOLS[tool_type](llm))
|
|
||||||
elif tool_type in _EXTRA_LLM_TOOLS:
|
|
||||||
if llm is None:
|
|
||||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
|
||||||
_get_llm_tool_func, extra_keys = _EXTRA_LLM_TOOLS[tool_type]
|
|
||||||
if missing_keys := set(extra_keys).difference(kwargs):
|
|
||||||
raise ValueError(
|
|
||||||
f"Tool {tool_type} requires some parameters that were not "
|
|
||||||
f"provided: {missing_keys}"
|
|
||||||
)
|
|
||||||
tools.append(_get_llm_tool_func(llm=llm, **kwargs))
|
|
||||||
elif tool_type in _EXTRA_OPTIONAL_TOOLS:
|
|
||||||
_get_tool_func, extra_keys = _EXTRA_OPTIONAL_TOOLS[tool_type]
|
|
||||||
kwargs = {k: value for k, value in kwargs.items() if value}
|
|
||||||
tools.append(_get_tool_func(**kwargs))
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Got unknown tool {tool_type}")
|
|
||||||
return tools
|
|
||||||
|
|
||||||
|
|
||||||
def build_prompt_template(prompt, tools):
|
def build_prompt_template(prompt, tools):
|
||||||
"""Build PromptTemplate from ZeroShotPrompt"""
|
"""Build PromptTemplate from ZeroShotPrompt"""
|
||||||
prefix = prompt["node"]["template"]["prefix"]["value"]
|
prefix = prompt["node"]["template"]["prefix"]["value"]
|
||||||
223
src/backend/langflow/interface/initialize/vector_store.py
Normal file
223
src/backend/langflow/interface/initialize/vector_store.py
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
import json
|
||||||
|
from typing import Any, Callable, Dict, Type
|
||||||
|
from langchain.vectorstores import (
|
||||||
|
Pinecone,
|
||||||
|
Qdrant,
|
||||||
|
Chroma,
|
||||||
|
FAISS,
|
||||||
|
Weaviate,
|
||||||
|
SupabaseVectorStore,
|
||||||
|
MongoDBAtlasVectorSearch,
|
||||||
|
)
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def docs_in_params(params: dict) -> bool:
|
||||||
|
"""Check if params has documents OR texts and one of them is not an empty list,
|
||||||
|
If any of them is not an empty list, return True, else return False"""
|
||||||
|
return ("documents" in params and params["documents"]) or (
|
||||||
|
"texts" in params and params["texts"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict):
|
||||||
|
"""Initialize mongodb and return the class object"""
|
||||||
|
|
||||||
|
MONGODB_ATLAS_CLUSTER_URI = params.pop("mongodb_atlas_cluster_uri")
|
||||||
|
if not MONGODB_ATLAS_CLUSTER_URI:
|
||||||
|
raise ValueError("Mongodb atlas cluster uri must be provided in the params")
|
||||||
|
from pymongo import MongoClient
|
||||||
|
import certifi
|
||||||
|
|
||||||
|
client: MongoClient = MongoClient(
|
||||||
|
MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where()
|
||||||
|
)
|
||||||
|
db_name = params.pop("db_name", None)
|
||||||
|
collection_name = params.pop("collection_name", None)
|
||||||
|
if not db_name or not collection_name:
|
||||||
|
raise ValueError("db_name and collection_name must be provided in the params")
|
||||||
|
|
||||||
|
index_name = params.pop("index_name", None)
|
||||||
|
if not index_name:
|
||||||
|
raise ValueError("index_name must be provided in the params")
|
||||||
|
|
||||||
|
collection = client[db_name][collection_name]
|
||||||
|
if not docs_in_params(params):
|
||||||
|
# __init__ requires collection, embedding and index_name
|
||||||
|
init_args = {
|
||||||
|
"collection": collection,
|
||||||
|
"index_name": index_name,
|
||||||
|
"embedding": params.get("embedding"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return class_object(**init_args)
|
||||||
|
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
|
||||||
|
params["collection"] = collection
|
||||||
|
params["index_name"] = index_name
|
||||||
|
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_supabase(class_object: Type[SupabaseVectorStore], params: dict):
|
||||||
|
"""Initialize supabase and return the class object"""
|
||||||
|
from supabase.client import Client, create_client
|
||||||
|
|
||||||
|
if "supabase_url" not in params or "supabase_service_key" not in params:
|
||||||
|
raise ValueError("Supabase url and service key must be provided in the params")
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
|
||||||
|
client_kwargs = {
|
||||||
|
"supabase_url": params.pop("supabase_url"),
|
||||||
|
"supabase_key": params.pop("supabase_service_key"),
|
||||||
|
}
|
||||||
|
|
||||||
|
supabase: Client = create_client(**client_kwargs)
|
||||||
|
if not docs_in_params(params):
|
||||||
|
params.pop("documents", None)
|
||||||
|
params.pop("texts", None)
|
||||||
|
return class_object(client=supabase, **params)
|
||||||
|
# If there are docs in the params, create a new index
|
||||||
|
|
||||||
|
return class_object.from_documents(client=supabase, **params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_weaviate(class_object: Type[Weaviate], params: dict):
|
||||||
|
"""Initialize weaviate and return the class object"""
|
||||||
|
if not docs_in_params(params):
|
||||||
|
import weaviate # type: ignore
|
||||||
|
|
||||||
|
client_kwargs_json = params.get("client_kwargs", "{}")
|
||||||
|
client_kwargs = json.loads(client_kwargs_json)
|
||||||
|
client_params = {
|
||||||
|
"url": params.get("weaviate_url"),
|
||||||
|
}
|
||||||
|
client_params.update(client_kwargs)
|
||||||
|
weaviate_client = weaviate.Client(**client_params)
|
||||||
|
|
||||||
|
new_params = {
|
||||||
|
"client": weaviate_client,
|
||||||
|
"index_name": params.get("index_name"),
|
||||||
|
"text_key": params.get("text_key"),
|
||||||
|
}
|
||||||
|
return class_object(**new_params)
|
||||||
|
# If there are docs in the params, create a new index
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_faiss(class_object: Type[FAISS], params: dict):
|
||||||
|
"""Initialize faiss and return the class object"""
|
||||||
|
|
||||||
|
if not docs_in_params(params):
|
||||||
|
return class_object.load_local
|
||||||
|
|
||||||
|
save_local = params.get("save_local")
|
||||||
|
faiss_index = class_object(**params)
|
||||||
|
if save_local:
|
||||||
|
faiss_index.save_local(folder_path=save_local)
|
||||||
|
return faiss_index
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
||||||
|
"""Initialize pinecone and return the class object"""
|
||||||
|
|
||||||
|
import pinecone # type: ignore
|
||||||
|
|
||||||
|
pinecone_api_key = params.get("pinecone_api_key")
|
||||||
|
pinecone_env = params.get("pinecone_env")
|
||||||
|
|
||||||
|
if pinecone_api_key is None or pinecone_env is None:
|
||||||
|
if os.getenv("PINECONE_API_KEY") is not None:
|
||||||
|
pinecone_api_key = os.getenv("PINECONE_API_KEY")
|
||||||
|
if os.getenv("PINECONE_ENV") is not None:
|
||||||
|
pinecone_env = os.getenv("PINECONE_ENV")
|
||||||
|
|
||||||
|
if pinecone_api_key is None or pinecone_env is None:
|
||||||
|
raise ValueError(
|
||||||
|
"Pinecone API key and environment must be provided in the params"
|
||||||
|
)
|
||||||
|
|
||||||
|
# initialize pinecone
|
||||||
|
pinecone.init(
|
||||||
|
api_key=pinecone_api_key, # find at app.pinecone.io
|
||||||
|
environment=pinecone_env, # next to api key in console
|
||||||
|
)
|
||||||
|
|
||||||
|
# If there are no docs in the params, return an existing index
|
||||||
|
# but first remove any texts or docs keys from the params
|
||||||
|
if not docs_in_params(params):
|
||||||
|
existing_index_params = {
|
||||||
|
"embedding": params.pop("embedding"),
|
||||||
|
}
|
||||||
|
if "index_name" in params:
|
||||||
|
existing_index_params["index_name"] = params.pop("index_name")
|
||||||
|
if "namespace" in params:
|
||||||
|
existing_index_params["namespace"] = params.pop("namespace")
|
||||||
|
|
||||||
|
return class_object.from_existing_index(**existing_index_params)
|
||||||
|
# If there are docs in the params, create a new index
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_chroma(class_object: Type[Chroma], params: dict):
|
||||||
|
"""Initialize a ChromaDB object from the params"""
|
||||||
|
persist = params.pop("persist", False)
|
||||||
|
if not docs_in_params(params):
|
||||||
|
params.pop("documents", None)
|
||||||
|
params.pop("texts", None)
|
||||||
|
params["embedding_function"] = params.pop("embedding")
|
||||||
|
chromadb = class_object(**params)
|
||||||
|
else:
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
for doc in params["documents"]:
|
||||||
|
if doc.metadata is None:
|
||||||
|
doc.metadata = {}
|
||||||
|
for key, value in doc.metadata.items():
|
||||||
|
if value is None:
|
||||||
|
doc.metadata[key] = ""
|
||||||
|
chromadb = class_object.from_documents(**params)
|
||||||
|
if persist:
|
||||||
|
chromadb.persist()
|
||||||
|
return chromadb
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_qdrant(class_object: Type[Qdrant], params: dict):
|
||||||
|
if not docs_in_params(params):
|
||||||
|
if "location" not in params and "api_key" not in params:
|
||||||
|
raise ValueError("Location and API key must be provided in the params")
|
||||||
|
from qdrant_client import QdrantClient
|
||||||
|
|
||||||
|
client_params = {
|
||||||
|
"location": params.pop("location"),
|
||||||
|
"api_key": params.pop("api_key"),
|
||||||
|
}
|
||||||
|
lc_params = {
|
||||||
|
"collection_name": params.pop("collection_name"),
|
||||||
|
"embeddings": params.pop("embedding"),
|
||||||
|
}
|
||||||
|
client = QdrantClient(**client_params)
|
||||||
|
|
||||||
|
return class_object(client=client, **lc_params)
|
||||||
|
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
vecstore_initializer: Dict[str, Callable[[Type[Any], dict], Any]] = {
|
||||||
|
"Pinecone": initialize_pinecone,
|
||||||
|
"Chroma": initialize_chroma,
|
||||||
|
"Qdrant": initialize_qdrant,
|
||||||
|
"Weaviate": initialize_weaviate,
|
||||||
|
"FAISS": initialize_faiss,
|
||||||
|
"SupabaseVectorStore": initialize_supabase,
|
||||||
|
"MongoDBAtlasVectorSearch": initialize_mongodb,
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import contextlib
|
|
||||||
import io
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from langchain.schema import AgentAction
|
from langchain.schema import AgentAction
|
||||||
import json
|
import json
|
||||||
|
|
@ -10,7 +8,8 @@ from langflow.interface.run import (
|
||||||
)
|
)
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
|
from langchain.chains.base import Chain
|
||||||
|
from langchain.vectorstores.base import VectorStore
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -55,69 +54,42 @@ def format_actions(actions: List[Tuple[AgentAction, str]]) -> str:
|
||||||
return "\n".join(output)
|
return "\n".join(output)
|
||||||
|
|
||||||
|
|
||||||
def get_result_and_thought(langchain_object, message: str):
|
def get_result_and_thought(langchain_object: Any, inputs: dict):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
try:
|
try:
|
||||||
if hasattr(langchain_object, "verbose"):
|
if hasattr(langchain_object, "verbose"):
|
||||||
langchain_object.verbose = True
|
langchain_object.verbose = True
|
||||||
chat_input = None
|
|
||||||
memory_key = ""
|
|
||||||
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
|
||||||
memory_key = langchain_object.memory.memory_key
|
|
||||||
|
|
||||||
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"):
|
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||||
# https://github.com/hwchase17/langchain/issues/2068
|
langchain_object.return_intermediate_steps = True
|
||||||
# Deactivating until we have a frontend solution
|
|
||||||
# to display intermediate steps
|
|
||||||
langchain_object.return_intermediate_steps = False
|
|
||||||
|
|
||||||
fix_memory_inputs(langchain_object)
|
fix_memory_inputs(langchain_object)
|
||||||
|
|
||||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
try:
|
||||||
try:
|
output = langchain_object(inputs, return_only_outputs=True)
|
||||||
# if hasattr(langchain_object, "acall"):
|
except ValueError as exc:
|
||||||
# output = await langchain_object.acall(chat_input)
|
# make the error message more informative
|
||||||
# else:
|
logger.debug(f"Error: {str(exc)}")
|
||||||
output = langchain_object(chat_input)
|
output = langchain_object.run(inputs)
|
||||||
except ValueError as exc:
|
|
||||||
# make the error message more informative
|
|
||||||
logger.debug(f"Error: {str(exc)}")
|
|
||||||
output = langchain_object.run(chat_input)
|
|
||||||
|
|
||||||
intermediate_steps = (
|
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
|
||||||
)
|
|
||||||
|
|
||||||
result = (
|
|
||||||
output.get(langchain_object.output_keys[0])
|
|
||||||
if isinstance(output, dict)
|
|
||||||
else output
|
|
||||||
)
|
|
||||||
if intermediate_steps:
|
|
||||||
thought = format_actions(intermediate_steps)
|
|
||||||
else:
|
|
||||||
thought = output_buffer.getvalue()
|
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValueError(f"Error: {str(exc)}") from exc
|
raise ValueError(f"Error: {str(exc)}") from exc
|
||||||
return result, thought
|
return output
|
||||||
|
|
||||||
|
|
||||||
def process_graph_cached(data_graph: Dict[str, Any], message: str):
|
def get_input_str_if_only_one_input(inputs: dict) -> Optional[str]:
|
||||||
|
"""Get input string if only one input is provided"""
|
||||||
|
return list(inputs.values())[0] if len(inputs) == 1 else None
|
||||||
|
|
||||||
|
|
||||||
|
def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = None):
|
||||||
"""
|
"""
|
||||||
Process graph by extracting input variables and replacing ZeroShotPrompt
|
Process graph by extracting input variables and replacing ZeroShotPrompt
|
||||||
with PromptTemplate,then run the graph and return the result and thought.
|
with PromptTemplate,then run the graph and return the result and thought.
|
||||||
"""
|
"""
|
||||||
# Load langchain object
|
# Load langchain object
|
||||||
langchain_object = build_langchain_object_with_caching(data_graph)
|
langchain_object = build_langchain_object_with_caching(data_graph)
|
||||||
logger.debug("Loaded langchain object")
|
logger.debug("Loaded LangChain object")
|
||||||
|
|
||||||
if langchain_object is None:
|
if langchain_object is None:
|
||||||
# Raise user facing error
|
# Raise user facing error
|
||||||
|
|
@ -126,10 +98,16 @@ def process_graph_cached(data_graph: Dict[str, Any], message: str):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
logger.debug("Generating result and thought")
|
if isinstance(langchain_object, Chain):
|
||||||
result, thought = get_result_and_thought(langchain_object, message)
|
if inputs is None:
|
||||||
logger.debug("Generated result and thought")
|
raise ValueError("Inputs must be provided for a Chain")
|
||||||
return {"result": str(result), "thought": thought.strip()}
|
logger.debug("Generating result and thought")
|
||||||
|
result = get_result_and_thought(langchain_object, inputs)
|
||||||
|
logger.debug("Generated result and thought")
|
||||||
|
elif isinstance(langchain_object, VectorStore):
|
||||||
|
class_name = langchain_object.__class__.__name__
|
||||||
|
result = {"message": f"Processed {class_name} successfully"}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def load_flow_from_json(
|
def load_flow_from_json(
|
||||||
|
|
@ -206,7 +184,8 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
for tweak_name, tweak_value in node_tweaks.items():
|
for tweak_name, tweak_value in node_tweaks.items():
|
||||||
if tweak_name and tweak_value and tweak_name in template_data:
|
if tweak_name and tweak_value and tweak_name in template_data:
|
||||||
template_data[tweak_name]["value"] = tweak_value
|
key = tweak_name if tweak_name == "file_path" else "value"
|
||||||
|
template_data[tweak_name][key] = tweak_value
|
||||||
|
|
||||||
|
|
||||||
def process_tweaks(
|
def process_tweaks(
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class TemplateFieldCreator(BaseModel, ABC):
|
||||||
suffixes: list[str] = []
|
suffixes: list[str] = []
|
||||||
fileTypes: list[str] = []
|
fileTypes: list[str] = []
|
||||||
file_types: list[str] = []
|
file_types: list[str] = []
|
||||||
content: Union[str, None] = None
|
file_path: Union[str, None] = None
|
||||||
password: bool = False
|
password: bool = False
|
||||||
options: list[str] = []
|
options: list[str] = []
|
||||||
name: str = ""
|
name: str = ""
|
||||||
|
|
@ -35,7 +35,7 @@ class TemplateFieldCreator(BaseModel, ABC):
|
||||||
result["fileTypes"] = result.pop("file_types")
|
result["fileTypes"] = result.pop("file_types")
|
||||||
|
|
||||||
if self.field_type == "file":
|
if self.field_type == "file":
|
||||||
result["content"] = self.content
|
result["file_path"] = self.file_path
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,37 @@ class ChainFrontendNode(FrontendNode):
|
||||||
self.template.add_field(
|
self.template.add_field(
|
||||||
TemplateField(
|
TemplateField(
|
||||||
field_type="BaseChatMemory",
|
field_type="BaseChatMemory",
|
||||||
required=False,
|
required=True,
|
||||||
show=True,
|
show=True,
|
||||||
name="memory",
|
name="memory",
|
||||||
advanced=False,
|
advanced=False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# add return_source_documents
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="return_source_documents",
|
||||||
|
advanced=False,
|
||||||
|
value=True,
|
||||||
|
display_name="Return source documents",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
is_list=True,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
options=QA_CHAIN_TYPES,
|
||||||
|
value=QA_CHAIN_TYPES[0],
|
||||||
|
name="chain_type",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
|
@ -47,7 +72,7 @@ class ChainFrontendNode(FrontendNode):
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
if field.name == "memory":
|
if field.name == "memory":
|
||||||
field.required = False
|
# field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
if field.name == "verbose":
|
if field.name == "verbose":
|
||||||
|
|
@ -59,6 +84,12 @@ class ChainFrontendNode(FrontendNode):
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
|
||||||
|
if field.name == "return_source_documents":
|
||||||
|
field.required = False
|
||||||
|
field.show = True
|
||||||
|
field.advanced = True
|
||||||
|
field.value = True
|
||||||
|
|
||||||
|
|
||||||
class SeriesCharacterChainNode(FrontendNode):
|
class SeriesCharacterChainNode(FrontendNode):
|
||||||
name: str = "SeriesCharacterChain"
|
name: str = "SeriesCharacterChain"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
from typing import Optional
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
def build_template(
|
def build_file_field(
|
||||||
suffixes: list, fileTypes: list, name: str = "file_path"
|
suffixes: list, fileTypes: list, name: str = "file_path"
|
||||||
) -> TemplateField:
|
) -> TemplateField:
|
||||||
"""Build a template field for a document loader."""
|
"""Build a template field for a document loader."""
|
||||||
|
|
@ -19,33 +20,35 @@ def build_template(
|
||||||
|
|
||||||
class DocumentLoaderFrontNode(FrontendNode):
|
class DocumentLoaderFrontNode(FrontendNode):
|
||||||
file_path_templates = {
|
file_path_templates = {
|
||||||
"AirbyteJSONLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
"AirbyteJSONLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
"CoNLLULoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
|
"CoNLLULoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||||
"CSVLoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
|
"CSVLoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||||
"UnstructuredEmailLoader": build_template(suffixes=[".eml"], fileTypes=["eml"]),
|
"UnstructuredEmailLoader": build_file_field(
|
||||||
"EverNoteLoader": build_template(suffixes=[".xml"], fileTypes=["xml"]),
|
suffixes=[".eml"], fileTypes=["eml"]
|
||||||
"FacebookChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
),
|
||||||
"GutenbergLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
|
"SlackDirectoryLoader": build_file_field(suffixes=[".zip"], fileTypes=["zip"]),
|
||||||
"BSHTMLLoader": build_template(suffixes=[".html"], fileTypes=["html"]),
|
"EverNoteLoader": build_file_field(suffixes=[".xml"], fileTypes=["xml"]),
|
||||||
"UnstructuredHTMLLoader": build_template(
|
"FacebookChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
|
"GutenbergLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]),
|
||||||
|
"BSHTMLLoader": build_file_field(suffixes=[".html"], fileTypes=["html"]),
|
||||||
|
"UnstructuredHTMLLoader": build_file_field(
|
||||||
suffixes=[".html"], fileTypes=["html"]
|
suffixes=[".html"], fileTypes=["html"]
|
||||||
),
|
),
|
||||||
"UnstructuredImageLoader": build_template(
|
"UnstructuredImageLoader": build_file_field(
|
||||||
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
|
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
|
||||||
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
|
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
|
||||||
),
|
),
|
||||||
"UnstructuredMarkdownLoader": build_template(
|
"UnstructuredMarkdownLoader": build_file_field(
|
||||||
suffixes=[".md"], fileTypes=["md"]
|
suffixes=[".md"], fileTypes=["md"]
|
||||||
),
|
),
|
||||||
"PyPDFLoader": build_template(suffixes=[".pdf"], fileTypes=["pdf"]),
|
"PyPDFLoader": build_file_field(suffixes=[".pdf"], fileTypes=["pdf"]),
|
||||||
"UnstructuredPowerPointLoader": build_template(
|
"UnstructuredPowerPointLoader": build_file_field(
|
||||||
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
|
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
|
||||||
),
|
),
|
||||||
"SlackDirectoryLoader": build_template(suffixes=[".zip"], fileTypes=["zip"]),
|
"SRTLoader": build_file_field(suffixes=[".srt"], fileTypes=["srt"]),
|
||||||
"SRTLoader": build_template(suffixes=[".srt"], fileTypes=["srt"]),
|
"TelegramChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
"TelegramChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
"TextLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]),
|
||||||
"TextLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
|
"UnstructuredWordDocumentLoader": build_file_field(
|
||||||
"UnstructuredWordDocumentLoader": build_template(
|
|
||||||
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
|
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +56,54 @@ class DocumentLoaderFrontNode(FrontendNode):
|
||||||
def add_extra_fields(self) -> None:
|
def add_extra_fields(self) -> None:
|
||||||
name = None
|
name = None
|
||||||
display_name = "Web Page"
|
display_name = "Web Page"
|
||||||
if self.template.type_name in self.file_path_templates:
|
if self.template.type_name in {"GitLoader"}:
|
||||||
|
# Add fields repo_path, clone_url, branch and file_filter
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="repo_path",
|
||||||
|
value="",
|
||||||
|
display_name="Path to repository",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="clone_url",
|
||||||
|
value="",
|
||||||
|
display_name="Clone URL",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="branch",
|
||||||
|
value="",
|
||||||
|
display_name="Branch",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="file_filter",
|
||||||
|
value="",
|
||||||
|
display_name="File extensions (comma-separated)",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif self.template.type_name in self.file_path_templates:
|
||||||
self.template.add_field(self.file_path_templates[self.template.type_name])
|
self.template.add_field(self.file_path_templates[self.template.type_name])
|
||||||
elif self.template.type_name in {
|
elif self.template.type_name in {
|
||||||
"WebBaseLoader",
|
"WebBaseLoader",
|
||||||
|
|
@ -67,8 +117,9 @@ class DocumentLoaderFrontNode(FrontendNode):
|
||||||
elif self.template.type_name in {"GitbookLoader"}:
|
elif self.template.type_name in {"GitbookLoader"}:
|
||||||
name = "web_page"
|
name = "web_page"
|
||||||
elif self.template.type_name in {
|
elif self.template.type_name in {
|
||||||
"NotionDirectoryLoader",
|
"DirectoryLoader",
|
||||||
"ReadTheDocsLoader",
|
"ReadTheDocsLoader",
|
||||||
|
"NotionDirectoryLoader",
|
||||||
}:
|
}:
|
||||||
name = "path"
|
name = "path"
|
||||||
display_name = "Local directory"
|
display_name = "Local directory"
|
||||||
|
|
@ -83,3 +134,34 @@ class DocumentLoaderFrontNode(FrontendNode):
|
||||||
display_name=display_name,
|
display_name=display_name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if self.template.type_name in {"DirectoryLoader"}:
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="glob",
|
||||||
|
value="**/*.txt",
|
||||||
|
display_name="glob",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# add a metadata field of type dict
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="code",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="metadata",
|
||||||
|
value="{}",
|
||||||
|
display_name="Metadata",
|
||||||
|
multiline=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
if field.name == "metadata":
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
field.show = True
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,27 @@ class MemoryFrontendNode(FrontendNode):
|
||||||
value=False,
|
value=False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# add input_key and output_key str fields
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="input_key",
|
||||||
|
advanced=True,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="output_key",
|
||||||
|
advanced=True,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
|
@ -36,3 +57,10 @@ class MemoryFrontendNode(FrontendNode):
|
||||||
field.required = False
|
field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
if field.name in ["input_key", "output_key"]:
|
||||||
|
field.required = False
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
field.value = ""
|
||||||
|
if field.name == "memory_key":
|
||||||
|
field.value = "chat_history"
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,16 @@ class PythonFunctionToolNode(FrontendNode):
|
||||||
name="code",
|
name="code",
|
||||||
advanced=False,
|
advanced=False,
|
||||||
),
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
value=False,
|
||||||
|
name="return_direct",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
description: str = "Python function to be executed."
|
description: str = "Python function to be executed."
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
@ -6,6 +6,19 @@ from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
class VectorStoreFrontendNode(FrontendNode):
|
class VectorStoreFrontendNode(FrontendNode):
|
||||||
def add_extra_fields(self) -> None:
|
def add_extra_fields(self) -> None:
|
||||||
|
extra_fields: List[TemplateField] = []
|
||||||
|
# Add search_kwargs field
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="search_kwargs",
|
||||||
|
field_type="code",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="{}",
|
||||||
|
)
|
||||||
|
extra_fields.append(extra_field)
|
||||||
if self.template.type_name == "Weaviate":
|
if self.template.type_name == "Weaviate":
|
||||||
extra_field = TemplateField(
|
extra_field = TemplateField(
|
||||||
name="weaviate_url",
|
name="weaviate_url",
|
||||||
|
|
@ -17,8 +30,174 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
multiline=False,
|
multiline=False,
|
||||||
value="http://localhost:8080",
|
value="http://localhost:8080",
|
||||||
)
|
)
|
||||||
|
# Add client_kwargs field
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="client_kwargs",
|
||||||
|
field_type="code",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="{}",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2))
|
||||||
|
|
||||||
self.template.add_field(extra_field)
|
elif self.template.type_name == "Chroma":
|
||||||
|
# New bool field for persist parameter
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="persist",
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
value=True,
|
||||||
|
display_name="Persist",
|
||||||
|
)
|
||||||
|
extra_fields.append(extra_field)
|
||||||
|
elif self.template.type_name == "Pinecone":
|
||||||
|
# add pinecone_api_key and pinecone_env
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="pinecone_api_key",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="pinecone_env",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2))
|
||||||
|
elif self.template.type_name == "FAISS":
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="folder_path",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Local Path",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="index_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
value="",
|
||||||
|
display_name="Index Name",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2))
|
||||||
|
elif self.template.type_name == "SupabaseVectorStore":
|
||||||
|
self.display_name = "Supabase"
|
||||||
|
# Add table_name and query_name
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="table_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="query_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
# Add supabase_url and supabase_service_key
|
||||||
|
extra_field3 = TemplateField(
|
||||||
|
name="supabase_url",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field4 = TemplateField(
|
||||||
|
name="supabase_service_key",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2, extra_field3, extra_field4))
|
||||||
|
|
||||||
|
elif self.template.type_name == "MongoDBAtlasVectorSearch":
|
||||||
|
self.display_name = "MongoDB Atlas"
|
||||||
|
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="mongodb_atlas_cluster_uri",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="MongoDB Atlas Cluster URI",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="collection_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Collection Name",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field3 = TemplateField(
|
||||||
|
name="db_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Database Name",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field4 = TemplateField(
|
||||||
|
name="index_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Index Name",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2, extra_field3, extra_field4))
|
||||||
|
|
||||||
|
if extra_fields:
|
||||||
|
for field in extra_fields:
|
||||||
|
self.template.add_field(field)
|
||||||
|
|
||||||
def add_extra_base_classes(self) -> None:
|
def add_extra_base_classes(self) -> None:
|
||||||
self.base_classes.append("BaseRetriever")
|
self.base_classes.append("BaseRetriever")
|
||||||
|
|
@ -27,7 +206,25 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
FrontendNode.format_field(field, name)
|
FrontendNode.format_field(field, name)
|
||||||
# Define common field attributes
|
# Define common field attributes
|
||||||
basic_fields = ["work_dir", "collection_name", "api_key", "location"]
|
basic_fields = [
|
||||||
|
"work_dir",
|
||||||
|
"collection_name",
|
||||||
|
"api_key",
|
||||||
|
"location",
|
||||||
|
"persist_directory",
|
||||||
|
"persist",
|
||||||
|
"weaviate_url",
|
||||||
|
"index_name",
|
||||||
|
"namespace",
|
||||||
|
"folder_path",
|
||||||
|
"table_name",
|
||||||
|
"query_name",
|
||||||
|
"supabase_url",
|
||||||
|
"supabase_service_key",
|
||||||
|
"mongodb_atlas_cluster_uri",
|
||||||
|
"collection_name",
|
||||||
|
"db_name",
|
||||||
|
]
|
||||||
advanced_fields = [
|
advanced_fields = [
|
||||||
"n_dim",
|
"n_dim",
|
||||||
"key",
|
"key",
|
||||||
|
|
@ -43,14 +240,21 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
"https",
|
"https",
|
||||||
"prefer_grpc",
|
"prefer_grpc",
|
||||||
"grpc_port",
|
"grpc_port",
|
||||||
|
"pinecone_api_key",
|
||||||
|
"pinecone_env",
|
||||||
|
"client_kwargs",
|
||||||
|
"search_kwargs",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Check and set field attributes
|
# Check and set field attributes
|
||||||
if field.name == "texts":
|
if field.name == "texts":
|
||||||
|
# if field.name is "texts" it has to be replaced
|
||||||
|
# when instantiating the vectorstores
|
||||||
field.name = "documents"
|
field.name = "documents"
|
||||||
|
|
||||||
field.field_type = "TextSplitter"
|
field.field_type = "TextSplitter"
|
||||||
field.display_name = "Text Splitter"
|
field.display_name = "Documents"
|
||||||
field.required = True
|
field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
|
||||||
|
|
@ -78,5 +282,6 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
field.advanced = True
|
field.advanced = True
|
||||||
if "key" in field.name:
|
if "key" in field.name:
|
||||||
field.password = False
|
field.password = False
|
||||||
# TODO: Weaviate requires weaviate_url to be passed as it is not part of
|
|
||||||
# the class or from_texts method. We need the add_extra_fields to fix this
|
elif field.name == "text_key":
|
||||||
|
field.show = False
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from typing import Callable, Optional, Union
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
|
|
||||||
|
|
||||||
class Template(BaseModel):
|
class Template(BaseModel):
|
||||||
|
|
@ -18,8 +19,15 @@ class Template(BaseModel):
|
||||||
for field in self.fields:
|
for field in self.fields:
|
||||||
format_field_func(field, name)
|
format_field_func(field, name)
|
||||||
|
|
||||||
|
def sort_fields(self):
|
||||||
|
# first sort alphabetically
|
||||||
|
# then sort fields so that fields that have .field_type in DIRECT_TYPES are first
|
||||||
|
self.fields.sort(key=lambda x: x.name)
|
||||||
|
self.fields.sort(key=lambda x: x.field_type in DIRECT_TYPES, reverse=False)
|
||||||
|
|
||||||
def to_dict(self, format_field_func=None):
|
def to_dict(self, format_field_func=None):
|
||||||
self.process_fields(self.type_name, format_field_func)
|
self.process_fields(self.type_name, format_field_func)
|
||||||
|
self.sort_fields()
|
||||||
result = {field.name: field.to_dict() for field in self.fields}
|
result = {field.name: field.to_dict() for field in self.fields}
|
||||||
result["_type"] = self.type_name # type: ignore
|
result["_type"] = self.type_name # type: ignore
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,4 @@ def python_function(text: str) -> str:
|
||||||
\"\"\"This is a default python function that returns the input text\"\"\"
|
\"\"\"This is a default python function that returns the input text\"\"\"
|
||||||
return text
|
return text
|
||||||
"""
|
"""
|
||||||
|
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
||||||
|
|
|
||||||
|
|
@ -299,12 +299,15 @@ def format_dict(d, name: Optional[str] = None):
|
||||||
if name == "OpenAI" and key == "model_name":
|
if name == "OpenAI" and key == "model_name":
|
||||||
value["options"] = constants.OPENAI_MODELS
|
value["options"] = constants.OPENAI_MODELS
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
|
value["value"] = constants.OPENAI_MODELS[0]
|
||||||
elif name == "ChatOpenAI" and key == "model_name":
|
elif name == "ChatOpenAI" and key == "model_name":
|
||||||
value["options"] = constants.CHAT_OPENAI_MODELS
|
value["options"] = constants.CHAT_OPENAI_MODELS
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
|
value["value"] = constants.CHAT_OPENAI_MODELS[0]
|
||||||
elif (name == "Anthropic" or name == "ChatAnthropic") and key == "model_name":
|
elif (name == "Anthropic" or name == "ChatAnthropic") and key == "model_name":
|
||||||
value["options"] = constants.ANTHROPIC_MODELS
|
value["options"] = constants.ANTHROPIC_MODELS
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
|
value["value"] = constants.ANTHROPIC_MODELS[0]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
43
src/frontend/package-lock.json
generated
43
src/frontend/package-lock.json
generated
|
|
@ -427,14 +427,6 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": {
|
|
||||||
"version": "7.22.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
|
|
||||||
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@emotion/babel-plugin": {
|
"node_modules/@emotion/babel-plugin": {
|
||||||
"version": "11.11.0",
|
"version": "11.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
||||||
|
|
@ -931,9 +923,9 @@
|
||||||
"integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g=="
|
"integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g=="
|
||||||
},
|
},
|
||||||
"node_modules/@floating-ui/dom": {
|
"node_modules/@floating-ui/dom": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.2.tgz",
|
||||||
"integrity": "sha512-loCXUOLzIC3jp50RFOKXZ/kQjjz26ryr/23M+FWG9jrmAv8lRf3DUfC2AiVZ3+K316GOhB08CR+Povwz8e9mDw==",
|
"integrity": "sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/core": "^1.3.1"
|
"@floating-ui/core": "^1.3.1"
|
||||||
}
|
}
|
||||||
|
|
@ -1003,19 +995,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.4.14",
|
"version": "1.4.15",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.17",
|
"version": "0.3.18",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
|
||||||
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
|
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "3.1.0",
|
"@jridgewell/resolve-uri": "3.1.0",
|
||||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.4.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||||
|
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
||||||
|
},
|
||||||
"node_modules/@mole-inc/bin-wrapper": {
|
"node_modules/@mole-inc/bin-wrapper": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@mole-inc/bin-wrapper/-/bin-wrapper-8.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mole-inc/bin-wrapper/-/bin-wrapper-8.0.1.tgz",
|
||||||
|
|
@ -5013,9 +5010,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.434",
|
"version": "1.4.438",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.434.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.438.tgz",
|
||||||
"integrity": "sha512-5Gvm09UZTQRaWrimRtWRO5rvaX6Kpk5WHAPKDa7A4Gj6NIPuJ8w8WNpnxCXdd+CJJt6RBU6tUw0KyULoW6XuHw=="
|
"integrity": "sha512-x94U0FhphEsHsOloCvlsujHCvoir0ZQ73ZAs/QN4PLx98uNvyEU79F75rq1db75Bx/atvuh7KPeuxelh+xfYJw=="
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
|
|
@ -8280,9 +8277,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pirates": {
|
"node_modules/pirates": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||||
"integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
|
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
||||||
import {
|
import {
|
||||||
classNames,
|
classNames,
|
||||||
|
getRandomKeyByssmm,
|
||||||
groupByFamily,
|
groupByFamily,
|
||||||
isValidConnection,
|
isValidConnection,
|
||||||
nodeIconsLucide,
|
nodeIconsLucide,
|
||||||
|
|
@ -41,7 +42,7 @@ export default function ParameterComponent({
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
const [position, setPosition] = useState(0);
|
const [position, setPosition] = useState(0);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const { setTabsState, tabId } = useContext(TabsContext);
|
const { setTabsState, tabId, save } = useContext(TabsContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
||||||
|
|
@ -83,7 +84,7 @@ export default function ParameterComponent({
|
||||||
|
|
||||||
refHtml.current = groupedObj.map((item, i) => (
|
refHtml.current = groupedObj.map((item, i) => (
|
||||||
<span
|
<span
|
||||||
key={i}
|
key={getRandomKeyByssmm()}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
i > 0 ? "items-center flex mt-3" : "items-center flex"
|
i > 0 ? "items-center flex mt-3" : "items-center flex"
|
||||||
)}
|
)}
|
||||||
|
|
@ -103,14 +104,14 @@ export default function ParameterComponent({
|
||||||
-
|
-
|
||||||
{item.type.split(", ").length > 2
|
{item.type.split(", ").length > 2
|
||||||
? item.type.split(", ").map((el, i) => (
|
? item.type.split(", ").map((el, i) => (
|
||||||
<>
|
<React.Fragment key={el + i}>
|
||||||
<span key={i}>
|
<span>
|
||||||
{i == item.type.split(", ").length - 1
|
{i === item.type.split(", ").length - 1
|
||||||
? el
|
? el
|
||||||
: (el += `, `)}
|
: (el += `, `)}
|
||||||
</span>
|
</span>
|
||||||
{i % 2 == 0 && i > 0 && <br></br>}
|
{i % 2 === 0 && i > 0 && <br />}
|
||||||
</>
|
</React.Fragment>
|
||||||
))
|
))
|
||||||
: item.type}
|
: item.type}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -240,7 +241,8 @@ export default function ParameterComponent({
|
||||||
fileTypes={data.node.template[name].fileTypes}
|
fileTypes={data.node.template[name].fileTypes}
|
||||||
suffixes={data.node.template[name].suffixes}
|
suffixes={data.node.template[name].suffixes}
|
||||||
onFileChange={(t: string) => {
|
onFileChange={(t: string) => {
|
||||||
data.node.template[name].content = t;
|
data.node.template[name].file_path = t;
|
||||||
|
save();
|
||||||
}}
|
}}
|
||||||
></InputFileComponent>
|
></InputFileComponent>
|
||||||
) : left === true && type === "int" ? (
|
) : left === true && type === "int" ? (
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,8 @@ export default function GenericNode({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="ml-2 truncate">
|
<div className="ml-2 truncate">
|
||||||
<ShadTooltip delayDuration={1500} content={data.type}>
|
<ShadTooltip delayDuration={1500} content={data.node.display_name}>
|
||||||
<div className="ml-2 truncate text-gray-800">{data.type}</div>
|
<div className="ml-2 truncate text-gray-800">{data.node.display_name}</div>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
6
src/frontend/src/components/LoadingSpinner/index.tsx
Normal file
6
src/frontend/src/components/LoadingSpinner/index.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import { RadialProgressType } from "../../types/components";
|
||||||
|
|
||||||
|
export default function LoadingSpinner({}) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ export default function RadialProgressComponent({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"radial-progress " + color} style={style}>
|
<div className={"radial-progress " + color} style={style}>
|
||||||
<strong className="text-[8px]">{value * 100}%</strong>
|
<strong className="text-[8px]">{Math.trunc(value * 100)}%</strong>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
import { Fragment, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { DropDownComponentType } from "../../types/components";
|
import { DropDownComponentType } from "../../types/components";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "../../utils";
|
||||||
import { INPUT_STYLE } from "../../constants";
|
import { INPUT_STYLE } from "../../constants";
|
||||||
|
|
@ -15,6 +15,9 @@ export default function Dropdown({
|
||||||
let [internalValue, setInternalValue] = useState(
|
let [internalValue, setInternalValue] = useState(
|
||||||
value === "" || !value ? "Choose an option" : value
|
value === "" || !value ? "Choose an option" : value
|
||||||
);
|
);
|
||||||
|
useEffect(()=>{
|
||||||
|
setInternalValue(value === "" || !value ? "Choose an option" : value)
|
||||||
|
},[value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export const MenuBar = ({ flows, tabId }) => {
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center font-medium text-sm rounded-md py-1 px-1.5 gap-0.5">
|
<div className="flex items-center font-medium text-sm rounded-md py-1 px-1.5 gap-0.5">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="gap-2 flex items-center max-w-[200px]"
|
className="gap-2 flex items-center max-w-[200px]"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { FileComponentType } from "../../types/components";
|
import { FileComponentType } from "../../types/components";
|
||||||
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import { INPUT_STYLE } from "../../constants";
|
import { INPUT_STYLE } from "../../constants";
|
||||||
import { FileSearch2 } from "lucide-react";
|
import { FileSearch2 } from "lucide-react";
|
||||||
|
import { uploadFile } from "../../controllers/API";
|
||||||
|
|
||||||
export default function InputFileComponent({
|
export default function InputFileComponent({
|
||||||
value,
|
value,
|
||||||
|
|
@ -14,7 +16,9 @@ export default function InputFileComponent({
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: FileComponentType) {
|
}: FileComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
|
const { tabId } = useContext(TabsContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
|
|
@ -23,12 +27,6 @@ export default function InputFileComponent({
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
|
|
||||||
function attachFile(fileReadEvent: ProgressEvent<FileReader>) {
|
|
||||||
fileReadEvent.preventDefault();
|
|
||||||
const file = fileReadEvent.target.result;
|
|
||||||
onFileChange(file as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkFileType(fileName: string): boolean {
|
function checkFileType(fileName: string): boolean {
|
||||||
for (let index = 0; index < suffixes.length; index++) {
|
for (let index = 0; index < suffixes.length; index++) {
|
||||||
if (fileName.endsWith(suffixes[index])) {
|
if (fileName.endsWith(suffixes[index])) {
|
||||||
|
|
@ -43,27 +41,54 @@ export default function InputFileComponent({
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = () => {
|
||||||
|
// Create a file input element
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.accept = suffixes.join(",");
|
input.accept = suffixes.join(",");
|
||||||
input.style.display = "none";
|
input.style.display = "none"; // Hidden from view
|
||||||
input.multiple = false;
|
input.multiple = false; // Allow only one file selection
|
||||||
|
|
||||||
input.onchange = (e: Event) => {
|
input.onchange = (e: Event) => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// Get the selected file
|
||||||
const file = (e.target as HTMLInputElement).files?.[0];
|
const file = (e.target as HTMLInputElement).files?.[0];
|
||||||
const fileData = new FileReader();
|
|
||||||
fileData.onload = attachFile;
|
// Check if the file type is correct
|
||||||
if (file && checkFileType(file.name)) {
|
if (file && checkFileType(file.name)) {
|
||||||
fileData.readAsDataURL(file);
|
// Upload the file
|
||||||
setMyValue(file.name);
|
uploadFile(file, tabId)
|
||||||
onChange(file.name);
|
.then((res) => res.data)
|
||||||
|
.then((data) => {
|
||||||
|
console.log("File uploaded successfully");
|
||||||
|
// Get the file name from the response
|
||||||
|
const { file_path } = data;
|
||||||
|
console.log("File name:", file_path);
|
||||||
|
|
||||||
|
// Update the state and callback with the name of the file
|
||||||
|
// sets the value to the user
|
||||||
|
setMyValue(file.name);
|
||||||
|
onChange(file.name);
|
||||||
|
// sets the value that goes to the backend
|
||||||
|
onFileChange(file_path);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.error("Error occurred while uploading file");
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Show an error if the file type is not allowed
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title:
|
title:
|
||||||
"Please select a valid file. Only files this files are allowed:",
|
"Please select a valid file. Only these file types are allowed:",
|
||||||
list: fileTypes,
|
list: fileTypes,
|
||||||
});
|
});
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Trigger the file selection dialog
|
||||||
input.click();
|
input.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -73,7 +98,7 @@ export default function InputFileComponent({
|
||||||
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center gap-3">
|
<div className="w-full flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
className={
|
className={
|
||||||
|
|
@ -88,7 +113,12 @@ export default function InputFileComponent({
|
||||||
{myValue !== "" ? myValue : "No file"}
|
{myValue !== "" ? myValue : "No file"}
|
||||||
</span>
|
</span>
|
||||||
<button onClick={handleButtonClick}>
|
<button onClick={handleButtonClick}>
|
||||||
{!editNode && <FileSearch2 className="w-8 h-8 hover:text-ring" />}
|
{!editNode && !loading && (
|
||||||
|
<FileSearch2 className="w-8 h-8 hover:text-ring" />
|
||||||
|
)}
|
||||||
|
{!editNode && loading && (
|
||||||
|
<span className="loading loading-spinner loading-sm pl-3 h-8 pointer-events-none"></span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const CardDescription = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<p
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export const getPythonApiCode = (flow: FlowType): string => {
|
||||||
|
|
||||||
BASE_API_URL = "${window.location.protocol}//${
|
BASE_API_URL = "${window.location.protocol}//${
|
||||||
window.location.host
|
window.location.host
|
||||||
}/api/v1/predict"
|
}/api/v1/process"
|
||||||
FLOW_ID = "${flowId}"
|
FLOW_ID = "${flowId}"
|
||||||
# You can tweak the flow by adding a tweaks dictionary
|
# You can tweak the flow by adding a tweaks dictionary
|
||||||
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
||||||
|
|
@ -83,7 +83,7 @@ def run_flow(message: str, flow_id: str, tweaks: dict = None) -> dict:
|
||||||
"""
|
"""
|
||||||
api_url = f"{BASE_API_URL}/{flow_id}"
|
api_url = f"{BASE_API_URL}/{flow_id}"
|
||||||
|
|
||||||
payload = {"message": message}
|
payload = {"inputs": {"input": message}}
|
||||||
|
|
||||||
if tweaks:
|
if tweaks:
|
||||||
payload["tweaks"] = tweaks
|
payload["tweaks"] = tweaks
|
||||||
|
|
@ -106,9 +106,9 @@ export const getCurlCode = (flow: FlowType): string => {
|
||||||
return `curl -X POST \\
|
return `curl -X POST \\
|
||||||
${window.location.protocol}//${
|
${window.location.protocol}//${
|
||||||
window.location.host
|
window.location.host
|
||||||
}/api/v1/predict/${flowId} \\
|
}/api/v1/process/${flowId} \\
|
||||||
-H 'Content-Type: application/json' \\
|
-H 'Content-Type: application/json' \\
|
||||||
-d '{"message": "Your message", "tweaks": ${JSON.stringify(
|
-d '{"inputs": {"input": message}, "tweaks": ${JSON.stringify(
|
||||||
tweaks,
|
tweaks,
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
PromptTypeAPI,
|
PromptTypeAPI,
|
||||||
errorsTypeAPI,
|
errorsTypeAPI,
|
||||||
InitTypeAPI,
|
InitTypeAPI,
|
||||||
|
UploadFileTypeAPI,
|
||||||
} from "./../../types/api/index";
|
} from "./../../types/api/index";
|
||||||
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
||||||
import axios, { AxiosResponse } from "axios";
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
|
@ -48,13 +49,6 @@ export async function postValidateCode(
|
||||||
return await axios.post("/api/v1/validate/code", { code });
|
return await axios.post("/api/v1/validate/code", { code });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postValidateNode(
|
|
||||||
nodeId: string,
|
|
||||||
data: any
|
|
||||||
): Promise<AxiosResponse<string>> {
|
|
||||||
return await axios.post(`/api/v1/validate/node/${nodeId}`, { data });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the prompt for the code block by sending it to an API endpoint.
|
* Checks the prompt for the code block by sending it to an API endpoint.
|
||||||
*
|
*
|
||||||
|
|
@ -319,3 +313,21 @@ export async function postBuildInit(
|
||||||
): Promise<AxiosResponse<InitTypeAPI>> {
|
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||||
return await axios.post(`/api/v1/build/init`, flow);
|
return await axios.post(`/api/v1/build/init`, flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetch(`/upload/${id}`, {
|
||||||
|
// method: "POST",
|
||||||
|
// body: formData,
|
||||||
|
// });
|
||||||
|
/**
|
||||||
|
* Uploads a file to the server.
|
||||||
|
* @param {File} file - The file to upload.
|
||||||
|
* @param {string} id - The ID of the flow to upload the file to.
|
||||||
|
*/
|
||||||
|
export async function uploadFile(
|
||||||
|
file: File,
|
||||||
|
id: string
|
||||||
|
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
return await axios.post(`/api/v1/upload/${id}`, formData);
|
||||||
|
}
|
||||||
|
|
|
||||||
9
src/frontend/src/icons/MongoDB/index.tsx
Normal file
9
src/frontend/src/icons/MongoDB/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React, { forwardRef } from "react";
|
||||||
|
import { ReactComponent as MongoDBSVG } from "./mongodb-icon.svg";
|
||||||
|
|
||||||
|
export const MongoDBIcon = forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.PropsWithChildren<{}>
|
||||||
|
>((props, ref) => {
|
||||||
|
return <MongoDBSVG ref={ref} {...props} />;
|
||||||
|
});
|
||||||
1
src/frontend/src/icons/MongoDB/mongodb-icon.svg
Normal file
1
src/frontend/src/icons/MongoDB/mongodb-icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 32 32"><path d="M15.9.087l.854 1.604c.192.296.4.558.645.802.715.715 1.394 1.464 2.004 2.266 1.447 1.9 2.423 4.01 3.12 6.292.418 1.394.645 2.824.662 4.27.07 4.323-1.412 8.035-4.4 11.12-.488.488-1.01.94-1.57 1.342-.296 0-.436-.227-.558-.436-.227-.383-.366-.82-.436-1.255-.105-.523-.174-1.046-.14-1.586v-.244C16.057 24.21 15.796.21 15.9.087z" fill="#599636"/><path d="M15.9.034c-.035-.07-.07-.017-.105.017.017.35-.105.662-.296.96-.21.296-.488.523-.767.767-1.55 1.342-2.77 2.963-3.747 4.776-1.3 2.44-1.97 5.055-2.16 7.808-.087.993.314 4.497.627 5.508.854 2.684 2.388 4.933 4.375 6.885.488.47 1.01.906 1.55 1.325.157 0 .174-.14.21-.244a4.78 4.78 0 0 0 .157-.68l.35-2.614L15.9.034z" fill="#6cac48"/><path d="M16.754 28.845c.035-.4.227-.732.436-1.063-.21-.087-.366-.26-.488-.453-.105-.174-.192-.383-.26-.575-.244-.732-.296-1.5-.366-2.248v-.453c-.087.07-.105.662-.105.75a17.37 17.37 0 0 1-.314 2.353c-.052.314-.087.627-.28.906 0 .035 0 .07.017.122.314.924.4 1.865.453 2.824v.35c0 .418-.017.33.33.47.14.052.296.07.436.174.105 0 .122-.087.122-.157l-.052-.575v-1.604c-.017-.28.035-.558.07-.82z" fill="#c2bfbf"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
9
src/frontend/src/icons/Pinecone/index.tsx
Normal file
9
src/frontend/src/icons/Pinecone/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React, { forwardRef } from "react";
|
||||||
|
import { ReactComponent as PineconeSVG } from "./pinecone_logo.svg";
|
||||||
|
|
||||||
|
export const PineconeIcon = forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.PropsWithChildren<{}>
|
||||||
|
>((props, ref) => {
|
||||||
|
return <PineconeSVG ref={ref} {...props} />;
|
||||||
|
});
|
||||||
21
src/frontend/src/icons/Pinecone/pinecone_logo.svg
Normal file
21
src/frontend/src/icons/Pinecone/pinecone_logo.svg
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<svg width="32" height="35" viewBox="0 0 32 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.8555 34.2962C14.9325 34.2962 15.8055 33.4451 15.8055 32.3954C15.8055 31.3456 14.9325 30.4946 13.8555 30.4946C12.7786 30.4946 11.9055 31.3456 11.9055 32.3954C11.9055 33.4451 12.7786 34.2962 13.8555 34.2962Z" fill="black"/>
|
||||||
|
<path d="M18.4138 7.19675L19.2512 2.66005" stroke="black" stroke-width="2.11786" stroke-linecap="square"/>
|
||||||
|
<path d="M22.2656 5.5855L19.3466 2.11099L15.3748 4.37292" stroke="black" stroke-width="2.11786" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M14.9202 26.5528L15.7337 22.0169" stroke="black" stroke-width="2.11786" stroke-linecap="square"/>
|
||||||
|
<path d="M18.7729 24.9304L15.83 21.4671L11.8701 23.741" stroke="black" stroke-width="2.11786" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M16.6077 17.1996L17.4212 12.6633" stroke="black" stroke-width="2.11786" stroke-linecap="square"/>
|
||||||
|
<path d="M20.4587 15.58L17.5277 12.128L13.5679 14.3904" stroke="black" stroke-width="2.11786" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M8.32871 26.1554L4.75171 28.5815" stroke="black" stroke-width="2.01017" stroke-linecap="square"/>
|
||||||
|
<path d="M8.54383 30.0865L4.3208 28.8738L4.63185 24.5944" stroke="black" stroke-width="2.01017" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.3213 28.4299L23.8096 31.9282" stroke="black" stroke-width="2.01017" stroke-linecap="square"/>
|
||||||
|
<path d="M19.718 32.045L24.1085 32.3365L25.3527 28.2438" stroke="black" stroke-width="2.01017" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M25.3999 21.3291L29.7784 22.0996" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||||
|
<path d="M26.9072 25.072L30.3048 22.1919L28.1634 18.3557" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M24.1196 12.8615L28.0197 10.763" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||||
|
<path d="M24.3357 8.83965L28.4869 10.5188L27.7093 14.8216" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M6.91639 18.1572L2.52588 17.4101" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||||
|
<path d="M4.17731 21.1645L2 17.328L5.36167 14.436" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.0799 10.6129L8.14893 7.34769" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||||
|
<path d="M12.2897 6.77496L7.80349 6.96156L7.01392 11.2649" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
9
src/frontend/src/icons/supabase/index.tsx
Normal file
9
src/frontend/src/icons/supabase/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React, { forwardRef } from "react";
|
||||||
|
import { ReactComponent as SupabaseSvg } from "./supabase-icon.svg";
|
||||||
|
|
||||||
|
export const SupabaseIcon = forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.PropsWithChildren<{}>
|
||||||
|
>((props, ref) => {
|
||||||
|
return <SupabaseSvg ref={ref} {...props} />;
|
||||||
|
});
|
||||||
99
src/frontend/src/icons/supabase/supabase-icon.svg
Normal file
99
src/frontend/src/icons/supabase/supabase-icon.svg
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
version="1.1"
|
||||||
|
id="svg20"
|
||||||
|
sodipodi:docname="supabase-icon.svg"
|
||||||
|
style="fill:none"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||||
|
<metadata
|
||||||
|
id="metadata24">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1687"
|
||||||
|
inkscape:window-height="849"
|
||||||
|
id="namedview22"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2.0884956"
|
||||||
|
inkscape:cx="54.5"
|
||||||
|
inkscape:cy="56.5"
|
||||||
|
inkscape:window-x="70"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg20" />
|
||||||
|
<path
|
||||||
|
d="m 37.41219,62.936701 c -1.634985,2.05896 -4.950068,0.93085 -4.989463,-1.69817 L 31.846665,22.786035 h 25.855406 c 4.683108,0 7.294967,5.409033 4.382927,9.07673 z"
|
||||||
|
id="path2"
|
||||||
|
style="fill:url(#paint0_linear);stroke-width:0.57177335"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 37.41219,62.936701 c -1.634985,2.05896 -4.950068,0.93085 -4.989463,-1.69817 L 31.846665,22.786035 h 25.855406 c 4.683108,0 7.294967,5.409033 4.382927,9.07673 z"
|
||||||
|
id="path4"
|
||||||
|
style="fill:url(#paint1_linear);fill-opacity:0.2;stroke-width:0.57177335"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 26.89694,1.0634102 c 1.634986,-2.05918508 4.950125,-0.93090008 4.989521,1.698149 L 32.138899,41.214003 H 6.607076 c -4.6832501,0 -7.29518376,-5.409032 -4.3830007,-9.07673 z"
|
||||||
|
id="path6"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#3ecf8e;stroke-width:0.57177335" />
|
||||||
|
<defs
|
||||||
|
id="defs18">
|
||||||
|
<linearGradient
|
||||||
|
id="paint0_linear"
|
||||||
|
x1="53.973801"
|
||||||
|
y1="54.973999"
|
||||||
|
x2="94.163498"
|
||||||
|
y2="71.829498"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.57177306,0,0,0.57177334,0.98590077,-0.12074988)">
|
||||||
|
<stop
|
||||||
|
stop-color="#249361"
|
||||||
|
id="stop8" />
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
stop-color="#3ECF8E"
|
||||||
|
id="stop10" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="paint1_linear"
|
||||||
|
x1="36.1558"
|
||||||
|
y1="30.577999"
|
||||||
|
x2="54.484402"
|
||||||
|
y2="65.080597"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.57177306,0,0,0.57177334,0.98590077,-0.12074988)">
|
||||||
|
<stop
|
||||||
|
id="stop13" />
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
stop-opacity="0"
|
||||||
|
id="stop15" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -101,6 +101,7 @@ export default function ModalField({
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
setEnabled(t);
|
setEnabled(t);
|
||||||
}}
|
}}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : type === "float" ? (
|
) : type === "float" ? (
|
||||||
|
|
|
||||||
|
|
@ -398,7 +398,8 @@ export default function Page({ flow }: { flow: FlowType }) {
|
||||||
zoomOnDoubleClick={!disableCopyPaste}
|
zoomOnDoubleClick={!disableCopyPaste}
|
||||||
selectNodesOnDrag={false}
|
selectNodesOnDrag={false}
|
||||||
className="theme-attribution"
|
className="theme-attribution"
|
||||||
minZoom={0.05}
|
minZoom={0.01}
|
||||||
|
maxZoom={8}
|
||||||
>
|
>
|
||||||
<Background className="dark:bg-gray-900" />
|
<Background className="dark:bg-gray-900" />
|
||||||
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600"></Controls>
|
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600"></Controls>
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ export default function ExtraSidebar() {
|
||||||
Object.keys(dataFilter[d]).length > 0 ? (
|
Object.keys(dataFilter[d]).length > 0 ? (
|
||||||
<DisclosureComponent
|
<DisclosureComponent
|
||||||
openDisc={search.length == 0 ? false : true}
|
openDisc={search.length == 0 ? false : true}
|
||||||
key={i}
|
key={nodeNames[d]}
|
||||||
button={{
|
button={{
|
||||||
title: nodeNames[d] ?? nodeNames.unknown,
|
title: nodeNames[d] ?? nodeNames.unknown,
|
||||||
Icon: nodeIconsLucide[d] ?? nodeIconsLucide.unknown,
|
Icon: nodeIconsLucide[d] ?? nodeIconsLucide.unknown,
|
||||||
|
|
@ -155,9 +155,10 @@ export default function ExtraSidebar() {
|
||||||
.sort()
|
.sort()
|
||||||
.map((t: string, k) => (
|
.map((t: string, k) => (
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
content={t}
|
content={data[d][t].display_name}
|
||||||
delayDuration={1500}
|
delayDuration={1500}
|
||||||
side="right"
|
side="right"
|
||||||
|
key={data[d][t].display_name}
|
||||||
>
|
>
|
||||||
<div key={k} data-tooltip-id={t}>
|
<div key={k} data-tooltip-id={t}>
|
||||||
<div
|
<div
|
||||||
|
|
@ -183,7 +184,7 @@ export default function ExtraSidebar() {
|
||||||
>
|
>
|
||||||
<div className="flex w-full justify-between text-sm px-3 py-1 bg-white dark:bg-gray-800 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
<div className="flex w-full justify-between text-sm px-3 py-1 bg-white dark:bg-gray-800 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
||||||
<span className="text-black dark:text-white w-full pr-1 truncate text-xs">
|
<span className="text-black dark:text-white w-full pr-1 truncate text-xs">
|
||||||
{t}
|
{data[d][t].display_name}
|
||||||
</span>
|
</span>
|
||||||
<Menu className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
<Menu className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ export type APIClassType = {
|
||||||
base_classes: Array<string>;
|
base_classes: Array<string>;
|
||||||
description: string;
|
description: string;
|
||||||
template: APITemplateType;
|
template: APITemplateType;
|
||||||
|
display_name: string;
|
||||||
[key: string]: Array<string> | string | APITemplateType;
|
[key: string]: Array<string> | string | APITemplateType;
|
||||||
};
|
};
|
||||||
export type TemplateVariableType = {
|
export type TemplateVariableType = {
|
||||||
|
|
@ -46,3 +47,8 @@ export type BuildStatusTypeAPI = {
|
||||||
export type InitTypeAPI = {
|
export type InitTypeAPI = {
|
||||||
flowId: string;
|
flowId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UploadFileTypeAPI = {
|
||||||
|
file_path: string;
|
||||||
|
flowId: string;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import { OpenAiIcon } from "./icons/OpenAi";
|
||||||
import { QDrantIcon } from "./icons/QDrant";
|
import { QDrantIcon } from "./icons/QDrant";
|
||||||
import { SearxIcon } from "./icons/Searx";
|
import { SearxIcon } from "./icons/Searx";
|
||||||
import { SlackIcon } from "./icons/Slack";
|
import { SlackIcon } from "./icons/Slack";
|
||||||
|
import { PineconeIcon } from "./icons/Pinecone";
|
||||||
import clsx, { ClassValue } from "clsx";
|
import clsx, { ClassValue } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "./constants";
|
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "./constants";
|
||||||
|
|
@ -63,6 +64,8 @@ import {
|
||||||
Wand2,
|
Wand2,
|
||||||
Wrench,
|
Wrench,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { SupabaseIcon } from "./icons/supabase";
|
||||||
|
import { MongoDBIcon } from "./icons/MongoDB";
|
||||||
|
|
||||||
export function classNames(...classes: Array<string>) {
|
export function classNames(...classes: Array<string>) {
|
||||||
return classes.filter(Boolean).join(" ");
|
return classes.filter(Boolean).join(" ");
|
||||||
|
|
@ -191,6 +194,9 @@ export const nodeIcons: {
|
||||||
ChatOpenAI: OpenAiIcon,
|
ChatOpenAI: OpenAiIcon,
|
||||||
OpenAI: OpenAiIcon,
|
OpenAI: OpenAiIcon,
|
||||||
OpenAIEmbeddings: OpenAiIcon,
|
OpenAIEmbeddings: OpenAiIcon,
|
||||||
|
Pinecone: PineconeIcon,
|
||||||
|
SupabaseVectorStore: SupabaseIcon,
|
||||||
|
MongoDBAtlasVectorSearch: MongoDBIcon,
|
||||||
// UnstructuredPowerPointLoader: PowerPointIcon, // word and powerpoint have differente styles
|
// UnstructuredPowerPointLoader: PowerPointIcon, // word and powerpoint have differente styles
|
||||||
Qdrant: QDrantIcon,
|
Qdrant: QDrantIcon,
|
||||||
// ReadTheDocsLoader: ReadTheDocsIcon, // does not work
|
// ReadTheDocsLoader: ReadTheDocsIcon, // does not work
|
||||||
|
|
@ -560,9 +566,7 @@ export function toFirstUpperCase(str: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function snakeToSpaces(str: string) {
|
export function snakeToSpaces(str: string) {
|
||||||
let result = str.split("_").join(" ");
|
return str.split("_").join(" ");
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toNormalCase(str: string) {
|
export function toNormalCase(str: string) {
|
||||||
|
|
@ -606,10 +610,7 @@ export function roundNumber(x: number, decimals: number) {
|
||||||
export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> {
|
export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> {
|
||||||
const sourceId = edge.source;
|
const sourceId = edge.source;
|
||||||
const targetId = edge.target;
|
const targetId = edge.target;
|
||||||
const connectedNodes = nodes.filter(
|
return nodes.filter((node) => node.id === targetId || node.id === sourceId);
|
||||||
(node) => node.id === targetId || node.id === sourceId
|
|
||||||
);
|
|
||||||
return connectedNodes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidConnection(
|
export function isValidConnection(
|
||||||
|
|
@ -856,7 +857,7 @@ export function groupByFamily(data, baseClasses) {
|
||||||
return foundIndex === index;
|
return foundIndex === index;
|
||||||
});
|
});
|
||||||
|
|
||||||
let groupedObj = groupedBy.reduce((result, item) => {
|
return groupedBy.reduce((result, item) => {
|
||||||
const existingGroup = result.find((group) => group.family === item.family);
|
const existingGroup = result.find((group) => group.family === item.family);
|
||||||
|
|
||||||
if (existingGroup) {
|
if (existingGroup) {
|
||||||
|
|
@ -867,8 +868,6 @@ export function groupByFamily(data, baseClasses) {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return groupedObj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildTweaks(flow) {
|
export function buildTweaks(flow) {
|
||||||
|
|
@ -909,9 +908,7 @@ export function validateNode(
|
||||||
)
|
)
|
||||||
? [
|
? [
|
||||||
`${type} is missing ${
|
`${type} is missing ${
|
||||||
template.display_name
|
template.display_name || toNormalCase(template[t].name)
|
||||||
? template.display_name
|
|
||||||
: toNormalCase(template[t].name)
|
|
||||||
}.`,
|
}.`,
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
|
|
@ -969,3 +966,10 @@ export function getRandomName(
|
||||||
// Return title case final name
|
// Return title case final name
|
||||||
return toTitleCase(final_name);
|
return toTitleCase(final_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRandomKeyByssmm(): string {
|
||||||
|
const now = new Date();
|
||||||
|
const seconds = String(now.getSeconds()).padStart(2, "0");
|
||||||
|
const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
|
||||||
|
return seconds + milliseconds;
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -97,7 +97,7 @@ def test_csv_agent(client: TestClient):
|
||||||
"name": "path",
|
"name": "path",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"list": False,
|
"list": False,
|
||||||
"content": None,
|
"file_path": None,
|
||||||
"advanced": False,
|
"advanced": False,
|
||||||
}
|
}
|
||||||
assert template["llm"] == {
|
assert template["llm"] == {
|
||||||
|
|
@ -137,6 +137,7 @@ def test_initialize_agent(client: TestClient):
|
||||||
"self-ask-with-search",
|
"self-ask-with-search",
|
||||||
"conversational-react-description",
|
"conversational-react-description",
|
||||||
"openai-functions",
|
"openai-functions",
|
||||||
|
"openai-multi-functions",
|
||||||
],
|
],
|
||||||
"name": "agent",
|
"name": "agent",
|
||||||
"type": "str",
|
"type": "str",
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ def test_template_field_defaults(sample_template_field: TemplateField):
|
||||||
assert sample_template_field.value is None
|
assert sample_template_field.value is None
|
||||||
assert sample_template_field.suffixes == []
|
assert sample_template_field.suffixes == []
|
||||||
assert sample_template_field.file_types == []
|
assert sample_template_field.file_types == []
|
||||||
assert sample_template_field.content is None
|
assert sample_template_field.file_path is None
|
||||||
assert sample_template_field.password is False
|
assert sample_template_field.password is False
|
||||||
assert sample_template_field.name == "test_field"
|
assert sample_template_field.name == "test_field"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from typing import Type, Union
|
from typing import Type, Union
|
||||||
from langflow.graph.edge.base import Edge
|
from langflow.graph.edge.base import Edge
|
||||||
from langflow.graph.vertex.base import Vertex
|
from langflow.graph.vertex.base import Vertex
|
||||||
|
|
@ -236,6 +238,7 @@ def test_build_params(basic_graph):
|
||||||
root = get_root_node(basic_graph)
|
root = get_root_node(basic_graph)
|
||||||
# Root node is a TimeTravelGuideChain
|
# Root node is a TimeTravelGuideChain
|
||||||
# which requires an llm and memory
|
# which requires an llm and memory
|
||||||
|
assert root is not None
|
||||||
assert isinstance(root.params, dict)
|
assert isinstance(root.params, dict)
|
||||||
assert "llm" in root.params
|
assert "llm" in root.params
|
||||||
assert "memory" in root.params
|
assert "memory" in root.params
|
||||||
|
|
@ -269,7 +272,6 @@ def test_tool_node_build(complex_graph):
|
||||||
assert tool_node is not None
|
assert tool_node is not None
|
||||||
built_object = tool_node.build()
|
built_object = tool_node.build()
|
||||||
assert built_object is not None
|
assert built_object is not None
|
||||||
# Add any further assertions specific to the ToolNode's build() method
|
|
||||||
|
|
||||||
|
|
||||||
def test_chain_node_build(complex_graph):
|
def test_chain_node_build(complex_graph):
|
||||||
|
|
@ -277,7 +279,6 @@ def test_chain_node_build(complex_graph):
|
||||||
assert chain_node is not None
|
assert chain_node is not None
|
||||||
built_object = chain_node.build()
|
built_object = chain_node.build()
|
||||||
assert built_object is not None
|
assert built_object is not None
|
||||||
# Add any further assertions specific to the ChainNode's build() method
|
|
||||||
|
|
||||||
|
|
||||||
def test_prompt_node_build(complex_graph):
|
def test_prompt_node_build(complex_graph):
|
||||||
|
|
@ -285,7 +286,6 @@ def test_prompt_node_build(complex_graph):
|
||||||
assert prompt_node is not None
|
assert prompt_node is not None
|
||||||
built_object = prompt_node.build()
|
built_object = prompt_node.build()
|
||||||
assert built_object is not None
|
assert built_object is not None
|
||||||
# Add any further assertions specific to the PromptNode's build() method
|
|
||||||
|
|
||||||
|
|
||||||
def test_llm_node_build(basic_graph):
|
def test_llm_node_build(basic_graph):
|
||||||
|
|
@ -293,23 +293,36 @@ def test_llm_node_build(basic_graph):
|
||||||
assert llm_node is not None
|
assert llm_node is not None
|
||||||
built_object = llm_node.build()
|
built_object = llm_node.build()
|
||||||
assert built_object is not None
|
assert built_object is not None
|
||||||
# Add any further assertions specific to the LLMNode's build() method
|
|
||||||
|
|
||||||
|
|
||||||
def test_toolkit_node_build(openapi_graph):
|
def test_toolkit_node_build(openapi_graph):
|
||||||
|
# Write a file to the disk
|
||||||
|
file_path = "api-with-examples.yaml"
|
||||||
|
with open(file_path, "w") as f:
|
||||||
|
f.write("openapi: 3.0.0")
|
||||||
|
|
||||||
toolkit_node = get_node_by_type(openapi_graph, ToolkitVertex)
|
toolkit_node = get_node_by_type(openapi_graph, ToolkitVertex)
|
||||||
assert toolkit_node is not None
|
assert toolkit_node is not None
|
||||||
built_object = toolkit_node.build()
|
built_object = toolkit_node.build()
|
||||||
assert built_object is not None
|
assert built_object is not None
|
||||||
# Add any further assertions specific to the ToolkitNode's build() method
|
# Remove the file
|
||||||
|
os.remove(file_path)
|
||||||
|
assert not Path(file_path).exists()
|
||||||
|
|
||||||
|
|
||||||
def test_file_tool_node_build(openapi_graph):
|
def test_file_tool_node_build(openapi_graph):
|
||||||
|
file_path = "api-with-examples.yaml"
|
||||||
|
with open(file_path, "w") as f:
|
||||||
|
f.write("openapi: 3.0.0")
|
||||||
|
|
||||||
|
assert Path(file_path).exists()
|
||||||
file_tool_node = get_node_by_type(openapi_graph, FileToolVertex)
|
file_tool_node = get_node_by_type(openapi_graph, FileToolVertex)
|
||||||
assert file_tool_node is not None
|
assert file_tool_node is not None
|
||||||
built_object = file_tool_node.build()
|
built_object = file_tool_node.build()
|
||||||
assert built_object is not None
|
assert built_object is not None
|
||||||
# Add any further assertions specific to the FileToolNode's build() method
|
# Remove the file
|
||||||
|
os.remove(file_path)
|
||||||
|
assert not Path(file_path).exists()
|
||||||
|
|
||||||
|
|
||||||
def test_wrapper_node_build(openapi_graph):
|
def test_wrapper_node_build(openapi_graph):
|
||||||
|
|
@ -317,7 +330,6 @@ def test_wrapper_node_build(openapi_graph):
|
||||||
assert wrapper_node is not None
|
assert wrapper_node is not None
|
||||||
built_object = wrapper_node.build()
|
built_object = wrapper_node.build()
|
||||||
assert built_object is not None
|
assert built_object is not None
|
||||||
# Add any further assertions specific to the WrapperNode's build() method
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_result_and_thought(basic_graph):
|
def test_get_result_and_thought(basic_graph):
|
||||||
|
|
@ -325,7 +337,7 @@ def test_get_result_and_thought(basic_graph):
|
||||||
responses = [
|
responses = [
|
||||||
"Final Answer: I am a response",
|
"Final Answer: I am a response",
|
||||||
]
|
]
|
||||||
message = "Hello"
|
message = {"input": "Hello"}
|
||||||
# Find the node that is an LLMNode and change the
|
# Find the node that is an LLMNode and change the
|
||||||
# _built_object to a FakeListLLM
|
# _built_object to a FakeListLLM
|
||||||
llm_node = get_node_by_type(basic_graph, LLMVertex)
|
llm_node = get_node_by_type(basic_graph, LLMVertex)
|
||||||
|
|
@ -338,8 +350,5 @@ def test_get_result_and_thought(basic_graph):
|
||||||
# now build again and check if FakeListLLM was used
|
# now build again and check if FakeListLLM was used
|
||||||
|
|
||||||
# Get the result and thought
|
# Get the result and thought
|
||||||
result, thought = get_result_and_thought(langchain_object, message)
|
result = get_result_and_thought(langchain_object, message)
|
||||||
# The result should be a str
|
assert isinstance(result, dict)
|
||||||
assert isinstance(result, str)
|
|
||||||
# The thought should be a Thought
|
|
||||||
assert isinstance(thought, str)
|
|
||||||
|
|
|
||||||
|
|
@ -369,7 +369,7 @@ def test_chat_open_ai(client: TestClient):
|
||||||
"placeholder": "",
|
"placeholder": "",
|
||||||
"show": True,
|
"show": True,
|
||||||
"multiline": False,
|
"multiline": False,
|
||||||
"value": "gpt-3.5-turbo",
|
"value": "gpt-3.5-turbo-0613",
|
||||||
"password": False,
|
"password": False,
|
||||||
"options": [
|
"options": [
|
||||||
"gpt-3.5-turbo-0613",
|
"gpt-3.5-turbo-0613",
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ def test_build_template_from_function():
|
||||||
# Test with valid name
|
# Test with valid name
|
||||||
result = build_template_from_function("ExampleClass1", type_to_loader_dict)
|
result = build_template_from_function("ExampleClass1", type_to_loader_dict)
|
||||||
|
|
||||||
|
assert result is not None
|
||||||
assert "template" in result
|
assert "template" in result
|
||||||
assert "description" in result
|
assert "description" in result
|
||||||
assert "base_classes" in result
|
assert "base_classes" in result
|
||||||
|
|
@ -67,6 +68,7 @@ def test_build_template_from_function():
|
||||||
result_with_function = build_template_from_function(
|
result_with_function = build_template_from_function(
|
||||||
"ExampleClass1", type_to_loader_dict, add_function=True
|
"ExampleClass1", type_to_loader_dict, add_function=True
|
||||||
)
|
)
|
||||||
|
assert result_with_function is not None
|
||||||
assert "function" in result_with_function["base_classes"]
|
assert "function" in result_with_function["base_classes"]
|
||||||
|
|
||||||
# Test with invalid name
|
# Test with invalid name
|
||||||
|
|
@ -80,6 +82,7 @@ def test_build_template_from_class():
|
||||||
|
|
||||||
# Test valid input
|
# Test valid input
|
||||||
result = build_template_from_class("Child", type_to_cls_dict)
|
result = build_template_from_class("Child", type_to_cls_dict)
|
||||||
|
assert result is not None
|
||||||
assert "template" in result
|
assert "template" in result
|
||||||
assert "description" in result
|
assert "description" in result
|
||||||
assert "base_classes" in result
|
assert "base_classes" in result
|
||||||
|
|
@ -222,6 +225,7 @@ def test_format_dict():
|
||||||
"password": False,
|
"password": False,
|
||||||
"multiline": False,
|
"multiline": False,
|
||||||
"options": OPENAI_MODELS,
|
"options": OPENAI_MODELS,
|
||||||
|
"value": "text-davinci-003",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expected_output_openai_chat = {
|
expected_output_openai_chat = {
|
||||||
|
|
@ -233,6 +237,7 @@ def test_format_dict():
|
||||||
"password": False,
|
"password": False,
|
||||||
"multiline": False,
|
"multiline": False,
|
||||||
"options": CHAT_OPENAI_MODELS,
|
"options": CHAT_OPENAI_MODELS,
|
||||||
|
"value": "gpt-3.5-turbo-0613",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert format_dict(input_dict, "OpenAI") == expected_output_openai
|
assert format_dict(input_dict, "OpenAI") == expected_output_openai
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue