Merge branch 'login' into authentication

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-08-25 15:23:22 +00:00 committed by GitHub
commit 16695c8241
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
97 changed files with 3022 additions and 1077 deletions

View file

@ -106,14 +106,9 @@ async def stream_build(flow_id: str):
return
logger.debug("Building langchain object")
try:
# Some error could happen when building the graph
graph = Graph.from_payload(graph_data)
except Exception as exc:
logger.exception(exc)
error_message = str(exc)
yield str(StreamData(event="error", data={"error": error_message}))
return
# Some error could happen when building the graph
graph = Graph.from_payload(graph_data)
number_of_nodes = len(graph.nodes)
flow_data_store[flow_id]["status"] = BuildStatus.IN_PROGRESS
@ -128,7 +123,9 @@ async def stream_build(flow_id: str):
params = vertex._built_object_repr()
valid = True
logger.debug(f"Building node {str(vertex.vertex_type)}")
logger.debug(f"Output: {params}")
logger.debug(
f"Output: {params[:100]}{'...' if len(params) > 100 else ''}"
)
if vertex.artifacts:
# The artifacts will be prompt variables
# passed to build_input_keys_response

View file

@ -65,7 +65,6 @@ def get_all():
logger.info(
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
)
logger.debug(custom_component_dict)
custom_components_from_file = merge_nested_dicts_with_renaming(
custom_components_from_file, custom_component_dict
)

View file

