Merge remote-tracking branch 'origin/dev' into feature/output_dropdown
This commit is contained in:
commit
bd4f4c8724
318 changed files with 14679 additions and 9306 deletions
14
.env.example
14
.env.example
|
|
@ -4,6 +4,19 @@
|
|||
# Do not commit .env file to git
|
||||
# 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
|
||||
# Postgres example: LANGFLOW_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/langflow
|
||||
# SQLite example:
|
||||
|
|
@ -56,7 +69,6 @@ LANGFLOW_REMOVE_API_KEYS=
|
|||
# LANGFLOW_REDIS_CACHE_EXPIRE (default: 3600)
|
||||
LANGFLOW_CACHE_TYPE=
|
||||
|
||||
|
||||
# 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
|
||||
# must be set if AUTO_LOGIN is set to false
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -141,7 +141,7 @@ backend:
|
|||
@echo 'Setting up the environment'
|
||||
@make setup_env
|
||||
make install_backend
|
||||
@-kill -9 `lsof -t -i:7860`
|
||||
@-kill -9 $(lsof -t -i:7860)
|
||||
ifdef 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
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ Alternatively, click the **"Open in Cloud Shell"** button below to launch Google
|
|||
|
||||
## Deploy on Railway
|
||||
|
||||
Use this template to deploy Langflow 1.0 Preview on Railway:
|
||||
|
||||
[](https://railway.app/template/UsJ1uB?referralCode=MnPSdg)
|
||||
|
||||
Or this one to deploy Langflow 0.6.x:
|
||||
|
||||
[](https://railway.app/template/JMXEWp?referralCode=MnPSdg)
|
||||
|
||||
## Deploy on Render
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
# PYTHON-BASE
|
||||
# Sets up all our shared environment variables
|
||||
################################
|
||||
FROM python:3.10-slim as python-base
|
||||
FROM python:3.12-slim as python-base
|
||||
|
||||
# python
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
|
|
@ -47,7 +47,7 @@ ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
|
|||
# Used to build deps + create our virtual environment
|
||||
################################
|
||||
FROM python-base as builder-base
|
||||
RUN
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
# deps for installing poetry
|
||||
|
|
@ -55,7 +55,12 @@ RUN apt-get update \
|
|||
# deps for building python deps
|
||||
build-essential \
|
||||
# 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
|
||||
|
|
@ -70,15 +75,12 @@ RUN --mount=type=cache,target=/root/.cache \
|
|||
RUN python -m pip install requests && cd ./scripts && python update_dependencies.py
|
||||
RUN $POETRY_HOME/bin/poetry lock
|
||||
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
|
||||
RUN useradd -m -u 1000 user
|
||||
COPY --from=builder-base /app/dist/*.tar.gz ./
|
||||
USER user
|
||||
# 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"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "7860"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "7860"]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
# PYTHON-BASE
|
||||
# Sets up all our shared environment variables
|
||||
################################
|
||||
FROM python:3.10-slim as python-base
|
||||
FROM python:3.12-slim as python-base
|
||||
|
||||
# python
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
|
|
@ -55,6 +55,8 @@ RUN apt-get update \
|
|||
build-essential \
|
||||
# npm
|
||||
npm \
|
||||
# gcc
|
||||
gcc \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
|
@ -76,17 +78,14 @@ RUN cd src/frontend && npm run build
|
|||
COPY src/backend ./src/backend
|
||||
RUN cp -r src/frontend/build src/backend/base/langflow/frontend
|
||||
RUN rm -rf src/backend/base/dist
|
||||
RUN cd src/backend/base && $POETRY_HOME/bin/poetry build --format sdist
|
||||
|
||||
# Final stage for the application
|
||||
FROM python-base as final
|
||||
RUN cd src/backend/base && $POETRY_HOME/bin/poetry build
|
||||
|
||||
# Copy virtual environment and built .tar.gz from builder base
|
||||
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
|
||||
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"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "7860"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "7860"]
|
||||
|
|
|
|||
45
docs/docs/administration/global-env.mdx
Normal file
45
docs/docs/administration/global-env.mdx
Normal 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).
|
||||
29
docs/docs/administration/playground.mdx
Normal file
29
docs/docs/administration/playground.mdx
Normal 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.
|
||||
|
|
@ -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.
|
||||
|
||||
<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>
|
||||
|
||||
---
|
||||
|
|
@ -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
|
||||
|
||||
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).
|
||||
|
||||
<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>
|
||||
|
||||
---
|
||||
|
|
@ -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).
|
||||
|
||||
<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>
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
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 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
|
||||
|
||||
|
|
@ -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/).
|
||||
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**.
|
||||
The share window appears:
|
||||
The share window appears:
|
||||
|
||||
<ZoomableImage
|
||||
alt="Docusaurus themed image"
|
||||
sources={{
|
||||
alt="Docusaurus themed image"
|
||||
sources={{
|
||||
light: "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.
|
||||
You can also **Export** your flow as a JSON file from this window.
|
||||
When you're ready to share the flow, click **Share Flow**.
|
||||
You should see a **Flow shared successfully** popup.
|
||||
You can also **Export** your flow as a JSON file from this window.
|
||||
When you're ready to share the flow, click **Share Flow**.
|
||||
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**.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ The RAG flow is a bit more complex. It consists of:
|
|||
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
|
||||
alt="Docusaurus themed image"
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ module.exports = {
|
|||
type: "category",
|
||||
label: "What's New?",
|
||||
collapsed: false,
|
||||
items: [
|
||||
"whats-new/a-new-chapter-langflow"
|
||||
],
|
||||
items: ["whats-new/a-new-chapter-langflow"],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
|
|
@ -17,7 +15,7 @@ module.exports = {
|
|||
"getting-started/install-langflow",
|
||||
"getting-started/quickstart",
|
||||
"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/document-qa",
|
||||
"starter-projects/memory-chatbot",
|
||||
"starter-projects/vector-store-rag"
|
||||
"starter-projects/vector-store-rag",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -40,10 +38,12 @@ module.exports = {
|
|||
"administration/login",
|
||||
"administration/api",
|
||||
"administration/cli",
|
||||
"administration/playground",
|
||||
"administration/global-env",
|
||||
"administration/components",
|
||||
"administration/collection",
|
||||
"administration/prompt-customization",
|
||||
"administration/langfuse_integration"
|
||||
"administration/langfuse_integration",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -58,7 +58,7 @@ module.exports = {
|
|||
"components/helpers",
|
||||
"components/vector-stores",
|
||||
"components/embeddings",
|
||||
"components/custom"
|
||||
"components/custom",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -74,7 +74,7 @@ module.exports = {
|
|||
"components/retrievers",
|
||||
"components/text-splitters",
|
||||
"components/toolkits",
|
||||
"components/tools"
|
||||
"components/tools",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -88,7 +88,7 @@ module.exports = {
|
|||
"examples/csv-loader",
|
||||
"examples/searchapi-tool",
|
||||
"examples/serp-api-tool",
|
||||
"examples/python-function"
|
||||
"examples/python-function",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -101,8 +101,8 @@ module.exports = {
|
|||
"migration/inputs-and-outputs",
|
||||
"migration/text-and-record",
|
||||
"migration/compatibility",
|
||||
"migration/global-variables"
|
||||
]
|
||||
"migration/global-variables",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
|
|
@ -111,16 +111,14 @@ module.exports = {
|
|||
items: [
|
||||
"tutorials/chatprompttemplate_guide",
|
||||
"tutorials/loading_document",
|
||||
"tutorials/rag-with-astradb"
|
||||
"tutorials/rag-with-astradb",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Deployment",
|
||||
collapsed: true,
|
||||
items: [
|
||||
"deployment/gcp-deployment"
|
||||
],
|
||||
items: ["deployment/gcp-deployment"],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
|
|
@ -130,7 +128,7 @@ module.exports = {
|
|||
"contributing/how-contribute",
|
||||
"contributing/github-issues",
|
||||
"contributing/community",
|
||||
"contributing/contribute-component"
|
||||
"contributing/contribute-component",
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
6506
docs/static/data/AstraDB-RAG-Flows.json
vendored
6506
docs/static/data/AstraDB-RAG-Flows.json
vendored
File diff suppressed because one or more lines are too long
BIN
docs/static/img/astra-rag-flow-run.png
vendored
BIN
docs/static/img/astra-rag-flow-run.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 93 KiB |
BIN
docs/static/img/global-env.png
vendored
Normal file
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
BIN
docs/static/img/playground-chat.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
191
example.har
191
example.har
File diff suppressed because one or more lines are too long
2047
poetry.lock
generated
2047
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "langflow"
|
||||
version = "1.0.0a33"
|
||||
version = "1.0.0a37"
|
||||
description = "A Python package with a built-in web application"
|
||||
authors = ["Langflow <contact@langflow.org>"]
|
||||
maintainers = [
|
||||
|
|
@ -29,21 +29,20 @@ python = ">=3.10,<3.13"
|
|||
langflow-base = { path = "./src/backend/base", develop = true }
|
||||
beautifulsoup4 = "^4.12.2"
|
||||
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"] }
|
||||
llama-cpp-python = { version = "~0.2.0", optional = true }
|
||||
networkx = "^3.1"
|
||||
pysrt = "^1.1.2"
|
||||
fake-useragent = "^1.4.0"
|
||||
fake-useragent = "^1.5.0"
|
||||
psycopg2-binary = "^2.9.6"
|
||||
pyarrow = "^14.0.0"
|
||||
wikipedia = "^1.4.0"
|
||||
qdrant-client = "^1.7.0"
|
||||
qdrant-client = "^1.9.0"
|
||||
weaviate-client = "*"
|
||||
sentence-transformers = { version = "^2.3.1", optional = true }
|
||||
ctransformers = { version = "^0.2.10", optional = true }
|
||||
cohere = "^5.1.7"
|
||||
faiss-cpu = "^1.7.4"
|
||||
cohere = "^5.5.3"
|
||||
faiss-cpu = "^1.8.0"
|
||||
types-cachetools = "^5.3.0.5"
|
||||
pinecone-client = "^3.0.3"
|
||||
pymongo = "^4.6.0"
|
||||
|
|
@ -57,10 +56,9 @@ redis = { version = "^5.0.1", optional = true }
|
|||
flower = { version = "^2.0.0", optional = true }
|
||||
metaphor-python = "^0.1.11"
|
||||
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
|
||||
langfuse = "^2.9.0"
|
||||
langfuse = "^2.33.0"
|
||||
metal-sdk = "^2.5.0"
|
||||
markupsafe = "^2.1.3"
|
||||
extract-msg = "^0.47.0"
|
||||
# jq is not available for windows
|
||||
boto3 = "^1.34.0"
|
||||
numexpr = "^2.8.6"
|
||||
|
|
@ -71,41 +69,41 @@ langchain-google-genai = "^1.0.1"
|
|||
langchain-cohere = "^0.1.0rc1"
|
||||
elasticsearch = "^8.12.0"
|
||||
pytube = "^15.0.0"
|
||||
llama-index = "^0.10.13"
|
||||
# unstructured = { extras = ["md"], version = "^0.12.4" }
|
||||
dspy-ai = "^2.4.0"
|
||||
html2text = "^2024.2.26"
|
||||
assemblyai = "^0.23.1"
|
||||
litellm = "^1.34.22"
|
||||
chromadb = "^0.4.24"
|
||||
assemblyai = "^0.26.0"
|
||||
litellm = "^1.38.0"
|
||||
chromadb = "^0.5.0"
|
||||
langchain-anthropic = "^0.1.6"
|
||||
langchain-astradb = "^0.1.0"
|
||||
langchain-astradb = "^0.3.0"
|
||||
langchain-openai = "^0.1.1"
|
||||
zep-python = { version = "^2.0.0rc5", allow-prereleases = true }
|
||||
langchain-google-vertexai = "^1.0.3"
|
||||
langchain-groq = "^0.1.3"
|
||||
langchain-pinecone = "^0.1.0"
|
||||
langchain-mistralai = "^0.1.6"
|
||||
couchbase = "^4.2.1"
|
||||
youtube-transcript-api = "^0.6.2"
|
||||
markdown = "^3.6"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
types-redis = "^4.6.0.5"
|
||||
ipykernel = "^6.29.0"
|
||||
mypy = "^1.9.0"
|
||||
ruff = "^0.3.5"
|
||||
mypy = "^1.10.0"
|
||||
ruff = "^0.4.5"
|
||||
httpx = "*"
|
||||
pytest = "^8.1.0"
|
||||
types-requests = "^2.31.0"
|
||||
requests = "^2.31.0"
|
||||
pytest-cov = "^4.1.0"
|
||||
pytest = "^8.2.0"
|
||||
types-requests = "^2.32.0"
|
||||
requests = "^2.32.0"
|
||||
pytest-cov = "^5.0.0"
|
||||
pandas-stubs = "^2.1.4.231227"
|
||||
types-pillow = "^10.2.0.20240213"
|
||||
types-pyyaml = "^6.0.12.8"
|
||||
types-python-jose = "^3.3.4.8"
|
||||
types-passlib = "^1.7.7.13"
|
||||
locust = "^2.23.1"
|
||||
pytest-mock = "^3.12.0"
|
||||
pytest-xdist = "^3.5.0"
|
||||
pytest-mock = "^3.14.0"
|
||||
pytest-xdist = "^3.6.0"
|
||||
types-pywin32 = "^306.0.0.4"
|
||||
types-google-cloud-ndb = "^2.2.0.0"
|
||||
pytest-sugar = "^1.0.0"
|
||||
|
|
@ -134,7 +132,7 @@ ignore-regex = '.*(Stati Uniti|Tense=Pres).*'
|
|||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "6.0"
|
||||
addopts = "-ra"
|
||||
addopts = "-ra -n auto"
|
||||
testpaths = ["tests", "integration"]
|
||||
console_output_style = "progress"
|
||||
filterwarnings = ["ignore::DeprecationWarning"]
|
||||
|
|
|
|||
|
|
@ -1,65 +1,85 @@
|
|||
import { Construct } from 'constructs';
|
||||
import * as ec2 from 'aws-cdk-lib/aws-ec2'
|
||||
import { Construct } from "constructs";
|
||||
import * as ec2 from "aws-cdk-lib/aws-ec2";
|
||||
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 {
|
||||
vpc: ec2.Vpc
|
||||
dbSG:ec2.SecurityGroup
|
||||
vpc: ec2.Vpc;
|
||||
dbSG: ec2.SecurityGroup;
|
||||
}
|
||||
|
||||
export class Rds extends Construct{
|
||||
readonly rdsCluster: rds.DatabaseCluster
|
||||
export class Rds extends Construct {
|
||||
readonly rdsCluster: rds.DatabaseCluster;
|
||||
|
||||
constructor(scope: Construct, id:string, props: RdsProps){
|
||||
constructor(scope: Construct, id: string, props: RdsProps) {
|
||||
super(scope, id);
|
||||
|
||||
const {vpc, dbSG} = props
|
||||
const instanceType = ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE4_GRAVITON, ec2.InstanceSize.MEDIUM)
|
||||
const { vpc, dbSG } = props;
|
||||
const instanceType = ec2.InstanceType.of(
|
||||
ec2.InstanceClass.BURSTABLE4_GRAVITON,
|
||||
ec2.InstanceSize.MEDIUM,
|
||||
);
|
||||
|
||||
// RDSのパスワードを自動生成してSecrets Managerに格納
|
||||
const rdsCredentials = rds.Credentials.fromGeneratedSecret('db_user',{
|
||||
secretName: 'langflow-DbSecret',
|
||||
})
|
||||
|
||||
const rdsCredentials = rds.Credentials.fromGeneratedSecret("db_user", {
|
||||
secretName: "langflow-DbSecret",
|
||||
});
|
||||
|
||||
// DB クラスターのパラメータグループ作成
|
||||
const clusterParameterGroup = new rds.ParameterGroup(scope, 'ClusterParameterGroup',{
|
||||
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||
version: rds.AuroraMysqlEngineVersion.VER_3_02_0
|
||||
}),
|
||||
description: 'for-langflow',
|
||||
})
|
||||
clusterParameterGroup.bindToCluster({})
|
||||
const clusterParameterGroup = new rds.ParameterGroup(
|
||||
scope,
|
||||
"ClusterParameterGroup",
|
||||
{
|
||||
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||
version: rds.AuroraMysqlEngineVersion.of(
|
||||
"8.0.mysql_aurora.3.05.2",
|
||||
"8.0",
|
||||
),
|
||||
}),
|
||||
description: "for-langflow",
|
||||
},
|
||||
);
|
||||
clusterParameterGroup.bindToCluster({});
|
||||
|
||||
// DB インスタンスのパラメタグループ作成
|
||||
const instanceParameterGroup = new rds.ParameterGroup(scope, 'InstanceParameterGroup',{
|
||||
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||
version: rds.AuroraMysqlEngineVersion.VER_3_02_0,
|
||||
}),
|
||||
description: 'for-langflow',
|
||||
})
|
||||
instanceParameterGroup.bindToInstance({})
|
||||
const instanceParameterGroup = new rds.ParameterGroup(
|
||||
scope,
|
||||
"InstanceParameterGroup",
|
||||
{
|
||||
engine: rds.DatabaseClusterEngine.auroraMysql({
|
||||
version: rds.AuroraMysqlEngineVersion.of(
|
||||
"8.0.mysql_aurora.3.05.2",
|
||||
"8.0",
|
||||
),
|
||||
}),
|
||||
description: "for-langflow",
|
||||
},
|
||||
);
|
||||
instanceParameterGroup.bindToInstance({});
|
||||
|
||||
this.rdsCluster = new rds.DatabaseCluster(scope, 'LangflowDbCluster', {
|
||||
this.rdsCluster = new rds.DatabaseCluster(scope, "LangflowDbCluster", {
|
||||
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,
|
||||
credentials: rdsCredentials,
|
||||
instanceIdentifierBase: 'langflow-instance',
|
||||
vpc:vpc,
|
||||
vpcSubnets:vpc.selectSubnets({
|
||||
subnetGroupName: 'langflow-Isolated',
|
||||
instanceIdentifierBase: "langflow-instance",
|
||||
vpc: vpc,
|
||||
vpcSubnets: vpc.selectSubnets({
|
||||
subnetGroupName: "langflow-Isolated",
|
||||
}),
|
||||
securityGroups:[dbSG],
|
||||
securityGroups: [dbSG],
|
||||
writer: rds.ClusterInstance.provisioned("WriterInstance", {
|
||||
instanceType: instanceType,
|
||||
enablePerformanceInsights: true,
|
||||
parameterGroup:instanceParameterGroup,
|
||||
parameterGroup: instanceParameterGroup,
|
||||
}),
|
||||
// 2台目以降はreaders:で設定
|
||||
// 2台目以降はreaders:で設定
|
||||
parameterGroup: clusterParameterGroup,
|
||||
defaultDatabaseName: 'langflow',
|
||||
})
|
||||
defaultDatabaseName: "langflow",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1085
scripts/aws/package-lock.json
generated
1085
scripts/aws/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -11,21 +11,21 @@
|
|||
"cdk": "cdk"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/node": "20.1.7",
|
||||
"aws-cdk": "^2.86.0",
|
||||
"jest": "^29.5.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "~5.1.3"
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.12.12",
|
||||
"aws-cdk": "^2.141.0",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-solutions-constructs/aws-cloudfront-s3": "^2.49.0",
|
||||
"aws-cdk-lib": "^2.124.0",
|
||||
"cdk-ecr-deployment": "^2.5.30",
|
||||
"constructs": "^10.0.0",
|
||||
"deploy-time-build": "^0.3.12",
|
||||
"dotenv": "^16.3.1",
|
||||
"@aws-solutions-constructs/aws-cloudfront-s3": "^2.57.0",
|
||||
"aws-cdk-lib": "^2.141.0",
|
||||
"cdk-ecr-deployment": "^3.0.55",
|
||||
"constructs": "^10.3.0",
|
||||
"deploy-time-build": "^0.3.21",
|
||||
"dotenv": "^16.4.5",
|
||||
"source-map-support": "^0.5.21"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ apt -y upgrade
|
|||
|
||||
# Install Python 3 pip, Langflow, and Nginx
|
||||
apt -y install python3-pip
|
||||
pip install langflow
|
||||
langflow --host 0.0.0.0 --port 7860
|
||||
pip3 install pip -U
|
||||
apt -y update
|
||||
pip3 install langflow
|
||||
langflow run --host 0.0.0.0 --port 7860
|
||||
EOF
|
||||
)
|
||||
|
||||
|
|
|
|||
1
src/backend/.gitignore
vendored
1
src/backend/.gitignore
vendored
|
|
@ -131,3 +131,4 @@ dmypy.json
|
|||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
*.db
|
||||
|
|
@ -2,9 +2,9 @@ import platform
|
|||
import socket
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import warnings
|
||||
|
||||
import click
|
||||
import httpx
|
||||
|
|
@ -17,8 +17,10 @@ from rich import print as rprint
|
|||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from sqlmodel import select
|
||||
|
||||
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.deps import get_db_service
|
||||
from langflow.services.utils import initialize_services
|
||||
|
|
@ -432,17 +434,57 @@ def superuser(
|
|||
# Verify that the superuser was created
|
||||
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:
|
||||
typer.echo("Superuser creation failed.")
|
||||
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.")
|
||||
|
||||
else:
|
||||
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()
|
||||
def migration(
|
||||
test: bool = typer.Option(True, help="Run migrations in test mode."),
|
||||
|
|
|
|||
|
|
@ -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 ###
|
||||
|
|
@ -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 ###
|
||||
|
|
@ -13,6 +13,7 @@ from langflow.api.v1 import (
|
|||
users_router,
|
||||
validate_router,
|
||||
variables_router,
|
||||
folders_router,
|
||||
)
|
||||
|
||||
router = APIRouter(
|
||||
|
|
@ -29,3 +30,4 @@ router.include_router(login_router)
|
|||
router.include_router(variables_router)
|
||||
router.include_router(files_router)
|
||||
router.include_router(monitor_router)
|
||||
router.include_router(folders_router)
|
||||
|
|
|
|||
|
|
@ -140,7 +140,10 @@ def get_file_path_value(file_path):
|
|||
# If the path is not in the cache dir, return empty string
|
||||
# This is to prevent access to files outside the cache dir
|
||||
# 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 file_path
|
||||
|
||||
|
|
|
|||
|
|
@ -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.validate import router as validate_router
|
||||
from langflow.api.v1.variable import router as variables_router
|
||||
from langflow.api.v1.folders import router as folders_router
|
||||
|
||||
__all__ = [
|
||||
"chat_router",
|
||||
|
|
@ -22,4 +23,5 @@ __all__ = [
|
|||
"variables_router",
|
||||
"monitor_router",
|
||||
"files_router",
|
||||
"folders_router",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from langchain.schema import AgentAction, AgentFinish
|
||||
from langchain_core.callbacks.base import AsyncCallbackHandler
|
||||
from loguru import logger
|
||||
|
||||
from langflow.api.v1.schemas import ChatResponse, PromptResponse
|
||||
from langflow.services.deps import get_chat_service, get_socket_service
|
||||
from langflow.utils.util import remove_ansi_escape_codes
|
||||
from langchain_core.agents import AgentAction, AgentFinish
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.services.socket.service import SocketIOService
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.graph.vertex.types import ChatVertex
|
||||
from langflow.graph.vertex.types import InterfaceVertex
|
||||
from langflow.services.session.service import SessionService
|
||||
|
||||
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)
|
||||
async def retrieve_vertices_order(
|
||||
flow_id: str,
|
||||
flow_id: uuid.UUID,
|
||||
data: Optional[Annotated[Optional[FlowDataRequest], Body(embed=True)]] = None,
|
||||
stop_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.
|
||||
"""
|
||||
try:
|
||||
flow_id_str = str(flow_id)
|
||||
# First, we need to check if the flow_id is in the cache
|
||||
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:
|
||||
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()
|
||||
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}")
|
||||
async def build_vertex(
|
||||
flow_id: str,
|
||||
flow_id: uuid.UUID,
|
||||
vertex_id: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
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.
|
||||
|
||||
"""
|
||||
flow_id_str = str(flow_id)
|
||||
|
||||
start_time = time.perf_counter()
|
||||
next_runnable_vertices = []
|
||||
top_level_vertices = []
|
||||
try:
|
||||
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 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(
|
||||
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:
|
||||
graph = cache.get("result")
|
||||
result_data_response = ResultDataResponse(results={})
|
||||
duration = ""
|
||||
vertex = graph.get_vertex(vertex_id)
|
||||
try:
|
||||
lock = chat_service._cache_locks[flow_id]
|
||||
set_cache_coro = partial(chat_service.set_cache, flow_id=flow_id)
|
||||
lock = chat_service._cache_locks[flow_id_str]
|
||||
set_cache_coro = partial(chat_service.set_cache, flow_id=flow_id_str)
|
||||
(
|
||||
next_runnable_vertices,
|
||||
top_level_vertices,
|
||||
|
|
@ -189,13 +188,13 @@ async def build_vertex(
|
|||
artifacts = {}
|
||||
# If there's an error building the vertex
|
||||
# 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
|
||||
if not vertex.will_stream:
|
||||
background_tasks.add_task(
|
||||
log_vertex_build,
|
||||
flow_id=flow_id,
|
||||
flow_id=flow_id_str,
|
||||
vertex_id=vertex_id,
|
||||
valid=valid,
|
||||
params=params,
|
||||
|
|
@ -212,7 +211,7 @@ async def build_vertex(
|
|||
inactivated_vertices = list(graph.inactivated_vertices)
|
||||
graph.reset_inactivated_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
|
||||
# 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)
|
||||
async def build_vertex_stream(
|
||||
flow_id: str,
|
||||
flow_id: uuid.UUID,
|
||||
vertex_id: str,
|
||||
session_id: Optional[str] = None,
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
flow_id_str = str(flow_id)
|
||||
|
||||
async def stream_vertex():
|
||||
try:
|
||||
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 there's no cache
|
||||
raise ValueError(f"No cache found for {flow_id}.")
|
||||
raise ValueError(f"No cache found for {flow_id_str}.")
|
||||
else:
|
||||
graph = cache.get("result")
|
||||
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)
|
||||
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"):
|
||||
raise ValueError(f"Vertex {vertex_id} does not support streaming")
|
||||
if isinstance(vertex._built_result, str) and vertex._built_result:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from http import HTTPStatus
|
||||
from typing import Annotated, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
import sqlalchemy as sa
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, UploadFile, status
|
||||
|
|
@ -18,9 +19,7 @@ from langflow.api.v1.schemas import (
|
|||
UploadFileResponse,
|
||||
)
|
||||
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.directory_reader import DirectoryReader
|
||||
from langflow.interface.custom.utils import build_custom_component_template
|
||||
from langflow.processing.process import process_tweaks, run_graph_internal
|
||||
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)
|
||||
async def simplified_run_flow(
|
||||
db: Annotated[Session, Depends(get_session)],
|
||||
flow_id: str,
|
||||
flow_id: UUID,
|
||||
input_request: SimplifiedAPIRequest = SimplifiedAPIRequest(),
|
||||
stream: bool = False,
|
||||
api_key_user: User = Depends(api_key_security),
|
||||
|
|
@ -111,26 +110,26 @@ async def simplified_run_flow(
|
|||
session_id = input_request.session_id
|
||||
|
||||
try:
|
||||
task_result: List[RunOutputs] = []
|
||||
flow_id_str = str(flow_id)
|
||||
artifacts = {}
|
||||
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)
|
||||
if graph is None:
|
||||
raise ValueError(f"Session {input_request.session_id} not found")
|
||||
else:
|
||||
# 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 = 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:
|
||||
raise ValueError(f"Flow {flow_id} not found")
|
||||
raise ValueError(f"Flow {flow_id_str} not found")
|
||||
|
||||
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 = 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 = [
|
||||
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(
|
||||
graph=graph,
|
||||
flow_id=flow_id,
|
||||
flow_id=flow_id_str,
|
||||
session_id=input_request.session_id,
|
||||
inputs=inputs,
|
||||
outputs=outputs,
|
||||
|
|
@ -166,12 +165,12 @@ async def simplified_run_flow(
|
|||
except sa.exc.StatementError as exc:
|
||||
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
||||
if "badly formed hexadecimal UUID string" in str(exc):
|
||||
logger.error(f"Flow ID {flow_id} 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
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
except ValueError as exc:
|
||||
if f"Flow {flow_id} not found" in str(exc):
|
||||
logger.error(f"Flow {flow_id} not found")
|
||||
if f"Flow {flow_id_str} not found" in str(exc):
|
||||
logger.error(f"Flow {flow_id_str} not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
elif f"Session {session_id} not found" in str(exc):
|
||||
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)
|
||||
async def experimental_run_flow(
|
||||
session: Annotated[Session, Depends(get_session)],
|
||||
flow_id: str,
|
||||
flow_id: UUID,
|
||||
inputs: Optional[List[InputValueRequest]] = [InputValueRequest(components=[], input_value="")],
|
||||
outputs: Optional[List[str]] = [],
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
flow_id_str = str(flow_id)
|
||||
if outputs is None:
|
||||
outputs = []
|
||||
|
||||
task_result: List[RunOutputs] = []
|
||||
artifacts = {}
|
||||
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)
|
||||
if graph is None:
|
||||
raise ValueError(f"Session {session_id} not found")
|
||||
else:
|
||||
# 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.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:
|
||||
raise ValueError(f"Flow {flow_id} not found")
|
||||
raise ValueError(f"Flow {flow_id_str} not found")
|
||||
|
||||
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 = 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(
|
||||
graph=graph,
|
||||
flow_id=flow_id,
|
||||
flow_id=flow_id_str,
|
||||
session_id=session_id,
|
||||
inputs=inputs,
|
||||
outputs=outputs,
|
||||
|
|
@ -272,12 +273,12 @@ async def experimental_run_flow(
|
|||
except sa.exc.StatementError as exc:
|
||||
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
||||
if "badly formed hexadecimal UUID string" in str(exc):
|
||||
logger.error(f"Flow ID {flow_id} 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
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
except ValueError as exc:
|
||||
if f"Flow {flow_id} not found" in str(exc):
|
||||
logger.error(f"Flow {flow_id} not found")
|
||||
if f"Flow {flow_id_str} not found" in str(exc):
|
||||
logger.error(f"Flow {flow_id_str} not found")
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
elif f"Session {session_id} not found" in str(exc):
|
||||
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(
|
||||
file: UploadFile,
|
||||
flow_id: str,
|
||||
flow_id: UUID,
|
||||
):
|
||||
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(
|
||||
flowId=flow_id,
|
||||
flowId=flow_id_str,
|
||||
file_path=file_path,
|
||||
)
|
||||
except Exception as exc:
|
||||
|
|
@ -400,23 +402,6 @@ async def custom_component(
|
|||
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)
|
||||
async def custom_component_update(
|
||||
code_request: UpdateCustomComponentRequest,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import hashlib
|
||||
from http import HTTPStatus
|
||||
from io import BytesIO
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile
|
||||
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
|
||||
# using the current user as the owner
|
||||
def get_flow_id(
|
||||
flow_id: str,
|
||||
flow_id: UUID,
|
||||
current_user=Depends(get_current_active_user),
|
||||
session=Depends(get_session),
|
||||
):
|
||||
flow_id_str = str(flow_id)
|
||||
# AttributeError: 'SelectOfScalar' object has no attribute 'first'
|
||||
flow = session.get(Flow, flow_id)
|
||||
flow = session.get(Flow, flow_id_str)
|
||||
if not flow:
|
||||
raise HTTPException(status_code=404, detail="Flow not found")
|
||||
if flow.user_id != current_user.id:
|
||||
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)
|
||||
async def upload_file(
|
||||
file: UploadFile,
|
||||
flow_id: str = Depends(get_flow_id),
|
||||
flow_id: UUID = Depends(get_flow_id),
|
||||
storage_service: StorageService = Depends(get_storage_service),
|
||||
):
|
||||
try:
|
||||
flow_id_str = str(flow_id)
|
||||
file_content = await file.read()
|
||||
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)
|
||||
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:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@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:
|
||||
flow_id_str = str(flow_id)
|
||||
extension = file_name.split(".")[-1]
|
||||
|
||||
if not extension:
|
||||
|
|
@ -62,7 +66,7 @@ async def download_file(file_name: str, flow_id: str, storage_service: StorageSe
|
|||
if not content_type:
|
||||
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 = {
|
||||
"Content-Disposition": f"attachment; filename={file_name} filename*=UTF-8''{file_name}",
|
||||
"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}")
|
||||
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:
|
||||
extension = file_name.split(".")[-1]
|
||||
flow_id_str = str(flow_id)
|
||||
|
||||
if not extension:
|
||||
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"):
|
||||
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)
|
||||
except Exception as 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}")
|
||||
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:
|
||||
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}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
|
@ -107,10 +113,11 @@ async def list_files(
|
|||
|
||||
@router.delete("/delete/{flow_id}/{file_name}")
|
||||
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:
|
||||
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"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
|
|
|||
|
|
@ -6,13 +6,15 @@ import orjson
|
|||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
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.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.services.auth.utils import get_current_active_user
|
||||
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.deps import get_session, get_settings_service
|
||||
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.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.commit()
|
||||
session.refresh(db_flow)
|
||||
|
|
@ -64,12 +71,9 @@ def read_flows(
|
|||
flow_ids = [flow.id for flow in flows]
|
||||
# with the session get the flows that DO NOT have a user_id
|
||||
try:
|
||||
example_flows = session.exec(
|
||||
select(Flow).where(
|
||||
Flow.user_id == None, # noqa
|
||||
Flow.folder == STARTER_FOLDER_NAME,
|
||||
)
|
||||
).all()
|
||||
folder = session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
|
||||
|
||||
example_flows = folder.flows if folder else []
|
||||
for example_flow in example_flows:
|
||||
if example_flow.id not in flow_ids:
|
||||
flows.append(example_flow) # type: ignore
|
||||
|
|
@ -129,6 +133,10 @@ def update_flow(
|
|||
if value is not None:
|
||||
setattr(db_flow, key, value)
|
||||
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.commit()
|
||||
session.refresh(db_flow)
|
||||
|
|
@ -208,3 +216,31 @@ async def download_file(
|
|||
"""Download all flows as a file."""
|
||||
flows = read_flows(current_user=current_user, session=session, settings_service=settings_service)
|
||||
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
|
||||
|
|
|
|||
239
src/backend/base/langflow/api/v1/folders.py
Normal file
239
src/backend/base/langflow/api/v1/folders.py
Normal 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)
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlmodel import Session
|
||||
|
||||
from langflow.api.v1.schemas import Token
|
||||
from langflow.services.auth.utils import (
|
||||
authenticate_user,
|
||||
|
|
@ -7,14 +9,10 @@ from langflow.services.auth.utils import (
|
|||
create_user_longterm_token,
|
||||
create_user_tokens,
|
||||
)
|
||||
from langflow.services.deps import (
|
||||
get_session,
|
||||
get_settings_service,
|
||||
get_variable_service,
|
||||
)
|
||||
from langflow.services.database.models.folder.utils import create_default_folder_if_it_doesnt_exist
|
||||
from langflow.services.deps import get_session, get_settings_service, get_variable_service
|
||||
from langflow.services.settings.manager import SettingsService
|
||||
from langflow.services.variable.service import VariableService
|
||||
from sqlmodel import Session
|
||||
|
||||
router = APIRouter(tags=["Login"])
|
||||
|
||||
|
|
@ -58,6 +56,8 @@ async def login_to_get_access_token(
|
|||
expires=auth_settings.ACCESS_TOKEN_EXPIRE_SECONDS,
|
||||
)
|
||||
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
|
||||
else:
|
||||
raise HTTPException(
|
||||
|
|
@ -86,6 +86,7 @@ async def auto_login(
|
|||
expires=None, # Set to None to make it a session cookie
|
||||
)
|
||||
variable_service.initialize_user_variables(user_id, db)
|
||||
create_default_folder_if_it_doesnt_exist(db, user_id)
|
||||
return tokens
|
||||
|
||||
raise HTTPException(
|
||||
|
|
@ -139,4 +140,3 @@ async def logout(response: Response):
|
|||
response.delete_cookie("refresh_token_lf")
|
||||
response.delete_cookie("access_token_lf")
|
||||
return {"message": "Logout successful"}
|
||||
return {"message": "Logout successful"}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
|
||||
|
||||
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
|
||||
|
||||
router = APIRouter(prefix="/monitor", tags=["Monitor"])
|
||||
|
|
@ -40,8 +43,9 @@ async def delete_vertex_builds(
|
|||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/messages")
|
||||
@router.get("/messages", response_model=List[MessageModelResponse])
|
||||
async def get_messages(
|
||||
flow_id: Optional[str] = Query(None),
|
||||
session_id: Optional[str] = Query(None),
|
||||
sender: 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),
|
||||
):
|
||||
try:
|
||||
return monitor_service.get_messages(
|
||||
df = monitor_service.get_messages(
|
||||
flow_id=flow_id,
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
session_id=session_id,
|
||||
order_by=order_by,
|
||||
)
|
||||
dicts = df.to_dict(orient="records")
|
||||
return [MessageModelResponse(**d) for d in dicts]
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/transactions")
|
||||
@router.get("/transactions", response_model=List[TransactionModelResponse])
|
||||
async def get_transactions(
|
||||
source: Optional[str] = Query(None),
|
||||
target: Optional[str] = Query(None),
|
||||
status: Optional[str] = Query(None),
|
||||
order_by: Optional[str] = Query("timestamp"),
|
||||
flow_id: Optional[str] = Query(None),
|
||||
monitor_service: MonitorService = Depends(get_monitor_service),
|
||||
):
|
||||
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:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
|
|
|||
|
|
@ -139,10 +139,20 @@ class FlowListCreate(BaseModel):
|
|||
flows: List[FlowCreate]
|
||||
|
||||
|
||||
class FlowListIds(BaseModel):
|
||||
flow_ids: List[str]
|
||||
|
||||
|
||||
class FlowListRead(BaseModel):
|
||||
flows: List[FlowRead]
|
||||
|
||||
|
||||
class FlowListReadWithFolderName(BaseModel):
|
||||
flows: List[FlowRead]
|
||||
name: str
|
||||
description: str
|
||||
|
||||
|
||||
class InitResponse(BaseModel):
|
||||
flowId: str
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from langflow.services.auth.utils import (
|
|||
get_password_hash,
|
||||
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.crud import get_user_by_id, update_user
|
||||
from langflow.services.deps import get_session, get_settings_service
|
||||
|
|
@ -36,6 +37,9 @@ def add_user(
|
|||
session.add(new_user)
|
||||
session.commit()
|
||||
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:
|
||||
session.rollback()
|
||||
raise HTTPException(status_code=400, detail="This username is unavailable.") from e
|
||||
|
|
|
|||
|
|
@ -49,12 +49,12 @@ class ChatComponent(CustomComponent):
|
|||
sender: Optional[str] = None,
|
||||
sender_name: Optional[str] = None,
|
||||
) -> list[Record]:
|
||||
|
||||
records = store_message(
|
||||
message,
|
||||
session_id=session_id,
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
flow_id=self.graph.flow_id,
|
||||
)
|
||||
|
||||
self.status = records
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
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.schema.schema import Record
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from fastapi import HTTPException
|
||||
from langchain.prompts import PromptTemplate
|
||||
from loguru import logger
|
||||
|
||||
from langflow.api.v1.base import INVALID_NAMES, check_input_variables
|
||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||
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]:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
from copy import deepcopy
|
||||
|
||||
|
||||
from langchain_core.documents import Document
|
||||
|
||||
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.
|
||||
"""
|
||||
# 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
|
||||
if isinstance(value, list):
|
||||
for i, item in enumerate(value):
|
||||
if isinstance(item, Record):
|
||||
d[key][i] = record_to_string(item)
|
||||
d_copy[key][i] = record_to_string(item)
|
||||
elif isinstance(item, Document):
|
||||
d[key][i] = document_to_string(item)
|
||||
d_copy[key][i] = document_to_string(item)
|
||||
elif isinstance(value, Record):
|
||||
d[key] = record_to_string(value)
|
||||
d_copy[key] = record_to_string(value)
|
||||
elif isinstance(value, Document):
|
||||
d[key] = document_to_string(value)
|
||||
return d
|
||||
d_copy[key] = document_to_string(value)
|
||||
return d_copy
|
||||
|
||||
|
||||
def document_to_string(document: Document) -> str:
|
||||
|
|
|
|||
|
|
@ -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 langflow.field_typing import (
|
||||
BaseLanguageModel,
|
||||
)
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_community.agent_toolkits import create_json_agent
|
||||
|
||||
|
||||
class JsonAgentComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -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.openai_functions_agent.base import OpenAIFunctionsAgent
|
||||
from langchain.memory.token_buffer import ConversationTokenBufferMemory
|
||||
from langchain.prompts import SystemMessagePromptTemplate
|
||||
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 langchain_openai import ChatOpenAI
|
||||
|
||||
from langflow.field_typing.range_spec import RangeSpec
|
||||
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):
|
||||
|
|
@ -57,9 +57,14 @@ class ConversationalAgent(CustomComponent):
|
|||
max_token_limit: int = 2000,
|
||||
temperature: float = 0.9,
|
||||
) -> AgentExecutor:
|
||||
if openai_api_key:
|
||||
api_key = SecretStr(openai_api_key)
|
||||
else:
|
||||
api_key = None
|
||||
|
||||
llm = ChatOpenAI(
|
||||
model=model_name,
|
||||
api_key=openai_api_key,
|
||||
api_key=api_key,
|
||||
base_url=openai_api_base,
|
||||
max_tokens=max_token_limit,
|
||||
temperature=temperature,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain.embeddings.base import Embeddings
|
||||
from langchain_community.embeddings import BedrockEmbeddings
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
|
||||
class AmazonBedrockEmeddingsComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -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 langchain_core.embeddings import Embeddings
|
||||
from langchain_openai import AzureOpenAIEmbeddings
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
|
||||
class AzureOpenAIEmbeddingsComponent(CustomComponent):
|
||||
|
|
@ -52,12 +52,16 @@ class AzureOpenAIEmbeddingsComponent(CustomComponent):
|
|||
api_version: str,
|
||||
api_key: str,
|
||||
) -> Embeddings:
|
||||
if api_key:
|
||||
azure_api_key = SecretStr(api_key)
|
||||
else:
|
||||
azure_api_key = None
|
||||
try:
|
||||
embeddings = AzureOpenAIEmbeddings(
|
||||
azure_endpoint=azure_endpoint,
|
||||
azure_deployment=azure_deployment,
|
||||
api_version=api_version,
|
||||
api_key=api_key,
|
||||
api_key=azure_api_key,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
from typing import List, Optional
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
from langchain_mistralai.embeddings import MistralAIEmbeddings
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.field_typing import Embeddings, NestedDict
|
||||
from langflow.field_typing import Embeddings
|
||||
|
||||
|
||||
class MistralAIEmbeddingsComponent(CustomComponent):
|
||||
display_name = "MistralAI Embeddings"
|
||||
|
|
@ -37,11 +37,7 @@ class MistralAIEmbeddingsComponent(CustomComponent):
|
|||
"advanced": True,
|
||||
"value": 120,
|
||||
},
|
||||
"endpoint": {
|
||||
"display_name": "API Endpoint",
|
||||
"advanced": True,
|
||||
"value": "https://api.mistral.ai/v1/"
|
||||
}
|
||||
"endpoint": {"display_name": "API Endpoint", "advanced": True, "value": "https://api.mistral.ai/v1/"},
|
||||
}
|
||||
|
||||
def build(
|
||||
|
|
@ -51,7 +47,7 @@ class MistralAIEmbeddingsComponent(CustomComponent):
|
|||
max_concurrent_requests: int = 64,
|
||||
max_retries: int = 5,
|
||||
timeout: int = 120,
|
||||
endpoint: str = "https://api.mistral.ai/v1/"
|
||||
endpoint: str = "https://api.mistral.ai/v1/",
|
||||
) -> Embeddings:
|
||||
if mistral_api_key:
|
||||
api_key = SecretStr(mistral_api_key)
|
||||
|
|
@ -64,6 +60,5 @@ class MistralAIEmbeddingsComponent(CustomComponent):
|
|||
endpoint=endpoint,
|
||||
max_concurrent_requests=max_concurrent_requests,
|
||||
max_retries=max_retries,
|
||||
timeout=timeout
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain.embeddings.base import Embeddings
|
||||
from langchain_community.embeddings import OllamaEmbeddings
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
|
||||
class OllamaEmbeddingsComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ from langflow.interface.custom.custom_component import CustomComponent
|
|||
from langflow.schema import Record
|
||||
from langflow.field_typing import Text
|
||||
|
||||
|
||||
class PassComponent(CustomComponent):
|
||||
display_name = "Pass"
|
||||
description = "A pass-through component that forwards the second input while ignoring the first, used for controlling workflow direction."
|
||||
field_order = ["ignored_input", "forwarded_input"]
|
||||
|
||||
|
||||
def build_config(self) -> dict:
|
||||
return {
|
||||
"ignored_input": {
|
||||
|
|
@ -19,7 +20,7 @@ class PassComponent(CustomComponent):
|
|||
"display_name": "Input",
|
||||
"info": "This input is forwarded by the component.",
|
||||
"input_types": ["Text", "Record"],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
def build(self, ignored_input: Text, forwarded_input: Text) -> Union[Text, Record]:
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ class StoreMessageComponent(CustomComponent):
|
|||
session_id: Optional[str] = None,
|
||||
message: str = "",
|
||||
) -> List[Record]:
|
||||
|
||||
store_message(
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class SubFlowComponent(CustomComponent):
|
|||
build_config["flow_name"]["options"] = self.get_flow_names()
|
||||
# Clean up the build config
|
||||
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]
|
||||
if field_value is not None and field_name == "flow_name":
|
||||
try:
|
||||
|
|
@ -85,20 +85,29 @@ class SubFlowComponent(CustomComponent):
|
|||
"display_name": "Tweaks",
|
||||
"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
|
||||
if not messages:
|
||||
return []
|
||||
records = []
|
||||
for message in messages:
|
||||
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)
|
||||
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()}
|
||||
run_outputs: List[Optional[RunOutputs]] = await self.run_flow(
|
||||
tweaks=tweaks,
|
||||
|
|
@ -112,7 +121,7 @@ class SubFlowComponent(CustomComponent):
|
|||
if run_output is not None:
|
||||
for output in run_output.outputs:
|
||||
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
|
||||
logger.debug(records)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from langflow.interface.custom.custom_component import CustomComponent
|
|||
from langflow.schema import Record
|
||||
from langflow.field_typing import Text
|
||||
|
||||
|
||||
class TextOperatorComponent(CustomComponent):
|
||||
display_name = "Text Operator"
|
||||
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": {
|
||||
"display_name": "Operator",
|
||||
"info": "The operator to apply for comparing the texts.",
|
||||
"options": [
|
||||
"equals",
|
||||
"not equals",
|
||||
"contains",
|
||||
"starts with",
|
||||
"ends with",
|
||||
"exists"
|
||||
],
|
||||
"options": ["equals", "not equals", "contains", "starts with", "ends with", "exists"],
|
||||
},
|
||||
"case_sensitive": {
|
||||
"display_name": "Case Sensitive",
|
||||
|
|
@ -51,11 +45,8 @@ class TextOperatorComponent(CustomComponent):
|
|||
case_sensitive: bool = False,
|
||||
true_output: Optional[Text] = "",
|
||||
) -> Union[Text, Record]:
|
||||
|
||||
if not input_text or not match_text:
|
||||
raise ValueError(
|
||||
"Both 'input_text' and 'match_text' must be provided and non-empty."
|
||||
)
|
||||
raise ValueError("Both 'input_text' and 'match_text' must be provided and non-empty.")
|
||||
|
||||
if not case_sensitive:
|
||||
input_text = input_text.lower()
|
||||
|
|
@ -82,4 +73,4 @@ class TextOperatorComponent(CustomComponent):
|
|||
self.status = "Comparison failed, stopping execution."
|
||||
self.stop()
|
||||
|
||||
return output_record
|
||||
return output_record
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain.llms.base import BaseLanguageModel
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.language_models import BaseLanguageModel
|
||||
|
||||
|
||||
class ChatAntropicSpecsComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
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 langchain_core.language_models import BaseLanguageModel
|
||||
from langchain_openai import AzureChatOpenAI
|
||||
from pydantic.v1 import SecretStr
|
||||
|
||||
|
||||
class AzureChatOpenAISpecsComponent(CustomComponent):
|
||||
|
|
@ -84,13 +84,17 @@ class AzureChatOpenAISpecsComponent(CustomComponent):
|
|||
temperature: float = 0.7,
|
||||
max_tokens: Optional[int] = 1000,
|
||||
) -> BaseLanguageModel:
|
||||
if api_key:
|
||||
azure_api_key = SecretStr(api_key)
|
||||
else:
|
||||
azure_api_key = None
|
||||
try:
|
||||
llm = AzureChatOpenAI(
|
||||
model=model,
|
||||
azure_endpoint=azure_endpoint,
|
||||
azure_deployment=azure_deployment,
|
||||
api_version=api_version,
|
||||
api_key=api_key,
|
||||
api_key=azure_api_key,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
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.field_typing import BaseLanguageModel, NestedDict
|
||||
|
|
@ -59,11 +61,15 @@ class ChatOpenAIComponent(CustomComponent):
|
|||
) -> BaseLanguageModel:
|
||||
if not openai_api_base:
|
||||
openai_api_base = "https://api.openai.com/v1"
|
||||
if openai_api_key:
|
||||
api_key = SecretStr(openai_api_key)
|
||||
else:
|
||||
api_key = None
|
||||
return ChatOpenAI(
|
||||
max_tokens=max_tokens,
|
||||
model_kwargs=model_kwargs,
|
||||
model=model_name,
|
||||
base_url=openai_api_base,
|
||||
api_key=openai_api_key,
|
||||
api_key=api_key,
|
||||
temperature=temperature,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain.schema import BaseRetriever
|
||||
from langchain_community.retrievers import AmazonKendraRetriever
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
|
||||
|
||||
class AmazonKendraRetrieverComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain.schema import BaseRetriever
|
||||
from langchain_community.retrievers import MetalRetriever
|
||||
from metal_sdk.metal import Metal # type: ignore
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
|
||||
|
||||
class MetalRetrieverComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import json
|
||||
from typing import List
|
||||
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from langchain.chains.query_constructor.base import AttributeInfo
|
||||
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 langchain_core.language_models import BaseLanguageModel
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
|
||||
class VectaraSelfQueryRetriverComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from typing import List
|
||||
|
||||
from langchain.text_splitter import CharacterTextSplitter
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langflow.utils.util import unescape_string
|
||||
from langchain_text_splitters import CharacterTextSplitter
|
||||
|
||||
|
||||
class CharacterTextSplitterComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from langchain.text_splitter import Language
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langchain_text_splitters import Language, RecursiveCharacterTextSplitter
|
||||
|
||||
|
||||
class LanguageRecursiveTextSplitterComponent(CustomComponent):
|
||||
|
|
@ -61,7 +60,6 @@ class LanguageRecursiveTextSplitterComponent(CustomComponent):
|
|||
Returns:
|
||||
list[str]: The chunks of text.
|
||||
"""
|
||||
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||
|
||||
# Make sure chunk_size and chunk_overlap are ints
|
||||
if isinstance(chunk_size, str):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
from typing import Optional
|
||||
|
||||
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema import Record
|
||||
from langflow.utils.util import build_loader_repr_from_records, unescape_string
|
||||
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
||||
|
||||
|
||||
class RecursiveCharacterTextSplitterComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
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 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):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreInfo
|
||||
from langchain_community.vectorstores import VectorStore
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
|
||||
class VectorStoreInfoComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import importlib
|
||||
|
||||
from langchain.agents import Tool
|
||||
from langchain_experimental.utilities import PythonREPL
|
||||
|
||||
from langflow.base.tools.base import build_status_from_tool
|
||||
from langflow.custom import CustomComponent
|
||||
from langchain_core.tools import Tool
|
||||
|
||||
|
||||
class PythonREPLToolComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from langchain.embeddings.base import Embeddings
|
||||
|
||||
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||
from langflow.components.vectorstores.Redis import RedisComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
|
||||
class RedisSearchComponent(RedisComponent, LCVectorStoreComponent):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from langchain.embeddings.base import Embeddings
|
||||
|
||||
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||
from langflow.components.vectorstores.Weaviate import WeaviateVectorStoreComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
|
||||
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ from .SupabaseVectorStoreSearch import SupabaseSearchComponent
|
|||
from .VectaraSearch import VectaraSearchComponent
|
||||
from .WeaviateSearch import WeaviateSearchVectorStore
|
||||
from .pgvectorSearch import PGVectorSearchComponent
|
||||
from .Couchbase import CouchbaseSearchComponent # type: ignore
|
||||
|
||||
__all__ = [
|
||||
"AstraDBSearchComponent",
|
||||
"ChromaSearchComponent",
|
||||
"CouchbaseSearchComponent",
|
||||
"FAISSSearchComponent",
|
||||
"MongoDBAtlasSearchComponent",
|
||||
"PineconeSearchComponent",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
from typing import List
|
||||
|
||||
from langchain.embeddings.base import Embeddings
|
||||
|
||||
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
|
||||
from langflow.components.vectorstores.pgvector import PGVectorComponent
|
||||
from langflow.field_typing import Text
|
||||
from langflow.schema import Record
|
||||
from langchain_core.embeddings import Embeddings
|
||||
|
||||
|
||||
class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent):
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
from typing import List, Optional, Union
|
||||
|
||||
from langchain.schema import BaseRetriever
|
||||
from langchain_astradb import AstraDBVectorStore
|
||||
from langchain_astradb.utils.astradb import SetupMode
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.field_typing import Embeddings, VectorStore
|
||||
from langflow.schema import Record
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
|
||||
|
||||
class AstraDBVectorStoreComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
from typing import List, Optional, Union
|
||||
|
||||
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 langflow.interface.custom.custom_component import CustomComponent
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
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 langflow.field_typing import Embeddings
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
|
||||
class FAISSComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
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_pinecone._utilities import DistanceStrategy
|
||||
from langchain_pinecone.vectorstores import PineconeVectorStore
|
||||
|
|
@ -9,6 +6,8 @@ from langchain_pinecone.vectorstores import PineconeVectorStore
|
|||
from langflow.field_typing import Embeddings
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
|
||||
class PineconeComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
from typing import Optional, Union
|
||||
|
||||
from langchain.schema import BaseRetriever
|
||||
from langchain_community.vectorstores import VectorStore
|
||||
from langchain_community.vectorstores.qdrant import Qdrant
|
||||
|
||||
from langflow.field_typing import Embeddings
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
|
||||
class QdrantComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
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_core.retrievers import BaseRetriever
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langchain_core.embeddings import Embeddings
|
||||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
|
||||
class RedisComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
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 supabase.client import Client, create_client
|
||||
|
||||
from langflow.field_typing import Embeddings
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langchain_core.retrievers import BaseRetriever
|
||||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
|
||||
class SupabaseComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
from typing import Optional, Union
|
||||
|
||||
import weaviate # type: ignore
|
||||
from langchain.embeddings.base import Embeddings
|
||||
from langchain.schema import BaseRetriever
|
||||
from langchain_community.vectorstores import VectorStore, Weaviate
|
||||
from langchain_community.vectorstores import Weaviate
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ from .SupabaseVectorStore import SupabaseComponent
|
|||
from .Vectara import VectaraComponent
|
||||
from .Weaviate import WeaviateVectorStoreComponent
|
||||
from .pgvector import PGVectorComponent
|
||||
from .Couchbase import CouchbaseComponent
|
||||
|
||||
__all__ = [
|
||||
"AstraDBVectorStoreComponent",
|
||||
"ChromaComponent",
|
||||
"CouchbaseComponent",
|
||||
"FAISSComponent",
|
||||
"MongoDBAtlasComponent",
|
||||
"PineconeComponent",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
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_core.retrievers import BaseRetriever
|
||||
|
||||
from langflow.interface.custom.custom_component import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
from langchain_core.embeddings import Embeddings
|
||||
from langchain_core.vectorstores import VectorStore
|
||||
|
||||
|
||||
class PGVectorComponent(CustomComponent):
|
||||
|
|
|
|||
|
|
@ -2,17 +2,18 @@ from typing import Callable, Dict, Text, Union
|
|||
|
||||
from langchain.agents.agent import AgentExecutor
|
||||
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.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
|
||||
from langchain.schema import BaseOutputParser, BaseRetriever, Document
|
||||
from langchain.schema.embeddings import Embeddings
|
||||
from langchain.schema.language_model import BaseLanguageModel
|
||||
from langchain.schema.memory import BaseMemory
|
||||
from langchain.text_splitter import TextSplitter
|
||||
from langchain.tools import Tool
|
||||
from langchain_community.vectorstores import VectorStore
|
||||
from langchain_core.document_loaders import BaseLoader
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.embeddings import Embeddings
|
||||
from langchain_core.language_models import BaseLLM, BaseLanguageModel
|
||||
from langchain_core.memory import BaseMemory
|
||||
from langchain_core.output_parsers import BaseOutputParser
|
||||
from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate
|
||||
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
|
||||
NestedDict = Dict[str, Union[str, Dict]]
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ from typing import TYPE_CHECKING, Any, List, Optional
|
|||
from loguru import logger
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from langflow.graph.edge.utils import build_clean_params
|
||||
from langflow.schema.schema import INPUT_FIELD_NAME
|
||||
from langflow.services.deps import get_monitor_service
|
||||
from langflow.services.monitor.utils import log_message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -143,7 +141,6 @@ class ContractEdge(Edge):
|
|||
if not self.is_fulfilled:
|
||||
await self.honor(source, target)
|
||||
|
||||
log_transaction(self, source, target, "success")
|
||||
# If the target vertex is a power component we log messages
|
||||
if target.vertex_type == "ChatOutput" and (
|
||||
isinstance(target.params.get(INPUT_FIELD_NAME), str)
|
||||
|
|
@ -157,26 +154,9 @@ class ContractEdge(Edge):
|
|||
message=target.params.get(INPUT_FIELD_NAME, {}),
|
||||
session_id=target.params.get("session_id", ""),
|
||||
artifacts=target.artifacts,
|
||||
flow_id=target.graph.flow_id,
|
||||
)
|
||||
return self.result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
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}")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -14,7 +14,7 @@ from langflow.graph.graph.state_manager import GraphStateManager
|
|||
from langflow.graph.graph.utils import process_flow
|
||||
from langflow.graph.schema import InterfaceComponentTypes, RunOutputs
|
||||
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.schema import Record
|
||||
from langflow.schema.schema import INPUT_FIELD_NAME, InputType
|
||||
|
|
@ -242,6 +242,7 @@ class Graph:
|
|||
outputs: list[str],
|
||||
stream: bool,
|
||||
session_id: str,
|
||||
fallback_to_env_vars: bool,
|
||||
) -> List[Optional["ResultData"]]:
|
||||
"""
|
||||
Runs the graph with the given inputs.
|
||||
|
|
@ -289,7 +290,7 @@ class Graph:
|
|||
start_component_id = next(
|
||||
(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()
|
||||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
|
|
@ -315,6 +316,7 @@ class Graph:
|
|||
outputs: Optional[list[str]] = None,
|
||||
session_id: Optional[str] = None,
|
||||
stream: bool = False,
|
||||
fallback_to_env_vars: bool = False,
|
||||
) -> List[RunOutputs]:
|
||||
"""
|
||||
Run the graph with the given inputs and return the outputs.
|
||||
|
|
@ -340,6 +342,7 @@ class Graph:
|
|||
outputs=outputs,
|
||||
session_id=session_id,
|
||||
stream=stream,
|
||||
fallback_to_env_vars=fallback_to_env_vars,
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
@ -362,6 +365,7 @@ class Graph:
|
|||
outputs: Optional[list[str]] = None,
|
||||
session_id: Optional[str] = None,
|
||||
stream: bool = False,
|
||||
fallback_to_env_vars: bool = False,
|
||||
) -> List[RunOutputs]:
|
||||
"""
|
||||
Runs the graph with the given inputs.
|
||||
|
|
@ -403,6 +407,7 @@ class Graph:
|
|||
outputs=outputs or [],
|
||||
stream=stream,
|
||||
session_id=session_id or "",
|
||||
fallback_to_env_vars=fallback_to_env_vars,
|
||||
)
|
||||
run_output_object = RunOutputs(inputs=run_inputs, outputs=run_outputs)
|
||||
logger.debug(f"Run outputs: {run_output_object}")
|
||||
|
|
@ -468,9 +473,9 @@ class Graph:
|
|||
"""Marks a branch of the graph."""
|
||||
if visited is None:
|
||||
visited = set()
|
||||
visited.add(vertex_id)
|
||||
if vertex_id in visited:
|
||||
return
|
||||
visited.add(vertex_id)
|
||||
|
||||
self.mark_vertex(vertex_id, state)
|
||||
|
||||
|
|
@ -712,6 +717,7 @@ class Graph:
|
|||
vertex_id: str,
|
||||
inputs_dict: Optional[Dict[str, str]] = None,
|
||||
user_id: Optional[str] = None,
|
||||
fallback_to_env_vars: bool = False,
|
||||
):
|
||||
"""
|
||||
Builds a vertex in the graph.
|
||||
|
|
@ -733,7 +739,7 @@ class Graph:
|
|||
vertex = self.get_vertex(vertex_id)
|
||||
try:
|
||||
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:
|
||||
params = vertex._built_object_repr()
|
||||
|
|
@ -796,7 +802,7 @@ class Graph:
|
|||
vertices.append(vertex)
|
||||
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."""
|
||||
|
||||
first_layer = self.sort_vertices(start_component_id=start_component_id)
|
||||
|
|
@ -821,6 +827,7 @@ class Graph:
|
|||
vertex_id=vertex_id,
|
||||
user_id=self.user_id,
|
||||
inputs_dict={},
|
||||
fallback_to_env_vars=fallback_to_env_vars,
|
||||
),
|
||||
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."""
|
||||
# First we check for the node_base_type
|
||||
node_name = node_id.split("-")[0]
|
||||
if node_name in ["ChatOutput", "ChatInput"]:
|
||||
return ChatVertex
|
||||
if node_name in InterfaceComponentTypes:
|
||||
return InterfaceVertex
|
||||
elif node_name in ["SharedState", "Notify", "Listen"]:
|
||||
return StateVertex
|
||||
elif node_base_type in lazy_load_vertex_dict.VERTEX_TYPE_MAP:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from langflow.graph.schema import CHAT_COMPONENTS
|
||||
from langflow.graph.vertex import types
|
||||
from langflow.interface.agents.base import agent_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.utils.lazy_load import LazyLoadDictBase
|
||||
|
||||
CHAT_COMPONENTS = ["ChatInput", "ChatOutput", "TextInput", "SessionID"]
|
||||
|
||||
|
||||
class VertexTypesDict(LazyLoadDictBase):
|
||||
def __init__(self):
|
||||
|
|
@ -47,7 +46,7 @@ class VertexTypesDict(LazyLoadDictBase):
|
|||
**{t: types.TextSplitterVertex for t in textsplitter_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.ChatVertex for t in CHAT_COMPONENTS},
|
||||
**{t: types.InterfaceVertex for t in CHAT_COMPONENTS},
|
||||
}
|
||||
|
||||
def get_custom_component_vertex_type(self):
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta):
|
|||
ChatOutput = "ChatOutput"
|
||||
TextInput = "TextInput"
|
||||
TextOutput = "TextOutput"
|
||||
RecordsOutput = "RecordsOutput"
|
||||
|
||||
def __contains__(cls, item):
|
||||
try:
|
||||
|
|
@ -40,6 +41,8 @@ class InterfaceComponentTypes(str, Enum, metaclass=ContainsEnumMeta):
|
|||
return True
|
||||
|
||||
|
||||
CHAT_COMPONENTS = [InterfaceComponentTypes.ChatInput, InterfaceComponentTypes.ChatOutput]
|
||||
RECORDS_COMPONENTS = [InterfaceComponentTypes.RecordsOutput]
|
||||
INPUT_COMPONENTS = [
|
||||
InterfaceComponentTypes.ChatInput,
|
||||
InterfaceComponentTypes.TextInput,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from loguru import logger
|
|||
|
||||
from langflow.graph.schema import INPUT_COMPONENTS, OUTPUT_COMPONENTS, InterfaceComponentTypes, ResultData
|
||||
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.listing import lazy_load_dict
|
||||
from langflow.schema.schema import INPUT_FIELD_NAME
|
||||
|
|
@ -317,7 +317,11 @@ class Vertex:
|
|||
params[field_name] = full_path
|
||||
elif field.get("required"):
|
||||
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:
|
||||
val = field.get("value")
|
||||
if field.get("type") == "code":
|
||||
|
|
@ -392,13 +396,17 @@ class Vertex:
|
|||
self.params = self._raw_params.copy()
|
||||
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.
|
||||
"""
|
||||
logger.debug(f"Building {self.display_name}")
|
||||
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._built = True
|
||||
|
|
@ -434,7 +442,11 @@ class Vertex:
|
|||
# to the frontend
|
||||
self.set_artifacts()
|
||||
artifacts = self.artifacts
|
||||
messages = self.extract_messages_from_artifacts(artifacts)
|
||||
if isinstance(artifacts, dict):
|
||||
messages = self.extract_messages_from_artifacts(artifacts)
|
||||
else:
|
||||
messages = []
|
||||
|
||||
result_dict = ResultData(
|
||||
results=result_dict,
|
||||
artifacts=artifacts,
|
||||
|
|
@ -502,7 +514,7 @@ class Vertex:
|
|||
if not self._is_vertex(value):
|
||||
self.params[key][sub_key] = value
|
||||
else:
|
||||
result = await value.get_result()
|
||||
result = await value.get_result(self)
|
||||
self.params[key][sub_key] = result
|
||||
|
||||
def _is_vertex(self, value):
|
||||
|
|
@ -517,9 +529,7 @@ class Vertex:
|
|||
"""
|
||||
return all(self._is_vertex(vertex) for vertex in value)
|
||||
|
||||
async def get_result(
|
||||
self,
|
||||
) -> Any:
|
||||
async def get_result(self, requester: "Vertex") -> Any:
|
||||
"""
|
||||
Retrieves the result of the vertex.
|
||||
|
||||
|
|
@ -529,9 +539,9 @@ class Vertex:
|
|||
The result of the vertex.
|
||||
"""
|
||||
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.
|
||||
|
||||
|
|
@ -541,15 +551,19 @@ class Vertex:
|
|||
The built result if use_result is True, else the built object.
|
||||
"""
|
||||
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")
|
||||
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"):
|
||||
"""
|
||||
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)
|
||||
if isinstance(result, list):
|
||||
self._extend_params_list_with_result(key, result)
|
||||
|
|
@ -565,7 +579,7 @@ class Vertex:
|
|||
"""
|
||||
self.params[key] = []
|
||||
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
|
||||
# because sometimes it is a Record and breaks the code
|
||||
if not isinstance(self.params[key], list):
|
||||
|
|
@ -608,7 +622,7 @@ class Vertex:
|
|||
if isinstance(self.params[key], list):
|
||||
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.
|
||||
"""
|
||||
|
|
@ -617,6 +631,7 @@ class Vertex:
|
|||
try:
|
||||
result = await loading.instantiate_class(
|
||||
user_id=user_id,
|
||||
fallback_to_env_vars=fallback_to_env_vars,
|
||||
vertex=self,
|
||||
)
|
||||
self._update_built_object_and_artifacts(result)
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ import yaml
|
|||
from langchain_core.messages import AIMessage
|
||||
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.vertex.base import Vertex
|
||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||
from langflow.schema import Record
|
||||
from langflow.schema.schema import INPUT_FIELD_NAME
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -309,7 +309,7 @@ class CustomComponentVertex(Vertex):
|
|||
return self.artifacts["repr"] or super()._built_object_repr()
|
||||
|
||||
|
||||
class ChatVertex(Vertex):
|
||||
class InterfaceVertex(Vertex):
|
||||
def __init__(self, data: Dict, graph):
|
||||
super().__init__(data, graph=graph, base_type="custom_components", is_task=True)
|
||||
self.steps = [self._build, self._run]
|
||||
|
|
@ -325,56 +325,131 @@ class ChatVertex(Vertex):
|
|||
return f"Task {self.task_id} is not running"
|
||||
if self.artifacts:
|
||||
# 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)
|
||||
return yaml_str
|
||||
return super()._built_object_repr()
|
||||
|
||||
def _process_chat_component(self):
|
||||
"""
|
||||
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
|
||||
sender = self.params.get("sender", None)
|
||||
sender_name = self.params.get("sender_name", None)
|
||||
message = self.params.get(INPUT_FIELD_NAME, None)
|
||||
if isinstance(message, str):
|
||||
message = unescape_string(message)
|
||||
stream_url = None
|
||||
if isinstance(self._built_object, AIMessage):
|
||||
artifacts = ChatOutputResponse.from_message(
|
||||
self._built_object,
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
)
|
||||
elif not isinstance(self._built_object, UnbuiltObject):
|
||||
if isinstance(self._built_object, dict):
|
||||
# Turn the dict into a pleasing to
|
||||
# read JSON inside a code block
|
||||
message = dict_to_codeblock(self._built_object)
|
||||
elif isinstance(self._built_object, Record):
|
||||
message = self._built_object.text
|
||||
elif isinstance(message, (AsyncIterator, Iterator)):
|
||||
stream_url = self.build_stream_url()
|
||||
message = ""
|
||||
elif not isinstance(self._built_object, str):
|
||||
message = str(self._built_object)
|
||||
# if the message is a generator or iterator
|
||||
# it means that it is a stream of messages
|
||||
else:
|
||||
message = self._built_object
|
||||
|
||||
artifacts = ChatOutputResponse(
|
||||
message=message,
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
stream_url=stream_url,
|
||||
)
|
||||
|
||||
self.will_stream = stream_url is not None
|
||||
if artifacts:
|
||||
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 ["ChatOutput", "ChatInput"]:
|
||||
artifacts = None
|
||||
sender = self.params.get("sender", None)
|
||||
sender_name = self.params.get("sender_name", None)
|
||||
message = self.params.get(INPUT_FIELD_NAME, None)
|
||||
if isinstance(message, str):
|
||||
message = unescape_string(message)
|
||||
stream_url = None
|
||||
if isinstance(self._built_object, AIMessage):
|
||||
artifacts = ChatOutputResponse.from_message(
|
||||
self._built_object,
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
)
|
||||
elif not isinstance(self._built_object, UnbuiltObject):
|
||||
if isinstance(self._built_object, dict):
|
||||
# Turn the dict into a pleasing to
|
||||
# read JSON inside a code block
|
||||
message = dict_to_codeblock(self._built_object)
|
||||
elif isinstance(self._built_object, Record):
|
||||
message = self._built_object.text
|
||||
elif isinstance(message, (AsyncIterator, Iterator)):
|
||||
stream_url = self.build_stream_url()
|
||||
message = ""
|
||||
elif not isinstance(self._built_object, str):
|
||||
message = str(self._built_object)
|
||||
# if the message is a generator or iterator
|
||||
# it means that it is a stream of messages
|
||||
else:
|
||||
message = self._built_object
|
||||
|
||||
artifacts = ChatOutputResponse(
|
||||
message=message,
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
stream_url=stream_url,
|
||||
)
|
||||
|
||||
self.will_stream = stream_url is not None
|
||||
if artifacts:
|
||||
self.artifacts = artifacts.model_dump(exclude_none=True)
|
||||
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 self.params["return_record"]:
|
||||
if self.params.get("return_record", False):
|
||||
self._built_object = Record(text=message, data=self.artifacts)
|
||||
else:
|
||||
self._built_object = message
|
||||
|
|
|
|||
|
|
@ -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.runnables import Runnable
|
||||
from loguru import logger
|
||||
|
||||
from langflow.services.deps import get_monitor_service
|
||||
from langflow.utils.constants import PYTHON_BASIC_TYPES
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langflow.graph.vertex.base import Vertex
|
||||
|
||||
|
||||
def is_basic_type(obj):
|
||||
return type(obj) in PYTHON_BASIC_TYPES
|
||||
|
|
@ -63,3 +67,49 @@ async def generate_result(built_object: Any, inputs: dict, has_external_output:
|
|||
else:
|
||||
result = built_object
|
||||
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}")
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ from sqlmodel import select
|
|||
from langflow.base.constants import FIELD_FORMAT_ATTRIBUTES, NODE_FORMAT_ATTRIBUTES
|
||||
from langflow.interface.types import get_all_components
|
||||
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
|
||||
|
||||
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
|
||||
# starter projects. We want to load these into the database so that users
|
||||
|
|
@ -158,6 +159,7 @@ def create_new_project(
|
|||
project_data,
|
||||
project_icon,
|
||||
project_icon_bg_color,
|
||||
new_folder_id
|
||||
):
|
||||
logger.debug(f"Creating starter project {project_name}")
|
||||
new_project = FlowCreate(
|
||||
|
|
@ -168,33 +170,41 @@ def create_new_project(
|
|||
data=project_data,
|
||||
is_component=project_is_component,
|
||||
updated_at=updated_at_datetime,
|
||||
folder=STARTER_FOLDER_NAME,
|
||||
folder_id=new_folder_id,
|
||||
)
|
||||
db_flow = Flow.model_validate(new_project, from_attributes=True)
|
||||
session.add(db_flow)
|
||||
|
||||
|
||||
def get_all_flows_similar_to_project(session, project_name):
|
||||
flows = session.exec(
|
||||
select(Flow).where(
|
||||
Flow.name == project_name,
|
||||
Flow.folder == STARTER_FOLDER_NAME,
|
||||
)
|
||||
).all()
|
||||
def get_all_flows_similar_to_project(session, folder_id):
|
||||
flows = session.exec(select(Folder).where(Folder.id == folder_id)).first().flows
|
||||
return flows
|
||||
|
||||
|
||||
def delete_start_projects(session):
|
||||
flows = session.exec(
|
||||
select(Flow).where(
|
||||
Flow.folder == STARTER_FOLDER_NAME,
|
||||
)
|
||||
).all()
|
||||
def delete_start_projects(session, folder_id):
|
||||
flows = session.exec(select(Folder).where(Folder.id == folder_id)).first().flows
|
||||
for flow in flows:
|
||||
session.delete(flow)
|
||||
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():
|
||||
components_paths = get_settings_service().settings.COMPONENTS_PATH
|
||||
try:
|
||||
|
|
@ -203,8 +213,9 @@ def create_or_update_starter_projects():
|
|||
logger.exception(f"Error loading components: {e}")
|
||||
raise e
|
||||
with session_scope() as session:
|
||||
new_folder = create_starter_folder(session)
|
||||
starter_projects = load_starter_projects()
|
||||
delete_start_projects(session)
|
||||
delete_start_projects(session, new_folder.id)
|
||||
for project_path, project in starter_projects:
|
||||
(
|
||||
project_name,
|
||||
|
|
@ -224,7 +235,7 @@ def create_or_update_starter_projects():
|
|||
|
||||
update_project_file(project_path, project, updated_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)
|
||||
|
||||
create_new_project(
|
||||
|
|
@ -236,4 +247,5 @@ def create_or_update_starter_projects():
|
|||
project_data,
|
||||
project_icon,
|
||||
project_icon_bg_color,
|
||||
new_folder.id
|
||||
)
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@
|
|||
"load_from_db": true,
|
||||
"title_case": false,
|
||||
"input_types": ["Text"],
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"stream": {
|
||||
"type": "bool",
|
||||
|
|
@ -716,9 +716,9 @@
|
|||
"edges": [
|
||||
{
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -736,13 +736,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -760,13 +760,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "user_input",
|
||||
|
|
@ -784,7 +784,7 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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": {
|
||||
|
|
|
|||
|
|
@ -566,7 +566,7 @@
|
|||
"load_from_db": false,
|
||||
"title_case": false,
|
||||
"input_types": ["Text"],
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"stream": {
|
||||
"type": "bool",
|
||||
|
|
@ -854,9 +854,9 @@
|
|||
{
|
||||
"source": "URL-HYPkR",
|
||||
"target": "Prompt-Rse03",
|
||||
"sourceHandle": "{œbaseClassesœ:[œRecordœ],œdataTypeœ:œURLœ,œidœ:œURL-HYPkRœ}",
|
||||
"targetHandle": "{œfieldNameœ:œreference_2œ,œidœ:œPrompt-Rse03œ,œinputTypesœ:[œDocumentœ,œBaseOutputParserœ,œRecordœ,œTextœ],œtypeœ:œstrœ}",
|
||||
"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œ}",
|
||||
"sourceHandle": "{\u0153baseClasses\u0153:[\u0153Record\u0153],\u0153dataType\u0153:\u0153URL\u0153,\u0153id\u0153:\u0153URL-HYPkR\u0153}",
|
||||
"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{\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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "reference_2",
|
||||
|
|
@ -878,9 +878,9 @@
|
|||
},
|
||||
{
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -898,13 +898,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "reference_1",
|
||||
|
|
@ -922,13 +922,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "instructions",
|
||||
|
|
@ -946,13 +946,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -970,7 +970,7 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -711,7 +711,7 @@
|
|||
"load_from_db": false,
|
||||
"title_case": false,
|
||||
"input_types": ["Text"],
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"stream": {
|
||||
"type": "bool",
|
||||
|
|
@ -825,9 +825,9 @@
|
|||
"edges": [
|
||||
{
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "Question",
|
||||
|
|
@ -845,13 +845,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "Document",
|
||||
|
|
@ -869,13 +869,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -893,13 +893,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -917,7 +917,7 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "08d5cccf-d098-4367-b14b-1078429c9ed9",
|
||||
"icon": "🤖",
|
||||
"icon": "\ud83e\udd16",
|
||||
"icon_bg_color": "#FFD700",
|
||||
"data": {
|
||||
"nodes": [
|
||||
|
|
@ -792,7 +792,7 @@
|
|||
"load_from_db": false,
|
||||
"title_case": false,
|
||||
"input_types": ["Text"],
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"stream": {
|
||||
"type": "bool",
|
||||
|
|
@ -1003,9 +1003,9 @@
|
|||
"edges": [
|
||||
{
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "context",
|
||||
|
|
@ -1023,14 +1023,14 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "user_message",
|
||||
|
|
@ -1048,14 +1048,14 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -1073,13 +1073,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -1097,13 +1097,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -1121,7 +1121,7 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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": {
|
||||
|
|
|
|||
|
|
@ -911,7 +911,7 @@
|
|||
"load_from_db": false,
|
||||
"title_case": false,
|
||||
"input_types": ["Text"],
|
||||
"value": ""
|
||||
"value": "OPENAI_API_KEY"
|
||||
},
|
||||
"stream": {
|
||||
"type": "bool",
|
||||
|
|
@ -1382,9 +1382,9 @@
|
|||
"edges": [
|
||||
{
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "document",
|
||||
|
|
@ -1402,13 +1402,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -1426,13 +1426,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -1450,13 +1450,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "summary",
|
||||
|
|
@ -1474,13 +1474,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -1498,13 +1498,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -1522,13 +1522,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -1546,13 +1546,13 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"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": {
|
||||
"targetHandle": {
|
||||
"fieldName": "input_value",
|
||||
|
|
@ -1570,7 +1570,7 @@
|
|||
"stroke": "#555"
|
||||
},
|
||||
"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": {
|
||||
|
|
|
|||
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
Loading…
Add table
Add a link
Reference in a new issue