refac: langflow_backend -> langflow
This commit is contained in:
parent
aa8e1aee8a
commit
70dbc7eb1e
104 changed files with 174 additions and 50 deletions
133
src/backend/.gitignore
vendored
Normal file
133
src/backend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
notebooks
|
||||
|
||||
# frontend
|
||||
src/frontend
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
14
src/backend/Dockerfile
Normal file
14
src/backend/Dockerfile
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
FROM logspace/backend_build as backend_build
|
||||
|
||||
FROM python:3.10-slim
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install git -y
|
||||
|
||||
COPY --from=backend_build /app/dist/*.whl /app/
|
||||
RUN pip install langflow-*.whl
|
||||
RUN rm *.whl
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD [ "uvicorn", "--host", "0.0.0.0", "--port", "80", "langflow.backend.app:app" ]
|
||||
52
src/backend/build.Dockerfile
Normal file
52
src/backend/build.Dockerfile
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# `python-base` sets up all our shared environment variables
|
||||
FROM python:3.10-slim
|
||||
|
||||
# python
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
# prevents python creating .pyc files
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
\
|
||||
# pip
|
||||
PIP_NO_CACHE_DIR=off \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||
PIP_DEFAULT_TIMEOUT=100 \
|
||||
\
|
||||
# poetry
|
||||
# https://python-poetry.org/docs/configuration/#using-environment-variables
|
||||
POETRY_VERSION=1.4.0 \
|
||||
# make poetry install to this location
|
||||
POETRY_HOME="/opt/poetry" \
|
||||
# make poetry create the virtual environment in the project's root
|
||||
# it gets named `.venv`
|
||||
POETRY_VIRTUALENVS_IN_PROJECT=true \
|
||||
# do not ask any interactive question
|
||||
POETRY_NO_INTERACTION=1 \
|
||||
\
|
||||
# paths
|
||||
# this is where our requirements + virtual environment will live
|
||||
PYSETUP_PATH="/opt/pysetup" \
|
||||
VENV_PATH="/opt/pysetup/.venv"
|
||||
|
||||
# prepend poetry and venv to path
|
||||
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
# deps for installing poetry
|
||||
curl \
|
||||
# deps for building python deps
|
||||
build-essential libpq-dev
|
||||
|
||||
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
# copy project requirement files here to ensure they will be cached.
|
||||
WORKDIR /app
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
COPY langflow/ ./langflow
|
||||
|
||||
# poetry install
|
||||
RUN poetry install --without dev
|
||||
|
||||
# build wheel
|
||||
RUN poetry build -f wheel
|
||||
6
src/backend/build_and_push
Executable file
6
src/backend/build_and_push
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#! /bin/bash
|
||||
|
||||
docker build -t logspace/backend_build -f build.Dockerfile .
|
||||
VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version)
|
||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION .
|
||||
docker push ibiscp/langflow:$VERSION
|
||||
1
src/backend/langflow/__init__.py
Normal file
1
src/backend/langflow/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from langflow.interface.loading import load_flow_from_json # noqa
|
||||
57
src/backend/langflow/__main__.py
Normal file
57
src/backend/langflow/__main__.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import multiprocessing
|
||||
import platform
|
||||
|
||||
from langflow.main import create_app
|
||||
|
||||
import typer
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_number_of_workers(workers=None):
|
||||
if workers == -1:
|
||||
workers = (multiprocessing.cpu_count() * 2) + 1
|
||||
return workers
|
||||
|
||||
|
||||
def serve(
|
||||
host: str = "127.0.0.1",
|
||||
workers: int = 1,
|
||||
timeout: int = 60,
|
||||
):
|
||||
app = create_app()
|
||||
# get the directory of the current file
|
||||
path = Path(__file__).parent
|
||||
static_files_dir = path / "frontend"
|
||||
app.mount(
|
||||
"/",
|
||||
StaticFiles(directory=static_files_dir, html=True),
|
||||
name="static",
|
||||
)
|
||||
port = 5003
|
||||
options = {
|
||||
"bind": f"{host}:{port}",
|
||||
"workers": get_number_of_workers(workers),
|
||||
"worker_class": "uvicorn.workers.UvicornWorker",
|
||||
"timeout": timeout,
|
||||
}
|
||||
|
||||
if platform.system() in ["Darwin", "Windows"]:
|
||||
# Run using uvicorn on MacOS and Windows
|
||||
# Windows doesn't support gunicorn
|
||||
# MacOS requires a env variable to be set to use gunicorn
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host=host, port=port, log_level="info")
|
||||
else:
|
||||
from langflow.server import LangflowApplication
|
||||
|
||||
LangflowApplication(app, options).run()
|
||||
|
||||
|
||||
def main():
|
||||
typer.run(serve)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
0
src/backend/langflow/api/__init__.py
Normal file
0
src/backend/langflow/api/__init__.py
Normal file
21
src/backend/langflow/api/endpoints.py
Normal file
21
src/backend/langflow/api/endpoints.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from fastapi import APIRouter, HTTPException
|
||||
from langflow.interface.types import build_langchain_types_dict
|
||||
from langflow.interface.run import process_data_graph
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
# build router
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
def get_all():
|
||||
return build_langchain_types_dict()
|
||||
|
||||
|
||||
@router.post("/predict")
|
||||
def get_load(data: Dict[str, Any]):
|
||||
try:
|
||||
return process_data_graph(data)
|
||||
except Exception as e:
|
||||
return HTTPException(status_code=500, detail=str(e))
|
||||
58
src/backend/langflow/api/list_endpoints.py
Normal file
58
src/backend/langflow/api/list_endpoints.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
from langflow.interface.listing import list_type
|
||||
|
||||
# build router
|
||||
router = APIRouter(
|
||||
prefix="/list",
|
||||
tags=["list"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/")
|
||||
def read_items():
|
||||
"""List all components"""
|
||||
return [
|
||||
"chains",
|
||||
"agents",
|
||||
"prompts",
|
||||
"llms",
|
||||
"tools",
|
||||
]
|
||||
|
||||
|
||||
@router.get("/chains")
|
||||
def list_chains():
|
||||
"""List all chain types"""
|
||||
return list_type("chains")
|
||||
|
||||
|
||||
@router.get("/agents")
|
||||
def list_agents():
|
||||
"""List all agent types"""
|
||||
# return list(agents.loading.AGENT_TO_CLASS.keys())
|
||||
return list_type("agents")
|
||||
|
||||
|
||||
@router.get("/prompts")
|
||||
def list_prompts():
|
||||
"""List all prompt types"""
|
||||
return list_type("prompts")
|
||||
|
||||
|
||||
@router.get("/llms")
|
||||
def list_llms():
|
||||
"""List all llm types"""
|
||||
return list_type("llms")
|
||||
|
||||
|
||||
@router.get("/memories")
|
||||
def list_memories():
|
||||
"""List all memory types"""
|
||||
return list_type("memories")
|
||||
|
||||
|
||||
@router.get("/tools")
|
||||
def list_tools():
|
||||
"""List all load tools"""
|
||||
return list_type("tools")
|
||||
63
src/backend/langflow/api/signature.py
Normal file
63
src/backend/langflow/api/signature.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from langflow.interface.signature import get_signature
|
||||
|
||||
# build router
|
||||
router = APIRouter(
|
||||
prefix="/signatures",
|
||||
tags=["signatures"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/chain")
|
||||
def get_chain(name: str):
|
||||
"""Get the signature of a chain."""
|
||||
try:
|
||||
return get_signature(name, "chains")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="Chain not found") from exc
|
||||
|
||||
|
||||
@router.get("/agent")
|
||||
def get_agent(name: str):
|
||||
"""Get the signature of an agent."""
|
||||
try:
|
||||
return get_signature(name, "agents")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="Agent not found") from exc
|
||||
|
||||
|
||||
@router.get("/prompt")
|
||||
def get_prompt(name: str):
|
||||
"""Get the signature of a prompt."""
|
||||
try:
|
||||
return get_signature(name, "prompts")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="Prompt not found") from exc
|
||||
|
||||
|
||||
@router.get("/llm")
|
||||
def get_llm(name: str):
|
||||
"""Get the signature of an llm."""
|
||||
try:
|
||||
return get_signature(name, "llms")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="LLM not found") from exc
|
||||
|
||||
|
||||
@router.get("/memory")
|
||||
def get_memory(name: str):
|
||||
"""Get the signature of a memory."""
|
||||
try:
|
||||
return get_signature(name, "memories")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="Memory not found") from exc
|
||||
|
||||
|
||||
@router.get("/tool")
|
||||
def get_tool(name: str):
|
||||
"""Get the signature of a tool."""
|
||||
try:
|
||||
return get_signature(name, "tools")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="Tool not found") from exc
|
||||
0
src/backend/langflow/custom/__init__.py
Normal file
0
src/backend/langflow/custom/__init__.py
Normal file
42
src/backend/langflow/custom/customs.py
Normal file
42
src/backend/langflow/custom/customs.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from langchain.agents.mrkl import prompt
|
||||
|
||||
|
||||
def get_custom_prompts():
|
||||
"""Get custom prompts."""
|
||||
|
||||
return {
|
||||
"ZeroShotPrompt": {
|
||||
"template": {
|
||||
"_type": "zero_shot",
|
||||
"prefix": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": True,
|
||||
"multiline": True,
|
||||
"value": prompt.PREFIX,
|
||||
},
|
||||
"suffix": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": True,
|
||||
"multiline": True,
|
||||
"value": prompt.SUFFIX,
|
||||
},
|
||||
"format_instructions": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": True,
|
||||
"multiline": True,
|
||||
"value": prompt.FORMAT_INSTRUCTIONS,
|
||||
},
|
||||
},
|
||||
"description": "Prompt template for Zero Shot Agent.",
|
||||
"base_classes": ["BasePromptTemplate"],
|
||||
}
|
||||
}
|
||||
15
src/backend/langflow/frontend/asset-manifest.json
Normal file
15
src/backend/langflow/frontend/asset-manifest.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.4028c70d.css",
|
||||
"main.js": "/static/js/main.8d80b6b5.js",
|
||||
"static/js/787.f861006f.chunk.js": "/static/js/787.f861006f.chunk.js",
|
||||
"index.html": "/index.html",
|
||||
"main.4028c70d.css.map": "/static/css/main.4028c70d.css.map",
|
||||
"main.8d80b6b5.js.map": "/static/js/main.8d80b6b5.js.map",
|
||||
"787.f861006f.chunk.js.map": "/static/js/787.f861006f.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.4028c70d.css",
|
||||
"static/js/main.8d80b6b5.js"
|
||||
]
|
||||
}
|
||||
1
src/backend/langflow/frontend/index.html
Normal file
1
src/backend/langflow/frontend/index.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>LangFLow</title><script defer="defer" src="/static/js/main.8d80b6b5.js"></script><link href="/static/css/main.4028c70d.css" rel="stylesheet"></head><body id="body" style="width:100%;height:100%"><noscript>You need to enable JavaScript to run this app.</noscript><div style="width:100vw;height:100vh" id="root"></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,2 @@
|
|||
"use strict";(self.webpackChunklangflow=self.webpackChunklangflow||[]).push([[787],{787:function(e,n,t){t.r(n),t.d(n,{getCLS:function(){return y},getFCP:function(){return g},getFID:function(){return C},getLCP:function(){return P},getTTFB:function(){return D}});var i,r,a,o,u=function(e,n){return{name:e,value:void 0===n?-1:n,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,n){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var t=new PerformanceObserver((function(e){return e.getEntries().map(n)}));return t.observe({type:e,buffered:!0}),t}}catch(e){}},f=function(e,n){var t=function t(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),n&&(removeEventListener("visibilitychange",t,!0),removeEventListener("pagehide",t,!0)))};addEventListener("visibilitychange",t,!0),addEventListener("pagehide",t,!0)},s=function(e){addEventListener("pageshow",(function(n){n.persisted&&e(n)}),!0)},m=function(e,n,t){var i;return function(r){n.value>=0&&(r||t)&&(n.delta=n.value-(i||0),(n.delta||void 0===i)&&(i=n.value,e(n)))}},v=-1,p=function(){return"hidden"===document.visibilityState?0:1/0},d=function(){f((function(e){var n=e.timeStamp;v=n}),!0)},l=function(){return v<0&&(v=p(),d(),s((function(){setTimeout((function(){v=p(),d()}),0)}))),{get firstHiddenTime(){return v}}},g=function(e,n){var t,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(f&&f.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=e.startTime,r.entries.push(e),t(!0)))},o=window.performance&&performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],f=o?null:c("paint",a);(o||f)&&(t=m(e,r,n),o&&a(o),s((function(i){r=u("FCP"),t=m(e,r,n),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,t(!0)}))}))})))},h=!1,T=-1,y=function(e,n){h||(g((function(e){T=e.value})),h=!0);var t,i=function(n){T>-1&&e(n)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var n=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-n.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,t())}},p=c("layout-shift",v);p&&(t=m(i,r,n),f((function(){p.takeRecords().map(v),t(!0)})),s((function(){a=0,T=-1,r=u("CLS",0),t=m(i,r,n)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,n){i||(i=n,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r<a-w){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+r};o.forEach((function(n){n(e)})),o=[]}},b=function(e){if(e.cancelable){var n=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,n){var t=function(){L(e,n),r()},i=function(){r()},r=function(){removeEventListener("pointerup",t,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",t,E),addEventListener("pointercancel",i,E)}(n,e):L(n,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(n){return e(n,b,E)}))},C=function(e,n){var t,a=l(),v=u("FID"),p=function(e){e.startTime<a.firstHiddenTime&&(v.value=e.processingStart-e.startTime,v.entries.push(e),t(!0))},d=c("first-input",p);t=m(e,v,n),d&&f((function(){d.takeRecords().map(p),d.disconnect()}),!0),d&&s((function(){var a;v=u("FID"),t=m(e,v,n),o=[],r=-1,i=null,F(addEventListener),a=p,o.push(a),S()}))},k={},P=function(e,n){var t,i=l(),r=u("LCP"),a=function(e){var n=e.startTime;n<i.firstHiddenTime&&(r.value=n,r.entries.push(e),t())},o=c("largest-contentful-paint",a);if(o){t=m(e,r,n);var v=function(){k[r.id]||(o.takeRecords().map(a),o.disconnect(),k[r.id]=!0,t(!0))};["keydown","click"].forEach((function(e){addEventListener(e,v,{once:!0,capture:!0})})),f(v,!0),s((function(i){r=u("LCP"),t=m(e,r,n),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,k[r.id]=!0,t(!0)}))}))}))}},D=function(e){var n,t=u("TTFB");n=function(){try{var n=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,n={entryType:"navigation",startTime:0};for(var t in e)"navigationStart"!==t&&"toJSON"!==t&&(n[t]=Math.max(e[t]-e.navigationStart,0));return n}();if(t.value=t.delta=n.responseStart,t.value<0||t.value>performance.now())return;t.entries=[n],e(t)}catch(e){}},"complete"===document.readyState?setTimeout(n,0):addEventListener("load",(function(){return setTimeout(n,0)}))}}}]);
|
||||
//# sourceMappingURL=787.f861006f.chunk.js.map
|
||||
File diff suppressed because one or more lines are too long
3
src/backend/langflow/frontend/static/js/main.8d80b6b5.js
Normal file
3
src/backend/langflow/frontend/static/js/main.8d80b6b5.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,100 @@
|
|||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Lodash <https://lodash.com/>
|
||||
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
|
||||
* Released under MIT license <https://lodash.com/license>
|
||||
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @mui/styled-engine v5.11.9
|
||||
*
|
||||
* @license MIT
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @remix-run/router v1.3.2
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router DOM v6.8.1
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
File diff suppressed because one or more lines are too long
0
src/backend/langflow/interface/__init__.py
Normal file
0
src/backend/langflow/interface/__init__.py
Normal file
74
src/backend/langflow/interface/listing.py
Normal file
74
src/backend/langflow/interface/listing.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
from langchain import chains, agents, prompts, llms
|
||||
from langflow.custom import customs
|
||||
from langflow.utils import util, allowed_components
|
||||
from langchain.agents.load_tools import get_all_tool_names
|
||||
from langchain.chains.conversation import memory as memories
|
||||
|
||||
|
||||
def list_type(object_type: str):
|
||||
"""List all components"""
|
||||
return {
|
||||
"chains": list_chain_types,
|
||||
"agents": list_agents,
|
||||
"prompts": list_prompts,
|
||||
"llms": list_llms,
|
||||
"tools": list_tools,
|
||||
"memories": list_memories,
|
||||
}.get(object_type, lambda: "Invalid type")()
|
||||
|
||||
|
||||
def list_agents():
|
||||
"""List all agent types"""
|
||||
# return list(agents.loading.AGENT_TO_CLASS.keys())
|
||||
return [
|
||||
agent.__name__
|
||||
for agent in agents.loading.AGENT_TO_CLASS.values()
|
||||
if agent.__name__ in allowed_components.AGENTS
|
||||
]
|
||||
|
||||
|
||||
def list_prompts():
|
||||
"""List all prompt types"""
|
||||
custom_prompts = customs.get_custom_prompts()
|
||||
library_prompts = [
|
||||
prompt.__annotations__["return"].__name__
|
||||
for prompt in prompts.loading.type_to_loader_dict.values()
|
||||
if prompt.__annotations__["return"].__name__ in allowed_components.PROMPTS
|
||||
]
|
||||
return library_prompts + list(custom_prompts.keys())
|
||||
|
||||
|
||||
def list_tools():
|
||||
"""List all load tools"""
|
||||
|
||||
tools = []
|
||||
|
||||
for tool in get_all_tool_names():
|
||||
tool_params = util.get_tool_params(util.get_tools_dict(tool))
|
||||
if tool_params and tool_params["name"] in allowed_components.TOOLS:
|
||||
tools.append(tool_params["name"])
|
||||
|
||||
return tools
|
||||
|
||||
|
||||
def list_llms():
|
||||
"""List all llm types"""
|
||||
return [
|
||||
llm.__name__
|
||||
for llm in llms.type_to_cls_dict.values()
|
||||
if llm.__name__ in allowed_components.LLMS
|
||||
]
|
||||
|
||||
|
||||
def list_chain_types():
|
||||
"""List all chain types"""
|
||||
return [
|
||||
chain.__annotations__["return"].__name__
|
||||
for chain in chains.loading.type_to_loader_dict.values()
|
||||
if chain.__annotations__["return"].__name__ in allowed_components.CHAINS
|
||||
]
|
||||
|
||||
|
||||
def list_memories():
|
||||
"""List all memory types"""
|
||||
return [memory.__name__ for memory in memories.type_to_cls_dict.values()]
|
||||
205
src/backend/langflow/interface/loading.py
Normal file
205
src/backend/langflow/interface/loading.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
import json
|
||||
from typing import Any, Dict, Optional
|
||||
from langflow.interface.types import get_type_list
|
||||
from langchain.agents.loading import load_agent_from_config
|
||||
from langchain.chains.loading import load_chain_from_config
|
||||
from langchain.llms.loading import load_llm_from_config
|
||||
from langflow.utils import payload
|
||||
from langflow.utils import util
|
||||
from langchain.llms.base import BaseLLM
|
||||
|
||||
from langchain.agents.agent import AgentExecutor
|
||||
from langchain.callbacks.base import BaseCallbackManager
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.agents.load_tools import (
|
||||
_BASE_TOOLS,
|
||||
_LLM_TOOLS,
|
||||
_EXTRA_LLM_TOOLS,
|
||||
_EXTRA_OPTIONAL_TOOLS,
|
||||
)
|
||||
|
||||
|
||||
def load_flow_from_json(path: str):
|
||||
"""Load flow from json file"""
|
||||
with open(path, "r") as f:
|
||||
flow_graph = json.load(f)
|
||||
data_graph = flow_graph["data"]
|
||||
extracted_json = extract_json(data_graph)
|
||||
return load_langchain_type_from_config(config=extracted_json)
|
||||
|
||||
|
||||
def extract_json(data_graph):
|
||||
nodes = data_graph["nodes"]
|
||||
# Substitute ZeroShotPrompt with PromptTemplate
|
||||
nodes = replace_zero_shot_prompt_with_prompt_template(nodes)
|
||||
# Add input variables
|
||||
nodes = payload.extract_input_variables(nodes)
|
||||
# Nodes, edges and root node
|
||||
edges = data_graph["edges"]
|
||||
root = payload.get_root_node(nodes, edges)
|
||||
return payload.build_json(root, nodes, edges)
|
||||
|
||||
|
||||
def replace_zero_shot_prompt_with_prompt_template(nodes):
|
||||
"""Replace ZeroShotPrompt with PromptTemplate"""
|
||||
for node in nodes:
|
||||
if node["data"]["type"] == "ZeroShotPrompt":
|
||||
# Build Prompt Template
|
||||
tools = [
|
||||
tool
|
||||
for tool in nodes
|
||||
if tool["type"] != "chatOutputNode"
|
||||
and "Tool" in tool["data"]["node"]["base_classes"]
|
||||
]
|
||||
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
||||
break
|
||||
return nodes
|
||||
|
||||
|
||||
def load_langchain_type_from_config(config: Dict[str, Any]):
|
||||
"""Load langchain type from config"""
|
||||
# Get type list
|
||||
type_list = get_type_list()
|
||||
if config["_type"] in type_list["agents"]:
|
||||
config = util.update_verbose(config, new_value=False)
|
||||
return load_agent_executor_from_config(config, verbose=True)
|
||||
elif config["_type"] in type_list["chains"]:
|
||||
config = util.update_verbose(config, new_value=False)
|
||||
return load_chain_from_config(config, verbose=True)
|
||||
elif config["_type"] in type_list["llms"]:
|
||||
config = util.update_verbose(config, new_value=True)
|
||||
return load_llm_from_config(config)
|
||||
else:
|
||||
raise ValueError("Type should be either agent, chain or llm")
|
||||
|
||||
|
||||
def load_agent_executor_from_config(
|
||||
config: dict,
|
||||
llm: Optional[BaseLLM] = None,
|
||||
tools: Optional[list[Tool]] = None,
|
||||
callback_manager: Optional[BaseCallbackManager] = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
tools = load_tools_from_config(config["allowed_tools"])
|
||||
config["allowed_tools"] = [tool.name for tool in tools] if tools else []
|
||||
agent_obj = load_agent_from_config(config, llm, tools, **kwargs)
|
||||
|
||||
return AgentExecutor.from_agent_and_tools(
|
||||
agent=agent_obj,
|
||||
tools=tools,
|
||||
callback_manager=callback_manager,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def load_tools_from_config(tool_list: list[dict]) -> list:
|
||||
"""Load tools based on a config list.
|
||||
|
||||
Args:
|
||||
config: config list.
|
||||
|
||||
Returns:
|
||||
List of tools.
|
||||
"""
|
||||
tools = []
|
||||
for tool in tool_list:
|
||||
tool_type = tool.pop("_type")
|
||||
llm_config = tool.pop("llm", None)
|
||||
llm = load_llm_from_config(llm_config) if llm_config else None
|
||||
kwargs = tool
|
||||
if tool_type in _BASE_TOOLS:
|
||||
tools.append(_BASE_TOOLS[tool_type]())
|
||||
elif tool_type in _LLM_TOOLS:
|
||||
if llm is None:
|
||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
||||
tools.append(_LLM_TOOLS[tool_type](llm))
|
||||
elif tool_type in _EXTRA_LLM_TOOLS:
|
||||
if llm is None:
|
||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
||||
_get_llm_tool_func, extra_keys = _EXTRA_LLM_TOOLS[tool_type]
|
||||
if missing_keys := set(extra_keys).difference(kwargs):
|
||||
raise ValueError(
|
||||
f"Tool {tool_type} requires some parameters that were not "
|
||||
f"provided: {missing_keys}"
|
||||
)
|
||||
tools.append(_get_llm_tool_func(llm=llm, **kwargs))
|
||||
elif tool_type in _EXTRA_OPTIONAL_TOOLS:
|
||||
_get_tool_func, extra_keys = _EXTRA_OPTIONAL_TOOLS[tool_type]
|
||||
kwargs = {k: value for k, value in kwargs.items() if value}
|
||||
tools.append(_get_tool_func(**kwargs))
|
||||
else:
|
||||
raise ValueError(f"Got unknown tool {tool_type}")
|
||||
return tools
|
||||
|
||||
|
||||
def build_prompt_template(prompt, tools):
|
||||
"""Build PromptTemplate from ZeroShotPrompt"""
|
||||
prefix = prompt["node"]["template"]["prefix"]["value"]
|
||||
suffix = prompt["node"]["template"]["suffix"]["value"]
|
||||
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
||||
|
||||
tool_strings = "\n".join(
|
||||
[
|
||||
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
|
||||
for tool in tools
|
||||
]
|
||||
)
|
||||
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
||||
format_instructions = format_instructions.format(tool_names=tool_names)
|
||||
value = "\n\n".join([prefix, tool_strings, format_instructions, suffix])
|
||||
|
||||
prompt["type"] = "PromptTemplate"
|
||||
|
||||
prompt["node"] = {
|
||||
"template": {
|
||||
"_type": "prompt",
|
||||
"input_variables": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"list": True,
|
||||
"show": False,
|
||||
"multiline": False,
|
||||
},
|
||||
"output_parser": {
|
||||
"type": "BaseOutputParser",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": False,
|
||||
"multline": False,
|
||||
"value": None,
|
||||
},
|
||||
"template": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": True,
|
||||
"multiline": True,
|
||||
"value": value,
|
||||
},
|
||||
"template_format": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": False,
|
||||
"multline": False,
|
||||
"value": "f-string",
|
||||
},
|
||||
"validate_template": {
|
||||
"type": "bool",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": False,
|
||||
"multline": False,
|
||||
"value": True,
|
||||
},
|
||||
},
|
||||
"description": "Schema to represent a prompt for an LLM.",
|
||||
"base_classes": ["BasePromptTemplate"],
|
||||
}
|
||||
|
||||
return prompt
|
||||
46
src/backend/langflow/interface/run.py
Normal file
46
src/backend/langflow/interface/run.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import contextlib
|
||||
import io
|
||||
import re
|
||||
from typing import Any, Dict
|
||||
from langflow.interface import loading
|
||||
|
||||
|
||||
def process_data_graph(data_graph: Dict[str, Any]):
|
||||
"""
|
||||
Process data graph by extracting input variables and replacing ZeroShotPrompt
|
||||
with PromptTemplate,then run the graph and return the result and thought.
|
||||
"""
|
||||
|
||||
extracted_json = loading.extract_json(data_graph)
|
||||
|
||||
message = data_graph["message"]
|
||||
|
||||
# Process json
|
||||
result, thought = get_result_and_thought(extracted_json, message)
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"thought": re.sub(
|
||||
r"\x1b\[([0-9,A-Z]{1,2}(;[0-9,A-Z]{1,2})?)?[m|K]", "", thought
|
||||
).strip(),
|
||||
}
|
||||
|
||||
|
||||
def get_result_and_thought(extracted_json: Dict[str, Any], message: str):
|
||||
"""Get result and thought from extracted json"""
|
||||
try:
|
||||
loaded_langchain = loading.load_langchain_type_from_config(
|
||||
config=extracted_json
|
||||
)
|
||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||
result = loaded_langchain(message)
|
||||
result = (
|
||||
result.get(loaded_langchain.output_keys[0])
|
||||
if isinstance(result, dict)
|
||||
else result
|
||||
)
|
||||
thought = output_buffer.getvalue()
|
||||
except Exception as e:
|
||||
result = f"Error: {str(e)}"
|
||||
thought = ""
|
||||
return result, thought
|
||||
119
src/backend/langflow/interface/signature.py
Normal file
119
src/backend/langflow/interface/signature.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
from typing import Dict, Any # noqa: F401
|
||||
from langchain import agents, chains, llms, prompts
|
||||
from langchain.agents.load_tools import (
|
||||
_BASE_TOOLS,
|
||||
_EXTRA_LLM_TOOLS,
|
||||
_EXTRA_OPTIONAL_TOOLS,
|
||||
_LLM_TOOLS,
|
||||
get_all_tool_names,
|
||||
)
|
||||
|
||||
from langflow.utils import util
|
||||
from langflow.custom import customs
|
||||
|
||||
|
||||
def get_signature(name: str, object_type: str):
|
||||
"""Get the signature of an object."""
|
||||
return {
|
||||
"chains": get_chain_signature,
|
||||
"agents": get_agent_signature,
|
||||
"prompts": get_prompt_signature,
|
||||
"llms": get_llm_signature,
|
||||
"tools": get_tool_signature,
|
||||
}.get(object_type, lambda name: f"Invalid type: {name}")(name)
|
||||
|
||||
|
||||
def get_chain_signature(name: str):
|
||||
"""Get the chain type by signature."""
|
||||
try:
|
||||
return util.build_template_from_function(
|
||||
name, chains.loading.type_to_loader_dict
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ValueError("Chain not found") from exc
|
||||
|
||||
|
||||
def get_agent_signature(name: str):
|
||||
"""Get the signature of an agent."""
|
||||
try:
|
||||
return util.build_template_from_class(name, agents.loading.AGENT_TO_CLASS)
|
||||
except ValueError as exc:
|
||||
raise ValueError("Agent not found") from exc
|
||||
|
||||
|
||||
def get_prompt_signature(name: str):
|
||||
"""Get the signature of a prompt."""
|
||||
try:
|
||||
if name in customs.get_custom_prompts().keys():
|
||||
return customs.get_custom_prompts()[name]
|
||||
return util.build_template_from_function(
|
||||
name, prompts.loading.type_to_loader_dict
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise ValueError("Prompt not found") from exc
|
||||
|
||||
|
||||
def get_llm_signature(name: str):
|
||||
"""Get the signature of an llm."""
|
||||
try:
|
||||
return util.build_template_from_class(name, llms.type_to_cls_dict)
|
||||
except ValueError as exc:
|
||||
raise ValueError("LLM not found") from exc
|
||||
|
||||
|
||||
def get_tool_signature(name: str):
|
||||
"""Get the signature of a tool."""
|
||||
|
||||
all_tools = {}
|
||||
for tool in get_all_tool_names():
|
||||
if tool_params := util.get_tool_params(util.get_tools_dict(tool)):
|
||||
all_tools[tool_params["name"]] = tool
|
||||
|
||||
# Raise error if name is not in tools
|
||||
if name not in all_tools.keys():
|
||||
raise ValueError("Tool not found")
|
||||
|
||||
type_dict = {
|
||||
"str": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
"list": False,
|
||||
"show": True,
|
||||
"placeholder": "",
|
||||
"value": "",
|
||||
},
|
||||
"llm": {"type": "BaseLLM", "required": True, "list": False, "show": True},
|
||||
}
|
||||
|
||||
tool_type = all_tools[name]
|
||||
|
||||
if tool_type in _BASE_TOOLS:
|
||||
params = []
|
||||
elif tool_type in _LLM_TOOLS:
|
||||
params = ["llm"]
|
||||
elif tool_type in _EXTRA_LLM_TOOLS:
|
||||
_, extra_keys = _EXTRA_LLM_TOOLS[tool_type]
|
||||
params = ["llm"] + extra_keys
|
||||
elif tool_type in _EXTRA_OPTIONAL_TOOLS:
|
||||
_, extra_keys = _EXTRA_OPTIONAL_TOOLS[tool_type]
|
||||
params = extra_keys
|
||||
else:
|
||||
params = []
|
||||
|
||||
template = {
|
||||
param: (type_dict[param].copy() if param == "llm" else type_dict["str"].copy())
|
||||
for param in params
|
||||
}
|
||||
|
||||
# Remove required from aiosession
|
||||
if "aiosession" in template.keys():
|
||||
template["aiosession"]["required"] = False
|
||||
template["aiosession"]["show"] = False
|
||||
|
||||
template["_type"] = tool_type # type: ignore
|
||||
|
||||
return {
|
||||
"template": template,
|
||||
**util.get_tool_params(util.get_tools_dict(tool_type)),
|
||||
"base_classes": ["Tool"],
|
||||
}
|
||||
31
src/backend/langflow/interface/types.py
Normal file
31
src/backend/langflow/interface/types.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
from langflow.interface.listing import list_type
|
||||
from langflow.interface.signature import get_signature
|
||||
|
||||
|
||||
def get_type_list():
|
||||
"""Get a list of all langchain types"""
|
||||
all_types = build_langchain_types_dict()
|
||||
|
||||
all_types.pop("tools")
|
||||
|
||||
for key, value in all_types.items():
|
||||
all_types[key] = [item["template"]["_type"] for item in value.values()]
|
||||
|
||||
return all_types
|
||||
|
||||
|
||||
def build_langchain_types_dict():
|
||||
"""Build a dictionary of all langchain types"""
|
||||
return {
|
||||
"chains": {
|
||||
chain: get_signature(chain, "chains") for chain in list_type("chains")
|
||||
},
|
||||
"agents": {
|
||||
agent: get_signature(agent, "agents") for agent in list_type("agents")
|
||||
},
|
||||
"prompts": {
|
||||
prompt: get_signature(prompt, "prompts") for prompt in list_type("prompts")
|
||||
},
|
||||
"llms": {llm: get_signature(llm, "llms") for llm in list_type("llms")},
|
||||
"tools": {tool: get_signature(tool, "tools") for tool in list_type("tools")},
|
||||
}
|
||||
36
src/backend/langflow/main.py
Normal file
36
src/backend/langflow/main.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
from fastapi import FastAPI
|
||||
from langflow.api.endpoints import router as endpoints_router
|
||||
from langflow.api.list_endpoints import router as list_router
|
||||
from langflow.api.signature import router as signatures_router
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create the FastAPI app and include the router."""
|
||||
app = FastAPI()
|
||||
|
||||
origins = [
|
||||
"*",
|
||||
]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(endpoints_router)
|
||||
app.include_router(list_router)
|
||||
app.include_router(signatures_router)
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="127.0.0.1", port=5003)
|
||||
20
src/backend/langflow/server.py
Normal file
20
src/backend/langflow/server.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from gunicorn.app.base import BaseApplication # type: ignore
|
||||
|
||||
|
||||
class LangflowApplication(BaseApplication):
|
||||
def __init__(self, app, options=None):
|
||||
self.options = options or {}
|
||||
self.application = app
|
||||
super().__init__()
|
||||
|
||||
def load_config(self):
|
||||
config = {
|
||||
key: value
|
||||
for key, value in self.options.items()
|
||||
if key in self.cfg.settings and value is not None
|
||||
}
|
||||
for key, value in config.items():
|
||||
self.cfg.set(key.lower(), value)
|
||||
|
||||
def load(self):
|
||||
return self.application
|
||||
0
src/backend/langflow/utils/__init__.py
Normal file
0
src/backend/langflow/utils/__init__.py
Normal file
9
src/backend/langflow/utils/allowed_components.py
Normal file
9
src/backend/langflow/utils/allowed_components.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
CHAINS = ["LLMChain", "LLMMathChain", "LLMChecker"]
|
||||
|
||||
AGENTS = ["ZeroShotAgent"]
|
||||
|
||||
PROMPTS = ["PromptTemplate", "FewShotPromptTemplate"]
|
||||
|
||||
LLMS = ["OpenAI"]
|
||||
|
||||
TOOLS = ["Search", "PAL-MATH", "Calculator", "Serper Search"]
|
||||
74
src/backend/langflow/utils/payload.py
Normal file
74
src/backend/langflow/utils/payload.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import contextlib
|
||||
import re
|
||||
|
||||
|
||||
def extract_input_variables(nodes):
|
||||
"""
|
||||
Extracts input variables from the template
|
||||
and adds them to the input_variables field.
|
||||
"""
|
||||
for node in nodes:
|
||||
with contextlib.suppress(Exception):
|
||||
if "input_variables" in node["data"]["node"]["template"]:
|
||||
if node["data"]["node"]["template"]["_type"] == "prompt":
|
||||
variables = re.findall(
|
||||
r"\{(.*?)\}",
|
||||
node["data"]["node"]["template"]["template"]["value"],
|
||||
)
|
||||
elif node["data"]["node"]["template"]["_type"] == "few_shot":
|
||||
variables = re.findall(
|
||||
r"\{(.*?)\}",
|
||||
node["data"]["node"]["template"]["prefix"]["value"]
|
||||
+ node["data"]["node"]["template"]["suffix"]["value"],
|
||||
)
|
||||
else:
|
||||
variables = []
|
||||
node["data"]["node"]["template"]["input_variables"]["value"] = variables
|
||||
return nodes
|
||||
|
||||
|
||||
def get_root_node(nodes, edges):
|
||||
"""
|
||||
Returns the root node of the template.
|
||||
"""
|
||||
incoming_edges = {edge["source"] for edge in edges}
|
||||
return next((node for node in nodes if node["id"] not in incoming_edges), None)
|
||||
|
||||
|
||||
def build_json(root, nodes, edges):
|
||||
"""
|
||||
Builds a json from the nodes and edges
|
||||
"""
|
||||
edge_ids = [edge["source"] for edge in edges if edge["target"] == root["id"]]
|
||||
local_nodes = [node for node in nodes if node["id"] in edge_ids]
|
||||
|
||||
if "node" not in root["data"]:
|
||||
return build_json(local_nodes[0], nodes, edges)
|
||||
|
||||
final_dict = root["data"]["node"]["template"].copy()
|
||||
|
||||
for key, value in final_dict.items():
|
||||
if key == "_type":
|
||||
continue
|
||||
|
||||
module_type = value["type"]
|
||||
|
||||
if "value" in value and value["value"] is not None:
|
||||
value = value["value"]
|
||||
elif "dict" in module_type:
|
||||
value = {}
|
||||
else:
|
||||
children = []
|
||||
for c in local_nodes:
|
||||
module_types = [c["data"]["type"]]
|
||||
if "node" in c["data"]:
|
||||
module_types += c["data"]["node"]["base_classes"]
|
||||
if module_type in module_types:
|
||||
children.append(c)
|
||||
|
||||
if value["required"] and not children:
|
||||
raise ValueError(f"No child with type {module_type} found")
|
||||
values = [build_json(child, nodes, edges) for child in children]
|
||||
value = list(values) if value["list"] else next(iter(values), None)
|
||||
final_dict[key] = value
|
||||
return final_dict
|
||||
321
src/backend/langflow/utils/util.py
Normal file
321
src/backend/langflow/utils/util.py
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
import ast
|
||||
import inspect
|
||||
import re
|
||||
import importlib
|
||||
|
||||
from langchain.agents.load_tools import (
|
||||
_BASE_TOOLS,
|
||||
_LLM_TOOLS,
|
||||
_EXTRA_LLM_TOOLS,
|
||||
_EXTRA_OPTIONAL_TOOLS,
|
||||
)
|
||||
from typing import Optional, Dict
|
||||
|
||||
|
||||
def build_template_from_function(name: str, type_to_loader_dict: Dict):
|
||||
classes = [
|
||||
item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()
|
||||
]
|
||||
|
||||
# Raise error if name is not in chains
|
||||
if name not in classes:
|
||||
raise ValueError(f"{name} not found")
|
||||
|
||||
for _type, v in type_to_loader_dict.items():
|
||||
if v.__annotations__["return"].__name__ == name:
|
||||
_class = v.__annotations__["return"]
|
||||
|
||||
docs = get_class_doc(_class)
|
||||
|
||||
variables = {"_type": _type}
|
||||
for class_field_items, value in _class.__fields__.items():
|
||||
if class_field_items in ["callback_manager", "requests_wrapper"]:
|
||||
continue
|
||||
variables[class_field_items] = {}
|
||||
for name_, value_ in value.__repr_args__():
|
||||
if name_ == "default_factory":
|
||||
try:
|
||||
variables[class_field_items][
|
||||
"default"
|
||||
] = get_default_factory(
|
||||
module=_class.__base__.__module__, function=value_
|
||||
)
|
||||
except Exception:
|
||||
variables[class_field_items]["default"] = None
|
||||
elif name_ not in ["name"]:
|
||||
variables[class_field_items][name_] = value_
|
||||
|
||||
variables[class_field_items]["placeholder"] = (
|
||||
docs["Attributes"][class_field_items]
|
||||
if class_field_items in docs["Attributes"]
|
||||
else ""
|
||||
)
|
||||
|
||||
return {
|
||||
"template": format_dict(variables, name),
|
||||
"description": docs["Description"],
|
||||
"base_classes": get_base_classes(_class),
|
||||
}
|
||||
|
||||
|
||||
def build_template_from_class(name: str, type_to_cls_dict: Dict):
|
||||
classes = [item.__name__ for item in type_to_cls_dict.values()]
|
||||
|
||||
# Raise error if name is not in chains
|
||||
if name not in classes:
|
||||
raise ValueError(f"{name} not found.")
|
||||
|
||||
for _type, v in type_to_cls_dict.items():
|
||||
if v.__name__ == name:
|
||||
_class = v
|
||||
|
||||
docs = get_class_doc(_class)
|
||||
|
||||
variables = {"_type": _type}
|
||||
for class_field_items, value in _class.__fields__.items():
|
||||
if class_field_items in ["callback_manager"]:
|
||||
continue
|
||||
variables[class_field_items] = {}
|
||||
for name_, value_ in value.__repr_args__():
|
||||
if name_ == "default_factory":
|
||||
try:
|
||||
variables[class_field_items][
|
||||
"default"
|
||||
] = get_default_factory(
|
||||
module=_class.__base__.__module__, function=value_
|
||||
)
|
||||
except Exception:
|
||||
variables[class_field_items]["default"] = None
|
||||
elif name_ not in ["name"]:
|
||||
variables[class_field_items][name_] = value_
|
||||
|
||||
variables[class_field_items]["placeholder"] = (
|
||||
docs["Attributes"][class_field_items]
|
||||
if class_field_items in docs["Attributes"]
|
||||
else ""
|
||||
)
|
||||
|
||||
return {
|
||||
"template": format_dict(variables, name),
|
||||
"description": docs["Description"],
|
||||
"base_classes": get_base_classes(_class),
|
||||
}
|
||||
|
||||
|
||||
def get_base_classes(cls):
|
||||
bases = cls.__bases__
|
||||
if not bases:
|
||||
return []
|
||||
else:
|
||||
result = []
|
||||
for base in bases:
|
||||
if any(type in base.__module__ for type in ["pydantic", "abc"]):
|
||||
continue
|
||||
result.append(base.__name__)
|
||||
result.extend(get_base_classes(base))
|
||||
return result
|
||||
|
||||
|
||||
def get_default_factory(module: str, function: str):
|
||||
pattern = r"<function (\w+)>"
|
||||
|
||||
if match := re.search(pattern, function):
|
||||
imported_module = importlib.import_module(module)
|
||||
return getattr(imported_module, match[1])()
|
||||
return None
|
||||
|
||||
|
||||
def get_tools_dict(name: Optional[str] = None):
|
||||
"""Get the tools dictionary."""
|
||||
tools = {
|
||||
**_BASE_TOOLS,
|
||||
**_LLM_TOOLS, # type: ignore
|
||||
**{k: v[0] for k, v in _EXTRA_LLM_TOOLS.items()}, # type: ignore
|
||||
**{k: v[0] for k, v in _EXTRA_OPTIONAL_TOOLS.items()},
|
||||
}
|
||||
return tools[name] if name else tools
|
||||
|
||||
|
||||
def get_tool_params(func, **kwargs):
|
||||
# Parse the function code into an abstract syntax tree
|
||||
tree = ast.parse(inspect.getsource(func))
|
||||
|
||||
# Iterate over the statements in the abstract syntax tree
|
||||
for node in ast.walk(tree):
|
||||
# Find the first return statement
|
||||
if isinstance(node, ast.Return):
|
||||
tool = node.value
|
||||
if isinstance(tool, ast.Call):
|
||||
if tool.func.id == "Tool":
|
||||
if tool.keywords:
|
||||
tool_params = {}
|
||||
for keyword in tool.keywords:
|
||||
if keyword.arg == "name":
|
||||
tool_params["name"] = ast.literal_eval(keyword.value)
|
||||
elif keyword.arg == "description":
|
||||
tool_params["description"] = ast.literal_eval(
|
||||
keyword.value
|
||||
)
|
||||
return tool_params
|
||||
return {
|
||||
"name": ast.literal_eval(tool.args[0]),
|
||||
"description": ast.literal_eval(tool.args[2]),
|
||||
}
|
||||
else:
|
||||
# get the class object from the return statement
|
||||
try:
|
||||
class_obj = eval(
|
||||
compile(ast.Expression(tool), "<string>", "eval")
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
return {
|
||||
"name": getattr(class_obj, "name"),
|
||||
"description": getattr(class_obj, "description"),
|
||||
}
|
||||
|
||||
# Return None if no return statement was found
|
||||
return None
|
||||
|
||||
|
||||
def get_class_doc(class_name):
|
||||
"""
|
||||
Extracts information from the docstring of a given class.
|
||||
|
||||
Args:
|
||||
class_name: the class to extract information from
|
||||
|
||||
Returns:
|
||||
A dictionary containing the extracted information, with keys
|
||||
for 'Description', 'Parameters', 'Attributes', and 'Returns'.
|
||||
"""
|
||||
# Get the class docstring
|
||||
docstring = class_name.__doc__
|
||||
|
||||
# Parse the docstring to extract information
|
||||
lines = docstring.split("\n")
|
||||
data = {
|
||||
"Description": "",
|
||||
"Parameters": {},
|
||||
"Attributes": {},
|
||||
"Example": [],
|
||||
"Returns": {},
|
||||
}
|
||||
|
||||
current_section = "Description"
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if (
|
||||
line.startswith(tuple(data.keys()))
|
||||
and len(line.split()) == 1
|
||||
and line.endswith(":")
|
||||
):
|
||||
current_section = line[:-1]
|
||||
continue
|
||||
|
||||
if current_section in ["Description", "Example"]:
|
||||
data[current_section] += line
|
||||
else:
|
||||
param, desc = line.split(":")
|
||||
data[current_section][param.strip()] = desc.strip()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def format_dict(d, name: Optional[str] = None):
|
||||
"""
|
||||
Formats a dictionary by removing certain keys and modifying the
|
||||
values of other keys.
|
||||
|
||||
Args:
|
||||
d: the dictionary to format
|
||||
name: the name of the class to format
|
||||
|
||||
Returns:
|
||||
A new dictionary with the desired modifications applied.
|
||||
"""
|
||||
|
||||
# Process remaining keys
|
||||
for key, value in d.items():
|
||||
if key == "_type":
|
||||
continue
|
||||
|
||||
_type = value["type"]
|
||||
|
||||
# Remove 'Optional' wrapper
|
||||
if "Optional" in _type:
|
||||
_type = _type.replace("Optional[", "")[:-1]
|
||||
|
||||
# Check for list type
|
||||
if "List" in _type:
|
||||
_type = _type.replace("List[", "")[:-1]
|
||||
value["list"] = True
|
||||
else:
|
||||
value["list"] = False
|
||||
|
||||
# Replace 'Mapping' with 'dict'
|
||||
if "Mapping" in _type:
|
||||
_type = _type.replace("Mapping", "dict")
|
||||
|
||||
# Change type from str to Tool
|
||||
value["type"] = "Tool" if key == "allowed_tools" else _type
|
||||
|
||||
# Show or not field
|
||||
value["show"] = bool(
|
||||
(value["required"] and key not in ["input_variables"])
|
||||
or key
|
||||
in [
|
||||
"allowed_tools",
|
||||
"memory",
|
||||
"prefix",
|
||||
"examples",
|
||||
"temperature",
|
||||
# "model_name",
|
||||
]
|
||||
or "api_key" in key
|
||||
)
|
||||
|
||||
# Add password field
|
||||
value["password"] = any(
|
||||
text in key for text in ["password", "token", "api", "key"]
|
||||
)
|
||||
|
||||
# Add multline
|
||||
value["multiline"] = key in ["suffix", "prefix", "template", "examples"]
|
||||
|
||||
# Replace default value with actual value
|
||||
if "default" in value:
|
||||
value["value"] = value["default"]
|
||||
value.pop("default")
|
||||
|
||||
# Add options to openai
|
||||
if name == "OpenAI" and key == "model_name":
|
||||
value["options"] = ["text-davinci-003", "text-davinci-002"]
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def update_verbose(d: dict, new_value: bool) -> dict:
|
||||
"""
|
||||
Recursively updates the value of the 'verbose' key in a dictionary.
|
||||
|
||||
Args:
|
||||
d: the dictionary to update
|
||||
new_value: the new value to set
|
||||
|
||||
Returns:
|
||||
The updated dictionary.
|
||||
"""
|
||||
|
||||
for k, v in d.items():
|
||||
if isinstance(v, dict):
|
||||
update_verbose(v, new_value)
|
||||
elif k == "verbose":
|
||||
d[k] = new_value
|
||||
return d
|
||||
8
src/backend/run
Executable file
8
src/backend/run
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
#! /bin/bash
|
||||
|
||||
poetry remove langchain
|
||||
docker build -t logspace/backend_build -f build.Dockerfile .
|
||||
VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version)
|
||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION .
|
||||
docker run -p 5003:80 -d ibiscp/langflow:$VERSION
|
||||
poetry add --editable ../../../langchain
|
||||
Loading…
Add table
Add a link
Reference in a new issue