@ -1,5 +1,7 @@
from typing import List
from uuid import UUID
from fastapi.encoders import jsonable_encoder
from langflow.api.utils import remove_api_keys
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
from langflow.services.database.models.flow import (
@ -10,12 +12,11 @@ from langflow.services.database.models.flow import (
)
from langflow.services.utils import get_session
from langflow.services.utils import get_settings_manager
import orjson
from sqlmodel import Session, select
from fastapi import APIRouter, Depends, HTTPException
from fastapi.encoders import jsonable_encoder
from fastapi import File, UploadFile
import json
# build router
router = APIRouter(prefix="/flows", tags=["Flows"])
@ -105,7 +106,7 @@ async def upload_file(
):
"""Upload flows from a file."""
contents = await file.read()
data = json.loads(contents)
data = orjson.loads(contents)
if "flows" in data:
flow_list = FlowListCreate(**data)
else:

View file

@ -5,8 +5,9 @@ from uuid import UUID
from langflow.services.database.models.api_key.api_key import ApiKeyRead
from langflow.services.database.models.flow import FlowCreate, FlowRead
from langflow.services.database.models.user import UserRead
from langflow.services.database.models.base import orjson_dumps
from pydantic import BaseModel, Field, validator
import json
class BuildStatus(Enum):
@ -119,7 +120,9 @@ class StreamData(BaseModel):
data: dict
def __str__(self) -> str:
return f"event: {self.event}\ndata: {json.dumps(self.data)}\n\n"
return (
f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
)
class CustomComponentCode(BaseModel):

View file

@ -0,0 +1,76 @@
from langflow import CustomComponent
from langchain.schema import Document
from langflow.services.database.models.base import orjson_dumps
import requests
from typing import Optional
class GetRequest(CustomComponent):
display_name: str = "GET Request"
description: str = "Make a GET request to the given URL."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#get-request"
beta = True
field_config = {
"url": {
"display_name": "URL",
"info": "The URL to make the request to",
"is_list": True,
},
"headers": {
"display_name": "Headers",
"field_type": "code",
"info": "The headers to send with the request.",
},
"code": {"show": False},
"timeout": {
"display_name": "Timeout",
"field_type": "int",
"info": "The timeout to use for the request.",
"value": 5,
},
}
def get_document(
self, session: requests.Session, url: str, headers: Optional[dict], timeout: int
) -> Document:
try:
response = session.get(url, headers=headers, timeout=int(timeout))
try:
response_json = response.json()
result = orjson_dumps(response_json, indent_2=False)
except Exception:
result = response.text
self.repr_value = result
return Document(
page_content=result,
metadata={
"source": url,
"headers": headers,
"status_code": response.status_code,
},
)
except requests.Timeout:
return Document(
page_content="Request Timed Out",
metadata={"source": url, "headers": headers, "status_code": 408},
)
except Exception as exc:
return Document(
page_content=str(exc),
metadata={"source": url, "headers": headers, "status_code": 500},
)
def build(
self,
url: str,
headers: Optional[dict] = None,
timeout: int = 5,
) -> list[Document]:
if headers is None:
headers = {}
urls = url if isinstance(url, list) else [url]
with requests.Session() as session:
documents = [self.get_document(session, u, headers, timeout) for u in urls]
self.repr_value = documents
return documents

View file

@ -0,0 +1,55 @@
### JSON Document Builder
# Build a Document containing a JSON object using a key and another Document page content.
# **Params**
# - **Key:** The key to use for the JSON object.
# - **Document:** The Document page to use for the JSON object.
# **Output**
# - **Document:** The Document containing the JSON object.
from langflow import CustomComponent
from langchain.schema import Document
from langflow.services.database.models.base import orjson_dumps
class JSONDocumentBuilder(CustomComponent):
display_name: str = "JSON Document Builder"
description: str = "Build a Document containing a JSON object using a key and another Document page content."
output_types: list[str] = ["Document"]
beta = True
documentation: str = (
"https://docs.langflow.org/components/utilities#json-document-builder"
)
field_config = {
"key": {"display_name": "Key"},
"document": {"display_name": "Document"},
}
def build(
self,
key: str,
document: Document,
) -> Document:
documents = None
if isinstance(document, list):
documents = [
Document(
page_content=orjson_dumps({key: doc.page_content}, indent_2=False)
)
for doc in document
]
elif isinstance(document, Document):
documents = Document(
page_content=orjson_dumps({key: document.page_content}, indent_2=False)
)
else:
raise TypeError(
f"Expected Document or list of Documents, got {type(document)}"
)
self.repr_value = documents
return documents

View file

@ -0,0 +1,81 @@
from langflow import CustomComponent
from langchain.schema import Document
from langflow.services.database.models.base import orjson_dumps
import requests
from typing import Optional
class PostRequest(CustomComponent):
display_name: str = "POST Request"
description: str = "Make a POST request to the given URL."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#post-request"
beta = True
field_config = {
"url": {"display_name": "URL", "info": "The URL to make the request to."},
"headers": {
"display_name": "Headers",
"field_type": "code",
"info": "The headers to send with the request.",
},
"code": {"show": False},
"document": {"display_name": "Document"},
}
def post_document(
self,
session: requests.Session,
document: Document,
url: str,
headers: Optional[dict] = None,
) -> Document:
try:
response = session.post(url, headers=headers, data=document.page_content)
try:
response_json = response.json()
result = orjson_dumps(response_json, indent_2=False)
except Exception:
result = response.text
self.repr_value = result
return Document(
page_content=result,
metadata={
"source": url,
"headers": headers,
"status_code": response,
},
)
except Exception as exc:
return Document(
page_content=str(exc),
metadata={
"source": url,
"headers": headers,
"status_code": 500,
},
)
def build(
self,
document: Document,
url: str,
headers: Optional[dict] = None,
) -> list[Document]:
if headers is None:
headers = {}
if not isinstance(document, list) and isinstance(document, Document):
documents: list[Document] = [document]
elif isinstance(document, list) and all(
isinstance(doc, Document) for doc in document
):
documents = document
else:
raise ValueError("document must be a Document or a list of Documents")
with requests.Session() as session:
documents = [
self.post_document(session, doc, url, headers) for doc in documents
]
self.repr_value = documents
return documents

View file

@ -0,0 +1,94 @@
from typing import List, Optional
import requests
from langflow import CustomComponent
from langchain.schema import Document
from langflow.services.database.models.base import orjson_dumps
class UpdateRequest(CustomComponent):
display_name: str = "Update Request"
description: str = "Make a PATCH request to the given URL."
output_types: list[str] = ["Document"]
documentation: str = "https://docs.langflow.org/components/utilities#update-request"
beta = True
field_config = {
"url": {"display_name": "URL", "info": "The URL to make the request to."},
"headers": {
"display_name": "Headers",
"field_type": "code",
"info": "The headers to send with the request.",
},
"code": {"show": False},
"document": {"display_name": "Document"},
"method": {
"display_name": "Method",
"field_type": "str",
"info": "The HTTP method to use.",
"options": ["PATCH", "PUT"],
"value": "PATCH",
},
}
def update_document(
self,
session: requests.Session,
document: Document,
url: str,
headers: Optional[dict] = None,
method: str = "PATCH",
) -> Document:
try:
if method == "PATCH":
response = session.patch(
url, headers=headers, data=document.page_content
)
elif method == "PUT":
response = session.put(url, headers=headers, data=document.page_content)
else:
raise ValueError(f"Unsupported method: {method}")
try:
response_json = response.json()
result = orjson_dumps(response_json, indent_2=False)
except Exception:
result = response.text
self.repr_value = result
return Document(
page_content=result,
metadata={
"source": url,
"headers": headers,
"status_code": response.status_code,
},
)
except Exception as exc:
return Document(
page_content=str(exc),
metadata={"source": url, "headers": headers, "status_code": 500},
)
def build(
self,
method: str,
document: Document,
url: str,
headers: Optional[dict] = None,
) -> List[Document]:
if headers is None:
headers = {}
if not isinstance(document, list) and isinstance(document, Document):
documents: list[Document] = [document]
elif isinstance(document, list) and all(
isinstance(doc, Document) for doc in document
):
documents = document
else:
raise ValueError("document must be a Document or a list of Documents")
with requests.Session() as session:
documents = [
self.update_document(session, doc, url, headers, method)
for doc in documents
]
self.repr_value = documents
return documents

View file

@ -0,0 +1,109 @@
from typing import Optional, Union
from langflow import CustomComponent
from langchain.vectorstores import Chroma
from langchain.schema import Document
from langchain.vectorstores.base import VectorStore
from langchain.schema import BaseRetriever
from langchain.embeddings.base import Embeddings
import chromadb
class ChromaComponent(CustomComponent):
"""
A custom component for implementing a Vector Store using Chroma.
"""
display_name: str = "Chroma (Custom Component)"
description: str = "Implementation of Vector Store using Chroma"
documentation = "https://python.langchain.com/docs/integrations/vectorstores/chroma"
beta = True
def build_config(self):
"""
Builds the configuration for the component.
Returns:
- dict: A dictionary containing the configuration options for the component.
"""
return {
"collection_name": {"display_name": "Collection Name", "value": "langflow"},
"persist": {"display_name": "Persist"},
"persist_directory": {"display_name": "Persist Directory"},
"code": {"show": False, "display_name": "Code"},
"documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"},
"chroma_server_cors_allow_origins": {
"display_name": "Server CORS Allow Origins",
"advanced": True,
},
"chroma_server_host": {"display_name": "Server Host", "advanced": True},
"chroma_server_port": {"display_name": "Server Port", "advanced": True},
"chroma_server_grpc_port": {
"display_name": "Server gRPC Port",
"advanced": True,
},
"chroma_server_ssl_enabled": {
"display_name": "Server SSL Enabled",
"advanced": True,
},
}
def build(
self,
collection_name: str,
persist: bool,
chroma_server_ssl_enabled: bool,
persist_directory: Optional[str] = None,
embedding: Optional[Embeddings] = None,
documents: Optional[Document] = None,
chroma_server_cors_allow_origins: Optional[str] = None,
chroma_server_host: Optional[str] = None,
chroma_server_port: Optional[int] = None,
chroma_server_grpc_port: Optional[int] = None,
) -> Union[VectorStore, BaseRetriever]:
"""
Builds the Vector Store or BaseRetriever object.
Args:
- collection_name (str): The name of the collection.
- persist_directory (Optional[str]): The directory to persist the Vector Store to.
- chroma_server_ssl_enabled (bool): Whether to enable SSL for the Chroma server.
- persist (bool): Whether to persist the Vector Store or not.
- embedding (Optional[Embeddings]): The embeddings to use for the Vector Store.
- documents (Optional[Document]): The documents to use for the Vector Store.
- chroma_server_cors_allow_origins (Optional[str]): The CORS allow origins for the Chroma server.
- chroma_server_host (Optional[str]): The host for the Chroma server.
- chroma_server_port (Optional[int]): The port for the Chroma server.
- chroma_server_grpc_port (Optional[int]): The gRPC port for the Chroma server.
Returns:
- Union[VectorStore, BaseRetriever]: The Vector Store or BaseRetriever object.
"""
# Chroma settings
chroma_settings = None
if chroma_server_host is not None:
chroma_settings = chromadb.config.Settings(
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
or None,
chroma_server_host=chroma_server_host,
chroma_server_port=chroma_server_port or None,
chroma_server_grpc_port=chroma_server_grpc_port or None,
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
)
# If documents, then we need to create a Chroma instance using .from_documents
if documents is not None and embedding is not None:
return Chroma.from_documents(
documents=documents, # type: ignore
persist_directory=persist_directory if persist else None,
collection_name=collection_name,
embedding=embedding,
client_settings=chroma_settings,
)
return Chroma(
persist_directory=persist_directory, client_settings=chroma_settings
)

