Merge remote-tracking branch 'origin/dev' into feature/output_dropdown

This commit is contained in:
anovazzi1 2024-05-24 00:17:41 -03:00
commit bd4f4c8724
318 changed files with 14679 additions and 9306 deletions

View file

@ -4,6 +4,19 @@
# Do not commit .env file to git # Do not commit .env file to git
# Do not change .env.example file # Do not change .env.example file
# Config directory
# Directory where files, logs and database will be stored
# Example: LANGFLOW_CONFIG_DIR=~/.langflow
LANGFLOW_CONFIG_DIR=
# Save database in the config directory
# Values: true, false
# If false, the database will be saved in Langflow's root directory
# This means that the database will be deleted when Langflow is uninstalled
# and that the database will not be shared between different virtual environments
# Example: LANGFLOW_SAVE_DB_IN_CONFIG_DIR=true
LANGFLOW_SAVE_DB_IN_CONFIG_DIR=
# Database URL # Database URL
# Postgres example: LANGFLOW_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/langflow # Postgres example: LANGFLOW_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/langflow
# SQLite example: # SQLite example:
@ -56,7 +69,6 @@ LANGFLOW_REMOVE_API_KEYS=
# LANGFLOW_REDIS_CACHE_EXPIRE (default: 3600) # LANGFLOW_REDIS_CACHE_EXPIRE (default: 3600)
LANGFLOW_CACHE_TYPE= LANGFLOW_CACHE_TYPE=
# Set AUTO_LOGIN to false if you want to disable auto login # Set AUTO_LOGIN to false if you want to disable auto login
# and use the login form to login. LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD # and use the login form to login. LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD
# must be set if AUTO_LOGIN is set to false # must be set if AUTO_LOGIN is set to false

View file

@ -141,7 +141,7 @@ backend:
@echo 'Setting up the environment' @echo 'Setting up the environment'
@make setup_env @make setup_env
make install_backend make install_backend
@-kill -9 `lsof -t -i:7860` @-kill -9 $(lsof -t -i:7860)
ifdef login ifdef login
@echo "Running backend autologin is $(login)"; @echo "Running backend autologin is $(login)";
LANGFLOW_AUTO_LOGIN=$(login) poetry run uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --env-file .env --loop asyncio LANGFLOW_AUTO_LOGIN=$(login) poetry run uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload --env-file .env --loop asyncio

View file

