├── src ├── backend │ ├── langflow │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── endpoints.py │ │ │ ├── validate.py │ │ │ └── base.py │ │ ├── cache │ │ │ ├── __init__.py │ │ │ └── utils.py │ │ ├── utils │ │ │ ├── __init__.py │ │ │ ├── constants.py │ │ │ ├── logger.py │ │ │ └── payload.py │ │ ├── custom │ │ │ ├── __init__.py │ │ │ └── customs.py │ │ ├── interface │ │ │ ├── __init__.py │ │ │ ├── embeddings │ │ │ │ ├── __init__.py │ │ │ │ └── base.py │ │ │ ├── toolkits │ │ │ │ ├── __init__.py │ │ │ │ ├── custom.py │ │ │ │ └── base.py │ │ │ ├── wrappers │ │ │ │ ├── __init__.py │ │ │ │ └── base.py │ │ │ ├── vectorStore │ │ │ │ ├── __init__.py │ │ │ │ └── base.py │ │ │ ├── document_loaders │ │ │ │ └── __init__.py │ │ │ ├── llms │ │ │ │ ├── __init__.py │ │ │ │ └── base.py │ │ │ ├── tools │ │ │ │ ├── __init__.py │ │ │ │ ├── constants.py │ │ │ │ ├── custom.py │ │ │ │ └── util.py │ │ │ ├── agents │ │ │ │ ├── __init__.py │ │ │ │ ├── prebuilt.py │ │ │ │ └── base.py │ │ │ ├── chains │ │ │ │ ├── __init__.py │ │ │ │ ├── base.py │ │ │ │ └── custom.py │ │ │ ├── memories │ │ │ │ ├── __init__.py │ │ │ │ └── base.py │ │ │ ├── prompts │ │ │ │ ├── __init__.py │ │ │ │ ├── base.py │ │ │ │ └── custom.py │ │ │ ├── text_splitters │ │ │ │ ├── __init__.py │ │ │ │ └── base.py │ │ │ ├── importing │ │ │ │ ├── __init__.py │ │ │ │ └── utils.py │ │ │ ├── utils.py │ │ │ ├── listing.py │ │ │ ├── types.py │ │ │ ├── custom_lists.py │ │ │ └── base.py │ │ ├── template │ │ │ ├── __init__.py │ │ │ ├── fields.py │ │ │ └── constants.py │ │ ├── __init__.py │ │ ├── graph │ │ │ ├── constants.py │ │ │ ├── __init__.py │ │ │ └── utils.py │ │ ├── server.py │ │ ├── main.py │ │ ├── config.yaml │ │ ├── settings.py │ │ └── __main__.py │ ├── build_and_push │ ├── run │ ├── Dockerfile │ ├── build.Dockerfile │ └── .gitignore └── frontend │ ├── public │ ├── favicon.ico │ └── index.html │ ├── build.Dockerfile │ ├── src │ ├── assets │ │ └── text-security-disc.woff │ ├── types │ │ ├── entities │ │ │ └── index.ts │ │ ├── chat │ │ │ └── index.ts │ │ ├── typesContext │ │ │ └── index.ts │ │ ├── flow │ │ │ └── index.ts │ │ ├── tabs │ │ │ └── index.ts │ │ ├── alerts │ │ │ └── index.ts │ │ ├── api │ │ │ └── index.ts │ │ └── components │ │ │ └── index.ts │ ├── components │ │ ├── TooltipComponent │ │ │ └── index.tsx │ │ ├── LightTooltipComponent │ │ │ └── index.tsx │ │ ├── floatComponent │ │ │ └── index.tsx │ │ ├── inputComponent │ │ │ └── index.tsx │ │ ├── CrashErrorComponent │ │ │ └── index.tsx │ │ ├── intComponent │ │ │ └── index.tsx │ │ ├── textAreaComponent │ │ │ └── index.tsx │ │ ├── loadingComponent │ │ │ └── index.tsx │ │ ├── promptComponent │ │ │ └── index.tsx │ │ ├── codeAreaComponent │ │ │ └── index.tsx │ │ ├── chatComponent │ │ │ └── chatMessage │ │ │ │ └── index.tsx │ │ ├── inputListComponent │ │ │ └── index.tsx │ │ ├── inputFileComponent │ │ │ └── index.tsx │ │ ├── toggleComponent │ │ │ └── index.tsx │ │ └── dropdownComponent │ │ │ └── index.tsx │ ├── index.css │ ├── reportWebVitals.ts │ ├── index.tsx │ ├── controllers │ │ └── API │ │ │ └── index.ts │ ├── contexts │ │ ├── darkContext.tsx │ │ ├── index.tsx │ │ ├── popUpContext.tsx │ │ ├── typesContext.tsx │ │ └── locationContext.tsx │ ├── pages │ │ └── FlowPage │ │ │ └── components │ │ │ ├── ConnectionLineComponent │ │ │ └── index.tsx │ │ │ ├── DisclosureComponent │ │ │ └── index.tsx │ │ │ ├── tabComponent │ │ │ └── index.tsx │ │ │ ├── extraSidebarComponent │ │ │ └── index.tsx │ │ │ └── tabsManagerComponent │ │ │ └── index.tsx │ ├── App.css │ ├── alerts │ │ ├── success │ │ │ └── index.tsx │ │ ├── error │ │ │ └── index.tsx │ │ ├── notice │ │ │ └── index.tsx │ │ └── alertDropDown │ │ │ └── index.tsx │ ├── modals │ │ └── importModal │ │ │ ├── buttonBox │ │ │ └── index.tsx │ │ │ └── index.tsx │ ├── logo.svg │ └── CustomNodes │ │ └── GenericNode │ │ └── index.tsx │ ├── Dockerfile │ ├── set_proxy.sh │ ├── nginx.conf │ ├── build_and_push │ ├── .gitignore │ ├── dev.Dockerfile │ ├── tsconfig.json │ ├── tailwind.config.js │ ├── package.json │ └── README.md ├── img ├── langflow-demo.gif └── langflow-screen.png ├── docker_example ├── Dockerfile └── docker-compose.yml ├── tests ├── test_vectorstore_template.py ├── test_custom_types.py ├── conftest.py ├── test_loading.py ├── test_creators.py ├── test_cache.py ├── test_frontend_nodes.py ├── test_validate_code.py └── test_endpoints.py ├── dev.Dockerfile ├── docker-compose.yml ├── .github └── workflows │ ├── test.yml │ ├── lint.yml │ └── release.yml ├── docker-compose.debug.yml ├── LICENSE ├── .devcontainer └── devcontainer.json ├── pyproject.toml ├── Dockerfile ├── Makefile ├── README.md ├── CONTRIBUTING.md └── .gitignore /src/backend/langflow/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/cache/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/custom/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/template/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/template/fields.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/embeddings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/toolkits/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/toolkits/custom.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/wrappers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/vectorStore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/document_loaders/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/langflow-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/royshil/langflow/dev/img/langflow-demo.gif -------------------------------------------------------------------------------- /img/langflow-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/royshil/langflow/dev/img/langflow-screen.png -------------------------------------------------------------------------------- /src/backend/langflow/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.loading import load_flow_from_json # noqa 2 | -------------------------------------------------------------------------------- /src/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/royshil/langflow/dev/src/frontend/public/favicon.ico -------------------------------------------------------------------------------- /src/backend/langflow/graph/constants.py: -------------------------------------------------------------------------------- 1 | DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"] 2 | -------------------------------------------------------------------------------- /src/frontend/build.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine 2 | WORKDIR /app 3 | COPY . /app 4 | RUN npm install 5 | RUN npm run build -------------------------------------------------------------------------------- /src/backend/langflow/interface/llms/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.llms.base import LLMCreator 2 | 3 | __all__ = ["LLMCreator"] 4 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/tools/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.tools.base import ToolCreator 2 | 3 | __all__ = ["ToolCreator"] 4 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/agents/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.agents.base import AgentCreator 2 | 3 | __all__ = ["AgentCreator"] 4 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/chains/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.chains.base import ChainCreator 2 | 3 | __all__ = ["ChainCreator"] 4 | -------------------------------------------------------------------------------- /src/frontend/src/assets/text-security-disc.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/royshil/langflow/dev/src/frontend/src/assets/text-security-disc.woff -------------------------------------------------------------------------------- /src/backend/langflow/interface/memories/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.memories.base import MemoryCreator 2 | 3 | __all__ = ["MemoryCreator"] 4 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/prompts/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.prompts.base import PromptCreator 2 | 3 | __all__ = ["PromptCreator"] 4 | -------------------------------------------------------------------------------- /src/backend/langflow/graph/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.graph.base import Edge, Node 2 | from langflow.graph.graph import Graph 3 | 4 | __all__ = ["Graph", "Node", "Edge"] 5 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/text_splitters/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.text_splitters.base import TextSplitterCreator 2 | 3 | __all__ = ["TextSplitterCreator"] 4 | -------------------------------------------------------------------------------- /docker_example/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | RUN apt-get update && apt-get install gcc -y 4 | RUN pip install langflow>=0.0.33 5 | 6 | EXPOSE 7860 7 | CMD ["langflow", "--host", "0.0.0.0"] -------------------------------------------------------------------------------- /docker_example/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | langflow: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | ports: 9 | - "5003:5003" 10 | command: langflow --host 0.0.0.0 11 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/importing/__init__.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.importing.utils import import_by_type # noqa: F401 2 | 3 | # This module is used to import any langchain class by name. 4 | 5 | ALL = [ 6 | "import_by_type", 7 | ] 8 | -------------------------------------------------------------------------------- /src/frontend/src/types/entities/index.ts: -------------------------------------------------------------------------------- 1 | import { HomeIcon } from "@heroicons/react/24/outline"; 2 | 3 | export type sidebarNavigationItemType = { name: string, href: string, icon: React.ForwardRefExoticComponent>, current: boolean } 4 | -------------------------------------------------------------------------------- /src/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine as frontend_build 2 | ARG BACKEND 3 | WORKDIR /app 4 | COPY . /app 5 | RUN npm install 6 | RUN npm run build 7 | 8 | FROM nginx 9 | COPY --from=frontend_build /app/build/ /usr/share/nginx/html 10 | COPY /nginx.conf /etc/nginx/conf.d/default.conf -------------------------------------------------------------------------------- /src/frontend/src/types/chat/index.ts: -------------------------------------------------------------------------------- 1 | import { ReactFlowInstance } from 'reactflow'; 2 | import { FlowType } from "../flow"; 3 | 4 | export type ChatType = {flow:FlowType,reactFlowInstance:ReactFlowInstance} 5 | export type ChatMessageType = { message: string; isSend: boolean, thought?:string } -------------------------------------------------------------------------------- /src/backend/build_and_push: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | docker build -t logspace/backend_build -f build.Dockerfile . 4 | VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version) 5 | docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION . 6 | docker push ibiscp/langflow:$VERSION 7 | -------------------------------------------------------------------------------- /src/frontend/src/components/TooltipComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from "react"; 2 | import { LightTooltip } from "../LightTooltipComponent"; 3 | 4 | export default function Tooltip({ children, title }:{children:ReactElement,title:string}) { 5 | return {children}; 6 | } 7 | -------------------------------------------------------------------------------- /src/frontend/set_proxy.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Edit package.json to set proxy 3 | backend_url=$BACKEND_URL 4 | echo "Setting proxy to $backend_url" 5 | # Load package.json file and edit proxy 6 | packagejson=$(cat package.json) 7 | 8 | packagejson=$(echo "$packagejson" | jq ".proxy = \"$backend_url\"") 9 | 10 | echo "$packagejson" > package.json 11 | -------------------------------------------------------------------------------- /src/backend/run: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | poetry remove langchain 4 | docker build -t logspace/backend_build -f build.Dockerfile . 5 | VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version) 6 | docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION . 7 | docker run -p 5003:80 -d ibiscp/langflow:$VERSION 8 | poetry add --editable ../../../langchain 9 | -------------------------------------------------------------------------------- /src/frontend/src/types/typesContext/index.ts: -------------------------------------------------------------------------------- 1 | import { ReactFlowInstance } from "reactflow"; 2 | 3 | const types:{[char: string]: string}={} 4 | 5 | export type typesContextType = { 6 | reactFlowInstance: ReactFlowInstance|null; 7 | setReactFlowInstance: any; 8 | deleteNode: (idx: string) => void; 9 | types: typeof types; 10 | setTypes: (newState: {}) => void; 11 | }; -------------------------------------------------------------------------------- /src/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM logspace/backend_build as backend_build 2 | 3 | FROM python:3.10-slim 4 | WORKDIR /app 5 | 6 | RUN apt-get update && apt-get install git -y 7 | 8 | COPY --from=backend_build /app/dist/*.whl /app/ 9 | RUN pip install langflow-*.whl 10 | RUN rm *.whl 11 | 12 | EXPOSE 80 13 | 14 | CMD [ "uvicorn", "--host", "0.0.0.0", "--port", "80", "langflow.backend.app:app" ] 15 | -------------------------------------------------------------------------------- /src/frontend/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | gzip on; 3 | gzip_comp_level 2; 4 | gzip_min_length 1000; 5 | gzip_types text/xml text/css; 6 | gzip_http_version 1.1; 7 | gzip_vary on; 8 | gzip_disable "MSIE [4-6] \."; 9 | 10 | listen 80; 11 | 12 | location / { 13 | root /usr/share/nginx/html; 14 | index index.html index.htm; 15 | try_files $uri $uri/ /index.html =404; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/frontend/build_and_push: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Read the contents of the JSON file 4 | json=$(cat package.json) 5 | 6 | # Extract the value of the "version" field using jq 7 | VERSION=$(echo "$json" | jq -r '.version') 8 | 9 | docker build -t logspace/frontend_build -f build.Dockerfile . 10 | docker build --build-arg VERSION=$VERSION -t ibiscp/langflow_frontend:$VERSION . 11 | docker push ibiscp/langflow_frontend:$VERSION 12 | -------------------------------------------------------------------------------- /src/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/frontend/src/index.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | body { 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 6 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 7 | sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | code { 13 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 14 | monospace; 15 | } 16 | -------------------------------------------------------------------------------- /src/backend/langflow/utils/constants.py: -------------------------------------------------------------------------------- 1 | OPENAI_MODELS = [ 2 | "text-davinci-003", 3 | "text-davinci-002", 4 | "text-curie-001", 5 | "text-babbage-001", 6 | "text-ada-001", 7 | ] 8 | CHAT_OPENAI_MODELS = ["gpt-3.5-turbo", "gpt-4", "gpt-4-32k"] 9 | 10 | 11 | DEFAULT_PYTHON_FUNCTION = """ 12 | def python_function(text: str) -> str: 13 | \"\"\"This is a default python function that returns the input text\"\"\" 14 | return text 15 | """ 16 | -------------------------------------------------------------------------------- /src/frontend/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /tests/test_vectorstore_template.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | from langflow.settings import settings 3 | 4 | 5 | # check that all agents are in settings.agents 6 | # are in json_response["agents"] 7 | def test_vectorstores_settings(client: TestClient): 8 | response = client.get("/all") 9 | assert response.status_code == 200 10 | json_response = response.json() 11 | vectorstores = json_response["vectorstores"] 12 | assert set(vectorstores.keys()) == set(settings.vectorstores) 13 | -------------------------------------------------------------------------------- /src/backend/langflow/graph/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def validate_prompt(prompt: str): 5 | """Validate prompt.""" 6 | if extract_input_variables_from_prompt(prompt): 7 | return prompt 8 | 9 | return fix_prompt(prompt) 10 | 11 | 12 | def fix_prompt(prompt: str): 13 | """Fix prompt.""" 14 | return prompt + " {input}" 15 | 16 | 17 | def extract_input_variables_from_prompt(prompt: str) -> list[str]: 18 | """Extract input variables from prompt.""" 19 | return re.findall(r"{(.*?)}", prompt) 20 | -------------------------------------------------------------------------------- /src/frontend/src/types/flow/index.ts: -------------------------------------------------------------------------------- 1 | import { ChatMessageType } from './../chat/index'; 2 | import { APIClassType } from '../api/index'; 3 | import { ReactFlowJsonObject, XYPosition } from "reactflow"; 4 | 5 | export type FlowType = { 6 | name: string; 7 | id: string; 8 | data: ReactFlowJsonObject; 9 | chat: Array; 10 | description:string; 11 | }; 12 | export type NodeType = {id:string,type:string,position:XYPosition,data:NodeDataType} 13 | export type NodeDataType = {type:string,node?:APIClassType,id:string,value:any} -------------------------------------------------------------------------------- /src/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | import reportWebVitals from "./reportWebVitals"; 5 | import { BrowserRouter } from "react-router-dom"; 6 | import ContextWrapper from "./contexts"; 7 | 8 | const root = ReactDOM.createRoot( 9 | document.getElementById("root") as HTMLElement 10 | ); 11 | root.render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /src/frontend/src/types/tabs/index.ts: -------------------------------------------------------------------------------- 1 | import { FlowType } from "../flow"; 2 | 3 | export type TabsContextType = { 4 | save:()=>void; 5 | tabIndex: number; 6 | setTabIndex: (index: number) => void; 7 | flows: Array; 8 | removeFlow: (id: string) => void; 9 | addFlow: (flowData?: any) => void; 10 | updateFlow: (newFlow: FlowType) => void; 11 | incrementNodeId: () => number; 12 | downloadFlow: (flow:FlowType) => void; 13 | uploadFlow: () => void; 14 | lockChat:boolean; 15 | setLockChat:(prevState:boolean)=>void; 16 | hardReset:()=>void; 17 | }; -------------------------------------------------------------------------------- /src/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | LangFlow 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/test_custom_types.py: -------------------------------------------------------------------------------- 1 | # Test this: 2 | import pytest 3 | from langflow.interface.tools.custom import PythonFunction 4 | from langflow.utils import constants 5 | 6 | 7 | def test_python_function(): 8 | """Test Python function""" 9 | func = PythonFunction(code=constants.DEFAULT_PYTHON_FUNCTION) 10 | assert func.get_function()("text") == "text" 11 | # the tool decorator should raise an error if 12 | # the function is not str -> str 13 | 14 | # This raises ValidationError 15 | with pytest.raises(SyntaxError): 16 | func = PythonFunction(code=pytest.CODE_WITH_SYNTAX_ERROR) 17 | -------------------------------------------------------------------------------- /src/frontend/dev.Dockerfile: -------------------------------------------------------------------------------- 1 | #baseline 2 | FROM node:19-bullseye-slim AS base 3 | RUN mkdir -p /home/node/app 4 | RUN chown -R node:node /home/node && chmod -R 770 /home/node 5 | RUN apt-get update && apt-get install -y jq 6 | WORKDIR /home/node/app 7 | 8 | # client build 9 | FROM base AS builder-client 10 | ARG BACKEND_URL 11 | ENV BACKEND_URL $BACKEND_URL 12 | RUN echo "BACKEND_URL: $BACKEND_URL" 13 | 14 | WORKDIR /home/node/app 15 | COPY --chown=node:node . ./ 16 | 17 | COPY ./set_proxy.sh . 18 | RUN chmod +x set_proxy.sh && ./set_proxy.sh 19 | 20 | USER node 21 | 22 | RUN npm install --loglevel warn 23 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /src/frontend/src/types/alerts/index.ts: -------------------------------------------------------------------------------- 1 | export type ErrorAlertType = {title:string,list:Array,id:string,removeAlert:(id:string)=>void} 2 | export type NoticeAlertType = {title:string,link:string,id:string,removeAlert:(id:string)=>void} 3 | export type SuccessAlertType = {title:string,id:string, removeAlert:(id:string)=>void} 4 | export type SingleAlertComponentType = {dropItem:AlertItemType,removeAlert:(index:string)=>void} 5 | export type AlertDropdownType = {}; 6 | export type AlertItemType = { 7 | type: "notice" | "error" | "success"; 8 | title: string; 9 | link?: string; 10 | list?: Array; 11 | id: string; 12 | }; -------------------------------------------------------------------------------- /dev.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | 3 | WORKDIR /app 4 | 5 | # Install Poetry 6 | RUN apt-get update && apt-get install gcc g++ curl build-essential -y 7 | RUN curl -sSL https://install.python-poetry.org | python3 - 8 | # # Add Poetry to PATH 9 | ENV PATH="${PATH}:/root/.local/bin" 10 | # # Copy the pyproject.toml and poetry.lock files 11 | COPY poetry.lock pyproject.toml ./ 12 | # Copy the rest of the application codes 13 | COPY ./ ./ 14 | 15 | # Install dependencies 16 | RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi 17 | 18 | CMD ["uvicorn", "langflow.main:app", "--host", "0.0.0.0", "--port", "5003", "--reload", "log-level", "debug"] -------------------------------------------------------------------------------- /src/backend/langflow/server.py: -------------------------------------------------------------------------------- 1 | from gunicorn.app.base import BaseApplication # type: ignore 2 | 3 | 4 | class LangflowApplication(BaseApplication): 5 | def __init__(self, app, options=None): 6 | self.options = options or {} 7 | self.application = app 8 | super().__init__() 9 | 10 | def load_config(self): 11 | config = { 12 | key: value 13 | for key, value in self.options.items() 14 | if key in self.cfg.settings and value is not None 15 | } 16 | for key, value in config.items(): 17 | self.cfg.set(key.lower(), value) 18 | 19 | def load(self): 20 | return self.application 21 | -------------------------------------------------------------------------------- /src/frontend/src/components/LightTooltipComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; 3 | 4 | export const LightTooltip = styled(({ className, ...props }: TooltipProps) => ( 5 | 6 | ))(({ theme }) => ({ 7 | [`& .${tooltipClasses.tooltip}`]: { 8 | backgroundColor: theme.palette.common.white, 9 | color: 'rgba(0, 0, 0, 0.87)', 10 | boxShadow: theme.shadows[2], 11 | fontSize: 14, 12 | }, 13 | [`& .${tooltipClasses.arrow}:before`]: { 14 | color: theme.palette.common.white, 15 | boxShadow: theme.shadows[1], 16 | }, 17 | })); -------------------------------------------------------------------------------- /src/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": false, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | "noImplicitAny": false 23 | }, 24 | "include": [ 25 | "src" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | backend: 5 | build: 6 | context: ./ 7 | dockerfile: ./dev.Dockerfile 8 | ports: 9 | - "7860:7860" 10 | volumes: 11 | - ./:/app 12 | command: bash -c "uvicorn langflow.main:app --host 0.0.0.0 --port 7860 --reload" 13 | 14 | frontend: 15 | build: 16 | context: ./src/frontend 17 | dockerfile: ./dev.Dockerfile 18 | args: 19 | - BACKEND_URL=http://backend:7860 20 | ports: 21 | - "3000:3000" 22 | volumes: 23 | - ./src/frontend/public:/home/node/app/public 24 | - ./src/frontend/src:/home/node/app/src 25 | - ./src/frontend/package.json:/home/node/app/package.json 26 | restart: on-failure -------------------------------------------------------------------------------- /src/backend/langflow/interface/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import yaml 5 | 6 | 7 | def load_file_into_dict(file_path: str) -> dict: 8 | if not os.path.exists(file_path): 9 | raise FileNotFoundError(f"File not found: {file_path}") 10 | 11 | file_extension = os.path.splitext(file_path)[1].lower() 12 | 13 | if file_extension == ".json": 14 | with open(file_path, "r") as json_file: 15 | data = json.load(json_file) 16 | elif file_extension in [".yaml", ".yml"]: 17 | with open(file_path, "r") as yaml_file: 18 | data = yaml.safe_load(yaml_file) 19 | else: 20 | raise ValueError("Unsupported file type. Please provide a JSON or YAML file.") 21 | 22 | return data 23 | -------------------------------------------------------------------------------- /src/backend/langflow/custom/customs.py: -------------------------------------------------------------------------------- 1 | from langflow.template import nodes 2 | 3 | # These should always be instantiated 4 | CUSTOM_NODES = { 5 | "prompts": {"ZeroShotPrompt": nodes.ZeroShotPromptNode()}, 6 | "tools": {"PythonFunction": nodes.PythonFunctionNode(), "Tool": nodes.ToolNode()}, 7 | "agents": { 8 | "JsonAgent": nodes.JsonAgentNode(), 9 | "CSVAgent": nodes.CSVAgentNode(), 10 | "initialize_agent": nodes.InitializeAgentNode(), 11 | "VectorStoreAgent": nodes.VectorStoreAgentNode(), 12 | "VectorStoreRouterAgent": nodes.VectorStoreRouterAgentNode(), 13 | }, 14 | } 15 | 16 | 17 | def get_custom_nodes(node_type: str): 18 | """Get custom nodes.""" 19 | return CUSTOM_NODES.get(node_type, {}) 20 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/tools/constants.py: -------------------------------------------------------------------------------- 1 | from langchain.agents import Tool 2 | from langchain.agents.load_tools import ( 3 | _BASE_TOOLS, 4 | _EXTRA_LLM_TOOLS, 5 | _EXTRA_OPTIONAL_TOOLS, 6 | _LLM_TOOLS, 7 | ) 8 | from langchain.tools.json.tool import JsonSpec 9 | 10 | from langflow.interface.tools.custom import PythonFunction 11 | 12 | FILE_TOOLS = {"JsonSpec": JsonSpec} 13 | CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction} 14 | ALL_TOOLS_NAMES = { 15 | **_BASE_TOOLS, 16 | **_LLM_TOOLS, # type: ignore 17 | **{k: v[0] for k, v in _EXTRA_LLM_TOOLS.items()}, # type: ignore 18 | **{k: v[0] for k, v in _EXTRA_OPTIONAL_TOOLS.items()}, 19 | **CUSTOM_TOOLS, 20 | **FILE_TOOLS, # type: ignore 21 | } 22 | -------------------------------------------------------------------------------- /src/backend/langflow/api/endpoints.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any, Dict 3 | 4 | from fastapi import APIRouter, HTTPException 5 | 6 | from langflow.interface.run import process_graph_cached 7 | from langflow.interface.types import build_langchain_types_dict 8 | 9 | # build router 10 | router = APIRouter() 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | @router.get("/all") 15 | def get_all(): 16 | return build_langchain_types_dict() 17 | 18 | 19 | @router.post("/predict") 20 | def get_load(data: Dict[str, Any]): 21 | try: 22 | return process_graph_cached(data) 23 | except Exception as e: 24 | # Log stack trace 25 | logger.exception(e) 26 | raise HTTPException(status_code=500, detail=str(e)) from e 27 | -------------------------------------------------------------------------------- /src/frontend/src/controllers/API/index.ts: -------------------------------------------------------------------------------- 1 | import { PromptTypeAPI, errorsTypeAPI } from './../../types/api/index'; 2 | import { APIObjectType, sendAllProps } from '../../types/api/index'; 3 | import axios, { AxiosResponse } from "axios"; 4 | 5 | export async function getAll():Promise> { 6 | return await axios.get(`/all`); 7 | } 8 | 9 | export async function sendAll(data:sendAllProps) { 10 | return await axios.post(`/predict`, data); 11 | } 12 | 13 | export async function checkCode(code:string):Promise>{ 14 | 15 | return await axios.post('/validate/code',{code}) 16 | } 17 | 18 | export async function checkPrompt(template:string):Promise>{ 19 | 20 | return await axios.post('/validate/prompt',{template}) 21 | } -------------------------------------------------------------------------------- /src/frontend/src/contexts/darkContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useEffect, useState } from "react"; 2 | 3 | type darkContextType = { 4 | dark: {}; 5 | setDark: (newState: {}) => void; 6 | }; 7 | 8 | const initialValue = { 9 | dark: {}, 10 | setDark: () => {}, 11 | }; 12 | 13 | export const darkContext = createContext(initialValue); 14 | 15 | export function DarkProvider({ children }) { 16 | const [dark, setDark] = useState(false); 17 | useEffect(()=>{ 18 | if(dark){ 19 | document.getElementById("body").classList.add("dark"); 20 | } else { 21 | document.getElementById("body").classList.remove("dark"); 22 | } 23 | }, [dark]) 24 | return ( 25 | 31 | {children} 32 | 33 | ); 34 | } -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [dev] 8 | 9 | env: 10 | POETRY_VERSION: "1.4.0" 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | python-version: 18 | - "3.10" 19 | - "3.11" 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Install poetry 23 | run: pipx install poetry==$POETRY_VERSION 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v4 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | cache: "poetry" 29 | - name: Install dependencies 30 | run: poetry install 31 | - name: Run unit tests 32 | run: | 33 | make test 34 | -------------------------------------------------------------------------------- /src/backend/langflow/template/constants.py: -------------------------------------------------------------------------------- 1 | FORCE_SHOW_FIELDS = [ 2 | "allowed_tools", 3 | "memory", 4 | "prefix", 5 | "examples", 6 | "temperature", 7 | "model_name", 8 | "headers", 9 | "max_value_length", 10 | "max_tokens", 11 | ] 12 | 13 | DEFAULT_PROMPT = """ 14 | I want you to act as a naming consultant for new companies. 15 | 16 | Here are some examples of good company names: 17 | 18 | - search engine, Google 19 | - social media, Facebook 20 | - video sharing, YouTube 21 | 22 | The name should be short, catchy and easy to remember. 23 | 24 | What is a good name for a company that makes {product}? 25 | """ 26 | 27 | SYSTEM_PROMPT = """ 28 | You are a helpful assistant that talks casually about life in general. 29 | You are a good listener and you can talk about anything. 30 | """ 31 | 32 | HUMAN_PROMPT = "{input}" 33 | -------------------------------------------------------------------------------- /src/frontend/src/contexts/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { AlertProvider } from "./alertContext"; 3 | import { DarkProvider } from "./darkContext"; 4 | import { LocationProvider } from "./locationContext"; 5 | import PopUpProvider from "./popUpContext"; 6 | import { TabsProvider } from "./tabsContext"; 7 | import { TypesProvider } from "./typesContext"; 8 | 9 | export default function ContextWrapper({ children }: { children: ReactNode }) { 10 | //element to wrap all context 11 | return ( 12 | <> 13 | 14 | 15 | 16 | 17 | 18 | 19 | {children} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | from fastapi.testclient import TestClient 5 | 6 | 7 | def pytest_configure(): 8 | pytest.BASIC_EXAMPLE_PATH = ( 9 | Path(__file__).parent.absolute() / "data" / "basic_example.json" 10 | ) 11 | pytest.COMPLEX_EXAMPLE_PATH = ( 12 | Path(__file__).parent.absolute() / "data" / "complex_example.json" 13 | ) 14 | pytest.OPENAPI_EXAMPLE_PATH = ( 15 | Path(__file__).parent.absolute() / "data" / "Openapi.json" 16 | ) 17 | 18 | pytest.CODE_WITH_SYNTAX_ERROR = """ 19 | def get_text(): 20 | retun "Hello World" 21 | """ 22 | 23 | 24 | # Create client fixture for FastAPI 25 | @pytest.fixture(scope="module") 26 | def client(): 27 | from langflow.main import create_app 28 | 29 | app = create_app() 30 | 31 | with TestClient(app) as client: 32 | yield client 33 | -------------------------------------------------------------------------------- /src/backend/langflow/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from fastapi.middleware.cors import CORSMiddleware 3 | 4 | from langflow.api.endpoints import router as endpoints_router 5 | from langflow.api.validate import router as validate_router 6 | 7 | 8 | def create_app(): 9 | """Create the FastAPI app and include the router.""" 10 | app = FastAPI() 11 | 12 | origins = [ 13 | "*", 14 | ] 15 | 16 | app.add_middleware( 17 | CORSMiddleware, 18 | allow_origins=origins, 19 | allow_credentials=True, 20 | allow_methods=["*"], 21 | allow_headers=["*"], 22 | ) 23 | 24 | app.include_router(endpoints_router) 25 | app.include_router(validate_router) 26 | return app 27 | 28 | 29 | app = create_app() 30 | 31 | 32 | if __name__ == "__main__": 33 | import uvicorn 34 | 35 | uvicorn.run(app, host="127.0.0.1", port=7860) 36 | -------------------------------------------------------------------------------- /tests/test_loading.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | from langchain.agents import AgentExecutor 5 | from langflow import load_flow_from_json 6 | from langflow.graph import Graph 7 | from langflow.utils.payload import get_root_node 8 | 9 | 10 | def test_load_flow_from_json(): 11 | """Test loading a flow from a json file""" 12 | loaded = load_flow_from_json(pytest.BASIC_EXAMPLE_PATH) 13 | assert loaded is not None 14 | assert isinstance(loaded, AgentExecutor) 15 | 16 | 17 | def test_get_root_node(): 18 | with open(pytest.BASIC_EXAMPLE_PATH, "r") as f: 19 | flow_graph = json.load(f) 20 | data_graph = flow_graph["data"] 21 | nodes = data_graph["nodes"] 22 | edges = data_graph["edges"] 23 | graph = Graph(nodes, edges) 24 | root = get_root_node(graph) 25 | assert root is not None 26 | assert hasattr(root, "id") 27 | assert hasattr(root, "data") 28 | -------------------------------------------------------------------------------- /docker-compose.debug.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | backend: 5 | volumes: 6 | - ./:/app 7 | build: 8 | context: ./ 9 | dockerfile: ./dev.Dockerfile 10 | command: ["sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn langflow.main:app --host 0.0.0.0 --port 7860 --reload"] 11 | ports: 12 | - 7860:7860 13 | - 5678:5678 14 | restart: on-failure 15 | 16 | frontend: 17 | build: 18 | context: ./src/frontend 19 | dockerfile: ./dev.Dockerfile 20 | args: 21 | - BACKEND_URL=http://backend:7860 22 | ports: 23 | - "3000:3000" 24 | volumes: 25 | - ./src/frontend/public:/home/node/app/public 26 | - ./src/frontend/src:/home/node/app/src 27 | - ./src/frontend/package.json:/home/node/app/package.json 28 | restart: on-failure -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | env: 9 | POETRY_VERSION: "1.4.0" 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | python-version: 17 | - "3.8" 18 | - "3.9" 19 | - "3.10" 20 | - "3.11" 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Install poetry 24 | run: | 25 | pipx install poetry==$POETRY_VERSION 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | cache: poetry 31 | - name: Install dependencies 32 | run: | 33 | poetry install 34 | - name: Analysing the code with our lint 35 | run: | 36 | make lint 37 | -------------------------------------------------------------------------------- /src/frontend/src/pages/FlowPage/components/ConnectionLineComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { ConnectionLineComponentProps } from 'reactflow'; 2 | 3 | 4 | 5 | const ConnectionLineComponent = ({ 6 | fromX, 7 | fromY, 8 | toX, 9 | toY, 10 | connectionLineStyle = {} // provide a default value for connectionLineStyle 11 | }:ConnectionLineComponentProps) => { 12 | return ( 13 | 14 | 22 | 31 | 32 | ); 33 | }; 34 | 35 | export default ConnectionLineComponent; 36 | -------------------------------------------------------------------------------- /src/frontend/src/contexts/popUpContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | import React, { useState } from "react"; 3 | 4 | //context to set JSX element on the DOM 5 | export const PopUpContext = createContext({ 6 | openPopUp: (popUpElement: JSX.Element) => {}, 7 | closePopUp: () => {}, 8 | }); 9 | 10 | interface PopUpProviderProps { 11 | children: React.ReactNode; 12 | } 13 | 14 | const PopUpProvider = ({ children }: PopUpProviderProps) => { 15 | const [popUpElement, setPopUpElement] = useState(null); 16 | 17 | const openPopUp = (element: JSX.Element) => { 18 | setPopUpElement(element); 19 | }; 20 | 21 | const closePopUp = () => { 22 | setPopUpElement(null); 23 | }; 24 | 25 | return ( 26 | 27 | {children} 28 | {popUpElement} 29 | 30 | ); 31 | }; 32 | 33 | export default PopUpProvider; 34 | -------------------------------------------------------------------------------- /src/frontend/src/App.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .App { 6 | text-align: center; 7 | } 8 | 9 | .App-logo { 10 | height: 40vmin; 11 | pointer-events: none; 12 | } 13 | 14 | @media (prefers-reduced-motion: no-preference) { 15 | .App-logo { 16 | animation: App-logo-spin infinite 20s linear; 17 | } 18 | } 19 | 20 | .App-header { 21 | background-color: #282c34; 22 | min-height: 100vh; 23 | display: flex; 24 | flex-direction: column; 25 | align-items: center; 26 | justify-content: center; 27 | font-size: calc(10px + 2vmin); 28 | color: white; 29 | } 30 | 31 | .App-link { 32 | color: #61dafb; 33 | } 34 | 35 | @keyframes App-logo-spin { 36 | from { 37 | transform: rotate(0deg); 38 | } 39 | to { 40 | transform: rotate(360deg); 41 | } 42 | } 43 | 44 | @font-face{ 45 | font-family: text-security-disc; 46 | src: url("assets/text-security-disc.woff") format("woff"); 47 | } -------------------------------------------------------------------------------- /src/backend/langflow/interface/tools/custom.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Optional 2 | 3 | from pydantic import BaseModel, validator 4 | 5 | from langflow.utils import validate 6 | 7 | 8 | class Function(BaseModel): 9 | code: str 10 | function: Optional[Callable] = None 11 | imports: Optional[str] = None 12 | 13 | # Eval code and store the function 14 | def __init__(self, **data): 15 | super().__init__(**data) 16 | 17 | # Validate the function 18 | @validator("code") 19 | def validate_func(cls, v): 20 | try: 21 | validate.eval_function(v) 22 | except Exception as e: 23 | raise e 24 | 25 | return v 26 | 27 | def get_function(self): 28 | """Get the function""" 29 | function_name = validate.extract_function_name(self.code) 30 | 31 | return validate.create_function(self.code, function_name) 32 | 33 | 34 | class PythonFunction(Function): 35 | """Python function""" 36 | 37 | code: str 38 | -------------------------------------------------------------------------------- /src/backend/langflow/utils/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | 4 | from rich.logging import RichHandler 5 | 6 | logger = logging.getLogger("langflow") 7 | 8 | 9 | def configure(log_level: str = "INFO", log_file: Path = None): # type: ignore 10 | log_format = "%(asctime)s - %(levelname)s - %(message)s" 11 | log_level_value = getattr(logging, log_level.upper(), logging.INFO) 12 | 13 | logging.basicConfig( 14 | level=log_level_value, 15 | format=log_format, 16 | datefmt="[%X]", 17 | handlers=[RichHandler(rich_tracebacks=True)], 18 | ) 19 | 20 | if log_file: 21 | log_file = Path(log_file) 22 | log_file.parent.mkdir(parents=True, exist_ok=True) 23 | 24 | file_handler = logging.FileHandler(log_file) 25 | file_handler.setFormatter(logging.Formatter(log_format)) 26 | logger.addHandler(file_handler) 27 | 28 | logger.info(f"Logger set up with log level: {log_level_value}({log_level})") 29 | if log_file: 30 | logger.info(f"Log file: {log_file}") 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Logspace 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/frontend/src/types/api/index.ts: -------------------------------------------------------------------------------- 1 | import { Node,Edge,Viewport } from "reactflow" 2 | //kind and class are just representative names to represent the actual structure of the object received by the API 3 | 4 | export type APIObjectType = {kind:APIKindType,[key:string]:APIKindType} 5 | export type APIKindType= {class:APIClassType,[key:string]:APIClassType} 6 | export type APITemplateType = {variable:TemplateVariableType,[key:string]:TemplateVariableType} 7 | export type APIClassType ={base_classes:Array,description:string,template:APITemplateType,[key:string]:Array|string|APITemplateType} 8 | export type TemplateVariableType = {type:string,required:boolean,placeholder?:string,list:boolean,show:boolean,multiline?:boolean,value?:any,[key:string]:any} 9 | export type sendAllProps={ 10 | nodes: Node[]; 11 | edges: Edge[]; 12 | name:string, 13 | description:string; 14 | viewport: Viewport; 15 | message:string; 16 | 17 | chatHistory:{message:string,isSend:boolean}[], 18 | }; 19 | export type errorsTypeAPI={function:{errors:Array},imports:{errors:Array}} 20 | export type PromptTypeAPI = {input_variables:Array} -------------------------------------------------------------------------------- /src/frontend/src/components/floatComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { FloatComponentType } from "../../types/components"; 3 | 4 | export default function FloatComponent({value, onChange, disabled}: FloatComponentType){ 5 | const [myValue, setMyValue] = useState(value ?? ""); 6 | useEffect(()=> { 7 | if(disabled){ 8 | setMyValue(""); 9 | onChange(""); 10 | } 11 | }, [disabled, onChange]) 12 | return ( 13 |
14 | { 20 | setMyValue(e.target.value); 21 | onChange(e.target.value); 22 | }} 23 | /> 24 |
25 | ); 26 | } -------------------------------------------------------------------------------- /src/frontend/src/components/inputComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { InputComponentType } from "../../types/components"; 3 | import { classNames } from "../../utils"; 4 | 5 | export default function InputComponent({ 6 | value, 7 | onChange, 8 | disabled, 9 | password, 10 | }: InputComponentType) { 11 | const [myValue, setMyValue] = useState(value ?? ""); 12 | useEffect(() => { 13 | if (disabled) { 14 | setMyValue(""); 15 | onChange(""); 16 | } 17 | }, [disabled, onChange]); 18 | return ( 19 |
20 | 0?"password":"" 27 | )} 28 | placeholder="Type a text" 29 | onChange={(e) => { 30 | setMyValue(e.target.value); 31 | onChange(e.target.value); 32 | }} 33 | /> 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/wrappers/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional 2 | 3 | from langchain import requests 4 | 5 | from langflow.interface.base import LangChainTypeCreator 6 | from langflow.utils.util import build_template_from_class 7 | from langflow.utils.logger import logger 8 | 9 | 10 | class WrapperCreator(LangChainTypeCreator): 11 | type_name: str = "wrappers" 12 | 13 | @property 14 | def type_to_loader_dict(self) -> Dict: 15 | if self.type_dict is None: 16 | self.type_dict = { 17 | wrapper.__name__: wrapper for wrapper in [requests.TextRequestsWrapper] 18 | } 19 | return self.type_dict 20 | 21 | def get_signature(self, name: str) -> Optional[Dict]: 22 | try: 23 | return build_template_from_class(name, self.type_to_loader_dict) 24 | except ValueError as exc: 25 | raise ValueError("Wrapper not found") from exc 26 | except AttributeError as exc: 27 | logger.error(f"Wrapper {name} not loaded: {exc}") 28 | 29 | def to_list(self) -> List[str]: 30 | return list(self.type_to_loader_dict.keys()) 31 | 32 | 33 | wrapper_creator = WrapperCreator() 34 | -------------------------------------------------------------------------------- /src/backend/langflow/api/validate.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | 3 | from langflow.api.base import ( 4 | Code, 5 | CodeValidationResponse, 6 | Prompt, 7 | PromptValidationResponse, 8 | validate_prompt, 9 | ) 10 | from langflow.utils.logger import logger 11 | from langflow.utils.validate import validate_code 12 | 13 | # build router 14 | router = APIRouter(prefix="/validate", tags=["validate"]) 15 | 16 | 17 | @router.post("/code", status_code=200, response_model=CodeValidationResponse) 18 | def post_validate_code(code: Code): 19 | try: 20 | errors = validate_code(code.code) 21 | return CodeValidationResponse( 22 | imports=errors.get("imports", {}), 23 | function=errors.get("function", {}), 24 | ) 25 | except Exception as e: 26 | return HTTPException(status_code=500, detail=str(e)) 27 | 28 | 29 | @router.post("/prompt", status_code=200, response_model=PromptValidationResponse) 30 | def post_validate_prompt(prompt: Prompt): 31 | try: 32 | return validate_prompt(prompt.template) 33 | except Exception as e: 34 | logger.exception(e) 35 | raise HTTPException(status_code=500, detail=str(e)) from e 36 | -------------------------------------------------------------------------------- /src/frontend/src/contexts/typesContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, ReactNode, useState } from "react"; 2 | import { Node} from "reactflow"; 3 | import { typesContextType } from "../types/typesContext"; 4 | 5 | //context to share types adn functions from nodes to flow 6 | 7 | const initialValue:typesContextType = { 8 | reactFlowInstance: null, 9 | setReactFlowInstance: () => {}, 10 | deleteNode: () => {}, 11 | types: {}, 12 | setTypes: () => {}, 13 | }; 14 | 15 | export const typesContext = createContext(initialValue); 16 | 17 | export function TypesProvider({ children }:{children:ReactNode}) { 18 | const [types, setTypes] = useState({}); 19 | const [reactFlowInstance, setReactFlowInstance] = useState(null); 20 | function deleteNode(idx:string) { 21 | reactFlowInstance.setNodes( 22 | reactFlowInstance.getNodes().filter((n:Node) => n.id !== idx) 23 | ); 24 | reactFlowInstance.setEdges(reactFlowInstance.getEdges().filter((ns) => ns.source !== idx && ns.target !== idx)); 25 | } 26 | return ( 27 | 36 | {children} 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/universal 3 | { 4 | "name": "Default Linux Universal", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/universal:2-linux", 7 | "features": { 8 | "ghcr.io/devcontainers/features/aws-cli:1": {} 9 | }, 10 | "customizations": { 11 | "vscode": {"extensions": [ 12 | "actboy168.tasks", 13 | "GitHub.copilot", 14 | "ms-python.python", 15 | "sourcery.sourcery", 16 | "eamodio.gitlens" 17 | ]} 18 | } 19 | 20 | // Features to add to the dev container. More info: https://containers.dev/features. 21 | // "features": {}, 22 | 23 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 24 | // "forwardPorts": [], 25 | 26 | // Use 'postCreateCommand' to run commands after the container is created. 27 | // "postCreateCommand": "uname -a", 28 | 29 | // Configure tool-specific properties. 30 | // "customizations": {}, 31 | 32 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 33 | // "remoteUser": "root" 34 | } 35 | -------------------------------------------------------------------------------- /src/frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | const plugin = require('tailwindcss/plugin') 3 | module.exports = { 4 | content: ["./src/**/*.{js,ts,tsx,jsx}"], 5 | darkMode: 'class', 6 | important:true, 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [ 11 | require("@tailwindcss/forms")({ 12 | strategy: 'class', // only generate classes 13 | }), 14 | plugin(function ({ addUtilities }) { 15 | addUtilities({ 16 | '.scrollbar-hide': { 17 | /* IE and Edge */ 18 | '-ms-overflow-style': 'none', 19 | /* Firefox */ 20 | 'scrollbar-width': 'none', 21 | /* Safari and Chrome */ 22 | '&::-webkit-scrollbar': { 23 | display: 'none' 24 | } 25 | }, 26 | '.arrow-hide':{ 27 | '&::-webkit-inner-spin-button':{ 28 | '-webkit-appearance': 'none', 29 | 'margin': 0 30 | }, 31 | '&::-webkit-outer-spin-button':{ 32 | '-webkit-appearance': 'none', 33 | 'margin': 0 34 | }, 35 | }, 36 | '.password':{ 37 | "-webkit-text-security":"disc", 38 | "font-family": "text-security-disc" 39 | 40 | } 41 | } 42 | ) 43 | }) 44 | ], 45 | } 46 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/embeddings/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional 2 | 3 | from langflow.interface.base import LangChainTypeCreator 4 | from langflow.interface.custom_lists import embedding_type_to_cls_dict 5 | from langflow.settings import settings 6 | from langflow.utils.util import build_template_from_class 7 | from langflow.utils.logger import logger 8 | 9 | 10 | class EmbeddingCreator(LangChainTypeCreator): 11 | type_name: str = "embeddings" 12 | 13 | @property 14 | def type_to_loader_dict(self) -> Dict: 15 | return embedding_type_to_cls_dict 16 | 17 | def get_signature(self, name: str) -> Optional[Dict]: 18 | """Get the signature of an embedding.""" 19 | try: 20 | return build_template_from_class(name, embedding_type_to_cls_dict) 21 | except ValueError as exc: 22 | raise ValueError(f"Embedding {name} not found") from exc 23 | 24 | except AttributeError as exc: 25 | logger.error(f"Embedding {name} not loaded: {exc}") 26 | 27 | def to_list(self) -> List[str]: 28 | return [ 29 | embedding.__name__ 30 | for embedding in self.type_to_loader_dict.values() 31 | if embedding.__name__ in settings.embeddings or settings.dev 32 | ] 33 | 34 | 35 | embedding_creator = EmbeddingCreator() 36 | -------------------------------------------------------------------------------- /src/frontend/src/components/CrashErrorComponent/index.tsx: -------------------------------------------------------------------------------- 1 | export default function CrashErrorComponent({ error, resetErrorBoundary }) { 2 | return ( 3 |
4 |
5 |