View file

@ -40,7 +40,6 @@ class Edge:
if no_matched_type:
logger.debug(self.source_types)
logger.debug(self.target_reqs)
if no_matched_type:
raise ValueError(
f"Edge between {self.source.vertex_type} and {self.target.vertex_type} "
f"has no matched type"

View file

@ -4,6 +4,7 @@ from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES
from langflow.interface.custom.component import Component
from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.services.utils import get_db_manager
from langflow.interface.custom.utils import extract_inner_type
from langflow.utils import validate
@ -20,7 +21,7 @@ class CustomComponent(Component, extra=Extra.allow):
function_entrypoint_name = "build"
function: Optional[Callable] = None
return_type_valid_list = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
repr_value: Optional[str] = ""
repr_value: Optional[Any] = ""
def __init__(self, **data):
super().__init__(**data)
@ -123,6 +124,10 @@ class CustomComponent(Component, extra=Extra.allow):
return_type = build_method["return_type"]
if not return_type:
return []
# If list or List is in the return type, then we remove it and return the inner type
if return_type.startswith("list") or return_type.startswith("List"):
return_type = extract_inner_type(return_type)
# If the return type is not a Union, then we just return it as a list
if "Union" not in return_type:
return [return_type] if return_type in self.return_type_valid_list else []

View file

@ -77,7 +77,7 @@ class DirectoryReader:
]
filtered = [menu for menu in items if menu["components"]]
logger.debug(
f'Filtered components {"with errors" if with_errors else ""}: {filtered}'
f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}'
)
return {"menu": filtered}

