Fixes CLI and server (#2342)
* chore: update linting workflows to include dev branch in merge_group * Update README.md Add 1.0 banner * Update README.md * chore: update package versions in pyproject.toml files * Refactor "created_at" column type for consistency and fix cancel middleware (#2316) * chore: update linting workflows to include dev branch in merge_group * Update README.md Add 1.0 banner * Update README.md * chore: update package versions in pyproject.toml files * refactor: update "created_at" column type to use the "sa" module for consistency * Update README.md Add 1.0 banner * chore: Remove unused import in ToolCallingAgent.py * fix: adapt RequestCancelledMiddleware to handle cancelled requests * chore: Remove unused import in test_helper_components.py * refactor: Declare queue variable with explicit type in RequestCancelledMiddleware --------- Co-authored-by: Rodrigo Nader <rodrigosilvanader@gmail.com> * chore: Update AstraDB.py imports and method signature for search_documents * chore: Update package versions in pyproject.toml files * chore: Update run-name in release.yml for Langflow Release * fix: add call to _add_documents_to_vector_store in AstraDB component * chore: Fix missing parentheses in RequestCancelledMiddleware * chore: Update pydantic-settings and tenacity versions The commit updates the versions of the `pydantic-settings` and `tenacity` packages in the `poetry.lock` file. The `pydantic-settings` version is updated from 2.3.3 to 2.3.4, and the `tenacity` version is updated from 8.4.1 to 8.4.2. * Update README.md Add 1.0 banner * fix fetch data to work even with autologin true * format code * deactivate stop button until we have a better solution (#2337) * consistent auth error status code * [Fix] unhandled http errors in background tasks (#2326) * handle exceptions for background task * revert changes that is not related to this HTTP handler exception * Refactor model GoogleGenerativeAIModel (#2251) * refactor model GoogleGenerativeAIModel * adds model options --------- Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org> * Fix .env values not being honored in CLI (#2336) * chore: Update launch.json to include environment file The launch.json file was updated to include the environment file path for the "run" command in the "Python: Flask" configuration. This change ensures that the necessary environment variables are loaded when running the backend base of Langflow frontend. The previous configuration had the environment variables set in the "env" field, but it has been removed as it is redundant with the new environment file inclusion. * chore: Update dotenv import and environment variable handling This commit updates the import statement for the `dotenv` module in the `__main__.py` file. It adds the `dotenv_values` function to the import statement to enable loading environment variables from a file. Additionally, it introduces a new section of code that maps environment variables to their corresponding variables and types, allowing for more flexible and dynamic configuration. The commit also updates the `run` function to update variables based on environment variables, if they are present. This change improves the handling of environment variables and enhances the configurability of the application. * deactivate stop button until we have a better solution (#2337) * consistent auth error status code * [Fix] unhandled http errors in background tasks (#2326) * handle exceptions for background task * revert changes that is not related to this HTTP handler exception * Refactor model GoogleGenerativeAIModel (#2251) * refactor model GoogleGenerativeAIModel * adds model options --------- Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org> --------- Co-authored-by: ming luo <itestmycode@gmail.com> Co-authored-by: Ítalo Johnny <italojohnnydosanjos@gmail.com> * Update GitHub Actions workflows and dependencies (#2341) * chore: Add GitHub Actions workflow for testing documentation build * Fix server start command in GitHub Actions workflows * chore: Bump langflow and langflow-base versions * chore: Update GitHub Actions workflow for docs_test * chore: Update typing import in __main__.py * Fix user authentication and authorization issues (#2343) --------- Co-authored-by: Rodrigo Nader <rodrigosilvanader@gmail.com> Co-authored-by: anovazzi1 <otavio2204@gmail.com> Co-authored-by: ming luo <itestmycode@gmail.com> Co-authored-by: Ítalo Johnny <italojohnnydosanjos@gmail.com>
This commit is contained in:
parent
af98504e4a
commit
fff4537b1e
17 changed files with 365 additions and 272 deletions
|
|
@ -4,12 +4,12 @@ import sys
|
|||
import time
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import click
|
||||
import httpx
|
||||
import typer
|
||||
from dotenv import load_dotenv
|
||||
from dotenv import dotenv_values, load_dotenv
|
||||
from multiprocess import Process, cpu_count # type: ignore
|
||||
from packaging import version as pkg_version
|
||||
from rich import box
|
||||
|
|
@ -130,6 +130,29 @@ def run(
|
|||
|
||||
if env_file:
|
||||
load_dotenv(env_file, override=True)
|
||||
env_vars = dotenv_values(env_file)
|
||||
|
||||
# Define a mapping of environment variables to their corresponding variables and types
|
||||
env_var_mapping: dict[str, tuple[str, type | Callable[[Any], bool]]] = {
|
||||
"LANGFLOW_HOST": ("host", str),
|
||||
"LANGFLOW_PORT": ("port", int),
|
||||
"LANGFLOW_WORKERS": ("workers", int),
|
||||
"LANGFLOW_WORKER_TIMEOUT": ("timeout", int),
|
||||
"LANGFLOW_COMPONENTS_PATH": ("components_path", Path),
|
||||
"LANGFLOW_LOG_LEVEL": ("log_level", str),
|
||||
"LANGFLOW_LOG_FILE": ("log_file", Path),
|
||||
"LANGFLOW_LANGCHAIN_CACHE": ("cache", str),
|
||||
"LANGFLOW_FRONTEND_PATH": ("path", str),
|
||||
"LANGFLOW_OPEN_BROWSER": ("open_browser", lambda x: x.lower() == "true"),
|
||||
"LANGFLOW_REMOVE_API_KEYS": ("remove_api_keys", lambda x: x.lower() == "true"),
|
||||
"LANGFLOW_BACKEND_ONLY": ("backend_only", lambda x: x.lower() == "true"),
|
||||
"LANGFLOW_STORE": ("store", lambda x: x.lower() == "true"),
|
||||
}
|
||||
|
||||
# Update variables based on environment variables
|
||||
for env_var, (var_name, var_type) in env_var_mapping.items():
|
||||
if env_var in env_vars:
|
||||
locals()[var_name] = var_type(env_vars[env_var])
|
||||
|
||||
update_settings(
|
||||
dev=dev,
|
||||
|
|
|
|||
|
|
@ -116,11 +116,29 @@ async def simple_run_flow(
|
|||
return RunResponse(outputs=task_result, session_id=session_id)
|
||||
|
||||
except sa.exc.StatementError as exc:
|
||||
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
||||
if "badly formed hexadecimal UUID string" in str(exc):
|
||||
logger.error(f"Flow ID {flow_id_str} is not a valid UUID")
|
||||
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
||||
raise ValueError(str(exc)) from exc
|
||||
raise ValueError(str(exc)) from exc
|
||||
|
||||
|
||||
async def simple_run_flow_task(
|
||||
flow: Flow,
|
||||
input_request: SimplifiedAPIRequest,
|
||||
stream: bool = False,
|
||||
api_key_user: Optional[User] = None,
|
||||
):
|
||||
"""
|
||||
Run a flow task as a BackgroundTask, therefore it should not throw exceptions.
|
||||
"""
|
||||
try:
|
||||
result = await simple_run_flow(
|
||||
flow=flow,
|
||||
input_request=input_request,
|
||||
stream=stream,
|
||||
api_key_user=api_key_user,
|
||||
)
|
||||
return result
|
||||
|
||||
except Exception as exc:
|
||||
logger.exception(f"Error running flow {flow.id} task: {exc}")
|
||||
|
||||
|
||||
@router.post("/run/{flow_id_or_name}", response_model=RunResponse, response_model_exclude_none=True)
|
||||
|
|
@ -266,7 +284,7 @@ async def webhook_run_flow(
|
|||
)
|
||||
logger.debug("Starting background task")
|
||||
background_tasks.add_task( # type: ignore
|
||||
simple_run_flow,
|
||||
simple_run_flow_task,
|
||||
flow=flow,
|
||||
input_request=input_request,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,44 +3,40 @@ from pydantic.v1 import SecretStr
|
|||
from langflow.base.constants import STREAM_INFO_TEXT
|
||||
from langflow.base.models.model import LCModelComponent
|
||||
from langflow.field_typing import LanguageModel
|
||||
from langflow.io import BoolInput, DropdownInput, FloatInput, IntInput, MessageInput, Output, SecretStrInput, StrInput
|
||||
from langflow.inputs import (
|
||||
BoolInput,
|
||||
DropdownInput,
|
||||
FloatInput,
|
||||
IntInput,
|
||||
MessageInput,
|
||||
SecretStrInput,
|
||||
StrInput,
|
||||
)
|
||||
|
||||
|
||||
class GoogleGenerativeAIComponent(LCModelComponent):
|
||||
display_name: str = "Google Generative AI"
|
||||
description: str = "Generate text using Google Generative AI."
|
||||
display_name = "Google Generative AI"
|
||||
description = "Generate text using Google Generative AI."
|
||||
icon = "GoogleGenerativeAI"
|
||||
|
||||
inputs = [
|
||||
SecretStrInput(
|
||||
name="google_api_key",
|
||||
display_name="Google API Key",
|
||||
info="The Google API Key to use for the Google Generative AI.",
|
||||
MessageInput(name="input_value", display_name="Input"),
|
||||
IntInput(
|
||||
name="max_output_tokens",
|
||||
display_name="Max Output Tokens",
|
||||
info="The maximum number of tokens to generate.",
|
||||
),
|
||||
DropdownInput(
|
||||
name="model",
|
||||
display_name="Model",
|
||||
info="The name of the model to use.",
|
||||
options=["gemini-1.5-pro", "gemini-1.5-flash"],
|
||||
options=["gemini-1.5-pro", "gemini-1.5-flash", "gemini-1.0-pro", "gemini-1.0-pro-vision"],
|
||||
value="gemini-1.5-pro",
|
||||
),
|
||||
IntInput(
|
||||
name="max_output_tokens",
|
||||
display_name="Max Output Tokens",
|
||||
info="The maximum number of tokens to generate.",
|
||||
advanced=True,
|
||||
),
|
||||
FloatInput(
|
||||
name="temperature",
|
||||
display_name="Temperature",
|
||||
info="Run inference with this temperature. Must by in the closed interval [0.0, 1.0].",
|
||||
value=0.1,
|
||||
),
|
||||
IntInput(
|
||||
name="top_k",
|
||||
display_name="Top K",
|
||||
info="Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive.",
|
||||
advanced=True,
|
||||
SecretStrInput(
|
||||
name="google_api_key",
|
||||
display_name="Google API Key",
|
||||
info="The Google API Key to use for the Google Generative AI.",
|
||||
),
|
||||
FloatInput(
|
||||
name="top_p",
|
||||
|
|
@ -48,29 +44,26 @@ class GoogleGenerativeAIComponent(LCModelComponent):
|
|||
info="The maximum cumulative probability of tokens to consider when sampling.",
|
||||
advanced=True,
|
||||
),
|
||||
FloatInput(name="temperature", display_name="Temperature", value=0.1),
|
||||
BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True),
|
||||
IntInput(
|
||||
name="n",
|
||||
display_name="N",
|
||||
info="Number of chat completions to generate for each prompt. Note that the API may not return the full n completions if duplicates are generated.",
|
||||
advanced=True,
|
||||
),
|
||||
MessageInput(
|
||||
name="input_value",
|
||||
display_name="Input",
|
||||
info="The input to the model.",
|
||||
input_types=["Text", "Data", "Prompt"],
|
||||
),
|
||||
BoolInput(name="stream", display_name="Stream", info=STREAM_INFO_TEXT, advanced=True),
|
||||
StrInput(
|
||||
name="system_message",
|
||||
display_name="System Message",
|
||||
info="System message to pass to the model.",
|
||||
advanced=True,
|
||||
),
|
||||
]
|
||||
outputs = [
|
||||
Output(display_name="Text", name="text_output", method="text_response"),
|
||||
Output(display_name="Language Model", name="model_output", method="build_model"),
|
||||
IntInput(
|
||||
name="top_k",
|
||||
display_name="Top K",
|
||||
info="Decode using top-k sampling: consider the set of top_k most probable tokens. Must be positive.",
|
||||
advanced=True,
|
||||
),
|
||||
]
|
||||
|
||||
def build_model(self) -> LanguageModel:
|
||||
|
|
|
|||
|
|
@ -37,28 +37,26 @@ class RequestCancelledMiddleware(BaseHTTPMiddleware):
|
|||
super().__init__(app)
|
||||
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
queue: asyncio.Queue = asyncio.Queue()
|
||||
|
||||
async def message_poller(sentinel, handler_task, request):
|
||||
nonlocal queue
|
||||
while True:
|
||||
message = await request.receive()
|
||||
if message["type"] == "http.disconnect":
|
||||
handler_task.cancel()
|
||||
return sentinel # Break the loop
|
||||
|
||||
# Puts the message in the queue
|
||||
await queue.put(message)
|
||||
|
||||
sentinel = object()
|
||||
handler_task = asyncio.create_task(call_next(request))
|
||||
asyncio.create_task(message_poller(sentinel, handler_task, request))
|
||||
|
||||
try:
|
||||
response = await handler_task
|
||||
return response
|
||||
except asyncio.CancelledError:
|
||||
async def cancel_handler():
|
||||
while True:
|
||||
if await request.is_disconnected():
|
||||
return sentinel
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
handler_task = asyncio.create_task(call_next(request))
|
||||
cancel_task = asyncio.create_task(cancel_handler())
|
||||
|
||||
done, pending = await asyncio.wait([handler_task, cancel_task], return_when=asyncio.FIRST_COMPLETED)
|
||||
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
|
||||
if cancel_task in done:
|
||||
return Response("Request was cancelled", status_code=499)
|
||||
else:
|
||||
return await handler_task
|
||||
|
||||
|
||||
class JavaScriptMIMETypeMiddleware(BaseHTTPMiddleware):
|
||||
|
|
@ -126,7 +124,8 @@ def create_app():
|
|||
allow_headers=["*"],
|
||||
)
|
||||
app.add_middleware(JavaScriptMIMETypeMiddleware)
|
||||
app.add_middleware(RequestCancelledMiddleware)
|
||||
# ! Deactivating this until we find a better solution
|
||||
# app.add_middleware(RequestCancelledMiddleware)
|
||||
|
||||
@app.middleware("http")
|
||||
async def flatten_query_string_lists(request: Request, call_next):
|
||||
|
|
|
|||
|
|
@ -164,15 +164,15 @@ async def get_current_user_for_websocket(
|
|||
|
||||
def get_current_active_user(current_user: Annotated[User, Depends(get_current_user)]):
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Inactive user")
|
||||
return current_user
|
||||
|
||||
|
||||
def get_current_active_superuser(current_user: Annotated[User, Depends(get_current_user)]) -> User:
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=401, detail="Inactive user")
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Inactive user")
|
||||
if not current_user.is_superuser:
|
||||
raise HTTPException(status_code=400, detail="The user doesn't have enough privileges")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="The user doesn't have enough privileges")
|
||||
return current_user
|
||||
|
||||
|
||||
|
|
@ -324,8 +324,8 @@ def authenticate_user(username: str, password: str, db: Session = Depends(get_se
|
|||
|
||||
if not user.is_active:
|
||||
if not user.last_login_at:
|
||||
raise HTTPException(status_code=400, detail="Waiting for approval")
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Waiting for approval")
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Inactive user")
|
||||
|
||||
return user if verify_password(password, user.password) else None
|
||||
|
||||
|
|
|
|||
11
src/backend/base/poetry.lock
generated
11
src/backend/base/poetry.lock
generated
|
|
@ -1281,18 +1281,21 @@ types-requests = ">=2.31.0.2,<3.0.0.0"
|
|||
|
||||
[[package]]
|
||||
name = "langsmith"
|
||||
version = "0.1.81"
|
||||
version = "0.1.82"
|
||||
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8.1"
|
||||
files = [
|
||||
{file = "langsmith-0.1.81-py3-none-any.whl", hash = "sha256:3251d823225eef23ee541980b9d9e506367eabbb7f985a086b5d09e8f78ba7e9"},
|
||||
{file = "langsmith-0.1.81.tar.gz", hash = "sha256:585ef3a2251380bd2843a664c9a28da4a7d28432e3ee8bcebf291ffb8e1f0af0"},
|
||||
{file = "langsmith-0.1.82-py3-none-any.whl", hash = "sha256:9b3653e7d316036b0c60bf0bc3e280662d660f485a4ebd8e5c9d84f9831ae79c"},
|
||||
{file = "langsmith-0.1.82.tar.gz", hash = "sha256:c02e2bbc488c10c13b52c69d271eb40bd38da078d37b6ae7ae04a18bd48140be"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
orjson = ">=3.9.14,<4.0.0"
|
||||
pydantic = ">=1,<3"
|
||||
pydantic = [
|
||||
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
|
||||
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
|
||||
]
|
||||
requests = ">=2,<3"
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "langflow-base"
|
||||
version = "0.0.79"
|
||||
version = "0.0.80"
|
||||
description = "A Python package with a built-in web application"
|
||||
authors = ["Langflow <contact@langflow.org>"]
|
||||
maintainers = [
|
||||
|
|
|
|||
|
|
@ -103,13 +103,6 @@ export default function App() {
|
|||
if (isAuthenticated) {
|
||||
try {
|
||||
await setupAxiosDefaults();
|
||||
|
||||
const res = await getGlobalVariables();
|
||||
setGlobalVariables(res);
|
||||
|
||||
checkHasStore();
|
||||
fetchApiData();
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch data:", error);
|
||||
|
|
|
|||
|
|
@ -218,7 +218,8 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
/>
|
||||
<div>{printByBuildStatus()}</div>
|
||||
</div>
|
||||
<button
|
||||
{/* Deactivating this until we find a better solution */}
|
||||
{/* <button
|
||||
disabled={!isBuilding}
|
||||
onClick={(_) => {
|
||||
if (isBuilding) {
|
||||
|
|
@ -236,7 +237,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
>
|
||||
<IconComponent name="Square" className="h-4 w-4" />
|
||||
<span>Stop</span>
|
||||
</button>
|
||||
</button> */}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
import { createContext, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Cookies from "universal-cookie";
|
||||
import { getLoggedUser, requestLogout } from "../controllers/API";
|
||||
import {
|
||||
getGlobalVariables,
|
||||
getLoggedUser,
|
||||
requestLogout,
|
||||
} from "../controllers/API";
|
||||
import useAlertStore from "../stores/alertStore";
|
||||
import { useFolderStore } from "../stores/foldersStore";
|
||||
import { useGlobalVariablesStore } from "../stores/globalVariablesStore/globalVariables";
|
||||
import { useStoreStore } from "../stores/storeStore";
|
||||
import { Users } from "../types/api";
|
||||
import { AuthContextType } from "../types/contexts/auth";
|
||||
|
||||
|
|
@ -45,6 +51,11 @@ export function AuthProvider({ children }): React.ReactElement {
|
|||
);
|
||||
|
||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||
const setGlobalVariables = useGlobalVariablesStore(
|
||||
(state) => state.setGlobalVariables,
|
||||
);
|
||||
const checkHasStore = useStoreStore((state) => state.checkHasStore);
|
||||
const fetchApiData = useStoreStore((state) => state.fetchApiData);
|
||||
|
||||
useEffect(() => {
|
||||
const storedAccessToken = cookies.get("access_token_lf");
|
||||
|
|
@ -66,8 +77,11 @@ export function AuthProvider({ children }): React.ReactElement {
|
|||
setUserData(user);
|
||||
const isSuperUser = user!.is_superuser;
|
||||
setIsAdmin(isSuperUser);
|
||||
|
||||
getFoldersApi(true, true);
|
||||
const res = await getGlobalVariables();
|
||||
setGlobalVariables(res);
|
||||
checkHasStore();
|
||||
fetchApiData();
|
||||
})
|
||||
.catch((error) => {
|
||||
setLoading(false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue