Merge remote-tracking branch 'origin/dev' into db

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-06-09 06:59:21 -03:00
commit 51e4f9d109
19 changed files with 643 additions and 399 deletions

View file

@ -1,21 +1,29 @@
import multiprocessing
import sys
import time
import httpx
from multiprocess import Process, cpu_count # type: ignore
import platform
from pathlib import Path
from typing import Optional
from rich.panel import Panel
from rich import box
from rich import print as rprint
import typer
from fastapi.staticfiles import StaticFiles
from langflow.main import create_app
from langflow.settings import settings
from langflow.utils.logger import configure
from langflow.utils.logger import configure, logger
import webbrowser
app = typer.Typer()
def get_number_of_workers(workers=None):
if workers == -1:
workers = (multiprocessing.cpu_count() * 2) + 1
workers = (cpu_count() * 2) + 1
return workers
@ -80,7 +88,7 @@ def serve(
timeout: int = typer.Option(60, help="Worker timeout in seconds."),
port: int = typer.Option(7860, help="Port to listen on."),
config: str = typer.Option("config.yaml", help="Path to the configuration file."),
log_level: str = typer.Option("info", help="Logging level."),
log_level: str = typer.Option("critical", help="Logging level."),
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file."),
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
@ -88,6 +96,13 @@ def serve(
None,
help="Database URL to connect to. If not provided, a local SQLite database will be used.",
),
path: str = typer.Option(
None,
help="Path to the frontend directory containing build files. This is for development purposes only.",
),
open_browser: bool = typer.Option(
True, help="Open the browser after starting the server."
),
):
"""
Run the Langflow server.
@ -100,8 +115,11 @@ def serve(
update_settings(config, dev=dev, database_url=database_url)
app = create_app()
# get the directory of the current file
path = Path(__file__).parent
static_files_dir = path / "frontend"
if not path:
frontend_path = Path(__file__).parent
static_files_dir = frontend_path / "frontend"
else:
static_files_dir = Path(path)
app.mount(
"/",
StaticFiles(directory=static_files_dir, html=True),
@ -114,17 +132,74 @@ def serve(
"timeout": timeout,
}
if platform.system() in ["Darwin", "Windows"]:
# Run using uvicorn on MacOS and Windows
# Windows doesn't support gunicorn
# MacOS requires an env variable to be set to use gunicorn
import uvicorn
webapp_process = Process(
target=run_langflow, args=(host, port, log_level, options, app)
)
webapp_process.start()
status_code = 0
while status_code != 200:
try:
status_code = httpx.get(f"http://{host}:{port}").status_code
except Exception:
time.sleep(1)
uvicorn.run(app, host=host, port=port, log_level=log_level)
else:
from langflow.server import LangflowApplication
print_banner(host, port)
if open_browser:
webbrowser.open(f"http://{host}:{port}")
LangflowApplication(app, options).run()
def print_banner(host, port):
# console = Console()
word = "LangFlow"
colors = ["#690080", "#660099", "#4d00b3", "#3300cc", "#1a00e6", "#0000ff"]
styled_word = ""
for i, char in enumerate(word):
color = colors[i % len(colors)]
styled_word += f"[{color}]{char}[/]"
# Title with emojis and gradient text
title = (
f"[bold]Welcome to :chains: {styled_word} [/bold]\n\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:"
)
# 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
)
# Print the banner with a separator line before and after
rprint(panel)
def run_langflow(host, port, log_level, options, app):
"""
Run Langflow server on localhost
"""
try:
if platform.system() in ["Darwin", "Windows"]:
# Run using uvicorn on MacOS and Windows
# Windows doesn't support gunicorn
# MacOS requires an env variable to be set to use gunicorn
import uvicorn
uvicorn.run(app, host=host, port=port, log_level=log_level)
else:
from langflow.server import LangflowApplication
LangflowApplication(app, options).run()
except KeyboardInterrupt:
pass
except Exception as e:
logger.error(e)
sys.exit(1)
def main():

View file

@ -58,6 +58,7 @@ llms:
- Cohere
- Anthropic
- ChatAnthropic
- HuggingFaceHub
memories:
- ConversationBufferMemory
- ConversationSummaryMemory
@ -84,6 +85,7 @@ tools:
- Serper Search
- Tool
- PythonFunctionTool
- PythonFunction
- JsonSpec
- News API
- TMDB API

View file

@ -5,6 +5,7 @@ CUSTOM_NODES = {
"prompts": {"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode()},
"tools": {
"PythonFunctionTool": frontend_node.tools.PythonFunctionToolNode(),
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
"Tool": frontend_node.tools.ToolNode(),
},
"agents": {

View file

@ -24,7 +24,7 @@ from langflow.interface.importing.utils import get_function, import_by_type
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.types import get_type_list
from langflow.interface.utils import load_file_into_dict
from langflow.utils import util
from langflow.utils import util, validate
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
@ -101,6 +101,12 @@ def instantiate_tool(node_type, class_object, params):
elif node_type == "PythonFunctionTool":
params["func"] = get_function(params.get("code"))
return class_object(**params)
# For backward compatibility
elif node_type == "PythonFunction":
function_string = params["code"]
if isinstance(function_string, str):
return validate.eval_function(function_string)
raise ValueError("Function should be a string")
elif node_type.lower() == "tool":
return class_object(**params)
return class_object(**params)

View file

@ -9,10 +9,14 @@ from langchain.agents.load_tools import (
from langchain.tools.json.tool import JsonSpec
from langflow.interface.importing.utils import import_class
from langflow.interface.tools.custom import PythonFunctionTool
from langflow.interface.tools.custom import PythonFunctionTool, PythonFunction
FILE_TOOLS = {"JsonSpec": JsonSpec}
CUSTOM_TOOLS = {"Tool": Tool, "PythonFunctionTool": PythonFunctionTool}
CUSTOM_TOOLS = {
"Tool": Tool,
"PythonFunctionTool": PythonFunctionTool,
"PythonFunction": PythonFunction,
}
OTHER_TOOLS = {tool: import_class(f"langchain.tools.{tool}") for tool in tools.__all__}

View file

@ -1,4 +1,4 @@
from typing import Optional
from typing import Callable, Optional
from langflow.interface.importing.utils import get_function
from pydantic import BaseModel, validator
@ -9,6 +9,7 @@ from langchain.agents.tools import Tool
class Function(BaseModel):
code: str
function: Optional[Callable] = None
imports: Optional[str] = None
# Eval code and store the function
@ -25,6 +26,12 @@ class Function(BaseModel):
return v
def get_function(self):
"""Get the function"""
function_name = validate.extract_function_name(self.code)
return validate.create_function(self.code, function_name)
class PythonFunctionTool(Function, Tool):
"""Python function"""
@ -39,3 +46,9 @@ class PythonFunctionTool(Function, Tool):
self.code = code
self.func = get_function(self.code)
super().__init__(name=name, description=description, func=self.func)
class PythonFunction(Function):
"""Python function"""
code: str

View file

@ -45,8 +45,12 @@ def try_setting_streaming_options(langchain_object, websocket):
langchain_object.llm_chain, "llm"
):
llm = langchain_object.llm_chain.llm
if isinstance(llm, BaseLanguageModel) and hasattr(llm, "streaming"):
llm.streaming = True
if isinstance(llm, BaseLanguageModel):
if hasattr(llm, "streaming") and isinstance(llm.streaming, bool):
llm.streaming = True
elif hasattr(llm, "stream") and isinstance(llm.stream, bool):
llm.stream = True
return langchain_object

View file

@ -16,20 +16,30 @@ class LLMFrontendNode(FrontendNode):
def format_azure_field(field: TemplateField):
if field.name == "model_name":
field.show = False # Azure uses deployment_name instead of model_name.
if field.name == "openai_api_type":
elif field.name == "openai_api_type":
field.show = False
field.password = False
field.value = "azure"
if field.name == "openai_api_version":
elif field.name == "openai_api_version":
field.password = False
field.value = "2023-03-15-preview"
@staticmethod
def format_llama_field(field: TemplateField):
field.show = True
field.advanced = not field.required
@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
display_names_dict = {
"huggingfacehub_api_token": "HuggingFace Hub API Token",
}
FrontendNode.format_field(field, name)
LLMFrontendNode.format_openai_field(field)
if name and "azure" in name.lower():
LLMFrontendNode.format_azure_field(field)
if name and "llama" in name.lower():
LLMFrontendNode.format_llama_field(field)
SHOW_FIELDS = ["repo_id"]
if field.name in SHOW_FIELDS:
field.show = True
@ -46,7 +56,8 @@ class LLMFrontendNode(FrontendNode):
field.required = True
field.show = True
field.is_list = True
field.options = ["text-generation", "text2text-generation"]
field.options = ["text-generation", "text2text-generation", "summarization"]
field.value = field.options[0]
field.advanced = True
if display_name := display_names_dict.get(field.name):
@ -64,7 +75,3 @@ class LLMFrontendNode(FrontendNode):
]:
field.advanced = False
field.show = True
LLMFrontendNode.format_openai_field(field)
if name and "azure" in name.lower():
LLMFrontendNode.format_azure_field(field)

View file

@ -103,3 +103,27 @@ class PythonFunctionToolNode(FrontendNode):
def to_dict(self):
return super().to_dict()
class PythonFunctionNode(FrontendNode):
name: str = "PythonFunction"
template: Template = Template(
type_name="python_function",
fields=[
TemplateField(
field_type="code",
required=True,
placeholder="",
is_list=False,
show=True,
value=DEFAULT_PYTHON_FUNCTION,
name="code",
advanced=False,
)
],
)
description: str = "Python function to be executed."
base_classes: list[str] = ["function"]
def to_dict(self):
return super().to_dict()

View file

@ -49,7 +49,7 @@ export default function App() {
// Initialize state variable for the version
const [version, setVersion] = useState("");
useEffect(() => {
fetch("/version")
fetch("api/v1/version")
.then((res) => res.json())
.then((data) => {
setVersion(data.version);
@ -159,7 +159,7 @@ export default function App() {
className="absolute left-7 bottom-2 flex h-6 cursor-pointer flex-col items-center justify-start overflow-hidden rounded-lg bg-foreground px-2 text-center font-sans text-xs tracking-wide text-secondary transition-all duration-500 ease-in-out hover:h-12"
>
{version && <div className="mt-1">⛓️ LangFlow v{version}</div>}
<div className="mt-2">Created by Logspace</div>
<div className={version ? "mt-2" : "mt-1"}>Created by Logspace</div>
</a>
</div>
);

View file

@ -89,39 +89,47 @@ export function TabsProvider({ children }: { children: ReactNode }) {
let cookie = window.localStorage.getItem("tabsData");
if (cookie && Object.keys(templates).length > 0) {
let cookieObject: LangFlowState = JSON.parse(cookie);
cookieObject.flows.forEach((flow) => {
flow.data.edges.forEach((edge) => {
edge.className = "";
edge.style = { stroke: "#555555" };
});
flow.data.nodes.forEach((node) => {
const template = templates[node.data.type];
if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` });
try {
cookieObject.flows.forEach((flow) => {
if (!flow.data) {
return;
}
if (Object.keys(template["template"]).length > 0) {
node.data.node.base_classes = template["base_classes"];
flow.data.edges.forEach((edge) => {
if (edge.source === node.id) {
edge.sourceHandle = edge.sourceHandle
.split("|")
.slice(0, 2)
.concat(template["base_classes"])
.join("|");
}
});
node.data.node.description = template["description"];
node.data.node.template = updateTemplate(
template["template"] as unknown as APITemplateType,
node.data.node.template as APITemplateType
);
}
flow.data.edges.forEach((edge) => {
edge.className = "";
edge.style = { stroke: "#555555" };
});
flow.data.nodes.forEach((node) => {
const template = templates[node.data.type];
if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` });
return;
}
if (Object.keys(template["template"]).length > 0) {
node.data.node.base_classes = template["base_classes"];
flow.data.edges.forEach((edge) => {
if (edge.source === node.id) {
edge.sourceHandle = edge.sourceHandle
.split("|")
.slice(0, 2)
.concat(template["base_classes"])
.join("|");
}
});
node.data.node.description = template["description"];
node.data.node.template = updateTemplate(
template["template"] as unknown as APITemplateType,
node.data.node.template as APITemplateType
);
}
});
});
});
setTabIndex(cookieObject.tabIndex);
setFlows(cookieObject.flows);
setId(cookieObject.id);
setTabIndex(cookieObject.tabIndex);
setFlows(cookieObject.flows);
setId(cookieObject.id);
} catch (e) {
console.log(e);
}
}
}, [templates]);

View file

@ -14,13 +14,13 @@ export async function sendAll(data: sendAllProps) {
export async function checkCode(
code: string
): Promise<AxiosResponse<errorsTypeAPI>> {
return await axios.post("api/v1/validate/code", { code });
return await axios.post("/validate/code", { code });
}
export async function checkPrompt(
template: string
): Promise<AxiosResponse<PromptTypeAPI>> {
return await axios.post("api/v1/validate/prompt", { template });
return await axios.post("/validate/prompt", { template });
}
export async function getExamples(): Promise<FlowType[]> {
@ -37,7 +37,5 @@ export async function getExamples(): Promise<FlowType[]> {
return contentResponse.data;
});
const contents = await Promise.all(contentsPromises);
return contents;
return await Promise.all(contentsPromises);
}

View file

@ -215,19 +215,11 @@ export default function ChatModal({
}
};
ws.current = newWs;
} catch {
} catch (error) {
if (flow.id === "") {
connectWS();
} else {
setErrorData({
title: "There was an error on web connection, please: ",
list: [
"Refresh the page",
"Use a new flow tab",
"Check if the backend is up",
],
});
}
console.log(error);
}
}
@ -236,7 +228,7 @@ export default function ChatModal({
return () => {
console.log("unmount");
console.log(ws);
if (ws) {
if (ws.current) {
ws.current.close();
}
};
@ -244,8 +236,9 @@ export default function ChatModal({
useEffect(() => {
if (
ws.current.readyState === ws.current.CLOSED ||
ws.current.readyState === ws.current.CLOSING
ws.current &&
(ws.current.readyState === ws.current.CLOSED ||
ws.current.readyState === ws.current.CLOSING)
) {
connectWS();
setLockChat(false);