View file

@ -0,0 +1,10 @@
import re
def extract_inner_type(return_type: str) -> str:
"""
Extracts the inner type from a type hint that is a list.
"""
if match := re.match(r"list\[(.*)\]", return_type, re.IGNORECASE):
return match[1]
return return_type

View file

@ -1,4 +1,5 @@
import json
import orjson
from typing import Any, Callable, Dict, Sequence, Type
from langchain.agents import agent as agent_module
@ -66,7 +67,7 @@ def convert_kwargs(params):
for key in kwargs_keys:
if isinstance(params[key], str):
try:
params[key] = json.loads(params[key])
params[key] = orjson.loads(params[key])
except json.JSONDecodeError:
# if the string is not a valid json string, we will
# remove the key from the params
@ -310,7 +311,7 @@ def instantiate_documentloader(class_object: Type[BaseLoader], params: Dict):
metadata = params.pop("metadata", None)
if metadata and isinstance(metadata, str):
try:
metadata = json.loads(metadata)
metadata = orjson.loads(metadata)
except json.JSONDecodeError as exc:
raise ValueError(
"The metadata you provided is not a valid JSON string."

View file

@ -1,5 +1,7 @@
import contextlib
import json
from langflow.services.database.models.base import orjson_dumps
import orjson
from typing import Any, Dict, List
from langchain.agents import ZeroShotAgent
@ -95,9 +97,11 @@ def format_content(variable):
def try_to_load_json(content):
with contextlib.suppress(json.JSONDecodeError):
content = json.loads(content)
content = orjson.loads(content)
if isinstance(content, list):
content = ",".join([str(item) for item in content])
else:
content = orjson_dumps(content)
return content

View file

@ -1,4 +1,3 @@
import json
from typing import Any, Callable, Dict, Type
from langchain.vectorstores import (
Pinecone,
@ -12,6 +11,8 @@ from langchain.vectorstores import (
import os
import orjson
def docs_in_params(params: dict) -> bool:
"""Check if params has documents OR texts and one of them is not an empty list,
@ -92,7 +93,7 @@ def initialize_weaviate(class_object: Type[Weaviate], params: dict):
import weaviate # type: ignore
client_kwargs_json = params.get("client_kwargs", "{}")
client_kwargs = json.loads(client_kwargs_json)
client_kwargs = orjson.loads(client_kwargs_json)
client_params = {
"url": params.get("weaviate_url"),
}

View file

@ -190,17 +190,16 @@ def build_frontend_node(custom_component: CustomComponent):
def update_attributes(frontend_node, template_config):
"""Update the display name and description of a frontend node"""
if "display_name" in template_config:
frontend_node["display_name"] = template_config["display_name"]
if "description" in template_config:
frontend_node["description"] = template_config["description"]
if "beta" in template_config:
frontend_node["beta"] = template_config["beta"]
if "documentation" in template_config:
frontend_node["documentation"] = template_config["documentation"]
attributes = [
"display_name",
"description",
"beta",
"documentation",
"output_types",
]
for attribute in attributes:
if attribute in template_config:
frontend_node[attribute] = template_config[attribute]
def build_field_config(custom_component: CustomComponent):
@ -338,7 +337,9 @@ def build_valid_menu(valid_components):
valid_menu[menu_name] = {}
for component in menu_item["components"]:
logger.debug(f"Building component: {component}")
logger.debug(
f"Building component: {component.get('name'), component.get('output_types')}"
)
try:
component_name = component["name"]
component_code = component["code"]

View file

@ -1,6 +1,6 @@
import json
from pathlib import Path
from langchain.schema import AgentAction
import json
from langflow.interface.run import (
build_sorted_vertices_with_caching,
get_memory_key,

View file

@ -2,13 +2,13 @@ import base64
import contextlib
import functools
import hashlib
import json
import os
import tempfile
from collections import OrderedDict
from pathlib import Path
from typing import Any, Dict
from appdirs import user_cache_dir
from langflow.services.database.models.base import orjson_dumps
CACHE: Dict[str, Any] = {}
@ -90,7 +90,8 @@ def clear_old_cache_files(max_cache_size: int = 3):
def compute_dict_hash(graph_data):
graph_data = filter_json(graph_data)
cleaned_graph_json = json.dumps(graph_data, sort_keys=True)
cleaned_graph_json = orjson_dumps(graph_data, sort_keys=True)
return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest()

View file

@ -11,10 +11,10 @@ from langflow.utils.logger import logger
import asyncio
import json
from typing import Any, Dict, List
from langflow.services.cache.flow import InMemoryCache
import orjson
class ChatHistory(Subject):
@ -190,7 +190,7 @@ class ChatManager(Service):
while True:
json_payload = await websocket.receive_json()
try:
payload = json.loads(json_payload)
payload = orjson.loads(json_payload)
except TypeError:
payload = json_payload
if "clear_history" in payload:

View file

@ -2,9 +2,20 @@ from sqlmodel import SQLModel
import orjson
def orjson_dumps(v, *, default):
# orjson.dumps returns bytes, to match standard json.dumps we need to decode
return orjson.dumps(v, default=default).decode()
def orjson_dumps(v, *, default=None, sort_keys=False, indent_2=True):
option = orjson.OPT_SORT_KEYS if sort_keys else None
if indent_2:
# orjson.dumps returns bytes, to match standard json.dumps we need to decode
# option
# To modify how data is serialized, specify option. Each option is an integer constant in orjson.
# To specify multiple options, mask them together, e.g., option=orjson.OPT_STRICT_INTEGER | orjson.OPT_NAIVE_UTC
if option is None:
option = orjson.OPT_INDENT_2
else:
option |= orjson.OPT_INDENT_2
if default is None:
return orjson.dumps(v, option=option).decode()
return orjson.dumps(v, default=default, option=option).decode()
class SQLModelSerializable(SQLModel):

View file

@ -1,5 +1,6 @@
import contextlib
import json
import orjson
import os
from shutil import copy2
from typing import Optional, List
@ -175,7 +176,7 @@ class Settings(BaseSettings):
if isinstance(getattr(self, key), list):
# value might be a '[something]' string
with contextlib.suppress(json.decoder.JSONDecodeError):
value = json.loads(str(value))
value = orjson.loads(str(value))
if isinstance(value, list):
for item in value:
if isinstance(item, Path):

View file

@ -1,5 +1,5 @@
import json
from typing import Optional
from langflow.services.database.models.base import orjson_dumps
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
@ -89,7 +89,7 @@ class LLMFrontendNode(FrontendNode):
if field.name == "config":
field.show = True
field.advanced = True
field.value = json.dumps(CTRANSFORMERS_DEFAULT_CONFIG, indent=2)
field.value = orjson_dumps(CTRANSFORMERS_DEFAULT_CONFIG, indent_2=True)
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:

View file

@ -1,6 +1,6 @@
import ast
import json
from typing import Optional
from langflow.services.database.models.base import orjson_dumps
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.base import FrontendNode
@ -22,4 +22,4 @@ class UtilitiesFrontendNode(FrontendNode):
if isinstance(field.value, dict):
field.field_type = "code"
field.value = json.dumps(field.value, indent=4)
field.value = orjson_dumps(field.value)