Oops! An unknown error has occurred.

6 |

7 | Please click the 'Reset Application' button 8 | to restore the application's state. If the error persists, please 9 | create an issue on our GitHub page. We apologize for any inconvenience 10 | this may have caused. 11 |

12 |
13 | 19 | 25 | Create Issue 26 | 27 |
28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/frontend/src/components/intComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { FloatComponentType } from "../../types/components"; 3 | 4 | export default function IntComponent({ 5 | value, 6 | onChange, 7 | disabled, 8 | }: FloatComponentType) { 9 | const [myValue, setMyValue] = useState(value ?? ""); 10 | useEffect(() => { 11 | if (disabled) { 12 | setMyValue(""); 13 | onChange(""); 14 | } 15 | }, [disabled, onChange]); 16 | return ( 17 |
18 | { 20 | if (event.key !== 'Backspace' && event.key !== 'Enter' && event.key !== 'Delete' && event.key !== 'ArrowLeft' && event.key !== 'ArrowRight' && !/^[-]?\d*$/.test(event.key)) { 21 | event.preventDefault(); 22 | } 23 | }} 24 | type="number" 25 | value={myValue} 26 | className={ 27 | "block w-full form-input dark:bg-gray-900 arrow-hide dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + 28 | (disabled ? " bg-gray-200 dark:bg-gray-700" : "") 29 | } 30 | placeholder="Type a integer number" 31 | onChange={(e) => { 32 | setMyValue(e.target.value); 33 | onChange(e.target.value); 34 | }} 35 | /> 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | branches: 8 | - main 9 | paths: 10 | - "pyproject.toml" 11 | 12 | env: 13 | POETRY_VERSION: "1.4.0" 14 | 15 | jobs: 16 | if_release: 17 | if: | 18 | ${{ github.event.pull_request.merged == true }} 19 | && ${{ contains(github.event.pull_request.labels.*.name, 'Release') }} 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Install poetry 24 | run: pipx install poetry==$POETRY_VERSION 25 | - name: Set up Python 3.10 26 | uses: actions/setup-python@v4 27 | with: 28 | python-version: "3.10" 29 | cache: "poetry" 30 | - name: Build project for distribution 31 | run: make build 32 | - name: Check Version 33 | id: check-version 34 | run: | 35 | echo version=$(poetry version --short) >> $GITHUB_OUTPUT 36 | - name: Create Release 37 | uses: ncipollo/release-action@v1 38 | with: 39 | artifacts: "dist/*" 40 | token: ${{ secrets.GITHUB_TOKEN }} 41 | draft: false 42 | generateReleaseNotes: true 43 | tag: v${{ steps.check-version.outputs.version }} 44 | commit: main 45 | - name: Publish to PyPI 46 | env: 47 | POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_API_TOKEN }} 48 | run: | 49 | poetry publish 50 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/llms/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional, Type 2 | 3 | from langflow.interface.base import LangChainTypeCreator 4 | from langflow.interface.custom_lists import llm_type_to_cls_dict 5 | from langflow.settings import settings 6 | from langflow.template.nodes import LLMFrontendNode 7 | from langflow.utils.util import build_template_from_class 8 | from langflow.utils.logger import logger 9 | 10 | 11 | class LLMCreator(LangChainTypeCreator): 12 | type_name: str = "llms" 13 | 14 | @property 15 | def frontend_node_class(self) -> Type[LLMFrontendNode]: 16 | return LLMFrontendNode 17 | 18 | @property 19 | def type_to_loader_dict(self) -> Dict: 20 | if self.type_dict is None: 21 | self.type_dict = llm_type_to_cls_dict 22 | return self.type_dict 23 | 24 | def get_signature(self, name: str) -> Optional[Dict]: 25 | """Get the signature of an llm.""" 26 | try: 27 | return build_template_from_class(name, llm_type_to_cls_dict) 28 | except ValueError as exc: 29 | raise ValueError("LLM not found") from exc 30 | 31 | except AttributeError as exc: 32 | logger.error(f"LLM {name} not loaded: {exc}") 33 | 34 | def to_list(self) -> List[str]: 35 | return [ 36 | llm.__name__ 37 | for llm in self.type_to_loader_dict.values() 38 | if llm.__name__ in settings.llms or settings.dev 39 | ] 40 | 41 | 42 | llm_creator = LLMCreator() 43 | -------------------------------------------------------------------------------- /src/frontend/src/alerts/success/index.tsx: -------------------------------------------------------------------------------- 1 | import { Transition } from "@headlessui/react"; 2 | import { CheckCircleIcon, XMarkIcon } from "@heroicons/react/24/outline"; 3 | import { useEffect, useState } from "react"; 4 | import { SuccessAlertType } from "../../types/alerts"; 5 | 6 | export default function SuccessAlert({ 7 | title, 8 | id, 9 | removeAlert, 10 | }: SuccessAlertType) { 11 | const [show, setShow] = useState(true); 12 | useEffect(() => { 13 | if (show) { 14 | setTimeout(() => { 15 | setShow(false); 16 | setTimeout(() => { 17 | removeAlert(id); 18 | }, 500); 19 | }, 5000); 20 | } 21 | }, [id, removeAlert, show]); 22 | return ( 23 | 32 |
{ 34 | setShow(false); 35 | removeAlert(id); 36 | }} 37 | className="rounded-md w-96 mt-6 shadow-xl bg-green-50 p-4" 38 | > 39 |
40 |
41 |
46 |
47 |

{title}

48 |
49 |
50 |
51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /src/frontend/src/components/textAreaComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; 2 | import { useContext, useEffect, useState } from "react"; 3 | import { PopUpContext } from "../../contexts/popUpContext"; 4 | import CodeAreaModal from "../../modals/codeAreaModal"; 5 | import TextAreaModal from "../../modals/textAreaModal"; 6 | import { TextAreaComponentType } from "../../types/components"; 7 | 8 | export default function TextAreaComponent({ value, onChange, disabled }:TextAreaComponentType) { 9 | const [myValue, setMyValue] = useState(value); 10 | const { openPopUp } = useContext(PopUpContext); 11 | useEffect(() => { 12 | if (disabled) { 13 | setMyValue(""); 14 | onChange(""); 15 | } 16 | }, [disabled, onChange]); 17 | return ( 18 |
19 |
20 | 26 | {myValue !== "" ? myValue : 'Text empty'} 27 | 28 | 31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/frontend/src/pages/FlowPage/components/DisclosureComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ChevronRightIcon, 3 | } from "@heroicons/react/24/solid"; 4 | import { Disclosure } from "@headlessui/react"; 5 | import { DisclosureComponentType } from "../../../../types/components"; 6 | 7 | export default function DisclosureComponent({ 8 | button: { title, Icon, buttons = [] }, 9 | children, 10 | }: DisclosureComponentType 11 | ) { 12 | return ( 13 | 14 | {({ open }) => ( 15 | <> 16 |
17 | 18 |
19 | 20 | 21 | {title} 22 | 23 |
24 |
25 | {buttons.map((x, index) => ( 26 | 29 | ))} 30 |
31 | 36 |
37 |
38 |
39 |
40 | 41 | {children} 42 | 43 | 44 | )} 45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/frontend/src/modals/importModal/buttonBox/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { DocumentDuplicateIcon } from "@heroicons/react/solid"; 3 | import { classNames } from "../../../utils"; 4 | 5 | export default function ButtonBox({ 6 | onClick, 7 | title, 8 | description, 9 | icon, 10 | bgColor, 11 | textColor, 12 | deactivate 13 | }: { 14 | onClick: () => void; 15 | title: string; 16 | description: string; 17 | icon: ReactNode; 18 | bgColor: string; 19 | textColor: string; 20 | deactivate?:boolean; 21 | }) { 22 | return ( 23 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/agents/prebuilt.py: -------------------------------------------------------------------------------- 1 | from langchain import LLMChain 2 | from langchain.agents import AgentExecutor, ZeroShotAgent 3 | from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX 4 | from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit 5 | from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS 6 | from langchain.schema import BaseLanguageModel 7 | 8 | 9 | class MalfoyAgent(AgentExecutor): 10 | """Json agent""" 11 | 12 | prefix = "Malfoy: " 13 | 14 | @classmethod 15 | def initialize(cls, *args, **kwargs): 16 | return cls.from_toolkit_and_llm(*args, **kwargs) 17 | 18 | def __init__(self, *args, **kwargs): 19 | super().__init__(*args, **kwargs) 20 | 21 | @classmethod 22 | def from_toolkit_and_llm(cls, toolkit: JsonToolkit, llm: BaseLanguageModel): 23 | tools = toolkit.get_tools() 24 | tool_names = [tool.name for tool in tools] 25 | prompt = ZeroShotAgent.create_prompt( 26 | tools, 27 | prefix=JSON_PREFIX, 28 | suffix=JSON_SUFFIX, 29 | format_instructions=FORMAT_INSTRUCTIONS, 30 | input_variables=None, 31 | ) 32 | llm_chain = LLMChain( 33 | llm=llm, 34 | prompt=prompt, 35 | ) 36 | agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names) 37 | return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True) 38 | 39 | def run(self, *args, **kwargs): 40 | return super().run(*args, **kwargs) 41 | 42 | 43 | PREBUILT_AGENTS = { 44 | "MalfoyAgent": MalfoyAgent, 45 | } 46 | -------------------------------------------------------------------------------- /src/frontend/src/components/loadingComponent/index.tsx: -------------------------------------------------------------------------------- 1 | type LoadingComponentProps={ 2 | remSize:number 3 | } 4 | 5 | 6 | export default function LoadingComponent({remSize}:LoadingComponentProps){ 7 | return( 8 |
9 | 13 | Loading... 14 |
15 | ) 16 | } -------------------------------------------------------------------------------- /src/frontend/src/components/promptComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; 2 | import { useContext, useEffect, useState } from "react"; 3 | import { PopUpContext } from "../../contexts/popUpContext"; 4 | import CodeAreaModal from "../../modals/codeAreaModal"; 5 | import TextAreaModal from "../../modals/textAreaModal"; 6 | import { TextAreaComponentType } from "../../types/components"; 7 | import PromptAreaModal from "../../modals/promptModal"; 8 | 9 | export default function PromptAreaComponent({ value, onChange, disabled }:TextAreaComponentType) { 10 | const [myValue, setMyValue] = useState(value); 11 | const { openPopUp } = useContext(PopUpContext); 12 | useEffect(() => { 13 | if (disabled) { 14 | setMyValue(""); 15 | onChange(""); 16 | } 17 | }, [disabled, onChange]); 18 | return ( 19 |
20 |
21 | 27 | {myValue !== "" ? myValue : 'Text empty'} 28 | 29 | 32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/memories/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional, Type 2 | 3 | from langflow.interface.base import LangChainTypeCreator 4 | from langflow.interface.custom_lists import memory_type_to_cls_dict 5 | from langflow.settings import settings 6 | from langflow.template.base import FrontendNode 7 | from langflow.template.nodes import MemoryFrontendNode 8 | from langflow.utils.logger import logger 9 | from langflow.utils.util import build_template_from_class 10 | 11 | 12 | class MemoryCreator(LangChainTypeCreator): 13 | type_name: str = "memories" 14 | 15 | @property 16 | def frontend_node_class(self) -> Type[FrontendNode]: 17 | """The class type of the FrontendNode created in frontend_node.""" 18 | return MemoryFrontendNode 19 | 20 | @property 21 | def type_to_loader_dict(self) -> Dict: 22 | if self.type_dict is None: 23 | self.type_dict = memory_type_to_cls_dict 24 | return self.type_dict 25 | 26 | def get_signature(self, name: str) -> Optional[Dict]: 27 | """Get the signature of a memory.""" 28 | try: 29 | return build_template_from_class(name, memory_type_to_cls_dict) 30 | except ValueError as exc: 31 | raise ValueError("Memory not found") from exc 32 | except AttributeError as exc: 33 | logger.error(f"Memory {name} not loaded: {exc}") 34 | 35 | def to_list(self) -> List[str]: 36 | return [ 37 | memory.__name__ 38 | for memory in self.type_to_loader_dict.values() 39 | if memory.__name__ in settings.memories or settings.dev 40 | ] 41 | 42 | 43 | memory_creator = MemoryCreator() 44 | -------------------------------------------------------------------------------- /src/frontend/src/components/codeAreaComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; 2 | import { useContext, useEffect, useState } from "react"; 3 | import { PopUpContext } from "../../contexts/popUpContext"; 4 | import CodeAreaModal from "../../modals/codeAreaModal"; 5 | import TextAreaModal from "../../modals/textAreaModal"; 6 | import { TextAreaComponentType } from "../../types/components"; 7 | 8 | export default function CodeAreaComponent({ 9 | value, 10 | onChange, 11 | disabled, 12 | }: TextAreaComponentType) { 13 | const [myValue, setMyValue] = useState(value); 14 | const { openPopUp } = useContext(PopUpContext); 15 | useEffect(() => { 16 | if (disabled) { 17 | setMyValue(""); 18 | onChange(""); 19 | } 20 | }, [disabled, onChange]); 21 | return ( 22 |
23 |
24 | 30 | {myValue !== "" ? myValue : "Text empty"} 31 | 32 | 47 |
48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/backend/build.Dockerfile: -------------------------------------------------------------------------------- 1 | # `python-base` sets up all our shared environment variables 2 | FROM python:3.10-slim 3 | 4 | # python 5 | ENV PYTHONUNBUFFERED=1 \ 6 | # prevents python creating .pyc files 7 | PYTHONDONTWRITEBYTECODE=1 \ 8 | \ 9 | # pip 10 | PIP_NO_CACHE_DIR=off \ 11 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 12 | PIP_DEFAULT_TIMEOUT=100 \ 13 | \ 14 | # poetry 15 | # https://python-poetry.org/docs/configuration/#using-environment-variables 16 | POETRY_VERSION=1.4.0 \ 17 | # make poetry install to this location 18 | POETRY_HOME="/opt/poetry" \ 19 | # make poetry create the virtual environment in the project's root 20 | # it gets named `.venv` 21 | POETRY_VIRTUALENVS_IN_PROJECT=true \ 22 | # do not ask any interactive question 23 | POETRY_NO_INTERACTION=1 \ 24 | \ 25 | # paths 26 | # this is where our requirements + virtual environment will live 27 | PYSETUP_PATH="/opt/pysetup" \ 28 | VENV_PATH="/opt/pysetup/.venv" 29 | 30 | # prepend poetry and venv to path 31 | ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" 32 | 33 | RUN apt-get update \ 34 | && apt-get install --no-install-recommends -y \ 35 | # deps for installing poetry 36 | curl \ 37 | # deps for building python deps 38 | build-essential libpq-dev 39 | 40 | # install poetry - respects $POETRY_VERSION & $POETRY_HOME 41 | RUN curl -sSL https://install.python-poetry.org | python3 - 42 | 43 | # copy project requirement files here to ensure they will be cached. 44 | WORKDIR /app 45 | COPY poetry.lock pyproject.toml ./ 46 | COPY langflow/ ./langflow 47 | 48 | # poetry install 49 | RUN poetry install --without dev 50 | 51 | # build wheel 52 | RUN poetry build -f wheel 53 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/listing.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.agents.base import agent_creator 2 | from langflow.interface.chains.base import chain_creator 3 | from langflow.interface.document_loaders.base import documentloader_creator 4 | from langflow.interface.embeddings.base import embedding_creator 5 | from langflow.interface.llms.base import llm_creator 6 | from langflow.interface.memories.base import memory_creator 7 | from langflow.interface.prompts.base import prompt_creator 8 | from langflow.interface.text_splitters.base import textsplitter_creator 9 | from langflow.interface.toolkits.base import toolkits_creator 10 | from langflow.interface.tools.base import tool_creator 11 | from langflow.interface.vectorstore.base import vectorstore_creator 12 | from langflow.interface.wrappers.base import wrapper_creator 13 | 14 | 15 | def get_type_dict(): 16 | return { 17 | "agents": agent_creator.to_list(), 18 | "prompts": prompt_creator.to_list(), 19 | "llms": llm_creator.to_list(), 20 | "tools": tool_creator.to_list(), 21 | "chains": chain_creator.to_list(), 22 | "memory": memory_creator.to_list(), 23 | "toolkits": toolkits_creator.to_list(), 24 | "wrappers": wrapper_creator.to_list(), 25 | "documentLoaders": documentloader_creator.to_list(), 26 | "vectorStore": vectorstore_creator.to_list(), 27 | "embeddings": embedding_creator.to_list(), 28 | "textSplitters": textsplitter_creator.to_list(), 29 | } 30 | 31 | 32 | LANGCHAIN_TYPES_DICT = get_type_dict() 33 | 34 | # Now we'll build a dict with Langchain types and ours 35 | 36 | ALL_TYPES_DICT = { 37 | **LANGCHAIN_TYPES_DICT, 38 | "Custom": ["Custom Tool", "Python Function"], 39 | } 40 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "langflow" 3 | version = "0.0.55" 4 | description = "A Python package with a built-in web application" 5 | authors = ["Logspace "] 6 | maintainers = [ 7 | "Gabriel Almeida ", 8 | "Ibis Prevedello ", 9 | "Lucas Eduoli ", 10 | "Otávio Anovazzi ", 11 | ] 12 | repository = "https://github.com/logspace-ai/langflow" 13 | license = "MIT" 14 | readme = "README.md" 15 | keywords = ["nlp", "langchain", "openai", "gpt", "gui"] 16 | packages = [{ include = "langflow", from = "src/backend" }] 17 | include = ["src/backend/langflow/*", "src/backend/langflow/**/*"] 18 | 19 | 20 | [tool.poetry.scripts] 21 | langflow = "langflow.__main__:main" 22 | 23 | [tool.poetry.dependencies] 24 | python = "^3.9" 25 | fastapi = "^0.92.0" 26 | uvicorn = "^0.20.0" 27 | beautifulsoup4 = "^4.11.2" 28 | google-search-results = "^2.4.1" 29 | google-api-python-client = "^2.79.0" 30 | typer = "^0.7.0" 31 | gunicorn = "^20.1.0" 32 | langchain = "^0.0.131" 33 | openai = "^0.27.2" 34 | types-pyyaml = "^6.0.12.8" 35 | dill = "^0.3.6" 36 | pandas = "^1.5.3" 37 | chromadb = "^0.3.21" 38 | huggingface-hub = "^0.13.3" 39 | rich = "^13.3.3" 40 | llama-cpp-python = "0.1.23" 41 | networkx = "^3.1" 42 | unstructured = "^0.5.11" 43 | pypdf = "^3.7.1" 44 | lxml = "^4.9.2" 45 | pysrt = "^1.1.2" 46 | fake-useragent = "^1.1.3" 47 | 48 | [tool.poetry.group.dev.dependencies] 49 | black = "^23.1.0" 50 | ipykernel = "^6.21.2" 51 | mypy = "^1.1.1" 52 | ruff = "^0.0.254" 53 | httpx = "^0.23.3" 54 | pytest = "^7.2.2" 55 | types-requests = "^2.28.11" 56 | requests = "^2.28.0" 57 | 58 | [tool.ruff] 59 | line-length = 120 60 | 61 | [build-system] 62 | requires = ["poetry-core"] 63 | build-backend = "poetry.core.masonry.api" 64 | -------------------------------------------------------------------------------- /src/frontend/src/types/components/index.ts: -------------------------------------------------------------------------------- 1 | import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react"; 2 | import { NodeDataType } from "../flow/index"; 3 | export type InputComponentType = { 4 | value: string; 5 | disabled?: boolean; 6 | onChange: (value: string) => void; 7 | password: boolean; 8 | }; 9 | export type ToggleComponentType = { 10 | enabled: boolean; 11 | setEnabled: (state: boolean) => void; 12 | disabled: boolean; 13 | }; 14 | export type DropDownComponentType = { 15 | value: string; 16 | options: string[]; 17 | onSelect: (value: string) => void; 18 | }; 19 | export type ParameterComponentType = { 20 | data: NodeDataType; 21 | title: string; 22 | id: string; 23 | color: string; 24 | left: boolean; 25 | type: string; 26 | required?: boolean; 27 | name?: string; 28 | tooltipTitle: string; 29 | }; 30 | export type InputListComponentType = { 31 | value: string[]; 32 | onChange: (value: string[]) => void; 33 | disabled: boolean; 34 | }; 35 | 36 | export type TextAreaComponentType = { 37 | disabled: boolean; 38 | onChange: (value: string[] | string) => void; 39 | value: string; 40 | }; 41 | 42 | export type FileComponentType = { 43 | disabled: boolean; 44 | onChange: (value: string[] | string) => void; 45 | value: string; 46 | suffixes:Array; 47 | fileTypes:Array; 48 | onFileChange:(value: string) => void; 49 | }; 50 | 51 | export type DisclosureComponentType = { 52 | children: ReactNode; 53 | button: { 54 | title: string; 55 | Icon: ForwardRefExoticComponent>; 56 | buttons?: { 57 | Icon: ReactElement; 58 | title: string; 59 | onClick: (event?: React.MouseEvent) => void; 60 | }[]; 61 | }; 62 | }; 63 | export type FloatComponentType = { 64 | value: string; 65 | disabled?: boolean; 66 | onChange: (value: string) => void; 67 | }; 68 | -------------------------------------------------------------------------------- /tests/test_creators.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | 3 | import pytest 4 | from langflow.interface.agents.base import AgentCreator 5 | from langflow.interface.base import LangChainTypeCreator 6 | 7 | 8 | @pytest.fixture 9 | def sample_lang_chain_type_creator() -> LangChainTypeCreator: 10 | class SampleLangChainTypeCreator(LangChainTypeCreator): 11 | type_name: str = "test_type" 12 | 13 | def type_to_loader_dict(self) -> Dict: # type: ignore 14 | return {"test_type": "TestClass"} 15 | 16 | def to_list(self) -> List[str]: 17 | return ["node1", "node2"] 18 | 19 | def get_signature(self, name: str) -> Dict: 20 | return { 21 | "template": {"test_field": {"type": "str"}}, 22 | "description": "test description", 23 | "base_classes": ["base_class1", "base_class2"], 24 | } 25 | 26 | return SampleLangChainTypeCreator() 27 | 28 | 29 | @pytest.fixture 30 | def sample_agent_creator() -> AgentCreator: 31 | return AgentCreator() 32 | 33 | 34 | def test_lang_chain_type_creator_to_dict( 35 | sample_lang_chain_type_creator: LangChainTypeCreator, 36 | ): 37 | type_dict = sample_lang_chain_type_creator.to_dict() 38 | assert len(type_dict) == 1 39 | assert "test_type" in type_dict 40 | assert "node1" in type_dict["test_type"] 41 | assert "node2" in type_dict["test_type"] 42 | assert "template" in type_dict["test_type"]["node1"] 43 | assert "description" in type_dict["test_type"]["node1"] 44 | assert "base_classes" in type_dict["test_type"]["node1"] 45 | 46 | 47 | def test_agent_creator_type_to_loader_dict(sample_agent_creator: AgentCreator): 48 | type_to_loader_dict = sample_agent_creator.type_to_loader_dict 49 | assert len(type_to_loader_dict) > 0 50 | assert "JsonAgent" 51 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM logspace/backend_build as backend_build 2 | FROM logspace/frontend_build as frontend_build 3 | 4 | # `python-base` sets up all our shared environment variables 5 | FROM python:3.10-slim as langflow_build 6 | 7 | # python 8 | ENV PYTHONUNBUFFERED=1 \ 9 | # prevents python creating .pyc files 10 | PYTHONDONTWRITEBYTECODE=1 \ 11 | \ 12 | # pip 13 | PIP_NO_CACHE_DIR=off \ 14 | PIP_DISABLE_PIP_VERSION_CHECK=on \ 15 | PIP_DEFAULT_TIMEOUT=100 \ 16 | \ 17 | # poetry 18 | # https://python-poetry.org/docs/configuration/#using-environment-variables 19 | POETRY_VERSION=1.4.0 \ 20 | # make poetry install to this location 21 | POETRY_HOME="/opt/poetry" \ 22 | # make poetry create the virtual environment in the project's root 23 | # it gets named `.venv` 24 | POETRY_VIRTUALENVS_IN_PROJECT=true \ 25 | # do not ask any interactive question 26 | POETRY_NO_INTERACTION=1 \ 27 | \ 28 | # paths 29 | # this is where our requirements + virtual environment will live 30 | PYSETUP_PATH="/opt/pysetup" \ 31 | VENV_PATH="/opt/pysetup/.venv" 32 | 33 | # prepend poetry and venv to path 34 | ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" 35 | 36 | RUN apt-get update \ 37 | && apt-get install --no-install-recommends -y \ 38 | # deps for installing poetry 39 | curl \ 40 | # deps for building python deps 41 | build-essential libpq-dev git 42 | 43 | # install poetry - respects $POETRY_VERSION & $POETRY_HOME 44 | # RUN curl -sSL https://install.python-poetry.org | python3 - 45 | 46 | # copy project requirement files here to ensure they will be cached. 47 | WORKDIR /app 48 | COPY pyproject.toml ./ 49 | # copy langflow 50 | COPY ./langflow ./langflow 51 | 52 | # Copy files from frontend 53 | COPY --from=frontend_build /app/build /app/src/frontend/build/ 54 | 55 | RUN pip install . 56 | 57 | EXPOSE 5003 58 | 59 | CMD [ "langflow" ] 60 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/types.py: -------------------------------------------------------------------------------- 1 | from langflow.interface.agents.base import agent_creator 2 | from langflow.interface.chains.base import chain_creator 3 | from langflow.interface.document_loaders.base import documentloader_creator 4 | from langflow.interface.embeddings.base import embedding_creator 5 | from langflow.interface.llms.base import llm_creator 6 | from langflow.interface.memories.base import memory_creator 7 | from langflow.interface.prompts.base import prompt_creator 8 | from langflow.interface.text_splitters.base import textsplitter_creator 9 | from langflow.interface.toolkits.base import toolkits_creator 10 | from langflow.interface.tools.base import tool_creator 11 | from langflow.interface.vectorstore.base import vectorstore_creator 12 | from langflow.interface.wrappers.base import wrapper_creator 13 | 14 | 15 | def get_type_list(): 16 | """Get a list of all langchain types""" 17 | all_types = build_langchain_types_dict() 18 | 19 | # all_types.pop("tools") 20 | 21 | for key, value in all_types.items(): 22 | all_types[key] = [item["template"]["_type"] for item in value.values()] 23 | 24 | return all_types 25 | 26 | 27 | def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union 28 | """Build a dictionary of all langchain types""" 29 | 30 | all_types = {} 31 | 32 | creators = [ 33 | chain_creator, 34 | agent_creator, 35 | prompt_creator, 36 | llm_creator, 37 | memory_creator, 38 | tool_creator, 39 | toolkits_creator, 40 | wrapper_creator, 41 | embedding_creator, 42 | vectorstore_creator, 43 | documentloader_creator, 44 | textsplitter_creator, 45 | ] 46 | 47 | all_types = {} 48 | for creator in creators: 49 | created_types = creator.to_dict() 50 | if created_types[creator.type_name].values(): 51 | all_types.update(created_types) 52 | return all_types 53 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/text_splitters/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional 2 | 3 | from langflow.interface.base import LangChainTypeCreator 4 | from langflow.interface.custom_lists import textsplitter_type_to_cls_dict 5 | from langflow.settings import settings 6 | from langflow.utils.logger import logger 7 | from langflow.utils.util import build_template_from_class 8 | 9 | 10 | class TextSplitterCreator(LangChainTypeCreator): 11 | type_name: str = "textsplitters" 12 | 13 | @property 14 | def type_to_loader_dict(self) -> Dict: 15 | return textsplitter_type_to_cls_dict 16 | 17 | def get_signature(self, name: str) -> Optional[Dict]: 18 | """Get the signature of a text splitter.""" 19 | try: 20 | signature = build_template_from_class(name, textsplitter_type_to_cls_dict) 21 | 22 | signature["template"]["documents"] = { 23 | "type": "BaseLoader", 24 | "required": True, 25 | "show": True, 26 | "name": "documents", 27 | } 28 | 29 | signature["template"]["separator"] = { 30 | "type": "str", 31 | "required": True, 32 | "show": True, 33 | "value": ".", 34 | "name": "separator", 35 | "display_name": "Separator", 36 | } 37 | 38 | return signature 39 | except ValueError as exc: 40 | raise ValueError(f"Text Splitter {name} not found") from exc 41 | except AttributeError as exc: 42 | logger.error(f"Text Splitter {name} not loaded: {exc}") 43 | 44 | def to_list(self) -> List[str]: 45 | return [ 46 | textsplitter.__name__ 47 | for textsplitter in self.type_to_loader_dict.values() 48 | if textsplitter.__name__ in settings.textsplitters or settings.dev 49 | ] 50 | 51 | 52 | textsplitter_creator = TextSplitterCreator() 53 | -------------------------------------------------------------------------------- /src/frontend/src/alerts/error/index.tsx: -------------------------------------------------------------------------------- 1 | import { Transition } from "@headlessui/react"; 2 | import { XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline"; 3 | import { useEffect, useState } from "react"; 4 | import { ErrorAlertType } from "../../types/alerts"; 5 | 6 | export default function ErrorAlert({ 7 | title, 8 | list = [], 9 | id, 10 | removeAlert, 11 | }: ErrorAlertType) { 12 | const [show, setShow] = useState(true); 13 | useEffect(() => { 14 | if (show) { 15 | setTimeout(() => { 16 | setShow(false); 17 | setTimeout(() => { 18 | removeAlert(id); 19 | }, 500); 20 | }, 5000); 21 | } 22 | }, [id, removeAlert, show]); 23 | return ( 24 | 35 |
{ 37 | setShow(false); 38 | setTimeout(() => { 39 | removeAlert(id); 40 | }, 500); 41 | }} 42 | className="rounded-md w-96 mt-6 shadow-xl bg-red-50 p-4 cursor-pointer" 43 | > 44 |
45 |
46 |
48 |
49 |

{title}

50 | {list.length !== 0 ? ( 51 |
52 |
    53 | {list.map((item, index) => ( 54 |
  • {item}
  • 55 | ))} 56 |
57 |
58 | ) : ( 59 | <> 60 | )} 61 |
62 |
63 |
64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /src/frontend/src/alerts/notice/index.tsx: -------------------------------------------------------------------------------- 1 | import { Transition } from "@headlessui/react"; 2 | import { InformationCircleIcon, XMarkIcon } from "@heroicons/react/24/outline"; 3 | import { useEffect, useState } from "react"; 4 | import { Link } from "react-router-dom"; 5 | import { NoticeAlertType } from "../../types/alerts"; 6 | 7 | export default function NoticeAlert({ 8 | title, 9 | link = "", 10 | id, 11 | removeAlert, 12 | }: NoticeAlertType) { 13 | const [show, setShow] = useState(true); 14 | useEffect(() => { 15 | if (show) { 16 | setTimeout(() => { 17 | setShow(false); 18 | setTimeout(() => { 19 | removeAlert(id); 20 | }, 500); 21 | }, 5000); 22 | } 23 | }, [id, removeAlert, show]); 24 | return ( 25 | 34 |
{ 36 | setShow(false); 37 | removeAlert(id); 38 | }} 39 | className="rounded-md w-96 mt-6 shadow-xl bg-blue-50 p-4" 40 | > 41 |
42 |
43 |
48 |
49 |

{title}

50 |

51 | {link !== "" ? ( 52 | 56 | Details 57 | 58 | ) : ( 59 | <> 60 | )} 61 |

62 |
63 |
64 |
65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all format lint build 2 | 3 | all: help 4 | 5 | coverage: 6 | poetry run pytest --cov \ 7 | --cov-config=.coveragerc \ 8 | --cov-report xml \ 9 | --cov-report term-missing:skip-covered 10 | 11 | test: 12 | poetry run pytest tests 13 | 14 | format: 15 | poetry run black . 16 | poetry run ruff --select I --fix . 17 | 18 | lint: 19 | poetry run mypy . 20 | poetry run black . --check 21 | poetry run ruff . --fix 22 | 23 | install_frontend: 24 | cd src/frontend && npm install 25 | 26 | run_frontend: 27 | cd src/frontend && npm start 28 | 29 | run_backend: 30 | poetry run uvicorn langflow.main:app --port 5003 --reload 31 | 32 | build_frontend: 33 | cd src/frontend && CI='' npm run build 34 | cp -r src/frontend/build src/backend/langflow/frontend 35 | 36 | build: 37 | make install_frontend 38 | make build_frontend 39 | poetry build --format sdist 40 | rm -rf src/backend/langflow/frontend 41 | 42 | dev: 43 | make install_frontend 44 | ifeq ($(build),1) 45 | @echo 'Running docker compose up with build' 46 | docker compose $(if $(debug),-f docker-compose.debug.yml) up --build 47 | else 48 | @echo 'Running docker compose up without build' 49 | docker compose $(if $(debug),-f docker-compose.debug.yml) up 50 | endif 51 | 52 | publish: 53 | make build 54 | poetry publish 55 | 56 | help: 57 | @echo '----' 58 | @echo 'format - run code formatters' 59 | @echo 'lint - run linters' 60 | @echo 'install_frontend - install the frontend dependencies' 61 | @echo 'build_frontend - build the frontend static files' 62 | @echo 'run_frontend - run the frontend in development mode' 63 | @echo 'run_backend - run the backend in development mode' 64 | @echo 'build - build the frontend static files and package the project' 65 | @echo 'publish - build the frontend static files and package the project and publish it to PyPI' 66 | @echo 'dev - run the project in development mode with docker compose' 67 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/agents/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional 2 | 3 | from langchain.agents import loading 4 | 5 | from langflow.custom.customs import get_custom_nodes 6 | from langflow.interface.agents.custom import CUSTOM_AGENTS 7 | from langflow.interface.base import LangChainTypeCreator 8 | from langflow.settings import settings 9 | from langflow.utils.logger import logger 10 | from langflow.utils.util import build_template_from_class 11 | 12 | 13 | class AgentCreator(LangChainTypeCreator): 14 | type_name: str = "agents" 15 | 16 | @property 17 | def type_to_loader_dict(self) -> Dict: 18 | if self.type_dict is None: 19 | self.type_dict = loading.AGENT_TO_CLASS 20 | # Add JsonAgent to the list of agents 21 | for name, agent in CUSTOM_AGENTS.items(): 22 | # TODO: validate AgentType 23 | self.type_dict[name] = agent # type: ignore 24 | return self.type_dict 25 | 26 | def get_signature(self, name: str) -> Optional[Dict]: 27 | try: 28 | if name in get_custom_nodes(self.type_name).keys(): 29 | return get_custom_nodes(self.type_name)[name] 30 | return build_template_from_class( 31 | name, self.type_to_loader_dict, add_function=True 32 | ) 33 | except ValueError as exc: 34 | raise ValueError("Agent not found") from exc 35 | except AttributeError as exc: 36 | logger.error(f"Agent {name} not loaded: {exc}") 37 | 38 | # Now this is a generator 39 | def to_list(self) -> List[str]: 40 | names = [] 41 | for _, agent in self.type_to_loader_dict.items(): 42 | agent_name = ( 43 | agent.function_name() 44 | if hasattr(agent, "function_name") 45 | else agent.__name__ 46 | ) 47 | if agent_name in settings.agents or settings.dev: 48 | names.append(agent_name) 49 | return names 50 | 51 | 52 | agent_creator = AgentCreator() 53 | -------------------------------------------------------------------------------- /src/backend/langflow/config.yaml: -------------------------------------------------------------------------------- 1 | chains: 2 | - LLMChain 3 | - LLMMathChain 4 | - LLMCheckerChain 5 | - ConversationChain 6 | - SeriesCharacterChain 7 | - MidJourneyPromptChain 8 | - TimeTravelGuideChain 9 | 10 | agents: 11 | - ZeroShotAgent 12 | - JsonAgent 13 | - CSVAgent 14 | - initialize_agent 15 | - VectorStoreAgent 16 | - VectorStoreRouterAgent 17 | 18 | prompts: 19 | - PromptTemplate 20 | - FewShotPromptTemplate 21 | - ZeroShotPrompt 22 | # Wait more tests 23 | # - ChatPromptTemplate 24 | # - SystemMessagePromptTemplate 25 | # - HumanMessagePromptTemplate 26 | 27 | llms: 28 | - OpenAI 29 | # - AzureOpenAI 30 | - ChatOpenAI 31 | - HuggingFaceHub 32 | - LlamaCpp 33 | 34 | tools: 35 | - Search 36 | - PAL-MATH 37 | - Calculator 38 | - Serper Search 39 | - Tool 40 | - PythonFunction 41 | - JsonSpec 42 | 43 | wrappers: 44 | - RequestsWrapper 45 | 46 | toolkits: 47 | - OpenAPIToolkit 48 | - JsonToolkit 49 | - VectorStoreInfo 50 | - VectorStoreRouterToolkit 51 | 52 | memories: 53 | - ConversationBufferMemory 54 | - ConversationSummaryMemory 55 | - ConversationKGMemory 56 | 57 | embeddings: 58 | - OpenAIEmbeddings 59 | 60 | vectorstores: 61 | - Chroma 62 | 63 | documentloaders: 64 | - AirbyteJSONLoader 65 | - CoNLLULoader 66 | - CSVLoader 67 | - UnstructuredEmailLoader 68 | - EverNoteLoader 69 | - FacebookChatLoader 70 | - GutenbergLoader 71 | - BSHTMLLoader 72 | - UnstructuredHTMLLoader 73 | # - UnstructuredImageLoader # Issue with Python 3.11 (https://github.com/Unstructured-IO/unstructured-inference/issues/83) 74 | - UnstructuredMarkdownLoader 75 | - PyPDFLoader 76 | - UnstructuredPowerPointLoader 77 | - SRTLoader 78 | - TelegramChatLoader 79 | - TextLoader 80 | - UnstructuredWordDocumentLoader 81 | - WebBaseLoader 82 | - AZLyricsLoader 83 | - CollegeConfidentialLoader 84 | - HNLoader 85 | - IFixitLoader 86 | - IMSDbLoader 87 | - GitbookLoader 88 | - ReadTheDocsLoader 89 | 90 | textsplitters: 91 | - CharacterTextSplitter 92 | 93 | dev: false 94 | -------------------------------------------------------------------------------- /src/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "langflow", 3 | "version": "0.1.2", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.10.5", 7 | "@emotion/styled": "^11.10.5", 8 | "@headlessui/react": "^1.7.10", 9 | "@heroicons/react": "^2.0.15", 10 | "@mui/material": "^5.11.9", 11 | "@tailwindcss/forms": "^0.5.3", 12 | "@testing-library/jest-dom": "^5.16.5", 13 | "@testing-library/react": "^13.4.0", 14 | "@testing-library/user-event": "^13.5.0", 15 | "@types/jest": "^27.5.2", 16 | "@types/node": "^16.18.12", 17 | "@types/react": "^18.0.27", 18 | "@types/react-dom": "^18.0.10", 19 | "ace-builds": "^1.16.0", 20 | "ansi-to-html": "^0.7.2", 21 | "axios": "^1.3.2", 22 | "lodash": "^4.17.21", 23 | "react": "^18.2.0", 24 | "react-ace": "^10.1.0", 25 | "react-cookie": "^4.1.1", 26 | "react-dom": "^18.2.0", 27 | "react-error-boundary": "^4.0.2", 28 | "react-icons": "^4.7.1", 29 | "react-laag": "^2.0.5", 30 | "react-router-dom": "^6.8.1", 31 | "react-scripts": "5.0.1", 32 | "react-tabs": "^6.0.0", 33 | "reactflow": "^11.5.5", 34 | "tailwindcss": "^3.2.6", 35 | "typescript": "^4.9.5", 36 | "web-vitals": "^2.1.4" 37 | }, 38 | "scripts": { 39 | "start": "react-scripts start", 40 | "build": "react-scripts build", 41 | "test": "react-scripts test", 42 | "eject": "react-scripts eject" 43 | }, 44 | "eslintConfig": { 45 | "extends": [ 46 | "react-app", 47 | "react-app/jest" 48 | ] 49 | }, 50 | "browserslist": { 51 | "production": [ 52 | ">0.2%", 53 | "not dead", 54 | "not op_mini all" 55 | ], 56 | "development": [ 57 | "last 1 chrome version", 58 | "last 1 firefox version", 59 | "last 1 safari version" 60 | ] 61 | }, 62 | "proxy": "http://backend:7860" 63 | } -------------------------------------------------------------------------------- /tests/test_cache.py: -------------------------------------------------------------------------------- 1 | import json 2 | import tempfile 3 | from pathlib import Path 4 | 5 | import pytest 6 | from langflow.cache.utils import PREFIX, save_cache 7 | from langflow.interface.run import load_langchain_object 8 | 9 | 10 | def get_graph(_type="basic"): 11 | """Get a graph from a json file""" 12 | if _type == "basic": 13 | path = pytest.BASIC_EXAMPLE_PATH 14 | elif _type == "complex": 15 | path = pytest.COMPLEX_EXAMPLE_PATH 16 | elif _type == "openapi": 17 | path = pytest.OPENAPI_EXAMPLE_PATH 18 | 19 | with open(path, "r") as f: 20 | flow_graph = json.load(f) 21 | return flow_graph["data"] 22 | 23 | 24 | @pytest.fixture 25 | def basic_data_graph(): 26 | return get_graph() 27 | 28 | 29 | @pytest.fixture 30 | def complex_data_graph(): 31 | return get_graph("complex") 32 | 33 | 34 | @pytest.fixture 35 | def openapi_data_graph(): 36 | return get_graph("openapi") 37 | 38 | 39 | def langchain_objects_are_equal(obj1, obj2): 40 | return str(obj1) == str(obj2) 41 | 42 | 43 | def test_cache_creation(basic_data_graph): 44 | # Compute hash for the input data_graph 45 | # Call process_graph function to build and cache the langchain_object 46 | is_first_message = True 47 | computed_hash, langchain_object = load_langchain_object( 48 | basic_data_graph, is_first_message=is_first_message 49 | ) 50 | save_cache(computed_hash, langchain_object, is_first_message) 51 | # Check if the cache file exists 52 | cache_file = Path(tempfile.gettempdir()) / f"{PREFIX}/{computed_hash}.dill" 53 | 54 | assert cache_file.exists() 55 | 56 | 57 | def test_cache_reuse(basic_data_graph): 58 | # Call process_graph function to build and cache the langchain_object 59 | result1 = load_langchain_object(basic_data_graph) 60 | 61 | # Call process_graph function again to use the cached langchain_object 62 | result2 = load_langchain_object(basic_data_graph) 63 | 64 | # Compare the results to ensure the same langchain_object was used 65 | assert langchain_objects_are_equal(result1, result2) 66 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/vectorStore/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional 2 | 3 | from langflow.interface.base import LangChainTypeCreator 4 | from langflow.interface.custom_lists import vectorstores_type_to_cls_dict 5 | from langflow.settings import settings 6 | from langflow.utils.util import build_template_from_class 7 | from langflow.utils.logger import logger 8 | 9 | 10 | class VectorstoreCreator(LangChainTypeCreator): 11 | type_name: str = "vectorstores" 12 | 13 | @property 14 | def type_to_loader_dict(self) -> Dict: 15 | return vectorstores_type_to_cls_dict 16 | 17 | def get_signature(self, name: str) -> Optional[Dict]: 18 | """Get the signature of an embedding.""" 19 | try: 20 | signature = build_template_from_class(name, vectorstores_type_to_cls_dict) 21 | 22 | # TODO: Use FrontendendNode class to build the signature 23 | signature["template"] = { 24 | "documents": { 25 | "type": "TextSplitter", 26 | "required": True, 27 | "show": True, 28 | "name": "documents", 29 | "display_name": "Text Splitter", 30 | }, 31 | "embedding": { 32 | "type": "Embeddings", 33 | "required": True, 34 | "show": True, 35 | "name": "embedding", 36 | "display_name": "Embedding", 37 | }, 38 | } 39 | return signature 40 | 41 | except ValueError as exc: 42 | raise ValueError(f"Vector Store {name} not found") from exc 43 | except AttributeError as exc: 44 | logger.error(f"Vector Store {name} not loaded: {exc}") 45 | 46 | def to_list(self) -> List[str]: 47 | return [ 48 | vectorstore 49 | for vectorstore in self.type_to_loader_dict.keys() 50 | if vectorstore in settings.vectorstores or settings.dev 51 | ] 52 | 53 | 54 | vectorstore_creator = VectorstoreCreator() 55 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/chains/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional, Type 2 | 3 | from langflow.custom.customs import get_custom_nodes 4 | from langflow.interface.base import LangChainTypeCreator 5 | from langflow.interface.custom_lists import chain_type_to_cls_dict 6 | from langflow.settings import settings 7 | from langflow.template.nodes import ChainFrontendNode 8 | from langflow.utils.util import build_template_from_class 9 | from langflow.utils.logger import logger 10 | 11 | # Assuming necessary imports for Field, Template, and FrontendNode classes 12 | 13 | 14 | class ChainCreator(LangChainTypeCreator): 15 | type_name: str = "chains" 16 | 17 | @property 18 | def frontend_node_class(self) -> Type[ChainFrontendNode]: 19 | return ChainFrontendNode 20 | 21 | @property 22 | def type_to_loader_dict(self) -> Dict: 23 | if self.type_dict is None: 24 | self.type_dict = chain_type_to_cls_dict 25 | from langflow.interface.chains.custom import CUSTOM_CHAINS 26 | 27 | self.type_dict.update(CUSTOM_CHAINS) 28 | # Filter according to settings.chains 29 | self.type_dict = { 30 | name: chain 31 | for name, chain in self.type_dict.items() 32 | if name in settings.chains or settings.dev 33 | } 34 | return self.type_dict 35 | 36 | def get_signature(self, name: str) -> Optional[Dict]: 37 | try: 38 | if name in get_custom_nodes(self.type_name).keys(): 39 | return get_custom_nodes(self.type_name)[name] 40 | return build_template_from_class(name, self.type_to_loader_dict) 41 | except ValueError as exc: 42 | raise ValueError("Chain not found") from exc 43 | except AttributeError as exc: 44 | logger.error(f"Chain {name} not loaded: {exc}") 45 | 46 | def to_list(self) -> List[str]: 47 | custom_chains = list(get_custom_nodes("chains").keys()) 48 | default_chains = list(self.type_to_loader_dict.keys()) 49 | 50 | return default_chains + custom_chains 51 | 52 | 53 | chain_creator = ChainCreator() 54 | -------------------------------------------------------------------------------- /src/frontend/src/components/chatComponent/chatMessage/index.tsx: -------------------------------------------------------------------------------- 1 | import { ChatBubbleLeftEllipsisIcon, ChatBubbleOvalLeftEllipsisIcon, PlusSmallIcon } from "@heroicons/react/24/outline"; 2 | import { useState } from "react"; 3 | import { ChatMessageType } from "../../../types/chat"; 4 | import { nodeColors } from "../../../utils"; 5 | var Convert = require('ansi-to-html'); 6 | var convert = new Convert({newline:true}); 7 | 8 | export default function ChatMessage({ chat }: { chat: ChatMessageType }) { 9 | const [hidden, setHidden] = useState(true); 10 | return ( 11 |
12 | {!chat.isSend ? ( 13 |
14 |
18 | {hidden && chat.thought && chat.thought !== "" && ( 19 |
setHidden((prev) => !prev)} 21 | className="absolute top-2 right-2 cursor-pointer" 22 | > 23 | 24 |
25 | )} 26 | {chat.thought && chat.thought !== "" && !hidden && ( 27 |
setHidden((prev) => !prev)} 29 | style={{ backgroundColor: nodeColors["thought"] }} 30 | className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer" 31 | dangerouslySetInnerHTML={{ 32 | __html: convert.toHtml(chat.thought) 33 | }} 34 | >
35 | )} 36 | {chat.thought && chat.thought !== "" && !hidden &&

} 37 |
38 | {chat.message} 39 |
40 |
41 |
42 | ) : ( 43 |
44 |
45 | {chat.message} 46 |
47 |
48 | )} 49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/frontend/src/components/inputListComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; 2 | import { useEffect, useState } from "react"; 3 | import { InputListComponentType } from "../../types/components"; 4 | 5 | var _ = require("lodash"); 6 | 7 | export default function InputListComponent({ value, onChange, disabled}:InputListComponentType) { 8 | const [inputList, setInputList] = useState(value ?? [""]); 9 | useEffect(()=> { 10 | if(disabled){ 11 | setInputList([""]); 12 | onChange([""]); 13 | } 14 | }, [disabled, onChange]) 15 | return ( 16 |
17 | {inputList.map((i, idx) => ( 18 |
19 | { 25 | setInputList((old) => { 26 | let newInputList = _.cloneDeep(old); 27 | newInputList[idx] = e.target.value; 28 | return newInputList; 29 | }); 30 | onChange(inputList); 31 | }} 32 | /> 33 | {idx === inputList.length - 1 ? 34 | 42 | : } 50 |
51 | ))} 52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /src/backend/langflow/api/base.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, validator 2 | 3 | from langflow.graph.utils import extract_input_variables_from_prompt 4 | 5 | 6 | class Code(BaseModel): 7 | code: str 8 | 9 | 10 | class Prompt(BaseModel): 11 | template: str 12 | 13 | 14 | # Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}} 15 | class CodeValidationResponse(BaseModel): 16 | imports: dict 17 | function: dict 18 | 19 | @validator("imports") 20 | def validate_imports(cls, v): 21 | return v or {"errors": []} 22 | 23 | @validator("function") 24 | def validate_function(cls, v): 25 | return v or {"errors": []} 26 | 27 | 28 | class PromptValidationResponse(BaseModel): 29 | input_variables: list 30 | 31 | 32 | INVALID_CHARACTERS = { 33 | " ", 34 | ",", 35 | ".", 36 | ":", 37 | ";", 38 | "!", 39 | "?", 40 | "/", 41 | "\\", 42 | "(", 43 | ")", 44 | "[", 45 | "]", 46 | "{", 47 | "}", 48 | } 49 | 50 | 51 | def validate_prompt(template: str): 52 | input_variables = extract_input_variables_from_prompt(template) 53 | 54 | # Check if there are invalid characters in the input_variables 55 | input_variables = check_input_variables(input_variables) 56 | 57 | return PromptValidationResponse(input_variables=input_variables) 58 | 59 | 60 | def check_input_variables(input_variables: list): 61 | invalid_chars = [] 62 | fixed_variables = [] 63 | for variable in input_variables: 64 | new_var = variable 65 | for char in INVALID_CHARACTERS: 66 | if char in variable: 67 | invalid_chars.append(char) 68 | new_var = new_var.replace(char, "") 69 | fixed_variables.append(new_var) 70 | if new_var != variable: 71 | input_variables.remove(variable) 72 | input_variables.append(new_var) 73 | # If any of the input_variables is not in the fixed_variables, then it means that 74 | # there are invalid characters in the input_variables 75 | if any(var not in fixed_variables for var in input_variables): 76 | raise ValueError( 77 | f"Invalid input variables: {input_variables}. Please, use something like {fixed_variables} instead." 78 | ) 79 | 80 | return input_variables 81 | -------------------------------------------------------------------------------- /src/backend/langflow/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import List 3 | 4 | import yaml 5 | from pydantic import BaseSettings, root_validator 6 | 7 | 8 | class Settings(BaseSettings): 9 | chains: List[str] = [] 10 | agents: List[str] = [] 11 | prompts: List[str] = [] 12 | llms: List[str] = [] 13 | tools: List[str] = [] 14 | memories: List[str] = [] 15 | embeddings: List[str] = [] 16 | vectorstores: List[str] = [] 17 | documentloaders: List[str] = [] 18 | wrappers: List[str] = [] 19 | toolkits: List[str] = [] 20 | textsplitters: List[str] = [] 21 | dev: bool = False 22 | 23 | class Config: 24 | validate_assignment = True 25 | extra = "ignore" 26 | 27 | @root_validator(allow_reuse=True) 28 | def validate_lists(cls, values): 29 | for key, value in values.items(): 30 | if key != "dev" and not value: 31 | values[key] = [] 32 | return values 33 | 34 | def update_from_yaml(self, file_path: str): 35 | new_settings = load_settings_from_yaml(file_path) 36 | self.chains = new_settings.chains or [] 37 | self.agents = new_settings.agents or [] 38 | self.prompts = new_settings.prompts or [] 39 | self.llms = new_settings.llms or [] 40 | self.tools = new_settings.tools or [] 41 | self.memories = new_settings.memories or [] 42 | self.wrappers = new_settings.wrappers or [] 43 | self.toolkits = new_settings.toolkits or [] 44 | self.textsplitters = new_settings.textsplitters or [] 45 | self.dev = new_settings.dev or False 46 | 47 | 48 | def save_settings_to_yaml(settings: Settings, file_path: str): 49 | with open(file_path, "w") as f: 50 | settings_dict = settings.dict() 51 | yaml.dump(settings_dict, f) 52 | 53 | 54 | def load_settings_from_yaml(file_path: str) -> Settings: 55 | # Check if a string is a valid path or a file name 56 | if "/" not in file_path: 57 | # Get current path 58 | current_path = os.path.dirname(os.path.abspath(__file__)) 59 | 60 | file_path = os.path.join(current_path, file_path) 61 | 62 | with open(file_path, "r") as f: 63 | settings_dict = yaml.safe_load(f) 64 | 65 | return Settings(**settings_dict) 66 | 67 | 68 | settings = load_settings_from_yaml("config.yaml") 69 | -------------------------------------------------------------------------------- /tests/test_frontend_nodes.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from langflow.template.base import FrontendNode, Template, TemplateField 3 | 4 | 5 | @pytest.fixture 6 | def sample_template_field() -> TemplateField: 7 | return TemplateField(name="test_field", field_type="str") 8 | 9 | 10 | @pytest.fixture 11 | def sample_template(sample_template_field: TemplateField) -> Template: 12 | return Template(type_name="test_template", fields=[sample_template_field]) 13 | 14 | 15 | @pytest.fixture 16 | def sample_frontend_node(sample_template: Template) -> FrontendNode: 17 | return FrontendNode( 18 | template=sample_template, 19 | description="test description", 20 | base_classes=["base_class1", "base_class2"], 21 | name="test_frontend_node", 22 | ) 23 | 24 | 25 | def test_template_field_defaults(sample_template_field: TemplateField): 26 | assert sample_template_field.field_type == "str" 27 | assert sample_template_field.required is False 28 | assert sample_template_field.placeholder == "" 29 | assert sample_template_field.is_list is False 30 | assert sample_template_field.show is True 31 | assert sample_template_field.multiline is False 32 | assert sample_template_field.value is None 33 | assert sample_template_field.suffixes == [] 34 | assert sample_template_field.file_types == [] 35 | assert sample_template_field.content is None 36 | assert sample_template_field.password is False 37 | assert sample_template_field.name == "test_field" 38 | 39 | 40 | def test_template_to_dict( 41 | sample_template: Template, sample_template_field: TemplateField 42 | ): 43 | template_dict = sample_template.to_dict() 44 | assert template_dict["_type"] == "test_template" 45 | assert len(template_dict) == 2 # _type and test_field 46 | assert "test_field" in template_dict 47 | assert "type" in template_dict["test_field"] 48 | assert "required" in template_dict["test_field"] 49 | 50 | 51 | def test_frontend_node_to_dict(sample_frontend_node: FrontendNode): 52 | node_dict = sample_frontend_node.to_dict() 53 | assert len(node_dict) == 1 54 | assert "test_frontend_node" in node_dict 55 | assert "description" in node_dict["test_frontend_node"] 56 | assert "template" in node_dict["test_frontend_node"] 57 | assert "base_classes" in node_dict["test_frontend_node"] 58 | -------------------------------------------------------------------------------- /src/frontend/src/contexts/locationContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, ReactNode, useState } from "react"; 2 | 3 | //types for location context 4 | type locationContextType = { 5 | current: Array; 6 | setCurrent: (newState: Array) => void; 7 | isStackedOpen: boolean; 8 | setIsStackedOpen: (newState: boolean) => void; 9 | showSideBar: boolean; 10 | setShowSideBar: (newState: boolean) => void; 11 | extraNavigation: { 12 | title: string; 13 | options?: Array<{ 14 | name: string; 15 | href: string; 16 | icon: any; 17 | children?: Array; 18 | }>; 19 | }; 20 | setExtraNavigation: (newState: { 21 | title: string; 22 | options?: Array<{ 23 | name: string; 24 | href: string; 25 | icon: any; 26 | children?: Array; 27 | }>; 28 | }) => void; 29 | extraComponent: any; 30 | setExtraComponent: (newState: any) => void; 31 | }; 32 | 33 | //initial value for location context 34 | const initialValue = { 35 | //actual 36 | current: window.location.pathname.replace(/\/$/g, "").split("/"), 37 | isStackedOpen: 38 | window.innerWidth > 1024 && window.location.pathname.split("/")[1] 39 | ? true 40 | : false, 41 | setCurrent: () => {}, 42 | setIsStackedOpen: () => {}, 43 | showSideBar: window.location.pathname.split("/")[1] ? true : false, 44 | setShowSideBar: () => {}, 45 | extraNavigation: { title: "" }, 46 | setExtraNavigation: () => {}, 47 | extraComponent: <>, 48 | setExtraComponent: () => {}, 49 | }; 50 | 51 | export const locationContext = createContext(initialValue); 52 | 53 | export function LocationProvider({ children }:{children:ReactNode}) { 54 | const [current, setCurrent] = useState(initialValue.current); 55 | const [isStackedOpen, setIsStackedOpen] = useState( 56 | initialValue.isStackedOpen 57 | ); 58 | const [showSideBar, setShowSideBar] = useState(initialValue.showSideBar); 59 | const [extraNavigation, setExtraNavigation] = useState({ title: "" }); 60 | const [extraComponent, setExtraComponent] = useState(<>); 61 | return ( 62 | 76 | {children} 77 | 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /src/backend/langflow/__main__.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import platform 3 | from pathlib import Path 4 | 5 | import typer 6 | from fastapi.staticfiles import StaticFiles 7 | 8 | from langflow.main import create_app 9 | from langflow.settings import settings 10 | from langflow.utils.logger import configure 11 | 12 | app = typer.Typer() 13 | 14 | 15 | def get_number_of_workers(workers=None): 16 | if workers == -1: 17 | workers = (multiprocessing.cpu_count() * 2) + 1 18 | return workers 19 | 20 | 21 | def update_settings(config: str): 22 | """Update the settings from a config file.""" 23 | if config: 24 | settings.update_from_yaml(config) 25 | 26 | 27 | @app.command() 28 | def serve( 29 | host: str = typer.Option("127.0.0.1", help="Host to bind the server to."), 30 | workers: int = typer.Option(1, help="Number of worker processes."), 31 | timeout: int = typer.Option(60, help="Worker timeout in seconds."), 32 | port: int = typer.Option(7860, help="Port to listen on."), 33 | config: str = typer.Option("config.yaml", help="Path to the configuration file."), 34 | log_level: str = typer.Option("info", help="Logging level."), 35 | log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file."), 36 | ): 37 | """ 38 | Run the Langflow server. 39 | """ 40 | 41 | configure(log_level=log_level, log_file=log_file) 42 | update_settings(config) 43 | app = create_app() 44 | # get the directory of the current file 45 | path = Path(__file__).parent 46 | static_files_dir = path / "frontend" 47 | app.mount( 48 | "/", 49 | StaticFiles(directory=static_files_dir, html=True), 50 | name="static", 51 | ) 52 | options = { 53 | "bind": f"{host}:{port}", 54 | "workers": get_number_of_workers(workers), 55 | "worker_class": "uvicorn.workers.UvicornWorker", 56 | "timeout": timeout, 57 | } 58 | 59 | if platform.system() in ["Darwin", "Windows"]: 60 | # Run using uvicorn on MacOS and Windows 61 | # Windows doesn't support gunicorn 62 | # MacOS requires an env variable to be set to use gunicorn 63 | import uvicorn 64 | 65 | uvicorn.run(app, host=host, port=port, log_level=log_level) 66 | else: 67 | from langflow.server import LangflowApplication 68 | 69 | LangflowApplication(app, options).run() 70 | 71 | 72 | def main(): 73 | app() 74 | 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/custom_lists.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from typing import Any 3 | 4 | from langchain import ( 5 | chains, 6 | document_loaders, 7 | embeddings, 8 | llms, 9 | memory, 10 | requests, 11 | text_splitter, 12 | vectorstores, 13 | ) 14 | from langchain.agents import agent_toolkits 15 | from langchain.chat_models import ChatOpenAI 16 | 17 | from langflow.interface.importing.utils import import_class 18 | 19 | ## LLMs 20 | llm_type_to_cls_dict = llms.type_to_cls_dict 21 | llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore 22 | 23 | ## Chains 24 | chain_type_to_cls_dict: dict[str, Any] = { 25 | chain_name: import_class(f"langchain.chains.{chain_name}") 26 | for chain_name in chains.__all__ 27 | } 28 | 29 | ## Toolkits 30 | toolkit_type_to_loader_dict: dict[str, Any] = { 31 | toolkit_name: import_class(f"langchain.agents.agent_toolkits.{toolkit_name}") 32 | # if toolkit_name is lower case it is a loader 33 | for toolkit_name in agent_toolkits.__all__ 34 | if toolkit_name.islower() 35 | } 36 | 37 | toolkit_type_to_cls_dict: dict[str, Any] = { 38 | toolkit_name: import_class(f"langchain.agents.agent_toolkits.{toolkit_name}") 39 | # if toolkit_name is not lower case it is a class 40 | for toolkit_name in agent_toolkits.__all__ 41 | if not toolkit_name.islower() 42 | } 43 | 44 | ## Memories 45 | memory_type_to_cls_dict: dict[str, Any] = { 46 | memory_name: import_class(f"langchain.memory.{memory_name}") 47 | for memory_name in memory.__all__ 48 | } 49 | 50 | ## Wrappers 51 | wrapper_type_to_cls_dict: dict[str, Any] = { 52 | wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper] 53 | } 54 | 55 | ## Embeddings 56 | embedding_type_to_cls_dict: dict[str, Any] = { 57 | embedding_name: import_class(f"langchain.embeddings.{embedding_name}") 58 | for embedding_name in embeddings.__all__ 59 | } 60 | 61 | ## Vector Stores 62 | vectorstores_type_to_cls_dict: dict[str, Any] = { 63 | vectorstore_name: import_class(f"langchain.vectorstores.{vectorstore_name}") 64 | for vectorstore_name in vectorstores.__all__ 65 | } 66 | 67 | ## Document Loaders 68 | documentloaders_type_to_cls_dict: dict[str, Any] = { 69 | documentloader_name: import_class( 70 | f"langchain.document_loaders.{documentloader_name}" 71 | ) 72 | for documentloader_name in document_loaders.__all__ 73 | } 74 | 75 | ## Text Splitters 76 | textsplitter_type_to_cls_dict: dict[str, Any] = dict( 77 | inspect.getmembers(text_splitter, inspect.isclass) 78 | ) 79 | -------------------------------------------------------------------------------- /src/backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | notebooks 6 | 7 | # frontend 8 | src/frontend 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | -------------------------------------------------------------------------------- /src/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/frontend/src/components/inputFileComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { DocumentMagnifyingGlassIcon } from "@heroicons/react/24/outline"; 2 | import { useContext, useEffect, useState } from "react"; 3 | import { alertContext } from "../../contexts/alertContext"; 4 | import { FileComponentType } from "../../types/components"; 5 | 6 | export default function InputFileComponent({ 7 | value, 8 | onChange, 9 | disabled, 10 | suffixes, 11 | fileTypes, 12 | onFileChange 13 | }: FileComponentType) { 14 | const [myValue, setMyValue] = useState(value); 15 | const { setErrorData } = useContext(alertContext); 16 | useEffect(() => { 17 | if (disabled) { 18 | setMyValue(""); 19 | onChange(""); 20 | onFileChange("") 21 | } 22 | }, [disabled, onChange]); 23 | 24 | function attachFile(fileReadEvent: ProgressEvent) { 25 | fileReadEvent.preventDefault(); 26 | const file = fileReadEvent.target.result; 27 | onFileChange(file as string) 28 | } 29 | 30 | function checkFileType(fileName:string):boolean{ 31 | for (let index = 0; index < suffixes.length; index++) { 32 | if(fileName.endsWith(suffixes[index])){ 33 | return true 34 | } 35 | } 36 | return false 37 | } 38 | 39 | const handleButtonClick = () => { 40 | const input = document.createElement("input"); 41 | input.type = "file"; 42 | input.accept = suffixes.join(","); 43 | input.style.display = "none"; 44 | input.multiple = false; 45 | input.onchange = (e: Event) => { 46 | const file = (e.target as HTMLInputElement).files?.[0]; 47 | const fileData = new FileReader(); 48 | fileData.onload = attachFile; 49 | if (file && checkFileType(file.name)) { 50 | fileData.readAsDataURL(file); 51 | setMyValue(file.name); 52 | onChange(file.name); 53 | } else { 54 | setErrorData({ 55 | title: 56 | "Please select a valid file. Only files this files are allowed:", 57 | list: fileTypes, 58 | }); 59 | } 60 | }; 61 | input.click(); 62 | }; 63 | 64 | return ( 65 |
70 |
71 | 77 | {myValue !== "" ? myValue : "No file"} 78 | 79 | 82 |
83 |
84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/prompts/base.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional, Type 2 | 3 | from langchain import prompts 4 | 5 | from langflow.custom.customs import get_custom_nodes 6 | from langflow.interface.base import LangChainTypeCreator 7 | from langflow.interface.importing.utils import import_class 8 | from langflow.settings import settings 9 | from langflow.template.nodes import PromptFrontendNode 10 | from langflow.utils.util import build_template_from_class 11 | from langflow.utils.logger import logger 12 | 13 | 14 | class PromptCreator(LangChainTypeCreator): 15 | type_name: str = "prompts" 16 | 17 | @property 18 | def frontend_node_class(self) -> Type[PromptFrontendNode]: 19 | return PromptFrontendNode 20 | 21 | @property 22 | def type_to_loader_dict(self) -> Dict: 23 | if self.type_dict is None: 24 | self.type_dict = { 25 | prompt_name: import_class(f"langchain.prompts.{prompt_name}") 26 | # if prompt_name is not lower case it is a class 27 | for prompt_name in prompts.__all__ 28 | } 29 | # Merge CUSTOM_PROMPTS into self.type_dict 30 | from langflow.interface.prompts.custom import CUSTOM_PROMPTS 31 | 32 | self.type_dict.update(CUSTOM_PROMPTS) 33 | # Now filter according to settings.prompts 34 | self.type_dict = { 35 | name: prompt 36 | for name, prompt in self.type_dict.items() 37 | if name in settings.prompts or settings.dev 38 | } 39 | return self.type_dict 40 | 41 | def get_signature(self, name: str) -> Optional[Dict]: 42 | try: 43 | if name in get_custom_nodes(self.type_name).keys(): 44 | return get_custom_nodes(self.type_name)[name] 45 | return build_template_from_class(name, self.type_to_loader_dict) 46 | except ValueError as exc: 47 | # raise ValueError("Prompt not found") from exc 48 | logger.error(f"Prompt {name} not found: {exc}") 49 | except AttributeError as exc: 50 | logger.error(f"Prompt {name} not loaded: {exc}") 51 | 52 | def to_list(self) -> List[str]: 53 | custom_prompts = get_custom_nodes("prompts") 54 | # library_prompts = [ 55 | # prompt.__annotations__["return"].__name__ 56 | # for prompt in self.type_to_loader_dict.values() 57 | # if prompt.__annotations__["return"].__name__ in settings.prompts 58 | # or settings.dev 59 | # ] 60 | return list(self.type_to_loader_dict.keys()) + list(custom_prompts.keys()) 61 | 62 | 63 | prompt_creator = PromptCreator() 64 | -------------------------------------------------------------------------------- /src/frontend/src/alerts/alertDropDown/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useRef } from "react"; 2 | import { alertContext } from "../../contexts/alertContext"; 3 | import { XMarkIcon } from "@heroicons/react/24/solid"; 4 | import { TrashIcon } from "@heroicons/react/24/outline"; 5 | import SingleAlert from "./components/singleAlertComponent"; 6 | import { AlertDropdownType } from "../../types/alerts"; 7 | import { PopUpContext } from "../../contexts/popUpContext"; 8 | 9 | export default function AlertDropdown({}: AlertDropdownType) { 10 | const { closePopUp } = useContext(PopUpContext); 11 | const componentRef = useRef(null); 12 | 13 | useEffect(() => { 14 | function handleClickOutside(event: MouseEvent) { 15 | if ( 16 | componentRef.current && 17 | !componentRef.current.contains(event.target as Node) 18 | ) { 19 | closePopUp(); 20 | } 21 | } 22 | 23 | // Bind the event listener 24 | document.addEventListener("mousedown", handleClickOutside); 25 | 26 | // Cleanup the event listener when the component is unmounted 27 | return () => { 28 | document.removeEventListener("mousedown", handleClickOutside); 29 | }; 30 | }, [componentRef]); 31 | 32 | const { 33 | notificationList, 34 | clearNotificationList, 35 | removeFromNotificationList, 36 | } = useContext(alertContext); 37 | 38 | return ( 39 |
43 |
44 | Notifications 45 |
46 | 55 | 58 |
59 |
60 |
61 | {notificationList.length !== 0 ? ( 62 | notificationList.map((alertItem, index) => ( 63 | 68 | )) 69 | ) : ( 70 |
71 | No new notifications 72 |
73 | )} 74 |
75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/toolkits/base.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, List, Optional 2 | 3 | from langchain.agents import agent_toolkits 4 | 5 | from langflow.interface.base import LangChainTypeCreator 6 | from langflow.interface.importing.utils import import_class, import_module 7 | from langflow.settings import settings 8 | from langflow.utils.util import build_template_from_class 9 | from langflow.utils.logger import logger 10 | 11 | 12 | class ToolkitCreator(LangChainTypeCreator): 13 | type_name: str = "toolkits" 14 | all_types: List[str] = agent_toolkits.__all__ 15 | create_functions: Dict = { 16 | "JsonToolkit": [], 17 | "SQLDatabaseToolkit": [], 18 | "OpenAPIToolkit": ["create_openapi_agent"], 19 | "VectorStoreToolkit": [ 20 | "create_vectorstore_agent", 21 | "create_vectorstore_router_agent", 22 | "VectorStoreInfo", 23 | ], 24 | "ZapierToolkit": [], 25 | "PandasToolkit": ["create_pandas_dataframe_agent"], 26 | "CSVToolkit": ["create_csv_agent"], 27 | } 28 | 29 | @property 30 | def type_to_loader_dict(self) -> Dict: 31 | if self.type_dict is None: 32 | self.type_dict = { 33 | toolkit_name: import_class( 34 | f"langchain.agents.agent_toolkits.{toolkit_name}" 35 | ) 36 | # if toolkit_name is not lower case it is a class 37 | for toolkit_name in agent_toolkits.__all__ 38 | if not toolkit_name.islower() and toolkit_name in settings.toolkits 39 | } 40 | 41 | return self.type_dict 42 | 43 | def get_signature(self, name: str) -> Optional[Dict]: 44 | try: 45 | return build_template_from_class(name, self.type_to_loader_dict) 46 | except ValueError as exc: 47 | raise ValueError("Prompt not found") from exc 48 | except AttributeError as exc: 49 | logger.error(f"Prompt {name} not loaded: {exc}") 50 | 51 | def to_list(self) -> List[str]: 52 | return list(self.type_to_loader_dict.keys()) 53 | 54 | def get_create_function(self, name: str) -> Callable: 55 | if loader_name := self.create_functions.get(name, None): 56 | # import loader 57 | return import_module( 58 | f"from langchain.agents.agent_toolkits import {loader_name[0]}" 59 | ) 60 | else: 61 | raise ValueError("Loader not found") 62 | 63 | def has_create_function(self, name: str) -> bool: 64 | # check if the function list is not empty 65 | return bool(self.create_functions.get(name, None)) 66 | 67 | 68 | toolkits_creator = ToolkitCreator() 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # ⛓️ LangFlow 4 | 5 | ~ A User Interface For [LangChain](https://github.com/hwchase17/langchain) ~ 6 | 7 |

8 | GitHub Contributors 9 | GitHub Last Commit 10 | 11 | GitHub Issues 12 | GitHub Pull Requests 13 | Github License 14 |

15 | 16 | 17 | 18 | 19 | LangFlow is a GUI for [LangChain](https://github.com/hwchase17/langchain), designed with [react-flow](https://github.com/wbkd/react-flow) to provide an effortless way to experiment and prototype flows with drag-and-drop components and a chat box. 20 | 21 | ## 📦 Installation 22 | 23 | You can install LangFlow from pip: 24 | 25 | `pip install langflow` 26 | 27 | Next, run: 28 | 29 | `langflow` 30 | 31 | ## 🎨 Creating Flows 32 | 33 | Creating flows with LangFlow is easy. Simply drag sidebar components onto the canvas and connect them together to create your pipeline. LangFlow provides a range of [LangChain components](https://langchain.readthedocs.io/en/latest/reference.html) to choose from, including LLMs, prompt serializers, agents, and chains. 34 | 35 | Explore by editing prompt parameters, link chains and agents, track an agent's thought process, and export your flow. 36 | 37 | Once you're done, you can export your flow as a JSON file to use with LangChain. 38 | To do so, click the "Export" button in the top right corner of the canvas, then 39 | in Python, you can load the flow with: 40 | 41 | ```python 42 | from langflow import load_flow_from_json 43 | 44 | flow = load_flow_from_json("path/to/flow.json") 45 | # Now you can use it like any chain 46 | flow("Hey, have you heard of LangFlow?") 47 | ``` 48 | 49 | 50 | ## 👋 Contributing 51 | 52 | We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make LangFlow more accessible. 53 | 54 | 55 | [![Star History Chart](https://api.star-history.com/svg?repos=logspace-ai/langflow&type=Timeline)](https://star-history.com/#logspace-ai/langflow&Date) 56 | 57 | 58 | ## 📄 License 59 | 60 | LangFlow is released under the MIT License. See the LICENSE file for details. 61 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/prompts/custom.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional, Type 2 | 3 | from langchain.prompts import PromptTemplate 4 | from pydantic import root_validator 5 | 6 | from langflow.graph.utils import extract_input_variables_from_prompt 7 | 8 | # Steps to create a BaseCustomPrompt: 9 | # 1. Create a prompt template that endes with: 10 | # Current conversation: 11 | # {history} 12 | # Human: {input} 13 | # {ai_prefix}: 14 | # 2. Create a class that inherits from BaseCustomPrompt 15 | # 3. Add the following class attributes: 16 | # template: str = "" 17 | # description: Optional[str] 18 | # ai_prefix: Optional[str] = "{ai_prefix}" 19 | # 3.1. The ai_prefix should be a value in input_variables 20 | # SeriesCharacterPrompt is a working example 21 | # If used in a LLMChain, with a Memory module, it will work as expected 22 | # We should consider creating ConversationalChains that expose custom parameters 23 | # That way it will be easier to create custom prompts 24 | 25 | 26 | class BaseCustomPrompt(PromptTemplate): 27 | template: str = "" 28 | description: Optional[str] 29 | ai_prefix: Optional[str] 30 | 31 | @root_validator(pre=False) 32 | def build_template(cls, values): 33 | format_dict = {} 34 | ai_prefix_format_dict = {} 35 | for key in values.get("input_variables", []): 36 | new_value = values.get(key, f"{{{key}}}") 37 | format_dict[key] = new_value 38 | if key in values["ai_prefix"]: 39 | ai_prefix_format_dict[key] = new_value 40 | 41 | values["ai_prefix"] = values["ai_prefix"].format(**ai_prefix_format_dict) 42 | values["template"] = values["template"].format(**format_dict) 43 | 44 | values["template"] = values["template"] 45 | values["input_variables"] = extract_input_variables_from_prompt( 46 | values["template"] 47 | ) 48 | return values 49 | 50 | 51 | class SeriesCharacterPrompt(BaseCustomPrompt): 52 | # Add a very descriptive description for the prompt generator 53 | description: Optional[ 54 | str 55 | ] = "A prompt that asks the AI to act like a character from a series." 56 | character: str 57 | series: str 58 | template: str = """I want you to act like {character} from {series}. 59 | I want you to respond and answer like {character}. do not write any explanations. only answer like {character}. 60 | You must know all of the knowledge of {character}. 61 | 62 | Current conversation: 63 | {history} 64 | Human: {input} 65 | {character}:""" 66 | 67 | ai_prefix: str = "{character}" 68 | input_variables: List[str] = ["character", "series"] 69 | 70 | 71 | CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = { 72 | "SeriesCharacterPrompt": SeriesCharacterPrompt 73 | } 74 | 75 | if __name__ == "__main__": 76 | prompt = SeriesCharacterPrompt(character="Harry Potter", series="Harry Potter") 77 | print(prompt.template) 78 | -------------------------------------------------------------------------------- /tests/test_validate_code.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | 3 | import pytest 4 | from langflow.utils.validate import ( 5 | create_function, 6 | execute_function, 7 | extract_function_name, 8 | validate_code, 9 | ) 10 | from requests.exceptions import MissingSchema 11 | 12 | 13 | def test_validate_code(): 14 | # Test case with a valid import and function 15 | code1 = """ 16 | import math 17 | 18 | def square(x): 19 | return x ** 2 20 | """ 21 | errors1 = validate_code(code1) 22 | assert errors1 == {"imports": {"errors": []}, "function": {"errors": []}} 23 | 24 | # Test case with an invalid import and valid function 25 | code2 = """ 26 | import non_existent_module 27 | 28 | def square(x): 29 | return x ** 2 30 | """ 31 | errors2 = validate_code(code2) 32 | assert errors2 == { 33 | "imports": {"errors": ["No module named 'non_existent_module'"]}, 34 | "function": {"errors": []}, 35 | } 36 | 37 | # Test case with a valid import and invalid function syntax 38 | code3 = """ 39 | import math 40 | 41 | def square(x) 42 | return x ** 2 43 | """ 44 | errors3 = validate_code(code3) 45 | assert errors3 == { 46 | "imports": {"errors": []}, 47 | "function": {"errors": ["expected ':' (, line 4)"]}, 48 | } 49 | 50 | 51 | def test_execute_function_success(): 52 | code = """ 53 | import math 54 | 55 | def my_function(x): 56 | return math.sin(x) + 1 57 | """ 58 | result = execute_function(code, "my_function", 0.5) 59 | assert result == 1.479425538604203 60 | 61 | 62 | def test_execute_function_missing_module(): 63 | code = """ 64 | import some_missing_module 65 | 66 | def my_function(x): 67 | return some_missing_module.some_function(x) 68 | """ 69 | with pytest.raises(ModuleNotFoundError): 70 | execute_function(code, "my_function", 0.5) 71 | 72 | 73 | def test_execute_function_missing_function(): 74 | code = """ 75 | import math 76 | 77 | def my_function(x): 78 | return math.some_missing_function(x) 79 | """ 80 | with pytest.raises(AttributeError): 81 | execute_function(code, "my_function", 0.5) 82 | 83 | 84 | def test_execute_function_missing_schema(): 85 | code = """ 86 | import requests 87 | 88 | def my_function(x): 89 | return requests.get(x).text 90 | """ 91 | with mock.patch("requests.get", side_effect=MissingSchema): 92 | with pytest.raises(MissingSchema): 93 | execute_function(code, "my_function", "invalid_url") 94 | 95 | 96 | def test_create_function(): 97 | code = """ 98 | import math 99 | 100 | def my_function(x): 101 | return math.sin(x) + 1 102 | """ 103 | 104 | function_name = extract_function_name(code) 105 | function = create_function(code, function_name) 106 | result = function(0.5) 107 | assert result == 1.479425538604203 108 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any, Dict, List, Optional, Type, Union 3 | from langflow.utils.logger import logger 4 | 5 | from pydantic import BaseModel 6 | 7 | from langflow.template.base import FrontendNode, Template, TemplateField 8 | 9 | # Assuming necessary imports for Field, Template, and FrontendNode classes 10 | 11 | 12 | class LangChainTypeCreator(BaseModel, ABC): 13 | type_name: str 14 | type_dict: Optional[Dict] = None 15 | 16 | @property 17 | def frontend_node_class(self) -> Type[FrontendNode]: 18 | """The class type of the FrontendNode created in frontend_node.""" 19 | return FrontendNode 20 | 21 | @property 22 | @abstractmethod 23 | def type_to_loader_dict(self) -> Dict: 24 | if self.type_dict is None: 25 | raise NotImplementedError 26 | return self.type_dict 27 | 28 | @abstractmethod 29 | def get_signature(self, name: str) -> Union[Optional[Dict[Any, Any]], FrontendNode]: 30 | pass 31 | 32 | @abstractmethod 33 | def to_list(self) -> List[str]: 34 | pass 35 | 36 | def to_dict(self) -> Dict: 37 | result: Dict = {self.type_name: {}} 38 | 39 | for name in self.to_list(): 40 | # frontend_node.to_dict() returns a dict with the following structure: 41 | # {name: {template: {fields}, description: str}} 42 | # so we should update the result dict 43 | node = self.frontend_node(name) 44 | if node is not None: 45 | node = node.to_dict() 46 | result[self.type_name].update(node) 47 | 48 | return result 49 | 50 | def frontend_node(self, name) -> Union[FrontendNode, None]: 51 | signature = self.get_signature(name) 52 | if signature is None: 53 | logger.error(f"Node {name} not loaded") 54 | return 55 | if isinstance(signature, FrontendNode): 56 | return signature 57 | fields = [ 58 | TemplateField( 59 | name=key, 60 | field_type=value["type"], 61 | required=value.get("required", False), 62 | placeholder=value.get("placeholder", ""), 63 | is_list=value.get("list", False), 64 | show=value.get("show", True), 65 | multiline=value.get("multiline", False), 66 | value=value.get("value", None), 67 | suffixes=value.get("suffixes", []), 68 | file_types=value.get("fileTypes", []), 69 | content=value.get("content", None), 70 | ) 71 | for key, value in signature["template"].items() 72 | if key != "_type" 73 | ] 74 | template = Template(type_name=name, fields=fields) 75 | return self.frontend_node_class( 76 | template=template, 77 | description=signature.get("description", ""), 78 | base_classes=signature["base_classes"], 79 | name=name, 80 | ) 81 | -------------------------------------------------------------------------------- /src/frontend/src/pages/FlowPage/components/tabComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { PlusIcon, XMarkIcon } from "@heroicons/react/24/solid"; 2 | import { useContext, useState } from "react"; 3 | import { TabsContext } from "../../../../contexts/tabsContext"; 4 | import { FlowType } from "../../../../types/flow"; 5 | 6 | var _ = require("lodash"); 7 | 8 | export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,selected:boolean,onClick:()=>void}) { 9 | const { removeFlow, updateFlow, flows } = 10 | useContext(TabsContext); 11 | const [isRename, setIsRename] = useState(false); 12 | const [value, setValue] = useState(""); 13 | return ( 14 | <> 15 | {flow ? ( 16 | !selected ? ( 17 |
21 | {flow.name} 22 | 30 |
31 | ) : ( 32 |
33 | {isRename ? ( 34 | { 38 | setIsRename(false); 39 | if (value !== "") { 40 | let newFlow = _.cloneDeep(flow); 41 | newFlow.name = value; 42 | updateFlow(newFlow); 43 | } 44 | }} 45 | value={value} 46 | onChange={(e) => { 47 | setValue(e.target.value); 48 | }} 49 | /> 50 | ) : ( 51 |
52 | { 55 | setIsRename(true); 56 | setValue(flow.name); 57 | }} 58 | > 59 | {flow.name} 60 | 61 |
62 | )} 63 | 72 |
73 | ) 74 | ) : ( 75 |
76 | 82 |
83 | )} 84 | 85 | ); 86 | } 87 | 88 | -------------------------------------------------------------------------------- /src/frontend/src/components/toggleComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { Switch } from "@headlessui/react"; 2 | import { classNames } from "../../utils"; 3 | import { useEffect } from "react"; 4 | import { ToggleComponentType } from "../../types/components"; 5 | 6 | export default function ToggleComponent({ enabled, setEnabled, disabled }:ToggleComponentType) { 7 | useEffect(()=> { 8 | if(disabled){ 9 | setEnabled(false); 10 | } 11 | }, [disabled, setEnabled]) 12 | return ( 13 |
14 | { 17 | setEnabled(x); 18 | }} 19 | className={classNames( 20 | enabled ? "bg-indigo-600" : "bg-gray-200 dark:bg-gray-600", 21 | "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out " 22 | )} 23 | > 24 | Use setting 25 | 31 | 54 | 71 | 72 | 73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to LangFlow 2 | 3 | Hello there! We appreciate your interest in contributing to LangFlow. 4 | As an open-source project in a rapidly developing field, we are extremely open 5 | to contributions, whether it be in the form of a new feature, improved infra, or better documentation. 6 | 7 | To contribute to this project, please follow a ["fork and pull request"](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow. 8 | Please do not try to push directly to this repo unless you are a maintainer. 9 | 10 | ## 🗺️Contributing Guidelines 11 | 12 | ### 🚩GitHub Issues 13 | 14 | Our [issues](https://github.com/logspace-ai/langflow/issues) page is kept up to date 15 | with bugs, improvements, and feature requests. There is a taxonomy of labels to help 16 | with sorting and discovery of issues of interest. 17 | 18 | If you're looking for help with your code, consider posting a question on the 19 | [GitHub Discussions board](https://github.com/logspace-ai/langflow/discussions). Please 20 | understand that we won't be able to provide individual support via email. We 21 | also believe that help is much more valuable if it's **shared publicly**, 22 | so that more people can benefit from it. 23 | 24 | - **Describing your issue:** Try to provide as many details as possible. What 25 | exactly goes wrong? _How_ is it failing? Is there an error? 26 | "XY doesn't work" usually isn't that helpful for tracking down problems. Always 27 | remember to include the code you ran and if possible, extract only the relevant 28 | parts and don't just dump your entire script. This will make it easier for us to 29 | reproduce the error. 30 | 31 | - **Sharing long blocks of code or logs:** If you need to include long code, 32 | logs or tracebacks, you can wrap them in `
` and `
`. This 33 | [collapses the content](https://developer.mozilla.org/en/docs/Web/HTML/Element/details) 34 | so it only becomes visible on click, making the issue easier to read and follow. 35 | 36 | ### Issue labels 37 | 38 | [See this page](https://github.com/logspace-ai/langflow/labels) for an overview of 39 | the system we use to tag our issues and pull requests. 40 | 41 | 42 | ### Local development 43 | You can develop LangFlow using docker compose, or locally. 44 | 45 | #### **Docker compose** 46 | This will run the backend and frontend in separate containers. The frontend will be available at `localhost:3000` and the backend at `localhost:7860`. 47 | ```bash 48 | docker compose up --build 49 | # or 50 | make dev build=1 51 | ``` 52 | 53 | #### **Locally** 54 | Run locally by cloning the repository and installing the dependencies. We recommend using a virtual environment to isolate the dependencies from your system. 55 | 56 | Before you start, make sure you have the following installed: 57 | - Poetry 58 | - Node.js 59 | 60 | For the backend, you will need to install the dependencies and start the development server. 61 | ```bash 62 | poetry install 63 | make run_backend 64 | ``` 65 | For the frontend, you will need to install the dependencies and start the development server. 66 | ```bash 67 | cd src/frontend 68 | npm install 69 | npm start 70 | ``` 71 | 72 | -------------------------------------------------------------------------------- /src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { Bars2Icon } from "@heroicons/react/24/outline"; 2 | import DisclosureComponent from "../DisclosureComponent"; 3 | import { nodeColors, nodeIcons, nodeNames } from "../../../../utils"; 4 | import { useContext, useEffect, useState } from "react"; 5 | import { getAll } from "../../../../controllers/API"; 6 | import { typesContext } from "../../../../contexts/typesContext"; 7 | import { 8 | APIClassType, 9 | APIKindType, 10 | APIObjectType, 11 | } from "../../../../types/api"; 12 | 13 | export default function ExtraSidebar() { 14 | const [data, setData] = useState({}); 15 | const { setTypes } = useContext(typesContext); 16 | 17 | useEffect(() => { 18 | async function getTypes(): Promise { 19 | // Make an asynchronous API call to retrieve all data. 20 | let result = await getAll(); 21 | 22 | // Update the state of the component with the retrieved data. 23 | setData(result.data); 24 | 25 | // Set the types by reducing over the keys of the result data and updating the accumulator. 26 | setTypes( 27 | Object.keys(result.data).reduce((acc, curr) => { 28 | Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => { 29 | acc[c] = curr; 30 | // Add the base classes to the accumulator as well. 31 | result.data[curr][c].base_classes?.forEach((b) => { 32 | acc[b] = curr; 33 | }); 34 | }); 35 | return acc; 36 | }, {}) 37 | ); 38 | } 39 | // Call the getTypes function. 40 | getTypes(); 41 | }, [setTypes]); 42 | 43 | function onDragStart( 44 | event: React.DragEvent, 45 | data: { type: string; node?: APIClassType } 46 | ) { 47 | //start drag event 48 | event.dataTransfer.effectAllowed = "move"; 49 | event.dataTransfer.setData("json", JSON.stringify(data)); 50 | } 51 | 52 | return ( 53 |
54 | {Object.keys(data).map((d: keyof APIObjectType, i) => ( 55 | 62 |
63 | {Object.keys(data[d]).map((t: string, k) => ( 64 |
65 |
72 | onDragStart(event, { 73 | type: t, 74 | node: data[d][t], 75 | }) 76 | } 77 | > 78 |
79 | 80 | {t} 81 | 82 | 83 |
84 |
85 |
86 | ))} 87 | {Object.keys(data[d]).length===0 &&
Coming soon
} 88 |
89 |
90 | ))} 91 |
92 | ); 93 | } 94 | -------------------------------------------------------------------------------- /src/backend/langflow/utils/payload.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import re 3 | from typing import Dict 4 | 5 | 6 | def extract_input_variables(nodes): 7 | """ 8 | Extracts input variables from the template 9 | and adds them to the input_variables field. 10 | """ 11 | for node in nodes: 12 | with contextlib.suppress(Exception): 13 | if "input_variables" in node["data"]["node"]["template"]: 14 | if node["data"]["node"]["template"]["_type"] == "prompt": 15 | variables = re.findall( 16 | r"\{(.*?)\}", 17 | node["data"]["node"]["template"]["template"]["value"], 18 | ) 19 | elif node["data"]["node"]["template"]["_type"] == "few_shot": 20 | variables = re.findall( 21 | r"\{(.*?)\}", 22 | node["data"]["node"]["template"]["prefix"]["value"] 23 | + node["data"]["node"]["template"]["suffix"]["value"], 24 | ) 25 | else: 26 | variables = [] 27 | node["data"]["node"]["template"]["input_variables"]["value"] = variables 28 | return nodes 29 | 30 | 31 | def get_root_node(graph): 32 | """ 33 | Returns the root node of the template. 34 | """ 35 | incoming_edges = {edge.source for edge in graph.edges} 36 | return next((node for node in graph.nodes if node not in incoming_edges), None) 37 | 38 | 39 | def build_json(root, graph) -> Dict: 40 | if "node" not in root.data: 41 | # If the root node has no "node" key, then it has only one child, 42 | # which is the target of the single outgoing edge 43 | edge = root.edges[0] 44 | local_nodes = [edge.target] 45 | else: 46 | # Otherwise, find all children whose type matches the type 47 | # specified in the template 48 | node_type = root.node_type 49 | local_nodes = graph.get_nodes_with_target(root) 50 | 51 | if len(local_nodes) == 1: 52 | return build_json(local_nodes[0], graph) 53 | # Build a dictionary from the template 54 | template = root.data["node"]["template"] 55 | final_dict = template.copy() 56 | 57 | for key, value in final_dict.items(): 58 | if key == "_type": 59 | continue 60 | 61 | node_type = value["type"] 62 | 63 | if "value" in value and value["value"] is not None: 64 | # If the value is specified, use it 65 | value = value["value"] 66 | elif "dict" in node_type: 67 | # If the value is a dictionary, create an empty dictionary 68 | value = {} 69 | else: 70 | # Otherwise, recursively build the child nodes 71 | children = [] 72 | for local_node in local_nodes: 73 | node_children = graph.get_children_by_node_type(local_node, node_type) 74 | children.extend(node_children) 75 | 76 | if value["required"] and not children: 77 | raise ValueError(f"No child with type {node_type} found") 78 | values = [build_json(child, graph) for child in children] 79 | value = ( 80 | list(values) 81 | if value["list"] 82 | else next(iter(values), None) # type: ignore 83 | ) 84 | final_dict[key] = value 85 | 86 | return final_dict 87 | -------------------------------------------------------------------------------- /src/frontend/src/components/dropdownComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { Listbox, Transition } from "@headlessui/react"; 2 | import { ChevronUpDownIcon, CheckIcon } from "@heroicons/react/24/outline"; 3 | import { Fragment, useState } from "react"; 4 | import { DropDownComponentType } from "../../types/components"; 5 | import { classNames } from "../../utils"; 6 | 7 | export default function Dropdown({value, options, onSelect}:DropDownComponentType) { 8 | let [internalValue,setInternalValue] = useState(value===""||!value?"Choose an option":value) 9 | return ( 10 | <> 11 | { 12 | setInternalValue(value) 13 | onSelect(value) 14 | }}> 15 | {({ open }) => ( 16 | <> 17 |
18 | 19 | {internalValue} 20 | 21 | 26 | 27 | 28 | 35 | 36 | {options.map((option, id) => ( 37 | 40 | classNames( 41 | active ? "text-white bg-indigo-600" : "text-gray-900", 42 | "relative cursor-default select-none py-2 pl-3 pr-9" 43 | ) 44 | } 45 | value={option} 46 | > 47 | {({ selected, active }) => ( 48 | <> 49 | 55 | {option} 56 | 57 | 58 | {selected ? ( 59 | 65 | 70 | ) : null} 71 | 72 | )} 73 | 74 | ))} 75 | 76 | 77 |
78 | 79 | )} 80 |
81 | 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import { ReactFlowProvider } from "reactflow"; 3 | import TabComponent from "../tabComponent"; 4 | import { TabsContext } from "../../../../contexts/tabsContext"; 5 | import FlowPage from "../.."; 6 | import { darkContext } from "../../../../contexts/darkContext"; 7 | import { 8 | ArrowDownTrayIcon, 9 | ArrowUpTrayIcon, 10 | BellIcon, 11 | MoonIcon, 12 | SunIcon, 13 | } from "@heroicons/react/24/outline"; 14 | import { PopUpContext } from "../../../../contexts/popUpContext"; 15 | import AlertDropdown from "../../../../alerts/alertDropDown"; 16 | import { alertContext } from "../../../../contexts/alertContext"; 17 | import ImportModal from "../../../../modals/importModal"; 18 | import ExportModal from "../../../../modals/exportModal"; 19 | 20 | export default function TabsManagerComponent() { 21 | const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } = 22 | useContext(TabsContext); 23 | const { openPopUp } = useContext(PopUpContext); 24 | const AlertWidth = 256; 25 | const { dark, setDark } = useContext(darkContext); 26 | const { notificationCenter, setNotificationCenter } = 27 | useContext(alertContext); 28 | useEffect(() => { 29 | //create the first flow 30 | if (flows.length === 0) { 31 | addFlow(); 32 | } 33 | }, [addFlow, flows.length]); 34 | 35 | return ( 36 |
37 |
38 | {flows.map((flow, index) => { 39 | return ( 40 | setTabIndex(index)} 42 | selected={index === tabIndex} 43 | key={index} 44 | flow={flow} 45 | /> 46 | ); 47 | })} 48 | { 50 | addFlow(); 51 | }} 52 | selected={false} 53 | flow={null} 54 | /> 55 |
56 | 67 | 73 | 85 | 107 |
108 |
109 |
110 | 111 | {flows[tabIndex] ? ( 112 | 113 | ) : ( 114 | <> 115 | )} 116 | 117 |
118 |
119 | ); 120 | } 121 | -------------------------------------------------------------------------------- /tests/test_endpoints.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from fastapi.testclient import TestClient 3 | from langflow.interface.tools.constants import CUSTOM_TOOLS 4 | 5 | 6 | def test_get_all(client: TestClient): 7 | response = client.get("/all") 8 | assert response.status_code == 200 9 | json_response = response.json() 10 | # We need to test the custom nodes 11 | assert "ZeroShotPrompt" in json_response["prompts"] 12 | # All CUSTOM_TOOLS(dict) should be in the response 13 | assert all(tool in json_response["tools"] for tool in CUSTOM_TOOLS.keys()) 14 | 15 | 16 | def test_post_validate_code(client: TestClient): 17 | # Test case with a valid import and function 18 | code1 = """ 19 | import math 20 | 21 | def square(x): 22 | return x ** 2 23 | """ 24 | response1 = client.post("/validate/code", json={"code": code1}) 25 | assert response1.status_code == 200 26 | assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}} 27 | 28 | # Test case with an invalid import and valid function 29 | code2 = """ 30 | import non_existent_module 31 | 32 | def square(x): 33 | return x ** 2 34 | """ 35 | response2 = client.post("/validate/code", json={"code": code2}) 36 | assert response2.status_code == 200 37 | assert response2.json() == { 38 | "imports": {"errors": ["No module named 'non_existent_module'"]}, 39 | "function": {"errors": []}, 40 | } 41 | 42 | # Test case with a valid import and invalid function syntax 43 | code3 = """ 44 | import math 45 | 46 | def square(x) 47 | return x ** 2 48 | """ 49 | response3 = client.post("/validate/code", json={"code": code3}) 50 | assert response3.status_code == 200 51 | assert response3.json() == { 52 | "imports": {"errors": []}, 53 | "function": {"errors": ["expected ':' (, line 4)"]}, 54 | } 55 | 56 | # Test case with invalid JSON payload 57 | response4 = client.post("/validate/code", json={"invalid_key": code1}) 58 | assert response4.status_code == 422 59 | 60 | # Test case with an empty code string 61 | response5 = client.post("/validate/code", json={"code": ""}) 62 | assert response5.status_code == 200 63 | assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}} 64 | 65 | # Test case with a syntax error in the code 66 | code6 = """ 67 | import math 68 | 69 | def square(x) 70 | return x ** 2 71 | """ 72 | response6 = client.post("/validate/code", json={"code": code6}) 73 | assert response6.status_code == 200 74 | assert response6.json() == { 75 | "imports": {"errors": []}, 76 | "function": {"errors": ["expected ':' (, line 4)"]}, 77 | } 78 | 79 | 80 | VALID_PROMPT = """ 81 | I want you to act as a naming consultant for new companies. 82 | 83 | Here are some examples of good company names: 84 | 85 | - search engine, Google 86 | - social media, Facebook 87 | - video sharing, YouTube 88 | 89 | The name should be short, catchy and easy to remember. 90 | 91 | What is a good name for a company that makes {product}? 92 | """ 93 | 94 | INVALID_PROMPT = "This is an invalid prompt without any input variable." 95 | 96 | 97 | def test_valid_prompt(client: TestClient): 98 | response = client.post("/validate/prompt", json={"template": VALID_PROMPT}) 99 | assert response.status_code == 200 100 | assert response.json() == {"input_variables": ["product"]} 101 | 102 | 103 | def test_invalid_prompt(client: TestClient): 104 | response = client.post("/validate/prompt", json={"template": INVALID_PROMPT}) 105 | assert response.status_code == 200 106 | assert response.json() == {"input_variables": []} 107 | 108 | 109 | @pytest.mark.parametrize( 110 | "prompt,expected_input_variables", 111 | [ 112 | ("{color} is my favorite color.", ["color"]), 113 | ("The weather is {weather} today.", ["weather"]), 114 | ("This prompt has no variables.", []), 115 | ("{a}, {b}, and {c} are variables.", ["a", "b", "c"]), 116 | ], 117 | ) 118 | def test_various_prompts(client, prompt, expected_input_variables): 119 | response = client.post("/validate/prompt", json={"template": prompt}) 120 | assert response.status_code == 200 121 | assert response.json() == { 122 | "input_variables": expected_input_variables, 123 | } 124 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/chains/custom.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional, Type 2 | 3 | from langchain.chains import ConversationChain 4 | from langchain.memory.buffer import ConversationBufferMemory 5 | from langchain.schema import BaseMemory 6 | from pydantic import Field, root_validator 7 | 8 | from langflow.graph.utils import extract_input_variables_from_prompt 9 | 10 | DEFAULT_SUFFIX = """" 11 | Current conversation: 12 | {history} 13 | Human: {input} 14 | {ai_prefix}""" 15 | 16 | 17 | class BaseCustomChain(ConversationChain): 18 | """BaseCustomChain is a chain you can use to have a conversation with a custom character.""" 19 | 20 | template: Optional[str] 21 | 22 | ai_prefix_value: Optional[str] 23 | """Field to use as the ai_prefix. It needs to be set and has to be in the template""" 24 | 25 | @root_validator(pre=False) 26 | def build_template(cls, values): 27 | format_dict = {} 28 | input_variables = extract_input_variables_from_prompt(values["template"]) 29 | 30 | if values.get("ai_prefix_value", None) is None: 31 | values["ai_prefix_value"] = values["memory"].ai_prefix 32 | 33 | for key in input_variables: 34 | new_value = values.get(key, f"{{{key}}}") 35 | format_dict[key] = new_value 36 | if key == values.get("ai_prefix_value", None): 37 | values["memory"].ai_prefix = new_value 38 | 39 | values["template"] = values["template"].format(**format_dict) 40 | 41 | values["template"] = values["template"] 42 | values["input_variables"] = extract_input_variables_from_prompt( 43 | values["template"] 44 | ) 45 | values["prompt"].template = values["template"] 46 | values["prompt"].input_variables = values["input_variables"] 47 | return values 48 | 49 | 50 | class SeriesCharacterChain(BaseCustomChain): 51 | """SeriesCharacterChain is a chain you can use to have a conversation with a character from a series.""" 52 | 53 | character: str 54 | series: str 55 | template: Optional[ 56 | str 57 | ] = """I want you to act like {character} from {series}. 58 | I want you to respond and answer like {character}. do not write any explanations. only answer like {character}. 59 | You must know all of the knowledge of {character}. 60 | Current conversation: 61 | {history} 62 | Human: {input} 63 | {character}:""" 64 | memory: BaseMemory = Field(default_factory=ConversationBufferMemory) 65 | ai_prefix_value: Optional[str] = "character" 66 | """Default memory store.""" 67 | 68 | 69 | class MidJourneyPromptChain(BaseCustomChain): 70 | """MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts.""" 71 | 72 | template: Optional[ 73 | str 74 | ] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program. 75 | Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI. 76 | Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible. 77 | For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures. 78 | The more detailed and imaginative your description, the more interesting the resulting image will be. Here is your first prompt: 79 | "A field of wildflowers stretches out as far as the eye can see, each one a different color and shape. In the distance, a massive tree towers over the landscape, its branches reaching up to the sky like tentacles.\" 80 | 81 | Current conversation: 82 | {history} 83 | Human: {input} 84 | AI:""" # noqa: E501 85 | 86 | 87 | class TimeTravelGuideChain(BaseCustomChain): 88 | template: Optional[ 89 | str 90 | ] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information. 91 | Current conversation: 92 | {history} 93 | Human: {input} 94 | AI:""" # noqa: E501 95 | 96 | 97 | CUSTOM_CHAINS: Dict[str, Type[ConversationChain]] = { 98 | "SeriesCharacterChain": SeriesCharacterChain, 99 | "MidJourneyPromptChain": MidJourneyPromptChain, 100 | "TimeTravelGuideChain": TimeTravelGuideChain, 101 | } 102 | -------------------------------------------------------------------------------- /src/frontend/src/CustomNodes/GenericNode/index.tsx: -------------------------------------------------------------------------------- 1 | import { TrashIcon } from "@heroicons/react/24/outline"; 2 | import { 3 | classNames, 4 | nodeColors, 5 | nodeIcons, 6 | snakeToNormalCase, 7 | } from "../../utils"; 8 | import ParameterComponent from "./components/parameterComponent"; 9 | import { typesContext } from "../../contexts/typesContext"; 10 | import { useContext, useRef } from "react"; 11 | import { NodeDataType } from "../../types/flow"; 12 | import { alertContext } from "../../contexts/alertContext"; 13 | 14 | export default function GenericNode({ 15 | data, 16 | selected, 17 | }: { 18 | data: NodeDataType; 19 | selected: boolean; 20 | }) { 21 | const { setErrorData } = useContext(alertContext); 22 | const showError = useRef(true); 23 | const { types, deleteNode } = useContext(typesContext); 24 | const Icon = nodeIcons[types[data.type]]; 25 | if (!Icon) { 26 | if (showError.current) { 27 | setErrorData({ 28 | title: data.type 29 | ? `The ${data.type} node could not be rendered, please review your json file` 30 | : "There was a node that can't be rendered, please review your json file", 31 | }); 32 | showError.current = false; 33 | } 34 | deleteNode(data.id); 35 | return; 36 | } 37 | return ( 38 |
44 |
45 |
46 | 52 |
{data.type}
53 |
54 | 61 |
62 | 63 |
64 |
65 | {data.node.description} 66 |
67 | 68 | <> 69 | {Object.keys(data.node.template) 70 | .filter((t) => t.charAt(0) !== "_") 71 | .map((t: string, idx) => ( 72 |
73 | {idx === 0 ? ( 74 |
79 | !key.startsWith("_") && data.node.template[key].show 80 | ).length === 0 81 | ? "hidden" 82 | : "" 83 | )} 84 | > 85 | Inputs 86 |
87 | ) : ( 88 | <> 89 | )} 90 | {data.node.template[t].show ? ( 91 | 115 | ) : ( 116 | <> 117 | )} 118 |
119 | ))} 120 |
121 | Output 122 |
123 | 132 | 133 |
134 |
135 | ); 136 | } 137 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Mac 10 | .DS_Store 11 | 12 | # VSCode 13 | .vscode 14 | .chroma 15 | .ruff_cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | *.lcov 32 | 33 | # nyc test coverage 34 | .nyc_output 35 | 36 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 37 | .grunt 38 | 39 | # Bower dependency directory (https://bower.io/) 40 | bower_components 41 | 42 | # node-waf configuration 43 | .lock-wscript 44 | 45 | # Compiled binary addons (https://nodejs.org/api/addons.html) 46 | build/Release 47 | 48 | # Dependency directories 49 | node_modules/ 50 | jspm_packages/ 51 | 52 | # TypeScript v1 declaration files 53 | typings/ 54 | 55 | # TypeScript cache 56 | *.tsbuildinfo 57 | 58 | # Optional npm cache directory 59 | .npm 60 | 61 | # Optional eslint cache 62 | .eslintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variables file 80 | .env 81 | .env.test 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | 86 | # Next.js build output 87 | .next 88 | 89 | # Nuxt.js build / generate output 90 | .nuxt 91 | dist 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # FuseBox cache 106 | .fusebox/ 107 | 108 | # DynamoDB Local files 109 | .dynamodb/ 110 | 111 | # TernJS port file 112 | .tern-port 113 | # Byte-compiled / optimized / DLL files 114 | __pycache__/ 115 | *.py[cod] 116 | *$py.class 117 | notebooks 118 | 119 | # C extensions 120 | *.so 121 | 122 | # Distribution / packaging 123 | .Python 124 | build/ 125 | develop-eggs/ 126 | dist/ 127 | downloads/ 128 | eggs/ 129 | .eggs/ 130 | lib/ 131 | lib64/ 132 | parts/ 133 | sdist/ 134 | var/ 135 | wheels/ 136 | pip-wheel-metadata/ 137 | share/python-wheels/ 138 | *.egg-info/ 139 | .installed.cfg 140 | *.egg 141 | MANIFEST 142 | 143 | # PyInstaller 144 | # Usually these files are written by a python script from a template 145 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 146 | *.manifest 147 | *.spec 148 | 149 | # Installer logs 150 | pip-log.txt 151 | pip-delete-this-directory.txt 152 | 153 | # Unit test / coverage reports 154 | htmlcov/ 155 | .tox/ 156 | .nox/ 157 | .coverage 158 | .coverage.* 159 | .cache 160 | nosetests.xml 161 | coverage.xml 162 | *.cover 163 | *.py,cover 164 | .hypothesis/ 165 | .pytest_cache/ 166 | 167 | # Translations 168 | *.mo 169 | *.pot 170 | 171 | # Django stuff: 172 | *.log 173 | local_settings.py 174 | db.sqlite3 175 | db.sqlite3-journal 176 | 177 | # Flask stuff: 178 | instance/ 179 | .webassets-cache 180 | 181 | # Scrapy stuff: 182 | .scrapy 183 | 184 | # Sphinx documentation 185 | docs/_build/ 186 | 187 | # PyBuilder 188 | target/ 189 | 190 | # Jupyter Notebook 191 | .ipynb_checkpoints 192 | 193 | # IPython 194 | profile_default/ 195 | ipython_config.py 196 | 197 | # pyenv 198 | .python-version 199 | 200 | # pipenv 201 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 202 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 203 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 204 | # install all needed dependencies. 205 | #Pipfile.lock 206 | 207 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 208 | __pypackages__/ 209 | 210 | # Celery stuff 211 | celerybeat-schedule 212 | celerybeat.pid 213 | 214 | # SageMath parsed files 215 | *.sage.py 216 | 217 | # Environments 218 | .env 219 | .venv 220 | env/ 221 | venv/ 222 | ENV/ 223 | env.bak/ 224 | venv.bak/ 225 | 226 | # Spyder project settings 227 | .spyderproject 228 | .spyproject 229 | 230 | # Rope project settings 231 | .ropeproject 232 | 233 | # mkdocs documentation 234 | /site 235 | 236 | # mypy 237 | .mypy_cache/ 238 | .dmypy.json 239 | dmypy.json 240 | 241 | # Poetry 242 | .testenv/* 243 | -------------------------------------------------------------------------------- /src/backend/langflow/cache/utils.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import contextlib 3 | import functools 4 | import hashlib 5 | import json 6 | import os 7 | import tempfile 8 | from collections import OrderedDict 9 | from pathlib import Path 10 | 11 | import dill # type: ignore 12 | 13 | 14 | def create_cache_folder(func): 15 | def wrapper(*args, **kwargs): 16 | # Get the destination folder 17 | cache_path = Path(tempfile.gettempdir()) / PREFIX 18 | 19 | # Create the destination folder if it doesn't exist 20 | os.makedirs(cache_path, exist_ok=True) 21 | 22 | return func(*args, **kwargs) 23 | 24 | return wrapper 25 | 26 | 27 | def memoize_dict(maxsize=128): 28 | cache = OrderedDict() 29 | 30 | def decorator(func): 31 | @functools.wraps(func) 32 | def wrapper(*args, **kwargs): 33 | hashed = compute_dict_hash(args[0]) 34 | key = (func.__name__, hashed, frozenset(kwargs.items())) 35 | if key not in cache: 36 | result = func(*args, **kwargs) 37 | cache[key] = result 38 | if len(cache) > maxsize: 39 | cache.popitem(last=False) 40 | else: 41 | result = cache[key] 42 | return result 43 | 44 | def clear_cache(): 45 | cache.clear() 46 | 47 | wrapper.clear_cache = clear_cache 48 | return wrapper 49 | 50 | return decorator 51 | 52 | 53 | PREFIX = "langflow_cache" 54 | 55 | 56 | @create_cache_folder 57 | def clear_old_cache_files(max_cache_size: int = 3): 58 | cache_dir = Path(tempfile.gettempdir()) / PREFIX 59 | cache_files = list(cache_dir.glob("*.dill")) 60 | 61 | if len(cache_files) > max_cache_size: 62 | cache_files_sorted_by_mtime = sorted( 63 | cache_files, key=lambda x: x.stat().st_mtime, reverse=True 64 | ) 65 | 66 | for cache_file in cache_files_sorted_by_mtime[max_cache_size:]: 67 | with contextlib.suppress(OSError): 68 | os.remove(cache_file) 69 | 70 | 71 | def compute_dict_hash(graph_data): 72 | graph_data = filter_json(graph_data) 73 | 74 | cleaned_graph_json = json.dumps(graph_data, sort_keys=True) 75 | return hashlib.sha256(cleaned_graph_json.encode("utf-8")).hexdigest() 76 | 77 | 78 | def filter_json(json_data): 79 | filtered_data = json_data.copy() 80 | 81 | # Remove 'viewport' and 'chatHistory' keys 82 | if "viewport" in filtered_data: 83 | del filtered_data["viewport"] 84 | if "chatHistory" in filtered_data: 85 | del filtered_data["chatHistory"] 86 | 87 | # Filter nodes 88 | if "nodes" in filtered_data: 89 | for node in filtered_data["nodes"]: 90 | if "position" in node: 91 | del node["position"] 92 | if "positionAbsolute" in node: 93 | del node["positionAbsolute"] 94 | if "selected" in node: 95 | del node["selected"] 96 | if "dragging" in node: 97 | del node["dragging"] 98 | 99 | return filtered_data 100 | 101 | 102 | @create_cache_folder 103 | def save_binary_file(content: str, file_name: str, accepted_types: list[str]) -> str: 104 | """ 105 | Save a binary file to the specified folder. 106 | 107 | Args: 108 | content: The content of the file as a bytes object. 109 | file_name: The name of the file, including its extension. 110 | 111 | Returns: 112 | The path to the saved file. 113 | """ 114 | if not any(file_name.endswith(suffix) for suffix in accepted_types): 115 | raise ValueError(f"File {file_name} is not accepted") 116 | 117 | # Get the destination folder 118 | cache_path = Path(tempfile.gettempdir()) / PREFIX 119 | 120 | data = content.split(",")[1] 121 | decoded_bytes = base64.b64decode(data) 122 | 123 | # Create the full file path 124 | file_path = os.path.join(cache_path, file_name) 125 | 126 | # Save the binary content to the file 127 | with open(file_path, "wb") as file: 128 | file.write(decoded_bytes) 129 | 130 | return file_path 131 | 132 | 133 | @create_cache_folder 134 | def save_cache(hash_val: str, chat_data, clean_old_cache_files: bool): 135 | cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill" 136 | with cache_path.open("wb") as cache_file: 137 | dill.dump(chat_data, cache_file) 138 | 139 | if clean_old_cache_files: 140 | clear_old_cache_files() 141 | 142 | 143 | @create_cache_folder 144 | def load_cache(hash_val): 145 | cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill" 146 | if cache_path.exists(): 147 | with cache_path.open("rb") as cache_file: 148 | return dill.load(cache_file) 149 | return None 150 | -------------------------------------------------------------------------------- /src/frontend/src/modals/importModal/index.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from "@headlessui/react"; 2 | import { 3 | XMarkIcon, 4 | ArrowDownTrayIcon, 5 | DocumentDuplicateIcon, 6 | ComputerDesktopIcon, 7 | ArrowUpTrayIcon, 8 | } from "@heroicons/react/24/outline"; 9 | import { Fragment, useContext, useRef, useState } from "react"; 10 | import { PopUpContext } from "../../contexts/popUpContext"; 11 | import { TabsContext } from "../../contexts/tabsContext"; 12 | import ButtonBox from "./buttonBox"; 13 | 14 | export default function ImportModal() { 15 | const [open, setOpen] = useState(true); 16 | const { closePopUp } = useContext(PopUpContext); 17 | const ref = useRef(); 18 | const {uploadFlow} = useContext(TabsContext) 19 | function setModalOpen(x: boolean) { 20 | setOpen(x); 21 | if (x === false) { 22 | setTimeout(() => { 23 | closePopUp(); 24 | }, 300); 25 | } 26 | } 27 | return ( 28 | 29 | 35 | 44 |
45 | 46 | 47 |
48 |
49 | 58 | 59 |
60 | 70 |
71 |
72 |
73 |
74 |
79 |
80 | 84 | Import from 85 | 86 |
87 |
88 |
89 |
90 | 96 | } 97 | onClick={() => console.log("sdsds")} 98 | textColor="text-slate-400" 99 | title="Examples" 100 | > 101 | 106 | } 107 | onClick={() => {uploadFlow();setModalOpen(false)}} 108 | textColor="text-blue-500" 109 | title="Local file" 110 | > 111 |
112 |
113 | 114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/importing/utils.py: -------------------------------------------------------------------------------- 1 | # This module is used to import any langchain class by name. 2 | 3 | import importlib 4 | from typing import Any, Type 5 | 6 | from langchain import PromptTemplate 7 | from langchain.agents import Agent 8 | from langchain.chains.base import Chain 9 | from langchain.chat_models.base import BaseChatModel 10 | from langchain.llms.base import BaseLLM 11 | from langchain.tools import BaseTool 12 | 13 | from langflow.interface.tools.util import get_tool_by_name 14 | 15 | 16 | def import_module(module_path: str) -> Any: 17 | """Import module from module path""" 18 | if "from" not in module_path: 19 | # Import the module using the module path 20 | return importlib.import_module(module_path) 21 | # Split the module path into its components 22 | _, module_path, _, object_name = module_path.split() 23 | 24 | # Import the module using the module path 25 | module = importlib.import_module(module_path) 26 | 27 | return getattr(module, object_name) 28 | 29 | 30 | def import_by_type(_type: str, name: str) -> Any: 31 | """Import class by type and name""" 32 | if _type is None: 33 | raise ValueError(f"Type cannot be None. Check if {name} is in the config file.") 34 | func_dict = { 35 | "agents": import_agent, 36 | "prompts": import_prompt, 37 | "llms": {"llm": import_llm, "chat": import_chat_llm}, 38 | "tools": import_tool, 39 | "chains": import_chain, 40 | "toolkits": import_toolkit, 41 | "wrappers": import_wrapper, 42 | "memory": import_memory, 43 | "embeddings": import_embedding, 44 | "vectorstores": import_vectorstore, 45 | "documentloaders": import_documentloader, 46 | "textsplitters": import_textsplitter, 47 | } 48 | if _type == "llms": 49 | key = "chat" if "chat" in name.lower() else "llm" 50 | loaded_func = func_dict[_type][key] # type: ignore 51 | else: 52 | loaded_func = func_dict[_type] 53 | 54 | return loaded_func(name) 55 | 56 | 57 | def import_chat_llm(llm: str) -> BaseChatModel: 58 | """Import chat llm from llm name""" 59 | return import_class(f"langchain.chat_models.{llm}") 60 | 61 | 62 | def import_memory(memory: str) -> Any: 63 | """Import memory from memory name""" 64 | return import_module(f"from langchain.memory import {memory}") 65 | 66 | 67 | def import_class(class_path: str) -> Any: 68 | """Import class from class path""" 69 | module_path, class_name = class_path.rsplit(".", 1) 70 | module = import_module(module_path) 71 | return getattr(module, class_name) 72 | 73 | 74 | def import_prompt(prompt: str) -> Type[PromptTemplate]: 75 | from langflow.interface.prompts.custom import CUSTOM_PROMPTS 76 | 77 | """Import prompt from prompt name""" 78 | if prompt == "ZeroShotPrompt": 79 | return import_class("langchain.prompts.PromptTemplate") 80 | elif prompt in CUSTOM_PROMPTS: 81 | return CUSTOM_PROMPTS[prompt] 82 | return import_class(f"langchain.prompts.{prompt}") 83 | 84 | 85 | def import_wrapper(wrapper: str) -> Any: 86 | """Import wrapper from wrapper name""" 87 | return import_module(f"from langchain.requests import {wrapper}") 88 | 89 | 90 | def import_toolkit(toolkit: str) -> Any: 91 | """Import toolkit from toolkit name""" 92 | return import_module(f"from langchain.agents.agent_toolkits import {toolkit}") 93 | 94 | 95 | def import_agent(agent: str) -> Agent: 96 | """Import agent from agent name""" 97 | # check for custom agent 98 | 99 | return import_class(f"langchain.agents.{agent}") 100 | 101 | 102 | def import_llm(llm: str) -> BaseLLM: 103 | """Import llm from llm name""" 104 | return import_class(f"langchain.llms.{llm}") 105 | 106 | 107 | def import_tool(tool: str) -> BaseTool: 108 | """Import tool from tool name""" 109 | 110 | return get_tool_by_name(tool) 111 | 112 | 113 | def import_chain(chain: str) -> Type[Chain]: 114 | """Import chain from chain name""" 115 | from langflow.interface.chains.custom import CUSTOM_CHAINS 116 | 117 | if chain in CUSTOM_CHAINS: 118 | return CUSTOM_CHAINS[chain] 119 | return import_class(f"langchain.chains.{chain}") 120 | 121 | 122 | def import_embedding(embedding: str) -> Any: 123 | """Import embedding from embedding name""" 124 | return import_class(f"langchain.embeddings.{embedding}") 125 | 126 | 127 | def import_vectorstore(vectorstore: str) -> Any: 128 | """Import vectorstore from vectorstore name""" 129 | return import_class(f"langchain.vectorstores.{vectorstore}") 130 | 131 | 132 | def import_documentloader(documentloader: str) -> Any: 133 | """Import documentloader from documentloader name""" 134 | 135 | return import_class(f"langchain.document_loaders.{documentloader}") 136 | 137 | 138 | def import_textsplitter(textsplitter: str) -> Any: 139 | """Import textsplitter from textsplitter name""" 140 | return import_class(f"langchain.text_splitter.{textsplitter}") 141 | -------------------------------------------------------------------------------- /src/backend/langflow/interface/tools/util.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import inspect 3 | from typing import Dict, Union 4 | 5 | from langchain.agents.tools import Tool 6 | 7 | from langflow.interface.tools.constants import ALL_TOOLS_NAMES 8 | 9 | 10 | def get_tools_dict(): 11 | """Get the tools dictionary.""" 12 | 13 | all_tools = {} 14 | 15 | for tool, fcn in ALL_TOOLS_NAMES.items(): 16 | if tool_params := get_tool_params(fcn): 17 | tool_name = tool_params.get("name") or str(tool) 18 | all_tools[tool_name] = fcn 19 | 20 | return all_tools 21 | 22 | 23 | def get_tool_by_name(name: str): 24 | """Get a tool from the tools dictionary.""" 25 | tools = get_tools_dict() 26 | if name not in tools: 27 | raise ValueError(f"{name} not found.") 28 | return tools[name] 29 | 30 | 31 | def get_func_tool_params(func, **kwargs) -> Union[Dict, None]: 32 | tree = ast.parse(inspect.getsource(func)) 33 | 34 | # Iterate over the statements in the abstract syntax tree 35 | for node in ast.walk(tree): 36 | # Find the first return statement 37 | if isinstance(node, ast.Return): 38 | tool = node.value 39 | if isinstance(tool, ast.Call): 40 | if isinstance(tool.func, ast.Name) and tool.func.id == "Tool": 41 | if tool.keywords: 42 | tool_params = {} 43 | for keyword in tool.keywords: 44 | if keyword.arg == "name": 45 | tool_params["name"] = ast.literal_eval(keyword.value) 46 | elif keyword.arg == "description": 47 | tool_params["description"] = ast.literal_eval( 48 | keyword.value 49 | ) 50 | 51 | return tool_params 52 | return { 53 | "name": ast.literal_eval(tool.args[0]), 54 | "description": ast.literal_eval(tool.args[2]), 55 | } 56 | # 57 | else: 58 | # get the class object from the return statement 59 | try: 60 | class_obj = eval( 61 | compile(ast.Expression(tool), "", "eval") 62 | ) 63 | except Exception: 64 | return None 65 | 66 | return { 67 | "name": getattr(class_obj, "name"), 68 | "description": getattr(class_obj, "description"), 69 | } 70 | # Return None if no return statement was found 71 | return None 72 | 73 | 74 | def get_class_tool_params(cls, **kwargs) -> Union[Dict, None]: 75 | tree = ast.parse(inspect.getsource(cls)) 76 | 77 | tool_params = {} 78 | 79 | # Iterate over the statements in the abstract syntax tree 80 | for node in ast.walk(tree): 81 | if isinstance(node, ast.ClassDef): 82 | # Find the class definition and look for methods 83 | for stmt in node.body: 84 | if isinstance(stmt, ast.FunctionDef) and stmt.name == "__init__": 85 | # There is no assignment statements in the __init__ method 86 | # So we need to get the params from the function definition 87 | for arg in stmt.args.args: 88 | if arg.arg == "name": 89 | # It should be the name of the class 90 | tool_params[arg.arg] = cls.__name__ 91 | elif arg.arg == "self": 92 | continue 93 | # If there is not default value, set it to an empty string 94 | else: 95 | try: 96 | annotation = ast.literal_eval(arg.annotation) # type: ignore 97 | tool_params[arg.arg] = annotation 98 | except ValueError: 99 | tool_params[arg.arg] = "" 100 | # Get the attribute name and the annotation 101 | elif cls != Tool and isinstance(stmt, ast.AnnAssign): 102 | # Get the attribute name and the annotation 103 | tool_params[stmt.target.id] = "" # type: ignore 104 | 105 | return tool_params 106 | 107 | 108 | def get_tool_params(tool, **kwargs) -> Dict: 109 | # Parse the function code into an abstract syntax tree 110 | # Define if it is a function or a class 111 | if inspect.isfunction(tool): 112 | return get_func_tool_params(tool, **kwargs) or {} 113 | elif inspect.isclass(tool): 114 | # Get the parameters necessary to 115 | # instantiate the class 116 | return get_class_tool_params(tool, **kwargs) or {} 117 | else: 118 | raise ValueError("Tool must be a function or class.") 119 | --------------------------------------------------------------------------------