@ -100,6 +100,12 @@ Alternatively, click the **"Open in Cloud Shell"** button below to launch Google
## Deploy on Railway ## Deploy on Railway
Use this template to deploy Langflow 1.0 Preview on Railway:
[![Deploy 1.0 Preview on Railway](https://railway.app/button.svg)](https://railway.app/template/UsJ1uB?referralCode=MnPSdg)
Or this one to deploy Langflow 0.6.x:
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/JMXEWp?referralCode=MnPSdg) [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
## Deploy on Render ## Deploy on Render

View file

@ -10,7 +10,7 @@
# PYTHON-BASE # PYTHON-BASE
# Sets up all our shared environment variables # Sets up all our shared environment variables
################################ ################################
FROM python:3.10-slim as python-base FROM python:3.12-slim as python-base
# python # python
ENV PYTHONUNBUFFERED=1 \ ENV PYTHONUNBUFFERED=1 \
@ -47,7 +47,7 @@ ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
# Used to build deps + create our virtual environment # Used to build deps + create our virtual environment
################################ ################################
FROM python-base as builder-base FROM python-base as builder-base
RUN
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends -y \ && apt-get install --no-install-recommends -y \
# deps for installing poetry # deps for installing poetry
@ -55,7 +55,12 @@ RUN apt-get update \
# deps for building python deps # deps for building python deps
build-essential \ build-essential \
# npm # npm
npm npm \
# gcc
gcc \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Now we need to copy the entire project into the image # Now we need to copy the entire project into the image
@ -70,15 +75,12 @@ RUN --mount=type=cache,target=/root/.cache \
RUN python -m pip install requests && cd ./scripts && python update_dependencies.py RUN python -m pip install requests && cd ./scripts && python update_dependencies.py
RUN $POETRY_HOME/bin/poetry lock RUN $POETRY_HOME/bin/poetry lock
RUN $POETRY_HOME/bin/poetry build RUN $POETRY_HOME/bin/poetry build
# Final stage for the application
FROM python-base as final
# Copy virtual environment and built .tar.gz from builder base # Copy virtual environment and built .tar.gz from builder base
RUN useradd -m -u 1000 user RUN useradd -m -u 1000 user
COPY --from=builder-base /app/dist/*.tar.gz ./ USER user
# Install the package from the .tar.gz # Install the package from the .tar.gz
RUN python -m pip install *.tar.gz --user RUN python -m pip install /app/dist/*.tar.gz
WORKDIR /app
ENTRYPOINT ["python", "-m", "langflow", "run"] ENTRYPOINT ["python", "-m", "langflow", "run"]
CMD ["--host", "0.0.0.0", "--port", "7860"] CMD ["--host", "0.0.0.0", "--port", "7860"]

View file

@ -10,7 +10,7 @@
# PYTHON-BASE # PYTHON-BASE
# Sets up all our shared environment variables # Sets up all our shared environment variables
################################ ################################
FROM python:3.10-slim as python-base FROM python:3.12-slim as python-base
# python # python
ENV PYTHONUNBUFFERED=1 \ ENV PYTHONUNBUFFERED=1 \
@ -55,6 +55,8 @@ RUN apt-get update \
build-essential \ build-essential \
# npm # npm
npm \ npm \
# gcc
gcc \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
@ -76,17 +78,14 @@ RUN cd src/frontend && npm run build
COPY src/backend ./src/backend COPY src/backend ./src/backend
RUN cp -r src/frontend/build src/backend/base/langflow/frontend RUN cp -r src/frontend/build src/backend/base/langflow/frontend
RUN rm -rf src/backend/base/dist RUN rm -rf src/backend/base/dist
RUN cd src/backend/base && $POETRY_HOME/bin/poetry build --format sdist RUN cd src/backend/base && $POETRY_HOME/bin/poetry build
# Final stage for the application
FROM python-base as final
# Copy virtual environment and built .tar.gz from builder base # Copy virtual environment and built .tar.gz from builder base
RUN useradd -m -u 1000 user RUN useradd -m -u 1000 user
COPY --from=builder-base /app/src/backend/base/dist/*.tar.gz ./ USER user
# Install the package from the .tar.gz # Install the package from the .tar.gz
RUN pip install *.tar.gz --user RUN python -m pip install /app/src/backend/base/dist/*.tar.gz
WORKDIR /app
ENTRYPOINT ["python", "-m", "langflow", "run"] ENTRYPOINT ["python", "-m", "langflow", "run"]
CMD ["--host", "0.0.0.0", "--port", "7860"] CMD ["--host", "0.0.0.0", "--port", "7860"]

View file

@ -0,0 +1,45 @@
import ZoomableImage from "/src/theme/ZoomableImage.js";
import Admonition from "@theme/Admonition";
# Global environment variables
Langflow 1.0 alpha includes the option to add **Global Environment Variables** for your application.
## Add a global variable to a project
In this example, you'll add the `openai_api_key` credential as a global environment variable to the **Basic Prompting** starter project.
For more information on the starter flow, see [Basic prompting](../starter-projects/basic-prompting.mdx).
1. From the Langflow dashboard, click **New Project**.
2. Select **Basic Prompting**.
The **Basic Prompting** flow is created.
3. To create an environment variable for the **OpenAI** component:
1. In the **OpenAI API Key** field, click the **Globe** button, and then click **Add New Variable**.
2. In the **Variable Name** field, enter `openai_api_key`.
3. In the **Value** field, paste your OpenAI API Key (`sk-...`).
4. For the variable **Type**, select **Credential**.
5. In the **Apply to Fields** field, select **OpenAI API Key** to apply this variable to all fields named **OpenAI API Key**.
6. Click **Save Variable**.
You now have a `openai_api_key` global environment variable for your Langflow project.
<Admonition type="tip">
You can also create global variables in **Settings** > **Variables and
Secrets**.
</Admonition>
<ZoomableImage
alt="Docusaurus themed image"
sources={{
light: "img/global-env.png",
dark: "img/global-env.png",
}}
style={{ width: "40%", margin: "20px auto" }}
/>
4. To view and manage your project's global environment variables, visit **Settings** > **Variables and Secrets**.
For more on variables in HuggingFace Spaces, see [Managing Secrets](https://huggingface.co/docs/hub/spaces-overview#managing-secrets).

View file

@ -0,0 +1,29 @@
import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
import ReactPlayer from "react-player";
import Admonition from "@theme/Admonition";
# Playground
In Langflow 1.0 alpha, the **Playground** replaces the **Interaction Panel**.
The **Playground** provides an interface for interacting with flows without opening them in the flow editor.
It even works for flows hosted on the Langflow store!
As long as you have a flow's environment variables set, you can run it by clicking the **Playground** button.
1. From your **Collections** page, click **Playground** in one of your flows.
The **Playground** window opens.
<ZoomableImage
alt="Docusaurus themed image"
sources={{
light: useBaseUrl("img/playground-chat.png"),
dark: useBaseUrl("img/playground-chat.png"),
}}
style={{ width: "50%", maxWidth: "600px", margin: "0 auto" }}
/>
2. Chat with your bot as you normally would, all without having to open the editor.

View file

@ -26,7 +26,8 @@ The `Astra DB` initializes a vector store using Astra DB from records. It create
- **Collection Indexing Policy:** Indexing policy for the collection. - **Collection Indexing Policy:** Indexing policy for the collection.
<Admonition type="note" title="Note"> <Admonition type="note" title="Note">
Ensure you configure the necessary Astra DB token and API endpoint before starting. Ensure you configure the necessary Astra DB token and API endpoint before
starting.
</Admonition> </Admonition>
--- ---
@ -96,6 +97,44 @@ For detailed documentation and integration guides, please refer to the [Chroma C
--- ---
### Couchbase
`Couchbase` builds a Couchbase vector store from records, streamlining the storage and retrieval of documents.
**Parameters:**
- **Embedding:** Model used by Couchbase.
- **Input:** Documents or records.
- **Couchbase Cluster Connection String:** Cluster Connection string.
- **Couchbase Cluster Username:** Cluster Username.
- **Couchbase Cluster Password:** Cluster Password.
- **Bucket Name:** Bucket identifier in Couchbase.
- **Scope Name:** Scope identifier in Couchbase.
- **Collection Name:** Collection identifier in Couchbase.
- **Index Name:** Index identifier.
For detailed documentation and integration guides, please refer to the [Couchbase Component Documentation](https://python.langchain.com/docs/integrations/vectorstores/couchbase).
---
### Couchbase Search
`CouchbaseSearch` leverages the Couchbase component to search for documents based on similarity metric.
**Parameters:**
- **Input:** Search query.
- **Embedding:** Model used in the Vector Store.
- **Couchbase Cluster Connection String:** Cluster Connection string.
- **Couchbase Cluster Username:** Cluster Username.
- **Couchbase Cluster Password:** Cluster Password.
- **Bucket Name:** Bucket identifier.
- **Scope Name:** Scope identifier.
- **Collection Name:** Collection identifier in Couchbase.
- **Index Name:** Index identifier.
---
### FAISS ### FAISS
The `FAISS` component manages document ingestion into a FAISS Vector Store, optimizing document indexing and retrieval. The `FAISS` component manages document ingestion into a FAISS Vector Store, optimizing document indexing and retrieval.
@ -278,7 +317,8 @@ For more details, see the [PGVector Component Documentation](https://python.lang
For detailed documentation, refer to the [Redis Documentation](https://python.langchain.com/docs/integrations/vectorstores/redis). For detailed documentation, refer to the [Redis Documentation](https://python.langchain.com/docs/integrations/vectorstores/redis).
<Admonition type="note" title="Note"> <Admonition type="note" title="Note">
Ensure the Redis server URL and index name are configured correctly. Provide a schema if no documents are available. Ensure the Redis server URL and index name are configured correctly. Provide a
schema if no documents are available.
</Admonition> </Admonition>
--- ---
@ -389,7 +429,8 @@ For more information, consult the [Vectara Component Documentation](https://pyth
For more details, see the [Weaviate Component Documentation](https://python.langchain.com/docs/integrations/vectorstores/weaviate). For more details, see the [Weaviate Component Documentation](https://python.langchain.com/docs/integrations/vectorstores/weaviate).
<Admonition type="note" title="Note"> <Admonition type="note" title="Note">
Ensure Weaviate instance is running and accessible. Verify API key, index name, text key, and attributes are set correctly. Ensure Weaviate instance is running and accessible. Verify API key, index
name, text key, and attributes are set correctly.
</Admonition> </Admonition>
--- ---

View file

@ -16,7 +16,7 @@ You have a new document loader called **MyCustomDocumentLoader** and it would lo
6. Add the dependency to [/documentloaders/\_\_init\_\_.py](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/components/documentloaders/__init__.py) as `from .MyCustomDocumentLoader import MyCustomDocumentLoader`. 6. Add the dependency to [/documentloaders/\_\_init\_\_.py](https://github.com/langflow-ai/langflow/blob/dev/src/backend/base/langflow/components/documentloaders/__init__.py) as `from .MyCustomDocumentLoader import MyCustomDocumentLoader`.
7. Add any new dependencies to the outer [pyproject.toml](https://github.com/langflow-ai/langflow/blob/dev/pyproject.toml#L27) file. 7. Add any new dependencies to the outer [pyproject.toml](https://github.com/langflow-ai/langflow/blob/dev/pyproject.toml#L27) file.
8. Submit documentation for your component. For this example, you'd submit documentation to the [loaders page](https://github.com/langflow-ai/langflow/blob/dev/docs/docs/components/loaders.mdx). 8. Submit documentation for your component. For this example, you'd submit documentation to the [loaders page](https://github.com/langflow-ai/langflow/blob/dev/docs/docs/components/loaders.mdx).
8. Submit your changes as a pull request. The Langflow team will have a look, suggest changes, and add your component to Langflow. 9. Submit your changes as a pull request. The Langflow team will have a look, suggest changes, and add your component to Langflow.
## User Sharing ## User Sharing
@ -27,21 +27,19 @@ If so, you can share your component on the Langflow store.
1. [Register at the Langflow store](https://www.langflow.store/login/). 1. [Register at the Langflow store](https://www.langflow.store/login/).
2. Undergo pre-validation before receiving an API key. 2. Undergo pre-validation before receiving an API key.
3. To deploy your amazing component directly to the Langflow store, without it being merged into the main source code, navigate to your flow, and then click **Share**. 3. To deploy your amazing component directly to the Langflow store, without it being merged into the main source code, navigate to your flow, and then click **Share**.
The share window appears: The share window appears:
<ZoomableImage <ZoomableImage
alt="Docusaurus themed image" alt="Docusaurus themed image"
sources={{ sources={{
light: "img/add-component-to-store.png", light: "img/add-component-to-store.png",
dark: "img/add-component-to-store.png", dark: "img/add-component-to-store.png",
}} }}
style={{ width: "50%", margin: "20px auto" }} style={{ width: "50%", margin: "20px auto" }}
/> />
5. Choose whether you want to flow to be public or private. 5. Choose whether you want to flow to be public or private.
You can also **Export** your flow as a JSON file from this window. You can also **Export** your flow as a JSON file from this window.
When you're ready to share the flow, click **Share Flow**. When you're ready to share the flow, click **Share Flow**.
You should see a **Flow shared successfully** popup. You should see a **Flow shared successfully** popup.
6. To confirm, navigate to the **Langflow Store** and filter results by **Created By Me**. You should see your new flow on the **Langflow Store**. 6. To confirm, navigate to the **Langflow Store** and filter results by **Created By Me**. You should see your new flow on the **Langflow Store**.

View file

@ -143,7 +143,7 @@ The RAG flow is a bit more complex. It consists of:
style={{ width: "80%", margin: "20px auto" }} style={{ width: "80%", margin: "20px auto" }}
/> />
To run it all we have to do is click on the ⚡ _Run_ button and start interacting with your RAG application. To run it all we have to do is click on the 🤖 _Playground_ button and start interacting with your RAG application.
<ZoomableImage <ZoomableImage
alt="Docusaurus themed image" alt="Docusaurus themed image"

View file

@ -4,9 +4,7 @@ module.exports = {
type: "category", type: "category",
label: "What's New?", label: "What's New?",
collapsed: false, collapsed: false,
items: [ items: ["whats-new/a-new-chapter-langflow"],
"whats-new/a-new-chapter-langflow"
],
}, },
{ {
type: "category", type: "category",
@ -17,7 +15,7 @@ module.exports = {
"getting-started/install-langflow", "getting-started/install-langflow",
"getting-started/quickstart", "getting-started/quickstart",
"getting-started/huggingface-spaces", "getting-started/huggingface-spaces",
"getting-started/new-to-llms" "getting-started/new-to-llms",
], ],
}, },
{ {
@ -29,7 +27,7 @@ module.exports = {
"starter-projects/blog-writer", "starter-projects/blog-writer",
"starter-projects/document-qa", "starter-projects/document-qa",
"starter-projects/memory-chatbot", "starter-projects/memory-chatbot",
"starter-projects/vector-store-rag" "starter-projects/vector-store-rag",
], ],
}, },
{ {
@ -40,10 +38,12 @@ module.exports = {
"administration/login", "administration/login",
"administration/api", "administration/api",
"administration/cli", "administration/cli",
"administration/playground",
"administration/global-env",
"administration/components", "administration/components",
"administration/collection", "administration/collection",
"administration/prompt-customization", "administration/prompt-customization",
"administration/langfuse_integration" "administration/langfuse_integration",
], ],
}, },
{ {
@ -58,7 +58,7 @@ module.exports = {
"components/helpers", "components/helpers",
"components/vector-stores", "components/vector-stores",
"components/embeddings", "components/embeddings",
"components/custom" "components/custom",
], ],
}, },
{ {
@ -74,7 +74,7 @@ module.exports = {
"components/retrievers", "components/retrievers",
"components/text-splitters", "components/text-splitters",
"components/toolkits", "components/toolkits",
"components/tools" "components/tools",
], ],
}, },
{ {
@ -88,7 +88,7 @@ module.exports = {
"examples/csv-loader", "examples/csv-loader",
"examples/searchapi-tool", "examples/searchapi-tool",
"examples/serp-api-tool", "examples/serp-api-tool",
"examples/python-function" "examples/python-function",
], ],
}, },
{ {
@ -101,8 +101,8 @@ module.exports = {
"migration/inputs-and-outputs", "migration/inputs-and-outputs",
"migration/text-and-record", "migration/text-and-record",
"migration/compatibility", "migration/compatibility",
"migration/global-variables" "migration/global-variables",
] ],
}, },
{ {
type: "category", type: "category",
@ -111,16 +111,14 @@ module.exports = {
items: [ items: [
"tutorials/chatprompttemplate_guide", "tutorials/chatprompttemplate_guide",
"tutorials/loading_document", "tutorials/loading_document",
"tutorials/rag-with-astradb" "tutorials/rag-with-astradb",
], ],
}, },
{ {
type: "category", type: "category",
label: "Deployment", label: "Deployment",
collapsed: true, collapsed: true,
items: [ items: ["deployment/gcp-deployment"],
"deployment/gcp-deployment"
],
}, },
{ {
type: "category", type: "category",
@ -130,7 +128,7 @@ module.exports = {
"contributing/how-contribute", "contributing/how-contribute",
"contributing/github-issues", "contributing/github-issues",
"contributing/community", "contributing/community",
"contributing/contribute-component" "contributing/contribute-component",
], ],
}, },
], ],

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before After
Before After

BIN
docs/static/img/global-env.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
docs/static/img/playground-chat.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

File diff suppressed because one or more lines are too long

2047
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "1.0.0a33" version = "1.0.0a37"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"] authors = ["Langflow <contact@langflow.org>"]
maintainers = [ maintainers = [
@ -29,21 +29,20 @@ python = ">=3.10,<3.13"
langflow-base = { path = "./src/backend/base", develop = true } langflow-base = { path = "./src/backend/base", develop = true }
beautifulsoup4 = "^4.12.2" beautifulsoup4 = "^4.12.2"
google-search-results = "^2.4.1" google-search-results = "^2.4.1"
google-api-python-client = "^2.118.0" google-api-python-client = "^2.130.0"
huggingface-hub = { version = "^0.20.0", extras = ["inference"] } huggingface-hub = { version = "^0.20.0", extras = ["inference"] }
llama-cpp-python = { version = "~0.2.0", optional = true } llama-cpp-python = { version = "~0.2.0", optional = true }
networkx = "^3.1" networkx = "^3.1"
pysrt = "^1.1.2" fake-useragent = "^1.5.0"
fake-useragent = "^1.4.0"
psycopg2-binary = "^2.9.6" psycopg2-binary = "^2.9.6"
pyarrow = "^14.0.0" pyarrow = "^14.0.0"
wikipedia = "^1.4.0" wikipedia = "^1.4.0"
qdrant-client = "^1.7.0" qdrant-client = "^1.9.0"
weaviate-client = "*" weaviate-client = "*"
sentence-transformers = { version = "^2.3.1", optional = true } sentence-transformers = { version = "^2.3.1", optional = true }
ctransformers = { version = "^0.2.10", optional = true } ctransformers = { version = "^0.2.10", optional = true }
cohere = "^5.1.7" cohere = "^5.5.3"
faiss-cpu = "^1.7.4" faiss-cpu = "^1.8.0"
types-cachetools = "^5.3.0.5" types-cachetools = "^5.3.0.5"
pinecone-client = "^3.0.3" pinecone-client = "^3.0.3"
pymongo = "^4.6.0" pymongo = "^4.6.0"
@ -57,10 +56,9 @@ redis = { version = "^5.0.1", optional = true }
flower = { version = "^2.0.0", optional = true } flower = { version = "^2.0.0", optional = true }
metaphor-python = "^0.1.11" metaphor-python = "^0.1.11"
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" } pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
langfuse = "^2.9.0" langfuse = "^2.33.0"
metal-sdk = "^2.5.0" metal-sdk = "^2.5.0"
markupsafe = "^2.1.3" markupsafe = "^2.1.3"
extract-msg = "^0.47.0"
# jq is not available for windows # jq is not available for windows
boto3 = "^1.34.0" boto3 = "^1.34.0"
numexpr = "^2.8.6" numexpr = "^2.8.6"
@ -71,41 +69,41 @@ langchain-google-genai = "^1.0.1"
langchain-cohere = "^0.1.0rc1" langchain-cohere = "^0.1.0rc1"
elasticsearch = "^8.12.0" elasticsearch = "^8.12.0"
pytube = "^15.0.0" pytube = "^15.0.0"
llama-index = "^0.10.13"
# unstructured = { extras = ["md"], version = "^0.12.4" }
dspy-ai = "^2.4.0" dspy-ai = "^2.4.0"
html2text = "^2024.2.26" assemblyai = "^0.26.0"
assemblyai = "^0.23.1" litellm = "^1.38.0"
litellm = "^1.34.22" chromadb = "^0.5.0"
chromadb = "^0.4.24"
langchain-anthropic = "^0.1.6" langchain-anthropic = "^0.1.6"
langchain-astradb = "^0.1.0" langchain-astradb = "^0.3.0"
langchain-openai = "^0.1.1" langchain-openai = "^0.1.1"
zep-python = { version = "^2.0.0rc5", allow-prereleases = true } zep-python = { version = "^2.0.0rc5", allow-prereleases = true }
langchain-google-vertexai = "^1.0.3" langchain-google-vertexai = "^1.0.3"
langchain-groq = "^0.1.3" langchain-groq = "^0.1.3"
langchain-pinecone = "^0.1.0" langchain-pinecone = "^0.1.0"
langchain-mistralai = "^0.1.6" langchain-mistralai = "^0.1.6"
couchbase = "^4.2.1"
youtube-transcript-api = "^0.6.2"
markdown = "^3.6"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
types-redis = "^4.6.0.5" types-redis = "^4.6.0.5"
ipykernel = "^6.29.0" ipykernel = "^6.29.0"
mypy = "^1.9.0" mypy = "^1.10.0"
ruff = "^0.3.5" ruff = "^0.4.5"
httpx = "*" httpx = "*"
pytest = "^8.1.0" pytest = "^8.2.0"
types-requests = "^2.31.0" types-requests = "^2.32.0"
requests = "^2.31.0" requests = "^2.32.0"
pytest-cov = "^4.1.0" pytest-cov = "^5.0.0"
pandas-stubs = "^2.1.4.231227" pandas-stubs = "^2.1.4.231227"
types-pillow = "^10.2.0.20240213" types-pillow = "^10.2.0.20240213"
types-pyyaml = "^6.0.12.8" types-pyyaml = "^6.0.12.8"
types-python-jose = "^3.3.4.8" types-python-jose = "^3.3.4.8"
types-passlib = "^1.7.7.13" types-passlib = "^1.7.7.13"
locust = "^2.23.1" locust = "^2.23.1"
pytest-mock = "^3.12.0" pytest-mock = "^3.14.0"
pytest-xdist = "^3.5.0" pytest-xdist = "^3.6.0"
types-pywin32 = "^306.0.0.4" types-pywin32 = "^306.0.0.4"
types-google-cloud-ndb = "^2.2.0.0" types-google-cloud-ndb = "^2.2.0.0"
pytest-sugar = "^1.0.0" pytest-sugar = "^1.0.0"
@ -134,7 +132,7 @@ ignore-regex = '.*(Stati Uniti|Tense=Pres).*'
[tool.pytest.ini_options] [tool.pytest.ini_options]
minversion = "6.0" minversion = "6.0"
addopts = "-ra" addopts = "-ra -n auto"
testpaths = ["tests", "integration"] testpaths = ["tests", "integration"]
console_output_style = "progress" console_output_style = "progress"
filterwarnings = ["ignore::DeprecationWarning"] filterwarnings = ["ignore::DeprecationWarning"]

View file

@ -1,65 +1,85 @@
import { Construct } from 'constructs'; import { Construct } from "constructs";
import * as ec2 from 'aws-cdk-lib/aws-ec2' import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as rds from "aws-cdk-lib/aws-rds"; import * as rds from "aws-cdk-lib/aws-rds";
import * as cdk from 'aws-cdk-lib'; import * as cdk from "aws-cdk-lib";
interface RdsProps { interface RdsProps {
vpc: ec2.Vpc vpc: ec2.Vpc;
dbSG:ec2.SecurityGroup dbSG: ec2.SecurityGroup;
} }
export class Rds extends Construct{ export class Rds extends Construct {
readonly rdsCluster: rds.DatabaseCluster readonly rdsCluster: rds.DatabaseCluster;
constructor(scope: Construct, id:string, props: RdsProps){ constructor(scope: Construct, id: string, props: RdsProps) {
super(scope, id); super(scope, id);
const {vpc, dbSG} = props const { vpc, dbSG } = props;
const instanceType = ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE4_GRAVITON, ec2.InstanceSize.MEDIUM) const instanceType = ec2.InstanceType.of(
ec2.InstanceClass.BURSTABLE4_GRAVITON,
ec2.InstanceSize.MEDIUM,
);
// RDSのパスワードを自動生成してSecrets Managerに格納 // RDSのパスワードを自動生成してSecrets Managerに格納
const rdsCredentials = rds.Credentials.fromGeneratedSecret('db_user',{ const rdsCredentials = rds.Credentials.fromGeneratedSecret("db_user", {
secretName: 'langflow-DbSecret', secretName: "langflow-DbSecret",
}) });
// DB クラスターのパラメータグループ作成 // DB クラスターのパラメータグループ作成
const clusterParameterGroup = new rds.ParameterGroup(scope, 'ClusterParameterGroup',{ const clusterParameterGroup = new rds.ParameterGroup(
scope,
"ClusterParameterGroup",
{
engine: rds.DatabaseClusterEngine.auroraMysql({ engine: rds.DatabaseClusterEngine.auroraMysql({
version: rds.AuroraMysqlEngineVersion.VER_3_02_0 version: rds.AuroraMysqlEngineVersion.of(
"8.0.mysql_aurora.3.05.2",
"8.0",
),
}), }),
description: 'for-langflow', description: "for-langflow",
}) },
clusterParameterGroup.bindToCluster({}) );
clusterParameterGroup.bindToCluster({});
// DB インスタンスのパラメタグループ作成 // DB インスタンスのパラメタグループ作成
const instanceParameterGroup = new rds.ParameterGroup(scope, 'InstanceParameterGroup',{ const instanceParameterGroup = new rds.ParameterGroup(
scope,
"InstanceParameterGroup",
{
engine: rds.DatabaseClusterEngine.auroraMysql({ engine: rds.DatabaseClusterEngine.auroraMysql({
version: rds.AuroraMysqlEngineVersion.VER_3_02_0, version: rds.AuroraMysqlEngineVersion.of(
"8.0.mysql_aurora.3.05.2",
"8.0",
),
}), }),
description: 'for-langflow', description: "for-langflow",
}) },
instanceParameterGroup.bindToInstance({}) );
instanceParameterGroup.bindToInstance({});
this.rdsCluster = new rds.DatabaseCluster(scope, 'LangflowDbCluster', { this.rdsCluster = new rds.DatabaseCluster(scope, "LangflowDbCluster", {
engine: rds.DatabaseClusterEngine.auroraMysql({ engine: rds.DatabaseClusterEngine.auroraMysql({
version: rds.AuroraMysqlEngineVersion.VER_3_02_0, version: rds.AuroraMysqlEngineVersion.of(
"8.0.mysql_aurora.3.05.2",
"8.0",
),
}), }),
storageEncrypted: true, storageEncrypted: true,
credentials: rdsCredentials, credentials: rdsCredentials,
instanceIdentifierBase: 'langflow-instance', instanceIdentifierBase: "langflow-instance",
vpc:vpc, vpc: vpc,
vpcSubnets:vpc.selectSubnets({ vpcSubnets: vpc.selectSubnets({
subnetGroupName: 'langflow-Isolated', subnetGroupName: "langflow-Isolated",
}), }),
securityGroups:[dbSG], securityGroups: [dbSG],
writer: rds.ClusterInstance.provisioned("WriterInstance", { writer: rds.ClusterInstance.provisioned("WriterInstance", {
instanceType: instanceType, instanceType: instanceType,
enablePerformanceInsights: true, enablePerformanceInsights: true,
parameterGroup:instanceParameterGroup, parameterGroup: instanceParameterGroup,
}), }),
// 2台目以降はreaders:で設定 // 2台目以降はreaders:で設定
parameterGroup: clusterParameterGroup, parameterGroup: clusterParameterGroup,
defaultDatabaseName: 'langflow', defaultDatabaseName: "langflow",
}) });
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -11,21 +11,21 @@
"cdk": "cdk" "cdk": "cdk"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.1", "@types/jest": "^29.5.12",
"@types/node": "20.1.7", "@types/node": "^20.12.12",
"aws-cdk": "^2.86.0", "aws-cdk": "^2.141.0",
"jest": "^29.5.0", "jest": "^29.7.0",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.2",
"ts-node": "^10.9.1", "ts-node": "^10.9.2",
"typescript": "~5.1.3" "typescript": "^5.4.5"
}, },
"dependencies": { "dependencies": {
"@aws-solutions-constructs/aws-cloudfront-s3": "^2.49.0", "@aws-solutions-constructs/aws-cloudfront-s3": "^2.57.0",
"aws-cdk-lib": "^2.124.0", "aws-cdk-lib": "^2.141.0",
"cdk-ecr-deployment": "^2.5.30", "cdk-ecr-deployment": "^3.0.55",
"constructs": "^10.0.0", "constructs": "^10.3.0",
"deploy-time-build": "^0.3.12", "deploy-time-build": "^0.3.21",
"dotenv": "^16.3.1", "dotenv": "^16.4.5",
"source-map-support": "^0.5.21" "source-map-support": "^0.5.21"
} }
} }

View file

@ -48,8 +48,10 @@ apt -y upgrade
# Install Python 3 pip, Langflow, and Nginx # Install Python 3 pip, Langflow, and Nginx
apt -y install python3-pip apt -y install python3-pip
pip install langflow pip3 install pip -U
langflow --host 0.0.0.0 --port 7860 apt -y update
pip3 install langflow
langflow run --host 0.0.0.0 --port 7860
EOF EOF
) )

View file

@ -131,3 +131,4 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
*.db

View file

@ -2,9 +2,9 @@ import platform
import socket import socket
import sys import sys
import time import time
import warnings
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
import warnings
import click import click
import httpx import httpx
@ -17,8 +17,10 @@ from rich import print as rprint
from rich.console import Console from rich.console import Console
from rich.panel import Panel from rich.panel import Panel
from rich.table import Table from rich.table import Table
from sqlmodel import select
from langflow.main import setup_app from langflow.main import setup_app
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
from langflow.services.database.utils import session_getter from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service from langflow.services.deps import get_db_service
from langflow.services.utils import initialize_services from langflow.services.utils import initialize_services
@ -432,17 +434,57 @@ def superuser(
# Verify that the superuser was created # Verify that the superuser was created
from langflow.services.database.models.user.model import User from langflow.services.database.models.user.model import User
user: User = session.query(User).filter(User.username == username).first() user: User = session.exec(select(User).where(User.username == username)).first()
if user is None or not user.is_superuser: if user is None or not user.is_superuser:
typer.echo("Superuser creation failed.") typer.echo("Superuser creation failed.")
return return
# Now create the first folder for the user
result = create_default_folder_if_it_doesnt_exist(session, user.id)
if result:
typer.echo("Default folder created successfully.")
else:
raise RuntimeError("Could not create default folder.")
typer.echo("Superuser created successfully.") typer.echo("Superuser created successfully.")
else: else:
typer.echo("Superuser creation failed.") typer.echo("Superuser creation failed.")
# command to copy the langflow database from the cache to the current directory
# because now the database is stored per installation
@app.command()
def copy_db():
"""
Copy the database files to the current directory.
This function copies the 'langflow.db' and 'langflow-pre.db' files from the cache directory to the current directory.
If the files exist in the cache directory, they will be copied to the same directory as this script (__main__.py).
Returns:
None
"""
import shutil
from platformdirs import user_cache_dir
cache_dir = Path(user_cache_dir("langflow"))
db_path = cache_dir / "langflow.db"
pre_db_path = cache_dir / "langflow-pre.db"
# It should be copied to the current directory
# this file is __main__.py and it should be in the same directory as the database
destination_folder = Path(__file__).parent
if db_path.exists():
shutil.copy(db_path, destination_folder)
typer.echo(f"Database copied to {destination_folder}")
else:
typer.echo("Database not found in the cache directory.")
if pre_db_path.exists():
shutil.copy(pre_db_path, destination_folder)
typer.echo(f"Pre-release database copied to {destination_folder}")
else:
typer.echo("Pre-release database not found in the cache directory.")
@app.command() @app.command()
def migration( def migration(
test: bool = typer.Option(True, help="Run migrations in test mode."), test: bool = typer.Option(True, help="Run migrations in test mode."),

View file

@ -0,0 +1,78 @@
"""Add Folder table
Revision ID: 012fb73ac359
Revises: c153816fd85f
Create Date: 2024-05-07 12:52:16.954691
"""
from typing import Sequence, Union
import sqlalchemy as sa
import sqlmodel
from alembic import op
from sqlalchemy.engine.reflection import Inspector
# revision identifiers, used by Alembic.
revision: str = "012fb73ac359"
down_revision: Union[str, None] = "c153816fd85f"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "folder" not in table_names:
op.create_table(
"folder",
sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
sa.Column("parent_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
sa.ForeignKeyConstraint(
["parent_id"],
["folder.id"],
),
sa.ForeignKeyConstraint(
["user_id"],
["user.id"],
),
sa.PrimaryKeyConstraint("id"),
)
indexes = inspector.get_indexes("folder")
if "ix_folder_name" not in [index["name"] for index in indexes]:
with op.batch_alter_table("folder", schema=None) as batch_op:
batch_op.create_index(batch_op.f("ix_folder_name"), ["name"], unique=False)
if "folder_id" not in inspector.get_columns("flow"):
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.add_column(sa.Column("folder_id", sqlmodel.sql.sqltypes.GUID(), nullable=True))
batch_op.create_foreign_key("flow_folder_id_fkey", "folder", ["folder_id"], ["id"])
batch_op.drop_column("folder")
# ### end Alembic commands ###
def downgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
table_names = inspector.get_table_names()
# ### commands auto generated by Alembic - please adjust! ###
if "folder_id" in inspector.get_columns("flow"):
with op.batch_alter_table("flow", schema=None) as batch_op:
batch_op.add_column(sa.Column("folder", sa.VARCHAR(), nullable=True))
batch_op.drop_constraint("flow_folder_id_fkey", type_="foreignkey")
batch_op.drop_column("folder_id")
indexes = inspector.get_indexes("folder")
if "ix_folder_name" in [index["name"] for index in indexes]:
with op.batch_alter_table("folder", schema=None) as batch_op:
batch_op.drop_index(batch_op.f("ix_folder_name"))
if "folder" in table_names:
op.drop_table("folder")
# ### end Alembic commands ###

View file

@ -0,0 +1,43 @@
"""Add missing index
Revision ID: 29fe8f1f806b
Revises: 012fb73ac359
Create Date: 2024-05-21 09:23:48.772367
"""
from typing import Sequence, Union
from alembic import op
from sqlalchemy.engine.reflection import Inspector
revision: str = "29fe8f1f806b"
down_revision: Union[str, None] = "012fb73ac359"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
# ### commands auto generated by Alembic - please adjust! ###
indexes = inspector.get_indexes("flow")
with op.batch_alter_table("flow", schema=None) as batch_op:
indexes_names = [index["name"] for index in indexes]
if "ix_flow_folder_id" not in indexes_names:
batch_op.create_index(batch_op.f("ix_flow_folder_id"), ["folder_id"], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
conn = op.get_bind()
inspector = Inspector.from_engine(conn) # type: ignore
# ### commands auto generated by Alembic - please adjust! ###
indexes = inspector.get_indexes("flow")
with op.batch_alter_table("flow", schema=None) as batch_op:
indexes_names = [index["name"] for index in indexes]
if "ix_flow_folder_id" in indexes_names:
batch_op.drop_index(batch_op.f("ix_flow_folder_id"))
# ### end Alembic commands ###

View file

@ -13,6 +13,7 @@ from langflow.api.v1 import (
users_router, users_router,
validate_router, validate_router,
variables_router, variables_router,
folders_router,
) )
router = APIRouter( router = APIRouter(
@ -29,3 +30,4 @@ router.include_router(login_router)
router.include_router(variables_router) router.include_router(variables_router)
router.include_router(files_router) router.include_router(files_router)
router.include_router(monitor_router) router.include_router(monitor_router)
router.include_router(folders_router)

View file

@ -140,7 +140,10 @@ def get_file_path_value(file_path):
# If the path is not in the cache dir, return empty string # If the path is not in the cache dir, return empty string
# This is to prevent access to files outside the cache dir # This is to prevent access to files outside the cache dir
# If the path is not a file, return empty string # If the path is not a file, return empty string
if not path.exists() or not str(path).startswith(user_cache_dir("langflow", "langflow")): if not str(path).startswith(user_cache_dir("langflow", "langflow")):
return ""
if not path.exists():
return "" return ""
return file_path return file_path

View file

@ -9,6 +9,7 @@ from langflow.api.v1.store import router as store_router
from langflow.api.v1.users import router as users_router from langflow.api.v1.users import router as users_router
from langflow.api.v1.validate import router as validate_router from langflow.api.v1.validate import router as validate_router
from langflow.api.v1.variable import router as variables_router from langflow.api.v1.variable import router as variables_router
from langflow.api.v1.folders import router as folders_router
__all__ = [ __all__ = [
"chat_router", "chat_router",
@ -22,4 +23,5 @@ __all__ = [
"variables_router", "variables_router",
"monitor_router", "monitor_router",
"files_router", "files_router",
"folders_router",
] ]

View file

@ -1,13 +1,12 @@
from typing import TYPE_CHECKING, Any, Dict, List, Optional from typing import TYPE_CHECKING, Any, Dict, List, Optional
from uuid import UUID from uuid import UUID
from langchain.schema import AgentAction, AgentFinish
from langchain_core.callbacks.base import AsyncCallbackHandler from langchain_core.callbacks.base import AsyncCallbackHandler
from loguru import logger from loguru import logger
from langflow.api.v1.schemas import ChatResponse, PromptResponse from langflow.api.v1.schemas import ChatResponse, PromptResponse
from langflow.services.deps import get_chat_service, get_socket_service from langflow.services.deps import get_chat_service, get_socket_service
from langflow.utils.util import remove_ansi_escape_codes from langflow.utils.util import remove_ansi_escape_codes
from langchain_core.agents import AgentAction, AgentFinish
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.socket.service import SocketIOService from langflow.services.socket.service import SocketIOService

View file

@ -29,7 +29,7 @@ from langflow.services.deps import get_chat_service, get_session, get_session_se
from langflow.services.monitor.utils import log_vertex_build from langflow.services.monitor.utils import log_vertex_build
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.graph.vertex.types import ChatVertex from langflow.graph.vertex.types import InterfaceVertex
from langflow.services.session.service import SessionService from langflow.services.session.service import SessionService
router = APIRouter(tags=["Chat"]) router = APIRouter(tags=["Chat"])
@ -53,7 +53,7 @@ async def try_running_celery_task(vertex, user_id):
@router.post("/build/{flow_id}/vertices", response_model=VerticesOrderResponse) @router.post("/build/{flow_id}/vertices", response_model=VerticesOrderResponse)
async def retrieve_vertices_order( async def retrieve_vertices_order(
flow_id: str, flow_id: uuid.UUID,
data: Optional[Annotated[Optional[FlowDataRequest], Body(embed=True)]] = None, data: Optional[Annotated[Optional[FlowDataRequest], Body(embed=True)]] = None,
stop_component_id: Optional[str] = None, stop_component_id: Optional[str] = None,
start_component_id: Optional[str] = None, start_component_id: Optional[str] = None,
@ -78,12 +78,13 @@ async def retrieve_vertices_order(
HTTPException: If there is an error checking the build status. HTTPException: If there is an error checking the build status.
""" """
try: try:
flow_id_str = str(flow_id)
# First, we need to check if the flow_id is in the cache # First, we need to check if the flow_id is in the cache
if not data: if not data:
graph = await build_and_cache_graph_from_db(flow_id=flow_id, session=session, chat_service=chat_service) graph = await build_and_cache_graph_from_db(flow_id=flow_id_str, session=session, chat_service=chat_service)
else: else:
graph = await build_and_cache_graph_from_data( graph = await build_and_cache_graph_from_data(
flow_id=flow_id, graph_data=data.model_dump(), chat_service=chat_service flow_id=flow_id_str, graph_data=data.model_dump(), chat_service=chat_service
) )
graph.validate_stream() graph.validate_stream()
if stop_component_id or start_component_id: if stop_component_id or start_component_id:
@ -119,7 +120,7 @@ async def retrieve_vertices_order(
@router.post("/build/{flow_id}/vertices/{vertex_id}") @router.post("/build/{flow_id}/vertices/{vertex_id}")
async def build_vertex( async def build_vertex(
flow_id: str, flow_id: uuid.UUID,
vertex_id: str, vertex_id: str,
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,
inputs: Annotated[Optional[InputValueRequest], Body(embed=True)] = None, inputs: Annotated[Optional[InputValueRequest], Body(embed=True)] = None,
@ -143,27 +144,25 @@ async def build_vertex(
HTTPException: If there is an error building the vertex. HTTPException: If there is an error building the vertex.
""" """
flow_id_str = str(flow_id)
start_time = time.perf_counter()
next_runnable_vertices = [] next_runnable_vertices = []
top_level_vertices = [] top_level_vertices = []
try: try:
start_time = time.perf_counter() start_time = time.perf_counter()
cache = await chat_service.get_cache(flow_id) cache = await chat_service.get_cache(flow_id_str)
if not cache: if not cache:
# If there's no cache # If there's no cache
logger.warning(f"No cache found for {flow_id}. Building graph starting at {vertex_id}") logger.warning(f"No cache found for {flow_id_str}. Building graph starting at {vertex_id}")
graph = await build_and_cache_graph_from_db( graph = await build_and_cache_graph_from_db(
flow_id=flow_id, session=next(get_session()), chat_service=chat_service flow_id=flow_id_str, session=next(get_session()), chat_service=chat_service
) )
else: else:
graph = cache.get("result") graph = cache.get("result")
result_data_response = ResultDataResponse(results={})
duration = ""
vertex = graph.get_vertex(vertex_id) vertex = graph.get_vertex(vertex_id)
try: try:
lock = chat_service._cache_locks[flow_id] lock = chat_service._cache_locks[flow_id_str]
set_cache_coro = partial(chat_service.set_cache, flow_id=flow_id) set_cache_coro = partial(chat_service.set_cache, flow_id=flow_id_str)
( (
next_runnable_vertices, next_runnable_vertices,
top_level_vertices, top_level_vertices,
@ -189,13 +188,13 @@ async def build_vertex(
artifacts = {} artifacts = {}
# If there's an error building the vertex # If there's an error building the vertex
# we need to clear the cache # we need to clear the cache
await chat_service.clear_cache(flow_id) await chat_service.clear_cache(flow_id_str)
# Log the vertex build # Log the vertex build
if not vertex.will_stream: if not vertex.will_stream:
background_tasks.add_task( background_tasks.add_task(
log_vertex_build, log_vertex_build,
flow_id=flow_id, flow_id=flow_id_str,
vertex_id=vertex_id, vertex_id=vertex_id,
valid=valid, valid=valid,
params=params, params=params,
@ -212,7 +211,7 @@ async def build_vertex(
inactivated_vertices = list(graph.inactivated_vertices) inactivated_vertices = list(graph.inactivated_vertices)
graph.reset_inactivated_vertices() graph.reset_inactivated_vertices()
graph.reset_activated_vertices() graph.reset_activated_vertices()
await chat_service.set_cache(flow_id, graph) await chat_service.set_cache(flow_id_str, graph)
# graph.stop_vertex tells us if the user asked # graph.stop_vertex tells us if the user asked
# to stop the build of the graph at a certain vertex # to stop the build of the graph at a certain vertex
@ -240,7 +239,7 @@ async def build_vertex(
@router.get("/build/{flow_id}/{vertex_id}/stream", response_class=StreamingResponse) @router.get("/build/{flow_id}/{vertex_id}/stream", response_class=StreamingResponse)
async def build_vertex_stream( async def build_vertex_stream(
flow_id: str, flow_id: uuid.UUID,
vertex_id: str, vertex_id: str,
session_id: Optional[str] = None, session_id: Optional[str] = None,
chat_service: "ChatService" = Depends(get_chat_service), chat_service: "ChatService" = Depends(get_chat_service),
@ -272,23 +271,24 @@ async def build_vertex_stream(
HTTPException: If an error occurs while building the vertex. HTTPException: If an error occurs while building the vertex.
""" """
try: try:
flow_id_str = str(flow_id)
async def stream_vertex(): async def stream_vertex():
try: try:
if not session_id: if not session_id:
cache = await chat_service.get_cache(flow_id) cache = await chat_service.get_cache(flow_id_str)
if not cache: if not cache:
# If there's no cache # If there's no cache
raise ValueError(f"No cache found for {flow_id}.") raise ValueError(f"No cache found for {flow_id_str}.")
else: else:
graph = cache.get("result") graph = cache.get("result")
else: else:
session_data = await session_service.load_session(session_id, flow_id=flow_id) session_data = await session_service.load_session(session_id, flow_id=flow_id_str)
graph, artifacts = session_data if session_data else (None, None) graph, artifacts = session_data if session_data else (None, None)
if not graph: if not graph:
raise ValueError(f"No graph found for {flow_id}.") raise ValueError(f"No graph found for {flow_id_str}.")
vertex: "ChatVertex" = graph.get_vertex(vertex_id) vertex: "InterfaceVertex" = graph.get_vertex(vertex_id)
if not hasattr(vertex, "stream"): if not hasattr(vertex, "stream"):
raise ValueError(f"Vertex {vertex_id} does not support streaming") raise ValueError(f"Vertex {vertex_id} does not support streaming")
if isinstance(vertex._built_result, str) and vertex._built_result: if isinstance(vertex._built_result, str) and vertex._built_result:

View file

@ -1,5 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Annotated, List, Optional, Union from typing import Annotated, List, Optional, Union
from uuid import UUID
import sqlalchemy as sa import sqlalchemy as sa
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status
@ -18,9 +19,7 @@ from langflow.api.v1.schemas import (
UploadFileResponse, UploadFileResponse,
) )
from langflow.graph.graph.base import Graph from langflow.graph.graph.base import Graph
from langflow.graph.schema import RunOutputs
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.interface.custom.utils import build_custom_component_template from langflow.interface.custom.utils import build_custom_component_template
from langflow.processing.process import process_tweaks, run_graph_internal from langflow.processing.process import process_tweaks, run_graph_internal
from langflow.schema.graph import Tweaks from langflow.schema.graph import Tweaks
@ -54,7 +53,7 @@ def get_all(
@router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True) @router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True)
async def simplified_run_flow( async def simplified_run_flow(
db: Annotated[Session, Depends(get_session)], db: Annotated[Session, Depends(get_session)],
flow_id: str, flow_id: UUID,
input_request: SimplifiedAPIRequest = SimplifiedAPIRequest(), input_request: SimplifiedAPIRequest = SimplifiedAPIRequest(),
stream: bool = False, stream: bool = False,
api_key_user: User = Depends(api_key_security), api_key_user: User = Depends(api_key_security),
@ -111,26 +110,26 @@ async def simplified_run_flow(
session_id = input_request.session_id session_id = input_request.session_id
try: try:
task_result: List[RunOutputs] = [] flow_id_str = str(flow_id)
artifacts = {} artifacts = {}
if input_request.session_id: if input_request.session_id:
session_data = await session_service.load_session(input_request.session_id, flow_id=flow_id) session_data = await session_service.load_session(input_request.session_id, flow_id=flow_id_str)
graph, artifacts = session_data if session_data else (None, None) graph, artifacts = session_data if session_data else (None, None)
if graph is None: if graph is None:
raise ValueError(f"Session {input_request.session_id} not found") raise ValueError(f"Session {input_request.session_id} not found")
else: else:
# Get the flow that matches the flow_id and belongs to the user # Get the flow that matches the flow_id and belongs to the user
# flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first() # flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
flow = db.exec(select(Flow).where(Flow.id == flow_id).where(Flow.user_id == api_key_user.id)).first() flow = db.exec(select(Flow).where(Flow.id == flow_id_str).where(Flow.user_id == api_key_user.id)).first()
if flow is None: if flow is None:
raise ValueError(f"Flow {flow_id} not found") raise ValueError(f"Flow {flow_id_str} not found")
if flow.data is None: if flow.data is None:
raise ValueError(f"Flow {flow_id} has no data") raise ValueError(f"Flow {flow_id_str} has no data")
graph_data = flow.data graph_data = flow.data
graph_data = process_tweaks(graph_data, input_request.tweaks or {}, stream=stream) graph_data = process_tweaks(graph_data, input_request.tweaks or {}, stream=stream)
graph = Graph.from_payload(graph_data, flow_id=flow_id, user_id=str(api_key_user.id)) graph = Graph.from_payload(graph_data, flow_id=flow_id_str, user_id=str(api_key_user.id))
inputs = [ inputs = [
InputValueRequest(components=[], input_value=input_request.input_value, type=input_request.input_type) InputValueRequest(components=[], input_value=input_request.input_value, type=input_request.input_type)
] ]
@ -153,7 +152,7 @@ async def simplified_run_flow(
] ]
task_result, session_id = await run_graph_internal( task_result, session_id = await run_graph_internal(
graph=graph, graph=graph,
flow_id=flow_id, flow_id=flow_id_str,
session_id=input_request.session_id, session_id=input_request.session_id,
inputs=inputs, inputs=inputs,
outputs=outputs, outputs=outputs,
@ -166,12 +165,12 @@ async def simplified_run_flow(
except sa.exc.StatementError as exc: except sa.exc.StatementError as exc:
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string') # StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
if "badly formed hexadecimal UUID string" in str(exc): if "badly formed hexadecimal UUID string" in str(exc):
logger.error(f"Flow ID {flow_id} is not a valid UUID") logger.error(f"Flow ID {flow_id_str} is not a valid UUID")
# This means the Flow ID is not a valid UUID which means it can't find the flow # This means the Flow ID is not a valid UUID which means it can't find the flow
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except ValueError as exc: except ValueError as exc:
if f"Flow {flow_id} not found" in str(exc): if f"Flow {flow_id_str} not found" in str(exc):
logger.error(f"Flow {flow_id} not found") logger.error(f"Flow {flow_id_str} not found")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
elif f"Session {session_id} not found" in str(exc): elif f"Session {session_id} not found" in str(exc):
logger.error(f"Session {session_id} not found") logger.error(f"Session {session_id} not found")
@ -187,7 +186,7 @@ async def simplified_run_flow(
@router.post("/run/advanced/{flow_id}", response_model=RunResponse, response_model_exclude_none=True) @router.post("/run/advanced/{flow_id}", response_model=RunResponse, response_model_exclude_none=True)
async def experimental_run_flow( async def experimental_run_flow(
session: Annotated[Session, Depends(get_session)], session: Annotated[Session, Depends(get_session)],
flow_id: str, flow_id: UUID,
inputs: Optional[List[InputValueRequest]] = [InputValueRequest(components=[], input_value="")], inputs: Optional[List[InputValueRequest]] = [InputValueRequest(components=[], input_value="")],
outputs: Optional[List[str]] = [], outputs: Optional[List[str]] = [],
tweaks: Annotated[Optional[Tweaks], Body(embed=True)] = None, # noqa: F821 tweaks: Annotated[Optional[Tweaks], Body(embed=True)] = None, # noqa: F821
@ -235,31 +234,33 @@ async def experimental_run_flow(
This endpoint facilitates complex flow executions with customized inputs, outputs, and configurations, catering to diverse application requirements. This endpoint facilitates complex flow executions with customized inputs, outputs, and configurations, catering to diverse application requirements.
""" """
try: try:
flow_id_str = str(flow_id)
if outputs is None: if outputs is None:
outputs = [] outputs = []
task_result: List[RunOutputs] = []
artifacts = {} artifacts = {}
if session_id: if session_id:
session_data = await session_service.load_session(session_id, flow_id=flow_id) session_data = await session_service.load_session(session_id, flow_id=flow_id_str)
graph, artifacts = session_data if session_data else (None, None) graph, artifacts = session_data if session_data else (None, None)
if graph is None: if graph is None:
raise ValueError(f"Session {session_id} not found") raise ValueError(f"Session {session_id} not found")
else: else:
# Get the flow that matches the flow_id and belongs to the user # Get the flow that matches the flow_id and belongs to the user
# flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first() # flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
flow = session.exec(select(Flow).where(Flow.id == flow_id).where(Flow.user_id == api_key_user.id)).first() flow = session.exec(
select(Flow).where(Flow.id == flow_id_str).where(Flow.user_id == api_key_user.id)
).first()
if flow is None: if flow is None:
raise ValueError(f"Flow {flow_id} not found") raise ValueError(f"Flow {flow_id_str} not found")
if flow.data is None: if flow.data is None:
raise ValueError(f"Flow {flow_id} has no data") raise ValueError(f"Flow {flow_id_str} has no data")
graph_data = flow.data graph_data = flow.data
graph_data = process_tweaks(graph_data, tweaks or {}) graph_data = process_tweaks(graph_data, tweaks or {})
graph = Graph.from_payload(graph_data, flow_id=flow_id) graph = Graph.from_payload(graph_data, flow_id=flow_id_str)
task_result, session_id = await run_graph_internal( task_result, session_id = await run_graph_internal(
graph=graph, graph=graph,
flow_id=flow_id, flow_id=flow_id_str,
session_id=session_id, session_id=session_id,
inputs=inputs, inputs=inputs,
outputs=outputs, outputs=outputs,
@ -272,12 +273,12 @@ async def experimental_run_flow(
except sa.exc.StatementError as exc: except sa.exc.StatementError as exc:
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string') # StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
if "badly formed hexadecimal UUID string" in str(exc): if "badly formed hexadecimal UUID string" in str(exc):
logger.error(f"Flow ID {flow_id} is not a valid UUID") logger.error(f"Flow ID {flow_id_str} is not a valid UUID")
# This means the Flow ID is not a valid UUID which means it can't find the flow # This means the Flow ID is not a valid UUID which means it can't find the flow
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except ValueError as exc: except ValueError as exc:
if f"Flow {flow_id} not found" in str(exc): if f"Flow {flow_id_str} not found" in str(exc):
logger.error(f"Flow {flow_id} not found") logger.error(f"Flow {flow_id_str} not found")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
elif f"Session {session_id} not found" in str(exc): elif f"Session {session_id} not found" in str(exc):
logger.error(f"Session {session_id} not found") logger.error(f"Session {session_id} not found")
@ -357,13 +358,14 @@ async def get_task_status(task_id: str):
) )
async def create_upload_file( async def create_upload_file(
file: UploadFile, file: UploadFile,
flow_id: str, flow_id: UUID,
): ):
try: try:
file_path = save_uploaded_file(file, folder_name=flow_id) flow_id_str = str(flow_id)
file_path = save_uploaded_file(file, folder_name=flow_id_str)
return UploadFileResponse( return UploadFileResponse(
flowId=flow_id, flowId=flow_id_str,
file_path=file_path, file_path=file_path,
) )
except Exception as exc: except Exception as exc:
@ -400,23 +402,6 @@ async def custom_component(
return built_frontend_node return built_frontend_node
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
async def reload_custom_component(path: str, user: User = Depends(get_current_active_user)):
from langflow.interface.custom.utils import build_custom_component_template
try:
reader = DirectoryReader("")
valid, content = reader.process_file(path)
if not valid:
raise ValueError(content)
extractor = CustomComponent(code=content)
frontend_node, _ = build_custom_component_template(extractor, user_id=user.id)
return frontend_node
except Exception as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.post("/custom_component/update", status_code=HTTPStatus.OK) @router.post("/custom_component/update", status_code=HTTPStatus.OK)
async def custom_component_update( async def custom_component_update(
code_request: UpdateCustomComponentRequest, code_request: UpdateCustomComponentRequest,

View file

@ -1,6 +1,7 @@
import hashlib import hashlib
from http import HTTPStatus from http import HTTPStatus
from io import BytesIO from io import BytesIO
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, UploadFile from fastapi import APIRouter, Depends, HTTPException, UploadFile
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
@ -20,38 +21,41 @@ router = APIRouter(tags=["Files"], prefix="/files")
# then finds it in the database and returns it while # then finds it in the database and returns it while
# using the current user as the owner # using the current user as the owner
def get_flow_id( def get_flow_id(
flow_id: str, flow_id: UUID,
current_user=Depends(get_current_active_user), current_user=Depends(get_current_active_user),
session=Depends(get_session), session=Depends(get_session),
): ):
flow_id_str = str(flow_id)
# AttributeError: 'SelectOfScalar' object has no attribute 'first' # AttributeError: 'SelectOfScalar' object has no attribute 'first'
flow = session.get(Flow, flow_id) flow = session.get(Flow, flow_id_str)
if not flow: if not flow:
raise HTTPException(status_code=404, detail="Flow not found") raise HTTPException(status_code=404, detail="Flow not found")
if flow.user_id != current_user.id: if flow.user_id != current_user.id:
raise HTTPException(status_code=403, detail="You don't have access to this flow") raise HTTPException(status_code=403, detail="You don't have access to this flow")
return flow_id return flow_id_str
@router.post("/upload/{flow_id}", status_code=HTTPStatus.CREATED) @router.post("/upload/{flow_id}", status_code=HTTPStatus.CREATED)
async def upload_file( async def upload_file(
file: UploadFile, file: UploadFile,
flow_id: str = Depends(get_flow_id), flow_id: UUID = Depends(get_flow_id),
storage_service: StorageService = Depends(get_storage_service), storage_service: StorageService = Depends(get_storage_service),
): ):
try: try:
flow_id_str = str(flow_id)
file_content = await file.read() file_content = await file.read()
file_name = file.filename or hashlib.sha256(file_content).hexdigest() file_name = file.filename or hashlib.sha256(file_content).hexdigest()
folder = flow_id folder = flow_id_str
await storage_service.save_file(flow_id=folder, file_name=file_name, data=file_content) await storage_service.save_file(flow_id=folder, file_name=file_name, data=file_content)
return UploadFileResponse(flowId=flow_id, file_path=f"{folder}/{file_name}") return UploadFileResponse(flowId=flow_id_str, file_path=f"{folder}/{file_name}")
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.get("/download/{flow_id}/{file_name}") @router.get("/download/{flow_id}/{file_name}")
async def download_file(file_name: str, flow_id: str, storage_service: StorageService = Depends(get_storage_service)): async def download_file(file_name: str, flow_id: UUID, storage_service: StorageService = Depends(get_storage_service)):
try: try:
flow_id_str = str(flow_id)
extension = file_name.split(".")[-1] extension = file_name.split(".")[-1]
if not extension: if not extension:
@ -62,7 +66,7 @@ async def download_file(file_name: str, flow_id: str, storage_service: StorageSe
if not content_type: if not content_type:
raise HTTPException(status_code=500, detail=f"Content type not found for extension {extension}") raise HTTPException(status_code=500, detail=f"Content type not found for extension {extension}")
file_content = await storage_service.get_file(flow_id=flow_id, file_name=file_name) file_content = await storage_service.get_file(flow_id=flow_id_str, file_name=file_name)
headers = { headers = {
"Content-Disposition": f"attachment; filename={file_name} filename*=UTF-8''{file_name}", "Content-Disposition": f"attachment; filename={file_name} filename*=UTF-8''{file_name}",
"Content-Type": "application/octet-stream", "Content-Type": "application/octet-stream",
@ -74,9 +78,10 @@ async def download_file(file_name: str, flow_id: str, storage_service: StorageSe
@router.get("/images/{flow_id}/{file_name}") @router.get("/images/{flow_id}/{file_name}")
async def download_image(file_name: str, flow_id: str, storage_service: StorageService = Depends(get_storage_service)): async def download_image(file_name: str, flow_id: UUID, storage_service: StorageService = Depends(get_storage_service)):
try: try:
extension = file_name.split(".")[-1] extension = file_name.split(".")[-1]
flow_id_str = str(flow_id)
if not extension: if not extension:
raise HTTPException(status_code=500, detail=f"Extension not found for file {file_name}") raise HTTPException(status_code=500, detail=f"Extension not found for file {file_name}")
@ -88,7 +93,7 @@ async def download_image(file_name: str, flow_id: str, storage_service: StorageS
elif not content_type.startswith("image"): elif not content_type.startswith("image"):
raise HTTPException(status_code=500, detail=f"Content type {content_type} is not an image") raise HTTPException(status_code=500, detail=f"Content type {content_type} is not an image")
file_content = await storage_service.get_file(flow_id=flow_id, file_name=file_name) file_content = await storage_service.get_file(flow_id=flow_id_str, file_name=file_name)
return StreamingResponse(BytesIO(file_content), media_type=content_type) return StreamingResponse(BytesIO(file_content), media_type=content_type)
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@ -96,10 +101,11 @@ async def download_image(file_name: str, flow_id: str, storage_service: StorageS
@router.get("/list/{flow_id}") @router.get("/list/{flow_id}")
async def list_files( async def list_files(
flow_id: str = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service) flow_id: UUID = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service)
): ):
try: try:
files = await storage_service.list_files(flow_id=flow_id) flow_id_str = str(flow_id)
files = await storage_service.list_files(flow_id=flow_id_str)
return {"files": files} return {"files": files}
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@ -107,10 +113,11 @@ async def list_files(
@router.delete("/delete/{flow_id}/{file_name}") @router.delete("/delete/{flow_id}/{file_name}")
async def delete_file( async def delete_file(
file_name: str, flow_id: str = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service) file_name: str, flow_id: UUID = Depends(get_flow_id), storage_service: StorageService = Depends(get_storage_service)
): ):
try: try:
await storage_service.delete_file(flow_id=flow_id, file_name=file_name) flow_id_str = str(flow_id)
await storage_service.delete_file(flow_id=flow_id_str, file_name=file_name)
return {"message": f"File {file_name} deleted successfully"} return {"message": f"File {file_name} deleted successfully"}
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))

View file

@ -6,13 +6,15 @@ import orjson
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from loguru import logger from loguru import logger
from sqlmodel import Session, select from sqlmodel import Session, col, select
from langflow.api.utils import remove_api_keys, validate_is_component from langflow.api.utils import remove_api_keys, validate_is_component
from langflow.api.v1.schemas import FlowListCreate, FlowListRead from langflow.api.v1.schemas import FlowListCreate, FlowListIds, FlowListRead
from langflow.initial_setup.setup import STARTER_FOLDER_NAME from langflow.initial_setup.setup import STARTER_FOLDER_NAME
from langflow.services.auth.utils import get_current_active_user from langflow.services.auth.utils import get_current_active_user
from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
from langflow.services.database.models.folder.model import Folder
from langflow.services.database.models.user.model import User from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session, get_settings_service from langflow.services.deps import get_session, get_settings_service
from langflow.services.settings.service import SettingsService from langflow.services.settings.service import SettingsService
@ -35,6 +37,11 @@ def create_flow(
db_flow = Flow.model_validate(flow, from_attributes=True) db_flow = Flow.model_validate(flow, from_attributes=True)
db_flow.updated_at = datetime.now(timezone.utc) db_flow.updated_at = datetime.now(timezone.utc)
if db_flow.folder_id is None:
default_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
if default_folder:
db_flow.folder_id = default_folder.id
session.add(db_flow) session.add(db_flow)
session.commit() session.commit()
session.refresh(db_flow) session.refresh(db_flow)
@ -64,12 +71,9 @@ def read_flows(
flow_ids = [flow.id for flow in flows] flow_ids = [flow.id for flow in flows]
# with the session get the flows that DO NOT have a user_id # with the session get the flows that DO NOT have a user_id
try: try:
example_flows = session.exec( folder = session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
select(Flow).where(
Flow.user_id == None, # noqa example_flows = folder.flows if folder else []
Flow.folder == STARTER_FOLDER_NAME,
)
).all()
for example_flow in example_flows: for example_flow in example_flows:
if example_flow.id not in flow_ids: if example_flow.id not in flow_ids:
flows.append(example_flow) # type: ignore flows.append(example_flow) # type: ignore
@ -129,6 +133,10 @@ def update_flow(
if value is not None: if value is not None:
setattr(db_flow, key, value) setattr(db_flow, key, value)
db_flow.updated_at = datetime.now(timezone.utc) db_flow.updated_at = datetime.now(timezone.utc)
if db_flow.folder_id is None:
default_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
if default_folder:
db_flow.folder_id = default_folder.id
session.add(db_flow) session.add(db_flow)
session.commit() session.commit()
session.refresh(db_flow) session.refresh(db_flow)
@ -208,3 +216,31 @@ async def download_file(
"""Download all flows as a file.""" """Download all flows as a file."""
flows = read_flows(current_user=current_user, session=session, settings_service=settings_service) flows = read_flows(current_user=current_user, session=session, settings_service=settings_service)
return FlowListRead(flows=flows) return FlowListRead(flows=flows)
@router.post("/multiple_delete/")
async def delete_multiple_flows(
flow_ids: FlowListIds, user: User = Depends(get_current_active_user), db: Session = Depends(get_session)
):
"""
Delete multiple flows by their IDs.
Args:
flow_ids (List[str]): The list of flow IDs to delete.
user (User, optional): The user making the request. Defaults to the current active user.
Returns:
dict: A dictionary containing the number of flows deleted.
"""
try:
deleted_flows = db.exec(
select(Flow).where(col(Flow.id).in_(flow_ids.flow_ids)).where(Flow.user_id == user.id)
).all()
for flow in deleted_flows:
db.delete(flow)
db.commit()
return {"deleted": len(deleted_flows)}
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc

View file

@ -0,0 +1,239 @@
from typing import List
from uuid import UUID
import orjson
from fastapi import APIRouter, Depends, File, HTTPException, Response, UploadFile, status
from sqlalchemy import or_, update
from sqlmodel import Session, select
from langflow.api.v1.flows import create_flows
from langflow.api.v1.schemas import FlowListCreate, FlowListReadWithFolderName
from langflow.services.auth.utils import get_current_active_user
from langflow.services.database.models.flow.model import Flow, FlowCreate, FlowRead
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
from langflow.services.database.models.folder.model import (
Folder,
FolderCreate,
FolderRead,
FolderReadWithFlows,
FolderUpdate,
)
from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session
router = APIRouter(prefix="/folders", tags=["Folders"])
@router.post("/", response_model=FolderRead, status_code=201)
def create_folder(
*,
session: Session = Depends(get_session),
folder: FolderCreate,
current_user: User = Depends(get_current_active_user),
):
try:
new_folder = Folder.model_validate(folder, from_attributes=True)
new_folder.user_id = current_user.id
folder_results = session.exec(
select(Folder).where(
Folder.name.like(f"{new_folder.name}%"), # type: ignore
Folder.user_id == current_user.id,
)
)
existing_folder_names = [folder.name for folder in folder_results]
if existing_folder_names:
new_folder.name = f"{new_folder.name} ({len(existing_folder_names) + 1})"
session.add(new_folder)
session.commit()
session.refresh(new_folder)
if folder.components_list:
update_statement_components = (
update(Flow).where(Flow.id.in_(folder.components_list)).values(folder_id=new_folder.id) # type: ignore
)
session.exec(update_statement_components) # type: ignore
session.commit()
if folder.flows_list:
update_statement_flows = update(Flow).where(Flow.id.in_(folder.flows_list)).values(folder_id=new_folder.id) # type: ignore
session.exec(update_statement_flows) # type: ignore
session.commit()
return new_folder
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/", response_model=List[FolderRead], status_code=200)
def read_folders(
*,
session: Session = Depends(get_session),
current_user: User = Depends(get_current_active_user),
):
try:
folders = session.exec(
select(Folder).where(
or_(Folder.user_id == current_user.id, Folder.user_id == None) # type: ignore # noqa: E711
)
).all()
return folders
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{folder_id}", response_model=FolderReadWithFlows, status_code=200)
def read_folder(
*,
session: Session = Depends(get_session),
folder_id: UUID,
current_user: User = Depends(get_current_active_user),
):
try:
folder = session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)).first()
if not folder:
raise HTTPException(status_code=404, detail="Folder not found")
return folder
except Exception as e:
if "No result found" in str(e):
raise HTTPException(status_code=404, detail="Folder not found")
raise HTTPException(status_code=500, detail=str(e))
@router.patch("/{folder_id}", response_model=FolderRead, status_code=200)
def update_folder(
*,
session: Session = Depends(get_session),
folder_id: UUID,
folder: FolderUpdate, # Assuming FolderUpdate is a Pydantic model defining updatable fields
current_user: User = Depends(get_current_active_user),
):
try:
existing_folder = session.exec(
select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)
).first()
if not existing_folder:
raise HTTPException(status_code=404, detail="Folder not found")
folder_data = folder.model_dump(exclude_unset=True)
for key, value in folder_data.items():
if key != "components" and key != "flows":
setattr(existing_folder, key, value)
session.add(existing_folder)
session.commit()
session.refresh(existing_folder)
concat_folder_components = folder.components + folder.flows
flows_ids = session.exec(select(Flow.id).where(Flow.folder_id == existing_folder.id)).all()
excluded_flows = list(set(flows_ids) - set(concat_folder_components))
my_collection_folder = session.exec(select(Folder).where(Folder.name == DEFAULT_FOLDER_NAME)).first()
if my_collection_folder:
update_statement_my_collection = (
update(Flow).where(Flow.id.in_(excluded_flows)).values(folder_id=my_collection_folder.id) # type: ignore
)
session.exec(update_statement_my_collection) # type: ignore
session.commit()
if concat_folder_components:
update_statement_components = (
update(Flow).where(Flow.id.in_(concat_folder_components)).values(folder_id=existing_folder.id) # type: ignore
)
session.exec(update_statement_components) # type: ignore
session.commit()
return existing_folder
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{folder_id}", status_code=204)
def delete_folder(
*,
session: Session = Depends(get_session),
folder_id: UUID,
current_user: User = Depends(get_current_active_user),
):
try:
folder = session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)).first()
if not folder:
raise HTTPException(status_code=404, detail="Folder not found")
session.delete(folder)
session.commit()
flows = session.exec(select(Flow).where(Flow.folder_id == folder_id, Folder.user_id == current_user.id)).all()
for flow in flows:
session.delete(flow)
session.commit()
return Response(status_code=status.HTTP_204_NO_CONTENT)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/download/{folder_id}", response_model=FlowListReadWithFolderName, status_code=200)
async def download_file(
*,
session: Session = Depends(get_session),
folder_id: UUID,
current_user: User = Depends(get_current_active_user),
):
"""Download all flows from folder."""
try:
folder = session.exec(select(Folder).where(Folder.id == folder_id, Folder.user_id == current_user.id)).first()
return folder
except Exception as e:
if "No result found" in str(e):
raise HTTPException(status_code=404, detail="Folder not found")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/upload/", response_model=List[FlowRead], status_code=201)
async def upload_file(
*,
session: Session = Depends(get_session),
file: UploadFile = File(...),
current_user: User = Depends(get_current_active_user),
):
"""Upload flows from a file."""
contents = await file.read()
data = orjson.loads(contents)
if not data:
raise HTTPException(status_code=400, detail="No flows found in the file")
folder_results = session.exec(
select(Folder).where(
Folder.name == data["folder_name"],
Folder.user_id == current_user.id,
)
)
existing_folder_names = [folder.name for folder in folder_results]
if existing_folder_names:
data["folder_name"] = f"{data['folder_name']} ({len(existing_folder_names) + 1})"
folder = FolderCreate(name=data["folder_name"], description=data["folder_description"])
new_folder = Folder.model_validate(folder, from_attributes=True)
new_folder.id = None
new_folder.user_id = current_user.id
session.add(new_folder)
session.commit()
session.refresh(new_folder)
del data["folder_name"]
del data["folder_description"]
if "flows" in data:
flow_list = FlowListCreate(flows=[FlowCreate(**flow) for flow in data["flows"]])
else:
raise HTTPException(status_code=400, detail="No flows found in the data")
# Now we set the user_id for all flows
for flow in flow_list.flows:
flow.user_id = current_user.id
flow.folder_id = new_folder.id
return create_flows(session=session, flow_list=flow_list, current_user=current_user)

View file

@ -1,5 +1,7 @@
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import Session
from langflow.api.v1.schemas import Token from langflow.api.v1.schemas import Token
from langflow.services.auth.utils import ( from langflow.services.auth.utils import (
authenticate_user, authenticate_user,
@ -7,14 +9,10 @@ from langflow.services.auth.utils import (
create_user_longterm_token, create_user_longterm_token,
create_user_tokens, create_user_tokens,
) )
from langflow.services.deps import ( from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
get_session, from langflow.services.deps import get_session, get_settings_service, get_variable_service
get_settings_service,
get_variable_service,
)
from langflow.services.settings.manager import SettingsService from langflow.services.settings.manager import SettingsService
from langflow.services.variable.service import VariableService from langflow.services.variable.service import VariableService
from sqlmodel import Session
router = APIRouter(tags=["Login"]) router = APIRouter(tags=["Login"])
@ -58,6 +56,8 @@ async def login_to_get_access_token(
expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS, expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
) )
variable_service.initialize_user_variables(user.id, db) variable_service.initialize_user_variables(user.id, db)
# Create default folder for user if it doesn't exist
create_default_folder_if_it_doesnt_exist(db, user.id)
return tokens return tokens
else: else:
raise HTTPException( raise HTTPException(
@ -86,6 +86,7 @@ async def auto_login(
expires=None, # Set to None to make it a session cookie expires=None, # Set to None to make it a session cookie
) )
variable_service.initialize_user_variables(user_id, db) variable_service.initialize_user_variables(user_id, db)
create_default_folder_if_it_doesnt_exist(db, user_id)
return tokens return tokens
raise HTTPException( raise HTTPException(
@ -139,4 +140,3 @@ async def logout(response: Response):
response.delete_cookie("refresh_token_lf") response.delete_cookie("refresh_token_lf")
response.delete_cookie("access_token_lf") response.delete_cookie("access_token_lf")
return {"message": "Logout successful"} return {"message": "Logout successful"}
return {"message": "Logout successful"}

View file

@ -1,10 +1,13 @@
from typing import Optional from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from langflow.services.deps import get_monitor_service from langflow.services.deps import get_monitor_service
from langflow.services.monitor.schema import VertexBuildMapModel from langflow.services.monitor.schema import (
MessageModelResponse,
TransactionModelResponse,
VertexBuildMapModel,
)
from langflow.services.monitor.service import MonitorService from langflow.services.monitor.service import MonitorService
router = APIRouter(prefix="/monitor", tags=["Monitor"]) router = APIRouter(prefix="/monitor", tags=["Monitor"])
@ -40,8 +43,9 @@ async def delete_vertex_builds(
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.get("/messages") @router.get("/messages", response_model=List[MessageModelResponse])
async def get_messages( async def get_messages(
flow_id: Optional[str] = Query(None),
session_id: Optional[str] = Query(None), session_id: Optional[str] = Query(None),
sender: Optional[str] = Query(None), sender: Optional[str] = Query(None),
sender_name: Optional[str] = Query(None), sender_name: Optional[str] = Query(None),
@ -49,25 +53,32 @@ async def get_messages(
monitor_service: MonitorService = Depends(get_monitor_service), monitor_service: MonitorService = Depends(get_monitor_service),
): ):
try: try:
return monitor_service.get_messages( df = monitor_service.get_messages(
flow_id=flow_id,
sender=sender, sender=sender,
sender_name=sender_name, sender_name=sender_name,
session_id=session_id, session_id=session_id,
order_by=order_by, order_by=order_by,
) )
dicts = df.to_dict(orient="records")
return [MessageModelResponse(**d) for d in dicts]
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.get("/transactions") @router.get("/transactions", response_model=List[TransactionModelResponse])
async def get_transactions( async def get_transactions(
source: Optional[str] = Query(None), source: Optional[str] = Query(None),
target: Optional[str] = Query(None), target: Optional[str] = Query(None),
status: Optional[str] = Query(None), status: Optional[str] = Query(None),
order_by: Optional[str] = Query("timestamp"), order_by: Optional[str] = Query("timestamp"),
flow_id: Optional[str] = Query(None),
monitor_service: MonitorService = Depends(get_monitor_service), monitor_service: MonitorService = Depends(get_monitor_service),
): ):
try: try:
return monitor_service.get_transactions(source=source, target=target, status=status, order_by=order_by) dicts = monitor_service.get_transactions(
source=source, target=target, status=status, order_by=order_by, flow_id=flow_id
)
return [TransactionModelResponse(**d) for d in dicts]
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))

View file

@ -139,10 +139,20 @@ class FlowListCreate(BaseModel):
flows: List[FlowCreate] flows: List[FlowCreate]
class FlowListIds(BaseModel):
flow_ids: List[str]
class FlowListRead(BaseModel): class FlowListRead(BaseModel):
flows: List[FlowRead] flows: List[FlowRead]
class FlowListReadWithFolderName(BaseModel):
flows: List[FlowRead]
name: str
description: str
class InitResponse(BaseModel): class InitResponse(BaseModel):
flowId: str flowId: str

View file

@ -13,6 +13,7 @@ from langflow.services.auth.utils import (
get_password_hash, get_password_hash,
verify_password, verify_password,
) )
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
from langflow.services.database.models.user import User, UserCreate, UserRead, UserUpdate from langflow.services.database.models.user import User, UserCreate, UserRead, UserUpdate
from langflow.services.database.models.user.crud import get_user_by_id, update_user from langflow.services.database.models.user.crud import get_user_by_id, update_user
from langflow.services.deps import get_session, get_settings_service from langflow.services.deps import get_session, get_settings_service
@ -36,6 +37,9 @@ def add_user(
session.add(new_user) session.add(new_user)
session.commit() session.commit()
session.refresh(new_user) session.refresh(new_user)
folder = create_default_folder_if_it_doesnt_exist(session, new_user.id)
if not folder:
raise HTTPException(status_code=500, detail="Error creating default folder")
except IntegrityError as e: except IntegrityError as e:
session.rollback() session.rollback()
raise HTTPException(status_code=400, detail="This username is unavailable.") from e raise HTTPException(status_code=400, detail="This username is unavailable.") from e

View file

@ -49,12 +49,12 @@ class ChatComponent(CustomComponent):
sender: Optional[str] = None, sender: Optional[str] = None,
sender_name: Optional[str] = None, sender_name: Optional[str] = None,
) -> list[Record]: ) -> list[Record]:
records = store_message( records = store_message(
message, message,
session_id=session_id, session_id=session_id,
sender=sender, sender=sender,
sender_name=sender_name, sender_name=sender_name,
flow_id=self.graph.flow_id,
) )
self.status = records self.status = records

View file

@ -1,7 +1,5 @@
from typing import Optional from typing import Optional
from langflow.field_typing import Text
from langflow.helpers.record import records_to_text
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record

View file

@ -1,10 +1,10 @@
from fastapi import HTTPException from fastapi import HTTPException
from langchain.prompts import PromptTemplate
from loguru import logger from loguru import logger
from langflow.api.v1.base import INVALID_NAMES, check_input_variables from langflow.api.v1.base import INVALID_NAMES, check_input_variables
from langflow.interface.utils import extract_input_variables_from_prompt from langflow.interface.utils import extract_input_variables_from_prompt
from langflow.template.field.prompt import DefaultPromptField from langflow.template.field.prompt import DefaultPromptField
from langchain_core.prompts import PromptTemplate
def validate_prompt(prompt_template: str, silent_errors: bool = False) -> list[str]: def validate_prompt(prompt_template: str, silent_errors: bool = False) -> list[str]:

View file

@ -1,3 +1,6 @@
from copy import deepcopy
from langchain_core.documents import Document from langchain_core.documents import Document
from langflow.schema import Record from langflow.schema import Record
@ -27,19 +30,20 @@ def dict_values_to_string(d: dict) -> dict:
dict: The dictionary with values converted to strings. dict: The dictionary with values converted to strings.
""" """
# Do something similar to the above # Do something similar to the above
for key, value in d.items(): d_copy = deepcopy(d)
for key, value in d_copy.items():
# it could be a list of records or documents or strings # it could be a list of records or documents or strings
if isinstance(value, list): if isinstance(value, list):
for i, item in enumerate(value): for i, item in enumerate(value):
if isinstance(item, Record): if isinstance(item, Record):
d[key][i] = record_to_string(item) d_copy[key][i] = record_to_string(item)
elif isinstance(item, Document): elif isinstance(item, Document):
d[key][i] = document_to_string(item) d_copy[key][i] = document_to_string(item)
elif isinstance(value, Record): elif isinstance(value, Record):
d[key] = record_to_string(value) d_copy[key] = record_to_string(value)
elif isinstance(value, Document): elif isinstance(value, Document):
d[key] = document_to_string(value) d_copy[key] = document_to_string(value)
return d return d_copy
def document_to_string(document: Document) -> str: def document_to_string(document: Document) -> str:

View file

@ -1,10 +1,11 @@
from langchain.agents import AgentExecutor, create_json_agent from langchain.agents import AgentExecutor
from langchain_community.agent_toolkits.json.toolkit import JsonToolkit from langchain_community.agent_toolkits.json.toolkit import JsonToolkit
from langflow.field_typing import ( from langflow.field_typing import (
BaseLanguageModel, BaseLanguageModel,
) )
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_community.agent_toolkits import create_json_agent
class JsonAgentComponent(CustomComponent): class JsonAgentComponent(CustomComponent):

View file

@ -4,14 +4,14 @@ from langchain.agents.agent import AgentExecutor
from langchain.agents.agent_toolkits.conversational_retrieval.openai_functions import _get_default_system_message from langchain.agents.agent_toolkits.conversational_retrieval.openai_functions import _get_default_system_message
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
from langchain.memory.token_buffer import ConversationTokenBufferMemory from langchain.memory.token_buffer import ConversationTokenBufferMemory
from langchain.prompts import SystemMessagePromptTemplate from langchain_openai import ChatOpenAI
from langchain.prompts.chat import MessagesPlaceholder
from langchain.schema.memory import BaseMemory
from langchain.tools import Tool
from langchain_community.chat_models import ChatOpenAI
from langflow.field_typing.range_spec import RangeSpec from langflow.field_typing.range_spec import RangeSpec
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from pydantic.v1 import SecretStr
from langchain_core.memory import BaseMemory
from langchain_core.prompts import MessagesPlaceholder, SystemMessagePromptTemplate
from langchain_core.tools import Tool
class ConversationalAgent(CustomComponent): class ConversationalAgent(CustomComponent):
@ -57,9 +57,14 @@ class ConversationalAgent(CustomComponent):
max_token_limit: int = 2000, max_token_limit: int = 2000,
temperature: float = 0.9, temperature: float = 0.9,
) -> AgentExecutor: ) -> AgentExecutor:
if openai_api_key:
api_key = SecretStr(openai_api_key)
else:
api_key = None
llm = ChatOpenAI( llm = ChatOpenAI(
model=model_name, model=model_name,
api_key=openai_api_key, api_key=api_key,
base_url=openai_api_base, base_url=openai_api_base,
max_tokens=max_token_limit, max_tokens=max_token_limit,
temperature=temperature, temperature=temperature,

View file

@ -1,9 +1,8 @@
from typing import Optional from typing import Optional
from langchain.embeddings.base import Embeddings
from langchain_community.embeddings import BedrockEmbeddings from langchain_community.embeddings import BedrockEmbeddings
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.embeddings import Embeddings
class AmazonBedrockEmeddingsComponent(CustomComponent): class AmazonBedrockEmeddingsComponent(CustomComponent):

View file

@ -1,7 +1,7 @@
from langchain.embeddings.base import Embeddings
from langchain_community.embeddings import AzureOpenAIEmbeddings
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.embeddings import Embeddings
from langchain_openai import AzureOpenAIEmbeddings
from pydantic.v1 import SecretStr
class AzureOpenAIEmbeddingsComponent(CustomComponent): class AzureOpenAIEmbeddingsComponent(CustomComponent):
@ -52,12 +52,16 @@ class AzureOpenAIEmbeddingsComponent(CustomComponent):
api_version: str, api_version: str,
api_key: str, api_key: str,
) -> Embeddings: ) -> Embeddings:
if api_key:
azure_api_key = SecretStr(api_key)
else:
azure_api_key = None
try: try:
embeddings = AzureOpenAIEmbeddings( embeddings = AzureOpenAIEmbeddings(
azure_endpoint=azure_endpoint, azure_endpoint=azure_endpoint,
azure_deployment=azure_deployment, azure_deployment=azure_deployment,
api_version=api_version, api_version=api_version,
api_key=api_key, api_key=azure_api_key,
) )
except Exception as e: except Exception as e:

View file

@ -1,9 +1,9 @@
from typing import List, Optional
from pydantic.v1 import SecretStr from pydantic.v1 import SecretStr
from langchain_mistralai.embeddings import MistralAIEmbeddings from langchain_mistralai.embeddings import MistralAIEmbeddings
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.field_typing import Embeddings, NestedDict from langflow.field_typing import Embeddings
class MistralAIEmbeddingsComponent(CustomComponent): class MistralAIEmbeddingsComponent(CustomComponent):
display_name = "MistralAI Embeddings" display_name = "MistralAI Embeddings"
@ -37,11 +37,7 @@ class MistralAIEmbeddingsComponent(CustomComponent):
"advanced": True, "advanced": True,
"value": 120, "value": 120,
}, },
"endpoint": { "endpoint": {"display_name": "API Endpoint", "advanced": True, "value": "https://api.mistral.ai/v1/"},
"display_name": "API Endpoint",
"advanced": True,
"value": "https://api.mistral.ai/v1/"
}
} }
def build( def build(
@ -51,7 +47,7 @@ class MistralAIEmbeddingsComponent(CustomComponent):
max_concurrent_requests: int = 64, max_concurrent_requests: int = 64,
max_retries: int = 5, max_retries: int = 5,
timeout: int = 120, timeout: int = 120,
endpoint: str = "https://api.mistral.ai/v1/" endpoint: str = "https://api.mistral.ai/v1/",
) -> Embeddings: ) -> Embeddings:
if mistral_api_key: if mistral_api_key:
api_key = SecretStr(mistral_api_key) api_key = SecretStr(mistral_api_key)
@ -64,6 +60,5 @@ class MistralAIEmbeddingsComponent(CustomComponent):
endpoint=endpoint, endpoint=endpoint,
max_concurrent_requests=max_concurrent_requests, max_concurrent_requests=max_concurrent_requests,
max_retries=max_retries, max_retries=max_retries,
timeout=timeout timeout=timeout,
) )

View file

@ -1,9 +1,8 @@
from typing import Optional from typing import Optional
from langchain.embeddings.base import Embeddings
from langchain_community.embeddings import OllamaEmbeddings from langchain_community.embeddings import OllamaEmbeddings
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.embeddings import Embeddings
class OllamaEmbeddingsComponent(CustomComponent): class OllamaEmbeddingsComponent(CustomComponent):

View file

@ -3,6 +3,7 @@ from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record from langflow.schema import Record
from langflow.field_typing import Text from langflow.field_typing import Text
class PassComponent(CustomComponent): class PassComponent(CustomComponent):
display_name = "Pass" display_name = "Pass"
description = "A pass-through component that forwards the second input while ignoring the first, used for controlling workflow direction." description = "A pass-through component that forwards the second input while ignoring the first, used for controlling workflow direction."
@ -19,7 +20,7 @@ class PassComponent(CustomComponent):
"display_name": "Input", "display_name": "Input",
"info": "This input is forwarded by the component.", "info": "This input is forwarded by the component.",
"input_types": ["Text", "Record"], "input_types": ["Text", "Record"],
} },
} }
def build(self, ignored_input: Text, forwarded_input: Text) -> Union[Text, Record]: def build(self, ignored_input: Text, forwarded_input: Text) -> Union[Text, Record]:

View file

@ -32,7 +32,6 @@ class StoreMessageComponent(CustomComponent):
session_id: Optional[str] = None, session_id: Optional[str] = None,
message: str = "", message: str = "",
) -> List[Record]: ) -> List[Record]:
store_message( store_message(
sender=sender, sender=sender,
sender_name=sender_name, sender_name=sender_name,

View file

@ -35,7 +35,7 @@ class SubFlowComponent(CustomComponent):
build_config["flow_name"]["options"] = self.get_flow_names() build_config["flow_name"]["options"] = self.get_flow_names()
# Clean up the build config # Clean up the build config
for key in list(build_config.keys()): for key in list(build_config.keys()):
if key not in self.field_order + ["code", "_type"]: if key not in self.field_order + ["code", "_type", "get_final_results_only"]:
del build_config[key] del build_config[key]
if field_value is not None and field_name == "flow_name": if field_value is not None and field_name == "flow_name":
try: try:
@ -85,20 +85,29 @@ class SubFlowComponent(CustomComponent):
"display_name": "Tweaks", "display_name": "Tweaks",
"info": "Tweaks to apply to the flow.", "info": "Tweaks to apply to the flow.",
}, },
"get_final_results_only": {
"display_name": "Get Final Results Only",
"info": "If False, the output will contain all outputs from the flow.",
"advanced": True,
},
} }
def build_records_from_result_data(self, result_data: ResultData) -> List[Record]: def build_records_from_result_data(self, result_data: ResultData, get_final_results_only: bool) -> List[Record]:
messages = result_data.messages messages = result_data.messages
if not messages: if not messages:
return [] return []
records = [] records = []
for message in messages: for message in messages:
message_dict = message if isinstance(message, dict) else message.model_dump() message_dict = message if isinstance(message, dict) else message.model_dump()
record = Record(data={"result": result_data.model_dump(), "message": message_dict.get("message", "")}) if get_final_results_only:
result_data_dict = result_data.model_dump()
results = result_data_dict.get("results", {})
inner_result = results.get("result", {})
record = Record(data={"result": inner_result, "message": message_dict}, text_key="result")
records.append(record) records.append(record)
return records return records
async def build(self, flow_name: str, **kwargs) -> List[Record]: async def build(self, flow_name: str, get_final_results_only: bool = True, **kwargs) -> List[Record]:
tweaks = {key: {"input_value": value} for key, value in kwargs.items()} tweaks = {key: {"input_value": value} for key, value in kwargs.items()}
run_outputs: List[Optional[RunOutputs]] = await self.run_flow( run_outputs: List[Optional[RunOutputs]] = await self.run_flow(
tweaks=tweaks, tweaks=tweaks,
@ -112,7 +121,7 @@ class SubFlowComponent(CustomComponent):
if run_output is not None: if run_output is not None:
for output in run_output.outputs: for output in run_output.outputs:
if output: if output:
records.extend(self.build_records_from_result_data(output)) records.extend(self.build_records_from_result_data(output, get_final_results_only))
self.status = records self.status = records
logger.debug(records) logger.debug(records)

View file

@ -4,6 +4,7 @@ from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record from langflow.schema import Record
from langflow.field_typing import Text from langflow.field_typing import Text
class TextOperatorComponent(CustomComponent): class TextOperatorComponent(CustomComponent):
display_name = "Text Operator" display_name = "Text Operator"
description = "Compares two text inputs based on a specified condition such as equality or inequality, with optional case sensitivity." description = "Compares two text inputs based on a specified condition such as equality or inequality, with optional case sensitivity."
@ -21,14 +22,7 @@ class TextOperatorComponent(CustomComponent):
"operator": { "operator": {
"display_name": "Operator", "display_name": "Operator",
"info": "The operator to apply for comparing the texts.", "info": "The operator to apply for comparing the texts.",
"options": [ "options": ["equals", "not equals", "contains", "starts with", "ends with", "exists"],
"equals",
"not equals",
"contains",
"starts with",
"ends with",
"exists"
],
}, },
"case_sensitive": { "case_sensitive": {
"display_name": "Case Sensitive", "display_name": "Case Sensitive",
@ -51,11 +45,8 @@ class TextOperatorComponent(CustomComponent):
case_sensitive: bool = False, case_sensitive: bool = False,
true_output: Optional[Text] = "", true_output: Optional[Text] = "",
) -> Union[Text, Record]: ) -> Union[Text, Record]:
if not input_text or not match_text: if not input_text or not match_text:
raise ValueError( raise ValueError("Both 'input_text' and 'match_text' must be provided and non-empty.")
"Both 'input_text' and 'match_text' must be provided and non-empty."
)
if not case_sensitive: if not case_sensitive:
input_text = input_text.lower() input_text = input_text.lower()

View file

@ -0,0 +1,91 @@
from typing import Optional, cast
from langchain_astradb.chat_message_histories import AstraDBChatMessageHistory
from langflow.base.memory.memory import BaseMemoryComponent
from langflow.field_typing import Text
from langflow.schema.schema import Record
class AstraDBMessageReaderComponent(BaseMemoryComponent):
display_name = "Astra DB Message Reader"
description = "Retrieves stored chat messages from Astra DB."
def build_config(self):
return {
"session_id": {
"display_name": "Session ID",
"info": "Session ID of the chat history.",
"input_types": ["Text"],
},
"collection_name": {
"display_name": "Collection Name",
"info": "Collection name for Astra DB.",
"input_types": ["Text"],
},
"token": {
"display_name": "Astra DB Application Token",
"info": "Token for the Astra DB instance.",
"password": True,
},
"api_endpoint": {
"display_name": "Astra DB API Endpoint",
"info": "API Endpoint for the Astra DB instance.",
"password": True,
},
"namespace": {
"display_name": "Namespace",
"info": "Namespace for the Astra DB instance.",
"input_types": ["Text"],
"advanced": True,
},
}
def get_messages(self, **kwargs) -> list[Record]:
"""
Retrieves messages from the AstraDBChatMessageHistory memory.
Args:
memory (AstraDBChatMessageHistory): The AstraDBChatMessageHistory instance to retrieve messages from.
Returns:
list[Record]: A list of Record objects representing the search results.
"""
memory: AstraDBChatMessageHistory = cast(AstraDBChatMessageHistory, kwargs.get("memory"))
if not memory:
raise ValueError("AstraDBChatMessageHistory instance is required.")
# Get messages from the memory
messages = memory.messages
results = [Record.from_lc_message(message) for message in messages]
return list(results)
def build(
self,
session_id: Text,
collection_name: str,
token: str,
api_endpoint: str,
namespace: Optional[str] = None,
) -> list[Record]:
try:
pass
except ImportError:
raise ImportError(
"Could not import langchain Astra DB integration package. "
"Please install it with `pip install langchain-astradb`."
)
memory = AstraDBChatMessageHistory(
session_id=session_id,
collection_name=collection_name,
token=token,
api_endpoint=api_endpoint,
namespace=namespace,
)
records = self.get_messages(memory=memory)
self.status = records
return records

View file

@ -0,0 +1,117 @@
from typing import Optional
from langflow.base.memory.memory import BaseMemoryComponent
from langflow.field_typing import Text
from langflow.schema.schema import Record
from langchain_core.messages import BaseMessage
from langchain_astradb import AstraDBChatMessageHistory
class AstraDBMessageWriterComponent(BaseMemoryComponent):
display_name = "Astra DB Message Writer"
description = "Writes a message to Astra DB."
def build_config(self):
return {
"input_value": {
"display_name": "Input Record",
"info": "Record to write to Astra DB.",
},
"session_id": {
"display_name": "Session ID",
"info": "Session ID of the chat history.",
"input_types": ["Text"],
},
"collection_name": {
"display_name": "Collection Name",
"info": "Collection name for Astra DB.",
"input_types": ["Text"],
},
"token": {
"display_name": "Astra DB Application Token",
"info": "Token for the Astra DB instance.",
"password": True,
},
"api_endpoint": {
"display_name": "Astra DB API Endpoint",
"info": "API Endpoint for the Astra DB instance.",
"password": True,
},
"namespace": {
"display_name": "Namespace",
"info": "Namespace for the Astra DB instance.",
"input_types": ["Text"],
"advanced": True,
},
}
def add_message(
self,
sender: str,
sender_name: str,
text: Text,
session_id: str,
metadata: Optional[dict] = None,
**kwargs,
):
"""
Adds a message to the AstraDBChatMessageHistory memory.
Args:
sender (Text): The type of the message sender. Valid values are "Machine" or "User".
sender_name (Text): The name of the message sender.
text (Text): The content of the message.
session_id (Text): The session ID associated with the message.
metadata (dict | None, optional): Additional metadata for the message. Defaults to None.
**kwargs: Additional keyword arguments.
Raises:
ValueError: If the AstraDBChatMessageHistory instance is not provided.
"""
memory: AstraDBChatMessageHistory | None = kwargs.pop("memory", None)
if memory is None:
raise ValueError("AstraDBChatMessageHistory instance is required.")
text_list = [
BaseMessage(
content=text,
sender=sender,
sender_name=sender_name,
metadata=metadata,
session_id=session_id,
)
]
memory.add_messages(text_list)
def build(
self,
input_value: Record,
session_id: Text,
collection_name: str,
token: str,
api_endpoint: str,
namespace: Optional[str] = None,
) -> Record:
try:
pass
except ImportError:
raise ImportError(
"Could not import langchain Astra DB integration package. "
"Please install it with `pip install langchain-astradb`."
)
memory = AstraDBChatMessageHistory(
session_id=session_id,
collection_name=collection_name,
token=token,
api_endpoint=api_endpoint,
namespace=namespace,
)
self.add_message(**input_value.data, memory=memory)
self.status = f"Added message to Astra DB memory for session {session_id}"
return input_value

View file

@ -1,10 +1,9 @@
from typing import Optional from typing import Optional
from langchain.llms.base import BaseLanguageModel
from langchain_anthropic import ChatAnthropic from langchain_anthropic import ChatAnthropic
from pydantic.v1 import SecretStr from pydantic.v1 import SecretStr
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.language_models import BaseLanguageModel
class ChatAntropicSpecsComponent(CustomComponent): class ChatAntropicSpecsComponent(CustomComponent):

View file

@ -1,9 +1,9 @@
from typing import Optional from typing import Optional
from langchain.llms.base import BaseLanguageModel
from langchain_community.chat_models.azure_openai import AzureChatOpenAI
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.language_models import BaseLanguageModel
from langchain_openai import AzureChatOpenAI
from pydantic.v1 import SecretStr
class AzureChatOpenAISpecsComponent(CustomComponent): class AzureChatOpenAISpecsComponent(CustomComponent):
@ -84,13 +84,17 @@ class AzureChatOpenAISpecsComponent(CustomComponent):
temperature: float = 0.7, temperature: float = 0.7,
max_tokens: Optional[int] = 1000, max_tokens: Optional[int] = 1000,
) -> BaseLanguageModel: ) -> BaseLanguageModel:
if api_key:
azure_api_key = SecretStr(api_key)
else:
azure_api_key = None
try: try:
llm = AzureChatOpenAI( llm = AzureChatOpenAI(
model=model, model=model,
azure_endpoint=azure_endpoint, azure_endpoint=azure_endpoint,
azure_deployment=azure_deployment, azure_deployment=azure_deployment,
api_version=api_version, api_version=api_version,
api_key=api_key, api_key=azure_api_key,
temperature=temperature, temperature=temperature,
max_tokens=max_tokens, max_tokens=max_tokens,
) )

View file

@ -1,6 +1,8 @@
from typing import Optional from typing import Optional
from langchain_community.chat_models.openai import ChatOpenAI from langchain_openai import ChatOpenAI
from pydantic.v1 import SecretStr
from langflow.base.models.openai_constants import MODEL_NAMES from langflow.base.models.openai_constants import MODEL_NAMES
from langflow.field_typing import BaseLanguageModel, NestedDict from langflow.field_typing import BaseLanguageModel, NestedDict
@ -59,11 +61,15 @@ class ChatOpenAIComponent(CustomComponent):
) -> BaseLanguageModel: ) -> BaseLanguageModel:
if not openai_api_base: if not openai_api_base:
openai_api_base = "https://api.openai.com/v1" openai_api_base = "https://api.openai.com/v1"
if openai_api_key:
api_key = SecretStr(openai_api_key)
else:
api_key = None
return ChatOpenAI( return ChatOpenAI(
max_tokens=max_tokens, max_tokens=max_tokens,
model_kwargs=model_kwargs, model_kwargs=model_kwargs,
model=model_name, model=model_name,
base_url=openai_api_base, base_url=openai_api_base,
api_key=openai_api_key, api_key=api_key,
temperature=temperature, temperature=temperature,
) )

View file

@ -0,0 +1,10 @@
from langflow.custom import CustomComponent
from langflow.schema import Record
class RecordsOutput(CustomComponent):
display_name = "Records Output"
description = "Display Records as a Table"
def build(self, input_value: Record) -> Record:
return input_value

View file

@ -1,9 +1,8 @@
from typing import Optional from typing import Optional
from langchain.schema import BaseRetriever
from langchain_community.retrievers import AmazonKendraRetriever from langchain_community.retrievers import AmazonKendraRetriever
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.retrievers import BaseRetriever
class AmazonKendraRetrieverComponent(CustomComponent): class AmazonKendraRetrieverComponent(CustomComponent):

View file

@ -1,10 +1,9 @@
from typing import Optional from typing import Optional
from langchain.schema import BaseRetriever
from langchain_community.retrievers import MetalRetriever from langchain_community.retrievers import MetalRetriever
from metal_sdk.metal import Metal # type: ignore from metal_sdk.metal import Metal # type: ignore
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.retrievers import BaseRetriever
class MetalRetrieverComponent(CustomComponent): class MetalRetrieverComponent(CustomComponent):

View file

@ -1,13 +1,12 @@
import json import json
from typing import List from typing import List
from langchain.base_language import BaseLanguageModel
from langchain.chains.query_constructor.base import AttributeInfo from langchain.chains.query_constructor.base import AttributeInfo
from langchain.retrievers.self_query.base import SelfQueryRetriever from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.schema import BaseRetriever
from langchain.schema.vectorstore import VectorStore
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.language_models import BaseLanguageModel
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class VectaraSelfQueryRetriverComponent(CustomComponent): class VectaraSelfQueryRetriverComponent(CustomComponent):

View file

@ -1,10 +1,9 @@
from typing import List from typing import List
from langchain.text_splitter import CharacterTextSplitter
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langflow.utils.util import unescape_string from langflow.utils.util import unescape_string
from langchain_text_splitters import CharacterTextSplitter
class CharacterTextSplitterComponent(CustomComponent): class CharacterTextSplitterComponent(CustomComponent):

View file

@ -1,9 +1,8 @@
from typing import List, Optional from typing import List, Optional
from langchain.text_splitter import Language
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter
class LanguageRecursiveTextSplitterComponent(CustomComponent): class LanguageRecursiveTextSplitterComponent(CustomComponent):
@ -61,7 +60,6 @@ class LanguageRecursiveTextSplitterComponent(CustomComponent):
Returns: Returns:
list[str]: The chunks of text. list[str]: The chunks of text.
""" """
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Make sure chunk_size and chunk_overlap are ints # Make sure chunk_size and chunk_overlap are ints
if isinstance(chunk_size, str): if isinstance(chunk_size, str):

View file

@ -1,11 +1,10 @@
from typing import Optional from typing import Optional
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.documents import Document from langchain_core.documents import Document
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema import Record from langflow.schema import Record
from langflow.utils.util import build_loader_repr_from_records, unescape_string from langflow.utils.util import build_loader_repr_from_records, unescape_string
from langchain_text_splitters import RecursiveCharacterTextSplitter
class RecursiveCharacterTextSplitterComponent(CustomComponent): class RecursiveCharacterTextSplitterComponent(CustomComponent):

View file

@ -1,11 +1,9 @@
from typing import List, Union from typing import List, Union
from langchain.agents import tool
from langchain.agents.agent_toolkits.base import BaseToolkit
from langchain.tools import Tool
from metaphor_python import Metaphor # type: ignore from metaphor_python import Metaphor # type: ignore
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_community.agent_toolkits.base import BaseToolkit
from langchain_core.tools import Tool, tool
class MetaphorToolkit(CustomComponent): class MetaphorToolkit(CustomComponent):

View file

@ -1,7 +1,7 @@
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo
from langchain_community.vectorstores import VectorStore
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langchain_core.vectorstores import VectorStore
class VectorStoreInfoComponent(CustomComponent): class VectorStoreInfoComponent(CustomComponent):

View file

@ -1,10 +1,9 @@
import importlib import importlib
from langchain.agents import Tool
from langchain_experimental.utilities import PythonREPL from langchain_experimental.utilities import PythonREPL
from langflow.base.tools.base import build_status_from_tool from langflow.base.tools.base import build_status_from_tool
from langflow.custom import CustomComponent from langflow.custom import CustomComponent
from langchain_core.tools import Tool
class PythonREPLToolComponent(CustomComponent): class PythonREPLToolComponent(CustomComponent):

View file

@ -0,0 +1,69 @@
from typing import List
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Couchbase import CouchbaseComponent
from langflow.field_typing import Embeddings, Text
from langflow.schema import Record
class CouchbaseSearchComponent(LCVectorStoreComponent):
display_name = "Couchbase Search"
description = "Search a Couchbase Vector Store for similar documents."
documentation = "https://python.langchain.com/docs/integrations/vectorstores/couchbase"
icon = "Couchbase"
field_order = [
"couchbase_connection_string",
"couchbase_username",
"couchbase_password",
"bucket_name",
"scope_name",
"collection_name",
"index_name",
]
def build_config(self):
return {
"input_value": {"display_name": "Input"},
"embedding": {"display_name": "Embedding"},
"couchbase_connection_string": {"display_name": "Couchbase Cluster connection string", "required": True},
"couchbase_username": {"display_name": "Couchbase username", "required": True},
"couchbase_password": {"display_name": "Couchbase password", "password": True, "required": True},
"bucket_name": {"display_name": "Bucket Name", "required": True},
"scope_name": {"display_name": "Scope Name", "required": True},
"collection_name": {"display_name": "Collection Name", "required": True},
"index_name": {"display_name": "Index Name", "required": True},
"number_of_results": {
"display_name": "Number of Results",
"info": "Number of results to return.",
"advanced": True,
},
}
def build( # type: ignore[override]
self,
input_value: Text,
embedding: Embeddings,
number_of_results: int = 4,
bucket_name: str = "",
scope_name: str = "",
collection_name: str = "",
index_name: str = "",
couchbase_connection_string: str = "",
couchbase_username: str = "",
couchbase_password: str = "",
) -> List[Record]:
vector_store = CouchbaseComponent().build(
couchbase_connection_string=couchbase_connection_string,
couchbase_username=couchbase_username,
couchbase_password=couchbase_password,
bucket_name=bucket_name,
scope_name=scope_name,
collection_name=collection_name,
embedding=embedding,
index_name=index_name,
)
if not vector_store:
raise ValueError("Failed to create Couchbase Vector Store")
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type="similarity", k=number_of_results
)

View file

@ -1,11 +1,10 @@
from typing import List, Optional from typing import List, Optional
from langchain.embeddings.base import Embeddings
from langflow.components.vectorstores.base.model import LCVectorStoreComponent from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Redis import RedisComponent from langflow.components.vectorstores.Redis import RedisComponent
from langflow.field_typing import Text from langflow.field_typing import Text
from langflow.schema import Record from langflow.schema import Record
from langchain_core.embeddings import Embeddings
class RedisSearchComponent(RedisComponent, LCVectorStoreComponent): class RedisSearchComponent(RedisComponent, LCVectorStoreComponent):

View file

@ -1,11 +1,10 @@
from typing import List, Optional from typing import List, Optional
from langchain.embeddings.base import Embeddings
from langflow.components.vectorstores.base.model import LCVectorStoreComponent from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Weaviate import WeaviateVectorStoreComponent from langflow.components.vectorstores.Weaviate import WeaviateVectorStoreComponent
from langflow.field_typing import Text from langflow.field_typing import Text
from langflow.schema import Record from langflow.schema import Record
from langchain_core.embeddings import Embeddings
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent): class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):

View file

@ -9,10 +9,12 @@ from .SupabaseVectorStoreSearch import SupabaseSearchComponent
from .VectaraSearch import VectaraSearchComponent from .VectaraSearch import VectaraSearchComponent
from .WeaviateSearch import WeaviateSearchVectorStore from .WeaviateSearch import WeaviateSearchVectorStore
from .pgvectorSearch import PGVectorSearchComponent from .pgvectorSearch import PGVectorSearchComponent
from .Couchbase import CouchbaseSearchComponent # type: ignore
__all__ = [ __all__ = [
"AstraDBSearchComponent", "AstraDBSearchComponent",
"ChromaSearchComponent", "ChromaSearchComponent",
"CouchbaseSearchComponent",
"FAISSSearchComponent", "FAISSSearchComponent",
"MongoDBAtlasSearchComponent", "MongoDBAtlasSearchComponent",
"PineconeSearchComponent", "PineconeSearchComponent",

View file

@ -1,11 +1,10 @@
from typing import List from typing import List
from langchain.embeddings.base import Embeddings
from langflow.components.vectorstores.base.model import LCVectorStoreComponent from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.pgvector import PGVectorComponent from langflow.components.vectorstores.pgvector import PGVectorComponent
from langflow.field_typing import Text from langflow.field_typing import Text
from langflow.schema import Record from langflow.schema import Record
from langchain_core.embeddings import Embeddings
class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent): class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent):

View file

@ -1,12 +1,11 @@
from typing import List, Optional, Union from typing import List, Optional, Union
from langchain.schema import BaseRetriever
from langchain_astradb import AstraDBVectorStore from langchain_astradb import AstraDBVectorStore
from langchain_astradb.utils.astradb import SetupMode from langchain_astradb.utils.astradb import SetupMode
from langflow.custom import CustomComponent from langflow.custom import CustomComponent
from langflow.field_typing import Embeddings, VectorStore from langflow.field_typing import Embeddings, VectorStore
from langflow.schema import Record from langflow.schema import Record
from langchain_core.retrievers import BaseRetriever
class AstraDBVectorStoreComponent(CustomComponent): class AstraDBVectorStoreComponent(CustomComponent):

View file

@ -1,13 +1,13 @@
from typing import List, Optional, Union from typing import List, Optional, Union
import chromadb # type: ignore import chromadb # type: ignore
from langchain.embeddings.base import Embeddings
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.chroma import Chroma from langchain_community.vectorstores.chroma import Chroma
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langchain_core.embeddings import Embeddings
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class ChromaComponent(CustomComponent): class ChromaComponent(CustomComponent):

View file

@ -0,0 +1,90 @@
from typing import List, Optional, Union
from langchain_community.vectorstores import CouchbaseVectorStore
from langflow.custom import CustomComponent
from langflow.field_typing import Embeddings, VectorStore
from langflow.schema import Record
from datetime import timedelta
from couchbase.auth import PasswordAuthenticator # type: ignore
from couchbase.cluster import Cluster # type: ignore
from couchbase.options import ClusterOptions # type: ignore
from langchain_core.retrievers import BaseRetriever
class CouchbaseComponent(CustomComponent):
display_name = "Couchbase"
description = "Construct a `Couchbase Vector Search` vector store from raw documents."
documentation = "https://python.langchain.com/docs/integrations/vectorstores/couchbase"
icon = "Couchbase"
field_order = [
"couchbase_connection_string",
"couchbase_username",
"couchbase_password",
"bucket_name",
"scope_name",
"collection_name",
"index_name",
]
def build_config(self):
return {
"inputs": {"display_name": "Input", "input_types": ["Document", "Record"]},
"embedding": {"display_name": "Embedding"},
"couchbase_connection_string": {"display_name": "Couchbase Cluster connection string", "required": True},
"couchbase_username": {"display_name": "Couchbase username", "required": True},
"couchbase_password": {"display_name": "Couchbase password", "password": True, "required": True},
"bucket_name": {"display_name": "Bucket Name", "required": True},
"scope_name": {"display_name": "Scope Name", "required": True},
"collection_name": {"display_name": "Collection Name", "required": True},
"index_name": {"display_name": "Index Name", "required": True},
}
def build(
self,
embedding: Embeddings,
inputs: Optional[List[Record]] = None,
bucket_name: str = "",
scope_name: str = "",
collection_name: str = "",
index_name: str = "",
couchbase_connection_string: str = "",
couchbase_username: str = "",
couchbase_password: str = "",
) -> Union[VectorStore, BaseRetriever]:
try:
auth = PasswordAuthenticator(couchbase_username, couchbase_password)
options = ClusterOptions(auth)
cluster = Cluster(couchbase_connection_string, options)
cluster.wait_until_ready(timedelta(seconds=5))
except Exception as e:
raise ValueError(f"Failed to connect to Couchbase: {e}")
documents = []
for _input in inputs or []:
if isinstance(_input, Record):
documents.append(_input.to_lc_document())
else:
documents.append(_input)
if documents:
vector_store = CouchbaseVectorStore.from_documents(
documents=documents,
cluster=cluster,
bucket_name=bucket_name,
scope_name=scope_name,
collection_name=collection_name,
embedding=embedding,
index_name=index_name,
)
else:
vector_store = CouchbaseVectorStore(
cluster=cluster,
bucket_name=bucket_name,
scope_name=scope_name,
collection_name=collection_name,
embedding=embedding,
index_name=index_name,
)
return vector_store

View file

@ -1,12 +1,11 @@
from typing import List, Text, Union from typing import List, Text, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.faiss import FAISS from langchain_community.vectorstores.faiss import FAISS
from langflow.field_typing import Embeddings from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class FAISSComponent(CustomComponent): class FAISSComponent(CustomComponent):

View file

@ -1,7 +1,4 @@
from typing import List, Optional, Union from typing import List, Optional, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_core.documents import Document from langchain_core.documents import Document
from langchain_pinecone._utilities import DistanceStrategy from langchain_pinecone._utilities import DistanceStrategy
from langchain_pinecone.vectorstores import PineconeVectorStore from langchain_pinecone.vectorstores import PineconeVectorStore
@ -9,6 +6,8 @@ from langchain_pinecone.vectorstores import PineconeVectorStore
from langflow.field_typing import Embeddings from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class PineconeComponent(CustomComponent): class PineconeComponent(CustomComponent):

View file

@ -1,12 +1,11 @@
from typing import Optional, Union from typing import Optional, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.qdrant import Qdrant from langchain_community.vectorstores.qdrant import Qdrant
from langflow.field_typing import Embeddings from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class QdrantComponent(CustomComponent): class QdrantComponent(CustomComponent):

View file

@ -1,12 +1,11 @@
from typing import Optional, Union from typing import Optional, Union
from langchain.embeddings.base import Embeddings
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.redis import Redis from langchain_community.vectorstores.redis import Redis
from langchain_core.retrievers import BaseRetriever from langchain_core.retrievers import BaseRetriever
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langchain_core.embeddings import Embeddings
from langchain_core.vectorstores import VectorStore
class RedisComponent(CustomComponent): class RedisComponent(CustomComponent):

View file

@ -1,13 +1,12 @@
from typing import List, Optional, Union from typing import List, Optional, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.supabase import SupabaseVectorStore from langchain_community.vectorstores.supabase import SupabaseVectorStore
from supabase.client import Client, create_client from supabase.client import Client, create_client
from langflow.field_typing import Embeddings from langflow.field_typing import Embeddings
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class SupabaseComponent(CustomComponent): class SupabaseComponent(CustomComponent):

View file

@ -1,13 +1,14 @@
from typing import Optional, Union from typing import Optional, Union
import weaviate # type: ignore import weaviate # type: ignore
from langchain.embeddings.base import Embeddings from langchain_community.vectorstores import Weaviate
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore, Weaviate
from langchain_core.documents import Document from langchain_core.documents import Document
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langchain_core.embeddings import Embeddings
from langchain_core.retrievers import BaseRetriever
from langchain_core.vectorstores import VectorStore
class WeaviateVectorStoreComponent(CustomComponent): class WeaviateVectorStoreComponent(CustomComponent):

View file

@ -9,10 +9,12 @@ from .SupabaseVectorStore import SupabaseComponent
from .Vectara import VectaraComponent from .Vectara import VectaraComponent
from .Weaviate import WeaviateVectorStoreComponent from .Weaviate import WeaviateVectorStoreComponent
from .pgvector import PGVectorComponent from .pgvector import PGVectorComponent
from .Couchbase import CouchbaseComponent
__all__ = [ __all__ = [
"AstraDBVectorStoreComponent", "AstraDBVectorStoreComponent",
"ChromaComponent", "ChromaComponent",
"CouchbaseComponent",
"FAISSComponent", "FAISSComponent",
"MongoDBAtlasComponent", "MongoDBAtlasComponent",
"PineconeComponent", "PineconeComponent",

View file

@ -1,12 +1,11 @@
from typing import Optional, Union from typing import Optional, Union
from langchain.embeddings.base import Embeddings
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.pgvector import PGVector from langchain_community.vectorstores.pgvector import PGVector
from langchain_core.retrievers import BaseRetriever from langchain_core.retrievers import BaseRetriever
from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.custom.custom_component import CustomComponent
from langflow.schema.schema import Record from langflow.schema.schema import Record
from langchain_core.embeddings import Embeddings
from langchain_core.vectorstores import VectorStore
class PGVectorComponent(CustomComponent): class PGVectorComponent(CustomComponent):

View file

@ -2,17 +2,18 @@ from typing import Callable, Dict, Text, Union
from langchain.agents.agent import AgentExecutor from langchain.agents.agent import AgentExecutor
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.document_loaders.base import BaseLoader
from langchain.llms.base import BaseLLM
from langchain.memory.chat_memory import BaseChatMemory from langchain.memory.chat_memory import BaseChatMemory
from langchain.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate from langchain_core.document_loaders import BaseLoader
from langchain.schema import BaseOutputParser, BaseRetriever, Document from langchain_core.documents import Document
from langchain.schema.embeddings import Embeddings from langchain_core.embeddings import Embeddings
from langchain.schema.language_model import BaseLanguageModel from langchain_core.language_models import BaseLLM, BaseLanguageModel
from langchain.schema.memory import BaseMemory from langchain_core.memory import BaseMemory
from langchain.text_splitter import TextSplitter from langchain_core.output_parsers import BaseOutputParser
from langchain.tools import Tool from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
from langchain_community.vectorstores import VectorStore from langchain_core.retrievers import BaseRetriever
from langchain_core.tools import Tool
from langchain_core.vectorstores import VectorStore
from langchain_text_splitters import TextSplitter
# Type alias for more complex dicts # Type alias for more complex dicts
NestedDict = Dict[str, Union[str, Dict]] NestedDict = Dict[str, Union[str, Dict]]

View file

@ -3,9 +3,7 @@ from typing import TYPE_CHECKING, Any, List, Optional
from loguru import logger from loguru import logger
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from langflow.graph.edge.utils import build_clean_params
from langflow.schema.schema import INPUT_FIELD_NAME from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.deps import get_monitor_service
from langflow.services.monitor.utils import log_message from langflow.services.monitor.utils import log_message
if TYPE_CHECKING: if TYPE_CHECKING:
@ -143,7 +141,6 @@ class ContractEdge(Edge):
if not self.is_fulfilled: if not self.is_fulfilled:
await self.honor(source, target) await self.honor(source, target)
log_transaction(self, source, target, "success")
# If the target vertex is a power component we log messages # If the target vertex is a power component we log messages
if target.vertex_type == "ChatOutput" and ( if target.vertex_type == "ChatOutput" and (
isinstance(target.params.get(INPUT_FIELD_NAME), str) isinstance(target.params.get(INPUT_FIELD_NAME), str)
@ -157,26 +154,9 @@ class ContractEdge(Edge):
message=target.params.get(INPUT_FIELD_NAME, {}), message=target.params.get(INPUT_FIELD_NAME, {}),
session_id=target.params.get("session_id", ""), session_id=target.params.get("session_id", ""),
artifacts=target.artifacts, artifacts=target.artifacts,
flow_id=target.graph.flow_id,
) )
return self.result return self.result
def __repr__(self) -> str: def __repr__(self) -> str:
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}" return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
def log_transaction(edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None):
try:
monitor_service = get_monitor_service()
clean_params = build_clean_params(target)
data = {
"source": source.vertex_type,
"target": target.vertex_type,
"target_args": clean_params,
"timestamp": monitor_service.get_timestamp(),
"status": status,
"error": error,
}
monitor_service.add_row(table_name="transactions", data=data)
except Exception as e:
logger.error(f"Error logging transaction: {e}")
logger.error(f"Error logging transaction: {e}")

View file

@ -1,19 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
def build_clean_params(target: "Vertex") -> dict:
"""
Cleans the parameters of the target vertex.
"""
# Removes all keys that the values aren't python types like str, int, bool, etc.
params = {
key: value for key, value in target.params.items() if isinstance(value, (str, int, bool, float, list, dict))
}
# if it is a list we need to check if the contents are python types
for key, value in params.items():
if isinstance(value, list):
params[key] = [item for item in value if isinstance(item, (str, int, bool, float, list, dict))]
return params

View file

@ -14,7 +14,7 @@ from langflow.graph.graph.state_manager import GraphStateManager
from langflow.graph.graph.utils import process_flow from langflow.graph.graph.utils import process_flow
from langflow.graph.schema import InterfaceComponentTypes, RunOutputs from langflow.graph.schema import InterfaceComponentTypes, RunOutputs
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import ChatVertex, FileToolVertex, LLMVertex, StateVertex, ToolkitVertex from langflow.graph.vertex.types import FileToolVertex, InterfaceVertex, LLMVertex, StateVertex, ToolkitVertex
from langflow.interface.tools.constants import FILE_TOOLS from langflow.interface.tools.constants import FILE_TOOLS
from langflow.schema import Record from langflow.schema import Record
from langflow.schema.schema import INPUT_FIELD_NAME, InputType from langflow.schema.schema import INPUT_FIELD_NAME, InputType
@ -242,6 +242,7 @@ class Graph:
outputs: list[str], outputs: list[str],
stream: bool, stream: bool,
session_id: str, session_id: str,
fallback_to_env_vars: bool,
) -> List[Optional["ResultData"]]: ) -> List[Optional["ResultData"]]:
""" """
Runs the graph with the given inputs. Runs the graph with the given inputs.
@ -289,7 +290,7 @@ class Graph:
start_component_id = next( start_component_id = next(
(vertex_id for vertex_id in self._is_input_vertices if "chat" in vertex_id.lower()), None (vertex_id for vertex_id in self._is_input_vertices if "chat" in vertex_id.lower()), None
) )
await self.process(start_component_id=start_component_id) await self.process(start_component_id=start_component_id, fallback_to_env_vars=fallback_to_env_vars)
self.increment_run_count() self.increment_run_count()
except Exception as exc: except Exception as exc:
logger.exception(exc) logger.exception(exc)
@ -315,6 +316,7 @@ class Graph:
outputs: Optional[list[str]] = None, outputs: Optional[list[str]] = None,
session_id: Optional[str] = None, session_id: Optional[str] = None,
stream: bool = False, stream: bool = False,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]: ) -> List[RunOutputs]:
""" """
Run the graph with the given inputs and return the outputs. Run the graph with the given inputs and return the outputs.
@ -340,6 +342,7 @@ class Graph:
outputs=outputs, outputs=outputs,
session_id=session_id, session_id=session_id,
stream=stream, stream=stream,
fallback_to_env_vars=fallback_to_env_vars,
) )
try: try:
@ -362,6 +365,7 @@ class Graph:
outputs: Optional[list[str]] = None, outputs: Optional[list[str]] = None,
session_id: Optional[str] = None, session_id: Optional[str] = None,
stream: bool = False, stream: bool = False,
fallback_to_env_vars: bool = False,
) -> List[RunOutputs]: ) -> List[RunOutputs]:
""" """
Runs the graph with the given inputs. Runs the graph with the given inputs.
@ -403,6 +407,7 @@ class Graph:
outputs=outputs or [], outputs=outputs or [],
stream=stream, stream=stream,
session_id=session_id or "", session_id=session_id or "",
fallback_to_env_vars=fallback_to_env_vars,
) )
run_output_object = RunOutputs(inputs=run_inputs, outputs=run_outputs) run_output_object = RunOutputs(inputs=run_inputs, outputs=run_outputs)
logger.debug(f"Run outputs: {run_output_object}") logger.debug(f"Run outputs: {run_output_object}")
@ -468,9 +473,9 @@ class Graph:
"""Marks a branch of the graph.""" """Marks a branch of the graph."""
if visited is None: if visited is None:
visited = set() visited = set()
visited.add(vertex_id)
if vertex_id in visited: if vertex_id in visited:
return return
visited.add(vertex_id)
self.mark_vertex(vertex_id, state) self.mark_vertex(vertex_id, state)
@ -712,6 +717,7 @@ class Graph:
vertex_id: str, vertex_id: str,
inputs_dict: Optional[Dict[str, str]] = None, inputs_dict: Optional[Dict[str, str]] = None,
user_id: Optional[str] = None, user_id: Optional[str] = None,
fallback_to_env_vars: bool = False,
): ):
""" """
Builds a vertex in the graph. Builds a vertex in the graph.
@ -733,7 +739,7 @@ class Graph:
vertex = self.get_vertex(vertex_id) vertex = self.get_vertex(vertex_id)
try: try:
if not vertex.frozen or not vertex._built: if not vertex.frozen or not vertex._built:
await vertex.build(user_id=user_id, inputs=inputs_dict) await vertex.build(user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars)
if vertex.result is not None: if vertex.result is not None:
params = vertex._built_object_repr() params = vertex._built_object_repr()
@ -796,7 +802,7 @@ class Graph:
vertices.append(vertex) vertices.append(vertex)
return vertices return vertices
async def process(self, start_component_id: Optional[str] = None) -> "Graph": async def process(self, fallback_to_env_vars: bool, start_component_id: Optional[str] = None) -> "Graph":
"""Processes the graph with vertices in each layer run in parallel.""" """Processes the graph with vertices in each layer run in parallel."""
first_layer = self.sort_vertices(start_component_id=start_component_id) first_layer = self.sort_vertices(start_component_id=start_component_id)
@ -821,6 +827,7 @@ class Graph:
vertex_id=vertex_id, vertex_id=vertex_id,
user_id=self.user_id, user_id=self.user_id,
inputs_dict={}, inputs_dict={},
fallback_to_env_vars=fallback_to_env_vars,
), ),
name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}", name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
) )
@ -987,8 +994,8 @@ class Graph:
"""Returns the node class based on the node type.""" """Returns the node class based on the node type."""
# First we check for the node_base_type # First we check for the node_base_type
node_name = node_id.split("-")[0] node_name = node_id.split("-")[0]
if node_name in ["ChatOutput", "ChatInput"]: if node_name in InterfaceComponentTypes:
return ChatVertex return InterfaceVertex
elif node_name in ["SharedState", "Notify", "Listen"]: elif node_name in ["SharedState", "Notify", "Listen"]:
return StateVertex return StateVertex
elif node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP: elif node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:

View file

@ -1,3 +1,4 @@
from langflow.graph.schema import CHAT_COMPONENTS
from langflow.graph.vertex import types from langflow.graph.vertex import types
from langflow.interface.agents.base import agent_creator from langflow.interface.agents.base import agent_creator
from langflow.interface.custom.base import custom_component_creator from langflow.interface.custom.base import custom_component_creator
@ -13,8 +14,6 @@ from langflow.interface.tools.base import tool_creator
from langflow.interface.wrappers.base import wrapper_creator from langflow.interface.wrappers.base import wrapper_creator
from langflow.utils.lazy_load import LazyLoadDictBase from langflow.utils.lazy_load import LazyLoadDictBase
CHAT_COMPONENTS = ["ChatInput", "ChatOutput", "TextInput", "SessionID"]
class VertexTypesDict(LazyLoadDictBase): class VertexTypesDict(LazyLoadDictBase):
def __init__(self): def __init__(self):
@ -47,7 +46,7 @@ class VertexTypesDict(LazyLoadDictBase):
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()}, **{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()}, **{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
**{t: types.RetrieverVertex for t in retriever_creator.to_list()}, **{t: types.RetrieverVertex for t in retriever_creator.to_list()},
**{t: types.ChatVertex for t in CHAT_COMPONENTS}, **{t: types.InterfaceVertex for t in CHAT_COMPONENTS},
} }
def get_custom_component_vertex_type(self): def get_custom_component_vertex_type(self):

View file

@ -30,6 +30,7 @@ class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta):
ChatOutput = "ChatOutput" ChatOutput = "ChatOutput"
TextInput = "TextInput" TextInput = "TextInput"
TextOutput = "TextOutput" TextOutput = "TextOutput"
RecordsOutput = "RecordsOutput"
def __contains__(cls, item): def __contains__(cls, item):
try: try:
@ -40,6 +41,8 @@ class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta):
return True return True
CHAT_COMPONENTS = [InterfaceComponentTypes.ChatInput, InterfaceComponentTypes.ChatOutput]
RECORDS_COMPONENTS = [InterfaceComponentTypes.RecordsOutput]
INPUT_COMPONENTS = [ INPUT_COMPONENTS = [
InterfaceComponentTypes.ChatInput, InterfaceComponentTypes.ChatInput,
InterfaceComponentTypes.TextInput, InterfaceComponentTypes.TextInput,

View file

@ -10,7 +10,7 @@ from loguru import logger
from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData
from langflow.graph.utils import UnbuiltObject, UnbuiltResult from langflow.graph.utils import UnbuiltObject, UnbuiltResult
from langflow.graph.vertex.utils import generate_result from langflow.graph.vertex.utils import generate_result, log_transaction
from langflow.interface.initialize import loading from langflow.interface.initialize import loading
from langflow.interface.listing import lazy_load_dict from langflow.interface.listing import lazy_load_dict
from langflow.schema.schema import INPUT_FIELD_NAME from langflow.schema.schema import INPUT_FIELD_NAME
@ -317,7 +317,11 @@ class Vertex:
params[field_name] = full_path params[field_name] = full_path
elif field.get("required"): elif field.get("required"):
field_display_name = field.get("display_name") field_display_name = field.get("display_name")
raise ValueError(f"File path not found for {field_display_name} in component {self.display_name}") logger.warning(
f"File path not found for {field_display_name} in component {self.display_name}. Setting to None."
)
params[field_name] = None
elif field.get("type") in DIRECT_TYPES and params.get(field_name) is None: elif field.get("type") in DIRECT_TYPES and params.get(field_name) is None:
val = field.get("value") val = field.get("value")
if field.get("type") == "code": if field.get("type") == "code":
@ -392,13 +396,17 @@ class Vertex:
self.params = self._raw_params.copy() self.params = self._raw_params.copy()
self.updated_raw_params = True self.updated_raw_params = True
async def _build(self, user_id=None): async def _build(
self,
fallback_to_env_vars,
user_id=None,
):
""" """
Initiate the build process. Initiate the build process.
""" """
logger.debug(f"Building {self.display_name}") logger.debug(f"Building {self.display_name}")
await self._build_each_vertex_in_params_dict(user_id) await self._build_each_vertex_in_params_dict(user_id)
await self._get_and_instantiate_class(user_id) await self._get_and_instantiate_class(user_id, fallback_to_env_vars)
self._validate_built_object() self._validate_built_object()
self._built = True self._built = True
@ -434,7 +442,11 @@ class Vertex:
# to the frontend # to the frontend
self.set_artifacts() self.set_artifacts()
artifacts = self.artifacts artifacts = self.artifacts
if isinstance(artifacts, dict):
messages = self.extract_messages_from_artifacts(artifacts) messages = self.extract_messages_from_artifacts(artifacts)
else:
messages = []
result_dict = ResultData( result_dict = ResultData(
results=result_dict, results=result_dict,
artifacts=artifacts, artifacts=artifacts,
@ -502,7 +514,7 @@ class Vertex:
if not self._is_vertex(value): if not self._is_vertex(value):
self.params[key][sub_key] = value self.params[key][sub_key] = value
else: else:
result = await value.get_result() result = await value.get_result(self)
self.params[key][sub_key] = result self.params[key][sub_key] = result
def _is_vertex(self, value): def _is_vertex(self, value):
@ -517,9 +529,7 @@ class Vertex:
""" """
return all(self._is_vertex(vertex) for vertex in value) return all(self._is_vertex(vertex) for vertex in value)
async def get_result( async def get_result(self, requester: "Vertex") -> Any:
self,
) -> Any:
""" """
Retrieves the result of the vertex. Retrieves the result of the vertex.
@ -529,9 +539,9 @@ class Vertex:
The result of the vertex. The result of the vertex.
""" """
async with self._lock: async with self._lock:
return await self._get_result() return await self._get_result(requester)
async def _get_result(self) -> Any: async def _get_result(self, requester: "Vertex") -> Any:
""" """
Retrieves the result of the built component. Retrieves the result of the built component.
@ -541,15 +551,19 @@ class Vertex:
The built result if use_result is True, else the built object. The built result if use_result is True, else the built object.
""" """
if not self._built: if not self._built:
log_transaction(source=self, target=requester, flow_id=self.graph.flow_id, status="error")
raise ValueError(f"Component {self.display_name} has not been built yet") raise ValueError(f"Component {self.display_name} has not been built yet")
return self._built_result if self.use_result else self._built_object
result = self._built_result if self.use_result else self._built_object
log_transaction(source=self, target=requester, flow_id=self.graph.flow_id, status="success")
return result
async def _build_vertex_and_update_params(self, key, vertex: "Vertex"): async def _build_vertex_and_update_params(self, key, vertex: "Vertex"):
""" """
Builds a given vertex and updates the params dictionary accordingly. Builds a given vertex and updates the params dictionary accordingly.
""" """
result = await vertex.get_result() result = await vertex.get_result(self)
self._handle_func(key, result) self._handle_func(key, result)
if isinstance(result, list): if isinstance(result, list):
self._extend_params_list_with_result(key, result) self._extend_params_list_with_result(key, result)
@ -565,7 +579,7 @@ class Vertex:
""" """
self.params[key] = [] self.params[key] = []
for vertex in vertices: for vertex in vertices:
result = await vertex.get_result() result = await vertex.get_result(self)
# Weird check to see if the params[key] is a list # Weird check to see if the params[key] is a list
# because sometimes it is a Record and breaks the code # because sometimes it is a Record and breaks the code
if not isinstance(self.params[key], list): if not isinstance(self.params[key], list):
@ -608,7 +622,7 @@ class Vertex:
if isinstance(self.params[key], list): if isinstance(self.params[key], list):
self.params[key].extend(result) self.params[key].extend(result)
async def _get_and_instantiate_class(self, user_id=None): async def _get_and_instantiate_class(self, user_id=None, fallback_to_env_vars=False):
""" """
Gets the class from a dictionary and instantiates it with the params. Gets the class from a dictionary and instantiates it with the params.
""" """
@ -617,6 +631,7 @@ class Vertex:
try: try:
result = await loading.instantiate_class( result = await loading.instantiate_class(
user_id=user_id, user_id=user_id,
fallback_to_env_vars=fallback_to_env_vars,
vertex=self, vertex=self,
) )
self._update_built_object_and_artifacts(result) self._update_built_object_and_artifacts(result)

View file

@ -6,14 +6,14 @@ import yaml
from langchain_core.messages import AIMessage from langchain_core.messages import AIMessage
from loguru import logger from loguru import logger
from langflow.graph.schema import InterfaceComponentTypes from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes
from langflow.graph.utils import UnbuiltObject, flatten_list, serialize_field from langflow.graph.utils import UnbuiltObject, flatten_list, serialize_field
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.interface.utils import extract_input_variables_from_prompt from langflow.interface.utils import extract_input_variables_from_prompt
from langflow.schema import Record from langflow.schema import Record
from langflow.schema.schema import INPUT_FIELD_NAME from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.monitor.utils import log_vertex_build from langflow.services.monitor.utils import log_vertex_build
from langflow.utils.schemas import ChatOutputResponse from langflow.utils.schemas import ChatOutputResponse, RecordOutputResponse
from langflow.utils.util import unescape_string from langflow.utils.util import unescape_string
@ -309,7 +309,7 @@ class CustomComponentVertex(Vertex):
return self.artifacts["repr"] or super()._built_object_repr() return self.artifacts["repr"] or super()._built_object_repr()
class ChatVertex(Vertex): class InterfaceVertex(Vertex):
def __init__(self, data: Dict, graph): def __init__(self, data: Dict, graph):
super().__init__(data, graph=graph, base_type="custom_components", is_task=True) super().__init__(data, graph=graph, base_type="custom_components", is_task=True)
self.steps = [self._build, self._run] self.steps = [self._build, self._run]
@ -325,14 +325,45 @@ class ChatVertex(Vertex):
return f"Task {self.task_id} is not running" return f"Task {self.task_id} is not running"
if self.artifacts: if self.artifacts:
# dump as a yaml string # dump as a yaml string
artifacts = {k.title().replace("_", " "): v for k, v in self.artifacts.items() if v is not None} if isinstance(self.artifacts, dict):
_artifacts = [self.artifacts]
elif hasattr(self.artifacts, "records"):
_artifacts = self.artifacts.records
else:
_artifacts = self.artifacts
artifacts = []
for artifact in _artifacts:
# artifacts = {k.title().replace("_", " "): v for k, v in self.artifacts.items() if v is not None}
artifact = {k.title().replace("_", " "): v for k, v in artifact.items() if v is not None}
artifacts.append(artifact)
yaml_str = yaml.dump(artifacts, default_flow_style=False, allow_unicode=True) yaml_str = yaml.dump(artifacts, default_flow_style=False, allow_unicode=True)
return yaml_str return yaml_str
return super()._built_object_repr() return super()._built_object_repr()
async def _run(self, *args, **kwargs): def _process_chat_component(self):
if self.is_interface_component: """
if self.vertex_type in ["ChatOutput", "ChatInput"]: Process the chat component and return the message.
This method processes the chat component by extracting the necessary parameters
such as sender, sender_name, and message from the `params` dictionary. It then
performs additional operations based on the type of the `_built_object` attribute.
If `_built_object` is an instance of `AIMessage`, it creates a `ChatOutputResponse`
object using the `from_message` method. If `_built_object` is not an instance of
`UnbuiltObject`, it checks the type of `_built_object` and performs specific
operations accordingly. If `_built_object` is a dictionary, it converts it into a
code block. If `_built_object` is an instance of `Record`, it assigns the `text`
attribute to the `message` variable. If `message` is an instance of `AsyncIterator`
or `Iterator`, it builds a stream URL and sets `message` to an empty string. If
`_built_object` is not a string, it converts it to a string. If `message` is a
generator or iterator, it assigns it to the `message` variable. Finally, it creates
a `ChatOutputResponse` object using the extracted parameters and assigns it to the
`artifacts` attribute. If `artifacts` is not None, it calls the `model_dump` method
on it and assigns the result to the `artifacts` attribute. It then returns the
`message` variable.
Returns:
str: The processed message.
"""
artifacts = None artifacts = None
sender = self.params.get("sender", None) sender = self.params.get("sender", None)
sender_name = self.params.get("sender_name", None) sender_name = self.params.get("sender_name", None)
@ -373,8 +404,52 @@ class ChatVertex(Vertex):
self.will_stream = stream_url is not None self.will_stream = stream_url is not None
if artifacts: if artifacts:
self.artifacts = artifacts.model_dump(exclude_none=True) self.artifacts = artifacts.model_dump(exclude_none=True)
return message
def _process_record_component(self):
"""
Process the record component of the vertex.
If the built object is an instance of `Record`, it calls the `model_dump` method
and assigns the result to the `artifacts` attribute.
If the built object is a list, it iterates over each element and checks if it is
an instance of `Record`. If it is, it calls the `model_dump` method and appends
the result to the `artifacts` list. If it is not, it raises a `ValueError` if the
`ignore_errors` parameter is set to `False`, or logs an error message if it is set
to `True`.
Returns:
The built object.
Raises:
ValueError: If an element in the list is not an instance of `Record` and
`ignore_errors` is set to `False`.
"""
if isinstance(self._built_object, Record):
artifacts = [self._built_object.data]
elif isinstance(self._built_object, list):
artifacts = []
ignore_errors = self.params.get("ignore_errors", False)
for record in self._built_object:
if isinstance(record, Record):
artifacts.append(record.data)
elif ignore_errors:
logger.error(f"Record expected, but got {record} of type {type(record)}")
else:
raise ValueError(f"Record expected, but got {record} of type {type(record)}")
self.artifacts = RecordOutputResponse(records=artifacts)
return self._built_object
async def _run(self, *args, **kwargs):
if self.is_interface_component:
if self.vertex_type in CHAT_COMPONENTS:
message = self._process_chat_component()
elif self.vertex_type in RECORDS_COMPONENTS:
message = self._process_record_component()
if isinstance(self._built_object, (AsyncIterator, Iterator)): if isinstance(self._built_object, (AsyncIterator, Iterator)):
if self.params["return_record"]: if self.params.get("return_record", False):
self._built_object = Record(text=message, data=self.artifacts) self._built_object = Record(text=message, data=self.artifacts)
else: else:
self._built_object = message self._built_object = message

View file

@ -1,11 +1,15 @@
from typing import Any, Optional, Union from typing import Any, Optional, Union, TYPE_CHECKING
from langchain_core.messages import BaseMessage from langchain_core.messages import BaseMessage
from langchain_core.runnables import Runnable from langchain_core.runnables import Runnable
from loguru import logger from loguru import logger
from langflow.services.deps import get_monitor_service
from langflow.utils.constants import PYTHON_BASIC_TYPES from langflow.utils.constants import PYTHON_BASIC_TYPES
if TYPE_CHECKING:
from langflow.graph.vertex.base import Vertex
def is_basic_type(obj): def is_basic_type(obj):
return type(obj) in PYTHON_BASIC_TYPES return type(obj) in PYTHON_BASIC_TYPES
@ -63,3 +67,49 @@ async def generate_result(built_object: Any, inputs: dict, has_external_output:
else: else:
result = built_object result = built_object
return result return result
def build_clean_params(target: "Vertex") -> dict:
"""
Cleans the parameters of the target vertex.
"""
# Removes all keys that the values aren't python types like str, int, bool, etc.
params = {
key: value for key, value in target.params.items() if isinstance(value, (str, int, bool, float, list, dict))
}
# if it is a list we need to check if the contents are python types
for key, value in params.items():
if isinstance(value, list):
params[key] = [item for item in value if isinstance(item, (str, int, bool, float, list, dict))]
return params
def log_transaction(source: "Vertex", target: "Vertex", flow_id, status, error=None):
"""
Logs a transaction between two vertices.
Args:
source (Vertex): The source vertex of the transaction.
target (Vertex): The target vertex of the transaction.
status: The status of the transaction.
error (Optional): Any error associated with the transaction.
Raises:
Exception: If there is an error while logging the transaction.
"""
try:
monitor_service = get_monitor_service()
clean_params = build_clean_params(target)
data = {
"source": source.vertex_type,
"target": target.vertex_type,
"target_args": clean_params,
"timestamp": monitor_service.get_timestamp(),
"status": status,
"error": error,
"flow_id": flow_id,
}
monitor_service.add_row(table_name="transactions", data=data)
except Exception as e:
logger.error(f"Error logging transaction: {e}")

View file

@ -11,10 +11,11 @@ from sqlmodel import select
from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES
from langflow.interface.types import get_all_components from langflow.interface.types import get_all_components
from langflow.services.database.models.flow.model import Flow, FlowCreate from langflow.services.database.models.flow.model import Flow, FlowCreate
from langflow.services.database.models.folder.model import Folder, FolderCreate
from langflow.services.deps import get_settings_service, session_scope from langflow.services.deps import get_settings_service, session_scope
STARTER_FOLDER_NAME = "Starter Projects" STARTER_FOLDER_NAME = "Starter Projects"
STARTER_FOLDER_DESCRIPTION = "Starter projects to help you get started in Langflow."
# In the folder ./starter_projects we have a few JSON files that represent # In the folder ./starter_projects we have a few JSON files that represent
# starter projects. We want to load these into the database so that users # starter projects. We want to load these into the database so that users
@ -158,6 +159,7 @@ def create_new_project(
project_data, project_data,
project_icon, project_icon,
project_icon_bg_color, project_icon_bg_color,
new_folder_id
): ):
logger.debug(f"Creating starter project {project_name}") logger.debug(f"Creating starter project {project_name}")
new_project = FlowCreate( new_project = FlowCreate(
@ -168,33 +170,41 @@ def create_new_project(
data=project_data, data=project_data,
is_component=project_is_component, is_component=project_is_component,
updated_at=updated_at_datetime, updated_at=updated_at_datetime,
folder=STARTER_FOLDER_NAME, folder_id=new_folder_id,
) )
db_flow = Flow.model_validate(new_project, from_attributes=True) db_flow = Flow.model_validate(new_project, from_attributes=True)
session.add(db_flow) session.add(db_flow)
def get_all_flows_similar_to_project(session, project_name): def get_all_flows_similar_to_project(session, folder_id):
flows = session.exec( flows = session.exec(select(Folder).where(Folder.id == folder_id)).first().flows
select(Flow).where(
Flow.name == project_name,
Flow.folder == STARTER_FOLDER_NAME,
)
).all()
return flows return flows
def delete_start_projects(session): def delete_start_projects(session, folder_id):
flows = session.exec( flows = session.exec(select(Folder).where(Folder.id == folder_id)).first().flows
select(Flow).where(
Flow.folder == STARTER_FOLDER_NAME,
)
).all()
for flow in flows: for flow in flows:
session.delete(flow) session.delete(flow)
session.commit() session.commit()
def folder_exists(session, folder_name):
folder = session.exec(select(Folder).where(Folder.name == folder_name)).first()
return folder is not None
def create_starter_folder(session):
if not folder_exists(session, STARTER_FOLDER_NAME):
new_folder = FolderCreate(name=STARTER_FOLDER_NAME, description=STARTER_FOLDER_DESCRIPTION)
db_folder = Folder.model_validate(new_folder, from_attributes=True)
session.add(db_folder)
session.commit()
session.refresh(db_folder)
return db_folder
else:
return session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
def create_or_update_starter_projects(): def create_or_update_starter_projects():
components_paths = get_settings_service().settings.COMPONENTS_PATH components_paths = get_settings_service().settings.COMPONENTS_PATH
try: try:
@ -203,8 +213,9 @@ def create_or_update_starter_projects():
logger.exception(f"Error loading components: {e}") logger.exception(f"Error loading components: {e}")
raise e raise e
with session_scope() as session: with session_scope() as session:
new_folder = create_starter_folder(session)
starter_projects = load_starter_projects() starter_projects = load_starter_projects()
delete_start_projects(session) delete_start_projects(session, new_folder.id)
for project_path, project in starter_projects: for project_path, project in starter_projects:
( (
project_name, project_name,
@ -224,7 +235,7 @@ def create_or_update_starter_projects():
update_project_file(project_path, project, updated_project_data) update_project_file(project_path, project, updated_project_data)
if project_name and project_data: if project_name and project_data:
for existing_project in get_all_flows_similar_to_project(session, project_name): for existing_project in get_all_flows_similar_to_project(session, new_folder.id):
session.delete(existing_project) session.delete(existing_project)
create_new_project( create_new_project(
@ -236,4 +247,5 @@ def create_or_update_starter_projects():
project_data, project_data,
project_icon, project_icon,
project_icon_bg_color, project_icon_bg_color,
new_folder.id
) )

View file

@ -262,7 +262,7 @@
"load_from_db": true, "load_from_db": true,
"title_case": false, "title_case": false,
"input_types": ["Text"], "input_types": ["Text"],
"value": "" "value": "OPENAI_API_KEY"
}, },
"stream": { "stream": {
"type": "bool", "type": "bool",
@ -716,9 +716,9 @@
"edges": [ "edges": [
{ {
"source": "OpenAIModel-k39HS", "source": "OpenAIModel-k39HS",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-k39HSœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153}",
"target": "ChatOutput-njtka", "target": "ChatOutput-njtka",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-njtkaœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-njtka\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -736,13 +736,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-k39HS{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-k39HSœ}-ChatOutput-njtka{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-njtkaœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-OpenAIModel-k39HS{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153}-ChatOutput-njtka{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-njtka\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "Prompt-uxBqP", "source": "Prompt-uxBqP",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-uxBqPœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153}",
"target": "OpenAIModel-k39HS", "target": "OpenAIModel-k39HS",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-k39HSœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -760,13 +760,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-uxBqP{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-uxBqPœ}-OpenAIModel-k39HS{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-k39HSœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-Prompt-uxBqP{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153}-OpenAIModel-k39HS{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-k39HS\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "ChatInput-P3fgL", "source": "ChatInput-P3fgL",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œRecordœ,œstrœ,œTextœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-P3fgLœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-P3fgL\u0153}",
"target": "Prompt-uxBqP", "target": "Prompt-uxBqP",
"targetHandle": "{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-uxBqPœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153user_input\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "user_input", "fieldName": "user_input",
@ -784,7 +784,7 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-P3fgL{œbaseClassesœ:[œobjectœ,œRecordœ,œstrœ,œTextœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-P3fgLœ}-Prompt-uxBqP{œfieldNameœ:œuser_inputœ,œidœ:œPrompt-uxBqPœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-ChatInput-P3fgL{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-P3fgL\u0153}-Prompt-uxBqP{\u0153fieldName\u0153:\u0153user_input\u0153,\u0153id\u0153:\u0153Prompt-uxBqP\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
} }
], ],
"viewport": { "viewport": {

View file

@ -566,7 +566,7 @@
"load_from_db": false, "load_from_db": false,
"title_case": false, "title_case": false,
"input_types": ["Text"], "input_types": ["Text"],
"value": "" "value": "OPENAI_API_KEY"
}, },
"stream": { "stream": {
"type": "bool", "type": "bool",
@ -854,9 +854,9 @@
{ {
"source": "URL-HYPkR", "source": "URL-HYPkR",
"target": "Prompt-Rse03", "target": "Prompt-Rse03",
"sourceHandle": "{œbaseClassesœ:[œRecordœ],œdataTypeœ:œURLœ,œidœ:œURL-HYPkRœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153URL\u0153,\u0153id\u0153:\u0153URL-HYPkR\u0153}",
"targetHandle": "{œfieldNameœ:œreference_2œ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153reference_2\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"id": "reactflow__edge-URL-HYPkR{œbaseClassesœ:[œRecordœ],œdataTypeœ:œURLœ,œidœ:œURL-HYPkRœ}-Prompt-Rse03{œfieldNameœ:œreference_2œ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "id": "reactflow__edge-URL-HYPkR{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153URL\u0153,\u0153id\u0153:\u0153URL-HYPkR\u0153}-Prompt-Rse03{\u0153fieldName\u0153:\u0153reference_2\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "reference_2", "fieldName": "reference_2",
@ -878,9 +878,9 @@
}, },
{ {
"source": "OpenAIModel-gi29P", "source": "OpenAIModel-gi29P",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-gi29Pœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-gi29P\u0153}",
"target": "ChatOutput-JPlxl", "target": "ChatOutput-JPlxl",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-JPlxlœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-JPlxl\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -898,13 +898,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-gi29P{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-gi29Pœ}-ChatOutput-JPlxl{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-JPlxlœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-OpenAIModel-gi29P{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-gi29P\u0153}-ChatOutput-JPlxl{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-JPlxl\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "URL-2cX90", "source": "URL-2cX90",
"sourceHandle": "{œbaseClassesœ:[œRecordœ],œdataTypeœ:œURLœ,œidœ:œURL-2cX90œ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153URL\u0153,\u0153id\u0153:\u0153URL-2cX90\u0153}",
"target": "Prompt-Rse03", "target": "Prompt-Rse03",
"targetHandle": "{œfieldNameœ:œreference_1œ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153reference_1\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "reference_1", "fieldName": "reference_1",
@ -922,13 +922,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-URL-2cX90{œbaseClassesœ:[œRecordœ],œdataTypeœ:œURLœ,œidœ:œURL-2cX90œ}-Prompt-Rse03{œfieldNameœ:œreference_1œ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-URL-2cX90{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153URL\u0153,\u0153id\u0153:\u0153URL-2cX90\u0153}-Prompt-Rse03{\u0153fieldName\u0153:\u0153reference_1\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "TextInput-og8Or", "source": "TextInput-og8Or",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œTextInputœ,œidœ:œTextInput-og8Orœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153TextInput\u0153,\u0153id\u0153:\u0153TextInput-og8Or\u0153}",
"target": "Prompt-Rse03", "target": "Prompt-Rse03",
"targetHandle": "{œfieldNameœ:œinstructionsœ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153instructions\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "instructions", "fieldName": "instructions",
@ -946,13 +946,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-TextInput-og8Or{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œTextInputœ,œidœ:œTextInput-og8Orœ}-Prompt-Rse03{œfieldNameœ:œinstructionsœ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-TextInput-og8Or{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153TextInput\u0153,\u0153id\u0153:\u0153TextInput-og8Or\u0153}-Prompt-Rse03{\u0153fieldName\u0153:\u0153instructions\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "Prompt-Rse03", "source": "Prompt-Rse03",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-Rse03œ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153}",
"target": "OpenAIModel-gi29P", "target": "OpenAIModel-gi29P",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-gi29Pœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-gi29P\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -970,7 +970,7 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-Rse03{œbaseClassesœ:[œobjectœ,œTextœ,œstrœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-Rse03œ}-OpenAIModel-gi29P{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-gi29Pœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "id": "reactflow__edge-Prompt-Rse03{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153Text\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-Rse03\u0153}-OpenAIModel-gi29P{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-gi29P\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"selected": false "selected": false
} }
], ],

View file

@ -711,7 +711,7 @@
"load_from_db": false, "load_from_db": false,
"title_case": false, "title_case": false,
"input_types": ["Text"], "input_types": ["Text"],
"value": "" "value": "OPENAI_API_KEY"
}, },
"stream": { "stream": {
"type": "bool", "type": "bool",
@ -825,9 +825,9 @@
"edges": [ "edges": [
{ {
"source": "ChatInput-MsSJ9", "source": "ChatInput-MsSJ9",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œRecordœ,œTextœ,œobjectœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-MsSJ9œ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Record\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-MsSJ9\u0153}",
"target": "Prompt-tHwPf", "target": "Prompt-tHwPf",
"targetHandle": "{œfieldNameœ:œQuestionœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153Question\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "Question", "fieldName": "Question",
@ -845,13 +845,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-MsSJ9{œbaseClassesœ:[œstrœ,œRecordœ,œTextœ,œobjectœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-MsSJ9œ}-Prompt-tHwPf{œfieldNameœ:œQuestionœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-ChatInput-MsSJ9{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Record\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-MsSJ9\u0153}-Prompt-tHwPf{\u0153fieldName\u0153:\u0153Question\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "File-6TEsD", "source": "File-6TEsD",
"sourceHandle": "{œbaseClassesœ:[œRecordœ],œdataTypeœ:œFileœ,œidœ:œFile-6TEsDœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153File\u0153,\u0153id\u0153:\u0153File-6TEsD\u0153}",
"target": "Prompt-tHwPf", "target": "Prompt-tHwPf",
"targetHandle": "{œfieldNameœ:œDocumentœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153Document\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "Document", "fieldName": "Document",
@ -869,13 +869,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-File-6TEsD{œbaseClassesœ:[œRecordœ],œdataTypeœ:œFileœ,œidœ:œFile-6TEsDœ}-Prompt-tHwPf{œfieldNameœ:œDocumentœ,œidœ:œPrompt-tHwPfœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-File-6TEsD{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153File\u0153,\u0153id\u0153:\u0153File-6TEsD\u0153}-Prompt-tHwPf{\u0153fieldName\u0153:\u0153Document\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "Prompt-tHwPf", "source": "Prompt-tHwPf",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-tHwPfœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153}",
"target": "OpenAIModel-Bt067", "target": "OpenAIModel-Bt067",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-Bt067œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -893,13 +893,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-tHwPf{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-tHwPfœ}-OpenAIModel-Bt067{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-Bt067œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-Prompt-tHwPf{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-tHwPf\u0153}-OpenAIModel-Bt067{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "OpenAIModel-Bt067", "source": "OpenAIModel-Bt067",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-Bt067œ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153}",
"target": "ChatOutput-F5Awj", "target": "ChatOutput-F5Awj",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-F5Awjœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-F5Awj\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -917,7 +917,7 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-Bt067{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-Bt067œ}-ChatOutput-F5Awj{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-F5Awjœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-OpenAIModel-Bt067{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-Bt067\u0153}-ChatOutput-F5Awj{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-F5Awj\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
} }
], ],
"viewport": { "viewport": {

View file

@ -1,6 +1,6 @@
{ {
"id": "08d5cccf-d098-4367-b14b-1078429c9ed9", "id": "08d5cccf-d098-4367-b14b-1078429c9ed9",
"icon": "🤖", "icon": "\ud83e\udd16",
"icon_bg_color": "#FFD700", "icon_bg_color": "#FFD700",
"data": { "data": {
"nodes": [ "nodes": [
@ -792,7 +792,7 @@
"load_from_db": false, "load_from_db": false,
"title_case": false, "title_case": false,
"input_types": ["Text"], "input_types": ["Text"],
"value": "" "value": "OPENAI_API_KEY"
}, },
"stream": { "stream": {
"type": "bool", "type": "bool",
@ -1003,9 +1003,9 @@
"edges": [ "edges": [
{ {
"source": "MemoryComponent-cdA1J", "source": "MemoryComponent-cdA1J",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}",
"target": "Prompt-ODkUx", "target": "Prompt-ODkUx",
"targetHandle": "{œfieldNameœ:œcontextœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153context\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "context", "fieldName": "context",
@ -1023,14 +1023,14 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-MemoryComponent-cdA1J{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}-Prompt-ODkUx{œfieldNameœ:œcontextœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "id": "reactflow__edge-MemoryComponent-cdA1J{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}-Prompt-ODkUx{\u0153fieldName\u0153:\u0153context\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"selected": false "selected": false
}, },
{ {
"source": "ChatInput-t7F8v", "source": "ChatInput-t7F8v",
"sourceHandle": "{œbaseClassesœ:[œTextœ,œobjectœ,œRecordœ,œstrœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-t7F8vœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-t7F8v\u0153}",
"target": "Prompt-ODkUx", "target": "Prompt-ODkUx",
"targetHandle": "{œfieldNameœ:œuser_messageœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153user_message\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "user_message", "fieldName": "user_message",
@ -1048,14 +1048,14 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-ChatInput-t7F8v{œbaseClassesœ:[œTextœ,œobjectœ,œRecordœ,œstrœ],œdataTypeœ:œChatInputœ,œidœ:œChatInput-t7F8vœ}-Prompt-ODkUx{œfieldNameœ:œuser_messageœ,œidœ:œPrompt-ODkUxœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "id": "reactflow__edge-ChatInput-t7F8v{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153object\u0153,\u0153Record\u0153,\u0153str\u0153],\u0153dataType\u0153:\u0153ChatInput\u0153,\u0153id\u0153:\u0153ChatInput-t7F8v\u0153}-Prompt-ODkUx{\u0153fieldName\u0153:\u0153user_message\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"selected": false "selected": false
}, },
{ {
"source": "Prompt-ODkUx", "source": "Prompt-ODkUx",
"sourceHandle": "{œbaseClassesœ:[œTextœ,œstrœ,œobjectœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-ODkUxœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153str\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153}",
"target": "OpenAIModel-9RykF", "target": "OpenAIModel-9RykF",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-9RykFœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1073,13 +1073,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-ODkUx{œbaseClassesœ:[œTextœ,œstrœ,œobjectœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-ODkUxœ}-OpenAIModel-9RykF{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-9RykFœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-Prompt-ODkUx{\u0153baseClasses\u0153:[\u0153Text\u0153,\u0153str\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-ODkUx\u0153}-OpenAIModel-9RykF{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "OpenAIModel-9RykF", "source": "OpenAIModel-9RykF",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œobjectœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-9RykFœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153object\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153}",
"target": "ChatOutput-P1jEe", "target": "ChatOutput-P1jEe",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-P1jEeœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-P1jEe\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1097,13 +1097,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-9RykF{œbaseClassesœ:[œstrœ,œobjectœ,œTextœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-9RykFœ}-ChatOutput-P1jEe{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-P1jEeœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-OpenAIModel-9RykF{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153object\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-9RykF\u0153}-ChatOutput-P1jEe{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-P1jEe\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "MemoryComponent-cdA1J", "source": "MemoryComponent-cdA1J",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}",
"target": "TextOutput-vrs6T", "target": "TextOutput-vrs6T",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-vrs6Tœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-vrs6T\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1121,7 +1121,7 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-foreground stroke-connection", "className": "stroke-foreground stroke-connection",
"id": "reactflow__edge-MemoryComponent-cdA1J{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œMemoryComponentœ,œidœ:œMemoryComponent-cdA1Jœ}-TextOutput-vrs6T{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-vrs6Tœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-MemoryComponent-cdA1J{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153MemoryComponent\u0153,\u0153id\u0153:\u0153MemoryComponent-cdA1J\u0153}-TextOutput-vrs6T{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-vrs6T\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
} }
], ],
"viewport": { "viewport": {

View file

@ -911,7 +911,7 @@
"load_from_db": false, "load_from_db": false,
"title_case": false, "title_case": false,
"input_types": ["Text"], "input_types": ["Text"],
"value": "" "value": "OPENAI_API_KEY"
}, },
"stream": { "stream": {
"type": "bool", "type": "bool",
@ -1382,9 +1382,9 @@
"edges": [ "edges": [
{ {
"source": "TextInput-sptaH", "source": "TextInput-sptaH",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œTextInputœ,œidœ:œTextInput-sptaHœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153TextInput\u0153,\u0153id\u0153:\u0153TextInput-sptaH\u0153}",
"target": "Prompt-amqBu", "target": "Prompt-amqBu",
"targetHandle": "{œfieldNameœ:œdocumentœ,œidœ:œPrompt-amqBuœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153document\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "document", "fieldName": "document",
@ -1402,13 +1402,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-TextInput-sptaH{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œTextInputœ,œidœ:œTextInput-sptaHœ}-Prompt-amqBu{œfieldNameœ:œdocumentœ,œidœ:œPrompt-amqBuœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-TextInput-sptaH{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153TextInput\u0153,\u0153id\u0153:\u0153TextInput-sptaH\u0153}-Prompt-amqBu{\u0153fieldName\u0153:\u0153document\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "Prompt-amqBu", "source": "Prompt-amqBu",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-amqBuœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153}",
"target": "TextOutput-2MS4a", "target": "TextOutput-2MS4a",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-2MS4aœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-2MS4a\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1426,13 +1426,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-amqBu{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-amqBuœ}-TextOutput-2MS4a{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-2MS4aœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-Prompt-amqBu{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153}-TextOutput-2MS4a{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-2MS4a\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "Prompt-amqBu", "source": "Prompt-amqBu",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-amqBuœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153}",
"target": "OpenAIModel-uYXZJ", "target": "OpenAIModel-uYXZJ",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-uYXZJœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1450,13 +1450,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-amqBu{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-amqBuœ}-OpenAIModel-uYXZJ{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-uYXZJœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-Prompt-amqBu{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-amqBu\u0153}-OpenAIModel-uYXZJ{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "OpenAIModel-uYXZJ", "source": "OpenAIModel-uYXZJ",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-uYXZJœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153}",
"target": "Prompt-gTNiz", "target": "Prompt-gTNiz",
"targetHandle": "{œfieldNameœ:œsummaryœ,œidœ:œPrompt-gTNizœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153summary\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "summary", "fieldName": "summary",
@ -1474,13 +1474,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-uYXZJ{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-uYXZJœ}-Prompt-gTNiz{œfieldNameœ:œsummaryœ,œidœ:œPrompt-gTNizœ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-OpenAIModel-uYXZJ{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153}-Prompt-gTNiz{\u0153fieldName\u0153:\u0153summary\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153,\u0153inputTypes\u0153:[\u0153Document\u0153,\u0153BaseOutputParser\u0153,\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "OpenAIModel-uYXZJ", "source": "OpenAIModel-uYXZJ",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-uYXZJœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153}",
"target": "ChatOutput-EJkG3", "target": "ChatOutput-EJkG3",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-EJkG3œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-EJkG3\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1498,13 +1498,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-uYXZJ{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-uYXZJœ}-ChatOutput-EJkG3{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-EJkG3œ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-OpenAIModel-uYXZJ{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-uYXZJ\u0153}-ChatOutput-EJkG3{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-EJkG3\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "Prompt-gTNiz", "source": "Prompt-gTNiz",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-gTNizœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153}",
"target": "TextOutput-MUDOR", "target": "TextOutput-MUDOR",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-MUDORœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-MUDOR\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1522,13 +1522,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-gTNiz{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-gTNizœ}-TextOutput-MUDOR{œfieldNameœ:œinput_valueœ,œidœ:œTextOutput-MUDORœ,œinputTypesœ:[œRecordœ,œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-Prompt-gTNiz{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153}-TextOutput-MUDOR{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153TextOutput-MUDOR\u0153,\u0153inputTypes\u0153:[\u0153Record\u0153,\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "Prompt-gTNiz", "source": "Prompt-gTNiz",
"sourceHandle": "{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-gTNizœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153}",
"target": "OpenAIModel-XawYB", "target": "OpenAIModel-XawYB",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-XawYBœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-XawYB\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1546,13 +1546,13 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-Prompt-gTNiz{œbaseClassesœ:[œobjectœ,œstrœ,œTextœ],œdataTypeœ:œPromptœ,œidœ:œPrompt-gTNizœ}-OpenAIModel-XawYB{œfieldNameœ:œinput_valueœ,œidœ:œOpenAIModel-XawYBœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-Prompt-gTNiz{\u0153baseClasses\u0153:[\u0153object\u0153,\u0153str\u0153,\u0153Text\u0153],\u0153dataType\u0153:\u0153Prompt\u0153,\u0153id\u0153:\u0153Prompt-gTNiz\u0153}-OpenAIModel-XawYB{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153OpenAIModel-XawYB\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
}, },
{ {
"source": "OpenAIModel-XawYB", "source": "OpenAIModel-XawYB",
"sourceHandle": "{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-XawYBœ}", "sourceHandle": "{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-XawYB\u0153}",
"target": "ChatOutput-DNmvg", "target": "ChatOutput-DNmvg",
"targetHandle": "{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-DNmvgœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}", "targetHandle": "{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-DNmvg\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}",
"data": { "data": {
"targetHandle": { "targetHandle": {
"fieldName": "input_value", "fieldName": "input_value",
@ -1570,7 +1570,7 @@
"stroke": "#555" "stroke": "#555"
}, },
"className": "stroke-gray-900 stroke-connection", "className": "stroke-gray-900 stroke-connection",
"id": "reactflow__edge-OpenAIModel-XawYB{œbaseClassesœ:[œstrœ,œTextœ,œobjectœ],œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-XawYBœ}-ChatOutput-DNmvg{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-DNmvgœ,œinputTypesœ:[œTextœ],œtypeœ:œstrœ}" "id": "reactflow__edge-OpenAIModel-XawYB{\u0153baseClasses\u0153:[\u0153str\u0153,\u0153Text\u0153,\u0153object\u0153],\u0153dataType\u0153:\u0153OpenAIModel\u0153,\u0153id\u0153:\u0153OpenAIModel-XawYB\u0153}-ChatOutput-DNmvg{\u0153fieldName\u0153:\u0153input_value\u0153,\u0153id\u0153:\u0153ChatOutput-DNmvg\u0153,\u0153inputTypes\u0153:[\u0153Text\u0153],\u0153type\u0153:\u0153str\u0153}"
} }
], ],
"viewport": { "viewport": {

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more