merge fix

This commit is contained in:
cristhianzl 2024-04-08 12:56:28 -03:00
commit 357029865f
72 changed files with 2282 additions and 2558 deletions

View file

@ -2,25 +2,25 @@ import platform
import socket
import sys
import time
import webbrowser
from pathlib import Path
from typing import Optional
import click
import httpx
import typer
from dotenv import load_dotenv
from multiprocess import Process, cpu_count # type: ignore
from rich import box
from rich import print as rprint
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from langflow.main import setup_app
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service, get_settings_service
from langflow.services.utils import initialize_services, initialize_settings_service
from langflow.utils.logger import configure, logger
from multiprocess import Process, cpu_count # type: ignore
from packaging import version as pkg_version
from rich import box
from rich import print as rprint
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
console = Console()
@ -99,8 +99,12 @@ def update_settings(
@app.command()
def run(
host: str = typer.Option("127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"),
workers: int = typer.Option(1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"),
host: str = typer.Option(
"127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"
),
workers: int = typer.Option(
1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"
),
timeout: int = typer.Option(300, help="Worker timeout in seconds."),
port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"),
components_path: Optional[Path] = typer.Option(
@ -108,11 +112,19 @@ def run(
help="Path to the directory containing custom components.",
envvar="LANGFLOW_COMPONENTS_PATH",
),
config: str = typer.Option(Path(__file__).parent / "config.yaml", help="Path to the configuration file."),
config: str = typer.Option(
Path(__file__).parent / "config.yaml", help="Path to the configuration file."
),
# .env file param
env_file: Path = typer.Option(None, help="Path to the .env file containing environment variables."),
log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"),
env_file: Path = typer.Option(
None, help="Path to the .env file containing environment variables."
),
log_level: str = typer.Option(
"critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
),
log_file: Path = typer.Option(
"logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"
),
cache: Optional[str] = typer.Option(
envvar="LANGFLOW_LANGCHAIN_CACHE",
help="Type of cache to use. (InMemoryCache, SQLiteCache)",
@ -189,22 +201,30 @@ def run(
else:
# Run using gunicorn on Linux
run_on_mac_or_linux(host, port, log_level, options, app, open_browser)
if open_browser:
click.launch(f"http://{host}:{port}")
def run_on_mac_or_linux(host, port, log_level, options, app, open_browser=True):
webapp_process = Process(target=run_langflow, args=(host, port, log_level, options, app))
webapp_process.start()
def wait_for_server_ready(host, port):
"""
Wait for the server to become ready by polling the health endpoint.
"""
status_code = 0
while status_code != 200:
try:
status_code = httpx.get(f"http://{host}:{port}/health").status_code
except Exception:
time.sleep(1)
def run_on_mac_or_linux(host, port, log_level, options, app):
webapp_process = Process(
target=run_langflow, args=(host, port, log_level, options, app)
)
webapp_process.start()
wait_for_server_ready(host, port)
print_banner(host, port)
if open_browser:
webbrowser.open(f"http://{host}:{port}")
def run_on_windows(host, port, log_level, options, app):
@ -245,40 +265,165 @@ def get_free_port(port):
return port
def print_banner(host, port):
def version_is_prerelease(version: str):
"""
Check if a version is a pre-release version.
"""
return "a" in version or "b" in version or "rc" in version
def get_letter_from_version(version: str):
"""
Get the letter from a pre-release version.
"""
if "a" in version:
return "a"
if "b" in version:
return "b"
if "rc" in version:
return "rc"
return None
def build_new_version_notice(current_version: str, package_name: str):
"""
Build a new version notice.
"""
# The idea here is that we want to show a notice to the user
# when a new version of Langflow is available.
# The key is that if the version the user has is a pre-release
# e.g 0.0.0a1, then we find the latest version that is pre-release
# otherwise we find the latest stable version.
# we will show the notice either way, but only if the version
# the user has is not the latest version.
if version_is_prerelease(current_version):
# curl -s "https://pypi.org/pypi/langflow/json" | jq -r '.releases | keys | .[]' | sort -V | tail -n 1
# this command will give us the latest pre-release version
package_info = httpx.get(f"https://pypi.org/pypi/{package_name}/json").json()
# 4.0.0a1 or 4.0.0b1 or 4.0.0rc1
# find which type of pre-release version we have
# could be a1, b1, rc1
# we want the a, b, or rc and the number
suffix_letter = get_letter_from_version(current_version)
number_version = current_version.split(suffix_letter)[0]
latest_version = sorted(
package_info["releases"].keys(),
key=lambda x: x.split(suffix_letter)[-1] and number_version in x,
)[-1]
if version_is_prerelease(latest_version) and latest_version != current_version:
return (
True,
f"A new pre-release version of {package_name} is available: {latest_version}",
)
else:
latest_version = httpx.get(f"https://pypi.org/pypi/{package_name}/json").json()[
"info"
]["version"]
if not version_is_prerelease(latest_version):
return (
False,
f"A new version of {package_name} is available: {latest_version}",
)
return False, ""
def is_prerelease(version: str) -> bool:
return "a" in version or "b" in version or "rc" in version
def fetch_latest_version(package_name: str, include_prerelease: bool) -> str:
response = httpx.get(f"https://pypi.org/pypi/{package_name}/json")
versions = response.json()["releases"].keys()
valid_versions = [v for v in versions if include_prerelease or not is_prerelease(v)]
if not valid_versions:
return None # Handle case where no valid versions are found
return max(valid_versions, key=lambda v: pkg_version.parse(v))
def build_version_notice(current_version: str, package_name: str) -> str:
latest_version = fetch_latest_version(package_name, is_prerelease(current_version))
if latest_version and pkg_version.parse(current_version) < pkg_version.parse(
latest_version
):
release_type = "pre-release" if is_prerelease(latest_version) else "version"
return f"A new {release_type} of {package_name} is available: {latest_version}"
return ""
def generate_pip_command(package_names, is_pre_release):
"""
Generate the pip install command based on the packages and whether it's a pre-release.
"""
base_command = "pip install"
if is_pre_release:
return f"{base_command} {' '.join(package_names)} -U --pre"
else:
return f"{base_command} {' '.join(package_names)} -U"
def stylize_text(text: str, to_style: str, is_prerelease: bool) -> str:
color = "#42a7f5" if is_prerelease else "#6e42f5"
# return "".join(f"[{color}]{char}[/]" for char in text)
styled_text = f"[{color}]{to_style}[/]"
return text.replace(to_style, styled_text)
def print_banner(host: str, port: int):
notices = []
package_names = [] # Track package names for pip install instructions
is_pre_release = False # Track if any package is a pre-release
package_name = ""
try:
from langflow.version import __version__
from langflow.version import __version__ as langflow_version
version = __version__
word = "Langflow"
is_pre_release |= is_prerelease(langflow_version) # Update pre-release status
notice = build_version_notice(langflow_version, "langflow")
notice = stylize_text(notice, "langflow", is_pre_release)
if notice:
notices.append(notice)
package_names.append("langflow")
package_name = "Langflow"
except ImportError:
from importlib import metadata
langflow_version = None
version = metadata.version("langflow-base")
word = "Langflow Base"
# Attempt to handle langflow-base similarly
if langflow_version is None: # This means langflow.version was not imported
try:
from importlib import metadata
colors = ["#6e42f5"]
langflow_base_version = metadata.version("langflow-base")
is_pre_release |= is_prerelease(
langflow_base_version
) # Update pre-release status
notice = build_version_notice(langflow_base_version, "langflow-base")
notice = stylize_text(notice, "langflow-base", is_pre_release)
if notice:
notices.append(notice)
package_names.append("langflow-base")
package_name = "Langflow Base"
except ImportError as e:
logger.exception(e)
raise e
styled_word = ""
# Generate pip command based on the collected data
pip_command = generate_pip_command(package_names, is_pre_release)
for i, char in enumerate(word):
color = colors[i % len(colors)]
styled_word += f"[{color}]{char}[/]"
# Add pip install command to notices if any package needs an update
if notices:
notices.append(f"Run '{pip_command}' to update.")
# Title with emojis and gradient text
title = (
f"[bold]Welcome to :chains: {styled_word} v{version}[/bold]\n"
f"Access [link=http://{host}:{port}]http://{host}:{port}[/link]"
)
info_text = (
"Collaborate, and contribute at our "
"[bold][link=https://github.com/logspace-ai/langflow]GitHub Repo[/link][/bold] :rocket:"
styled_notices = [f"[bold]{notice}[/bold]" for notice in notices if notice]
styled_package_name = stylize_text(
package_name, package_name, any("pre-release" in notice for notice in notices)
)
# Create a panel with the title and the info text, and a border around it
panel = Panel(f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False)
title = f"[bold]Welcome to :chains: {styled_package_name}[/bold]\n"
info_text = "Collaborate, and contribute at our [bold][link=https://github.com/logspace-ai/langflow]GitHub Repo[/link][/bold] :rocket:"
access_link = f"Access [link=http://{host}:{port}]http://{host}:{port}[/link]"
# Print the banner with a separator line before and after
panel_content = "\n\n".join([title, *styled_notices, info_text, access_link])
panel = Panel(panel_content, box=box.ROUNDED, border_style="blue", expand=False)
rprint(panel)
@ -314,8 +459,12 @@ def run_langflow(host, port, log_level, options, app):
@app.command()
def superuser(
username: str = typer.Option(..., prompt=True, help="Username for the superuser."),
password: str = typer.Option(..., prompt=True, hide_input=True, help="Password for the superuser."),
log_level: str = typer.Option("error", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
password: str = typer.Option(
..., prompt=True, hide_input=True, help="Password for the superuser."
),
log_level: str = typer.Option(
"error", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"
),
):
"""
Create a superuser.

View file

@ -86,7 +86,7 @@ class ChatComponent(CustomComponent):
input_value: Optional[Union[str, Record]] = None,
session_id: Optional[str] = None,
return_record: Optional[bool] = False,
record_template: Optional[str] = "Text: {text}\nData: {data}",
record_template: str = "Text: {text}\nData: {data}",
) -> Union[Text, Record]:
input_value_record: Optional[Record] = None
if return_record:

View file

@ -1,4 +1,4 @@
from typing import Optional, Union
from typing import Optional
from langflow.field_typing import Text
from langflow.helpers.record import records_to_text
@ -27,7 +27,7 @@ class TextComponent(CustomComponent):
def build(
self,
input_value: Optional[Union[Text, Record]] = "",
input_value: Optional[Text] = "",
record_template: Optional[str] = "{text}",
) -> Text:
if isinstance(input_value, Record):

View file

@ -94,14 +94,14 @@ class APIRequest(CustomComponent):
self,
method: str,
urls: List[str],
headers: Optional[Record] = None,
_headers: Optional[Record] = None,
body: Optional[Record] = None,
timeout: int = 5,
) -> List[Record]:
if headers is None:
if _headers is None:
headers = {}
else:
headers = headers.data
headers = _headers.data
bodies = []
if body:

View file

@ -44,6 +44,7 @@ class CreateRecordComponent(CustomComponent):
)
build_config[field.name] = field.to_dict()
build_config["number_of_fields"]["value"] = field_value_int
return build_config
def build_config(self):

View file

@ -890,9 +890,9 @@ class Graph:
raise ValueError(f"Source vertex {edge['source']} not found")
if target is None:
raise ValueError(f"Target vertex {edge['target']} not found")
edge = ContractEdge(source, target, edge)
new_edge = ContractEdge(source, target, edge)
edges.add(edge)
edges.add(new_edge)
return list(edges)

View file

@ -85,7 +85,7 @@ class RunnableVerticesManager:
for v_id in set(next_runnable_vertices): # Use set to avoid duplicates
self.update_vertex_run_state(v_id, is_runnable=False)
self.remove_from_predecessors(v_id)
await set_cache_coro(data=graph, lock=lock)
await set_cache_coro(data=graph, lock=lock) # type: ignore
return next_runnable_vertices
@staticmethod

View file

@ -4,6 +4,7 @@ import inspect
import types
from enum import Enum
from typing import TYPE_CHECKING, Any, AsyncIterator, Callable, Dict, Iterator, List, Optional
import os
from loguru import logger
@ -305,7 +306,7 @@ class Vertex:
if file_path := field.get("file_path"):
storage_service = get_storage_service()
try:
flow_id, file_name = file_path.split("/")
flow_id, file_name = os.path.split(file_path)
full_path = storage_service.build_full_path(flow_id, file_name)
except ValueError as e:
if "too many values to unpack" in str(e):

View file

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Callable, Coroutine, List, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, Callable, Coroutine, List, Optional, Tuple, Type, Union, cast
from pydantic.v1 import BaseModel, Field, create_model
from sqlmodel import select
@ -63,12 +63,14 @@ def find_flow(flow_name: str, user_id: str) -> Optional[str]:
async def run_flow(
inputs: Union[dict, List[dict]] = None,
inputs: Optional[Union[dict, List[dict]]] = None,
tweaks: Optional[dict] = None,
flow_id: Optional[str] = None,
flow_name: Optional[str] = None,
user_id: Optional[str] = None,
) -> Any:
if user_id is None:
raise ValueError("Session is invalid")
graph = await load_flow(user_id, flow_id, flow_name, tweaks)
if inputs is None:
@ -77,7 +79,7 @@ async def run_flow(
inputs_components = []
types = []
for input_dict in inputs:
inputs_list.append({INPUT_FIELD_NAME: input_dict.get("input_value")})
inputs_list.append({INPUT_FIELD_NAME: cast(str, input_dict.get("input_value"))})
inputs_components.append(input_dict.get("components", []))
types.append(input_dict.get("type", []))
@ -138,12 +140,12 @@ async def flow_function({func_args}):
"""
compiled_func = compile(func_body, "<string>", "exec")
local_scope = {}
local_scope: dict = {}
exec(compiled_func, globals(), local_scope)
return local_scope["flow_function"]
def build_function_and_schema(flow_record: Record, graph: "Graph") -> Tuple[Callable, BaseModel]:
def build_function_and_schema(flow_record: Record, graph: "Graph") -> Tuple[Callable, Type[BaseModel]]:
"""
Builds a dynamic function and schema for a given flow.
@ -178,7 +180,7 @@ def get_flow_inputs(graph: "Graph") -> List["Vertex"]:
return inputs
def build_schema_from_inputs(name: str, inputs: List[tuple[str, str, str]]) -> BaseModel:
def build_schema_from_inputs(name: str, inputs: List["Vertex"]) -> Type[BaseModel]:
"""
Builds a schema from the given inputs.
@ -196,4 +198,4 @@ def build_schema_from_inputs(name: str, inputs: List[tuple[str, str, str]]) -> B
field_name = input_.display_name.lower().replace(" ", "_")
description = input_.description
fields[field_name] = (str, Field(default="", description=description))
return create_model(name, **fields)
return create_model(name, **fields) # type: ignore

View file

@ -1,3 +1,4 @@
from typing import Union
from langchain_core.documents import Document
from langflow.schema import Record
@ -16,7 +17,7 @@ def docs_to_records(documents: list[Document]) -> list[Record]:
return [Record.from_document(document) for document in documents]
def records_to_text(template: str, records: list[Record]) -> str:
def records_to_text(template: str, records: Union[Record, list[Record]]) -> str:
"""
Converts a list of Records to a list of texts.

View file

@ -0,0 +1,888 @@
{
"id": "c091a57f-43a7-4a5e-b352-035ae8d8379c",
"data": {
"nodes": [
{
"id": "Prompt-uxBqP",
"type": "genericNode",
"position": {
"x": 53.588791333410654,
"y": -107.07318910019967
},
"data": {
"type": "Prompt",
"node": {
"template": {
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.interface.custom.custom_component import CustomComponent\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"template": {
"type": "prompt",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"value": "Answer the user as if you were a pirate.\n\nUser: {user_input}\n\nAnswer: ",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "template",
"display_name": "Template",
"advanced": false,
"input_types": [
"Text"
],
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false
},
"_type": "CustomComponent",
"user_input": {
"field_type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "user_input",
"display_name": "user_input",
"advanced": false,
"input_types": [
"Document",
"BaseOutputParser",
"Record",
"Text"
],
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false,
"type": "str"
}
},
"description": "Create a prompt template with dynamic variables.",
"icon": "prompts",
"is_input": null,
"is_output": null,
"is_composition": null,
"base_classes": [
"object",
"str",
"Text"
],
"name": "",
"display_name": "Prompt",
"documentation": "",
"custom_fields": {
"template": [
"user_input"
]
},
"output_types": [
"Text"
],
"full_path": null,
"field_formatters": {},
"frozen": false,
"field_order": [],
"beta": false,
"error": null
},
"id": "Prompt-uxBqP",
"description": "Create a prompt template with dynamic variables.",
"display_name": "Prompt"
},
"selected": true,
"width": 384,
"height": 383,
"dragging": false,
"positionAbsolute": {
"x": 53.588791333410654,
"y": -107.07318910019967
}
},
{
"id": "OpenAIModel-k39HS",
"type": "genericNode",
"position": {
"x": 634.8148772766217,
"y": 27.035057029045305
},
"data": {
"type": "OpenAIModel",
"node": {
"template": {
"input_value": {
"type": "str",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "input_value",
"display_name": "Input",
"advanced": false,
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": [\n \"gpt-4-turbo-preview\",\n \"gpt-3.5-turbo\",\n \"gpt-4-0125-preview\",\n \"gpt-4-1106-preview\",\n \"gpt-4-vision-preview\",\n \"gpt-3.5-turbo-0125\",\n \"gpt-3.5-turbo-1106\",\n ],\n \"value\": \"gpt-4-turbo-preview\",\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float,\n model_name: str,\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n output = ChatOpenAI(\n max_tokens=max_tokens,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=openai_api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"max_tokens": {
"type": "int",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"value": 256,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "max_tokens",
"display_name": "Max Tokens",
"advanced": true,
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false
},
"model_kwargs": {
"type": "NestedDict",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"value": {},
"fileTypes": [],
"file_path": "",
"password": false,
"name": "model_kwargs",
"display_name": "Model Kwargs",
"advanced": true,
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false
},
"model_name": {
"type": "str",
"required": true,
"placeholder": "",
"list": true,
"show": true,
"multiline": false,
"value": "gpt-3.5-turbo",
"fileTypes": [],
"file_path": "",
"password": false,
"options": [
"gpt-4-turbo-preview",
"gpt-3.5-turbo",
"gpt-4-0125-preview",
"gpt-4-1106-preview",
"gpt-4-vision-preview",
"gpt-3.5-turbo-0125",
"gpt-3.5-turbo-1106"
],
"name": "model_name",
"display_name": "Model Name",
"advanced": false,
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"openai_api_base": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "openai_api_base",
"display_name": "OpenAI API Base",
"advanced": true,
"dynamic": false,
"info": "The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\n\nYou can change this to use other APIs like JinaChat, LocalAI and Prem.",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"openai_api_key": {
"type": "str",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"fileTypes": [],
"file_path": "",
"password": true,
"name": "openai_api_key",
"display_name": "OpenAI API Key",
"advanced": false,
"dynamic": false,
"info": "The OpenAI API Key to use for the OpenAI model.",
"load_from_db": true,
"title_case": false,
"input_types": [
"Text"
],
"value": ""
},
"stream": {
"type": "bool",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"value": true,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "stream",
"display_name": "Stream",
"advanced": true,
"dynamic": false,
"info": "Stream the response from the model. Streaming works only in Chat.",
"load_from_db": false,
"title_case": false
},
"system_message": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "system_message",
"display_name": "System Message",
"advanced": true,
"dynamic": false,
"info": "System message to pass to the model.",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"temperature": {
"type": "float",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"value": 0.1,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "temperature",
"display_name": "Temperature",
"advanced": false,
"dynamic": false,
"info": "",
"rangeSpec": {
"step_type": "float",
"min": -1,
"max": 1,
"step": 0.1
},
"load_from_db": false,
"title_case": false
},
"_type": "CustomComponent"
},
"description": "Generates text using OpenAI LLMs.",
"icon": "OpenAI",
"base_classes": [
"object",
"Text",
"str"
],
"display_name": "OpenAI",
"documentation": "",
"custom_fields": {
"input_value": null,
"openai_api_key": null,
"temperature": null,
"model_name": null,
"max_tokens": null,
"model_kwargs": null,
"openai_api_base": null,
"stream": null,
"system_message": null
},
"output_types": [
"Text"
],
"field_formatters": {},
"frozen": false,
"field_order": [
"max_tokens",
"model_kwargs",
"model_name",
"openai_api_base",
"openai_api_key",
"temperature",
"input_value",
"system_message",
"stream"
],
"beta": false
},
"id": "OpenAIModel-k39HS",
"description": "Generates text using OpenAI LLMs.",
"display_name": "OpenAI"
},
"selected": false,
"width": 384,
"height": 563,
"positionAbsolute": {
"x": 634.8148772766217,
"y": 27.035057029045305
},
"dragging": false
},
{
"id": "ChatOutput-njtka",
"type": "genericNode",
"position": {
"x": 1193.250417197867,
"y": 71.88476890163852
},
"data": {
"type": "ChatOutput",
"node": {
"template": {
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatOutput(ChatComponent):\n display_name = \"Chat Output\"\n description = \"Display a chat message in the Interaction Panel.\"\n icon = \"ChatOutput\"\n\n def build(\n self,\n sender: Optional[str] = \"Machine\",\n sender_name: Optional[str] = \"AI\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n record_template: Optional[str] = \"{text}\",\n ) -> Union[Text, Record]:\n return super().build(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n record_template=record_template,\n )\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"input_value": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "input_value",
"display_name": "Message",
"advanced": false,
"input_types": [
"Text"
],
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false
},
"record_template": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "{text}",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "record_template",
"display_name": "Record Template",
"advanced": true,
"dynamic": false,
"info": "In case of Message being a Record, this template will be used to convert it to text.",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"return_record": {
"type": "bool",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"value": false,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "return_record",
"display_name": "Return Record",
"advanced": true,
"dynamic": false,
"info": "Return the message as a record containing the sender, sender_name, and session_id.",
"load_from_db": false,
"title_case": false
},
"sender": {
"type": "str",
"required": false,
"placeholder": "",
"list": true,
"show": true,
"multiline": false,
"value": "Machine",
"fileTypes": [],
"file_path": "",
"password": false,
"options": [
"Machine",
"User"
],
"name": "sender",
"display_name": "Sender Type",
"advanced": true,
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"sender_name": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"value": "AI",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "sender_name",
"display_name": "Sender Name",
"advanced": false,
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"session_id": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "session_id",
"display_name": "Session ID",
"advanced": true,
"dynamic": false,
"info": "If provided, the message will be stored in the memory.",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"_type": "CustomComponent"
},
"description": "Display a chat message in the Interaction Panel.",
"icon": "ChatOutput",
"base_classes": [
"Record",
"Text",
"str",
"object"
],
"display_name": "Chat Output",
"documentation": "",
"custom_fields": {
"sender": null,
"sender_name": null,
"input_value": null,
"session_id": null,
"return_record": null,
"record_template": null
},
"output_types": [
"Text",
"Record"
],
"field_formatters": {},
"frozen": false,
"field_order": [],
"beta": false
},
"id": "ChatOutput-njtka"
},
"selected": false,
"width": 384,
"height": 383,
"positionAbsolute": {
"x": 1193.250417197867,
"y": 71.88476890163852
},
"dragging": false
},
{
"id": "ChatInput-P3fgL",
"type": "genericNode",
"position": {
"x": -495.2223093083827,
"y": -232.56998443685862
},
"data": {
"type": "ChatInput",
"node": {
"template": {
"code": {
"type": "code",
"required": true,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional, Union\n\nfrom langflow.base.io.chat import ChatComponent\nfrom langflow.field_typing import Text\nfrom langflow.schema import Record\n\n\nclass ChatInput(ChatComponent):\n display_name = \"Chat Input\"\n description = \"Get chat inputs from the Interaction Panel.\"\n icon = \"ChatInput\"\n\n def build_config(self):\n build_config = super().build_config()\n build_config[\"input_value\"] = {\n \"input_types\": [],\n \"display_name\": \"Message\",\n \"multiline\": True,\n }\n\n return build_config\n\n def build(\n self,\n sender: Optional[str] = \"User\",\n sender_name: Optional[str] = \"User\",\n input_value: Optional[str] = None,\n session_id: Optional[str] = None,\n return_record: Optional[bool] = False,\n ) -> Union[Text, Record]:\n return super().build(\n sender=sender,\n sender_name=sender_name,\n input_value=input_value,\n session_id=session_id,\n return_record=return_record,\n )\n",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "code",
"advanced": true,
"dynamic": true,
"info": "",
"load_from_db": false,
"title_case": false
},
"input_value": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": true,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "input_value",
"display_name": "Message",
"advanced": false,
"input_types": [],
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false,
"value": "hi"
},
"return_record": {
"type": "bool",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"value": false,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "return_record",
"display_name": "Return Record",
"advanced": true,
"dynamic": false,
"info": "Return the message as a record containing the sender, sender_name, and session_id.",
"load_from_db": false,
"title_case": false
},
"sender": {
"type": "str",
"required": false,
"placeholder": "",
"list": true,
"show": true,
"multiline": false,
"value": "User",
"fileTypes": [],
"file_path": "",
"password": false,
"options": [
"Machine",
"User"
],
"name": "sender",
"display_name": "Sender Type",
"advanced": true,
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"sender_name": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"value": "User",
"fileTypes": [],
"file_path": "",
"password": false,
"name": "sender_name",
"display_name": "Sender Name",
"advanced": false,
"dynamic": false,
"info": "",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"session_id": {
"type": "str",
"required": false,
"placeholder": "",
"list": false,
"show": true,
"multiline": false,
"fileTypes": [],
"file_path": "",
"password": false,
"name": "session_id",
"display_name": "Session ID",
"advanced": true,
"dynamic": false,
"info": "If provided, the message will be stored in the memory.",
"load_from_db": false,
"title_case": false,
"input_types": [
"Text"
]
},
"_type": "CustomComponent"
},
"description": "Get chat inputs from the Interaction Panel.",
"icon": "ChatInput",
"base_classes": [
"object",
"Record",
"str",
"Text"
],
"display_name": "Chat Input",
"documentation": "",
"custom_fields": {
"sender": null,
"sender_name": null,
"input_value": null,
"session_id": null,
"return_record": null
},
"output_types": [
"Text",
"Record"
],
"field_formatters": {},
"frozen": false,
"field_order": [],
"beta": false
},
"id": "ChatInput-P3fgL"
},
"selected": false,
"width": 384,
"height": 375,
"positionAbsolute": {
"x": -495.2223093083827,
"y": -232.56998443685862
},
"dragging": false
}
],
"edges": [
{
"source": "OpenAIModel-k39HS",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-k39HSœ}",
"target": "ChatOutput-njtka",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-njtkaœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"data": {
"targetHandle": {
"fieldName": "input_value",
"id": "ChatOutput-njtka",
"inputTypes": [
"Text"
],
"type": "str"
},
"sourceHandle": {
"baseClasses": [
"object",
"Text",
"str"
],
"dataType": "OpenAIModel",
"id": "OpenAIModel-k39HS"
}
},
"style": {
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-k39HS{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-k39HSœ}-ChatOutput-njtka{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-njtkaœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
},
{
"source": "Prompt-uxBqP",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-uxBqPœ}",
"target": "OpenAIModel-k39HS",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-k39HSœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}",
"data": {
"targetHandle": {
"fieldName": "input_value",
"id": "OpenAIModel-k39HS",
"inputTypes": [
"Text"
],
"type": "str"
},
"sourceHandle": {
"baseClasses": [
"object",
"str",
"Text"
],
"dataType": "Prompt",
"id": "Prompt-uxBqP"
}
},
"style": {
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-uxBqP{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-uxBqPœ}-OpenAIModel-k39HS{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-k39HSœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}"
},
{
"source": "ChatInput-P3fgL",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œRecordœ,œstrœ,œTextœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-P3fgLœ}",
"target": "Prompt-uxBqP",
"targetHandle": "{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-uxBqPœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
"data": {
"targetHandle": {
"fieldName": "user_input",
"id": "Prompt-uxBqP",
"inputTypes": [
"Document",
"BaseOutputParser",
"Record",
"Text"
],
"type": "str"
},
"sourceHandle": {
"baseClasses": [
"object",
"Record",
"str",
"Text"
],
"dataType": "ChatInput",
"id": "ChatInput-P3fgL"
}
},
"style": {
"stroke": "#555"
},
"className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-P3fgL{œbaseClassesœ:[œobjectœ,œRecordœ,œstrœ,œTextœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-P3fgLœ}-Prompt-uxBqP{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-uxBqPœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}"
}
],
"viewport": {
"x": 260.58251815500563,
"y": 318.2261172111936,
"zoom": 0.43514115784696294
}
},
"description": "This flow will get you experimenting with the basics of the UI, the Chat and the Prompt component. \n\nTry changing the Template in it to see how the model behaves. \nYou can change it to this and a Text Input into the `type_of_person` variable : \"Answer the user as if you were a pirate.\n\nUser: {user_input}\n\nAnswer: \" ",
"name": "Basic Prompting (Hello, world!)",
"last_tested_version": "1.0.0a4",
"is_component": false
}

View file

@ -1 +1 @@
from langflow.processing.load import load_flow_from_json # noqa: F401
from langflow.processing.load import load_flow_from_json, run_flow_from_json # noqa: F401

View file

@ -3,7 +3,7 @@ from pathlib import Path
from typing import Optional
from urllib.parse import urlencode
import nest_asyncio
import nest_asyncio # type: ignore
import socketio # type: ignore
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware

View file

@ -13,7 +13,7 @@ class Record(BaseModel):
data (dict, optional): Additional data associated with the record.
"""
text_key: Optional[str] = "text"
text_key: str = "text"
data: dict = {}
default_value: Optional[str] = ""

View file

@ -1,24 +1,45 @@
import os
import logging
from gunicorn import glogging
from gunicorn.app.base import BaseApplication # type: ignore
from uvicorn.workers import UvicornWorker
from langflow.utils.logger import InterceptHandler # type: ignore
class LangflowUvicornWorker(UvicornWorker):
CONFIG_KWARGS = {"loop": "asyncio"}
class Logger(glogging.Logger):
"""Implements and overrides the gunicorn logging interface.
This class inherits from the standard gunicorn logger and overrides it by
replacing the handlers with `InterceptHandler` in order to route the
gunicorn logs to loguru.
"""
def __init__(self, cfg):
super().__init__(cfg)
logging.getLogger("gunicorn.error").handlers = [InterceptHandler()]
logging.getLogger("gunicorn.access").handlers = [InterceptHandler()]
class LangflowApplication(BaseApplication):
def __init__(self, app, options=None):
self.options = options or {}
self.options["worker_class"] = "langflow.server.LangflowUvicornWorker"
self.options["loglevel"] = os.getenv("LANGFLOW_LOG_LEVEL", "error").lower()
self.options["logger_class"] = Logger
self.application = app
super().__init__()
def load_config(self):
config = {key: value for key, value in self.options.items() if key in self.cfg.settings and value is not None}
config = {
key: value
for key, value in self.options.items()
if key in self.cfg.settings and value is not None
}
for key, value in config.items():
self.cfg.set(key.lower(), value)

View file

@ -1,22 +1,22 @@
from datetime import datetime
import time
from datetime import datetime
from pathlib import Path
from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import command, util
from alembic.config import Config
from loguru import logger
from sqlalchemy import inspect
from sqlalchemy.exc import OperationalError
from sqlmodel import Session, SQLModel, create_engine, select, text
from langflow.services.base import Service
from langflow.services.database import models # noqa
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults
from langflow.services.deps import get_settings_service
from langflow.services.utils import teardown_superuser
from loguru import logger
from sqlalchemy import inspect
from sqlalchemy.exc import OperationalError
from sqlmodel import Session, SQLModel, create_engine, select, text
if TYPE_CHECKING:
from sqlalchemy.engine import Engine
@ -37,7 +37,10 @@ class DatabaseService(Service):
def _create_engine(self) -> "Engine":
"""Create the engine for the database."""
settings_service = get_settings_service()
if settings_service.settings.DATABASE_URL and settings_service.settings.DATABASE_URL.startswith("sqlite"):
if (
settings_service.settings.DATABASE_URL
and settings_service.settings.DATABASE_URL.startswith("sqlite")
):
connect_args = {"check_same_thread": False}
else:
connect_args = {}
@ -49,7 +52,9 @@ class DatabaseService(Service):
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None: # If an exception has been raised
logger.error(f"Session rollback because of exception: {exc_type.__name__} {exc_value}")
logger.error(
f"Session rollback because of exception: {exc_type.__name__} {exc_value}"
)
self._session.rollback()
else:
self._session.commit()
@ -66,7 +71,9 @@ class DatabaseService(Service):
settings_service = get_settings_service()
if settings_service.auth_settings.AUTO_LOGIN:
with Session(self.engine) as session:
flows = session.exec(select(models.Flow).where(models.Flow.user_id is None)).all()
flows = session.exec(
select(models.Flow).where(models.Flow.user_id is None)
).all()
if flows:
logger.debug("Migrating flows to default superuser")
username = settings_service.auth_settings.SUPERUSER
@ -96,14 +103,16 @@ class DatabaseService(Service):
expected_columns = list(model.model_fields.keys())
try:
available_columns = [col["name"] for col in inspector.get_columns(table)]
available_columns = [
col["name"] for col in inspector.get_columns(table)
]
except sa.exc.NoSuchTableError:
logger.error(f"Missing table: {table}")
logger.debug(f"Missing table: {table}")
return False
for column in expected_columns:
if column not in available_columns:
logger.error(f"Missing column: {column} in table {table}")
logger.debug(f"Missing column: {column} in table {table}")
return False
for table in legacy_tables:
@ -160,7 +169,9 @@ class DatabaseService(Service):
buffer.write(f"{datetime.now().isoformat()}: Checking migrations\n")
command.check(alembic_cfg)
except Exception as exc:
if isinstance(exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)):
if isinstance(
exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)
):
command.upgrade(alembic_cfg, "head")
time.sleep(3)
@ -197,7 +208,10 @@ class DatabaseService(Service):
# We will check that all models are in the database
# and that the database is up to date with all columns
sql_models = [models.Flow, models.User, models.ApiKey]
return [TableResults(sql_model.__tablename__, self.check_table(sql_model)) for sql_model in sql_models]
return [
TableResults(sql_model.__tablename__, self.check_table(sql_model))
for sql_model in sql_models
]
def check_table(self, model):
results = []
@ -206,7 +220,9 @@ class DatabaseService(Service):
expected_columns = list(model.__fields__.keys())
available_columns = []
try:
available_columns = [col["name"] for col in inspector.get_columns(table_name)]
available_columns = [
col["name"] for col in inspector.get_columns(table_name)
]
results.append(Result(name=table_name, type="table", success=True))
except sa.exc.NoSuchTableError:
logger.error(f"Missing table: {table_name}")
@ -237,7 +253,9 @@ class DatabaseService(Service):
try:
table.create(self.engine, checkfirst=True)
except OperationalError as oe:
logger.warning(f"Table {table} already exists, skipping. Exception: {oe}")
logger.warning(
f"Table {table} already exists, skipping. Exception: {oe}"
)
except Exception as exc:
logger.error(f"Error creating table {table}: {exc}")
raise RuntimeError(f"Error creating table {table}") from exc
@ -249,7 +267,9 @@ class DatabaseService(Service):
if table not in table_names:
logger.error("Something went wrong creating the database and tables.")
logger.error("Please check your database settings.")
raise RuntimeError("Something went wrong creating the database and tables.")
raise RuntimeError(
"Something went wrong creating the database and tables."
)
logger.debug("Database and tables created successfully")

View file

@ -128,7 +128,7 @@ class MonitorService(Service):
if conditions:
query += " WHERE " + " AND ".join(conditions)
if order_by:
if order_by and order:
# Make sure the order is from newest to oldest
query += f" ORDER BY {order_by} {order.upper()}"

View file

@ -30,8 +30,8 @@ class StateService(Service):
class InMemoryStateService(StateService):
def __init__(self, settings_service: SettingsService):
self.settings_service = settings_service
self.states = {}
self.observers = defaultdict(list)
self.states: dict = {}
self.observers: dict = defaultdict(list)
self.lock = Lock()
def append_state(self, key, new_state, run_id: str):

View file

@ -1,3 +1,4 @@
import logging
import os
from pathlib import Path
from typing import Optional
@ -25,7 +26,10 @@ def patching(record):
def configure(log_level: Optional[str] = None, log_file: Optional[Path] = None):
if os.getenv("LANGFLOW_LOG_LEVEL", "").upper() in VALID_LOG_LEVELS and log_level is None:
if (
os.getenv("LANGFLOW_LOG_LEVEL", "").upper() in VALID_LOG_LEVELS
and log_level is None
):
log_level = os.getenv("LANGFLOW_LOG_LEVEL")
if log_level is None:
log_level = "ERROR"
@ -67,3 +71,46 @@ def configure(log_level: Optional[str] = None, log_file: Optional[Path] = None):
logger.debug(f"Logger set up with log level: {log_level}")
if log_file:
logger.debug(f"Log file: {log_file}")
setup_uvicorn_logger()
setup_gunicorn_logger()
def setup_uvicorn_logger():
loggers = (
logging.getLogger(name)
for name in logging.root.manager.loggerDict
if name.startswith("uvicorn.")
)
for uvicorn_logger in loggers:
uvicorn_logger.handlers = []
logging.getLogger("uvicorn").handlers = [InterceptHandler()]
def setup_gunicorn_logger():
logging.getLogger("gunicorn.error").handlers = [InterceptHandler()]
logging.getLogger("gunicorn.access").handlers = [InterceptHandler()]
class InterceptHandler(logging.Handler):
"""
Default handler from examples in loguru documentaion.
See https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging
"""
def emit(self, record):
# Get corresponding Loguru level if it exists
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(
level, record.getMessage()
)

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "langflow-base"
version = "0.0.18"
version = "0.0.21"
description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"]
maintainers = [
@ -27,19 +27,19 @@ langflow-base = "langflow.__main__:main"
[tool.poetry.dependencies]
python = ">=3.10,<3.12"
fastapi = "^0.109.0"
fastapi = "^0.110.1"
httpx = "*"
uvicorn = "^0.27.0"
uvicorn = "^0.29.0"
gunicorn = "^21.2.0"
langchain = "~0.1.0"
sqlmodel = "^0.0.14"
langchain = "~0.1.14"
sqlmodel = "^0.0.16"
loguru = "^0.7.1"
rich = "^13.7.0"
langchain-experimental = "*"
pydantic = "^2.5.0"
pydantic-settings = "^2.1.0"
websockets = "*"
typer = "^0.9.0"
typer = "^0.12.0"
cachetools = "^5.3.1"
platformdirs = "^4.2.0"
python-multipart = "^0.0.7"
@ -57,37 +57,33 @@ python-socketio = "^5.11.0"
python-docx = "^1.1.0"
jq = { version = "^1.7.0", markers = "sys_platform != 'win32'" }
pypdf = "^4.1.0"
chromadb = "^0.4.24"
langchain-anthropic = "^0.1.4"
langchain-astradb = "^0.1.0"
nest-asyncio = "^1.6.0"
emoji = "^2.11.0"
cryptography = "^42.0.5"
langchain-openai = "^0.1.1"
[tool.poetry.group.dev.dependencies]
pytest-asyncio = "^0.21.1"
pytest-asyncio = "^0.23.1"
types-redis = "^4.6.0.5"
ipykernel = "^6.26.0"
mypy = "^1.7.1"
ruff = "^0.1.5"
ruff = "^0.3.5"
httpx = "*"
pytest = "^7.4.2"
pytest = "^8.1.1"
types-requests = "^2.31.0"
requests = "^2.31.0"
pytest-cov = "^4.1.0"
pandas-stubs = "^2.0.0.230412"
types-pillow = "^9.5.0.2"
pytest-cov = "^5.0.0"
pandas-stubs = "^2.2.1.230412"
types-pillow = "^10.2.0.0"
types-pyyaml = "^6.0.12.8"
types-python-jose = "^3.3.4.8"
types-passlib = "^1.7.7.13"
locust = "^2.16.1"
pytest-mock = "^3.11.1"
pytest-xdist = "^3.3.1"
locust = "^2.24.1"
pytest-mock = "^3.14.0"
pytest-xdist = "^3.5.0"
types-pywin32 = "^306.0.0.4"
types-google-cloud-ndb = "^2.2.0.0"
pytest-sugar = "^0.9.7"
types-google-cloud-ndb = "^2.3.0.0"
pytest-sugar = "^1.0.0"
[tool.poetry.extras]

View file

@ -43,7 +43,7 @@ export default function UndrawCardComponent({
}}
/>
);
case "Basic Prompting (Ahoy World!)":
case "Basic Prompting (Hello, world!)":
return (
<BasicPrompt
style={{

View file

@ -34,11 +34,15 @@ export default function NewFlowModal({
{/* {examples.map((example, idx) => {
return <UndrawCardComponent key={idx} flow={example} />;
})} */}
{examples.find((e) => e.name == "Basic Prompting (Ahoy World!)") && (
{examples.find(
(e) => e.name == "Basic Prompting (Hello, world!)"
) && (
<UndrawCardComponent
key={1}
flow={
examples.find((e) => e.name == "Basic Prompting (Ahoy World!)")!
examples.find(
(e) => e.name == "Basic Prompting (Hello, world!)"
)!
}
/>
)}

View file

@ -37,11 +37,11 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
)}
<a
target={"_blank"}
href="https://logspace.ai/"
href="https://medium.com/logspace/langflow-datastax-better-together-1b7462cebc4d"
className="logspace-page-icon"
>
{version && <div className="mt-1">⛓️ Langflow v{version}</div>}
<div className={version ? "mt-2" : "mt-1"}>Created by Logspace</div>
{version && <div className="mt-1">Langflow 🤝 DataStax</div>}
<div className={version ? "mt-2" : "mt-1"}>⛓️ v{version}</div>
</a>
</div>
</>

View file

@ -80,6 +80,15 @@ export type InputGlobalComponentType = {
editNode?: boolean;
};
export type InputGlobalComponentType = {
disabled: boolean;
onChange: (value: string) => void;
setDb: (value: boolean) => void;
name: string;
data: NodeDataType;
editNode?: boolean;
};
export type KeyPairListComponentType = {
value: any;
onChange: (value: Object[]) => void;

View file

@ -1,4 +1,5 @@
import { debounce } from "lodash";
import { SAVE_DEBOUNCE_TIME } from "../constants/constants";
import { postCustomComponentUpdate } from "../controllers/API";
import { ResponseErrorTypeAPI } from "../types/api";
import { NodeDataType } from "../types/flow";
@ -38,4 +39,7 @@ export const handleUpdateValues = async (name: string, data: NodeDataType) => {
}
};
export const debouncedHandleUpdateValues = debounce(handleUpdateValues, 200);
export const debouncedHandleUpdateValues = debounce(
handleUpdateValues,
SAVE_DEBOUNCE_TIME
);

View file

@ -1,5 +1,8 @@
import { test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
// await page.waitForTimeout(16000);
// test.setTimeout(140000);
});
test.describe("Auto_login tests", () => {
test("auto_login sign in", async ({ page }) => {
await page.goto("http:localhost:3000/");

View file

@ -1,5 +1,8 @@
import { expect, test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
await page.waitForTimeout(2000);
test.setTimeout(120000);
});
test("CodeAreaModalComponent", async ({ page }) => {
await page.goto("http:localhost:3000/");
await page.waitForTimeout(2000);

View file

@ -1,6 +1,9 @@
import { expect, test } from "@playwright/test";
import { readFileSync } from "fs";
test.beforeEach(async ({ page }) => {
// await page.waitForTimeout(3000);
// test.setTimeout(120000);
});
test.describe("drag and drop test", () => {
/// <reference lib="dom"/>
test("drop collection", async ({ page }) => {

View file

@ -1,4 +1,8 @@
import { Page, test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
// await page.waitForTimeout(6000);
// test.setTimeout(120000);
});
test.describe("Flow Page tests", () => {
async function goToFlowPage(page: Page) {

View file

@ -1,5 +1,8 @@
import { expect, test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
// await page.waitForTimeout(8000);
// test.setTimeout(120000);
});
test("InputComponent", async ({ page }) => {
await page.goto("http:localhost:3000/");
await page.waitForTimeout(2000);

View file

@ -1,5 +1,8 @@
import { expect, test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
// await page.waitForTimeout(20000);
// test.setTimeout(120000);
});
test("KeypairListComponent", async ({ page }) => {
await page.goto("http:localhost:3000/");
await page.waitForTimeout(2000);

View file

@ -1,6 +1,5 @@
import { expect, test } from "@playwright/test";
import uaParser from "ua-parser-js";
test("LangflowShortcuts", async ({ page }) => {
const getUA = await page.evaluate(() => navigator.userAgent);
const userAgentInfo = uaParser(getUA);

View file

@ -1,5 +1,8 @@
import { expect, test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
// await page.waitForTimeout(12000);
// test.setTimeout(120000);
});
test("NestedComponent", async ({ page }) => {
await page.goto("http:localhost:3000/");
await page.waitForTimeout(2000);

View file

@ -1,5 +1,8 @@
import { expect, test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
// await page.waitForTimeout(13000);
// test.setTimeout(120000);
});
test("PromptTemplateComponent", async ({ page }) => {
await page.goto("http:localhost:3000/");
await page.waitForTimeout(2000);

View file

@ -1,6 +1,9 @@
import { Page, expect, test } from "@playwright/test";
import { readFileSync } from "fs";
test.beforeEach(async ({ page }) => {
// await page.waitForTimeout(14000);
// test.setTimeout(120000);
});
test.describe("save component tests", () => {
async function saveComponent(page: Page, pattern: RegExp, n: number) {
for (let i = 0; i < n; i++) {

View file

@ -1,5 +1,8 @@
import { expect, test } from "@playwright/test";
test.beforeEach(async ({ page }) => {
// await page.waitForTimeout(15000);
// test.setTimeout(120000);
});
test("ToggleComponent", async ({ page }) => {
await page.goto("http:localhost:3000/");
await page.waitForTimeout(2000);