New Component, Multiple outputs and type refactor (#2246)

This PR adds a new way of building Langflow Components, adds the
possibility of defining more than one output and adds the `io` module
that exposes many types of inputs.

The components that inherit from `CustomComponent` should still work. 

Components now mostly output `Data`(former `Record`) and `Message`. 

`MessageInput` passes a `Message` object to the component while
`TextInput` gets the `.text` from the `Message` and passes it as a
string.
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-06-21 12:11:56 -07:00 • committed by GitHub
commit 65e1c4230a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
584 changed files with 29315 additions and 22179 deletions

View file

@ -53,26 +53,6 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# - name: Build Docker Image
# uses: docker/build-push-action@v5
# id: docker_build
# with:
# context: .
# push: false # Do not push yet
# platforms: "linux/amd64,linux/arm64/v8"
# load: true
# file: ${{ matrix.file }}
# tags: ${{ env.TEST_TAG }}
# - name: Run Container
# run: |
# docker run -d --name test_container -p 8000:8000 ${{ needs.setup.outputs.tags }}
# - name: Wait for Container to Start and Check Health
# run: |
# timeout 40 bash -c 'until curl -f http://127.0.0.1:8000/health; do sleep 1; done' || (echo "Server did not start in time" && docker logs test_container && docker stop test_container && docker rm test_container && exit 1)
# - name: Stop and Remove Container
# run: |
# docker stop test_container
# docker rm test_container
- name: Build and Push Docker Image
uses: docker/build-push-action@v5
with:

View file

@ -10,8 +10,10 @@ env:
jobs:
run-linters:
name: Run linters
name: Run Prettier
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
@ -38,16 +40,13 @@ jobs:
npm install
if: ${{ steps.setup-node.outputs.cache-hit != 'true' }}
- name: Run linters
uses: wearerequired/lint-action@v2
- name: Run Prettier
run: |
cd src/frontend
npm run format
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
github_token: ${{ secrets.github_token }}
auto_fix: true
git_email: "gabriel@langflow.org"
# Enable linters
# eslint: true
# eslint_auto_fix: true
prettier: true
prettier_auto_fix: true
prettier_args: '--write \"{tests,src}/**/*.{js,jsx,ts,tsx,json,md}\" --ignore-path .prettierignore'
commit_message: Apply Prettier formatting
branch: ${{ github.head_ref }}

View file

@ -36,5 +36,11 @@ jobs:
- name: Run Ruff
run: poetry run ruff check --output-format=github .
- name: Run Ruff format
run: poetry run ruff format --check .
run: poetry run ruff format .
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Apply Ruff formatting
branch: ${{ github.head_ref }}

6
.gitignore vendored
View file

@ -268,5 +268,7 @@ stuff/*
src/frontend/playwright-report/index.html
*.bak
prof/*
*-shm
*-wal
src/frontend/temp
*.db-shm
*.db-wal

4
.vscode/launch.json vendored
View file

@ -15,7 +15,9 @@
"--log-level",
"debug",
"--loop",
"asyncio"
"asyncio",
"--reload-include",
"src/backend/*"
],
"jinja": true,
"justMyCode": false,

View file

@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

View file

@ -208,6 +208,7 @@ ifdef base
endif
ifdef main
make build_frontend
make build_langflow
endif

View file

@ -106,26 +106,26 @@ python -m langflow run
### Options
| Option | Description |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--help` | Displays all available options. |
| `--host` | Defines the host to bind the server to. Can be set using the `LANGFLOW_HOST` environment variable. The default is `127.0.0.1`. |
| `--workers` | Sets the number of worker processes. Can be set using the `LANGFLOW_WORKERS` environment variable. The default is `1`. |
| `--timeout` | Sets the worker timeout in seconds. The default is `60`. |
| `--port` | Sets the port to listen on. Can be set using the `LANGFLOW_PORT` environment variable. The default is `7860`. |
| `--env-file` | Specifies the path to the .env file containing environment variables. The default is `.env`. |
| `--log-level` | Defines the logging level. Can be set using the `LANGFLOW_LOG_LEVEL` environment variable. The default is `critical`. |
| `--components-path` | Specifies the path to the directory containing custom components. Can be set using the `LANGFLOW_COMPONENTS_PATH` environment variable. The default is `langflow/components`. |
| `--log-file` | Specifies the path to the log file. Can be set using the `LANGFLOW_LOG_FILE` environment variable. The default is `logs/langflow.log`. |
| `--cache` | Select the type of cache to use. Options are `InMemoryCache` and `SQLiteCache`. Can be set using the `LANGFLOW_LANGCHAIN_CACHE` environment variable. The default is `SQLiteCache`. |
| `--dev`/`--no-dev` | Toggles the development mode. The default is `no-dev`. |
| `--path` | Specifies the path to the frontend directory containing build files. This option is for development purposes only. Can be set using the `LANGFLOW_FRONTEND_PATH` environment variable. |
| `--open-browser`/`--no-open-browser` | Toggles the option to open the browser after starting the server. Can be set using the `LANGFLOW_OPEN_BROWSER` environment variable. The default is `open-browser`. |
| `--remove-api-keys`/`--no-remove-api-keys` | Toggles the option to remove API keys from the projects saved in the database. Can be set using the `LANGFLOW_REMOVE_API_KEYS` environment variable. The default is `no-remove-api-keys`. |
| `--install-completion [bash\|zsh\|fish\|powershell\|pwsh]` | Installs completion for the specified shell. |
| `--show-completion [bash\|zsh\|fish\|powershell\|pwsh]` | Shows completion for the specified shell, allowing you to copy it or customize the installation. |
| `--backend-only` | This parameter, with a default value of `False`, allows running only the backend server without the frontend. It can also be set using the `LANGFLOW_BACKEND_ONLY` environment variable. For more, see [Backend-only](../deployment/backend-only.md). |
| `--store` | This parameter, with a default value of `True`, enables the store features, use `--no-store` to deactivate it. It can be configured using the `LANGFLOW_STORE` environment variable. |
| Option | Description |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--help` | Displays all available options. |
| `--host` | Defines the host to bind the server to. Can be set using the `LANGFLOW_HOST` environment variable. The default is `127.0.0.1`. |
| `--workers` | Sets the number of worker processes. Can be set using the `LANGFLOW_WORKERS` environment variable. The default is `1`. |
| `--timeout` | Sets the worker timeout in seconds. The default is `60`. |
| `--port` | Sets the port to listen on. Can be set using the `LANGFLOW_PORT` environment variable. The default is `7860`. |
| `--env-file` | Specifies the path to the .env file containing environment variables. The default is `.env`. |
| `--log-level` | Defines the logging level. Can be set using the `LANGFLOW_LOG_LEVEL` environment variable. The default is `critical`. |
| `--components-path` | Specifies the path to the directory containing custom components. Can be set using the `LANGFLOW_COMPONENTS_PATH` environment variable. The default is `langflow/components`. |
| `--log-file` | Specifies the path to the log file. Can be set using the `LANGFLOW_LOG_FILE` environment variable. The default is `logs/langflow.log`. |
| `--cache` | Select the type of cache to use. Options are `InMemoryCache` and `SQLiteCache`. Can be set using the `LANGFLOW_LANGCHAIN_CACHE` environment variable. The default is `SQLiteCache`. |
| `--dev`/`--no-dev` | Toggles the development mode. The default is `no-dev`. |
| `--path` | Specifies the path to the frontend directory containing build files. This option is for development purposes only. Can be set using the `LANGFLOW_FRONTEND_PATH` environment variable. |
| `--open-browser`/`--no-open-browser` | Toggles the option to open the browser after starting the server. Can be set using the `LANGFLOW_OPEN_BROWSER` environment variable. The default is `open-browser`. |
| `--remove-api-keys`/`--no-remove-api-keys` | Toggles the option to remove API keys from the projects saved in the database. Can be set using the `LANGFLOW_REMOVE_API_KEYS` environment variable. The default is `no-remove-api-keys`. |
| `--install-completion [bash\|zsh\|fish\|powershell\|pwsh]` | Installs completion for the specified shell. |
| `--show-completion [bash\|zsh\|fish\|powershell\|pwsh]` | Shows completion for the specified shell, allowing you to copy it or customize the installation. |
| `--backend-only` | This parameter, with a default value of `False`, allows running only the backend server without the frontend. It can also be set using the `LANGFLOW_BACKEND_ONLY` environment variable. For more, see [Backend-only](../deployment/backend-only). |
| `--store` | This parameter, with a default value of `True`, enables the store features, use `--no-store` to deactivate it. It can be configured using the `LANGFLOW_STORE` environment variable. |
#### CLI environment variables

View file

@ -44,9 +44,9 @@ Adding or removing any of the below components modifies your Playground so you c
- Text Input
- Chat Output
- Text Output
- Records Output
- Data Output
- Inspect Memory
You can also select **Options** > **Logs** to see your flow's logs.
For more information, see [Inputs and Outputs](../components/inputs-and-outputs.mdx).
For more information, see [Inputs and Outputs](../components/inputs-and-outputs)

View file

@ -24,15 +24,15 @@ Provide the session ID to clear its message history.
---
## Extract Key From Record
## Extract Key From Data
This component extracts specified keys from a record.
**Parameters**
- **Record:**
- **Data:**
- **Display Name:** Record
- **Display Name:** Data
- **Info:** The record from which to extract keys.
- **Keys:**
@ -112,18 +112,18 @@ Call this component without parameters to list all flows.
---
## Merge Records
## Merge Data
This component merges a list of records.
This component merges a list of Data.
**Parameters**
- **Records:**
- **Display Name:** Records
- **Data:**
- **Display Name:** Data
**Usage**
Provide the records you want to merge.
Provide the Data you want to merge.
---
@ -138,9 +138,9 @@ This component generates a notification.
- **Display Name:** Name
- **Info:** The notification's name.
- **Record:**
- **Data:**
- **Display Name:** Record
- **Display Name:** Data
- **Info:** Optionally, a record to store in the notification.
- **Append:**

View file

@ -13,7 +13,7 @@ This component retrieves stored chat messages based on a specific session ID.
- **Number of messages:** Number of messages to retrieve.
- **Session ID:** The session ID of the chat history.
- **Order:** Choose the message order, either "Ascending" or "Descending".
- **Record template:** (Optional) Template to convert a record to text. If left empty, the system dynamically sets it to the record's text key.
- **Data template:** (Optional) Template to convert a record to text. If left empty, the system dynamically sets it to the record's text key.
---
@ -59,13 +59,13 @@ Learn more about creating custom components at [Custom Component](http://docs.la
---
### Documents to records
### Documents to Data
Convert LangChain documents into records.
Convert LangChain documents into Data.
#### Parameters
- **Documents:** Documents to be converted into records.
- **Documents:** Documents to be converted into Data.
---
@ -93,14 +93,14 @@ Retrieves stored chat messages based on a specific session ID.
---
### Records to text
### Data to text
Convert records into plain text following a specified template.
Convert Data into plain text following a specified template.
#### Parameters
- **Records:** The records to convert to text.
- **Template:** The template used for formatting the records. It can contain keys like `{text}`, `{data}`, or any other key in the record.
- **Data:** The Data to convert to text.
- **Template:** The template used for formatting the Data. It can contain keys like `{text}`, `{data}`, or any other key in the record.
---
@ -124,5 +124,5 @@ Update a record with text-based key/value pairs, similar to updating a Python di
#### Parameters
- **Record:** The record to update.
- **Data:** The record to update.
- **New data:** The new data to update the record with.

View file

@ -8,11 +8,11 @@ They also dynamically change the Playground and can be renamed to facilitate bui
## Inputs
Inputs are components used to define where data enters your flow. They can receive data from the user, a database, or any other source that can be converted to Text or Record.
Inputs are components used to define where data enters your flow. They can receive data from the user, a database, or any other source that can be converted to Text or Data.
The difference between Chat Input and other Input components is the output format, the number of configurable fields, and the way they are displayed in the Playground.
Chat Input components can output `Text` or `Record`. When you want to pass the sender name or sender to the next component, use the `Record` output. To pass only the message, use the `Text` output, useful when saving the message to a database or memory system like Zep.
Chat Input components can output `Text` or `Data`. When you want to pass the sender name or sender to the next component, use the `Data` output. To pass only the message, use the `Text` output, useful when saving the message to a database or memory system like Zep.
You can find out more about Chat Input and other Inputs [here](#chat-input).
@ -38,8 +38,8 @@ This component collects user input from the chat.
<Admonition type="note" title="Note">
<p>
If `As Record` is `true` and the `Message` is a `Record`, the data of the
`Record` will be updated with the `Sender`, `Sender Name`, and `Session ID`.
If `As Data` is `true` and the `Message` is a `Data`, the data of the `Data`
will be updated with the `Sender`, `Sender Name`, and `Session ID`.
</p>
</Admonition>
@ -70,11 +70,11 @@ The **Text Input** component adds an **Input** field on the Playground. This ena
**Parameters**
- **Value:** Specifies the text input value. This is where the user inputs text data that will be passed to the next component in the sequence. If no value is provided, it defaults to an empty string.
- **Record Template:** Specifies how a `Record` should be converted into `Text`.
- **Data Template:** Specifies how a `Data` should be converted into `Text`.
The **Record Template** field is used to specify how a `Record` should be converted into `Text`. This is particularly useful when you want to extract specific information from a `Record` and pass it as text to the next component in the sequence.
The **Data Template** field is used to specify how a `Data` should be converted into `Text`. This is particularly useful when you want to extract specific information from a `Data` and pass it as text to the next component in the sequence.
For example, if you have a `Record` with the following structure:
For example, if you have a `Data` with the following structure:
```json
{
@ -84,9 +84,9 @@ For example, if you have a `Record` with the following structure:
}
```
A template with `Name: {name}, Age: {age}` will convert the `Record` into a text string of `Name: John Doe, Age: 30`.
A template with `Name: {name}, Age: {age}` will convert the `Data` into a text string of `Name: John Doe, Age: 30`.
If you pass more than one `Record`, the text will be concatenated with a new line separator.
If you pass more than one `Data`, the text will be concatenated with a new line separator.
## Outputs
@ -112,8 +112,8 @@ This component sends a message to the chat.
<Admonition type="note" title="Note">
<p>
If `As Record` is `true` and the `Message` is a `Record`, the data in the
`Record` is updated with the `Sender`, `Sender Name`, and `Session ID`.
If `As Data` is `true` and the `Message` is a `Data`, the data in the `Data`
is updated with the `Sender`, `Sender Name`, and `Session ID`.
</p>
</Admonition>
@ -154,4 +154,5 @@ The `PromptTemplate` component enables users to create prompts and define variab
After defining a variable in the prompt template, it acts as its own component input. See [Prompt Customization](../administration/prompt-customization) for more details.
- **template:** The template used to format an individual request.
- **template:** The template used to format an individual request.import Admonition from "@theme/Admonition";
import ZoomableImage from "/src/theme/ZoomableImage.js";

View file

@ -1,16 +1,16 @@
# Text and Record
# Text and Data
In Langflow 1.0, we added two main input and output types: `Text` and `Record`.
In Langflow 1.0, we added two main input and output types: `Text` and `Data`.
`Text` is a simple string input and output type, while `Record` is a structure very similar to a dictionary in Python. It is a key-value pair data structure.
`Text` is a simple string input and output type, while `Data` is a structure very similar to a dictionary in Python. It is a key-value pair data structure.
We've created a few components to help you work with these types. Let's see how a few of them work.
## Records To Text
## Data To Text
This is a component that takes in Records and outputs a `Text`. It does this using a template string and concatenating the values of the `Record`, one per line.
This is a component that takes in Data and outputs a `Text`. It does this using a template string and concatenating the values of the `Data`, one per line.
If we have the following Records:
If we have the following Data:
```json
{
@ -32,13 +32,13 @@ Alice: Hello!
John: Hi!
```
## Create Record
## Create Data
This component allows you to create a `Record` from a number of inputs. You can add as many key-value pairs as you want (as long as it is less than 15). Once you've picked that number you'll need to write the name of the Key and can pass `Text` values from other components to it.
This component allows you to create a `Data` from a number of inputs. You can add as many key-value pairs as you want (as long as it is less than 15). Once you've picked that number you'll need to write the name of the Key and can pass `Text` values from other components to it.
## Documents To Records
## Documents To Data
This component takes in a LangChain `Document` and outputs a `Record`. It does this by extracting the `page_content` and the `metadata` from the `Document` and adding them to the `Record` as text and data respectively.
This component takes in a LangChain `Document` and outputs a `Data`. It does this by extracting the `page_content` and the `metadata` from the `Document` and adding them to the `Data` as text and data respectively.
## Why is this useful?

View file

@ -4,11 +4,11 @@ import Admonition from "@theme/Admonition";
### Astra DB
The `Astra DB` initializes a vector store using Astra DB from records. It creates Astra DB-based vector indexes to efficiently store and retrieve documents.
The `Astra DB` initializes a vector store using Astra DB from Data. It creates Astra DB-based vector indexes to efficiently store and retrieve documents.
**Parameters:**
- **Input:** Documents or records for input.
- **Input:** Documents or Data for input.
- **Embedding:** Embedding model Astra DB uses.
- **Collection Name:** Name of the Astra DB collection.
- **Token:** Authentication token for Astra DB.
@ -99,12 +99,12 @@ 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.
`Couchbase` builds a Couchbase vector store from Data, streamlining the storage and retrieval of documents.
**Parameters:**
- **Embedding:** Model used by Couchbase.
- **Input:** Documents or records.
- **Input:** Documents or Data.
- **Couchbase Cluster Connection String:** Cluster Connection string.
- **Couchbase Cluster Username:** Cluster Username.
- **Couchbase Cluster Password:** Cluster Password.
@ -165,12 +165,12 @@ For more details, see the [FAISS Component Documentation](https://faiss.ai/index
### MongoDB Atlas
`MongoDBAtlas` builds a MongoDB Atlas-based vector store from records, streamlining the storage and retrieval of documents.
`MongoDBAtlas` builds a MongoDB Atlas-based vector store from Data, streamlining the storage and retrieval of documents.
**Parameters:**
- **Embedding:** Model used by MongoDB Atlas.
- **Input:** Documents or records.
- **Input:** Documents or Data.
- **Collection Name:** Collection identifier in MongoDB Atlas.
- **Database Name:** Database identifier.
- **Index Name:** Index identifier.
@ -235,11 +235,11 @@ For more details, see the [PGVector Component Documentation](https://python.lang
### Pinecone
`Pinecone` constructs a Pinecone wrapper from records, setting up Pinecone-based vector indexes for document storage and retrieval.
`Pinecone` constructs a Pinecone wrapper from Data, setting up Pinecone-based vector indexes for document storage and retrieval.
**Parameters:**
- **Input:** Documents or records.
- **Input:** Documents or Data.
- **Embedding:** Model used.
- **Index Name:** Index identifier.
- **Namespace:** Namespace used.
@ -278,7 +278,7 @@ For more details, see the [PGVector Component Documentation](https://python.lang
**Parameters:**
- **Input:** Documents or records.
- **Input:** Documents or Data.
- **Embedding:** Model used.
- **API Key:** Qdrant API key.
- **Collection Name:** Collection identifier.
@ -345,7 +345,7 @@ For detailed documentation, refer to the [Redis Documentation](https://python.la
**Parameters:**
- **Input:** Documents or records.
- **Input:** Documents or data.
- **Embedding:** Model used.
- **Query Name:** Optional query name.
- **Search Kwargs:** Advanced search parameters.

View file

@ -15,7 +15,7 @@ You have a new document loader called **MyCustomDocumentLoader** and it would lo
5. Add the code to the [/components/documentloaders](https://github.com/langflow-ai/langflow/tree/dev/src/backend/base/langflow/components) folder.
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 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).
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

View file

@ -9,11 +9,11 @@ Langflow will now serve requests to its API without the frontend running.
## Prerequisites
- [Langflow installed](../getting-started/install-langflow.mdx)
- [Langflow installed](../getting-started/install-langflow)
- [OpenAI API key](https://platform.openai.com)
- [A Langflow flow created](../starter-projects/basic-prompting.mdx)
- [A Langflow flow created](../starter-projects/basic-prompting)
## Download your flow's curl call
@ -120,4 +120,4 @@ The result is similar to the curl call:
Your Python app POSTs to your Langflow server, and the server runs the flow and returns the result.
See [API](../administration/api.mdx) for more ways to interact with your headless Langflow server.
See [API](../administration/api) for more ways to interact with your headless Langflow server.

View file

@ -4,11 +4,11 @@ import ZoomableImage from "/src/theme/ZoomableImage.js";
import ReactPlayer from "react-player";
import Admonition from "@theme/Admonition";
# Create Record
# Create Data
In Langflow, a `Record` has a structure very similar to a Python dictionary. It is a key-value pair data structure.
In Langflow, a `Data` has a structure very similar to a Python dictionary. It is a key-value pair data structure.
The **Create Record** component allows you to dynamically create a `Record` from a specified number of inputs. You can add as many key-value pairs as you want (as long as it is less than 15 😅). Once you've chosen the number of `Records`, add keys and fill up values, or pass on values from other components to the component using the input handles.
The **Create Data** component allows you to dynamically create a `Data` from a specified number of inputs. You can add as many key-value pairs as you want (as long as it is less than 15 😅). Once you've chosen the number of `Data`, add keys and fill up values, or pass on values from other components to the component using the input handles.
<div
style={{ marginBottom: "20px", display: "flex", justifyContent: "center" }}

View file

@ -27,7 +27,7 @@ For example, the **OpenAI LLM** is a **component** of the **Basic prompting** fl
A **flow** is a pipeline of components connected together in the Langflow canvas.
For example, the [Basic prompting](../starter-projects/basic-prompting.mdx) flow is a pipeline of four components:
For example, the [Basic prompting](../starter-projects/basic-prompting) flow is a pipeline of four components:
<ZoomableImage
alt="Docusaurus themed image"
@ -117,7 +117,7 @@ You can modify the code and save it.
#### Save
Save your component to the **Saved** components folder for re-use.
Save your component to the **Saved** components folder for reuse.
#### Duplicate
@ -143,19 +143,19 @@ Duplicate your component in the canvas.
### Group multiple components
Components without input or output nodes can be grouped into a single component for re-use.
Components without input or output nodes can be grouped into a single component for reuse.
This is useful for combining large flows into single components (like RAG with a vector database, for example) and saves space in the canvas.
1. Hold **Shift** and drag to select the **Prompt** and **OpenAI** components.
2. Select **Group**.
3. The components merge into a single component.
4. To save the new component, select **Save**. It can now be re-used from the **Saved** components folder.
4. To save the new component, select **Save**. It can now be reused from the **Saved** components folder.
## Playground
Run your flow by clicking the **![Playground icon](/logos/botmessage.svg)Playground** button.
For more, see [Playground](../administration/playground.mdx).
For more, see [Playground](../administration/playground).
## API
@ -210,7 +210,7 @@ The **Python Code** tab displays code to interact with your flow's `.json` file
### Chat Widget HTML
The **Chat Widget HTML** tab displays code that can be inserted in the `<body>` of your HTML to interact with your flow.
For more, see the [Chat widget documentation](../administration/chat-widget.mdx).
For more, see the [Chat widget documentation](../administration/chat-widget).
### Tweaks
@ -250,7 +250,7 @@ Select **Download Collection** to save your project to your local machine. This
Select **Upload Collection** to upload a flow or component `.json` file from your local machine.
Select **New Project** to create a new project. In addition to a blank canvas, [starter projects](../starter-projects/basic-prompting.mdx) are also available.
Select **New Project** to create a new project. In addition to a blank canvas, [starter projects](../starter-projects/basic-prompting) are also available.
## Project options menu
@ -275,4 +275,8 @@ To see options for your project, in the upper left corner of the canvas, select
**Export** - Download your current project to your local machine as a `.json` file.
**Undo** or **Redo** - Undo or redo your last action.
**Undo** or **Redo** - Undo or redo your last action.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";

View file

@ -70,7 +70,7 @@ python -m langflow run
│ Collaborate, and contribute at our GitHub Repo 🚀 │
```
3. Continue on to the [Quickstart](./quickstart.mdx).
3. Continue on to the [Quickstart](./quickstart).
## HuggingFace Spaces
@ -89,4 +89,7 @@ You'll be presented with the following screen:
style={{ width: "80%", maxWidth: "800px", margin: "0 auto" }}
/>
Name your Space, define the visibility (Public or Private), and click on **Duplicate Space** to start the installation process. When installation is finished, you'll be redirected to the Space's main page to start using Langflow right away!
Name your Space, define the visibility (Public or Private), and click on **Duplicate Space** to start the installation process. When installation is finished, you'll be redirected to the Space's main page to start using Langflow right away!import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
import Admonition from "@theme/Admonition";

View file

@ -2,9 +2,9 @@
Large Language Models, or LLMs, are part of an exciting new world in computing.
We made Langflow for anyone to create with LLMs, and hope you'll feel comfortable installing Langflow and [getting started](./quickstart.mdx).
We made Langflow for anyone to create with LLMs, and hope you'll feel comfortable installing Langflow and [getting started](./quickstart).
If you want to learn more about LLMs, prompt engineering, and AI models, Langflow recommends [promptingguide.ai](https://promptingguide.ai), an open-source repository of prompt engineering content maintained by AI experts.
PromptingGuide offers content for [beginners](https://www.promptingguide.ai/introduction/basics) and [experts](https://www.promptingguide.ai/techniques/cot), as well as the latest [research papers](https://www.promptingguide.ai/papers) and [test results](https://www.promptingguide.ai/research) fueling AI's progress.
Wherever you are on your AI journey, it's helpful to keep Prompting Guide open in a tab.
Wherever you are on your AI journey, it's helpful to keep Prompting Guide open in a tab.

View file

@ -12,7 +12,7 @@ This guide demonstrates how to build a basic prompt flow and modify that prompt
- [Python >=3.10](https://www.python.org/downloads/release/python-3100/) and [pip](https://pypi.org/project/pip/) or [pipx](https://pipx.pypa.io/stable/installation/)
- [Langflow installed and running](./install-langflow.mdx)
- [Langflow installed and running](./install-langflow)
- [OpenAI API key](https://platform.openai.com)
@ -77,6 +77,10 @@ By adding Langflow components to your flow, you can create all sorts of interest
Here are a couple of examples:
- [Memory chatbot](/starter-projects/memory-chatbot.mdx)
- [Blog writer](/starter-projects/blog-writer.mdx)
- [Document QA](/starter-projects/document-qa.mdx)
- [Memory chatbot](/starter-projects/memory-chatbot)
- [Blog writer](/starter-projects/blog-writer)
- [Document QA](/starter-projects/document-qa)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";

View file

@ -136,10 +136,10 @@ The RAG flow is a bit more complex. It consists of:
- **Chat Input** component that defines where to put the user input coming from the Playground
- **OpenAI Embeddings** component that generates embeddings from the user input
- **Astra DB Search** component that retrieves the most relevant Records from the Astra DB database
- **Text Output** component that turns the Records into Text by concatenating them and also displays it in the Playground
- **Astra DB Search** component that retrieves the most relevant Data from the Astra DB database
- **Text Output** component that turns the Data into Text by concatenating them and also displays it in the Playground
- One interesting point you'll see here is that this component is named `Extracted Chunks`, and that is how it will appear in the Playground
- **Prompt** component that takes in the user input and the retrieved Records as text and builds a prompt for the OpenAI model
- **Prompt** component that takes in the user input and the retrieved Data as text and builds a prompt for the OpenAI model
- **OpenAI** component that generates a response to the prompt
- **Chat Output** component that displays the response in the Playground
@ -176,7 +176,7 @@ Because this flow has a **Chat Input** and a **Text Output** component, the Pane
style={{ width: "80%", margin: "20px auto" }}
/>
Once we interact with it we get a response and the Extracted Chunks section is updated with the retrieved records.
Once we interact with it we get a response and the Extracted Chunks section is updated with the retrieved data.
<ZoomableImage
alt="Docusaurus themed image"
@ -192,4 +192,16 @@ And that's it! You have successfully ran a RAG application using Astra DB and La
# Conclusion
In this guide, we have learned how to run a RAG application using Astra DB and Langflow.
We have seen how to create an Astra DB database, import the Astra DB RAG Flows project into Langflow, and run the ingestion and RAG flows.
We have seen how to create an Astra DB database, import the Astra DB RAG Flows project into Langflow, and run the ingestion and RAG flows.import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
import Admonition from "@theme/Admonition";import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
import Admonition from "@theme/Admonition";import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
import Admonition from "@theme/Admonition";import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
import Admonition from "@theme/Admonition";

View file

@ -33,7 +33,7 @@ import requests
from typing import Dict
from langflow import CustomComponent
from langflow.schema import Record
from langflow.schema import Data
class NotionDatabaseProperties(CustomComponent):
@ -61,7 +61,7 @@ class NotionDatabaseProperties(CustomComponent):
self,
database_id: str,
notion_secret: str,
) -> Record:
) -> Data:
url = f"https://api.notion.com/v1/databases/{database_id}"
headers = {
"Authorization": f"Bearer {notion_secret}",
@ -74,7 +74,7 @@ class NotionDatabaseProperties(CustomComponent):
data = response.json()
properties = data.get("properties", {})
record = Record(text=str(response.json()), data=properties)
record = Data(text=str(response.json()), data=properties)
self.status = f"Retrieved {len(properties)} properties from the Notion database.\n {record.text}"
return record
```

View file

@ -39,7 +39,7 @@ import requests
import json
from typing import Dict, Any, List
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.schema import Data
class NotionListPages(CustomComponent):
display_name = "List Pages [Notion]"
@ -83,7 +83,7 @@ class NotionListPages(CustomComponent):
notion_secret: str,
database_id: str,
query_payload: str = "{}",
) -> List[Record]:
) -> List[Data]:
try:
query_data = json.loads(query_payload)
filter_obj = query_data.get("filter")
@ -107,7 +107,7 @@ class NotionListPages(CustomComponent):
response.raise_for_status()
results = response.json()
records = []
data = []
combined_text = f"Pages found: {len(results['results'])}\n\n"
for page in results['results']:
page_data = {
@ -127,14 +127,14 @@ class NotionListPages(CustomComponent):
)
combined_text += text
records.append(Record(text=text, data=page_data))
data.append(Data(text=text, data=page_data))
self.status = combined_text.strip()
return records
return data
except Exception as e:
self.status = f"An error occurred: {str(e)}"
return [Record(text=self.status, data=[])]
return [Data(text=self.status, data=[])]
```
<Admonition type="info" title="Example Usage">

View file

@ -30,7 +30,7 @@ import requests
from typing import List
from langflow import CustomComponent
from langflow.schema import Record
from langflow.schema import Data
class NotionUserList(CustomComponent):
@ -52,7 +52,7 @@ class NotionUserList(CustomComponent):
def build(
self,
notion_secret: str,
) -> List[Record]:
) -> List[Data]:
url = "https://api.notion.com/v1/users"
headers = {
"Authorization": f"Bearer {notion_secret}",
@ -65,14 +65,14 @@ class NotionUserList(CustomComponent):
data = response.json()
results = data['results']
records = []
data = []
for user in results:
id = user['id']
type = user['type']
name = user.get('name', '')
avatar_url = user.get('avatar_url', '')
record_data = {
data_dict = {
"id": id,
"type": type,
"name": name,
@ -80,15 +80,15 @@ class NotionUserList(CustomComponent):
}
output = "User:\n"
for key, value in record_data.items():
for key, value in data_dict.items():
output += f"{key.replace('_', ' ').title()}: {value}\n"
output += "________________________\n"
record = Record(text=output, data=record_data)
records.append(record)
record = Data(text=output, data=data_dict)
data.append(record)
self.status = "\n".join(record.text for record in records)
return records
self.status = "\n".join(record.text for record in data)
return data
```
## Example Usage

View file

@ -36,7 +36,7 @@ import requests
from typing import Dict, Any
from langflow import CustomComponent
from langflow.schema import Record
from langflow.schema import Data
class NotionPageContent(CustomComponent):
@ -64,7 +64,7 @@ class NotionPageContent(CustomComponent):
self,
page_id: str,
notion_secret: str,
) -> Record:
) -> Data:
blocks_url = f"https://api.notion.com/v1/blocks/{page_id}/children?page_size=100"
headers = {
"Authorization": f"Bearer {notion_secret}",
@ -80,7 +80,7 @@ class NotionPageContent(CustomComponent):
content = self.parse_blocks(blocks_data["results"])
self.status = content
return Record(data={"content": content}, text=content)
return Data(data={"content": content}, text=content)
def parse_blocks(self, blocks: list) -> str:
content = ""

View file

@ -26,7 +26,7 @@ import requests
from typing import Dict, Any
from langflow import CustomComponent
from langflow.schema import Record
from langflow.schema import Data
class NotionPageUpdate(CustomComponent):
@ -61,7 +61,7 @@ class NotionPageUpdate(CustomComponent):
page_id: str,
properties: str,
notion_secret: str,
) -> Record:
) -> Data:
url = f"https://api.notion.com/v1/pages/{page_id}"
headers = {
"Authorization": f"Bearer {notion_secret}",
@ -88,7 +88,7 @@ class NotionPageUpdate(CustomComponent):
output += f"{prop_name}: {prop_value}\n"
self.status = output
return Record(data=updated_page)
return Data(data=updated_page)
```
Let's break down the key parts of this component:
@ -99,7 +99,7 @@ Let's break down the key parts of this component:
- The component interacts with the Notion API to update the page properties. It constructs the API URL, headers, and request data based on the provided parameters.
- The processed data is returned as a `Record` object, which can be connected to other components in the Langflow flow. The `Record` object contains the updated page data.
- The processed data is returned as a `Data` object, which can be connected to other components in the Langflow flow. The `Data` object contains the updated page data.
- The component also stores the updated page properties in the `status` attribute for logging and debugging purposes.
@ -124,7 +124,7 @@ When using the `NotionPageUpdate` component, consider the following best practic
- Ensure that you have a valid Notion integration token with the necessary permissions to update page properties.
- Handle edge cases and error scenarios gracefully, such as invalid JSON format for properties or API request failures.
- We recommend using an LLM to generate the inputs for this component, to allow flexibilty
- We recommend using an LLM to generate the inputs for this component, to allow flexibility
By leveraging the `NotionPageUpdate` component in Langflow, you can easily integrate updating Notion page properties into your language model workflows and build powerful applications that extend Langflow's capabilities.

View file

@ -36,7 +36,7 @@ To use the `NotionSearch` component in a Langflow flow, follow these steps:
import requests
from typing import Dict, Any, List
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.schema import Data
class NotionSearch(CustomComponent):
display_name = "Search Notion"
@ -88,7 +88,7 @@ class NotionSearch(CustomComponent):
query: str = "",
filter_value: str = "page",
sort_direction: str = "descending",
) -> List[Record]:
) -> List[Data]:
try:
url = "https://api.notion.com/v1/search"
headers = {
@ -113,7 +113,7 @@ class NotionSearch(CustomComponent):
response.raise_for_status()
results = response.json()
records = []
data = []
combined_text = f"Results found: {len(results['results'])}\n\n"
for result in results['results']:
result_data = {
@ -135,14 +135,14 @@ class NotionSearch(CustomComponent):
text += f"type: {result['object']}\nlast_edited_time: {result['last_edited_time']}\n\n"
combined_text += text
records.append(Record(text=text, data=result_data))
data.append(Data(text=text, data=result_data))
self.status = combined_text
return records
return data
except Exception as e:
self.status = f"An error occurred: {str(e)}"
return [Record(text=self.status, data=[])]
return [Data(text=self.status, data=[])]
```
## Example Usage

View file

@ -16,7 +16,7 @@ We have a special channel in our Discord server dedicated to Langflow 1.0 migrat
- Continued support for LangChain and new support for multiple frameworks
- Redesigned sidebar and customizable interaction panel
- New Native Categories and Components
- Improved user experience with Text and Record modes
- Improved user experience with Text and Data modes
- CustomComponent for all components
- Compatibility with previous versions using Runnable Executor
- Multiple flows in the canvas
@ -32,49 +32,49 @@ We have a special channel in our Discord server dedicated to Langflow 1.0 migrat
Langflow 1.0 introduces adds the concept of Inputs and Outputs to flows, allowing a clear definition of the data flow between components. Discover how to use Inputs and Outputs to pass data between components and create more dynamic flows.
[Learn more about Inputs and Outputs](../components/inputs-and-outputs.mdx)
[Learn more about Inputs and Outputs](../components/inputs-and-outputs)
## To Compose or Not to Compose: The Choice is Yours
Even though composition is still possible in Langflow 1.0, the new standard is getting data moving through the flow. This allows for more flexibility and control over the data flow in your projects.
[See our example components](../examples/create-record.mdx) for examples of interweaving LangChain components with our Core components.
[See our example components](../examples/create-record) for examples of interweaving LangChain components with our Core components.
## Continued Support for LangChain and Multiple Frameworks
Langflow 1.0 continues to support LangChain while also introducing support for multiple frameworks. This is another important boon that adding the paradigm of data flow brings to the table. Find out how to leverage the power of different frameworks in your projects.
[Learn more about compatibility and updating existing flows](./compatibility.mdx)
[Learn more about compatibility and updating existing flows](./compatibility)
## Sidebar Redesign and Customizable Playground
We've expanded on the chat experience by creating a customizable interaction panel that allows you to design a panel that fits your needs and interact with it. The sidebar has also been redesigned to provide a more intuitive and user-friendly experience. Explore the new sidebar and interaction panel features to enhance your workflow.
[Learn more about the Playground](../administration/playground.mdx)
[Learn more about the Playground](../administration/playground)
## New Native Categories and Components
Langflow 1.0 introduces many new native categories, including Inputs, Outputs, Helpers, Experimental, Models, and more. Discover the new components available, such as Chat Input, Prompt, Files, API Request, and others.
[Learn more about new components](../components/inputs-and-outputs.mdx)
[Learn more about new components](../components/inputs-and-outputs)
## New Way of Using Langflow: Text and Record (and more to come)
## New Way of Using Langflow: Text and Data (and more to come)
With the introduction of Text and Record types connections between Components are more intuitive and easier to understand. This is the first step in a series of improvements to the way you interact with Langflow. Learn how to use Text, and Record and how they help you build better flows.
With the introduction of Text and Data types connections between Components are more intuitive and easier to understand. This is the first step in a series of improvements to the way you interact with Langflow. Learn how to use Text, and Data and how they help you build better flows.
[Learn more about Text and Record](../components/text-and-record.mdx)
[Learn more about Text and Record](../components/text-and-record)
## CustomComponent for All Components
Almost all components in Langflow 1.0 are now CustomComponents, allowing you to check and modify the code of each component. Discover how to leverage this feature to customize your components to your specific needs.
[Learn more about CustomComponents](../components/custom.mdx)
[Learn more about CustomComponents](../components/custom)
## Compatibility with Previous Versions
To use flows built in previous versions of Langflow, you can utilize the experimental component Runnable Executor along with an Input and Output. **We'd love your feedback on this**. Learn how to adapt your existing flows to work seamlessly in the new version of Langflow.
[Learn more about Compatibility with Previous Versions](./compatibility.mdx)
[Learn more about Compatibility with Previous Versions](./compatibility)
## Multiple Flows in the Canvas
@ -86,37 +86,37 @@ Langflow 1.0 allows you to have more than one flow in the canvas and run them se
Each component now displays its status more clearly, allowing you to quickly identify any issues or errors. Explore how to use the new component status feature to troubleshoot and optimize your flows.
[Learn more about Component Status](../getting-started/canvas.mdx#component)
[Learn more about Component Status](../getting-started/canvas#component)
## Connecting Output Components
You can now connect Output components to any other component (that has a Text output), providing a better understanding of the data flow. Explore the possibilities of connecting Output components and how it enhances your flow's functionality.
[Learn more about Inputs and Outputs](../components/inputs-and-outputs.mdx)
[Learn more about Inputs and Outputs](../components/inputs-and-outputs)
## Renaming and Editing Component Descriptions
Langflow 1.0 allows you to rename and edit the description of each component, making it easier to understand and interact with the flow. Learn how to customize your component names and descriptions for improved clarity.
[Learn more about Component Descriptions](../getting-started/canvas.mdx#component-parameters)
[Learn more about Component Descriptions](../getting-started/canvas#component-parameters)
## Passing Tweaks and Inputs in the API
Things got a whole lot easier. You can now pass tweaks and inputs in the API by referencing the Display Name of the component. Discover how to leverage this feature to dynamically control your flow's behavior.
[Learn more about Tweaks and API inputs](../getting-started/canvas.mdx#tweaks)
[Learn more about Tweaks and API inputs](../getting-started/canvas#tweaks)
## Global Variables for Text Fields
Global Variables can be used in any Text Field across your projects. Learn how to define and utilize Global Variables to streamline your workflow.
[Learn more about Global Variables](../administration/global-env.mdx)
[Learn more about Global Variables](../administration/global-env)
## Experimental Components
Explore the experimental components available in Langflow 1.0, such as SubFlow, which allows you to load a flow as a component dynamically, and Flow as Tool, which enables you to use a flow as a tool for an Agent.
[Learn more about Experimental Components](../components/experimental.mdx)
[Learn more about Experimental Components](../components/experimental)
## Experimental State Management System

View file

@ -14,7 +14,7 @@ This article demonstrates how to use Langflow's prompt tools to issue basic prom
## Prerequisites
- [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow)
- [OpenAI API key created](https://platform.openai.com)
@ -62,4 +62,8 @@ This should be interesting...
The **Edit Prompt** window opens.
2. Change `Answer the user as if you were a pirate` to a different character, perhaps `Answer the user as if you were Harold Abelson.`
3. Run the basic prompting flow again.
The response will be markedly different.
The response will be markedly different.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";

View file

@ -10,7 +10,7 @@ Build a blog writer with OpenAI that uses URLs for reference content.
## Prerequisites
- [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow)
- [OpenAI API key created](https://platform.openai.com)
@ -75,4 +75,8 @@ The `reference_1` and `reference_2` values are received from the **URL** fields
3. The **OpenAI** component constructs a blog post with the **URL** items as context.
The default **URL** values are for web pages at `promptingguide.ai`, so your blog post will be about prompting LLMs.
To write about something different, change the values in the **URL** components, and see what the LLM constructs.
To write about something different, change the values in the **URL** components, and see what the LLM constructs.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";

View file

@ -10,7 +10,7 @@ Build a question-and-answer chatbot with a document loaded from local memory.
## Prerequisites
- [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow)
- [OpenAI API key created](https://platform.openai.com)
@ -67,4 +67,8 @@ Including a file with the prompt gives the **OpenAI** component context it may n
The issue occurred during the execution of migrations in the application. Specifically, an error was raised by the Alembic library, indicating that new upgrade operations were detected that had not been accounted for in the existing migration scripts. The operation in question involved modifying the nullable property of a column (apikey, created_at) in the database, with details about the existing type (DATETIME()), existing server default, and other properties.
```
This result indicates that the bot received the loaded document and understood the context surrounding the vague question. It also correctly identified the issue in the error log, and followed up with appropriate troubleshooting suggestions. Nice!
This result indicates that the bot received the loaded document and understood the context surrounding the vague question. It also correctly identified the issue in the error log, and followed up with appropriate troubleshooting suggestions. Nice!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";

View file

@ -6,11 +6,11 @@ import Admonition from "@theme/Admonition";
# Memory Chatbot
This flow extends the [basic prompting flow](./basic-prompting.mdx) to include chat memory for unique SessionIDs.
This flow extends the [basic prompting flow](./basic-prompting) to include chat memory for unique SessionIDs.
## Prerequisites
- [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow)
- [OpenAI API key created](https://platform.openai.com)
@ -81,4 +81,8 @@ To store **Session ID** as a Langflow variable, in the **Session ID** field, cli
1. In the **Variable Name** field, enter a name like `customer_chat_emea`.
2. In the **Value** field, enter a value like `1B5EBD79-6E9C-4533-B2C8-7E4FF29E983B`.
3. Click **Save Variable**.
4. Apply this variable to **Chat Input**.
4. Apply this variable to **Chat Input**.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";

View file

@ -23,7 +23,7 @@ We've chosen [Astra DB](https://astra.datastax.com/signup?utm_source=langflow-pr
to create your own Langflow workspace in minutes.
</Admonition>
- [Langflow installed and running](../getting-started/install-langflow.mdx)
- [Langflow installed and running](../getting-started/install-langflow)
- [OpenAI API key](https://platform.openai.com)
@ -56,9 +56,9 @@ The **query** flow (top of the screen) allows users to chat with the embedded ve
- **Chat Input** component defines where to put the user input coming from the Playground.
- **OpenAI Embeddings** component generates embeddings from the user input.
- **Astra DB Search** component retrieves the most relevant Records from the Astra DB database.
- **Text Output** component turns the Records into Text by concatenating them and also displays it in the Playground.
- **Prompt** component takes in the user input and the retrieved Records as text and builds a prompt for the OpenAI model.
- **Astra DB Search** component retrieves the most relevant Data from the Astra DB database.
- **Text Output** component turns the Data into Text by concatenating them and also displays it in the Playground.
- **Prompt** component takes in the user input and the retrieved Data as text and builds a prompt for the OpenAI model.
- **OpenAI** component generates a response to the prompt.
- **Chat Output** component displays the response in the Playground.
@ -106,4 +106,8 @@ AI
You should use a 3/8 inch wrench to remove the oil drain cap.
```
This is the size the engine manual lists as well. This confirms our flow works, because the query returns the unique knowledge we embedded from the Astra vector store.
This is the size the engine manual lists as well. This confirms our flow works, because the query returns the unique knowledge we embedded from the Astra vector store.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";

View file

@ -130,10 +130,10 @@ The RAG flow is a bit more complex. It consists of:
- **Chat Input** component that defines where to put the user input coming from the Playground
- **OpenAI Embeddings** component that generates embeddings from the user input
- **Astra DB Search** component that retrieves the most relevant Records from the Astra DB database
- **Text Output** component that turns the Records into Text by concatenating them and also displays it in the Playground
- **Astra DB Search** component that retrieves the most relevant Data from the Astra DB database
- **Text Output** component that turns the Data into Text by concatenating them and also displays it in the Playground
- One interesting point you'll see here is that this component is named `Extracted Chunks`, and that is how it will appear in the Playground
- **Prompt** component that takes in the user input and the retrieved Records as text and builds a prompt for the OpenAI model
- **Prompt** component that takes in the user input and the retrieved Data as text and builds a prompt for the OpenAI model
- **OpenAI** component that generates a response to the prompt
- **Chat Output** component that displays the response in the Playground
@ -170,7 +170,7 @@ Because this flow has a **Chat Input** and a **Text Output** component, the Pane
style={{ width: "80%", margin: "20px auto" }}
/>
Once we interact with it we get a response and the Extracted Chunks section is updated with the retrieved records.
Once we interact with it we get a response and the Extracted Chunks section is updated with the retrieved Data.
<ZoomableImage
alt="Docusaurus themed image"
@ -186,7 +186,4 @@ And that's it! You have successfully ran a RAG application using Astra DB and La
# Conclusion
In this guide, we have learned how to run a RAG application using Astra DB and Langflow.
We have seen how to create an Astra DB database, import the Astra DB RAG Flows project into Langflow, and run the ingestion and RAG flows.import ThemedImage from "@theme/ThemedImage";
import useBaseUrl from "@docusaurus/useBaseUrl";
import ZoomableImage from "/src/theme/ZoomableImage.js";
import Admonition from "@theme/Admonition";
We have seen how to create an Astra DB database, import the Astra DB RAG Flows project into Langflow, and run the ingestion and RAG flows.

4791
docs/package-lock.json generated

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

291
poetry.lock generated
View file

@ -472,17 +472,17 @@ files = [
[[package]]
name = "boto3"
version = "1.34.128"
version = "1.34.129"
description = "The AWS SDK for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "boto3-1.34.128-py3-none-any.whl", hash = "sha256:a048ff980a81cd652724a73bc496c519b336fabe19cc8bfc6c53b2ff6eb22c7b"},
{file = "boto3-1.34.128.tar.gz", hash = "sha256:43a6e99f53a8d34b3b4dbe424dbcc6b894350dc41a85b0af7c7bc24a7ec2cead"},
{file = "boto3-1.34.129-py3-none-any.whl", hash = "sha256:cc73de1c9d953b1f9da6ee2404af717e93d888f790f3e0291b22d1b8489eb401"},
{file = "boto3-1.34.129.tar.gz", hash = "sha256:a7a696fd3e7f5f43a81450b441f3eb6c5a89d28efe867cd97d8fc73ea5d8c139"},
]
[package.dependencies]
botocore = ">=1.34.128,<1.35.0"
botocore = ">=1.34.129,<1.35.0"
jmespath = ">=0.7.1,<2.0.0"
s3transfer = ">=0.10.0,<0.11.0"
@ -491,13 +491,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
version = "1.34.128"
version = "1.34.129"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">=3.8"
files = [
{file = "botocore-1.34.128-py3-none-any.whl", hash = "sha256:db67fda136c372ab3fa432580c819c89ba18d28a6152a4d2a7ea40d44082892e"},
{file = "botocore-1.34.128.tar.gz", hash = "sha256:8d8e03f7c8c080ecafda72036eb3b482d649f8417c90b5dca33b7c2c47adb0c9"},
{file = "botocore-1.34.129-py3-none-any.whl", hash = "sha256:86d3dd30996aa459e9c3321edac12aebe47c73cb4acc7556941f9b4c39726088"},
{file = "botocore-1.34.129.tar.gz", hash = "sha256:7c56e25af6112d69c5d14a15b42f76ba7687687abc463a96ac5edca19c0a9c2d"},
]
[package.dependencies]
@ -1005,13 +1005,13 @@ numpy = "*"
[[package]]
name = "chromadb"
version = "0.5.0"
version = "0.5.3"
description = "Chroma."
optional = false
python-versions = ">=3.8"
files = [
{file = "chromadb-0.5.0-py3-none-any.whl", hash = "sha256:8193dc65c143b61d8faf87f02c44ecfa778d471febd70de517f51c5d88a06009"},
{file = "chromadb-0.5.0.tar.gz", hash = "sha256:7954af614a9ff7b2902ddbd0a162f33f7ec0669e2429903905c4f7876d1f766f"},
{file = "chromadb-0.5.3-py3-none-any.whl", hash = "sha256:b3874f08356e291c68c6d2e177db472cd51f22f3af7b9746215b748fd1e29982"},
{file = "chromadb-0.5.3.tar.gz", hash = "sha256:05d887f56a46b2e0fc6ac5ab979503a27b9ee50d5ca9e455f83b2fb9840cd026"},
]
[package.dependencies]
@ -1020,10 +1020,11 @@ build = ">=1.0.3"
chroma-hnswlib = "0.7.3"
fastapi = ">=0.95.2"
grpcio = ">=1.58.0"
httpx = ">=0.27.0"
importlib-resources = "*"
kubernetes = ">=28.1.0"
mmh3 = ">=4.0.1"
numpy = ">=1.22.5"
numpy = ">=1.22.5,<2.0.0"
onnxruntime = ">=1.14.1"
opentelemetry-api = ">=1.2.0"
opentelemetry-exporter-otlp-proto-grpc = ">=1.2.0"
@ -2439,8 +2440,8 @@ files = [
[package.dependencies]
cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""}
greenlet = [
{version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""},
{version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""},
{version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""},
]
"zope.event" = "*"
"zope.interface" = "*"
@ -2535,6 +2536,38 @@ benchmarks = ["httplib2", "httpx", "requests", "urllib3"]
dev = ["dpkt", "pytest", "requests"]
examples = ["oauth2"]
[[package]]
name = "gitdb"
version = "4.0.11"
description = "Git Object Database"
optional = false
python-versions = ">=3.7"
files = [
{file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"},
{file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"},
]
[package.dependencies]
smmap = ">=3.0.1,<6"
[[package]]
name = "gitpython"
version = "3.1.43"
description = "GitPython is a Python library used to interact with Git repositories"
optional = false
python-versions = ">=3.7"
files = [
{file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"},
{file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"},
]
[package.dependencies]
gitdb = ">=4.0.1,<5"
[package.extras]
doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"]
test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"]
[[package]]
name = "google-ai-generativelanguage"
version = "0.6.4"
@ -2567,12 +2600,12 @@ files = [
google-auth = ">=2.14.1,<3.0.dev0"
googleapis-common-protos = ">=1.56.2,<2.0.dev0"
grpcio = [
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
{version = ">=1.49.1,<2.0dev", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
{version = ">=1.33.2,<2.0dev", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
]
grpcio-status = [
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
{version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""},
{version = ">=1.33.2,<2.0.dev0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""},
]
proto-plus = ">=1.22.3,<2.0.0dev"
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0.dev0"
@ -2641,13 +2674,13 @@ httplib2 = ">=0.19.0"
[[package]]
name = "google-cloud-aiplatform"
version = "1.55.0"
version = "1.56.0"
description = "Vertex AI API client library"
optional = false
python-versions = ">=3.8"
files = [
{file = "google-cloud-aiplatform-1.55.0.tar.gz", hash = "sha256:aa87cb6c49ae5fde87fb831ce8ad4a853c4656fe04babe505e9144c7a9e09c1a"},
{file = "google_cloud_aiplatform-1.55.0-py2.py3-none-any.whl", hash = "sha256:c6cc76ca5537f4636a0c3f8c0288d2e0d2d86ef708e562d2654313e11d6ee46a"},
{file = "google-cloud-aiplatform-1.56.0.tar.gz", hash = "sha256:d4cfb085427dac01142915f523949ac2955d6c7f148d95017d3286a77caf5d5e"},
{file = "google_cloud_aiplatform-1.56.0-py2.py3-none-any.whl", hash = "sha256:ee1ab3bd115c3caebf8ddfd3e47eeb8396a3ec2fc5f5baf1a5c295c8d64333ab"},
]
[package.dependencies]
@ -2669,8 +2702,8 @@ cloud-profiler = ["tensorboard-plugin-profile (>=2.4.0,<3.0.0dev)", "tensorflow
datasets = ["pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)"]
endpoint = ["requests (>=2.28.1)"]
full = ["cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.109.1)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (>=0.1.6)", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "nest-asyncio (>=1.0.0,<1.6.0)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "requests (>=2.28.1)", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.4.0,<3.0.0dev)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)"]
langchain = ["langchain (>=0.1.16,<0.3)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)"]
langchain-testing = ["absl-py", "cloudpickle (>=2.2.1,<4.0)", "langchain (>=0.1.16,<0.3)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)", "pytest-xdist"]
langchain = ["langchain (>=0.1.16,<0.3)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)", "tenacity (<=8.3)"]
langchain-testing = ["absl-py", "cloudpickle (>=3.0,<4.0)", "langchain (>=0.1.16,<0.3)", "langchain-core (<0.2)", "langchain-google-vertexai (<2)", "openinference-instrumentation-langchain (>=0.1.19,<0.2)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)", "pytest-xdist", "tenacity (<=8.3)"]
lit = ["explainable-ai-sdk (>=1.0.0)", "lit-nlp (==0.4.0)", "pandas (>=1.0.0)", "tensorflow (>=2.3.0,<3.0.0dev)"]
metadata = ["numpy (>=1.15.0)", "pandas (>=1.0.0)"]
pipelines = ["pyyaml (>=5.3.1,<7)"]
@ -2680,7 +2713,7 @@ private-endpoints = ["requests (>=2.28.1)", "urllib3 (>=1.21.1,<1.27)"]
rapid-evaluation = ["nest-asyncio (>=1.0.0,<1.6.0)", "pandas (>=1.0.0,<2.2.0)"]
ray = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "setuptools (<70.0.0)"]
ray-testing = ["google-cloud-bigquery", "google-cloud-bigquery-storage", "immutabledict", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pytest-xdist", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "ray[train] (==2.9.3)", "scikit-learn", "setuptools (<70.0.0)", "tensorflow", "torch (>=2.0.0,<2.1.0)", "xgboost", "xgboost-ray"]
reasoningengine = ["cloudpickle (>=2.2.1,<4.0)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)"]
reasoningengine = ["cloudpickle (>=3.0,<4.0)", "opentelemetry-exporter-gcp-trace (<2)", "opentelemetry-sdk (<2)", "pydantic (>=2.6.3,<3)"]
tensorboard = ["tensorboard-plugin-profile (>=2.4.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.4.0,<3.0.0dev)", "werkzeug (>=2.0.0,<2.1.0dev)"]
testing = ["bigframes", "cloudpickle (<3.0)", "docker (>=5.0.3)", "explainable-ai-sdk (>=1.0.0)", "fastapi (>=0.71.0,<=0.109.1)", "google-api-core (>=2.11,<3.0.0)", "google-cloud-bigquery", "google-cloud-bigquery-storage", "google-cloud-logging (<4.0)", "google-vizier (>=0.1.6)", "grpcio-testing", "httpx (>=0.23.0,<0.25.0)", "immutabledict", "ipython", "kfp (>=2.6.0,<3.0.0)", "lit-nlp (==0.4.0)", "mlflow (>=1.27.0,<=2.1.1)", "nest-asyncio (>=1.0.0,<1.6.0)", "numpy (>=1.15.0)", "pandas (>=1.0.0)", "pandas (>=1.0.0,<2.2.0)", "pyarrow (>=10.0.1)", "pyarrow (>=14.0.0)", "pyarrow (>=3.0.0,<8.0dev)", "pyarrow (>=6.0.1)", "pydantic (<2)", "pyfakefs", "pytest-asyncio", "pytest-xdist", "pyyaml (>=5.3.1,<7)", "ray[default] (>=2.4,<2.5.dev0 || >2.9.0,!=2.9.1,!=2.9.2,<=2.9.3)", "ray[default] (>=2.5,<=2.9.3)", "requests (>=2.28.1)", "requests-toolbelt (<1.0.0)", "scikit-learn", "setuptools (<70.0.0)", "starlette (>=0.17.1)", "tensorboard-plugin-profile (>=2.4.0,<3.0.0dev)", "tensorflow (==2.13.0)", "tensorflow (==2.16.1)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.3.0,<3.0.0dev)", "tensorflow (>=2.4.0,<3.0.0dev)", "torch (>=2.0.0,<2.1.0)", "torch (>=2.2.0)", "urllib3 (>=1.21.1,<1.27)", "uvicorn[standard] (>=0.16.0)", "werkzeug (>=2.0.0,<2.1.0dev)", "xgboost"]
vizier = ["google-vizier (>=0.1.6)"]
@ -4140,6 +4173,22 @@ astrapy = ">=1.2,<2.0"
langchain-core = ">=0.1.31,<0.3"
numpy = ">=1,<2"
[[package]]
name = "langchain-aws"
version = "0.1.6"
description = "An integration package connecting AWS and LangChain"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_aws-0.1.6-py3-none-any.whl", hash = "sha256:a747c282c2df29281918818238fea5f7e183cfa1b28add4a08d7f3f9a4438ca9"},
{file = "langchain_aws-0.1.6.tar.gz", hash = "sha256:d5ceb3db72aef98b2fb2336add7932f1ed045df8824fef24cd3613d93d8b4efb"},
]
[package.dependencies]
boto3 = ">=1.34.51,<1.35.0"
langchain-core = ">=0.1.45,<0.3"
numpy = ">=1,<2"
[[package]]
name = "langchain-chroma"
version = "0.1.1"
@ -4200,22 +4249,25 @@ tenacity = ">=8.1.0,<9.0.0"
[[package]]
name = "langchain-core"
version = "0.2.8"
version = "0.2.9"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_core-0.2.8-py3-none-any.whl", hash = "sha256:172c81c858dc1f3123cc72b7e44e10f44c92f8a761cae18c364081f6c208e9f6"},
{file = "langchain_core-0.2.8.tar.gz", hash = "sha256:2db866a4514672c4875b69d5590aa2ed50aa0d144874268bef68d74b5e7f33f9"},
{file = "langchain_core-0.2.9-py3-none-any.whl", hash = "sha256:426a5a4fea95a5db995ba5ab560b76edd4998fb6fe52ccc28ac987092a4cbfcd"},
{file = "langchain_core-0.2.9.tar.gz", hash = "sha256:f1c59082642921727844e1cd0eb36d451edd1872c20e193aa3142aac03495986"},
]
[package.dependencies]
jsonpatch = ">=1.33,<2.0"
langsmith = ">=0.1.75,<0.2.0"
packaging = ">=23.2,<25"
pydantic = ">=1,<3"
pydantic = [
{version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
]
PyYAML = ">=5.3"
tenacity = ">=8.1.0,<9.0.0"
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
[[package]]
name = "langchain-experimental"
@ -4301,6 +4353,22 @@ httpx-sse = ">=0.3.1,<1"
langchain-core = ">=0.2.0,<0.3"
tokenizers = ">=0.15.1,<1"
[[package]]
name = "langchain-mongodb"
version = "0.1.6"
description = "An integration package connecting MongoDB and LangChain"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_mongodb-0.1.6-py3-none-any.whl", hash = "sha256:ba5e1e388131ae00e8fcd39c4b38ce42c29f00c4bc39c58023b354b597ffa8df"},
{file = "langchain_mongodb-0.1.6.tar.gz", hash = "sha256:a7a6b66d5270d6f8732c4e848f9a3742bbd4485c8829a2d629332ee683b936d1"},
]
[package.dependencies]
langchain-core = ">=0.1.46,<0.3"
numpy = ">=1,<2"
pymongo = ">=4.6.1,<5.0"
[[package]]
name = "langchain-openai"
version = "0.1.8"
@ -4465,13 +4533,13 @@ openai = ["openai (>=0.27.8)"]
[[package]]
name = "langsmith"
version = "0.1.78"
version = "0.1.80"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langsmith-0.1.78-py3-none-any.whl", hash = "sha256:87bc5d9072bfcb6392d7552cbcd6089dcc1faed36d688b1587d80bd48a1acba2"},
{file = "langsmith-0.1.78.tar.gz", hash = "sha256:d9112d2e9298ec6b02d3b1afec6ed557df9db3746c79d34ef3b448fc18e116cd"},
{file = "langsmith-0.1.80-py3-none-any.whl", hash = "sha256:951fc29576b52afd8378d41f6db343090fea863e3620f0ca97e83b221f93c94d"},
{file = "langsmith-0.1.80.tar.gz", hash = "sha256:a29b1dde27612308beee424f1388ad844c8e7e375bf2ac8bdf4da174013f279d"},
]
[package.dependencies]
@ -4481,18 +4549,19 @@ requests = ">=2,<3"
[[package]]
name = "litellm"
version = "1.40.15"
version = "1.40.16"
description = "Library to easily interface with LLM API providers"
optional = false
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
files = [
{file = "litellm-1.40.15-py3-none-any.whl", hash = "sha256:4f51376535857c109723cede78f50e0794be3814b6da1f02ea8263fe5480dc42"},
{file = "litellm-1.40.15.tar.gz", hash = "sha256:bd3ca27c69deb47c5d11c69a6c2f7a618512720bd2d94ff64d055c09d9fba366"},
{file = "litellm-1.40.16-py3-none-any.whl", hash = "sha256:b78571a39a3b7819ca9cb96278f4b15b97557e2cd50e9aeee9210a7e744b8667"},
{file = "litellm-1.40.16.tar.gz", hash = "sha256:b32cd15c7cf9084aa0f92eb7103323741d63cc8a14e28b739054b0cb6682c431"},
]
[package.dependencies]
aiohttp = "*"
click = "*"
ijson = "*"
importlib-metadata = ">=6.8.0"
jinja2 = ">=3.1.2,<4.0.0"
openai = ">=1.27.0"
@ -4551,8 +4620,8 @@ psutil = ">=5.9.1"
pywin32 = {version = "*", markers = "platform_system == \"Windows\""}
pyzmq = ">=25.0.0"
requests = [
{version = ">=2.26.0", markers = "python_version <= \"3.11\""},
{version = ">=2.32.2", markers = "python_version > \"3.11\""},
{version = ">=2.26.0", markers = "python_version <= \"3.11\""},
]
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
Werkzeug = ">=2.0.0"
@ -6020,9 +6089,9 @@ files = [
[package.dependencies]
numpy = [
{version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""},
{version = ">=1.22.4,<2", markers = "python_version < \"3.11\""},
{version = ">=1.23.2,<2", markers = "python_version == \"3.11\""},
{version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""},
]
python-dateutil = ">=2.8.2"
pytz = ">=2020.1"
@ -6427,27 +6496,28 @@ files = [
[[package]]
name = "psutil"
version = "5.9.8"
version = "6.0.0"
description = "Cross-platform lib for process and system monitoring in Python."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
files = [
{file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"},
{file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"},
{file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"},
{file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"},
{file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"},
{file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"},
{file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"},
{file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"},
{file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"},
{file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"},
{file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"},
{file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"},
{file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"},
{file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"},
{file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"},
{file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"},
{file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"},
{file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"},
{file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"},
{file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"},
{file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"},
{file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"},
{file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"},
{file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"},
{file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"},
{file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"},
{file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"},
{file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"},
{file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"},
{file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"},
{file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"},
{file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"},
{file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"},
]
[package.extras]
@ -7105,12 +7175,12 @@ image = ["Pillow (>=8.0.0)"]
[[package]]
name = "pyperclip"
version = "1.8.2"
version = "1.9.0"
description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)"
optional = false
python-versions = "*"
files = [
{file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"},
{file = "pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310"},
]
[[package]]
@ -8482,6 +8552,17 @@ files = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "smmap"
version = "5.0.1"
description = "A pure Python implementation of a sliding window memory map manager"
optional = false
python-versions = ">=3.7"
files = [
{file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"},
{file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
@ -8506,64 +8587,64 @@ files = [
[[package]]
name = "sqlalchemy"
version = "2.0.30"
version = "2.0.31"
description = "Database Abstraction Library"
optional = false
python-versions = ">=3.7"
files = [
{file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc"},
{file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2"},
{file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7"},
{file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797"},
{file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af"},
{file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5"},
{file = "SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8"},
{file = "SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb"},
{file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0"},
{file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46"},
{file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa"},
{file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13"},
{file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9"},
{file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0"},
{file = "SQLAlchemy-2.0.30-cp311-cp311-win32.whl", hash = "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49"},
{file = "SQLAlchemy-2.0.30-cp311-cp311-win_amd64.whl", hash = "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260"},
{file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"},
{file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"},
{file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"},
{file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"},
{file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"},
{file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"},
{file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"},
{file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"},
{file = "SQLAlchemy-2.0.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57"},
{file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0"},
{file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad"},
{file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf"},
{file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b"},
{file = "SQLAlchemy-2.0.30-cp37-cp37m-win32.whl", hash = "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584"},
{file = "SQLAlchemy-2.0.30-cp37-cp37m-win_amd64.whl", hash = "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e"},
{file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3"},
{file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7"},
{file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd"},
{file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9"},
{file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621"},
{file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb"},
{file = "SQLAlchemy-2.0.30-cp38-cp38-win32.whl", hash = "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6"},
{file = "SQLAlchemy-2.0.30-cp38-cp38-win_amd64.whl", hash = "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e"},
{file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a"},
{file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b"},
{file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6"},
{file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7"},
{file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953"},
{file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c"},
{file = "SQLAlchemy-2.0.30-cp39-cp39-win32.whl", hash = "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513"},
{file = "SQLAlchemy-2.0.30-cp39-cp39-win_amd64.whl", hash = "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221"},
{file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"},
{file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"},
{file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"},
{file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"},
{file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"},
{file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"},
{file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"},
{file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"},
{file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"},
{file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"},
{file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"},
{file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"},
{file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"},
{file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"},
{file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"},
{file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"},
{file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"},
{file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"},
{file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"},
{file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"},
{file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"},
{file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"},
{file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"},
{file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"},
{file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"},
{file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"},
{file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"},
{file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"},
{file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"},
{file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"},
{file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"},
{file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"},
{file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"},
{file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"},
{file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"},
{file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"},
{file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"},
{file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"},
{file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"},
{file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"},
{file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"},
{file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"},
{file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"},
{file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"},
{file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"},
{file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"},
{file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"},
{file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"},
{file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"},
{file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"},
{file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"},
]
[package.dependencies]
greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""}
greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
typing-extensions = ">=4.6.0"
[package.extras]
@ -10545,4 +10626,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.13"
content-hash = "6cfa9f164710bf283b50a8e12e9d0c91c58f5d0176bbb277b5f5d3630ec8b2cb"
content-hash = "332baeb07342a5ad1367ce6b13a87bd96504f628d2e91e33dfda1a8317b2de13"

View file

@ -74,7 +74,7 @@ assemblyai = "^0.26.0"
litellm = "^1.38.0"
chromadb = "^0.5.0"
langchain-anthropic = "^0.1.6"
langchain-astradb = "^0.3.0"
langchain-astradb = "^0.3.3"
langchain-openai = "^0.1.1"
zep-python = { version = "^2.0.0rc5", allow-prereleases = true }
langchain-google-vertexai = "^1.0.3"
@ -86,8 +86,11 @@ youtube-transcript-api = "^0.6.2"
markdown = "^3.6"
langchain-chroma = "^0.1.1"
upstash-vector = "^0.4.0"
gitpython = "^3.1.43"
cassio = { extras = ["cassio"], version = "^0.1.7", optional = true }
unstructured = {extras = ["docx", "md", "pptx"], version = "^0.14.4"}
langchain-aws = "^0.1.6"
langchain-mongodb = "^0.1.6"
[tool.poetry.group.dev.dependencies]
@ -130,17 +133,16 @@ local = ["llama-cpp-python", "sentence-transformers", "ctransformers"]
optional = true
[tool.poetry.group.spelling.dependencies]
codespell = "^2.2.6"
codespell = "^2.3.0"
[tool.codespell]
skip = '.git,*.pdf,*.svg,*.pdf,*.yaml,*.ipynb,poetry.lock,*.min.js,*.css,package-lock.json,*.trig'
skip = '.git,*.pdf,*.svg,*.pdf,*.yaml,*.ipynb,poetry.lock,*.min.js,*.css,package-lock.json,*.trig.,**/node_modules/**,./stuff/*,*.csv'
# Ignore latin etc
ignore-regex = '.*(Stati Uniti|Tense=Pres).*'
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -n auto"
testpaths = ["tests", "integration"]
console_output_style = "progress"
filterwarnings = ["ignore::DeprecationWarning"]

View file

@ -129,9 +129,6 @@ def update_template_field(frontend_template, key, value_dict):
template_field["value"] = ""
template_field["file_path"] = file_path_value
if "load_from_db" in value_dict and value_dict["load_from_db"]:
template_field["load_from_db"] = value_dict["load_from_db"]
def get_file_path_value(file_path):
"""Get the file path value if the file exists, else return empty string."""
@ -208,7 +205,7 @@ def format_elapsed_time(elapsed_time: float) -> str:
return f"{minutes} {minutes_unit}, {seconds} {seconds_unit}"
async def build_and_cache_graph_from_db(flow_id: str, session: Session, chat_service: "ChatService"):
async def build_graph_from_db(flow_id: str, session: Session, chat_service: "ChatService"):
"""Build and cache the graph."""
flow: Optional[Flow] = session.get(Flow, flow_id)
if not flow or not flow.data:

View file

@ -1,4 +1,5 @@
import time
import traceback
import uuid
from functools import partial
from typing import TYPE_CHECKING, Annotated, Optional
@ -9,7 +10,7 @@ from loguru import logger
from langflow.api.utils import (
build_and_cache_graph_from_data,
build_and_cache_graph_from_db,
build_graph_from_db,
format_elapsed_time,
format_exception_message,
get_top_level_vertices,
@ -23,6 +24,7 @@ from langflow.api.v1.schemas import (
VertexBuildResponse,
VerticesOrderResponse,
)
from langflow.exceptions.component import ComponentBuildException
from langflow.schema.schema import Log
from langflow.services.auth.utils import get_current_active_user
from langflow.services.chat.service import ChatService
@ -82,7 +84,7 @@ async def retrieve_vertices_order(
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_str, session=session, chat_service=chat_service)
graph = await build_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_str, graph_data=data.model_dump(), chat_service=chat_service
@ -109,6 +111,7 @@ async def retrieve_vertices_order(
run_id = uuid.uuid4()
graph.set_run_id(run_id)
vertices_to_run = list(graph.vertices_to_run) + get_top_level_vertices(graph, graph.vertices_to_run)
await chat_service.set_cache(str(flow_id), graph)
return VerticesOrderResponse(ids=first_layer, run_id=run_id, vertices_to_run=vertices_to_run)
except Exception as exc:
@ -156,7 +159,7 @@ async def build_vertex(
if not cache:
# If there's no cache
logger.warning(f"No cache found for {flow_id_str}. Building graph starting at {vertex_id}")
graph = await build_and_cache_graph_from_db(
graph = await build_graph_from_db(
flow_id=flow_id_str, session=next(get_session()), chat_service=chat_service
)
else:
@ -183,22 +186,29 @@ async def build_vertex(
lock, set_cache_coro, graph=graph, vertex=vertex, cache=False
)
top_level_vertices = graph.run_manager.get_top_level_vertices(graph, next_runnable_vertices)
log_obj = Log(message=vertex.artifacts_raw, type=vertex.artifacts_type)
result_data_response = ResultDataResponse(**result_dict.model_dump())
result_data_response = ResultDataResponse.model_validate(result_dict, from_attributes=True)
except Exception as exc:
logger.exception(f"Error building Component: {exc}")
params = format_exception_message(exc)
if isinstance(exc, ComponentBuildException):
params = exc.message
tb = exc.formatted_traceback
else:
tb = traceback.format_exc()
logger.exception(f"Error building Component: {exc}")
params = format_exception_message(exc)
message = {"errorMessage": params, "stackTrace": tb}
valid = False
log_obj = Log(message=params, type="error")
result_data_response = ResultDataResponse(results={})
output_label = vertex.outputs[0]["name"] if vertex.outputs else "output"
logs = {output_label: Log(message=message, type="error")}
result_data_response = ResultDataResponse(results={}, logs=logs)
artifacts = {}
# If there's an error building the vertex
# we need to clear the cache
await chat_service.clear_cache(flow_id_str)
result_data_response.message = artifacts
result_data_response.logs.append(log_obj)
# Log the vertex build
if not vertex.will_stream:
@ -220,6 +230,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_str, graph)
# graph.stop_vertex tells us if the user asked
@ -240,7 +251,7 @@ async def build_vertex(
)
return build_response
except Exception as exc:
logger.error(f"Error building Component: {exc}")
logger.error(f"Error building Component:\n\n{exc}")
logger.exception(exc)
message = parse_exception(exc)
raise HTTPException(status_code=500, detail=message) from exc
@ -284,18 +295,12 @@ async def build_vertex_stream(
async def stream_vertex():
try:
if not session_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_str}.")
else:
graph = cache.get("result")
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_str}.")
else:
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_str}.")
graph = cache.get("result")
vertex: "InterfaceVertex" = graph.get_vertex(vertex_id)
if not hasattr(vertex, "stream"):
@ -342,6 +347,7 @@ async def build_vertex_stream(
yield str(StreamData(event="error", data={"error": exc_message}))
finally:
logger.debug("Closing stream")
await chat_service.set_cache(flow_id_str, graph)
yield str(StreamData(event="close", data={"message": "Stream closed"}))
return StreamingResponse(stream_vertex(), media_type="text/event-stream")

View file

@ -1,9 +1,13 @@
from asyncio import Lock
from http import HTTPStatus
from typing import TYPE_CHECKING, Annotated, List, Optional, Union
from uuid import UUID
import sqlalchemy as sa
from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException, Request, UploadFile, status
from loguru import logger
from sqlmodel import Session, select
from langflow.api.utils import update_frontend_node_with_template_values
from langflow.api.v1.schemas import (
ConfigResponse,
@ -16,7 +20,7 @@ from langflow.api.v1.schemas import (
UpdateCustomComponentRequest,
UploadFileResponse,
)
from langflow.custom import CustomComponent
from langflow.custom.custom_component.component import Component
from langflow.custom.utils import build_custom_component_template
from langflow.graph.graph.base import Graph
from langflow.graph.schema import RunOutputs
@ -26,30 +30,40 @@ from langflow.schema.graph import Tweaks
from langflow.services.auth.utils import api_key_security, get_current_active_user
from langflow.services.cache.utils import save_uploaded_file
from langflow.services.database.models.flow import Flow
from langflow.services.database.models.flow.utils import get_all_webhook_components_in_flow, get_flow_by_id
from langflow.services.database.models.flow.utils import get_all_webhook_components_in_flow
from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session, get_session_service, get_settings_service, get_task_service
from langflow.services.deps import (
get_cache_service,
get_session,
get_session_service,
get_settings_service,
get_task_service,
)
from langflow.services.session.service import SessionService
from langflow.services.task.service import TaskService
from loguru import logger
from sqlmodel import Session, select
if TYPE_CHECKING:
from langflow.services.settings.manager import SettingsService
from langflow.services.cache.base import CacheService
from langflow.services.settings.service import SettingsService
router = APIRouter(tags=["Base"])
@router.get("/all", dependencies=[Depends(get_current_active_user)])
def get_all(
async def get_all(
settings_service=Depends(get_settings_service),
cache_service: "CacheService" = Depends(dependency=get_cache_service),
force_refresh: bool = False,
):
from langflow.interface.types import get_all_types_dict
from langflow.interface.types import get_and_cache_all_types_dict
logger.debug("Building langchain types dict")
try:
all_types_dict = get_all_types_dict(settings_service.settings.components_path)
return all_types_dict
async with Lock() as lock:
all_types_dict = await get_and_cache_all_types_dict(
settings_service=settings_service, cache_service=cache_service, force_refresh=force_refresh, lock=lock
)
return all_types_dict
except Exception as exc:
logger.exception(exc)
raise HTTPException(status_code=500, detail=str(exc)) from exc
@ -106,12 +120,10 @@ async def simple_run_flow(
@router.post("/run/{flow_id_or_name}", response_model=RunResponse, response_model_exclude_none=True)
async def simplified_run_flow(
db: Annotated[Session, Depends(get_session)],
flow: Annotated[Flow, Depends(get_flow_by_id_or_endpoint_name)],
input_request: SimplifiedAPIRequest = SimplifiedAPIRequest(),
stream: bool = False,
api_key_user: User = Depends(api_key_security),
session_service: SessionService = Depends(get_session_service),
):
"""
Executes a specified flow by ID with input customization, performance enhancements through caching, and optional data streaming.
@ -183,10 +195,10 @@ async def simplified_run_flow(
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
@router.post("/webhook/{flow_id}", response_model=dict, status_code=HTTPStatus.ACCEPTED)
@router.post("/webhook/{flow_id_or_name}", response_model=dict, status_code=HTTPStatus.ACCEPTED)
async def webhook_run_flow(
db: Annotated[Session, Depends(get_session)],
flow: Annotated[Flow, Depends(get_flow_by_id)],
flow: Annotated[Flow, Depends(get_flow_by_id_or_endpoint_name)],
request: Request,
background_tasks: BackgroundTasks,
session_service: SessionService = Depends(get_session_service),
@ -232,7 +244,6 @@ async def webhook_run_flow(
logger.debug("Starting background task")
background_tasks.add_task( # type: ignore
simple_run_flow,
db=db,
flow=flow,
input_request=input_request,
)
@ -453,7 +464,7 @@ async def custom_component(
raw_code: CustomComponentRequest,
user: User = Depends(get_current_active_user),
):
component = CustomComponent(code=raw_code.code)
component = Component(code=raw_code.code)
built_frontend_node, _ = build_custom_component_template(component, user_id=user.id)
@ -481,7 +492,7 @@ async def custom_component_update(
"""
try:
component = CustomComponent(code=code_request.code)
component = Component(code=code_request.code)
component_node, cc_instance = build_custom_component_template(
component,

View file

@ -50,6 +50,26 @@ def create_flow(
flow.name = f"{flow.name} ({max(numbers) + 1})"
else:
flow.name = f"{flow.name} (1)"
# Now check if the endpoint is unique
if (
flow.endpoint_name
and session.exec(
select(Flow).where(Flow.endpoint_name == flow.endpoint_name).where(Flow.user_id == current_user.id)
).first()
):
flows = session.exec(
select(Flow)
.where(Flow.endpoint_name.like(f"{flow.endpoint_name}-%")) # type: ignore
.where(Flow.user_id == current_user.id)
).all()
if flows:
# The endpoitn name is like "my-endpoint","my-endpoint-1", "my-endpoint-2"
# so we need to get the highest number and add 1
# we need to get the last part of the endpoint name
numbers = [int(flow.endpoint_name.split("-")[-1]) for flow in flows] # type: ignore
flow.endpoint_name = f"{flow.endpoint_name}-{max(numbers) + 1}"
else:
flow.endpoint_name = f"{flow.endpoint_name}-1"
db_flow = Flow.model_validate(flow, from_attributes=True)
db_flow.updated_at = datetime.now(timezone.utc)

View file

@ -11,7 +11,7 @@ from langflow.services.auth.utils import (
)
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.settings.service import SettingsService
from langflow.services.variable.service import VariableService
router = APIRouter(tags=["Login"])

View file

@ -1,5 +1,6 @@
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from langflow.services.deps import get_monitor_service

View file

@ -245,7 +245,7 @@ class VerticesOrderResponse(BaseModel):
class ResultDataResponse(BaseModel):
results: Optional[Any] = Field(default_factory=dict)
logs: List[Log | None] = Field(default_factory=list)
logs: dict[str, Log] = Field(default_factory=dict)
message: Optional[Any] = Field(default_factory=dict)
artifacts: Optional[Any] = Field(default_factory=dict)
timedelta: Optional[float] = None

View file

@ -4,10 +4,10 @@ from langchain.agents import AgentExecutor, BaseMultiActionAgent, BaseSingleActi
from langchain_core.messages import BaseMessage
from langchain_core.runnables import Runnable
from langflow.base.agents.utils import get_agents_list, records_to_messages
from langflow.base.agents.utils import data_to_messages, get_agents_list
from langflow.custom import CustomComponent
from langflow.field_typing import Text, Tool
from langflow.schema import Record
from langflow.schema import Data
class LCAgentComponent(CustomComponent):
@ -49,7 +49,7 @@ class LCAgentComponent(CustomComponent):
agent: Union[Runnable, BaseSingleActionAgent, BaseMultiActionAgent, AgentExecutor],
inputs: str,
tools: List[Tool],
message_history: Optional[List[Record]] = None,
message_history: Optional[List[Data]] = None,
handle_parsing_errors: bool = True,
output_key: str = "output",
) -> Text:
@ -64,7 +64,7 @@ class LCAgentComponent(CustomComponent):
)
input_dict: dict[str, str | list[BaseMessage]] = {"input": inputs}
if message_history:
input_dict["chat_history"] = records_to_messages(message_history)
input_dict["chat_history"] = data_to_messages(message_history)
result = await runnable.ainvoke(input_dict)
self.status = result
if output_key in result:

View file

@ -13,7 +13,7 @@ from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate
from langchain_core.tools import BaseTool
from pydantic import BaseModel
from langflow.schema import Record
from langflow.schema import Data
from .default_prompts import XML_AGENT_PROMPT
@ -34,17 +34,17 @@ class AgentSpec(BaseModel):
hub_repo: Optional[str] = None
def records_to_messages(records: List[Record]) -> List[BaseMessage]:
def data_to_messages(data: List[Data]) -> List[BaseMessage]:
"""
Convert a list of records to a list of messages.
Convert a list of data to a list of messages.
Args:
records (List[Record]): The records to convert.
data (List[Data]): The data to convert.
Returns:
List[Message]: The records as messages.
List[Message]: The data as messages.
"""
return [record.to_lc_message() for record in records]
return [value.to_lc_message() for value in data]
def validate_and_create_xml_agent(

View file

@ -11,7 +11,7 @@ import orjson
STREAM_INFO_TEXT = "Stream the response from the model. Streaming works only in Chat."
NODE_FORMAT_ATTRIBUTES = ["beta", "icon", "display_name", "description", "output_types"]
NODE_FORMAT_ATTRIBUTES = ["beta", "icon", "display_name", "description", "output_types", "edited"]
FIELD_FORMAT_ATTRIBUTES = [
@ -28,6 +28,8 @@ FIELD_FORMAT_ATTRIBUTES = [
"refresh_button",
"refresh_button_text",
"options",
"advanced",
"load_from_db",
]
ORJSON_OPTIONS = orjson.OPT_INDENT_2 | orjson.OPT_SORT_KEYS | orjson.OPT_OMIT_MICROSECONDS

View file

@ -45,7 +45,7 @@ def normalize_newlines(multiline_text):
def parse_curl_command(curl_command):
tokens = shlex.split(normalize_newlines(curl_command))
tokens = [token for token in tokens if token and token != " "]
if "curl" not in tokens[0]:
if tokens and "curl" not in tokens[0]:
raise ValueError("Invalid curl command")
args_template = {
"command": None,
@ -112,6 +112,10 @@ def parse_curl_command(curl_command):
def parse_context(curl_command):
method = "get"
if not curl_command:
return ParsedContext(
method=method, url="", data=None, headers={}, cookies={}, verify=True, auth=None, proxy=None
)
parsed_args: ParsedArgs = parse_curl_command(curl_command)

View file

@ -8,7 +8,7 @@ import chardet
import orjson
import yaml
from langflow.schema import Record
from langflow.schema import Data
# Types of files that can be read simply by file.read()
# and have 100% to be completely readable
@ -33,12 +33,7 @@ TEXT_FILE_TYPES = [
"tsx",
]
IMG_FILE_TYPES = [
"jpg",
"jpeg",
"png",
"bmp",
]
IMG_FILE_TYPES = ["jpg", "jpeg", "png", "bmp", "image"]
def normalize_text(text):
@ -82,7 +77,7 @@ def retrieve_file_paths(
# ! Removing unstructured dependency until
# ! 3.12 is supported
# def partition_file_to_record(file_path: str, silent_errors: bool) -> Optional[Record]:
# def partition_file_to_data(file_path: str, silent_errors: bool) -> Optional[Data]:
# # Use the partition function to load the file
# from unstructured.partition.auto import partition # type: ignore
@ -93,11 +88,11 @@ def retrieve_file_paths(
# raise ValueError(f"Error loading file {file_path}: {e}") from e
# return None
# # Create a Record
# # Create a Data
# text = "\n\n".join([Text(el) for el in elements])
# metadata = elements.metadata if hasattr(elements, "metadata") else {}
# metadata["file_path"] = file_path
# record = Record(text=text, data=metadata)
# record = Data(text=text, data=metadata)
# return record
@ -129,7 +124,7 @@ def parse_pdf_to_text(file_path: str) -> str:
return "\n\n".join([page.extract_text() for page in reader.pages])
def parse_text_file_to_record(file_path: str, silent_errors: bool) -> Optional[Record]:
def parse_text_file_to_data(file_path: str, silent_errors: bool) -> Optional[Data]:
try:
if file_path.endswith(".pdf"):
text = parse_pdf_to_text(file_path)
@ -145,6 +140,7 @@ def parse_text_file_to_record(file_path: str, silent_errors: bool) -> Optional[R
text = {k: normalize_text(v) if isinstance(v, str) else v for k, v in text.items()}
elif isinstance(text, list):
text = [normalize_text(item) if isinstance(item, str) else item for item in text]
text = orjson.dumps(text).decode("utf-8")
elif file_path.endswith(".yaml") or file_path.endswith(".yml"):
text = yaml.safe_load(text)
@ -156,7 +152,7 @@ def parse_text_file_to_record(file_path: str, silent_errors: bool) -> Optional[R
raise ValueError(f"Error loading file {file_path}: {e}") from e
return None
record = Record(data={"file_path": file_path, "text": text})
record = Data(data={"file_path": file_path, "text": text})
return record
@ -167,21 +163,21 @@ def parse_text_file_to_record(file_path: str, silent_errors: bool) -> Optional[R
# silent_errors: bool,
# max_concurrency: int,
# use_multithreading: bool,
# ) -> List[Optional[Record]]:
# ) -> List[Optional[Data]]:
# if use_multithreading:
# records = parallel_load_records(file_paths, silent_errors, max_concurrency)
# data = parallel_load_data(file_paths, silent_errors, max_concurrency)
# else:
# records = [partition_file_to_record(file_path, silent_errors) for file_path in file_paths]
# records = list(filter(None, records))
# return records
# data = [partition_file_to_data(file_path, silent_errors) for file_path in file_paths]
# data = list(filter(None, data))
# return data
def parallel_load_records(
def parallel_load_data(
file_paths: List[str],
silent_errors: bool,
max_concurrency: int,
load_function: Callable = parse_text_file_to_record,
) -> List[Optional[Record]]:
load_function: Callable = parse_text_file_to_data,
) -> List[Optional[Data]]:
with futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor:
loaded_files = executor.map(
lambda file_path: load_function(file_path, silent_errors),

View file

@ -3,62 +3,65 @@ from typing import List
from loguru import logger
from langflow.graph.schema import ResultData, RunOutputs
from langflow.schema import Record
from langflow.schema import Data
def build_records_from_run_outputs(run_outputs: RunOutputs) -> List[Record]:
def build_data_from_run_outputs(run_outputs: RunOutputs) -> List[Data]:
"""
Build a list of records from the given RunOutputs.
Build a list of data from the given RunOutputs.
Args:
run_outputs (RunOutputs): The RunOutputs object containing the output data.
Returns:
List[Record]: A list of records built from the RunOutputs.
List[Data]: A list of data built from the RunOutputs.
"""
if not run_outputs:
return []
records = []
data = []
for result_data in run_outputs.outputs:
if result_data:
records.extend(build_records_from_result_data(result_data))
return records
data.extend(build_data_from_result_data(result_data))
return data
def build_records_from_result_data(result_data: ResultData, get_final_results_only: bool = True) -> List[Record]:
def build_data_from_result_data(result_data: ResultData, get_final_results_only: bool = True) -> List[Data]:
"""
Build a list of records from the given ResultData.
Build a list of data from the given ResultData.
Args:
result_data (ResultData): The ResultData object containing the result data.
get_final_results_only (bool, optional): Whether to include only final results. Defaults to True.
Returns:
List[Record]: A list of records built from the ResultData.
List[Data]: A list of data built from the ResultData.
"""
messages = result_data.messages
records = []
if not messages:
return []
data = []
# Handle results without chat messages (calling flow)
if not messages:
# Result with a single record
if isinstance(result_data.artifacts, dict):
records.append(Record(data=result_data.artifacts))
data.append(Data(data=result_data.artifacts))
# List of artifacts
elif isinstance(result_data.artifacts, list):
for artifact in result_data.artifacts:
# If multiple records are found as artifacts, return as-is
if isinstance(artifact, Record):
records.append(artifact)
if isinstance(artifact, Data):
data.append(artifact)
else:
# Warn about unknown output type
logger.warning(f"Unable to build record output from unknown ResultData.artifact: {str(artifact)}")
# Chat or text output
elif result_data.results:
records.append(Record(data={"result": result_data.results}, text_key="result"))
return records
data.append(Data(data={"result": result_data.results}, text_key="result"))
return data
else:
return []
@ -68,22 +71,22 @@ def build_records_from_result_data(result_data: ResultData, get_final_results_on
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
record = Data(data={"result": inner_result, "message": message_dict}, text_key="result")
data.append(record)
return data
def format_flow_output_records(records: List[Record]) -> str:
def format_flow_output_data(data: List[Data]) -> str:
"""
Format the flow output records into a string.
Format the flow output data into a string.
Args:
records (List[Record]): The list of records to format.
data (List[Data]): The list of data to format.
Returns:
str: The formatted flow output records.
str: The formatted flow output data.
"""
result = "Flow run output:\n"
results = "\n".join([record.result for record in records if record.data["message"]])
results = "\n".join([value.result for value in data if value.data["message"]])
return result + results

View file

@ -1,13 +1,13 @@
from typing import Optional, Union
from langflow.base.data.utils import IMG_FILE_TYPES, TEXT_FILE_TYPES
from langflow.custom import CustomComponent
from langflow.custom import Component
from langflow.memory import store_message
from langflow.schema import Record
from langflow.schema import Data
from langflow.schema.message import Message
class ChatComponent(CustomComponent):
class ChatComponent(Component):
display_name = "Chat Component"
description = "Use as base for chat components."
@ -34,10 +34,10 @@ class ChatComponent(CustomComponent):
"info": "Return the message as a Message containing the sender, sender_name, and session_id.",
"advanced": True,
},
"record_template": {
"display_name": "Record Template",
"data_template": {
"display_name": "Data Template",
"multiline": True,
"info": "In case of Message being a Record, this template will be used to convert it to text.",
"info": "In case of Message being a Data, this template will be used to convert it to text.",
"advanced": True,
},
"files": {
@ -61,20 +61,20 @@ class ChatComponent(CustomComponent):
self.status = messages
return messages
def build_with_record(
def build_with_data(
self,
sender: Optional[str] = "User",
sender_name: Optional[str] = "User",
input_value: Optional[Union[str, Record, Message]] = None,
input_value: Optional[Union[str, Data, Message]] = None,
files: Optional[list[str]] = None,
session_id: Optional[str] = None,
return_message: Optional[bool] = False,
) -> Message:
message: Message | None = None
if isinstance(input_value, Record):
if isinstance(input_value, Data):
# Update the data of the record
message = Message.from_record(input_value)
message = Message.from_data(input_value)
else:
message = Message(
text=input_value, sender=sender, sender_name=sender_name, files=files, session_id=session_id

View file

@ -1,12 +1,12 @@
from typing import Optional
from langflow.custom import CustomComponent
from langflow.custom import Component
from langflow.field_typing import Text
from langflow.helpers.record import records_to_text
from langflow.schema import Record
from langflow.helpers.data import data_to_text
from langflow.schema import Data
class TextComponent(CustomComponent):
class TextComponent(Component):
display_name = "Text Component"
description = "Used to pass text to the next component."
@ -14,13 +14,13 @@ class TextComponent(CustomComponent):
return {
"input_value": {
"display_name": "Value",
"input_types": ["Text", "Record"],
"info": "Text or Record to be passed.",
"input_types": ["Text", "Data"],
"info": "Text or Data to be passed.",
},
"record_template": {
"display_name": "Record Template",
"data_template": {
"display_name": "Data Template",
"multiline": True,
"info": "Template to convert Record to Text. If left empty, it will be dynamically set to the Record's text key.",
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
"advanced": True,
},
}
@ -28,14 +28,16 @@ class TextComponent(CustomComponent):
def build(
self,
input_value: Optional[Text] = "",
record_template: Optional[str] = "{text}",
data_template: Optional[str] = "{text}",
) -> Text:
if isinstance(input_value, Record):
if record_template == "":
# it should be dynamically set to the Record's .text_key value
# meaning, if text_key = "bacon", then record_template = "{bacon}"
record_template = "{" + input_value.text_key + "}"
input_value = records_to_text(template=record_template, records=input_value)
if isinstance(input_value, Data):
if data_template == "":
# it should be dynamically set to the Data's .text_key value
# meaning, if text_key = "bacon", then data_template = "{bacon}"
data_template = "{" + input_value.text_key + "}"
input_value = data_to_text(template=data_template, data=input_value)
elif not input_value:
input_value = ""
self.status = input_value
if not input_value:
input_value = ""

View file

@ -1,7 +1,7 @@
from typing import Optional
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.schema import Data
class BaseMemoryComponent(CustomComponent):
@ -32,15 +32,15 @@ class BaseMemoryComponent(CustomComponent):
"info": "Order of the messages.",
"advanced": True,
},
"record_template": {
"display_name": "Record Template",
"data_template": {
"display_name": "Data Template",
"multiline": True,
"info": "Template to convert Record to Text. If left empty, it will be dynamically set to the Record's text key.",
"info": "Template to convert Data to Text. If left empty, it will be dynamically set to the Data's text key.",
"advanced": True,
},
}
def get_messages(self, **kwargs) -> list[Record]:
def get_messages(self, **kwargs) -> list[Data]:
raise NotImplementedError
def add_message(

View file

@ -1,18 +1,47 @@
import json
import warnings
from typing import Optional, Union
from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.language_models.llms import LLM
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
from langflow.custom import CustomComponent
from langflow.field_typing.prompt import Prompt
from langflow.custom import Component
from langflow.field_typing import LanguageModel
from langflow.schema.message import Message
from langflow.template.field.base import Output
class LCModelComponent(CustomComponent):
class LCModelComponent(Component):
display_name: str = "Model Name"
description: str = "Model Description"
outputs = [
Output(display_name="Text", name="text_output", method="text_response"),
Output(display_name="Language Model", name="model_output", method="build_model"),
]
def _get_exception_message(self, e: Exception):
return str(e)
def _validate_outputs(self):
# At least these two outputs must be defined
required_output_methods = ["text_response", "build_model"]
output_names = [output.name for output in self.outputs]
for method_name in required_output_methods:
if method_name not in output_names:
raise ValueError(f"Output with name '{method_name}' must be defined.")
elif not hasattr(self, method_name):
raise ValueError(f"Method '{method_name}' must be defined.")
def text_response(self) -> Message:
input_value = self.input_value
stream = self.stream
system_message = self.system_message
output = self.build_model()
result = self.get_chat_result(output, stream, input_value, system_message)
self.status = result
return result
def get_result(self, runnable: LLM, stream: bool, input_value: str):
"""
Retrieves the result from the output of a Runnable object.
@ -25,13 +54,18 @@ class LCModelComponent(CustomComponent):
Returns:
The result obtained from the output object.
"""
if stream:
result = runnable.stream(input_value)
else:
message = runnable.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
try:
if stream:
result = runnable.stream(input_value)
else:
message = runnable.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
except Exception as e:
if message := self._get_exception_message(e):
raise ValueError(message) from e
raise e
def build_status_message(self, message: AIMessage):
"""
@ -84,15 +118,15 @@ class LCModelComponent(CustomComponent):
return status_message
def get_chat_result(
self, runnable: BaseChatModel, stream: bool, input_value: str | Prompt, system_message: Optional[str] = None
self, runnable: LanguageModel, stream: bool, input_value: str | Message, system_message: Optional[str] = None
):
messages: list[Union[HumanMessage, SystemMessage]] = []
messages: list[Union[BaseMessage]] = []
if not input_value and not system_message:
raise ValueError("The message you want to send to the model is empty.")
if system_message:
messages.append(SystemMessage(content=system_message))
if input_value:
if isinstance(input_value, Prompt):
if isinstance(input_value, Message):
with warnings.catch_warnings():
warnings.simplefilter("ignore")
if "prompt" in input_value:
@ -102,15 +136,23 @@ class LCModelComponent(CustomComponent):
messages.append(input_value.to_lc_message())
else:
messages.append(HumanMessage(content=input_value))
inputs = messages or {} # type: ignore
if stream:
return runnable.stream(inputs) # type: ignore
else:
message = runnable.invoke(inputs) # type: ignore
result = message.content
if isinstance(message, AIMessage):
status_message = self.build_status_message(message)
self.status = status_message
inputs: Union[list, dict] = messages or {}
try:
if stream:
return runnable.stream(inputs)
else:
self.status = result
return result
message = runnable.invoke(inputs)
result = message.content if hasattr(message, "content") else message
if isinstance(message, AIMessage):
status_message = self.build_status_message(message)
self.status = status_message
elif isinstance(result, dict):
result = json.dumps(message, indent=4)
self.status = result
else:
self.status = result
return result
except Exception as e:
if message := self._get_exception_message(e):
raise ValueError(message) from e
raise e

View file

@ -2,16 +2,15 @@ from copy import deepcopy
from langchain_core.documents import Document
from langflow.schema import Record
from langflow.schema.message import Message
from langflow.schema import Data
def record_to_string(record: Record) -> str:
def data_to_string(record: Data) -> str:
"""
Convert a record to a string.
Args:
record (Record): The record to convert.
record (Data): The record to convert.
Returns:
str: The record as a string.
@ -29,22 +28,24 @@ def dict_values_to_string(d: dict) -> dict:
Returns:
dict: The dictionary with values converted to strings.
"""
from langflow.schema.message import Message
# Do something similar to the above
d_copy = deepcopy(d)
for key, value in d_copy.items():
# it could be a list of records or documents or strings
# it could be a list of data or documents or strings
if isinstance(value, list):
for i, item in enumerate(value):
if isinstance(item, Message):
d_copy[key][i] = item.text
elif isinstance(item, Record):
d_copy[key][i] = record_to_string(item)
elif isinstance(item, Data):
d_copy[key][i] = data_to_string(item)
elif isinstance(item, Document):
d_copy[key][i] = document_to_string(item)
elif isinstance(value, Message):
d_copy[key] = value.text
elif isinstance(value, Record):
d_copy[key] = record_to_string(value)
elif isinstance(value, Data):
d_copy[key] = data_to_string(value)
elif isinstance(value, Document):
d_copy[key] = document_to_string(value)
return d_copy

View file

@ -6,7 +6,7 @@ from langchain_core.runnables import RunnableConfig
from langchain_core.tools import ToolException
from pydantic.v1 import BaseModel
from langflow.base.flow_processing.utils import build_records_from_result_data, format_flow_output_records
from langflow.base.flow_processing.utils import build_data_from_result_data, format_flow_output_data
from langflow.graph.graph.base import Graph
from langflow.graph.vertex.base import Vertex
from langflow.helpers.flow import build_schema_from_inputs, get_arg_names, get_flow_inputs, run_flow
@ -59,14 +59,12 @@ class FlowTool(BaseTool):
return "No output"
run_output = run_outputs[0]
records = []
data = []
if run_output is not None:
for output in run_output.outputs:
if output:
records.extend(
build_records_from_result_data(output, get_final_results_only=self.get_final_results_only)
)
return format_flow_output_records(records)
data.extend(build_data_from_result_data(output, get_final_results_only=self.get_final_results_only))
return format_flow_output_data(data)
def validate_inputs(self, args_names: List[dict[str, str]], args: Any, kwargs: Any):
"""Validate the inputs."""
@ -107,11 +105,9 @@ class FlowTool(BaseTool):
return "No output"
run_output = run_outputs[0]
records = []
data = []
if run_output is not None:
for output in run_output.outputs:
if output:
records.extend(
build_records_from_result_data(output, get_final_results_only=self.get_final_results_only)
)
return format_flow_output_records(records)
data.extend(build_data_from_result_data(output, get_final_results_only=self.get_final_results_only))
return format_flow_output_data(data)

View file

@ -0,0 +1,104 @@
from typing import List
from langchain_core.documents import Document
from loguru import logger
from langflow.custom import Component
from langflow.field_typing import Retriever, Text, VectorStore
from langflow.helpers.data import docs_to_data
from langflow.io import Output
from langflow.schema import Data
class LCVectorStoreComponent(Component):
outputs = [
Output(
display_name="Retriever",
name="base_retriever",
method="build_base_retriever",
),
Output(
display_name="Search Results",
name="search_results",
method="search_documents",
),
]
def _validate_outputs(self):
# At least these three outputs must be defined
required_output_methods = ["build_base_retriever", "search_documents"]
output_names = [output.name for output in self.outputs]
for method_name in required_output_methods:
if method_name not in output_names:
raise ValueError(f"Output with name '{method_name}' must be defined.")
elif not hasattr(self, method_name):
raise ValueError(f"Method '{method_name}' must be defined.")
def search_with_vector_store(
self,
input_value: Text,
search_type: str,
vector_store: VectorStore,
k=10,
**kwargs,
) -> List[Data]:
"""
Search for data in the vector store based on the input value and search type.
Args:
input_value (Text): The input value to search for.
search_type (str): The type of search to perform.
vector_store (VectorStore): The vector store to search in.
Returns:
List[Data]: A list of data matching the search criteria.
Raises:
ValueError: If invalid inputs are provided.
"""
docs: List[Document] = []
if input_value and isinstance(input_value, str) and hasattr(vector_store, "search"):
docs = vector_store.search(query=input_value, search_type=search_type.lower(), k=k, **kwargs)
else:
raise ValueError("Invalid inputs provided.")
data = docs_to_data(docs)
self.status = data
return data
def build_vector_store(self) -> VectorStore:
"""
Builds the Vector Store object.c
"""
raise NotImplementedError("build_vector_store method must be implemented.")
def build_base_retriever(self) -> Retriever:
"""
Builds the BaseRetriever object.
"""
vector_store = self.build_vector_store()
if hasattr(vector_store, "as_retriever"):
return vector_store.as_retriever()
else:
raise ValueError(f"Vector Store {vector_store.__class__.__name__} does not have an as_retriever method.")
def search_documents(self) -> List[Data]:
"""
Search for documents in the Chroma vector store.
"""
search_query: str = self.search_query
if not search_query:
self.status = ""
return []
vector_store = self.build_vector_store()
logger.debug(f"Search input: {search_query}")
logger.debug(f"Search type: {self.search_type}")
logger.debug(f"Number of results: {self.number_of_results}")
search_results = self.search_with_vector_store(
search_query, self.search_type, vector_store, k=self.number_of_results
)
self.status = search_results
return search_results

View file

@ -1,24 +1,24 @@
from langflow.schema import Record
from langflow.schema import Data
def chroma_collection_to_records(collection_dict: dict):
def chroma_collection_to_data(collection_dict: dict):
"""
Converts a collection of chroma vectors into a list of records.
Converts a collection of chroma vectors into a list of data.
Args:
collection_dict (dict): A dictionary containing the collection of chroma vectors.
Returns:
list: A list of records, where each record represents a document in the collection.
list: A list of data, where each record represents a document in the collection.
"""
records = []
data = []
for i, doc in enumerate(collection_dict["documents"]):
record_dict = {
data_dict = {
"id": collection_dict["ids"][i],
"text": doc,
}
if "metadatas" in collection_dict:
for key, value in collection_dict["metadatas"][i].items():
record_dict[key] = value
records.append(Record(**record_dict))
return records
data_dict[key] = value
data.append(Data(**data_dict))
return data

View file

@ -1,7 +1,7 @@
from langchain_experimental.agents.agent_toolkits.csv.base import create_csv_agent
from langflow.custom import CustomComponent
from langflow.field_typing import AgentExecutor, BaseLanguageModel
from langflow.field_typing import AgentExecutor, LanguageModel
class CSVAgentComponent(CustomComponent):
@ -11,7 +11,7 @@ class CSVAgentComponent(CustomComponent):
def build_config(self):
return {
"llm": {"display_name": "LLM", "type": BaseLanguageModel},
"llm": {"display_name": "LLM", "type": LanguageModel},
"path": {"display_name": "Path", "field_type": "file", "suffixes": [".csv"], "file_types": [".csv"]},
"handle_parsing_errors": {"display_name": "Handle Parse Errors", "advanced": True},
"agent_type": {
@ -22,7 +22,7 @@ class CSVAgentComponent(CustomComponent):
}
def build(
self, llm: BaseLanguageModel, path: str, handle_parsing_errors: bool = True, agent_type: str = "openai-tools"
self, llm: LanguageModel, path: str, handle_parsing_errors: bool = True, agent_type: str = "openai-tools"
) -> AgentExecutor:
# Instantiate and return the CSV agent class with the provided llm and path
return create_csv_agent(

View file

@ -3,7 +3,7 @@ from langchain_community.agent_toolkits import create_json_agent
from langchain_community.agent_toolkits.json.toolkit import JsonToolkit
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel
from langflow.field_typing import LanguageModel
class JsonAgentComponent(CustomComponent):
@ -18,7 +18,7 @@ class JsonAgentComponent(CustomComponent):
def build(
self,
llm: BaseLanguageModel,
llm: LanguageModel,
toolkit: JsonToolkit,
) -> AgentExecutor:
return create_json_agent(llm=llm, toolkit=toolkit)

View file

@ -6,7 +6,7 @@ from langchain_community.agent_toolkits.sql.base import create_sql_agent
from langchain_community.utilities import SQLDatabase
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel
from langflow.field_typing import LanguageModel
class SQLAgentComponent(CustomComponent):
@ -22,7 +22,7 @@ class SQLAgentComponent(CustomComponent):
def build(
self,
llm: BaseLanguageModel,
llm: LanguageModel,
database_uri: str,
verbose: bool = False,
) -> Union[AgentExecutor, Callable]:

View file

@ -1,64 +1,107 @@
from typing import List, Optional
from typing import List, cast
from langchain.agents import AgentExecutor, BaseSingleActionAgent
from langchain.agents.tool_calling_agent.base import create_tool_calling_agent
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate
from langflow.base.agents.agent import LCAgentComponent
from langflow.field_typing import BaseLanguageModel, Text, Tool
from langflow.schema import Record
from langflow.custom import Component
from langflow.io import BoolInput, HandleInput, Output, TextInput
from langflow.schema import Data
from langflow.schema.message import Message
class ToolCallingAgentComponent(LCAgentComponent):
class ToolCallingAgentComponent(Component):
display_name: str = "Tool Calling Agent"
description: str = "Agent that uses tools. Only models that are compatible with function calling are supported."
icon = "Agent"
def build_config(self):
return {
"llm": {"display_name": "LLM"},
"tools": {"display_name": "Tools"},
"user_prompt": {
"display_name": "Prompt",
"multiline": True,
"info": "This prompt must contain 'input' key.",
},
"handle_parsing_errors": {
"display_name": "Handle Parsing Errors",
"info": "If True, the agent will handle parsing errors. If False, the agent will raise an error.",
"advanced": True,
},
"memory": {
"display_name": "Memory",
"info": "Memory to use for the agent.",
},
"input_value": {
"display_name": "Inputs",
"info": "Input text to pass to the agent.",
},
}
inputs = [
HandleInput(
name="llm",
display_name="LLM",
input_types=["LanguageModel"],
),
HandleInput(
name="tools",
display_name="Tools",
input_types=["Tool"],
is_list=True,
),
TextInput(
name="user_prompt",
display_name="Prompt",
info="This prompt must contain 'input' key.",
value="{input}",
),
BoolInput(
name="handle_parsing_errors",
display_name="Handle Parsing Errors",
info="If True, the agent will handle parsing errors. If False, the agent will raise an error.",
advanced=True,
value=True,
),
HandleInput(
name="memory",
display_name="Memory",
input_types=["Data"],
info="Memory to use for the agent.",
),
TextInput(
name="input_value",
display_name="Inputs",
info="Input text to pass to the agent.",
),
]
async def build(
self,
input_value: str,
llm: BaseLanguageModel,
tools: List[Tool],
user_prompt: str = "{input}",
message_history: Optional[List[Record]] = None,
system_message: str = "You are a helpful assistant",
handle_parsing_errors: bool = True,
) -> Text:
if "input" not in user_prompt:
outputs = [
Output(display_name="Text", name="text_output", method="run_agent"),
]
async def run_agent(self) -> Message:
if "input" not in self.user_prompt:
raise ValueError("Prompt must contain 'input' key.")
messages = [
("system", system_message),
("system", "You are a helpful assistant"),
(
"placeholder",
"{chat_history}",
),
("human", user_prompt),
("human", self.user_prompt),
("placeholder", "{agent_scratchpad}"),
]
prompt = ChatPromptTemplate.from_messages(messages)
agent = create_tool_calling_agent(llm, tools, prompt)
result = await self.run_agent(agent, input_value, tools, message_history, handle_parsing_errors)
agent = create_tool_calling_agent(self.llm, self.tools, prompt)
runnable = AgentExecutor.from_agent_and_tools(
agent=cast(BaseSingleActionAgent, agent),
tools=self.tools,
verbose=True,
handle_parsing_errors=self.handle_parsing_errors,
)
input_dict: dict[str, str | list[BaseMessage]] = {"input": self.input_value}
if hasattr(self, "memory") and self.memory:
input_dict["chat_history"] = self.convert_chat_history(self.memory)
result = await runnable.ainvoke(input_dict)
self.status = result
return result
if "output" not in result:
raise ValueError("Output key not found in result. Tried 'output'.")
result_string = result["output"]
return Message(text=result_string)
def convert_chat_history(self, chat_history: List[Message | Data]) -> List[BaseMessage]:
messages = []
for item in chat_history:
if isinstance(item, (Message, Data)):
messages.append(item.to_lc_message())
elif isinstance(item, str):
messages.append(HumanMessage(content=item))
elif isinstance(item, dict) and "sender" in item and "text" in item:
messages.append(HumanMessage(content=item["text"], sender=item["sender"]))
else:
raise ValueError(f"Invalid item ({type(item)}) in chat history: {item}")
return messages

View file

@ -4,7 +4,7 @@ from langchain.agents import AgentExecutor, create_vectorstore_agent
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreToolkit
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel
from langflow.field_typing import LanguageModel
class VectorStoreAgentComponent(CustomComponent):
@ -19,7 +19,7 @@ class VectorStoreAgentComponent(CustomComponent):
def build(
self,
llm: BaseLanguageModel,
llm: LanguageModel,
vector_store_toolkit: VectorStoreToolkit,
) -> Union[AgentExecutor, Callable]:
return create_vectorstore_agent(llm=llm, toolkit=vector_store_toolkit)

View file

@ -2,7 +2,7 @@ from typing import Callable
from langchain.agents import create_vectorstore_router_agent
from langchain.agents.agent_toolkits.vectorstore.toolkit import VectorStoreRouterToolkit
from langchain_core.language_models.base import BaseLanguageModel
from langflow.field_typing import LanguageModel
from langflow.custom import CustomComponent
@ -17,5 +17,5 @@ class VectorStoreRouterAgentComponent(CustomComponent):
"vectorstoreroutertoolkit": {"display_name": "Vector Store Router Toolkit"},
}
def build(self, llm: BaseLanguageModel, vectorstoreroutertoolkit: VectorStoreRouterToolkit) -> Callable:
def build(self, llm: LanguageModel, vectorstoreroutertoolkit: VectorStoreRouterToolkit) -> Callable:
return create_vectorstore_router_agent(llm=llm, toolkit=vectorstoreroutertoolkit)

View file

@ -4,8 +4,8 @@ from langchain.agents import create_xml_agent
from langchain_core.prompts import ChatPromptTemplate
from langflow.base.agents.agent import LCAgentComponent
from langflow.field_typing import BaseLanguageModel, Text, Tool
from langflow.schema import Record
from langflow.field_typing import LanguageModel, Text, Tool
from langflow.schema import Data
class XMLAgentComponent(LCAgentComponent):
@ -72,11 +72,11 @@ class XMLAgentComponent(LCAgentComponent):
async def build(
self,
input_value: str,
llm: BaseLanguageModel,
llm: LanguageModel,
tools: List[Tool],
user_prompt: str = "{input}",
system_message: str = "You are a helpful assistant",
message_history: Optional[List[Record]] = None,
message_history: Optional[List[Data]] = None,
tool_template: str = "{name}: {description}",
handle_parsing_errors: bool = True,
) -> Text:

View file

@ -3,7 +3,7 @@ from typing import Optional
from langchain.chains import ConversationChain
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
from langflow.field_typing import BaseMemory, LanguageModel, Text
class ConversationChainComponent(CustomComponent):
@ -27,7 +27,7 @@ class ConversationChainComponent(CustomComponent):
def build(
self,
input_value: Text,
llm: BaseLanguageModel,
llm: LanguageModel,
memory: Optional[BaseMemory] = None,
) -> Text:
if memory is None:

View file

@ -4,7 +4,7 @@ from langchain.chains.llm import LLMChain
from langchain_core.prompts import PromptTemplate
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
from langflow.field_typing import BaseMemory, LanguageModel, Text
class LLMChainComponent(CustomComponent):
@ -21,7 +21,7 @@ class LLMChainComponent(CustomComponent):
def build(
self,
template: Text,
llm: BaseLanguageModel,
llm: LanguageModel,
memory: Optional[BaseMemory] = None,
) -> Text:
prompt = PromptTemplate.from_template(template)

View file

@ -1,7 +1,7 @@
from langchain.chains import LLMCheckerChain
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel, Text
from langflow.field_typing import LanguageModel, Text
class LLMCheckerChainComponent(CustomComponent):
@ -21,7 +21,7 @@ class LLMCheckerChainComponent(CustomComponent):
def build(
self,
input_value: Text,
llm: BaseLanguageModel,
llm: LanguageModel,
) -> Text:
chain = LLMCheckerChain.from_llm(llm=llm)
response = chain.invoke({chain.input_key: input_value})

View file

@ -3,7 +3,7 @@ from typing import Optional
from langchain.chains import LLMChain, LLMMathChain
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel, BaseMemory, Text
from langflow.field_typing import BaseMemory, LanguageModel, Text
class LLMMathChainComponent(CustomComponent):
@ -27,7 +27,7 @@ class LLMMathChainComponent(CustomComponent):
def build(
self,
input_value: Text,
llm: BaseLanguageModel,
llm: LanguageModel,
llm_chain: LLMChain,
input_key: str = "question",
output_key: str = "answer",

View file

@ -4,8 +4,8 @@ from langchain.chains.retrieval_qa.base import RetrievalQA
from langchain_core.documents import Document
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel, BaseMemory, BaseRetriever, Text
from langflow.schema import Record
from langflow.field_typing import BaseMemory, BaseRetriever, LanguageModel, Text
from langflow.schema import Data
class RetrievalQAComponent(CustomComponent):
@ -23,13 +23,13 @@ class RetrievalQAComponent(CustomComponent):
"return_source_documents": {"display_name": "Return Source Documents"},
"input_value": {
"display_name": "Input",
"input_types": ["Record", "Document"],
"input_types": ["Data", "Document"],
},
}
def build(
self,
llm: BaseLanguageModel,
llm: LanguageModel,
chain_type: str,
retriever: BaseRetriever,
input_value: str = "",
@ -50,17 +50,17 @@ class RetrievalQAComponent(CustomComponent):
)
if isinstance(input_value, Document):
input_value = input_value.page_content
if isinstance(input_value, Record):
if isinstance(input_value, Data):
input_value = input_value.get_text()
self.status = runnable
result = runnable.invoke({input_key: input_value})
result = result.content if hasattr(result, "content") else result
# Result is a dict with keys "query", "result" and "source_documents"
# for now we just return the result
records = self.to_records(result.get("source_documents"))
data = self.to_data(result.get("source_documents"))
references_str = ""
if return_source_documents:
references_str = self.create_references_from_records(records)
references_str = self.create_references_from_data(data)
result_str = result.get("result", "")
final_result = "\n".join([Text(result_str), references_str])

View file

@ -4,7 +4,7 @@ from langchain.chains import RetrievalQAWithSourcesChain
from langchain_core.documents import Document
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel, BaseMemory, BaseRetriever, Text
from langflow.field_typing import BaseMemory, BaseRetriever, LanguageModel, Text
class RetrievalQAWithSourcesChainComponent(CustomComponent):
@ -32,7 +32,7 @@ class RetrievalQAWithSourcesChainComponent(CustomComponent):
self,
input_value: Text,
retriever: BaseRetriever,
llm: BaseLanguageModel,
llm: LanguageModel,
chain_type: str,
memory: Optional[BaseMemory] = None,
return_source_documents: Optional[bool] = True,
@ -53,10 +53,10 @@ class RetrievalQAWithSourcesChainComponent(CustomComponent):
result = result.content if hasattr(result, "content") else result
# Result is a dict with keys "query", "result" and "source_documents"
# for now we just return the result
records = self.to_records(result.get("source_documents"))
data = self.to_data(result.get("source_documents"))
references_str = ""
if return_source_documents:
references_str = self.create_references_from_records(records)
references_str = self.create_references_from_data(data)
result_str = Text(result.get("answer", ""))
final_result = "\n".join([result_str, references_str])
self.status = final_result

View file

@ -6,7 +6,7 @@ from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import Runnable
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel, Text
from langflow.field_typing import LanguageModel, Text
class SQLGeneratorComponent(CustomComponent):
@ -35,7 +35,7 @@ class SQLGeneratorComponent(CustomComponent):
self,
input_value: Text,
db: SQLDatabase,
llm: BaseLanguageModel,
llm: LanguageModel,
top_k: int = 5,
prompt: Optional[Text] = None,
) -> Text:

View file

@ -1,54 +1,77 @@
import asyncio
import json
from typing import Any, List, Optional
from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
import httpx
from loguru import logger
from langflow.base.curl.parse import parse_context
from langflow.custom import CustomComponent
from langflow.field_typing import NestedDict
from langflow.schema import Record
from langflow.custom import Component
from langflow.io import DataInput, DropdownInput, IntInput, NestedDictInput, Output, TextInput
from langflow.schema import Data
from langflow.schema.dotdict import dotdict
class APIRequest(CustomComponent):
display_name: str = "API Request"
description: str = "Make HTTP requests given one or more URLs."
output_types: list[str] = ["Record"]
documentation: str = "https://docs.langflow.org/components/utilities#api-request"
class APIRequestComponent(Component):
display_name = "API Request"
description = (
"This component allows you to make HTTP requests to one or more URLs. "
"You can provide headers and body as either dictionaries or Data objects. "
"Additionally, you can append query parameters to the URLs.\n\n"
"**Note:** Check advanced options for more settings."
)
icon = "Globe"
field_config = {
"urls": {"display_name": "URLs", "info": "URLs to make requests to."},
"curl": {
"display_name": "Curl",
"info": "Paste a curl command to populate the fields.",
"refresh_button": True,
"refresh_button_text": "",
},
"method": {
"display_name": "Method",
"info": "The HTTP method to use.",
"options": ["GET", "POST", "PATCH", "PUT"],
"value": "GET",
},
"headers": {
"display_name": "Headers",
"info": "The headers to send with the request.",
"input_types": ["Record"],
},
"body": {
"display_name": "Body",
"info": "The body to send with the request (for POST, PATCH, PUT).",
"input_types": ["Record"],
},
"timeout": {
"display_name": "Timeout",
"info": "The timeout to use for the request.",
"value": 5,
},
}
inputs = [
TextInput(
name="urls",
display_name="URLs",
is_list=True,
info="Enter one or more URLs, separated by commas.",
),
TextInput(
name="curl",
display_name="Curl",
info="Paste a curl command to populate the fields. This will fill in the dictionary fields for headers and body.",
advanced=False,
refresh_button=True,
),
DropdownInput(
name="method",
display_name="Method",
options=["GET", "POST", "PATCH", "PUT"],
value="GET",
info="The HTTP method to use (GET, POST, PATCH, PUT).",
),
NestedDictInput(
name="headers",
display_name="Headers",
info="The headers to send with the request as a dictionary. This is populated when using the CURL field.",
input_types=["Data"],
),
NestedDictInput(
name="body",
display_name="Body",
info="The body to send with the request as a dictionary (for POST, PATCH, PUT). This is populated when using the CURL field.",
input_types=["Data"],
),
DataInput(
name="query_params",
display_name="Query Parameters",
info="The query parameters to append to the URL.",
),
IntInput(
name="timeout",
display_name="Timeout",
value=5,
info="The timeout to use for the request.",
),
]
outputs = [
Output(display_name="Data", name="data", method="make_requests"),
]
def parse_curl(self, curl: str, build_config: dotdict) -> dotdict:
try:
@ -57,18 +80,21 @@ class APIRequest(CustomComponent):
build_config["method"]["value"] = parsed.method.upper()
build_config["headers"]["value"] = dict(parsed.headers)
try:
json_data = json.loads(parsed.data)
build_config["body"]["value"] = json_data
except json.JSONDecodeError as e:
print(e)
if parsed.data:
try:
json_data = json.loads(parsed.data)
build_config["body"]["value"] = json_data
except json.JSONDecodeError as e:
logger.error(f"Error decoding JSON data: {e}")
else:
build_config["body"]["value"] = {}
except Exception as exc:
logger.error(f"Error parsing curl: {exc}")
raise ValueError(f"Error parsing curl: {exc}")
return build_config
def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
if field_name == "curl" and field_value is not None:
if field_name == "curl" and field_value:
build_config = self.parse_curl(field_value, build_config)
return build_config
@ -80,20 +106,20 @@ class APIRequest(CustomComponent):
headers: Optional[dict] = None,
body: Optional[dict] = None,
timeout: int = 5,
) -> Record:
) -> Data:
method = method.upper()
if method not in ["GET", "POST", "PATCH", "PUT", "DELETE"]:
raise ValueError(f"Unsupported method: {method}")
data = body if body else None
payload = json.dumps(data)
payload = json.dumps(data) if data else None
try:
response = await client.request(method, url, headers=headers, content=payload, timeout=timeout)
try:
result = response.json()
except Exception:
result = response.text
return Record(
return Data(
data={
"source": url,
"headers": headers,
@ -102,7 +128,7 @@ class APIRequest(CustomComponent):
},
)
except httpx.TimeoutException:
return Record(
return Data(
data={
"source": url,
"headers": headers,
@ -111,7 +137,7 @@ class APIRequest(CustomComponent):
},
)
except Exception as exc:
return Record(
return Data(
data={
"source": url,
"headers": headers,
@ -120,36 +146,38 @@ class APIRequest(CustomComponent):
},
)
async def build(
self,
method: str,
urls: List[str],
curl: Optional[str] = None,
headers: Optional[NestedDict] = {},
body: Optional[NestedDict] = {},
timeout: int = 5,
) -> List[Record]:
if headers is None:
headers_dict = {}
elif isinstance(headers, Record):
headers_dict = headers.data
else:
headers_dict = headers
def add_query_params(self, url: str, params: dict) -> str:
url_parts = list(urlparse(url))
query = dict(parse_qsl(url_parts[4]))
query.update(params)
url_parts[4] = urlencode(query)
return urlunparse(url_parts)
bodies = []
if body:
if not isinstance(body, list):
bodies = [body]
else:
bodies = body
bodies = [b.data if isinstance(b, Record) else b for b in bodies] # type: ignore
async def make_requests(self) -> List[Data]:
method = self.method
urls = [url.strip() for url in self.urls if url.strip()]
curl = self.curl
headers = self.headers or {}
body = self.body or {}
timeout = self.timeout
query_params = self.query_params.data if self.query_params else {}
if curl:
self._build_config = self.parse_curl(curl, dotdict())
if isinstance(headers, Data):
headers = headers.data
if isinstance(body, Data):
body = body.data
bodies = [body] * len(urls)
urls = [self.add_query_params(url, query_params) for url in urls]
if len(urls) != len(bodies):
# add bodies with None
bodies += [None] * (len(urls) - len(bodies)) # type: ignore
async with httpx.AsyncClient() as client:
results = await asyncio.gather(
*[self.make_request(client, method, u, headers_dict, rec, timeout) for u, rec in zip(urls, bodies)]
*[self.make_request(client, method, u, headers, rec, timeout) for u, rec in zip(urls, bodies)]
)
self.status = results
return results

View file

@ -1,63 +1,88 @@
from typing import Any, Dict, List, Optional
from typing import List, Optional
from langflow.base.data.utils import parallel_load_records, parse_text_file_to_record, retrieve_file_paths
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.base.data.utils import parallel_load_data, parse_text_file_to_data, retrieve_file_paths
from langflow.custom import Component
from langflow.io import BoolInput, IntInput, TextInput
from langflow.schema import Data
from langflow.template import Output
class DirectoryComponent(CustomComponent):
class DirectoryComponent(Component):
display_name = "Directory"
description = "Recursively load files from a directory."
icon = "folder"
def build_config(self) -> Dict[str, Any]:
return {
"path": {"display_name": "Path"},
"types": {
"display_name": "Types",
"info": "File types to load. Leave empty to load all types.",
},
"depth": {"display_name": "Depth", "info": "Depth to search for files."},
"max_concurrency": {"display_name": "Max Concurrency", "advanced": True},
"load_hidden": {
"display_name": "Load Hidden",
"advanced": True,
"info": "If true, hidden files will be loaded.",
},
"recursive": {
"display_name": "Recursive",
"advanced": True,
"info": "If true, the search will be recursive.",
},
"silent_errors": {
"display_name": "Silent Errors",
"advanced": True,
"info": "If true, errors will not raise an exception.",
},
"use_multithreading": {
"display_name": "Use Multithreading",
"advanced": True,
},
}
inputs = [
TextInput(
name="path",
display_name="Path",
info="Path to the directory to load files from.",
),
TextInput(
name="types",
display_name="Types",
info="File types to load. Leave empty to load all types.",
),
IntInput(
name="depth",
display_name="Depth",
info="Depth to search for files.",
value=0,
),
IntInput(
name="max_concurrency",
display_name="Max Concurrency",
advanced=True,
info="Maximum concurrency for loading files.",
value=2,
),
BoolInput(
name="load_hidden",
display_name="Load Hidden",
advanced=True,
info="If true, hidden files will be loaded.",
),
BoolInput(
name="recursive",
display_name="Recursive",
advanced=True,
info="If true, the search will be recursive.",
),
BoolInput(
name="silent_errors",
display_name="Silent Errors",
advanced=True,
info="If true, errors will not raise an exception.",
),
BoolInput(
name="use_multithreading",
display_name="Use Multithreading",
advanced=True,
info="If true, multithreading will be used.",
),
]
outputs = [
Output(display_name="Data", name="data", method="load_directory"),
]
def load_directory(self) -> List[Optional[Data]]:
path = self.path
depth = self.depth
max_concurrency = self.max_concurrency
load_hidden = self.load_hidden
recursive = self.recursive
silent_errors = self.silent_errors
use_multithreading = self.use_multithreading
def build(
self,
path: str,
depth: int = 0,
max_concurrency: int = 2,
load_hidden: bool = False,
recursive: bool = True,
silent_errors: bool = False,
use_multithreading: bool = True,
) -> List[Optional[Record]]:
resolved_path = self.resolve_path(path)
file_paths = retrieve_file_paths(resolved_path, load_hidden, recursive, depth)
loaded_records = []
loaded_data = []
if use_multithreading:
loaded_records = parallel_load_records(file_paths, silent_errors, max_concurrency)
loaded_data = parallel_load_data(file_paths, silent_errors, max_concurrency)
else:
loaded_records = [parse_text_file_to_record(file_path, silent_errors) for file_path in file_paths]
loaded_records = list(filter(None, loaded_records))
self.status = loaded_records
return loaded_records
loaded_data = [parse_text_file_to_data(file_path, silent_errors) for file_path in file_paths]
loaded_data = list(filter(None, loaded_data))
self.status = loaded_data
return loaded_data

View file

@ -1,48 +1,48 @@
from pathlib import Path
from typing import Any, Dict
from langflow.base.data.utils import TEXT_FILE_TYPES, parse_text_file_to_record
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.base.data.utils import TEXT_FILE_TYPES, parse_text_file_to_data
from langflow.custom import Component
from langflow.io import BoolInput, FileInput, Output
from langflow.schema import Data
class FileComponent(CustomComponent):
class FileComponent(Component):
display_name = "File"
description = "A generic file loader."
icon = "file-text"
def build_config(self) -> Dict[str, Any]:
return {
"path": {
"display_name": "Path",
"field_type": "file",
"file_types": TEXT_FILE_TYPES,
"info": f"Supported file types: {', '.join(TEXT_FILE_TYPES)}",
},
"silent_errors": {
"display_name": "Silent Errors",
"advanced": True,
"info": "If true, errors will not raise an exception.",
},
}
inputs = [
FileInput(
name="path",
display_name="Path",
file_types=TEXT_FILE_TYPES,
info=f"Supported file types: {', '.join(TEXT_FILE_TYPES)}",
),
BoolInput(
name="silent_errors",
display_name="Silent Errors",
advanced=True,
info="If true, errors will not raise an exception.",
),
]
outputs = [
Output(display_name="Data", name="data", method="load_file"),
]
def load_file(self) -> Data:
if not self.path:
raise ValueError("Please, upload a file to use this component.")
resolved_path = self.resolve_path(self.path)
silent_errors = self.silent_errors
extension = Path(resolved_path).suffix[1:].lower()
def load_file(self, path: str, silent_errors: bool = False) -> Record:
resolved_path = self.resolve_path(path)
path_obj = Path(resolved_path)
extension = path_obj.suffix[1:].lower()
if extension == "doc":
raise ValueError("doc files are not supported. Please save as .docx")
if extension not in TEXT_FILE_TYPES:
raise ValueError(f"Unsupported file type: {extension}")
record = parse_text_file_to_record(resolved_path, silent_errors)
self.status = record if record else "No data"
return record or Record()
def build(
self,
path: str,
silent_errors: bool = False,
) -> Record:
record = self.load_file(path, silent_errors)
self.status = record
return record
data = parse_text_file_to_data(resolved_path, silent_errors)
self.status = data if data else "No data"
return data or Data()

View file

@ -1,27 +1,65 @@
from typing import Any, Dict
import re
from langchain_community.document_loaders.web_base import WebBaseLoader
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.custom import Component
from langflow.io import Output, TextInput
from langflow.schema import Data
class URLComponent(CustomComponent):
class URLComponent(Component):
display_name = "URL"
description = "Fetch content from one or more URLs."
icon = "layout-template"
def build_config(self) -> Dict[str, Any]:
return {
"urls": {"display_name": "URL"},
}
inputs = [
TextInput(
name="urls",
display_name="URLs",
info="Enter one or more URLs, separated by commas.",
is_list=True,
),
]
def build(
self,
urls: list[str],
) -> list[Record]:
loader = WebBaseLoader(web_paths=[url for url in urls if url])
outputs = [
Output(display_name="Data", name="data", method="fetch_content"),
]
def ensure_url(self, string: str) -> str:
"""
Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.
Raises an error if the string is not a valid URL.
Parameters:
string (str): The string to be checked and possibly modified.
Returns:
str: The modified string that is ensured to be a URL.
Raises:
ValueError: If the string is not a valid URL.
"""
if not string.startswith(("http://", "https://")):
string = "http://" + string
# Basic URL validation regex
url_regex = re.compile(
r"^(http://|https://)?" # http:// or https://
r"(([a-zA-Z0-9\.-]+)" # domain
r"(\.[a-zA-Z]{2,}))" # top-level domain
r"(:[0-9]{1,5})?" # optional port
r"(\/.*)?$" # optional path
)
if not re.match(url_regex, string):
raise ValueError(f"Invalid URL: {string}")
return string
def fetch_content(self) -> list[Data]:
urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]
loader = WebBaseLoader(web_paths=urls, encoding="utf-8")
docs = loader.load()
records = self.to_records(docs)
self.status = records
return records
data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]
self.status = data
return data

View file

@ -1,39 +1,37 @@
import json
import uuid
from typing import Any, Optional
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.schema.dotdict import dotdict
from langflow.custom import Component
from langflow.io import MultilineInput, Output
from langflow.schema import Data
class WebhookComponent(CustomComponent):
class WebhookComponent(Component):
display_name = "Webhook Input"
description = "Defines a webhook input for the flow."
def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
if field_name == "webhook_id":
build_config["webhook_id"]["value"] = uuid.uuid4().hex
return build_config
inputs = [
MultilineInput(
name="data",
display_name="Data",
info="Use this field to quickly test the webhook component by providing a JSON payload.",
)
]
outputs = [
Output(display_name="Data", name="output_data", method="build_data"),
]
def build_config(self):
return {
"data": {
"display_name": "Data",
"info": "Use this field to quickly test the webhook component by providing a JSON payload.",
"multiline": True,
}
}
def build(self, data: Optional[str] = "") -> Record:
message = ""
def build_data(self) -> Data:
message: str | Data = ""
if not self.data:
self.status = "No data provided."
return Data(data={})
try:
body = json.loads(data or "{}")
body = json.loads(self.data or "{}")
except json.JSONDecodeError:
body = {"payload": data}
message = f"Invalid JSON payload. Please check the format.\n\n{data}"
record = Record(data=body)
body = {"payload": self.data}
message = f"Invalid JSON payload. Please check the format.\n\n{self.data}"
data = Data(data=body)
if not message:
message = json.dumps(body, indent=2)
message = data
self.status = message
return record
return data

View file

@ -1,8 +1,7 @@
from .APIRequest import APIRequest
from .APIRequest import APIRequestComponent
from .Directory import DirectoryComponent
from .File import FileComponent
from .URL import URLComponent
from .Webhook import WebhookComponent
from .URL import URLComponent
__all__ = ["APIRequest", "DirectoryComponent", "FileComponent", "URLComponent", "WebhookComponent"]
__all__ = ["APIRequestComponent", "DirectoryComponent", "FileComponent", "URLComponent", "WebhookComponent"]

View file

@ -1,42 +1,48 @@
from typing import Optional
from langchain_community.embeddings import BedrockEmbeddings
from langchain_core.embeddings import Embeddings
from langflow.custom import CustomComponent
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import Embeddings
from langflow.io import DropdownInput, Output, TextInput
class AmazonBedrockEmeddingsComponent(CustomComponent):
class AmazonBedrockEmbeddingsComponent(LCModelComponent):
display_name: str = "Amazon Bedrock Embeddings"
description: str = "Generate embeddings using Amazon Bedrock models."
documentation = "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/bedrock"
icon = "Amazon"
def build_config(self):
return {
"model_id": {
"display_name": "Model Id",
"options": ["amazon.titan-embed-text-v1"],
},
"credentials_profile_name": {"display_name": "Credentials Profile Name"},
"endpoint_url": {"display_name": "Bedrock Endpoint URL"},
"region_name": {"display_name": "AWS Region"},
"code": {"show": False},
}
inputs = [
DropdownInput(
name="model_id",
display_name="Model Id",
options=["amazon.titan-embed-text-v1"],
value="amazon.titan-embed-text-v1",
),
TextInput(
name="credentials_profile_name",
display_name="Credentials Profile Name",
),
TextInput(
name="endpoint_url",
display_name="Bedrock Endpoint URL",
),
TextInput(
name="region_name",
display_name="AWS Region",
),
]
def build(
self,
model_id: str = "amazon.titan-embed-text-v1",
credentials_profile_name: Optional[str] = None,
endpoint_url: Optional[str] = None,
region_name: Optional[str] = None,
) -> Embeddings:
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
try:
output = BedrockEmbeddings(
credentials_profile_name=credentials_profile_name,
model_id=model_id,
endpoint_url=endpoint_url,
region_name=region_name,
credentials_profile_name=self.credentials_profile_name,
model_id=self.model_id,
endpoint_url=self.endpoint_url,
region_name=self.region_name,
) # type: ignore
except Exception as e:
raise ValueError("Could not connect to AmazonBedrock API.") from e
raise ValueError("Could not connect to Amazon Bedrock API.") from e
return output

View file

@ -1,16 +1,15 @@
from typing import Optional
from langchain_core.embeddings import Embeddings
from langchain_openai import AzureOpenAIEmbeddings
from pydantic.v1 import SecretStr
from langflow.custom import CustomComponent
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import Embeddings
from langflow.io import DropdownInput, IntInput, Output, SecretStrInput, TextInput
class AzureOpenAIEmbeddingsComponent(CustomComponent):
class AzureOpenAIEmbeddingsComponent(LCModelComponent):
display_name: str = "Azure OpenAI Embeddings"
description: str = "Generate embeddings using Azure OpenAI models."
documentation: str = "https://python.langchain.com/docs/integrations/text_embedding/azureopenai"
beta = False
icon = "Azure"
API_VERSION_OPTIONS = [
@ -22,57 +21,56 @@ class AzureOpenAIEmbeddingsComponent(CustomComponent):
"2023-08-01-preview",
]
def build_config(self):
return {
"azure_endpoint": {
"display_name": "Azure Endpoint",
"required": True,
"info": "Your Azure endpoint, including the resource.. Example: `https://example-resource.azure.openai.com/`",
},
"azure_deployment": {
"display_name": "Deployment Name",
"required": True,
},
"api_version": {
"display_name": "API Version",
"options": self.API_VERSION_OPTIONS,
"value": self.API_VERSION_OPTIONS[-1],
"advanced": True,
},
"api_key": {
"display_name": "API Key",
"required": True,
"password": True,
},
"code": {"show": False},
"dimensions": {
"display_name": "Dimensions",
"info": "The number of dimensions the resulting output embeddings should have. Only supported by certain models.",
"advanced": True,
},
}
inputs = [
TextInput(
name="azure_endpoint",
display_name="Azure Endpoint",
required=True,
info="Your Azure endpoint, including the resource. Example: `https://example-resource.azure.openai.com/`",
),
TextInput(
name="azure_deployment",
display_name="Deployment Name",
required=True,
),
DropdownInput(
name="api_version",
display_name="API Version",
options=API_VERSION_OPTIONS,
value=API_VERSION_OPTIONS[-1],
advanced=True,
),
SecretStrInput(
name="api_key",
display_name="API Key",
required=True,
),
IntInput(
name="dimensions",
display_name="Dimensions",
info="The number of dimensions the resulting output embeddings should have. Only supported by certain models.",
advanced=True,
),
]
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
if not self.api_key:
raise ValueError("API Key is required")
azure_api_key = SecretStr(self.api_key)
def build(
self,
azure_endpoint: str,
azure_deployment: str,
api_version: str,
api_key: str,
dimensions: Optional[int] = None,
) -> 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,
azure_endpoint=self.azure_endpoint,
azure_deployment=self.azure_deployment,
api_version=self.api_version,
api_key=azure_api_key,
dimensions=dimensions,
dimensions=self.dimensions,
)
except Exception as e:
raise ValueError("Could not connect to AzureOpenAIEmbeddings API.") from e

View file

@ -1,38 +1,44 @@
from typing import Optional
from langchain_community.embeddings.cohere import CohereEmbeddings
from langflow.custom import CustomComponent
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import Embeddings
from langflow.io import DropdownInput, FloatInput, IntInput, Output, SecretStrInput, TextInput
class CohereEmbeddingsComponent(CustomComponent):
class CohereEmbeddingsComponent(LCModelComponent):
display_name = "Cohere Embeddings"
description = "Generate embeddings using Cohere models."
icon = "Cohere"
inputs = [
SecretStrInput(name="cohere_api_key", display_name="Cohere API Key"),
DropdownInput(
name="model",
display_name="Model",
advanced=True,
options=[
"embed-english-v2.0",
"embed-multilingual-v2.0",
"embed-english-light-v2.0",
"embed-multilingual-light-v2.0",
],
value="embed-english-v2.0",
),
TextInput(name="truncate", display_name="Truncate", advanced=True),
IntInput(name="max_retries", display_name="Max Retries", value=3, advanced=True),
TextInput(name="user_agent", display_name="User Agent", advanced=True, value="langchain"),
FloatInput(name="request_timeout", display_name="Request Timeout", advanced=True),
]
def build_config(self):
return {
"cohere_api_key": {"display_name": "Cohere API Key", "password": True},
"model": {"display_name": "Model", "default": "embed-english-v2.0", "advanced": True},
"truncate": {"display_name": "Truncate", "advanced": True},
"max_retries": {"display_name": "Max Retries", "advanced": True},
"user_agent": {"display_name": "User Agent", "advanced": True},
"request_timeout": {"display_name": "Request Timeout", "advanced": True},
}
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build(
self,
request_timeout: Optional[float] = None,
cohere_api_key: str = "",
max_retries: int = 3,
model: str = "embed-english-v2.0",
truncate: Optional[str] = None,
user_agent: str = "langchain",
) -> CohereEmbeddings:
def build_embeddings(self) -> Embeddings:
return CohereEmbeddings( # type: ignore
max_retries=max_retries,
user_agent=user_agent,
request_timeout=request_timeout,
cohere_api_key=cohere_api_key,
model=model,
truncate=truncate,
cohere_api_key=self.cohere_api_key,
model=self.model,
truncate=self.truncate,
max_retries=self.max_retries,
user_agent=self.user_agent,
request_timeout=self.request_timeout or None,
)

View file

@ -1,11 +1,11 @@
from typing import Dict, Optional
from langchain_community.embeddings.huggingface import HuggingFaceEmbeddings
from langflow.custom import CustomComponent
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import Embeddings
from langflow.io import BoolInput, DictInput, TextInput, Output
class HuggingFaceEmbeddingsComponent(CustomComponent):
class HuggingFaceEmbeddingsComponent(LCModelComponent):
display_name = "Hugging Face Embeddings"
description = "Generate embeddings using HuggingFace models."
documentation = (
@ -13,27 +13,23 @@ class HuggingFaceEmbeddingsComponent(CustomComponent):
)
icon = "HuggingFace"
def build_config(self):
return {
"cache_folder": {"display_name": "Cache Folder", "advanced": True},
"encode_kwargs": {"display_name": "Encode Kwargs", "advanced": True, "field_type": "dict"},
"model_kwargs": {"display_name": "Model Kwargs", "field_type": "dict", "advanced": True},
"model_name": {"display_name": "Model Name"},
"multi_process": {"display_name": "Multi Process", "advanced": True},
}
inputs = [
TextInput(name="cache_folder", display_name="Cache Folder", advanced=True),
DictInput(name="encode_kwargs", display_name="Encode Kwargs", advanced=True),
DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
TextInput(name="model_name", display_name="Model Name", value="sentence-transformers/all-mpnet-base-v2"),
BoolInput(name="multi_process", display_name="Multi Process", advanced=True),
]
def build(
self,
cache_folder: Optional[str] = None,
encode_kwargs: Optional[Dict] = {},
model_kwargs: Optional[Dict] = {},
model_name: str = "sentence-transformers/all-mpnet-base-v2",
multi_process: bool = False,
) -> HuggingFaceEmbeddings:
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
return HuggingFaceEmbeddings(
cache_folder=cache_folder,
encode_kwargs=encode_kwargs,
model_kwargs=model_kwargs,
model_name=model_name,
multi_process=multi_process,
cache_folder=self.cache_folder,
encode_kwargs=self.encode_kwargs,
model_kwargs=self.model_kwargs,
model_name=self.model_name,
multi_process=self.multi_process,
)

View file

@ -1,44 +1,31 @@
from typing import Dict, Optional
from langchain_community.embeddings.huggingface import HuggingFaceInferenceAPIEmbeddings
from pydantic.v1.types import SecretStr
from langflow.custom import CustomComponent
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import Embeddings
from langflow.io import Output, SecretStrInput, TextInput
class HuggingFaceInferenceAPIEmbeddingsComponent(CustomComponent):
class HuggingFaceInferenceAPIEmbeddingsComponent(LCModelComponent):
display_name = "Hugging Face API Embeddings"
description = "Generate embeddings using Hugging Face Inference API models."
documentation = "https://github.com/huggingface/text-embeddings-inference"
icon = "HuggingFace"
def build_config(self):
return {
"api_key": {"display_name": "API Key", "password": True, "advanced": True},
"api_url": {"display_name": "API URL", "advanced": True},
"model_name": {"display_name": "Model Name"},
"cache_folder": {"display_name": "Cache Folder", "advanced": True},
"encode_kwargs": {"display_name": "Encode Kwargs", "advanced": True, "field_type": "dict"},
"model_kwargs": {"display_name": "Model Kwargs", "field_type": "dict", "advanced": True},
"multi_process": {"display_name": "Multi Process", "advanced": True},
}
inputs = [
SecretStrInput(name="api_key", display_name="API Key", advanced=True),
TextInput(name="api_url", display_name="API URL", advanced=True, value="http://localhost:8080"),
TextInput(name="model_name", display_name="Model Name", value="BAAI/bge-large-en-v1.5"),
]
def build(
self,
api_key: Optional[str] = "",
api_url: str = "http://localhost:8080",
model_name: str = "BAAI/bge-large-en-v1.5",
cache_folder: Optional[str] = None,
encode_kwargs: Optional[Dict] = {},
model_kwargs: Optional[Dict] = {},
multi_process: bool = False,
) -> HuggingFaceInferenceAPIEmbeddings:
if api_key:
secret_api_key = SecretStr(api_key)
else:
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
if not self.api_key:
raise ValueError("API Key is required")
return HuggingFaceInferenceAPIEmbeddings(
api_key=secret_api_key,
api_url=api_url,
model_name=model_name,
)
api_key = SecretStr(self.api_key)
return HuggingFaceInferenceAPIEmbeddings(api_key=api_key, api_url=self.api_url, model_name=self.model_name)

View file

@ -1,64 +1,56 @@
from langchain_mistralai.embeddings import MistralAIEmbeddings
from pydantic.v1 import SecretStr
from langflow.custom import CustomComponent
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import Embeddings
from langflow.io import DropdownInput, IntInput, Output, SecretStrInput, TextInput
class MistralAIEmbeddingsComponent(CustomComponent):
class MistralAIEmbeddingsComponent(LCModelComponent):
display_name = "MistralAI Embeddings"
description = "Generate embeddings using MistralAI models."
icon = "MistralAI"
def build_config(self):
return {
"model": {
"display_name": "Model",
"advanced": False,
"options": ["mistral-embed"],
"value": "mistral-embed",
},
"mistral_api_key": {
"display_name": "Mistral API Key",
"password": True,
"advanced": False,
},
"max_concurrent_requests": {
"display_name": "Max Concurrent Requests",
"advanced": True,
"value": 64,
},
"max_retries": {
"display_name": "Max Retries",
"advanced": True,
"value": 5,
},
"timeout": {
"display_name": "Request Timeout",
"advanced": True,
"value": 120,
},
"endpoint": {"display_name": "API Endpoint", "advanced": True, "value": "https://api.mistral.ai/v1/"},
}
inputs = [
DropdownInput(
name="model",
display_name="Model",
advanced=False,
options=["mistral-embed"],
value="mistral-embed",
),
SecretStrInput(name="mistral_api_key", display_name="Mistral API Key"),
IntInput(
name="max_concurrent_requests",
display_name="Max Concurrent Requests",
advanced=True,
value=64,
),
IntInput(name="max_retries", display_name="Max Retries", advanced=True, value=5),
IntInput(name="timeout", display_name="Request Timeout", advanced=True, value=120),
TextInput(
name="endpoint",
display_name="API Endpoint",
advanced=True,
value="https://api.mistral.ai/v1/",
),
]
def build(
self,
mistral_api_key: str,
model: str = "mistral-embed",
max_concurrent_requests: int = 64,
max_retries: int = 5,
timeout: int = 120,
endpoint: str = "https://api.mistral.ai/v1/",
) -> Embeddings:
if mistral_api_key:
api_key = SecretStr(mistral_api_key)
else:
api_key = None
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
if not self.mistral_api_key:
raise ValueError("Mistral API Key is required")
api_key = SecretStr(self.mistral_api_key)
return MistralAIEmbeddings(
api_key=api_key,
model=model,
endpoint=endpoint,
max_concurrent_requests=max_concurrent_requests,
max_retries=max_retries,
timeout=timeout,
model=self.model,
endpoint=self.endpoint,
max_concurrent_requests=self.max_concurrent_requests,
max_retries=self.max_retries,
timeout=self.timeout,
)

View file

@ -1,34 +1,44 @@
from typing import Optional
from langchain_community.embeddings import OllamaEmbeddings
from langchain_core.embeddings import Embeddings
from langflow.custom import CustomComponent
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import Embeddings
from langflow.io import FloatInput, Output, TextInput
class OllamaEmbeddingsComponent(CustomComponent):
class OllamaEmbeddingsComponent(LCModelComponent):
display_name: str = "Ollama Embeddings"
description: str = "Generate embeddings using Ollama models."
documentation = "https://python.langchain.com/docs/integrations/text_embedding/ollama"
icon = "Ollama"
def build_config(self):
return {
"model": {
"display_name": "Ollama Model",
},
"base_url": {"display_name": "Ollama Base URL"},
"temperature": {"display_name": "Model Temperature"},
"code": {"show": False},
}
inputs = [
TextInput(
name="model",
display_name="Ollama Model",
value="llama2",
),
TextInput(
name="base_url",
display_name="Ollama Base URL",
value="http://localhost:11434",
),
FloatInput(
name="temperature",
display_name="Model Temperature",
advanced=True,
),
]
def build(
self,
model: str = "llama2",
base_url: str = "http://localhost:11434",
temperature: Optional[float] = None,
) -> Embeddings:
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
try:
output = OllamaEmbeddings(model=model, base_url=base_url, temperature=temperature) # type: ignore
output = OllamaEmbeddings(
model=self.model,
base_url=self.base_url,
temperature=self.temperature,
) # type: ignore
except Exception as e:
raise ValueError("Could not connect to Ollama API.") from e
return output

View file

@ -1,150 +1,96 @@
from typing import Dict, List, Optional
from langchain_openai.embeddings.base import OpenAIEmbeddings
from pydantic.v1 import SecretStr
from langflow.custom import CustomComponent
from langflow.field_typing import Embeddings, NestedDict
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import Embeddings
from langflow.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, Output, SecretStrInput, TextInput
class OpenAIEmbeddingsComponent(CustomComponent):
class OpenAIEmbeddingsComponent(LCModelComponent):
display_name = "OpenAI Embeddings"
description = "Generate embeddings using OpenAI models."
icon = "OpenAI"
inputs = [
DictInput(
name="default_headers",
display_name="Default Headers",
advanced=True,
info="Default headers to use for the API request.",
),
DictInput(
name="default_query",
display_name="Default Query",
advanced=True,
info="Default query parameters to use for the API request.",
),
IntInput(name="chunk_size", display_name="Chunk Size", advanced=True, value=1000),
TextInput(name="client", display_name="Client", advanced=True),
TextInput(name="deployment", display_name="Deployment", advanced=True),
IntInput(name="embedding_ctx_length", display_name="Embedding Context Length", advanced=True, value=1536),
IntInput(name="max_retries", display_name="Max Retries", value=3, advanced=True),
DropdownInput(
name="model",
display_name="Model",
advanced=False,
options=[
"text-embedding-3-small",
"text-embedding-3-large",
"text-embedding-ada-002",
],
value="text-embedding-3-small",
),
DictInput(name="model_kwargs", display_name="Model Kwargs", advanced=True),
SecretStrInput(name="openai_api_base", display_name="OpenAI API Base", advanced=True),
SecretStrInput(name="openai_api_key", display_name="OpenAI API Key"),
SecretStrInput(name="openai_api_type", display_name="OpenAI API Type", advanced=True),
TextInput(name="openai_api_version", display_name="OpenAI API Version", advanced=True),
TextInput(
name="openai_organization",
display_name="OpenAI Organization",
advanced=True,
),
TextInput(name="openai_proxy", display_name="OpenAI Proxy", advanced=True),
FloatInput(name="request_timeout", display_name="Request Timeout", advanced=True),
BoolInput(name="show_progress_bar", display_name="Show Progress Bar", advanced=True),
BoolInput(name="skip_empty", display_name="Skip Empty", advanced=True),
TextInput(
name="tiktoken_model_name",
display_name="TikToken Model Name",
advanced=True,
),
BoolInput(
name="tiktoken_enable",
display_name="TikToken Enable",
advanced=True,
value=True,
info="If False, you must have transformers installed.",
),
]
def build_config(self):
return {
"allowed_special": {
"display_name": "Allowed Special",
"advanced": True,
"field_type": "str",
"is_list": True,
},
"default_headers": {
"display_name": "Default Headers",
"advanced": True,
"field_type": "dict",
},
"default_query": {
"display_name": "Default Query",
"advanced": True,
"field_type": "NestedDict",
},
"disallowed_special": {
"display_name": "Disallowed Special",
"advanced": True,
"field_type": "str",
"is_list": True,
},
"chunk_size": {"display_name": "Chunk Size", "advanced": True},
"client": {"display_name": "Client", "advanced": True},
"deployment": {"display_name": "Deployment", "advanced": True},
"embedding_ctx_length": {
"display_name": "Embedding Context Length",
"advanced": True,
},
"max_retries": {"display_name": "Max Retries", "advanced": True},
"model": {
"display_name": "Model",
"advanced": False,
"options": [
"text-embedding-3-small",
"text-embedding-3-large",
"text-embedding-ada-002",
],
},
"model_kwargs": {"display_name": "Model Kwargs", "advanced": True},
"openai_api_base": {
"display_name": "OpenAI API Base",
"password": True,
"advanced": True,
},
"openai_api_key": {"display_name": "OpenAI API Key", "password": True},
"openai_api_type": {
"display_name": "OpenAI API Type",
"advanced": True,
"password": True,
},
"openai_api_version": {
"display_name": "OpenAI API Version",
"advanced": True,
},
"openai_organization": {
"display_name": "OpenAI Organization",
"advanced": True,
},
"openai_proxy": {"display_name": "OpenAI Proxy", "advanced": True},
"request_timeout": {"display_name": "Request Timeout", "advanced": True},
"show_progress_bar": {
"display_name": "Show Progress Bar",
"advanced": True,
},
"skip_empty": {"display_name": "Skip Empty", "advanced": True},
"tiktoken_model_name": {
"display_name": "TikToken Model Name",
"advanced": True,
},
"tiktoken_enable": {"display_name": "TikToken Enable", "advanced": True},
"dimensions": {
"display_name": "Dimensions",
"info": "The number of dimensions the resulting output embeddings should have. Only supported by certain models.",
"advanced": True,
},
}
def build(
self,
openai_api_key: str,
default_headers: Optional[Dict[str, str]] = None,
default_query: Optional[NestedDict] = {},
allowed_special: List[str] = [],
disallowed_special: List[str] = ["all"],
chunk_size: int = 1000,
deployment: str = "text-embedding-ada-002",
embedding_ctx_length: int = 8191,
max_retries: int = 6,
model: str = "text-embedding-ada-002",
model_kwargs: NestedDict = {},
openai_api_base: Optional[str] = None,
openai_api_type: Optional[str] = None,
openai_api_version: Optional[str] = None,
openai_organization: Optional[str] = None,
openai_proxy: Optional[str] = None,
request_timeout: Optional[float] = None,
show_progress_bar: bool = False,
skip_empty: bool = False,
tiktoken_enable: bool = True,
tiktoken_model_name: Optional[str] = None,
dimensions: Optional[int] = None,
) -> Embeddings:
# This is to avoid errors with Vector Stores (e.g Chroma)
if disallowed_special == ["all"]:
disallowed_special = "all" # type: ignore
if openai_api_key:
api_key = SecretStr(openai_api_key)
else:
api_key = None
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
return OpenAIEmbeddings(
tiktoken_enabled=tiktoken_enable,
default_headers=default_headers,
default_query=default_query,
allowed_special=set(allowed_special),
tiktoken_enabled=self.tiktoken_enable,
default_headers=self.default_headers,
default_query=self.default_query,
allowed_special="all",
disallowed_special="all",
chunk_size=chunk_size,
deployment=deployment,
embedding_ctx_length=embedding_ctx_length,
max_retries=max_retries,
model=model,
model_kwargs=model_kwargs,
base_url=openai_api_base,
api_key=api_key,
openai_api_type=openai_api_type,
api_version=openai_api_version,
organization=openai_organization,
openai_proxy=openai_proxy,
timeout=request_timeout,
show_progress_bar=show_progress_bar,
skip_empty=skip_empty,
tiktoken_model_name=tiktoken_model_name,
dimensions=dimensions,
chunk_size=self.chunk_size,
deployment=self.deployment,
embedding_ctx_length=self.embedding_ctx_length,
max_retries=self.max_retries,
model=self.model,
model_kwargs=self.model_kwargs,
base_url=self.openai_api_base,
api_key=self.openai_api_key,
openai_api_type=self.openai_api_type,
api_version=self.openai_api_version,
organization=self.openai_organization,
openai_proxy=self.openai_proxy,
timeout=self.request_timeout or None,
show_progress_bar=self.show_progress_bar,
skip_empty=self.skip_empty,
tiktoken_model_name=self.tiktoken_model_name,
)

View file

@ -1,90 +1,119 @@
from typing import List, Optional
from langchain_google_vertexai import VertexAIEmbeddings
from langflow.custom import CustomComponent
from langflow.base.models.model import LCModelComponent
from langflow.field_typing import Embeddings
from langflow.io import BoolInput, DictInput, FileInput, FloatInput, IntInput, Output, TextInput
class VertexAIEmbeddingsComponent(CustomComponent):
class VertexAIEmbeddingsComponent(LCModelComponent):
display_name = "VertexAI Embeddings"
description = "Generate embeddings using Google Cloud VertexAI models."
icon = "VertexAI"
def build_config(self):
return {
"credentials": {
"display_name": "Credentials",
"value": "",
"file_types": [".json"],
"field_type": "file",
},
"instance": {
"display_name": "instance",
"advanced": True,
"field_type": "dict",
},
"location": {
"display_name": "Location",
"value": "us-central1",
"advanced": True,
},
"max_output_tokens": {"display_name": "Max Output Tokens", "value": 128},
"max_retries": {
"display_name": "Max Retries",
"value": 6,
"advanced": True,
},
"model_name": {
"display_name": "Model Name",
"value": "textembedding-gecko",
},
"n": {"display_name": "N", "value": 1, "advanced": True},
"project": {"display_name": "Project", "advanced": True},
"request_parallelism": {
"display_name": "Request Parallelism",
"value": 5,
"advanced": True,
},
"stop": {"display_name": "Stop", "advanced": True},
"streaming": {
"display_name": "Streaming",
"value": False,
"advanced": True,
},
"temperature": {"display_name": "Temperature", "value": 0.0},
"top_k": {"display_name": "Top K", "value": 40, "advanced": True},
"top_p": {"display_name": "Top P", "value": 0.95, "advanced": True},
}
inputs = [
FileInput(
name="credentials",
display_name="Credentials",
value="",
file_types=["json"], # Removed the dot
),
DictInput(
name="instance",
display_name="Instance",
advanced=True,
),
TextInput(
name="location",
display_name="Location",
value="us-central1",
advanced=True,
),
IntInput(
name="max_output_tokens",
display_name="Max Output Tokens",
value=128,
),
IntInput(
name="max_retries",
display_name="Max Retries",
value=6,
advanced=True,
),
TextInput(
name="model_name",
display_name="Model Name",
value="textembedding-gecko",
),
IntInput(
name="n",
display_name="N",
value=1,
advanced=True,
),
TextInput(
name="project",
display_name="Project",
advanced=True,
),
IntInput(
name="request_parallelism",
display_name="Request Parallelism",
value=5,
advanced=True,
),
TextInput(
name="stop",
display_name="Stop",
advanced=True,
),
BoolInput(
name="streaming",
display_name="Streaming",
value=False,
advanced=True,
),
FloatInput(
name="temperature",
display_name="Temperature",
value=0.0,
),
IntInput(
name="top_k",
display_name="Top K",
value=40,
advanced=True,
),
FloatInput(
name="top_p",
display_name="Top P",
value=0.95,
advanced=True,
),
]
outputs = [
Output(display_name="Embeddings", name="embeddings", method="build_embeddings"),
]
def build_embeddings(self) -> Embeddings:
try:
from langchain_google_vertexai import VertexAIEmbeddings
except ImportError:
raise ImportError(
"Please install the langchain-google-vertexai package to use the VertexAIEmbeddings component."
)
def build(
self,
instance: Optional[str] = None,
credentials: Optional[str] = None,
location: str = "us-central1",
max_output_tokens: int = 128,
max_retries: int = 6,
model_name: str = "textembedding-gecko",
n: int = 1,
project: Optional[str] = None,
request_parallelism: int = 5,
stop: Optional[List[str]] = None,
streaming: bool = False,
temperature: float = 0.0,
top_k: int = 40,
top_p: float = 0.95,
) -> VertexAIEmbeddings:
return VertexAIEmbeddings(
instance=instance,
credentials=credentials,
location=location,
max_output_tokens=max_output_tokens,
max_retries=max_retries,
model_name=model_name,
n=n,
project=project,
request_parallelism=request_parallelism,
stop=stop,
streaming=streaming,
temperature=temperature,
top_k=top_k,
top_p=top_p,
instance=self.instance,
credentials=self.credentials,
location=self.location,
max_output_tokens=self.max_output_tokens,
max_retries=self.max_retries,
model_name=self.model_name,
n=self.n,
project=self.project,
request_parallelism=self.request_parallelism,
stop=self.stop,
streaming=self.streaming,
temperature=self.temperature,
top_k=self.top_k,
top_p=self.top_p,
)

View file

@ -1,4 +1,4 @@
from .AmazonBedrockEmbeddings import AmazonBedrockEmeddingsComponent
from .AmazonBedrockEmbeddings import AmazonBedrockEmbeddingsComponent
from .AzureOpenAIEmbeddings import AzureOpenAIEmbeddingsComponent
from .CohereEmbeddings import CohereEmbeddingsComponent
from .HuggingFaceEmbeddings import HuggingFaceEmbeddingsComponent
@ -8,7 +8,7 @@ from .OpenAIEmbeddings import OpenAIEmbeddingsComponent
from .VertexAIEmbeddings import VertexAIEmbeddingsComponent
__all__ = [
"AmazonBedrockEmeddingsComponent",
"AmazonBedrockEmbeddingsComponent",
"AzureOpenAIEmbeddingsComponent",
"CohereEmbeddingsComponent",
"HuggingFaceEmbeddingsComponent",

View file

@ -5,8 +5,8 @@ from langchain_core.prompts.chat import HumanMessagePromptTemplate, SystemMessag
from langflow.base.agents.agent import LCAgentComponent
from langflow.base.agents.utils import AGENTS, AgentSpec, get_agents_list
from langflow.field_typing import BaseLanguageModel, Text, Tool
from langflow.schema import Record
from langflow.field_typing import LanguageModel, Text, Tool
from langflow.schema import Data
from langflow.schema.dotdict import dotdict
@ -145,11 +145,11 @@ class AgentComponent(LCAgentComponent):
self,
agent_name: str,
input_value: str,
llm: BaseLanguageModel,
llm: LanguageModel,
tools: List[Tool],
system_message: str = "You are a helpful assistant. Help the user answer any questions.",
user_prompt: str = "{input}",
message_history: Optional[List[Record]] = None,
message_history: Optional[List[Data]] = None,
tool_template: str = "{name}: {description}",
handle_parsing_errors: bool = True,
) -> Text:

View file

@ -21,6 +21,6 @@ class ClearMessageHistoryComponent(CustomComponent):
session_id: str,
) -> None:
delete_messages(session_id=session_id)
records = get_messages(session_id=session_id)
self.records = records
return records
data = get_messages(session_id=session_id)
self.data = data
return data

View file

@ -0,0 +1,86 @@
from langflow.custom import Component
from langflow.io import BoolInput, DropdownInput, MessageInput, Output, TextInput
from langflow.schema.message import Message
class ConditionalRouterComponent(Component):
display_name = "Conditional Router"
description = "Routes an input message to a corresponding output based on text comparison."
icon = "equal"
inputs = [
TextInput(
name="input_text",
display_name="Input Text",
info="The primary text input for the operation.",
),
TextInput(
name="match_text",
display_name="Match Text",
info="The text input to compare against.",
),
DropdownInput(
name="operator",
display_name="Operator",
options=["equals", "not equals", "contains", "starts with", "ends with"],
info="The operator to apply for comparing the texts.",
value="equals",
advanced=True,
),
BoolInput(
name="case_sensitive",
display_name="Case Sensitive",
info="If true, the comparison will be case sensitive.",
value=False,
advanced=True,
),
MessageInput(
name="message",
display_name="Message",
info="The message to pass through either route.",
),
]
outputs = [
Output(display_name="True Route", name="true_result", method="true_response"),
Output(display_name="False Route", name="false_result", method="false_response"),
]
def evaluate_condition(self, input_text: str, match_text: str, operator: str, case_sensitive: bool) -> bool:
if not case_sensitive:
input_text = input_text.lower()
match_text = match_text.lower()
if operator == "equals":
return input_text == match_text
elif operator == "not equals":
return input_text != match_text
elif operator == "contains":
return match_text in input_text
elif operator == "starts with":
return input_text.startswith(match_text)
elif operator == "ends with":
return input_text.endswith(match_text)
return False
def true_response(self) -> Message:
result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive)
if result:
self.stop("false_result")
response = self.message if self.message else self.input_text
self.status = response
return response
else:
self.stop("true_result")
return Message()
def false_response(self) -> Message:
result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive)
if not result:
self.stop("true_result")
response = self.message if self.message else self.input_text
self.status = response
return response
else:
self.stop("false_result")
return Message()

View file

@ -1,6 +1,6 @@
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.field_typing import Embeddings
from langflow.schema import Data
class EmbedComponent(CustomComponent):
@ -9,7 +9,7 @@ class EmbedComponent(CustomComponent):
def build_config(self):
return {"texts": {"display_name": "Texts"}, "embbedings": {"display_name": "Embeddings"}}
def build(self, texts: list[str], embbedings: Embeddings) -> Record:
vectors = Record(vector=embbedings.embed_documents(texts))
def build(self, texts: list[str], embbedings: Embeddings) -> Data:
vectors = Data(vector=embbedings.embed_documents(texts))
self.status = vectors
return vectors

View file

@ -1,45 +0,0 @@
from langflow.custom import CustomComponent
from langflow.schema import Record
class ExtractKeyFromRecordComponent(CustomComponent):
display_name = "Extract Key From Record"
description = "Extracts a key from a record."
beta: bool = True
field_config = {
"record": {"display_name": "Record"},
"keys": {
"display_name": "Keys",
"info": "The keys to extract from the record.",
"input_types": [],
},
"silent_error": {
"display_name": "Silent Errors",
"info": "If True, errors will not be raised.",
"advanced": True,
},
}
def build(self, record: Record, keys: list[str], silent_error: bool = True) -> Record:
"""
Extracts the keys from a record.
Args:
record (Record): The record from which to extract the keys.
keys (list[str]): The keys to extract from the record.
silent_error (bool): If True, errors will not be raised.
Returns:
dict: The extracted keys.
"""
extracted_keys = {}
for key in keys:
try:
extracted_keys[key] = getattr(record, key)
except AttributeError:
if not silent_error:
raise KeyError(f"The key '{key}' does not exist in the record.")
return_record = Record(data=extracted_keys)
self.status = return_record
return return_record

View file

@ -0,0 +1,45 @@
from langflow.custom import CustomComponent
from langflow.schema import Data
class ExtractKeyFromDataComponent(CustomComponent):
display_name = "Extract Key From Data"
description = "Extracts a key from a data."
beta: bool = True
field_config = {
"data": {"display_name": "Data"},
"keys": {
"display_name": "Keys",
"info": "The keys to extract from the data.",
"input_types": [],
},
"silent_error": {
"display_name": "Silent Errors",
"info": "If True, errors will not be raised.",
"advanced": True,
},
}
def build(self, data: Data, keys: list[str], silent_error: bool = True) -> Data:
"""
Extracts the keys from a data.
Args:
data (Data): The data from which to extract the keys.
keys (list[str]): The keys to extract from the data.
silent_error (bool): If True, errors will not be raised.
Returns:
dict: The extracted keys.
"""
extracted_keys = {}
for key in keys:
try:
extracted_keys[key] = getattr(data, key)
except AttributeError:
if not silent_error:
raise KeyError(f"The key '{key}' does not exist in the data.")
return_data = Data(data=extracted_keys)
self.status = return_data
return return_data

View file

@ -7,7 +7,7 @@ from langflow.custom import CustomComponent
from langflow.field_typing import Tool
from langflow.graph.graph.base import Graph
from langflow.helpers.flow import get_flow_inputs
from langflow.schema import Record
from langflow.schema import Data
from langflow.schema.dotdict import dotdict
@ -17,10 +17,10 @@ class FlowToolComponent(CustomComponent):
field_order = ["flow_name", "name", "description", "return_direct"]
def get_flow_names(self) -> List[str]:
flow_records = self.list_flows()
return [flow_record.data["name"] for flow_record in flow_records]
flow_datas = self.list_flows()
return [flow_data.data["name"] for flow_data in flow_datas]
def get_flow(self, flow_name: str) -> Optional[Record]:
def get_flow(self, flow_name: str) -> Optional[Data]:
"""
Retrieves a flow by its name.
@ -30,10 +30,10 @@ class FlowToolComponent(CustomComponent):
Returns:
Optional[Text]: The flow record if found, None otherwise.
"""
flow_records = self.list_flows()
for flow_record in flow_records:
if flow_record.data["name"] == flow_name:
return flow_record
flow_datas = self.list_flows()
for flow_data in flow_datas:
if flow_data.data["name"] == flow_name:
return flow_data
return None
def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
@ -69,10 +69,10 @@ class FlowToolComponent(CustomComponent):
async def build(self, flow_name: str, name: str, description: str, return_direct: bool = False) -> Tool:
FlowTool.update_forward_refs()
flow_record = self.get_flow(flow_name)
if not flow_record:
flow_data = self.get_flow(flow_name)
if not flow_data:
raise ValueError("Flow not found.")
graph = Graph.from_payload(flow_record.data["data"])
graph = Graph.from_payload(flow_data.data["data"])
inputs = get_flow_inputs(graph)
tool = FlowTool(
name=name,
@ -80,7 +80,7 @@ class FlowToolComponent(CustomComponent):
graph=graph,
return_direct=return_direct,
inputs=inputs,
flow_id=str(flow_record.id),
flow_id=str(flow_data.id),
user_id=str(self._user_id),
)
description_repr = repr(tool.description).strip("'")

View file

@ -1,7 +1,7 @@
from typing import List
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.schema import Data
class ListFlowsComponent(CustomComponent):
@ -15,7 +15,7 @@ class ListFlowsComponent(CustomComponent):
def build(
self,
) -> List[Record]:
) -> List[Data]:
flows = self.list_flows()
self.status = flows
return flows

View file

@ -1,5 +1,5 @@
from langflow.custom import CustomComponent
from langflow.schema import Record
from langflow.schema import Data
class ListenComponent(CustomComponent):
@ -15,7 +15,7 @@ class ListenComponent(CustomComponent):
},
}
def build(self, name: str) -> Record:
def build(self, name: str) -> Data:
state = self.get_state(name)
self.status = state
return state

View file

@ -0,0 +1,36 @@
from langflow.custom import CustomComponent
from langflow.schema import Data
class MergeDataComponent(CustomComponent):
display_name = "Merge Data"
description = "Merges data."
beta: bool = True
field_config = {
"data": {"display_name": "Data"},
}
def build(self, data: list[Data]) -> Data:
if not data:
return Data()
if len(data) == 1:
return data[0]
merged_data = Data()
for value in data:
if merged_data is None:
merged_data = value
else:
merged_data += value
self.status = merged_data
return merged_data
if __name__ == "__main__":
data = [
Data(data={"key1": "value1"}),
Data(data={"key2": "value2"}),
]
component = MergeDataComponent()
result = component.build(data)
print(result)

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