Merge branch 'dev' into vecstores
This commit is contained in:
commit
733f88aa69
36 changed files with 712 additions and 243 deletions
9
Makefile
9
Makefile
|
|
@ -42,14 +42,13 @@ build:
|
||||||
dev:
|
dev:
|
||||||
make install_frontend
|
make install_frontend
|
||||||
ifeq ($(build),1)
|
ifeq ($(build),1)
|
||||||
@echo 'Running docker compose up with build'
|
@echo 'Running docker compose up with build'
|
||||||
docker compose up --build
|
docker compose $(if $(debug),-f docker-compose.debug.yml) up --build
|
||||||
else
|
else
|
||||||
@echo 'Running docker compose up without build'
|
@echo 'Running docker compose up without build'
|
||||||
docker compose up
|
docker compose $(if $(debug),-f docker-compose.debug.yml) up
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
make build
|
make build
|
||||||
poetry publish
|
poetry publish
|
||||||
|
|
|
||||||
28
docker-compose.debug.yml
Normal file
28
docker-compose.debug.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
volumes:
|
||||||
|
- ./:/app
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: ./dev.Dockerfile
|
||||||
|
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"]
|
||||||
|
ports:
|
||||||
|
- 7860:7860
|
||||||
|
- 5678:5678
|
||||||
|
restart: on-failure
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./src/frontend
|
||||||
|
dockerfile: ./dev.Dockerfile
|
||||||
|
args:
|
||||||
|
- BACKEND_URL=http://backend:7860
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
- ./src/frontend/public:/home/node/app/public
|
||||||
|
- ./src/frontend/src:/home/node/app/src
|
||||||
|
- ./src/frontend/package.json:/home/node/app/package.json
|
||||||
|
restart: on-failure
|
||||||
19
poetry.lock
generated
19
poetry.lock
generated
|
|
@ -1199,14 +1199,14 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain"
|
name = "langchain"
|
||||||
version = "0.0.127"
|
version = "0.0.131"
|
||||||
description = "Building applications with LLMs through composability"
|
description = "Building applications with LLMs through composability"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.1,<4.0"
|
python-versions = ">=3.8.1,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "langchain-0.0.127-py3-none-any.whl", hash = "sha256:04ba053881e6098e80e0f4afc8922f3fe78923b160fd12d856aebce49c261918"},
|
{file = "langchain-0.0.131-py3-none-any.whl", hash = "sha256:3564a759e85095c9d71a78817da9cec1e2a8a0cda1bdd94ef8ac7008e432717a"},
|
||||||
{file = "langchain-0.0.127.tar.gz", hash = "sha256:e8a3b67fd86a6f79c4334f0a7588c9476fcb57b27a8fb0e617f47c01eaab8be8"},
|
{file = "langchain-0.0.131.tar.gz", hash = "sha256:61baf67fbec561ce38d187915a46e1c41139270826453600951760fde1a5d98a"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -1220,8 +1220,11 @@ SQLAlchemy = ">=1,<2"
|
||||||
tenacity = ">=8.1.0,<9.0.0"
|
tenacity = ">=8.1.0,<9.0.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.2.4,<0.3.0)", "beautifulsoup4 (>=4,<5)", "boto3 (>=1.26.96,<2.0.0)", "cohere (>=3,<4)", "deeplake (>=3.2.9,<4.0.0)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-search-results (>=2,<3)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "manifest-ml (>=0.0.1,<0.0.2)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "opensearch-py (>=2.0.0,<3.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "qdrant-client (>=1.0.4,<2.0.0)", "redis (>=4,<5)", "sentence-transformers (>=2,<3)", "spacy (>=3,<4)", "tensorflow-text (>=2.11.0,<3.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<2)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"]
|
all = ["aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.2.4,<0.3.0)", "beautifulsoup4 (>=4,<5)", "boto3 (>=1.26.96,<2.0.0)", "cohere (>=3,<4)", "deeplake (>=3.2.9,<4.0.0)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-search-results (>=2,<3)", "huggingface_hub (>=0,<1)", "jina (>=3.14,<4.0)", "jinja2 (>=3,<4)", "manifest-ml (>=0.0.1,<0.0.2)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "opensearch-py (>=2.0.0,<3.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "qdrant-client (>=1.1.1,<2.0.0)", "redis (>=4,<5)", "sentence-transformers (>=2,<3)", "spacy (>=3,<4)", "tensorflow-text (>=2.11.0,<3.0.0)", "tiktoken (>=0.3.2,<0.4.0)", "torch (>=1,<2)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"]
|
||||||
|
cohere = ["cohere (>=3,<4)"]
|
||||||
llms = ["anthropic (>=0.2.4,<0.3.0)", "cohere (>=3,<4)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "torch (>=1,<2)", "transformers (>=4,<5)"]
|
llms = ["anthropic (>=0.2.4,<0.3.0)", "cohere (>=3,<4)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "torch (>=1,<2)", "transformers (>=4,<5)"]
|
||||||
|
openai = ["openai (>=0,<1)"]
|
||||||
|
qdrant = ["qdrant-client (>=1.1.1,<2.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown-it-py"
|
name = "markdown-it-py"
|
||||||
|
|
@ -1506,14 +1509,14 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openai"
|
name = "openai"
|
||||||
version = "0.27.2"
|
version = "0.27.4"
|
||||||
description = "Python client library for the OpenAI API"
|
description = "Python client library for the OpenAI API"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7.1"
|
python-versions = ">=3.7.1"
|
||||||
files = [
|
files = [
|
||||||
{file = "openai-0.27.2-py3-none-any.whl", hash = "sha256:6df674cf257e9e0504f1fd191c333d3f6a2442b13218d0eccf06230eb24d320e"},
|
{file = "openai-0.27.4-py3-none-any.whl", hash = "sha256:3b82c867d531e1fd2003d9de2131e1c4bfd4c70b1a3149e0543a555b30807b70"},
|
||||||
{file = "openai-0.27.2.tar.gz", hash = "sha256:5869fdfa34b0ec66c39afa22f4a0fb83a135dff81f6505f52834c6ab3113f762"},
|
{file = "openai-0.27.4.tar.gz", hash = "sha256:9f9d27d26e62c6068f516c0729449954b5ef6994be1a6cbfe7dbefbc84423a04"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -2712,4 +2715,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "2b523f3d737ef8f7082e8156f096bce6f4f84a8bee9d07bd4ed23a29d3dcfab1"
|
content-hash = "91c68c5a5673f7b2bd0833af35da1262afd21d631cc62ec6ff9c65f69a96af0a"
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ google-search-results = "^2.4.1"
|
||||||
google-api-python-client = "^2.79.0"
|
google-api-python-client = "^2.79.0"
|
||||||
typer = "^0.7.0"
|
typer = "^0.7.0"
|
||||||
gunicorn = "^20.1.0"
|
gunicorn = "^20.1.0"
|
||||||
langchain = "^0.0.127"
|
langchain = "^0.0.131"
|
||||||
openai = "^0.27.2"
|
openai = "^0.27.2"
|
||||||
types-pyyaml = "^6.0.12.8"
|
types-pyyaml = "^6.0.12.8"
|
||||||
dill = "^0.3.6"
|
dill = "^0.3.6"
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ from pydantic import BaseModel, validator
|
||||||
class Code(BaseModel):
|
class Code(BaseModel):
|
||||||
code: str
|
code: str
|
||||||
|
|
||||||
@validator("code")
|
|
||||||
def validate_code(cls, v):
|
class Prompt(BaseModel):
|
||||||
return v
|
template: str
|
||||||
|
|
||||||
|
|
||||||
# Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}}
|
# Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}}
|
||||||
class ValidationResponse(BaseModel):
|
class CodeValidationResponse(BaseModel):
|
||||||
imports: dict
|
imports: dict
|
||||||
function: dict
|
function: dict
|
||||||
|
|
||||||
|
|
@ -21,3 +21,7 @@ class ValidationResponse(BaseModel):
|
||||||
@validator("function")
|
@validator("function")
|
||||||
def validate_function(cls, v):
|
def validate_function(cls, v):
|
||||||
return v or {"errors": []}
|
return v or {"errors": []}
|
||||||
|
|
||||||
|
|
||||||
|
class PromptValidationResponse(BaseModel):
|
||||||
|
input_variables: list
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,8 @@ from typing import Any, Dict
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
from langflow.api.base import Code, ValidationResponse
|
|
||||||
from langflow.interface.run import process_graph
|
from langflow.interface.run import process_graph
|
||||||
from langflow.interface.types import build_langchain_types_dict
|
from langflow.interface.types import build_langchain_types_dict
|
||||||
from langflow.utils.validate import validate_code
|
|
||||||
|
|
||||||
# build router
|
# build router
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
@ -26,15 +24,3 @@ def get_load(data: Dict[str, Any]):
|
||||||
# Log stack trace
|
# Log stack trace
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
@router.post("/validate", status_code=200, response_model=ValidationResponse)
|
|
||||||
def post_validate_code(code: Code):
|
|
||||||
try:
|
|
||||||
errors = validate_code(code.code)
|
|
||||||
return ValidationResponse(
|
|
||||||
imports=errors.get("imports", {}),
|
|
||||||
function=errors.get("function", {}),
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
return HTTPException(status_code=500, detail=str(e))
|
|
||||||
|
|
|
||||||
36
src/backend/langflow/api/validate.py
Normal file
36
src/backend/langflow/api/validate.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
|
from langflow.api.base import (
|
||||||
|
Code,
|
||||||
|
CodeValidationResponse,
|
||||||
|
Prompt,
|
||||||
|
PromptValidationResponse,
|
||||||
|
)
|
||||||
|
from langflow.graph.utils import extract_input_variables_from_prompt
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
from langflow.utils.validate import validate_code
|
||||||
|
|
||||||
|
# build router
|
||||||
|
router = APIRouter(prefix="/validate", tags=["validate"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/code", status_code=200, response_model=CodeValidationResponse)
|
||||||
|
def post_validate_code(code: Code):
|
||||||
|
try:
|
||||||
|
errors = validate_code(code.code)
|
||||||
|
return CodeValidationResponse(
|
||||||
|
imports=errors.get("imports", {}),
|
||||||
|
function=errors.get("function", {}),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/prompt", status_code=200, response_model=PromptValidationResponse)
|
||||||
|
def post_validate_prompt(prompt: Prompt):
|
||||||
|
try:
|
||||||
|
input_variables = extract_input_variables_from_prompt(prompt.template)
|
||||||
|
return PromptValidationResponse(input_variables=input_variables)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
return HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
@ -3,6 +3,9 @@ chains:
|
||||||
- LLMMathChain
|
- LLMMathChain
|
||||||
- LLMCheckerChain
|
- LLMCheckerChain
|
||||||
- ConversationChain
|
- ConversationChain
|
||||||
|
- SeriesCharacterChain
|
||||||
|
- MidJourneyPromptChain
|
||||||
|
- TimeTravelGuideChain
|
||||||
|
|
||||||
agents:
|
agents:
|
||||||
- ZeroShotAgent
|
- ZeroShotAgent
|
||||||
|
|
@ -13,9 +16,15 @@ agents:
|
||||||
prompts:
|
prompts:
|
||||||
- PromptTemplate
|
- PromptTemplate
|
||||||
- FewShotPromptTemplate
|
- FewShotPromptTemplate
|
||||||
|
- ZeroShotPrompt
|
||||||
|
# Wait more tests
|
||||||
|
# - ChatPromptTemplate
|
||||||
|
# - SystemMessagePromptTemplate
|
||||||
|
# - HumanMessagePromptTemplate
|
||||||
|
|
||||||
llms:
|
llms:
|
||||||
- OpenAI
|
- OpenAI
|
||||||
|
- AzureOpenAI
|
||||||
- ChatOpenAI
|
- ChatOpenAI
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
|
|
@ -36,6 +45,8 @@ toolkits:
|
||||||
|
|
||||||
memories:
|
memories:
|
||||||
- ConversationBufferMemory
|
- ConversationBufferMemory
|
||||||
|
- ConversationSummaryMemory
|
||||||
|
- ConversationKGMemory
|
||||||
|
|
||||||
embeddings:
|
embeddings:
|
||||||
- OpenAIEmbeddings
|
- OpenAIEmbeddings
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from langflow.template import nodes
|
from langflow.template import nodes
|
||||||
|
|
||||||
|
# These should always be instantiated
|
||||||
CUSTOM_NODES = {
|
CUSTOM_NODES = {
|
||||||
"prompts": {"ZeroShotPrompt": nodes.ZeroShotPromptNode()},
|
"prompts": {"ZeroShotPrompt": nodes.ZeroShotPromptNode()},
|
||||||
"tools": {"PythonFunction": nodes.PythonFunctionNode(), "Tool": nodes.ToolNode()},
|
"tools": {"PythonFunction": nodes.PythonFunctionNode(), "Tool": nodes.ToolNode()},
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from langflow.graph.nodes import (
|
||||||
ChainNode,
|
ChainNode,
|
||||||
FileToolNode,
|
FileToolNode,
|
||||||
LLMNode,
|
LLMNode,
|
||||||
|
MemoryNode,
|
||||||
PromptNode,
|
PromptNode,
|
||||||
ToolkitNode,
|
ToolkitNode,
|
||||||
ToolNode,
|
ToolNode,
|
||||||
|
|
@ -26,6 +27,7 @@ from langflow.interface.wrappers.base import wrapper_creator
|
||||||
from langflow.interface.embeddings.base import embedding_creator
|
from langflow.interface.embeddings.base import embedding_creator
|
||||||
from langflow.interface.vectorStore.base import vectorstore_creator
|
from langflow.interface.vectorStore.base import vectorstore_creator
|
||||||
from langflow.interface.documentLoaders.base import documentloader_creator
|
from langflow.interface.documentLoaders.base import documentloader_creator
|
||||||
|
from langflow.interface.memories.base import memory_creator
|
||||||
from langflow.utils import payload
|
from langflow.utils import payload
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,6 +60,12 @@ class Graph:
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
if isinstance(node, ToolkitNode):
|
if isinstance(node, ToolkitNode):
|
||||||
node.params["llm"] = llm_node
|
node.params["llm"] = llm_node
|
||||||
|
# remove invalid nodes
|
||||||
|
self.nodes = [node for node in self.nodes if self._validate_node(node)]
|
||||||
|
|
||||||
|
def _validate_node(self, node: Node) -> bool:
|
||||||
|
# All nodes that do not have edges are invalid
|
||||||
|
return len(node.edges) > 0
|
||||||
|
|
||||||
def get_node(self, node_id: str) -> Union[None, Node]:
|
def get_node(self, node_id: str) -> Union[None, Node]:
|
||||||
return next((node for node in self.nodes if node.id == node_id), None)
|
return next((node for node in self.nodes if node.id == node_id), None)
|
||||||
|
|
@ -71,6 +79,8 @@ class Graph:
|
||||||
def build(self) -> List[Node]:
|
def build(self) -> List[Node]:
|
||||||
# Get root node
|
# Get root node
|
||||||
root_node = payload.get_root_node(self)
|
root_node = payload.get_root_node(self)
|
||||||
|
if root_node is None:
|
||||||
|
raise ValueError("No root node found")
|
||||||
return root_node.build()
|
return root_node.build()
|
||||||
|
|
||||||
def get_node_neighbors(self, node: Node) -> Dict[Node, int]:
|
def get_node_neighbors(self, node: Node) -> Dict[Node, int]:
|
||||||
|
|
@ -142,6 +152,11 @@ class Graph:
|
||||||
nodes.append(VectorStoreNode(node))
|
nodes.append(VectorStoreNode(node))
|
||||||
elif node_type in documentloader_creator.to_list():
|
elif node_type in documentloader_creator.to_list():
|
||||||
nodes.append(DocumentLoaderNode(node))
|
nodes.append(DocumentLoaderNode(node))
|
||||||
|
elif (
|
||||||
|
node_type in memory_creator.to_list()
|
||||||
|
or node_lc_type in memory_creator.to_list()
|
||||||
|
):
|
||||||
|
nodes.append(MemoryNode(node))
|
||||||
else:
|
else:
|
||||||
nodes.append(Node(node))
|
nodes.append(Node(node))
|
||||||
return nodes
|
return nodes
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,9 @@ class PromptNode(Node):
|
||||||
for param in prompt_params:
|
for param in prompt_params:
|
||||||
prompt_text = self.params[param]
|
prompt_text = self.params[param]
|
||||||
variables = extract_input_variables_from_prompt(prompt_text)
|
variables = extract_input_variables_from_prompt(prompt_text)
|
||||||
|
|
||||||
self.params["input_variables"].extend(variables)
|
self.params["input_variables"].extend(variables)
|
||||||
|
self.params["input_variables"] = list(set(self.params["input_variables"]))
|
||||||
|
|
||||||
self._build()
|
self._build()
|
||||||
return deepcopy(self._built_object)
|
return deepcopy(self._built_object)
|
||||||
|
|
@ -148,27 +150,19 @@ class DocumentLoaderNode(Node):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="documentloaders")
|
super().__init__(data, base_type="documentloaders")
|
||||||
|
|
||||||
def build(self, force: bool = False) -> Any:
|
|
||||||
if not self._built or force:
|
|
||||||
self._build()
|
|
||||||
return deepcopy(self._built_object)
|
|
||||||
|
|
||||||
|
|
||||||
class EmbeddingNode(Node):
|
class EmbeddingNode(Node):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="embeddings")
|
super().__init__(data, base_type="embeddings")
|
||||||
|
|
||||||
def build(self, force: bool = False) -> Any:
|
|
||||||
if not self._built or force:
|
|
||||||
self._build()
|
|
||||||
return deepcopy(self._built_object)
|
|
||||||
|
|
||||||
|
|
||||||
class VectorStoreNode(Node):
|
class VectorStoreNode(Node):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="vectorstores")
|
super().__init__(data, base_type="vectorstores")
|
||||||
|
|
||||||
def build(self, force: bool = False) -> Any:
|
|
||||||
if not self._built or force:
|
class MemoryNode(Node):
|
||||||
self._build()
|
def __init__(self, data: Dict):
|
||||||
return deepcopy(self._built_object)
|
super().__init__(data, base_type="memory")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,14 @@ def load_file(file_name, file_content, accepted_types) -> Any:
|
||||||
return json.loads(decoded_string)
|
return json.loads(decoded_string)
|
||||||
elif suffix in ["yaml", "yml"]:
|
elif suffix in ["yaml", "yml"]:
|
||||||
# Return the yaml content
|
# Return the yaml content
|
||||||
return yaml.safe_load(decoded_string)
|
loaded_yaml = yaml.load(decoded_string, Loader=yaml.FullLoader)
|
||||||
|
try:
|
||||||
|
from langchain.agents.agent_toolkits.openapi.spec import reduce_openapi_spec # type: ignore
|
||||||
|
|
||||||
|
return reduce_openapi_spec(loaded_yaml)
|
||||||
|
except ImportError:
|
||||||
|
return loaded_yaml
|
||||||
|
|
||||||
elif suffix == "csv":
|
elif suffix == "csv":
|
||||||
# Load the csv content
|
# Load the csv content
|
||||||
csv_reader = csv.DictReader(io.StringIO(decoded_string))
|
csv_reader = csv.DictReader(io.StringIO(decoded_string))
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ class AgentCreator(LangChainTypeCreator):
|
||||||
self.type_dict = loading.AGENT_TO_CLASS
|
self.type_dict = loading.AGENT_TO_CLASS
|
||||||
# Add JsonAgent to the list of agents
|
# Add JsonAgent to the list of agents
|
||||||
for name, agent in CUSTOM_AGENTS.items():
|
for name, agent in CUSTOM_AGENTS.items():
|
||||||
self.type_dict[name] = agent
|
# TODO: validate AgentType
|
||||||
|
self.type_dict[name] = agent # type: ignore
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
def get_signature(self, name: str) -> Optional[Dict]:
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
|
|
@ -34,7 +35,7 @@ class AgentCreator(LangChainTypeCreator):
|
||||||
# Now this is a generator
|
# Now this is a generator
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
names = []
|
names = []
|
||||||
for name, agent in self.type_to_loader_dict.items():
|
for _, agent in self.type_to_loader_dict.items():
|
||||||
agent_name = (
|
agent_name = (
|
||||||
agent.function_name()
|
agent.function_name()
|
||||||
if hasattr(agent, "function_name")
|
if hasattr(agent, "function_name")
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,8 @@ class InitializeAgent(AgentExecutor):
|
||||||
return initialize_agent(
|
return initialize_agent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
llm=llm,
|
llm=llm,
|
||||||
agent=agent,
|
# LangChain now uses Enum for agent, but we still support string
|
||||||
|
agent=agent, # type: ignore
|
||||||
memory=memory,
|
memory=memory,
|
||||||
return_intermediate_steps=True,
|
return_intermediate_steps=True,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Type, Union
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
@ -12,6 +12,11 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
type_name: str
|
type_name: str
|
||||||
type_dict: Optional[Dict] = None
|
type_dict: Optional[Dict] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frontend_node_class(self) -> Type[FrontendNode]:
|
||||||
|
"""The class type of the FrontendNode created in frontend_node."""
|
||||||
|
return FrontendNode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
|
|
@ -62,7 +67,7 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
if key != "_type"
|
if key != "_type"
|
||||||
]
|
]
|
||||||
template = Template(type_name=name, fields=fields)
|
template = Template(type_name=name, fields=fields)
|
||||||
return FrontendNode(
|
return self.frontend_node_class(
|
||||||
template=template,
|
template=template,
|
||||||
description=signature.get("description", ""),
|
description=signature.get("description", ""),
|
||||||
base_classes=signature["base_classes"],
|
base_classes=signature["base_classes"],
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import chain_type_to_cls_dict
|
from langflow.interface.custom_lists import chain_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
|
|
@ -15,20 +16,30 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
self.type_dict = chain_type_to_cls_dict
|
self.type_dict = chain_type_to_cls_dict
|
||||||
|
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
||||||
|
|
||||||
|
self.type_dict.update(CUSTOM_CHAINS)
|
||||||
|
# Filter according to settings.chains
|
||||||
|
self.type_dict = {
|
||||||
|
name: chain
|
||||||
|
for name, chain in self.type_dict.items()
|
||||||
|
if name in settings.chains or settings.dev
|
||||||
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
def get_signature(self, name: str) -> Optional[Dict]:
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
try:
|
try:
|
||||||
return build_template_from_class(name, chain_type_to_cls_dict)
|
if name in get_custom_nodes(self.type_name).keys():
|
||||||
|
return get_custom_nodes(self.type_name)[name]
|
||||||
|
return build_template_from_class(name, self.type_to_loader_dict)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError("Memory not found") from exc
|
raise ValueError("Chain not found") from exc
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
return [
|
custom_chains = list(get_custom_nodes("chains").keys())
|
||||||
chain.__name__
|
default_chains = list(self.type_to_loader_dict.keys())
|
||||||
for chain in self.type_to_loader_dict.values()
|
|
||||||
if chain.__name__ in settings.chains or settings.dev
|
return default_chains + custom_chains
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
chain_creator = ChainCreator()
|
chain_creator = ChainCreator()
|
||||||
|
|
|
||||||
101
src/backend/langflow/interface/chains/custom.py
Normal file
101
src/backend/langflow/interface/chains/custom.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
from typing import Dict, Optional, Type
|
||||||
|
|
||||||
|
from langchain.chains import ConversationChain
|
||||||
|
from langchain.memory.buffer import ConversationBufferMemory
|
||||||
|
from langchain.schema import BaseMemory
|
||||||
|
from pydantic import Field, root_validator
|
||||||
|
|
||||||
|
from langflow.graph.utils import extract_input_variables_from_prompt
|
||||||
|
|
||||||
|
DEFAULT_SUFFIX = """"
|
||||||
|
Current conversation:
|
||||||
|
{history}
|
||||||
|
Human: {input}
|
||||||
|
{ai_prefix}"""
|
||||||
|
|
||||||
|
|
||||||
|
class BaseCustomChain(ConversationChain):
|
||||||
|
"""BaseCustomChain is a chain you can use to have a conversation with a custom character."""
|
||||||
|
|
||||||
|
template: Optional[str]
|
||||||
|
|
||||||
|
ai_prefix_key: Optional[str]
|
||||||
|
"""Field to use as the ai_prefix. It needs to be set and has to be in the template"""
|
||||||
|
|
||||||
|
@root_validator(pre=False)
|
||||||
|
def build_template(cls, values):
|
||||||
|
format_dict = {}
|
||||||
|
input_variables = extract_input_variables_from_prompt(values["template"])
|
||||||
|
|
||||||
|
if values.get("ai_prefix_key", None) is None:
|
||||||
|
values["ai_prefix_key"] = values["memory"].ai_prefix
|
||||||
|
|
||||||
|
for key in input_variables:
|
||||||
|
new_value = values.get(key, f"{{{key}}}")
|
||||||
|
format_dict[key] = new_value
|
||||||
|
if key == values.get("ai_prefix_key", None):
|
||||||
|
values["memory"].ai_prefix = new_value
|
||||||
|
|
||||||
|
values["template"] = values["template"].format(**format_dict)
|
||||||
|
|
||||||
|
values["template"] = values["template"]
|
||||||
|
values["input_variables"] = extract_input_variables_from_prompt(
|
||||||
|
values["template"]
|
||||||
|
)
|
||||||
|
values["prompt"].template = values["template"]
|
||||||
|
values["prompt"].input_variables = values["input_variables"]
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
class SeriesCharacterChain(BaseCustomChain):
|
||||||
|
"""SeriesCharacterChain is a chain you can use to have a conversation with a character from a series."""
|
||||||
|
|
||||||
|
character: str
|
||||||
|
series: str
|
||||||
|
template: Optional[
|
||||||
|
str
|
||||||
|
] = """I want you to act like {character} from {series}.
|
||||||
|
I want you to respond and answer like {character}. do not write any explanations. only answer like {character}.
|
||||||
|
You must know all of the knowledge of {character}.
|
||||||
|
Current conversation:
|
||||||
|
{history}
|
||||||
|
Human: {input}
|
||||||
|
{character}:"""
|
||||||
|
memory: BaseMemory = Field(default_factory=ConversationBufferMemory)
|
||||||
|
ai_prefix_key: Optional[str] = "character"
|
||||||
|
"""Default memory store."""
|
||||||
|
|
||||||
|
|
||||||
|
class MidJourneyPromptChain(BaseCustomChain):
|
||||||
|
"""MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."""
|
||||||
|
|
||||||
|
template: Optional[
|
||||||
|
str
|
||||||
|
] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program.
|
||||||
|
Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI.
|
||||||
|
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.
|
||||||
|
For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures.
|
||||||
|
The more detailed and imaginative your description, the more interesting the resulting image will be. Here is your first prompt:
|
||||||
|
"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.\"
|
||||||
|
|
||||||
|
Current conversation:
|
||||||
|
{history}
|
||||||
|
Human: {input}
|
||||||
|
AI:""" # noqa: E501
|
||||||
|
|
||||||
|
|
||||||
|
class TimeTravelGuideChain(BaseCustomChain):
|
||||||
|
template: Optional[
|
||||||
|
str
|
||||||
|
] = """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.
|
||||||
|
Current conversation:
|
||||||
|
{history}
|
||||||
|
Human: {input}
|
||||||
|
AI:""" # noqa: E501
|
||||||
|
|
||||||
|
|
||||||
|
CUSTOM_CHAINS: Dict[str, Type[ConversationChain]] = {
|
||||||
|
"SeriesCharacterChain": SeriesCharacterChain,
|
||||||
|
"MidJourneyPromptChain": MidJourneyPromptChain,
|
||||||
|
"TimeTravelGuideChain": TimeTravelGuideChain,
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# This module is used to import any langchain class by name.
|
# This module is used to import any langchain class by name.
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
from typing import Any
|
from typing import Any, Type
|
||||||
|
|
||||||
from langchain import PromptTemplate
|
from langchain import PromptTemplate
|
||||||
from langchain.agents import Agent
|
from langchain.agents import Agent
|
||||||
|
|
@ -30,6 +30,8 @@ def import_module(module_path: str) -> Any:
|
||||||
|
|
||||||
def import_by_type(_type: str, name: str) -> Any:
|
def import_by_type(_type: str, name: str) -> Any:
|
||||||
"""Import class by type and name"""
|
"""Import class by type and name"""
|
||||||
|
if _type is None:
|
||||||
|
raise ValueError(f"Type cannot be None. Check if {name} is in the config file.")
|
||||||
func_dict = {
|
func_dict = {
|
||||||
"agents": import_agent,
|
"agents": import_agent,
|
||||||
"prompts": import_prompt,
|
"prompts": import_prompt,
|
||||||
|
|
@ -69,10 +71,14 @@ def import_class(class_path: str) -> Any:
|
||||||
return getattr(module, class_name)
|
return getattr(module, class_name)
|
||||||
|
|
||||||
|
|
||||||
def import_prompt(prompt: str) -> PromptTemplate:
|
def import_prompt(prompt: str) -> Type[PromptTemplate]:
|
||||||
|
from langflow.interface.prompts.custom import CUSTOM_PROMPTS
|
||||||
|
|
||||||
"""Import prompt from prompt name"""
|
"""Import prompt from prompt name"""
|
||||||
if prompt == "ZeroShotPrompt":
|
if prompt == "ZeroShotPrompt":
|
||||||
return import_class("langchain.prompts.PromptTemplate")
|
return import_class("langchain.prompts.PromptTemplate")
|
||||||
|
elif prompt in CUSTOM_PROMPTS:
|
||||||
|
return CUSTOM_PROMPTS[prompt]
|
||||||
return import_class(f"langchain.prompts.{prompt}")
|
return import_class(f"langchain.prompts.{prompt}")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -104,8 +110,12 @@ def import_tool(tool: str) -> BaseTool:
|
||||||
return get_tool_by_name(tool)
|
return get_tool_by_name(tool)
|
||||||
|
|
||||||
|
|
||||||
def import_chain(chain: str) -> Chain:
|
def import_chain(chain: str) -> Type[Chain]:
|
||||||
"""Import chain from chain name"""
|
"""Import chain from chain name"""
|
||||||
|
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
||||||
|
|
||||||
|
if chain in CUSTOM_CHAINS:
|
||||||
|
return CUSTOM_CHAINS[chain]
|
||||||
return import_class(f"langchain.chains.{chain}")
|
return import_class(f"langchain.chains.{chain}")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
from langflow.interface.custom_lists import memory_type_to_cls_dict
|
from langflow.interface.custom_lists import memory_type_to_cls_dict
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
|
from langflow.template.base import FrontendNode
|
||||||
|
from langflow.template.nodes import MemoryFrontendNode
|
||||||
from langflow.utils.util import build_template_from_class
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
||||||
class MemoryCreator(LangChainTypeCreator):
|
class MemoryCreator(LangChainTypeCreator):
|
||||||
type_name: str = "memories"
|
type_name: str = "memories"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frontend_node_class(self) -> Type[FrontendNode]:
|
||||||
|
"""The class type of the FrontendNode created in frontend_node."""
|
||||||
|
return MemoryFrontendNode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,59 @@
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langchain.prompts import loading
|
from langchain import prompts
|
||||||
|
|
||||||
from langflow.custom.customs import get_custom_nodes
|
from langflow.custom.customs import get_custom_nodes
|
||||||
from langflow.interface.base import LangChainTypeCreator
|
from langflow.interface.base import LangChainTypeCreator
|
||||||
|
from langflow.interface.importing.utils import import_class
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from langflow.utils.util import build_template_from_function
|
from langflow.template.nodes import PromptFrontendNode
|
||||||
|
from langflow.utils.util import build_template_from_class
|
||||||
|
|
||||||
|
|
||||||
class PromptCreator(LangChainTypeCreator):
|
class PromptCreator(LangChainTypeCreator):
|
||||||
type_name: str = "prompts"
|
type_name: str = "prompts"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def frontend_node_class(self) -> Type[PromptFrontendNode]:
|
||||||
|
return PromptFrontendNode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
self.type_dict = loading.type_to_loader_dict
|
self.type_dict = {
|
||||||
|
prompt_name: import_class(f"langchain.prompts.{prompt_name}")
|
||||||
|
# if prompt_name is not lower case it is a class
|
||||||
|
for prompt_name in prompts.__all__
|
||||||
|
}
|
||||||
|
# Merge CUSTOM_PROMPTS into self.type_dict
|
||||||
|
from langflow.interface.prompts.custom import CUSTOM_PROMPTS
|
||||||
|
|
||||||
|
self.type_dict.update(CUSTOM_PROMPTS)
|
||||||
|
# Now filter according to settings.prompts
|
||||||
|
self.type_dict = {
|
||||||
|
name: prompt
|
||||||
|
for name, prompt in self.type_dict.items()
|
||||||
|
if name in settings.prompts or settings.dev
|
||||||
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
def get_signature(self, name: str) -> Optional[Dict]:
|
def get_signature(self, name: str) -> Optional[Dict]:
|
||||||
try:
|
try:
|
||||||
if name in get_custom_nodes(self.type_name).keys():
|
if name in get_custom_nodes(self.type_name).keys():
|
||||||
return get_custom_nodes(self.type_name)[name]
|
return get_custom_nodes(self.type_name)[name]
|
||||||
return build_template_from_function(name, self.type_to_loader_dict)
|
return build_template_from_class(name, self.type_to_loader_dict)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError("Prompt not found") from exc
|
raise ValueError("Prompt not found") from exc
|
||||||
|
|
||||||
def to_list(self) -> List[str]:
|
def to_list(self) -> List[str]:
|
||||||
custom_prompts = get_custom_nodes("prompts")
|
custom_prompts = get_custom_nodes("prompts")
|
||||||
library_prompts = [
|
# library_prompts = [
|
||||||
prompt.__annotations__["return"].__name__
|
# prompt.__annotations__["return"].__name__
|
||||||
for prompt in self.type_to_loader_dict.values()
|
# for prompt in self.type_to_loader_dict.values()
|
||||||
if prompt.__annotations__["return"].__name__ in settings.prompts
|
# if prompt.__annotations__["return"].__name__ in settings.prompts
|
||||||
or settings.dev
|
# or settings.dev
|
||||||
]
|
# ]
|
||||||
return library_prompts + list(custom_prompts.keys())
|
return list(self.type_to_loader_dict.keys()) + list(custom_prompts.keys())
|
||||||
|
|
||||||
|
|
||||||
prompt_creator = PromptCreator()
|
prompt_creator = PromptCreator()
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,52 @@
|
||||||
from typing import List, Optional
|
from typing import Dict, List, Optional, Type
|
||||||
|
|
||||||
from langchain.prompts import PromptTemplate
|
from langchain.prompts import PromptTemplate
|
||||||
from pydantic import root_validator
|
from pydantic import root_validator
|
||||||
|
|
||||||
from langflow.graph.utils import extract_input_variables_from_prompt
|
from langflow.graph.utils import extract_input_variables_from_prompt
|
||||||
from langflow.template.base import Template, TemplateField
|
|
||||||
from langflow.template.nodes import PromptTemplateNode
|
|
||||||
|
|
||||||
CHARACTER_PROMPT = """I want you to act like {character} from {series}.
|
# Steps to create a BaseCustomPrompt:
|
||||||
I want you to respond and answer like {character}. do not write any explanations. only answer like {character}.
|
# 1. Create a prompt template that endes with:
|
||||||
You must know all of the knowledge of {character}."""
|
# Current conversation:
|
||||||
|
# {history}
|
||||||
|
# Human: {input}
|
||||||
|
# {ai_prefix}:
|
||||||
|
# 2. Create a class that inherits from BaseCustomPrompt
|
||||||
|
# 3. Add the following class attributes:
|
||||||
|
# template: str = ""
|
||||||
|
# description: Optional[str]
|
||||||
|
# ai_prefix: Optional[str] = "{ai_prefix}"
|
||||||
|
# 3.1. The ai_prefix should be a value in input_variables
|
||||||
|
# SeriesCharacterPrompt is a working example
|
||||||
|
# If used in a LLMChain, with a Memory module, it will work as expected
|
||||||
|
# We should consider creating ConversationalChains that expose custom parameters
|
||||||
|
# That way it will be easier to create custom prompts
|
||||||
|
|
||||||
|
|
||||||
class BaseCustomPrompt(PromptTemplate):
|
class BaseCustomPrompt(PromptTemplate):
|
||||||
template: str = ""
|
template: str = ""
|
||||||
description: Optional[str]
|
description: Optional[str]
|
||||||
human_text: str = "\n {input}"
|
ai_prefix: Optional[str]
|
||||||
|
|
||||||
@root_validator(pre=False)
|
@root_validator(pre=False)
|
||||||
def build_template(cls, values):
|
def build_template(cls, values):
|
||||||
format_dict = {}
|
format_dict = {}
|
||||||
|
ai_prefix_format_dict = {}
|
||||||
for key in values.get("input_variables", []):
|
for key in values.get("input_variables", []):
|
||||||
new_value = values[key]
|
new_value = values.get(key, f"{{{key}}}")
|
||||||
format_dict[key] = new_value
|
format_dict[key] = new_value
|
||||||
|
if key in values["ai_prefix"]:
|
||||||
|
ai_prefix_format_dict[key] = new_value
|
||||||
|
|
||||||
|
values["ai_prefix"] = values["ai_prefix"].format(**ai_prefix_format_dict)
|
||||||
values["template"] = values["template"].format(**format_dict)
|
values["template"] = values["template"].format(**format_dict)
|
||||||
|
|
||||||
values["template"] = values["template"] + values["human_text"]
|
values["template"] = values["template"]
|
||||||
values["input_variables"] = extract_input_variables_from_prompt(
|
values["input_variables"] = extract_input_variables_from_prompt(
|
||||||
values["template"]
|
values["template"]
|
||||||
)
|
)
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def build_frontend_node(self) -> PromptTemplateNode:
|
|
||||||
return PromptTemplateNode(
|
|
||||||
template=Template(
|
|
||||||
type_name="test",
|
|
||||||
fields=[
|
|
||||||
TemplateField(name=field, field_type="str", required=True)
|
|
||||||
for field in self.input_variables
|
|
||||||
],
|
|
||||||
),
|
|
||||||
description=self.description or "",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SeriesCharacterPrompt(BaseCustomPrompt):
|
class SeriesCharacterPrompt(BaseCustomPrompt):
|
||||||
# Add a very descriptive description for the prompt generator
|
# Add a very descriptive description for the prompt generator
|
||||||
|
|
@ -52,14 +55,23 @@ class SeriesCharacterPrompt(BaseCustomPrompt):
|
||||||
] = "A prompt that asks the AI to act like a character from a series."
|
] = "A prompt that asks the AI to act like a character from a series."
|
||||||
character: str
|
character: str
|
||||||
series: str
|
series: str
|
||||||
human_text: str = "\n {input}"
|
template: str = """I want you to act like {character} from {series}.
|
||||||
template: str = CHARACTER_PROMPT
|
I want you to respond and answer like {character}. do not write any explanations. only answer like {character}.
|
||||||
|
You must know all of the knowledge of {character}.
|
||||||
|
|
||||||
|
Current conversation:
|
||||||
|
{history}
|
||||||
|
Human: {input}
|
||||||
|
{character}:"""
|
||||||
|
|
||||||
|
ai_prefix: str = "{character}"
|
||||||
input_variables: List[str] = ["character", "series"]
|
input_variables: List[str] = ["character", "series"]
|
||||||
|
|
||||||
|
|
||||||
|
CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = {
|
||||||
|
"SeriesCharacterPrompt": SeriesCharacterPrompt
|
||||||
|
}
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
prompt = SeriesCharacterPrompt(character="Walter White", series="Breaking Bad")
|
prompt = SeriesCharacterPrompt(character="Harry Potter", series="Harry Potter")
|
||||||
user_input = "I am the one who knocks"
|
print(prompt.template)
|
||||||
full_prompt = prompt.format(input=user_input)
|
|
||||||
print(full_prompt)
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,12 @@ def process_graph(data_graph: Dict[str, Any]):
|
||||||
)
|
)
|
||||||
logger.debug("Loaded langchain object")
|
logger.debug("Loaded langchain object")
|
||||||
|
|
||||||
|
if langchain_object is None:
|
||||||
|
# Raise user facing error
|
||||||
|
raise ValueError(
|
||||||
|
"There was an error loading the langchain_object. Please, check all the nodes and try again."
|
||||||
|
)
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
logger.debug("Generating result and thought")
|
logger.debug("Generating result and thought")
|
||||||
result, thought = get_result_and_thought_using_graph(langchain_object, message)
|
result, thought = get_result_and_thought_using_graph(langchain_object, message)
|
||||||
|
|
@ -66,34 +72,101 @@ def process_graph(data_graph: Dict[str, Any]):
|
||||||
return {"result": str(result), "thought": thought.strip()}
|
return {"result": str(result), "thought": thought.strip()}
|
||||||
|
|
||||||
|
|
||||||
def get_result_and_thought_using_graph(loaded_langchain, message: str):
|
def get_memory_key(langchain_object):
|
||||||
|
"""
|
||||||
|
Given a LangChain object, this function retrieves the current memory key from the object's memory attribute.
|
||||||
|
It then checks if the key exists in a dictionary of known memory keys and returns the corresponding key,
|
||||||
|
or None if the current key is not recognized.
|
||||||
|
"""
|
||||||
|
mem_key_dict = {
|
||||||
|
"chat_history": "history",
|
||||||
|
"history": "chat_history",
|
||||||
|
}
|
||||||
|
memory_key = langchain_object.memory.memory_key
|
||||||
|
return mem_key_dict.get(memory_key)
|
||||||
|
|
||||||
|
|
||||||
|
def update_memory_keys(langchain_object, possible_new_mem_key):
|
||||||
|
"""
|
||||||
|
Given a LangChain object and a possible new memory key, this function updates the input and output keys in the
|
||||||
|
object's memory attribute to exclude the current memory key and the possible new key. It then sets the memory key
|
||||||
|
to the possible new key.
|
||||||
|
"""
|
||||||
|
input_key = [
|
||||||
|
key
|
||||||
|
for key in langchain_object.input_keys
|
||||||
|
if key not in [langchain_object.memory.memory_key, possible_new_mem_key]
|
||||||
|
][0]
|
||||||
|
|
||||||
|
output_key = [
|
||||||
|
key
|
||||||
|
for key in langchain_object.output_keys
|
||||||
|
if key not in [langchain_object.memory.memory_key, possible_new_mem_key]
|
||||||
|
][0]
|
||||||
|
|
||||||
|
langchain_object.memory.input_key = input_key
|
||||||
|
langchain_object.memory.output_key = output_key
|
||||||
|
langchain_object.memory.memory_key = possible_new_mem_key
|
||||||
|
|
||||||
|
|
||||||
|
def fix_memory_inputs(langchain_object):
|
||||||
|
"""
|
||||||
|
Given a LangChain object, this function checks if it has a memory attribute and if that memory key exists in the
|
||||||
|
object's input variables. If so, it does nothing. Otherwise, it gets a possible new memory key using the
|
||||||
|
get_memory_key function and updates the memory keys using the update_memory_keys function.
|
||||||
|
"""
|
||||||
|
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
||||||
|
try:
|
||||||
|
if langchain_object.memory.memory_key in langchain_object.input_variables:
|
||||||
|
return
|
||||||
|
except AttributeError:
|
||||||
|
if (
|
||||||
|
langchain_object.memory.memory_key
|
||||||
|
in langchain_object.prompt.input_variables
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
possible_new_mem_key = get_memory_key(langchain_object)
|
||||||
|
if possible_new_mem_key is not None:
|
||||||
|
update_memory_keys(langchain_object, possible_new_mem_key)
|
||||||
|
|
||||||
|
|
||||||
|
def get_result_and_thought_using_graph(langchain_object, message: str):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
try:
|
try:
|
||||||
loaded_langchain.verbose = True
|
if hasattr(langchain_object, "verbose"):
|
||||||
|
langchain_object.verbose = True
|
||||||
|
chat_input = None
|
||||||
|
memory_key = ""
|
||||||
|
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
||||||
|
memory_key = langchain_object.memory.memory_key
|
||||||
|
|
||||||
|
for key in langchain_object.input_keys:
|
||||||
|
if key not in [memory_key, "chat_history"]:
|
||||||
|
chat_input = {key: message}
|
||||||
|
|
||||||
|
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||||
|
# https://github.com/hwchase17/langchain/issues/2068
|
||||||
|
# Deactivating until we have a frontend solution
|
||||||
|
# to display intermediate steps
|
||||||
|
langchain_object.return_intermediate_steps = False
|
||||||
|
|
||||||
|
fix_memory_inputs(langchain_object)
|
||||||
|
|
||||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||||
chat_input = None
|
|
||||||
for key in loaded_langchain.input_keys:
|
|
||||||
if key == "chat_history" and hasattr(loaded_langchain, "memory"):
|
|
||||||
loaded_langchain.memory.memory_key = "chat_history"
|
|
||||||
else:
|
|
||||||
chat_input = {key: message}
|
|
||||||
|
|
||||||
if hasattr(loaded_langchain, "return_intermediate_steps"):
|
|
||||||
# https://github.com/hwchase17/langchain/issues/2068
|
|
||||||
loaded_langchain.return_intermediate_steps = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
output = loaded_langchain(chat_input)
|
output = langchain_object(chat_input)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
logger.debug("Error: %s", str(exc))
|
# make the error message more informative
|
||||||
output = loaded_langchain.run(chat_input)
|
logger.debug(f"Error: {str(exc)}")
|
||||||
|
output = langchain_object.run(chat_input)
|
||||||
|
|
||||||
intermediate_steps = (
|
intermediate_steps = (
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
||||||
)
|
)
|
||||||
|
|
||||||
result = (
|
result = (
|
||||||
output.get(loaded_langchain.output_keys[0])
|
output.get(langchain_object.output_keys[0])
|
||||||
if isinstance(output, dict)
|
if isinstance(output, dict)
|
||||||
else output
|
else output
|
||||||
)
|
)
|
||||||
|
|
@ -110,16 +183,16 @@ def get_result_and_thought_using_graph(loaded_langchain, message: str):
|
||||||
def get_result_and_thought(extracted_json: Dict[str, Any], message: str):
|
def get_result_and_thought(extracted_json: Dict[str, Any], message: str):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
try:
|
try:
|
||||||
loaded_langchain = loading.load_langchain_type_from_config(
|
langchain_object = loading.load_langchain_type_from_config(
|
||||||
config=extracted_json
|
config=extracted_json
|
||||||
)
|
)
|
||||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||||
output = loaded_langchain(message)
|
output = langchain_object(message)
|
||||||
intermediate_steps = (
|
intermediate_steps = (
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
||||||
)
|
)
|
||||||
result = (
|
result = (
|
||||||
output.get(loaded_langchain.output_keys[0])
|
output.get(langchain_object.output_keys[0])
|
||||||
if isinstance(output, dict)
|
if isinstance(output, dict)
|
||||||
else output
|
else output
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class WrapperCreator(LangChainTypeCreator):
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
if self.type_dict is None:
|
if self.type_dict is None:
|
||||||
self.type_dict = {
|
self.type_dict = {
|
||||||
wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]
|
wrapper.__name__: wrapper for wrapper in [requests.TextRequestsWrapper]
|
||||||
}
|
}
|
||||||
return self.type_dict
|
return self.type_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from langflow.api.endpoints import router as endpoints_router
|
from langflow.api.endpoints import router as endpoints_router
|
||||||
|
from langflow.api.validate import router as validate_router
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
|
@ -21,6 +22,7 @@ def create_app():
|
||||||
)
|
)
|
||||||
|
|
||||||
app.include_router(endpoints_router)
|
app.include_router(endpoints_router)
|
||||||
|
app.include_router(validate_router)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ class TemplateFieldCreator(BaseModel, ABC):
|
||||||
password: bool = False
|
password: bool = False
|
||||||
options: list[str] = []
|
options: list[str] = []
|
||||||
name: str = ""
|
name: str = ""
|
||||||
|
display_name: Optional[str] = None
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
result = self.dict()
|
result = self.dict()
|
||||||
|
|
@ -216,6 +217,12 @@ class FrontendNode(BaseModel):
|
||||||
if name == "OpenAI" and key == "model_name":
|
if name == "OpenAI" and key == "model_name":
|
||||||
field.options = constants.OPENAI_MODELS
|
field.options = constants.OPENAI_MODELS
|
||||||
field.is_list = True
|
field.is_list = True
|
||||||
elif name == "ChatOpenAI" and key == "model_name":
|
elif name == "ChatOpenAI":
|
||||||
field.options = constants.CHAT_OPENAI_MODELS
|
if key == "model_name":
|
||||||
field.is_list = True
|
field.options = constants.CHAT_OPENAI_MODELS
|
||||||
|
field.is_list = True
|
||||||
|
if "api_key" in key and "OpenAI" in str(name):
|
||||||
|
field.display_name = "OpenAI API Key"
|
||||||
|
field.required = True
|
||||||
|
if field.value is None:
|
||||||
|
field.value = ""
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,24 @@ FORCE_SHOW_FIELDS = [
|
||||||
"max_value_length",
|
"max_value_length",
|
||||||
"max_tokens",
|
"max_tokens",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
DEFAULT_PROMPT = """
|
||||||
|
I want you to act as a naming consultant for new companies.
|
||||||
|
|
||||||
|
Here are some examples of good company names:
|
||||||
|
|
||||||
|
- search engine, Google
|
||||||
|
- social media, Facebook
|
||||||
|
- video sharing, YouTube
|
||||||
|
|
||||||
|
The name should be short, catchy and easy to remember.
|
||||||
|
|
||||||
|
What is a good name for a company that makes {product}?
|
||||||
|
"""
|
||||||
|
|
||||||
|
SYSTEM_PROMPT = """
|
||||||
|
You are a helpful assistant that talks casually about life in general.
|
||||||
|
You are a good listener and you can talk about anything.
|
||||||
|
"""
|
||||||
|
|
||||||
|
HUMAN_PROMPT = "{input}"
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,27 @@ from langchain.agents import loading
|
||||||
from langchain.agents.mrkl import prompt
|
from langchain.agents.mrkl import prompt
|
||||||
|
|
||||||
from langflow.template.base import FrontendNode, Template, TemplateField
|
from langflow.template.base import FrontendNode, Template, TemplateField
|
||||||
|
from langflow.template.constants import DEFAULT_PROMPT, HUMAN_PROMPT, SYSTEM_PROMPT
|
||||||
from langflow.utils.constants import DEFAULT_PYTHON_FUNCTION
|
from langflow.utils.constants import DEFAULT_PYTHON_FUNCTION
|
||||||
|
|
||||||
|
NON_CHAT_AGENTS = {
|
||||||
|
agent_type: agent_class
|
||||||
|
for agent_type, agent_class in loading.AGENT_TO_CLASS.items()
|
||||||
|
if "chat" not in agent_type.value
|
||||||
|
}
|
||||||
|
|
||||||
class ZeroShotPromptNode(FrontendNode):
|
|
||||||
|
class BasePromptFrontendNode(FrontendNode):
|
||||||
|
name: str
|
||||||
|
template: Template
|
||||||
|
description: str
|
||||||
|
base_classes: list[str]
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return super().to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class ZeroShotPromptNode(BasePromptFrontendNode):
|
||||||
name: str = "ZeroShotPrompt"
|
name: str = "ZeroShotPrompt"
|
||||||
template: Template = Template(
|
template: Template = Template(
|
||||||
type_name="zero_shot",
|
type_name="zero_shot",
|
||||||
|
|
@ -165,8 +182,8 @@ class InitializeAgentNode(FrontendNode):
|
||||||
is_list=True,
|
is_list=True,
|
||||||
show=True,
|
show=True,
|
||||||
multiline=False,
|
multiline=False,
|
||||||
options=list(loading.AGENT_TO_CLASS.keys()),
|
options=list(NON_CHAT_AGENTS.keys()),
|
||||||
value=list(loading.AGENT_TO_CLASS.keys())[0],
|
value=list(NON_CHAT_AGENTS.keys())[0],
|
||||||
name="agent",
|
name="agent",
|
||||||
),
|
),
|
||||||
TemplateField(
|
TemplateField(
|
||||||
|
|
@ -229,3 +246,37 @@ class CSVAgentNode(FrontendNode):
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return super().to_dict()
|
return super().to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class PromptFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
# if field.field_type == "StringPromptTemplate"
|
||||||
|
# change it to str
|
||||||
|
if field.field_type == "StringPromptTemplate" and "Message" in str(name):
|
||||||
|
field.field_type = "str"
|
||||||
|
field.multiline = True
|
||||||
|
field.value = HUMAN_PROMPT if "Human" in field.name else SYSTEM_PROMPT
|
||||||
|
if field.name == "template" and field.value == "":
|
||||||
|
field.value = DEFAULT_PROMPT
|
||||||
|
|
||||||
|
if (
|
||||||
|
"Union" in field.field_type
|
||||||
|
and "BaseMessagePromptTemplate" in field.field_type
|
||||||
|
):
|
||||||
|
field.field_type = "BaseMessagePromptTemplate"
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryFrontendNode(FrontendNode):
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
|
||||||
|
if not isinstance(field.value, str):
|
||||||
|
field.value = None
|
||||||
|
if field.name == "k":
|
||||||
|
field.required = True
|
||||||
|
field.show = True
|
||||||
|
field.field_type = "int"
|
||||||
|
field.value = 10
|
||||||
|
field.display_name = "Memory Size"
|
||||||
|
|
|
||||||
|
|
@ -60,4 +60,4 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"proxy": "http://backend:7860"
|
"proxy": "http://backend:7860"
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +153,7 @@ export default function ParameterComponent({
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={data.node.template[name].options}
|
options={data.node.template[name].options}
|
||||||
onSelect={(newValue) => (data.node.template[name].value = newValue)}
|
onSelect={(newValue) => (data.node.template[name].value = newValue)}
|
||||||
value={data.node.template[name].value ?? "chose an option"}
|
value={data.node.template[name].value ?? "Choose an option"}
|
||||||
></Dropdown>
|
></Dropdown>
|
||||||
) : left === true && type === "code" ? (
|
) : left === true && type === "code" ? (
|
||||||
<CodeAreaComponent
|
<CodeAreaComponent
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default function GenericNode({
|
||||||
});
|
});
|
||||||
showError.current = false;
|
showError.current = false;
|
||||||
}
|
}
|
||||||
deleteNode(data.id)
|
deleteNode(data.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|
@ -74,7 +74,12 @@ export default function GenericNode({
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"px-5 py-2 mt-2 dark:text-white text-center",
|
"px-5 py-2 mt-2 dark:text-white text-center",
|
||||||
Object.keys(data.node.template).filter(key => !key.startsWith('_') && data.node.template[key].show).length === 0?"hidden":""
|
Object.keys(data.node.template).filter(
|
||||||
|
(key) =>
|
||||||
|
!key.startsWith("_") && data.node.template[key].show
|
||||||
|
).length === 0
|
||||||
|
? "hidden"
|
||||||
|
: ""
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Inputs
|
Inputs
|
||||||
|
|
@ -89,7 +94,13 @@ export default function GenericNode({
|
||||||
nodeColors[types[data.node.template[t].type]] ??
|
nodeColors[types[data.node.template[t].type]] ??
|
||||||
nodeColors.unknown
|
nodeColors.unknown
|
||||||
}
|
}
|
||||||
title={snakeToNormalCase(t)}
|
title={
|
||||||
|
data.node.template[t].display_name
|
||||||
|
? data.node.template[t].display_name
|
||||||
|
: data.node.template[t].name
|
||||||
|
? snakeToNormalCase(data.node.template[t].name)
|
||||||
|
: snakeToNormalCase(t)
|
||||||
|
}
|
||||||
name={t}
|
name={t}
|
||||||
tooltipTitle={
|
tooltipTitle={
|
||||||
"Type: " +
|
"Type: " +
|
||||||
|
|
|
||||||
|
|
@ -75,25 +75,24 @@ export default function Chat({ flow, reactFlowInstance }: ChatType) {
|
||||||
}, [chatHistory]);
|
}, [chatHistory]);
|
||||||
function validateNodes() {
|
function validateNodes() {
|
||||||
if (
|
if (
|
||||||
reactFlowInstance
|
reactFlowInstance.getNodes().some(
|
||||||
.getNodes()
|
(n) =>
|
||||||
.some(
|
n.data.node &&
|
||||||
(n) =>
|
Object.keys(n.data.node.template).some((t: any) => {
|
||||||
n.data.node &&
|
return (
|
||||||
Object.keys(n.data.node.template).some(
|
n.data.node.template[t].required &&
|
||||||
(t: any) =>
|
(!n.data.node.template[t].value ||
|
||||||
n.data.node.template[t].required &&
|
n.data.node.template[t].value === "") &&
|
||||||
n.data.node.template[t].value === "" &&
|
!reactFlowInstance
|
||||||
n.data.node.template[t].required &&
|
.getEdges()
|
||||||
!reactFlowInstance
|
.some(
|
||||||
.getEdges()
|
(e) =>
|
||||||
.some(
|
e.targetHandle.split("|")[1] === t &&
|
||||||
(e) =>
|
e.targetHandle.split("|")[2] === n.id
|
||||||
e.sourceHandle.split("|")[1] === t &&
|
)
|
||||||
e.sourceHandle.split("|")[2] === n.id
|
);
|
||||||
)
|
})
|
||||||
)
|
)
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -130,10 +129,10 @@ export default function Chat({ flow, reactFlowInstance }: ChatType) {
|
||||||
setChatHistory((chatHistory) => {
|
setChatHistory((chatHistory) => {
|
||||||
let newChat = chatHistory;
|
let newChat = chatHistory;
|
||||||
|
|
||||||
lastMessage= newChat.pop().message;
|
lastMessage = newChat.pop().message;
|
||||||
return newChat;
|
return newChat;
|
||||||
});
|
});
|
||||||
setChatValue(lastMessage)
|
setChatValue(lastMessage);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ export async function sendAll(data:sendAllProps) {
|
||||||
|
|
||||||
export async function checkCode(code:string):Promise<AxiosResponse<errorsTypeAPI>>{
|
export async function checkCode(code:string):Promise<AxiosResponse<errorsTypeAPI>>{
|
||||||
|
|
||||||
return await axios.post('/validate',{code})
|
return await axios.post('/validate/code',{code})
|
||||||
}
|
}
|
||||||
|
|
@ -1,86 +1,93 @@
|
||||||
import { Bars2Icon } from "@heroicons/react/24/outline";
|
import { Bars2Icon } from "@heroicons/react/24/outline";
|
||||||
import DisclosureComponent from "../DisclosureComponent";
|
import DisclosureComponent from "../DisclosureComponent";
|
||||||
import {
|
import { nodeColors, nodeIcons, nodeNames } from "../../../../utils";
|
||||||
nodeColors,
|
|
||||||
nodeIcons,
|
|
||||||
nodeNames,
|
|
||||||
} from "../../../../utils";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { getAll } from "../../../../controllers/API";
|
import { getAll } from "../../../../controllers/API";
|
||||||
import { typesContext } from "../../../../contexts/typesContext";
|
import { typesContext } from "../../../../contexts/typesContext";
|
||||||
import { APIClassType, APIKindType, APIObjectType } from "../../../../types/api";
|
import {
|
||||||
|
APIClassType,
|
||||||
|
APIKindType,
|
||||||
|
APIObjectType,
|
||||||
|
} from "../../../../types/api";
|
||||||
|
|
||||||
export default function ExtraSidebar() {
|
export default function ExtraSidebar() {
|
||||||
const [data, setData] = useState({});
|
const [data, setData] = useState({});
|
||||||
const { setTypes} = useContext(typesContext);
|
const { setTypes } = useContext(typesContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getTypes():Promise<void>{
|
async function getTypes(): Promise<void> {
|
||||||
|
// Make an asynchronous API call to retrieve all data.
|
||||||
// Make an asynchronous API call to retrieve all data.
|
let result = await getAll();
|
||||||
let result = await getAll();
|
|
||||||
|
|
||||||
// Update the state of the component with the retrieved data.
|
|
||||||
setData(result.data);
|
|
||||||
|
|
||||||
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
|
||||||
setTypes(
|
|
||||||
Object.keys(result.data).reduce(
|
|
||||||
(acc, curr) => {
|
|
||||||
Object.keys(result.data[curr]).forEach((c:keyof APIKindType) => {
|
|
||||||
acc[c] = curr;
|
|
||||||
// Add the base classes to the accumulator as well.
|
|
||||||
result.data[curr][c].base_classes?.forEach((b) => {
|
|
||||||
acc[b] = curr;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return acc;
|
|
||||||
},{}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Call the getTypes function.
|
|
||||||
getTypes();
|
|
||||||
}, [setTypes]);
|
|
||||||
|
|
||||||
|
// Update the state of the component with the retrieved data.
|
||||||
|
setData(result.data);
|
||||||
|
|
||||||
function onDragStart(event: React.DragEvent<any>, data:{type:string,node?:APIClassType}) {
|
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||||
//start drag event
|
setTypes(
|
||||||
event.dataTransfer.effectAllowed = "move";
|
Object.keys(result.data).reduce((acc, curr) => {
|
||||||
event.dataTransfer.setData("json", JSON.stringify(data));
|
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
|
||||||
}
|
acc[c] = curr;
|
||||||
|
// Add the base classes to the accumulator as well.
|
||||||
|
result.data[curr][c].base_classes?.forEach((b) => {
|
||||||
|
acc[b] = curr;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Call the getTypes function.
|
||||||
|
getTypes();
|
||||||
|
}, [setTypes]);
|
||||||
|
|
||||||
return (
|
function onDragStart(
|
||||||
<div className="mt-1 w-full">
|
event: React.DragEvent<any>,
|
||||||
{Object.keys(data).map((d:keyof APIObjectType, i) => (
|
data: { type: string; node?: APIClassType }
|
||||||
<DisclosureComponent
|
) {
|
||||||
key={i}
|
//start drag event
|
||||||
button={{ title: nodeNames[d]??nodeNames.unknown, Icon: nodeIcons[d]??nodeIcons.unknown }}
|
event.dataTransfer.effectAllowed = "move";
|
||||||
>
|
event.dataTransfer.setData("json", JSON.stringify(data));
|
||||||
<div className="p-2 flex flex-col gap-2">
|
}
|
||||||
{Object.keys(data[d]).map((t: string, k) => (
|
|
||||||
<div key={k}>
|
return (
|
||||||
<div
|
<div className="mt-1 w-full">
|
||||||
draggable
|
{Object.keys(data).map((d: keyof APIObjectType, i) => (
|
||||||
className={" cursor-grab border-l-8 rounded-l-md"}
|
<DisclosureComponent
|
||||||
style={{ borderLeftColor: nodeColors[d]??nodeColors.unknown }}
|
key={i}
|
||||||
onDragStart={(event) =>
|
button={{
|
||||||
onDragStart(event, {
|
title: nodeNames[d] ?? nodeNames.unknown,
|
||||||
type: t,
|
Icon: nodeIcons[d] ?? nodeIcons.unknown,
|
||||||
node: data[d][t],
|
}}
|
||||||
})
|
>
|
||||||
}
|
<div className="p-2 flex flex-col gap-2">
|
||||||
>
|
{Object.keys(data[d]).map((t: string, k) => (
|
||||||
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
<div key={k}>
|
||||||
<span className="text-black dark:text-white w-36 truncate text-xs">{t}</span>
|
<div
|
||||||
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
draggable
|
||||||
</div>
|
className={" cursor-grab border-l-8 rounded-l-md"}
|
||||||
</div>
|
style={{
|
||||||
</div>
|
borderLeftColor: nodeColors[d] ?? nodeColors.unknown,
|
||||||
))}
|
}}
|
||||||
</div>
|
onDragStart={(event) =>
|
||||||
</DisclosureComponent>
|
onDragStart(event, {
|
||||||
))}
|
type: t,
|
||||||
</div>
|
node: data[d][t],
|
||||||
);
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
||||||
|
<span className="text-black dark:text-white w-36 truncate text-xs">
|
||||||
|
{t}
|
||||||
|
</span>
|
||||||
|
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{Object.keys(data[d]).length===0 && <div className="text-gray-400 text-center">Coming soon</div>}
|
||||||
|
</div>
|
||||||
|
</DisclosureComponent>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import {
|
||||||
ComputerDesktopIcon,
|
ComputerDesktopIcon,
|
||||||
Bars3CenterLeftIcon,
|
Bars3CenterLeftIcon,
|
||||||
GiftIcon,
|
GiftIcon,
|
||||||
PaperClipIcon,
|
|
||||||
QuestionMarkCircleIcon,
|
QuestionMarkCircleIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
|
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@
|
||||||
"multiline": false,
|
"multiline": false,
|
||||||
"password": false,
|
"password": false,
|
||||||
"name": "requests_wrapper",
|
"name": "requests_wrapper",
|
||||||
"type": "RequestsWrapper",
|
"type": "TextRequestsWrapper",
|
||||||
"list": false
|
"list": false
|
||||||
},
|
},
|
||||||
"_type": "OpenAPIToolkit"
|
"_type": "OpenAPIToolkit"
|
||||||
|
|
@ -154,7 +154,7 @@
|
||||||
"y": 532.9920887988924
|
"y": 532.9920887988924
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"type": "RequestsWrapper",
|
"type": "TextRequestsWrapper",
|
||||||
"node": {
|
"node": {
|
||||||
"template": {
|
"template": {
|
||||||
"headers": {
|
"headers": {
|
||||||
|
|
@ -178,11 +178,11 @@
|
||||||
"type": "ClientSession",
|
"type": "ClientSession",
|
||||||
"list": false
|
"list": false
|
||||||
},
|
},
|
||||||
"_type": "RequestsWrapper"
|
"_type": "TextRequestsWrapper"
|
||||||
},
|
},
|
||||||
"description": "Lightweight wrapper around requests library.",
|
"description": "Lightweight wrapper around requests library.",
|
||||||
"base_classes": [
|
"base_classes": [
|
||||||
"RequestsWrapper"
|
"TextRequestsWrapper"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"id": "dndnode_34",
|
"id": "dndnode_34",
|
||||||
|
|
@ -405,11 +405,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "dndnode_34",
|
"source": "dndnode_34",
|
||||||
"sourceHandle": "RequestsWrapper|dndnode_34|RequestsWrapper",
|
"sourceHandle": "TextRequestsWrapper|dndnode_34|TextRequestsWrapper",
|
||||||
"target": "dndnode_32",
|
"target": "dndnode_32",
|
||||||
"targetHandle": "RequestsWrapper|requests_wrapper|dndnode_32",
|
"targetHandle": "TextRequestsWrapper|requests_wrapper|dndnode_32",
|
||||||
"className": "animate-pulse",
|
"className": "animate-pulse",
|
||||||
"id": "reactflow__edge-dndnode_34RequestsWrapper|dndnode_34|RequestsWrapper-dndnode_32RequestsWrapper|requests_wrapper|dndnode_32",
|
"id": "reactflow__edge-dndnode_34RequestsWrapper|dndnode_34|TextRequestsWrapper-dndnode_32RequestsWrapper|requests_wrapper|dndnode_32",
|
||||||
"selected": false
|
"selected": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from langflow.interface.tools.constants import CUSTOM_TOOLS
|
from langflow.interface.tools.constants import CUSTOM_TOOLS
|
||||||
|
|
||||||
|
|
@ -20,7 +21,7 @@ import math
|
||||||
def square(x):
|
def square(x):
|
||||||
return x ** 2
|
return x ** 2
|
||||||
"""
|
"""
|
||||||
response1 = client.post("/validate", json={"code": code1})
|
response1 = client.post("/validate/code", json={"code": code1})
|
||||||
assert response1.status_code == 200
|
assert response1.status_code == 200
|
||||||
assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}}
|
assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}}
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ import non_existent_module
|
||||||
def square(x):
|
def square(x):
|
||||||
return x ** 2
|
return x ** 2
|
||||||
"""
|
"""
|
||||||
response2 = client.post("/validate", json={"code": code2})
|
response2 = client.post("/validate/code", json={"code": code2})
|
||||||
assert response2.status_code == 200
|
assert response2.status_code == 200
|
||||||
assert response2.json() == {
|
assert response2.json() == {
|
||||||
"imports": {"errors": ["No module named 'non_existent_module'"]},
|
"imports": {"errors": ["No module named 'non_existent_module'"]},
|
||||||
|
|
@ -45,7 +46,7 @@ import math
|
||||||
def square(x)
|
def square(x)
|
||||||
return x ** 2
|
return x ** 2
|
||||||
"""
|
"""
|
||||||
response3 = client.post("/validate", json={"code": code3})
|
response3 = client.post("/validate/code", json={"code": code3})
|
||||||
assert response3.status_code == 200
|
assert response3.status_code == 200
|
||||||
assert response3.json() == {
|
assert response3.json() == {
|
||||||
"imports": {"errors": []},
|
"imports": {"errors": []},
|
||||||
|
|
@ -53,11 +54,11 @@ def square(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Test case with invalid JSON payload
|
# Test case with invalid JSON payload
|
||||||
response4 = client.post("/validate", json={"invalid_key": code1})
|
response4 = client.post("/validate/code", json={"invalid_key": code1})
|
||||||
assert response4.status_code == 422
|
assert response4.status_code == 422
|
||||||
|
|
||||||
# Test case with an empty code string
|
# Test case with an empty code string
|
||||||
response5 = client.post("/validate", json={"code": ""})
|
response5 = client.post("/validate/code", json={"code": ""})
|
||||||
assert response5.status_code == 200
|
assert response5.status_code == 200
|
||||||
assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}}
|
assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}}
|
||||||
|
|
||||||
|
|
@ -68,9 +69,55 @@ import math
|
||||||
def square(x)
|
def square(x)
|
||||||
return x ** 2
|
return x ** 2
|
||||||
"""
|
"""
|
||||||
response6 = client.post("/validate", json={"code": code6})
|
response6 = client.post("/validate/code", json={"code": code6})
|
||||||
assert response6.status_code == 200
|
assert response6.status_code == 200
|
||||||
assert response6.json() == {
|
assert response6.json() == {
|
||||||
"imports": {"errors": []},
|
"imports": {"errors": []},
|
||||||
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
|
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VALID_PROMPT = """
|
||||||
|
I want you to act as a naming consultant for new companies.
|
||||||
|
|
||||||
|
Here are some examples of good company names:
|
||||||
|
|
||||||
|
- search engine, Google
|
||||||
|
- social media, Facebook
|
||||||
|
- video sharing, YouTube
|
||||||
|
|
||||||
|
The name should be short, catchy and easy to remember.
|
||||||
|
|
||||||
|
What is a good name for a company that makes {product}?
|
||||||
|
"""
|
||||||
|
|
||||||
|
INVALID_PROMPT = "This is an invalid prompt without any input variable."
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_prompt(client: TestClient):
|
||||||
|
response = client.post("/validate/prompt", json={"template": VALID_PROMPT})
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"input_variables": ["product"]}
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_prompt(client: TestClient):
|
||||||
|
response = client.post("/validate/prompt", json={"template": INVALID_PROMPT})
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {"input_variables": []}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"prompt,expected_input_variables",
|
||||||
|
[
|
||||||
|
("{color} is my favorite color.", ["color"]),
|
||||||
|
("The weather is {weather} today.", ["weather"]),
|
||||||
|
("This prompt has no variables.", []),
|
||||||
|
("{a}, {b}, and {c} are variables.", ["a", "b", "c"]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_various_prompts(client, prompt, expected_input_variables):
|
||||||
|
response = client.post("/validate/prompt", json={"template": prompt})
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {
|
||||||
|
"input_variables": expected_input_variables,
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue