diff --git a/README.md b/README.md
index 47bf2f2a..2db97a31 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
OpenRAG is a comprehensive Retrieval-Augmented Generation platform that enables intelligent document search and AI-powered conversations. Users can upload, process, and query documents through a chat interface backed by large language models and semantic search capabilities. The system utilizes Langflow for document ingestion, retrieval workflows, and intelligent nudges, providing a seamless RAG experience. Built with [Starlette](https://github.com/Kludex/starlette) and [Next.js](https://github.com/vercel/next.js). Powered by [OpenSearch](https://github.com/opensearch-project/OpenSearch), [Langflow](https://github.com/langflow-ai/langflow), and [Docling](https://github.com/docling-project/docling).
-
+
@@ -26,11 +26,17 @@ OpenRAG is a comprehensive Retrieval-Augmented Generation platform that enables
## Quickstart
-Use the OpenRAG Terminal User Interface (TUI) to manage your OpenRAG installation without complex command-line operations.
+To quickly run OpenRAG without creating or modifying any project files, use `uvx`:
-To quickly install and start OpenRAG, run `uvx openrag`.
+```bash
+uvx openrag
+```
+This runs OpenRAG without installing it to your project or globally.
+To run a specific version of OpenRAG, add the version to the command, such as: `uvx --from openrag==0.1.25 openrag`.
-To first set up a project and then install OpenRAG, do the following:
+## Install Python package
+
+To first set up a project and then install the OpenRAG Python package, do the following:
1. Create a new project with a virtual environment using `uv init`.
@@ -42,17 +48,22 @@ To first set up a project and then install OpenRAG, do the following:
The `(venv)` prompt doesn't change, but `uv` commands will automatically use the project's virtual environment.
For more information on virtual environments, see the [uv documentation](https://docs.astral.sh/uv/pip/environments).
-2. Ensure all dependencies are installed and updated in your virtual environment.
+2. Add OpenRAG to your project.
```bash
- uv sync
+ uv add openrag
```
-3. Install and start the OpenRAG TUI.
+ To add a specific version of OpenRAG:
```bash
- uvx openrag
+ uv add openrag==0.1.25
```
-
- To install a specific version of the Langflow package, add the required version to the command, such as `uvx --from openrag==0.1.25 openrag`.
+
+3. Start the OpenRAG TUI.
+ ```bash
+ uv run openrag
+ ```
+
+4. Continue with the [Quickstart](https://docs.openr.ag/quickstart).
For the full TUI installation guide, see [TUI](https://docs.openr.ag/install).
diff --git a/docs/docs/_partial-onboarding.mdx b/docs/docs/_partial-onboarding.mdx
index 6fc5c87e..3f2de8fb 100644
--- a/docs/docs/_partial-onboarding.mdx
+++ b/docs/docs/_partial-onboarding.mdx
@@ -3,7 +3,9 @@ import TabItem from '@theme/TabItem';
## Application onboarding
-The first time you start OpenRAG, whether using the TUI or a `.env` file, you must complete application onboarding.
+The first time you start OpenRAG, whether using the TUI or a `.env` file, it's recommended that you complete application onboarding.
+
+To skip onboarding, click **Skip onboarding**.
Values from onboarding can be changed later in the OpenRAG **Settings** page.
@@ -17,17 +19,19 @@ Choose one LLM provider and complete only those steps:
3. To load 2 sample PDFs, enable **Sample dataset**.
This is recommended, but not required.
4. Click **Complete**.
- 5. Continue with the [Quickstart](/quickstart).
+ 5. To complete the onboarding tasks, click **What is OpenRAG**, and then click **Add a Document**.
+ 6. Continue with the [Quickstart](/quickstart).
- 1. Complete the fields for **watsonx.ai API Endpoint**, **IBM API key**, and **IBM Project ID**.
+ 1. Complete the fields for **watsonx.ai API Endpoint**, **IBM Project ID**, and **IBM API key**.
These values are found in your IBM watsonx deployment.
2. Under **Advanced settings**, select your **Embedding Model** and **Language Model**.
3. To load 2 sample PDFs, enable **Sample dataset**.
This is recommended, but not required.
4. Click **Complete**.
- 5. Continue with the [Quickstart](/quickstart).
+ 5. To complete the onboarding tasks, click **What is OpenRAG**, and then click **Add a Document**.
+ 6. Continue with the [Quickstart](/quickstart).
@@ -42,6 +46,7 @@ Choose one LLM provider and complete only those steps:
3. To load 2 sample PDFs, enable **Sample dataset**.
This is recommended, but not required.
4. Click **Complete**.
- 5. Continue with the [Quickstart](/quickstart).
+ 5. To complete the onboarding tasks, click **What is OpenRAG**, and then click **Add a Document**.
+ 6. Continue with the [Quickstart](/quickstart).
\ No newline at end of file
diff --git a/docs/docs/core-components/agents.mdx b/docs/docs/core-components/agents.mdx
index a7c5ef24..88102a60 100644
--- a/docs/docs/core-components/agents.mdx
+++ b/docs/docs/core-components/agents.mdx
@@ -52,7 +52,7 @@ This filter is the [Knowledge filter](/knowledge#create-knowledge-filters), and
For an example of changing out the agent's language model in OpenRAG, see the [Quickstart](/quickstart#change-components).
-To restore the flow to its initial state, in OpenRAG, click
**Settings**, and then click **Restore Flow**.
+To restore the flow to its initial state, in OpenRAG, click
**Settings**, and then click **Restore Flow**.
OpenRAG warns you that this discards all custom settings. Click **Restore** to restore the flow.
## Additional Langflow functionality
diff --git a/docs/docs/core-components/ingestion.mdx b/docs/docs/core-components/ingestion.mdx
index 2c1bfa1a..f72e9ab0 100644
--- a/docs/docs/core-components/ingestion.mdx
+++ b/docs/docs/core-components/ingestion.mdx
@@ -8,21 +8,23 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import PartialModifyFlows from '@site/docs/_partial-modify-flows.mdx';
-OpenRAG uses [Docling](https://docling-project.github.io/docling/) for its document ingestion pipeline.
+OpenRAG uses [Docling](https://docling-project.github.io/docling/) for document ingestion.
More specifically, OpenRAG uses [Docling Serve](https://github.com/docling-project/docling-serve), which starts a `docling serve` process on your local machine and runs Docling ingestion through an API service.
Docling ingests documents from your local machine or OAuth connectors, splits them into chunks, and stores them as separate, structured documents in the OpenSearch `documents` index.
OpenRAG chose Docling for its support for a wide variety of file formats, high performance, and advanced understanding of tables and images.
-## Docling ingestion settings
+To modify OpenRAG's ingestion settings, including the Docling settings and ingestion flows, click 2" aria-hidden="true"/> **Settings**.
+
+## Knowledge ingestion settings
These settings configure the Docling ingestion parameters.
OpenRAG will warn you if `docling serve` is not running.
To start or stop `docling serve` or any other native services, in the TUI main menu, click **Start Native Services** or **Stop Native Services**.
-**Embedding model** determines which AI model is used to create vector embeddings. The default is `text-embedding-3-small`.
+**Embedding model** determines which AI model is used to create vector embeddings. The default is the OpenAI `text-embedding-3-small` model.
**Chunk size** determines how large each text chunk is in number of characters.
Larger chunks yield more context per chunk, but may include irrelevant information. Smaller chunks yield more precise semantic search, but may lack context.
@@ -32,6 +34,8 @@ The default value of `1000` characters provides a good starting point that balan
Use larger overlap values for documents where context is most important, and use smaller overlap values for simpler documents, or when optimization is most important.
The default value of 200 characters of overlap with a chunk size of 1000 (20% overlap) is suitable for general use cases. Decrease the overlap to 10% for a more efficient pipeline, or increase to 40% for more complex documents.
+**Table Structure** enables Docling's [`DocumentConverter`](https://docling-project.github.io/docling/reference/document_converter/) tool for parsing tables. Instead of treating tables as plain text, tables are output as structured table data with preserved relationships and metadata. **Table Structure** is enabled by default.
+
**OCR** enables or disabled OCR processing when extracting text from images and scanned documents.
OCR is disabled by default. This setting is best suited for processing text-based documents as quickly as possible with Docling's [`DocumentConverter`](https://docling-project.github.io/docling/reference/document_converter/). Images are ignored and not processed.
@@ -41,14 +45,6 @@ If OpenRAG detects that the local machine is running on macOS, OpenRAG uses the
**Picture descriptions** adds image descriptions generated by the [SmolVLM-256M-Instruct](https://huggingface.co/HuggingFaceTB/SmolVLM-Instruct) model to OCR processing. Enabling picture descriptions can slow ingestion performance.
-## Use OpenRAG default ingestion instead of Docling serve
-
-If you want to use OpenRAG's built-in pipeline instead of Docling serve, set `DISABLE_INGEST_WITH_LANGFLOW=true` in [Environment variables](/reference/configuration#document-processing).
-
-The built-in pipeline still uses the Docling processor, but uses it directly without the Docling Serve API.
-
-For more information, see [`processors.py` in the OpenRAG repository](https://github.com/langflow-ai/openrag/blob/main/src/models/processors.py#L58).
-
## Knowledge ingestion flows
[Flows](https://docs.langflow.org/concepts-overview) in Langflow are functional representations of application workflows, with multiple [component](https://docs.langflow.org/concepts-components) nodes connected as single steps in a workflow.
@@ -74,4 +70,12 @@ An additional knowledge ingestion flow is included in OpenRAG, where it is used
The agent calls this component to fetch web content, and the results are ingested into OpenSearch.
For more on using MCP clients in Langflow, see [MCP clients](https://docs.langflow.org/mcp-client).\
-To connect additional MCP servers to the MCP client, see [Connect to MCP servers from your application](https://docs.langflow.org/mcp-tutorial).
\ No newline at end of file
+To connect additional MCP servers to the MCP client, see [Connect to MCP servers from your application](https://docs.langflow.org/mcp-tutorial).
+
+## Use OpenRAG default ingestion instead of Docling serve
+
+If you want to use OpenRAG's built-in pipeline instead of Docling serve, set `DISABLE_INGEST_WITH_LANGFLOW=true` in [Environment variables](/reference/configuration#document-processing).
+
+The built-in pipeline still uses the Docling processor, but uses it directly without the Docling Serve API.
+
+For more information, see [`processors.py` in the OpenRAG repository](https://github.com/langflow-ai/openrag/blob/main/src/models/processors.py#L58).
\ No newline at end of file
diff --git a/docs/docs/core-components/knowledge.mdx b/docs/docs/core-components/knowledge.mdx
index cae39659..588f1f45 100644
--- a/docs/docs/core-components/knowledge.mdx
+++ b/docs/docs/core-components/knowledge.mdx
@@ -31,12 +31,14 @@ The **Knowledge Ingest** flow uses Langflow's [**File** component](https://docs.
The default path to your local folder is mounted from the `./documents` folder in your OpenRAG project directory to the `/app/documents/` directory inside the Docker container. Files added to the host or the container will be visible in both locations. To configure this location, modify the **Documents Paths** variable in either the TUI's [Advanced Setup](/install#setup) menu or in the `.env` used by Docker Compose.
-To load and process a single file from the mapped location, click
**Add Knowledge**, and then click **Add File**.
+To load and process a single file from the mapped location, click **Add Knowledge**, and then click
**File**.
The file is loaded into your OpenSearch database, and appears in the Knowledge page.
-To load and process a directory from the mapped location, click
**Add Knowledge**, and then click **Process Folder**.
+To load and process a directory from the mapped location, click **Add Knowledge**, and then click
**Folder**.
The files are loaded into your OpenSearch database, and appear in the Knowledge page.
+To add files directly to a chat session, click
in the chat input and select the files you want to include. Files added this way are processed and made available to the agent for the current conversation, and are not permanently added to the knowledge base.
+
### Ingest files through OAuth connectors {#oauth-ingestion}
OpenRAG supports Google Drive, OneDrive, and Sharepoint as OAuth connectors for seamless document synchronization.
@@ -61,11 +63,11 @@ If you wish to use another provider, add the secrets to another provider.
1. Stop the Docker deployment.
2. Add the OAuth provider's client and secret key in the `.env` file for Docker Compose.
- ```bash
- GOOGLE_OAUTH_CLIENT_ID='YOUR_OAUTH_CLIENT_ID'
- GOOGLE_OAUTH_CLIENT_SECRET='YOUR_OAUTH_CLIENT_SECRET'
- ```
- 3. Save your `.env`. file.
+ ```bash
+ GOOGLE_OAUTH_CLIENT_ID='YOUR_OAUTH_CLIENT_ID'
+ GOOGLE_OAUTH_CLIENT_SECRET='YOUR_OAUTH_CLIENT_SECRET'
+ ```
+ 3. Save your `.env` file.
4. Start the Docker deployment.
@@ -75,11 +77,11 @@ A successful authentication opens OpenRAG with the required scopes for your conn
To add knowledge from an OAuth-connected storage provider, do the following:
-1. Click
**Add Knowledge**, and then select the storage provider, for example, **Google Drive**.
+1. Click **Add Knowledge**, and then select the storage provider, for example, **Google Drive**.
The **Add Cloud Knowledge** page opens.
-2. To add files or folders from the connected storage, click
**Add Files**.
+2. To add files or folders from the connected storage, click **Add Files**.
Select the files or folders you want and click **Select**.
-You can select multiples.
+You can select multiple files.
3. When your files are selected, click **Ingest Files**.
The ingestion process may take some time, depending on the size of your documents.
4. When ingestion is complete, your documents are available in the Knowledge screen.
@@ -104,11 +106,11 @@ Knowledge filters help agents work more efficiently with large document collecti
To create a knowledge filter, do the following:
-1. Click
**All Knowledge**, and then click
**Create New Filter**.
- The **Create New Knowledge Filter** pane appears.
-2. Enter a **Name** and **Description**, and then click
**Create Filter**.
-A new filter is created with default settings that match everything.
-3. To modify the default filter, click
**All Knowledge**, and then click your new filter to edit it in the **Knowledge Filter** pane.
+1. Click **Knowledge**, and then click
**Knowledge Filters**.
+ The **Knowledge Filter** pane appears.
+2. Enter a **Name** and **Description**, and then click **Create Filter**.
+A new filter is created with default settings that match all documents.
+3. To modify the filter, click
**Knowledge**, and then click your new filter to edit it in the **Knowledge Filter** pane.
The following filter options are configurable.
@@ -116,15 +118,17 @@ A new filter is created with default settings that match everything.
* **Data Sources**: Select specific data sources or folders to include.
* **Document Types**: Filter by file type.
* **Owners**: Filter by who uploaded the documents.
- * **Sources**: Filter by connector types, such as local upload or Google Drive.
- * **Result Limit**: Set maximum number of results. The default is `10`.
+ * **Connectors**: Filter by connector types, such as local upload or Google Drive.
+ * **Response Limit**: Set maximum number of results. The default is `10`.
* **Score Threshold**: Set minimum relevance score. The default score is `0`.
-4. When you're done editing the filter, click
**Save Configuration**.
+4. When you're done editing the filter, click **Update Filter**.
-5. To apply the filter to OpenRAG globally, click
**All Knowledge**, and then select the filter to apply.
+5. To apply the filter to OpenRAG globally, click
**Knowledge**, and then select the filter to apply. One filter can be enabled at a time.
- To apply the filter to a single chat session, in the
**Chat** window, click **@**, and then select the filter to apply.
+ To apply the filter to a single chat session, in the
**Chat** window, click
, and then select the filter to apply.
+
+ To delete the filter, in the **Knowledge Filter** pane, click **Delete Filter**.
## OpenRAG default configuration
diff --git a/docs/docs/get-started/docker.mdx b/docs/docs/get-started/docker.mdx
index a43825bd..24658a0b 100644
--- a/docs/docs/get-started/docker.mdx
+++ b/docs/docs/get-started/docker.mdx
@@ -48,15 +48,28 @@ To install OpenRAG with Docker Compose, do the following:
touch .env
```
-4. The Docker Compose files are populated with the values from your .env. The following values must be set:
+4. The Docker Compose files are populated with the values from your `.env` file. The following values must be set:
```bash
OPENSEARCH_PASSWORD=your_secure_password
OPENAI_API_KEY=your_openai_api_key
- LANGFLOW_SUPERUSER=admin
- LANGFLOW_SUPERUSER_PASSWORD=your_langflow_password
LANGFLOW_SECRET_KEY=your_secret_key
```
+
+ `OPENSEARCH_PASSWORD` can be automatically generated when using the TUI, but for a Docker Compose installation, you can set it manually instead. To generate an OpenSearch admin password, see the [OpenSearch documentation](https://docs.opensearch.org/latest/security/configuration/demo-configuration/#setting-up-a-custom-admin-password).
+
+ The `OPENAI_API_KEY` is found in your OpenAI account.
+
+ `LANGFLOW_SECRET_KEY` is automatically generated when using the TUI, and Langflow will also auto-generate it if not set. For more information, see the [Langflow documentation](https://docs.langflow.org/api-keys-and-authentication#langflow-secret-key).
+
+ The following Langflow configuration values are optional but important to consider:
+
+ ```bash
+ LANGFLOW_SUPERUSER=admin
+ LANGFLOW_SUPERUSER_PASSWORD=your_langflow_password
+ ```
+
+ `LANGFLOW_SUPERUSER` defaults to `admin`. You can omit it or set it to a different username. `LANGFLOW_SUPERUSER_PASSWORD` is optional. If omitted, Langflow runs in [autologin mode](https://docs.langflow.org/api-keys-and-authentication#langflow-auto-login) with no password required. If set, Langflow requires password authentication.
For more information on configuring OpenRAG with environment variables, see [Environment variables](/reference/configuration).
diff --git a/docs/docs/get-started/install.mdx b/docs/docs/get-started/install.mdx
index d8fa252f..94b7d22f 100644
--- a/docs/docs/get-started/install.mdx
+++ b/docs/docs/get-started/install.mdx
@@ -7,9 +7,9 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import PartialOnboarding from '@site/docs/_partial-onboarding.mdx';
-[Install the OpenRAG Python wheel](#install-python-wheel), and then run the [OpenRAG Terminal User Interface(TUI)](#setup) to start your OpenRAG deployment with a guided setup process.
+[Install OpenRAG](#install) and then run the [OpenRAG Terminal User Interface(TUI)](#setup) to start your OpenRAG deployment with a guided setup process.
-The OpenRAG Terminal User Interface (TUI) allows you to set up, configure, and monitor your OpenRAG deployment directly from the terminal, on any operating system.
+The OpenRAG Terminal User Interface (TUI) allows you to set up, configure, and monitor your OpenRAG deployment directly from the terminal.

@@ -28,13 +28,13 @@ If you prefer running Podman or Docker containers and manually editing `.env` fi
- Create an [OpenAI API key](https://platform.openai.com/api-keys). This key is **required** to start OpenRAG, but you can choose a different model provider during [Application Onboarding](#application-onboarding).
- Optional: Install GPU support with an NVIDIA GPU, [CUDA](https://docs.nvidia.com/cuda/) support, and compatible NVIDIA drivers on the OpenRAG host machine. If you don't have GPU capabilities, OpenRAG provides an alternate CPU-only deployment.
-## Install the OpenRAG Python wheel {#install-python-wheel}
+## Install OpenRAG {#install}
-The OpenRAG wheel installs the Terminal User Interface (TUI) for configuring and running OpenRAG.
+:::note Windows users
+To use OpenRAG on Windows, use [WSL (Windows Subsystem for Linux)](https://learn.microsoft.com/en-us/windows/wsl/install).
+:::
-To quickly install and start OpenRAG, run `uvx openrag`.
-
-To first set up a project and then install OpenRAG, do the following:
+To set up a project and install OpenRAG as a dependency, do the following:
1. Create a new project with a virtual environment using `uv init`.
@@ -46,20 +46,23 @@ To first set up a project and then install OpenRAG, do the following:
The `(venv)` prompt doesn't change, but `uv` commands will automatically use the project's virtual environment.
For more information on virtual environments, see the [uv documentation](https://docs.astral.sh/uv/pip/environments).
-2. Ensure all dependencies are installed and updated in your virtual environment.
+2. Add OpenRAG to your project.
```bash
- uv sync
+ uv add openrag
```
-3. Install and start the OpenRAG TUI.
+ To add a specific version of OpenRAG:
```bash
- uvx openrag
+ uv add openrag==0.1.25
+ ```
+
+3. Start the OpenRAG TUI.
+ ```bash
+ uv run openrag
```
-
- To install a specific version of the Langflow package, add the required version to the command, such as `uvx --from openrag==0.1.25 openrag`.
- Install a local wheel without uvx
+ Install a local wheel
If you downloaded the OpenRAG wheel to your local machine, follow these steps:
@@ -102,10 +105,14 @@ If the TUI detects OAuth credentials, it enforces the **Advanced Setup** path.
1. To install OpenRAG with **Basic Setup**, click **Basic Setup** or press 1.
2. Click **Generate Passwords** to generate passwords for OpenSearch and Langflow.
+
+ The OpenSearch password is required. The Langflow admin password is optional.
+ If no Langflow admin password is generated, Langflow runs in [autologin mode](https://docs.langflow.org/api-keys-and-authentication#langflow-auto-login) with no password required.
+
3. Paste your OpenAI API key in the OpenAI API key field.
4. Click **Save Configuration**.
Your passwords are saved in the `.env` file used to start OpenRAG.
- 5. To start OpenRAG, click **Start Container Services**.
+ 5. To start OpenRAG, click **Start All Services**.
Startup pulls container images and runs them, so it can take some time.
When startup is complete, the TUI displays the following:
```bash
@@ -119,6 +126,10 @@ If the TUI detects OAuth credentials, it enforces the **Advanced Setup** path.
1. To install OpenRAG with **Advanced Setup**, click **Advanced Setup** or press 2.
2. Click **Generate Passwords** to generate passwords for OpenSearch and Langflow.
+
+ The OpenSearch password is required. The Langflow admin password is optional.
+ If no Langflow admin password is generated, Langflow runs in [autologin mode](https://docs.langflow.org/api-keys-and-authentication#langflow-auto-login) with no password required.
+
3. Paste your OpenAI API key in the OpenAI API key field.
4. Add your client and secret values for Google or Microsoft OAuth.
These values can be found with your OAuth provider.
@@ -127,14 +138,14 @@ If the TUI detects OAuth credentials, it enforces the **Advanced Setup** path.
These are the URLs your OAuth provider will redirect back to after user sign-in.
Register these redirect values with your OAuth provider as they are presented in the TUI.
6. Click **Save Configuration**.
- 7. To start OpenRAG, click **Start Container Services**.
+ 7. To start OpenRAG, click **Start All Services**.
Startup pulls container images and runs them, so it can take some time.
When startup is complete, the TUI displays the following:
```bash
Services started successfully
Command completed successfully
```
- 8. To open the OpenRAG application, click **Open App**, press 6, or navigate to `http://localhost:3000`.
+ 8. To open the OpenRAG application, click **Open App**.
You are presented with your provider's OAuth sign-in screen.
After sign-in, you are redirected to the redirect URI.
@@ -155,13 +166,21 @@ If the TUI detects OAuth credentials, it enforces the **Advanced Setup** path.
-## Manage OpenRAG containers with the TUI
+## Close the OpenRAG TUI
+
+To close the OpenRAG TUI, press q.
+The OpenRAG containers will continue to be served until the containers are stopped.
+For more information, see [Manage OpenRAG containers with the TUI ](#tui-container-management).
+
+To start the TUI again, run `uv run openrag`.
+
+## Manage OpenRAG containers with the TUI {#tui-container-management}
After installation, the TUI can deploy, manage, and upgrade your OpenRAG containers.
-### Start container services
+### Start all services
-Click **Start Container Services** to start the OpenRAG containers.
+Click **Start All Services** to start the OpenRAG containers.
The TUI automatically detects your container runtime, and then checks if your machine has compatible GPU support by checking for `CUDA`, `NVIDIA_SMI`, and Docker/Podman runtime support. This check determines which Docker Compose file OpenRAG uses.
The TUI then pulls the images and deploys the containers with the following command.
```bash
@@ -170,15 +189,6 @@ docker compose up -d
If images are missing, the TUI runs `docker compose pull`, then runs `docker compose up -d`.
-### Start native services
-
-A "native" service in OpenRAG refers to a service run natively on your machine, and not within a container.
-The `docling serve` process is a native service in OpenRAG, because it's a document processing service that is run on your local machine, and controlled separately from the containers.
-
-To start or stop `docling serve` or any other native services, in the TUI main menu, click **Start Native Services** or **Stop Native Services**.
-
-To view the status, port, or PID of a native service, in the TUI main menu, click [Status](#status).
-
### Status
The **Status** menu displays information on your container deployment.
@@ -207,6 +217,15 @@ When the first command is complete, OpenRAG removes any additional Docker object
docker system prune -f
```
+### Native services status
+
+A _native service_ in OpenRAG refers to a service run locally on your machine, and not within a container.
+The `docling serve` process is a native service in OpenRAG, because it's a document processing service that is run on your local machine, and controlled separately from the containers.
+
+To start or stop `docling serve` or any other native services, in the TUI Status menu, click **Stop** or **Restart**.
+
+To view the status, port, or PID of a native service, in the TUI main menu, click [Status](#status).
+
## Diagnostics
The **Diagnostics** menu provides health monitoring for your container runtimes and monitoring of your OpenSearch security.
\ No newline at end of file
diff --git a/docs/docs/get-started/quickstart.mdx b/docs/docs/get-started/quickstart.mdx
index 80259617..92ed71c8 100644
--- a/docs/docs/get-started/quickstart.mdx
+++ b/docs/docs/get-started/quickstart.mdx
@@ -7,7 +7,7 @@ import Icon from "@site/src/components/icon/icon";
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
-Get started with OpenRAG by loading your knowledge, swapping out your language model, and then chatting with the OpenRAG API.
+Get started with OpenRAG by loading your knowledge, swapping out your language model, and then chatting with the Langflow API.
## Prerequisites
@@ -17,20 +17,20 @@ Get started with OpenRAG by loading your knowledge, swapping out your language m
1. In OpenRAG, click **Chat**.
The chat is powered by the OpenRAG OpenSearch Agent.
- For more information, see [Langflow Agents](/agents).
+ For more information, see [Langflow in OpenRAG](/agents).
2. Ask `What documents are available to you?`
The agent responds with a message summarizing the documents that OpenRAG loads by default.
Knowledge is stored in OpenSearch.
- For more information, see [Knowledge](/knowledge).
+ For more information, see [OpenSearch in OpenRAG](/knowledge).
3. To confirm the agent is correct about the default knowledge, click **Knowledge**.
The **Knowledge** page lists the documents OpenRAG has ingested into the OpenSearch vector database.
- Click on a document to display the chunks derived from splitting the default documents into the vector database.
-4. To add documents to your knowledge base, click **Add Knowledge**.
- * Select **Add File** to add a single file from your local machine.
- * Select **Process Folder** to process an entire folder of documents from your local machine.
+ Click on a document to display the chunks derived from splitting the default documents into the OpenSearch vector database.
+4. To add documents to your knowledge base, click **Add Knowledge**.
+ * Select **File** to add a single file from your local machine.
+ * Select **Folder** to process an entire folder of documents from your local machine. The default directory is `/documents` in your OpenRAG directory.
* Select your cloud storage provider to add knowledge from an OAuth-connected storage provider. For more information, see [OAuth ingestion](/knowledge#oauth-ingestion).
5. Return to the Chat window and ask a question about your loaded data.
- For example, with a manual about a PC tablet loaded, ask `How do I connect this device to WiFI?`
+ For example, with a manual about a PC tablet loaded, ask `How do I connect this device to WiFi?`
The agent responds with a message indicating it now has your knowledge as context for answering questions.
6. Click **Function Call: search_documents (tool_call)**.
This log describes how the agent uses tools.
@@ -44,8 +44,12 @@ In this example, you'll try a different LLM to demonstrate how the Agent's respo
1. To edit the Agent's behavior, click **Edit in Langflow**.
You can more quickly access the **Language Model** and **Agent Instructions** fields in this page, but for illustration purposes, navigate to the Langflow visual builder.
+To revert the flow to its initial state, click **Restore flow**.
2. OpenRAG warns you that you're entering Langflow. Click **Proceed**.
-The OpenRAG OpenSearch Agent flow appears in a new browser window.
+
+ If Langflow requests login information, enter the `LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD` from the `.env` file in your OpenRAG directory.
+
+ The OpenRAG OpenSearch Agent flow appears in a new browser window.

3. Find the **Language Model** component, and then change the **Model Name** field to a different OpenAI model.
diff --git a/docs/docs/get-started/what-is-openrag.mdx b/docs/docs/get-started/what-is-openrag.mdx
index 129d2df9..ee227db9 100644
--- a/docs/docs/get-started/what-is-openrag.mdx
+++ b/docs/docs/get-started/what-is-openrag.mdx
@@ -7,7 +7,7 @@ OpenRAG is an open-source package for building agentic RAG systems that integrat
OpenRAG connects and amplifies three popular, proven open-source projects into one powerful platform:
-* [Langflow](https://docs.langflow.org): Langflow is a popular tool for building and deploying AI agents and MCP servers. It supports all major LLMs, vector databases, and a growing library of AI tools.
+* [Langflow](https://docs.langflow.org): Langflow is a versatile tool for building and deploying AI agents and MCP servers. It supports all major LLMs, vector databases, and a growing library of AI tools.
* [OpenSearch](https://docs.opensearch.org/latest/): OpenSearch is a community-driven, Apache 2.0-licensed open source search and analytics suite that makes it easy to ingest, search, visualize, and analyze data.
diff --git a/docs/docs/support/troubleshoot.mdx b/docs/docs/support/troubleshoot.mdx
index 9eceee63..67b927e0 100644
--- a/docs/docs/support/troubleshoot.mdx
+++ b/docs/docs/support/troubleshoot.mdx
@@ -38,6 +38,26 @@ This example increases the machine size to 8 GB of RAM, which should be sufficie
Ensure ports 3000, 7860, 8000, 9200, 5601 are available.
+## OCR ingestion fails (easyocr not installed)
+
+If Docling ingestion fails with an OCR-related error and mentions `easyocr` is missing, this is likely due to a stale `uv` cache.
+
+`easyocr` is already included as a dependency in OpenRAG's `pyproject.toml`. Project-managed installations using `uv sync` and `uv run` always sync dependencies directly from your `pyproject.toml`, so they should have `easyocr` installed.
+
+If you're running OpenRAG with `uvx openrag`, `uvx` creates a cached, ephemeral environment that doesn't modify your project. This cache may become stale.
+
+On macOS, this cache directory is typically a user cache directory such as `/Users/USER_NAME/.cache/uv`.
+1. To clear the uv cache, run:
+ ```bash
+ uv cache clean
+ ```
+2. Start OpenRAG:
+ ```bash
+ uvx openrag
+ ```
+
+If you do not need OCR, you can disable OCR-based processing in your ingestion settings to avoid requiring `easyocr`.
+
## Langflow container already exists
If you are running other versions of Langflow containers on your machine, you may encounter an issue where Docker or Podman thinks Langflow is already up.
diff --git a/docs/pdf/openrag-documentation.pdf b/docs/pdf/openrag-documentation.pdf
index c66ed6dc..8009577b 100644
Binary files a/docs/pdf/openrag-documentation.pdf and b/docs/pdf/openrag-documentation.pdf differ
diff --git a/flows/components/ollama_embedding.json b/flows/components/ollama_embedding.json
index 24974b46..e8f4789c 100644
--- a/flows/components/ollama_embedding.json
+++ b/flows/components/ollama_embedding.json
@@ -31,7 +31,7 @@
"list": false,
"show": true,
"multiline": true,
- "value": "from typing import Any\nfrom urllib.parse import urljoin\n\nimport httpx\nfrom langchain_ollama import OllamaEmbeddings\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.ollama_constants import OLLAMA_EMBEDDING_MODELS, URL_LIST\nfrom lfx.field_typing import Embeddings\nfrom lfx.io import DropdownInput, MessageTextInput, Output\n\nHTTP_STATUS_OK = 200\n\n\nclass OllamaEmbeddingsComponent(LCModelComponent):\n display_name: str = \"Ollama Embeddings\"\n description: str = \"Generate embeddings using Ollama models.\"\n documentation = \"https://python.langchain.com/docs/integrations/text_embedding/ollama\"\n icon = \"Ollama\"\n name = \"OllamaEmbeddings\"\n\n inputs = [\n DropdownInput(\n name=\"model_name\",\n display_name=\"Ollama Model\",\n value=\"\",\n options=[],\n real_time_refresh=True,\n refresh_button=True,\n combobox=True,\n required=True,\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Ollama Base URL\",\n value=\"\",\n required=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Embeddings\", name=\"embeddings\", method=\"build_embeddings\"),\n ]\n\n def build_embeddings(self) -> Embeddings:\n try:\n output = OllamaEmbeddings(model=self.model_name, base_url=self.base_url)\n except Exception as e:\n msg = (\n \"Unable to connect to the Ollama API. \",\n \"Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.\",\n )\n raise ValueError(msg) from e\n return output\n\n async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):\n if field_name in {\"base_url\", \"model_name\"} and not await self.is_valid_ollama_url(field_value):\n # Check if any URL in the list is valid\n valid_url = \"\"\n for url in URL_LIST:\n if await self.is_valid_ollama_url(url):\n valid_url = url\n break\n build_config[\"base_url\"][\"value\"] = valid_url\n if field_name in {\"model_name\", \"base_url\", \"tool_model_enabled\"}:\n if await self.is_valid_ollama_url(self.base_url):\n build_config[\"model_name\"][\"options\"] = await self.get_model(self.base_url)\n elif await self.is_valid_ollama_url(build_config[\"base_url\"].get(\"value\", \"\")):\n build_config[\"model_name\"][\"options\"] = await self.get_model(build_config[\"base_url\"].get(\"value\", \"\"))\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n return build_config\n\n async def get_model(self, base_url_value: str) -> list[str]:\n \"\"\"Get the model names from Ollama.\"\"\"\n model_ids = []\n try:\n url = urljoin(base_url_value, \"/api/tags\")\n async with httpx.AsyncClient() as client:\n response = await client.get(url)\n response.raise_for_status()\n data = response.json()\n\n model_ids = [model[\"name\"] for model in data.get(\"models\", [])]\n # this to ensure that not embedding models are included.\n # not even the base models since models can have 1b 2b etc\n # handles cases when embeddings models have tags like :latest - etc.\n model_ids = [\n model\n for model in model_ids\n if any(model.startswith(f\"{embedding_model}\") for embedding_model in OLLAMA_EMBEDDING_MODELS)\n ]\n\n except (ImportError, ValueError, httpx.RequestError) as e:\n msg = \"Could not get model names from Ollama.\"\n raise ValueError(msg) from e\n\n return model_ids\n\n async def is_valid_ollama_url(self, url: str) -> bool:\n try:\n async with httpx.AsyncClient() as client:\n return (await client.get(f\"{url}/api/tags\")).status_code == HTTP_STATUS_OK\n except httpx.RequestError:\n return False\n",
+ "value": "from typing import Any\nfrom urllib.parse import urljoin\n\nimport httpx\nfrom langchain_ollama import OllamaEmbeddings\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.ollama_constants import OLLAMA_EMBEDDING_MODELS\nfrom lfx.field_typing import Embeddings\nfrom lfx.io import DropdownInput, MessageTextInput, Output\nfrom lfx.utils.util import transform_localhost_url\n\nHTTP_STATUS_OK = 200\n\n\nclass OllamaEmbeddingsComponent(LCModelComponent):\n display_name: str = \"Ollama Embeddings\"\n description: str = \"Generate embeddings using Ollama models.\"\n documentation = \"https://python.langchain.com/docs/integrations/text_embedding/ollama\"\n icon = \"Ollama\"\n name = \"OllamaEmbeddings\"\n\n inputs = [\n DropdownInput(\n name=\"model_name\",\n display_name=\"Ollama Model\",\n value=\"\",\n options=[],\n real_time_refresh=True,\n refresh_button=True,\n combobox=True,\n required=True,\n ),\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Ollama Base URL\",\n value=\"\",\n required=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Embeddings\", name=\"embeddings\", method=\"build_embeddings\"),\n ]\n\n def build_embeddings(self) -> Embeddings:\n transformed_base_url = transform_localhost_url(self.base_url)\n try:\n output = OllamaEmbeddings(model=self.model_name, base_url=transformed_base_url)\n except Exception as e:\n msg = (\n \"Unable to connect to the Ollama API. \",\n \"Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.\",\n )\n raise ValueError(msg) from e\n return output\n\n async def update_build_config(self, build_config: dict, _field_value: Any, field_name: str | None = None):\n if field_name in {\"base_url\", \"model_name\"} and not await self.is_valid_ollama_url(self.base_url):\n msg = \"Ollama is not running on the provided base URL. Please start Ollama and try again.\"\n raise ValueError(msg)\n if field_name in {\"model_name\", \"base_url\", \"tool_model_enabled\"}:\n if await self.is_valid_ollama_url(self.base_url):\n build_config[\"model_name\"][\"options\"] = await self.get_model(self.base_url)\n else:\n build_config[\"model_name\"][\"options\"] = []\n\n return build_config\n\n async def get_model(self, base_url_value: str) -> list[str]:\n \"\"\"Get the model names from Ollama.\"\"\"\n model_ids = []\n try:\n base_url_value = transform_localhost_url(base_url_value)\n url = urljoin(base_url_value, \"/api/tags\")\n async with httpx.AsyncClient() as client:\n response = await client.get(url)\n response.raise_for_status()\n data = response.json()\n\n model_ids = [model[\"name\"] for model in data.get(\"models\", [])]\n # this to ensure that not embedding models are included.\n # not even the base models since models can have 1b 2b etc\n # handles cases when embeddings models have tags like :latest - etc.\n model_ids = [\n model\n for model in model_ids\n if any(model.startswith(f\"{embedding_model}\") for embedding_model in OLLAMA_EMBEDDING_MODELS)\n ]\n\n except (ImportError, ValueError, httpx.RequestError) as e:\n msg = \"Could not get model names from Ollama.\"\n raise ValueError(msg) from e\n\n return model_ids\n\n async def is_valid_ollama_url(self, url: str) -> bool:\n try:\n async with httpx.AsyncClient() as client:\n url = transform_localhost_url(url)\n return (await client.get(f\"{url}/api/tags\")).status_code == HTTP_STATUS_OK\n except httpx.RequestError:\n return False\n",
"fileTypes": [],
"file_path": "",
"password": false,
@@ -99,44 +99,27 @@
"legacy": false,
"edited": false,
"metadata": {
- "keywords": [
- "model",
- "llm",
- "language model",
- "large language model"
- ],
+ "keywords": ["model", "llm", "language model", "large language model"],
"module": "lfx.components.ollama.ollama_embeddings.OllamaEmbeddingsComponent",
- "code_hash": "c41821735548",
+ "code_hash": "9ef83e250bee",
"dependencies": {
"total_dependencies": 3,
"dependencies": [
- {
- "name": "httpx",
- "version": "0.28.1"
- },
- {
- "name": "langchain_ollama",
- "version": "0.2.1"
- },
- {
- "name": "lfx",
- "version": null
- }
+ { "name": "httpx", "version": "0.28.1" },
+ { "name": "langchain_ollama", "version": "0.2.1" },
+ { "name": "lfx", "version": "0.1.12.dev32" }
]
}
},
"tool_mode": false,
- "last_updated": "2025-09-29T18:40:10.242Z",
+ "last_updated": "2025-10-29T19:54:23.774Z",
"official": false
},
"showNode": true,
"type": "OllamaEmbeddings",
- "id": "OllamaEmbeddings-vnNn8"
- },
- "id": "OllamaEmbeddings-vnNn8",
- "position": {
- "x": 0,
- "y": 0
+ "id": "OllamaEmbeddings-3JO8z"
},
+ "id": "OllamaEmbeddings-3JO8z",
+ "position": { "x": 0, "y": 0 },
"type": "genericNode"
-}
\ No newline at end of file
+}
diff --git a/flows/components/ollama_llm.json b/flows/components/ollama_llm.json
index 4aa6ee62..9dc83439 100644
--- a/flows/components/ollama_llm.json
+++ b/flows/components/ollama_llm.json
@@ -1,606 +1,579 @@
{
- "data": {
- "node": {
- "template": {
- "_type": "Component",
- "base_url": {
- "tool_mode": false,
- "trace_as_input": true,
- "trace_as_metadata": true,
- "load_from_db": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "base_url",
- "value": "OLLAMA_BASE_URL",
- "display_name": "Base URL",
- "advanced": false,
- "input_types": [
- "Message"
- ],
- "dynamic": false,
- "info": "Endpoint of the Ollama API.",
- "real_time_refresh": true,
- "title_case": false,
- "type": "str",
- "_input_type": "MessageTextInput"
- },
- "code": {
- "type": "code",
- "required": true,
- "placeholder": "",
- "list": false,
- "show": true,
- "multiline": true,
- "value": "import asyncio\nfrom typing import Any\nfrom urllib.parse import urljoin\n\nimport httpx\nfrom langchain_ollama import ChatOllama\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.ollama_constants import URL_LIST\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SliderInput\nfrom lfx.log.logger import logger\n\nHTTP_STATUS_OK = 200\n\n\nclass ChatOllamaComponent(LCModelComponent):\n display_name = \"Ollama\"\n description = \"Generate text using Ollama Local LLMs.\"\n icon = \"Ollama\"\n name = \"OllamaModel\"\n\n # Define constants for JSON keys\n JSON_MODELS_KEY = \"models\"\n JSON_NAME_KEY = \"name\"\n JSON_CAPABILITIES_KEY = \"capabilities\"\n DESIRED_CAPABILITY = \"completion\"\n TOOL_CALLING_CAPABILITY = \"tools\"\n\n inputs = [\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"Endpoint of the Ollama API.\",\n value=\"\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=[],\n info=\"Refer to https://ollama.com/library for more models.\",\n refresh_button=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n MessageTextInput(\n name=\"format\", display_name=\"Format\", info=\"Specify the format of the output (e.g., json).\", advanced=True\n ),\n DictInput(name=\"metadata\", display_name=\"Metadata\", info=\"Metadata to add to the run trace.\", advanced=True),\n DropdownInput(\n name=\"mirostat\",\n display_name=\"Mirostat\",\n options=[\"Disabled\", \"Mirostat\", \"Mirostat 2.0\"],\n info=\"Enable/disable Mirostat sampling for controlling perplexity.\",\n value=\"Disabled\",\n advanced=True,\n real_time_refresh=True,\n ),\n FloatInput(\n name=\"mirostat_eta\",\n display_name=\"Mirostat Eta\",\n info=\"Learning rate for Mirostat algorithm. (Default: 0.1)\",\n advanced=True,\n ),\n FloatInput(\n name=\"mirostat_tau\",\n display_name=\"Mirostat Tau\",\n info=\"Controls the balance between coherence and diversity of the output. (Default: 5.0)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_ctx\",\n display_name=\"Context Window Size\",\n info=\"Size of the context window for generating tokens. (Default: 2048)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_gpu\",\n display_name=\"Number of GPUs\",\n info=\"Number of GPUs to use for computation. (Default: 1 on macOS, 0 to disable)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_thread\",\n display_name=\"Number of Threads\",\n info=\"Number of threads to use during computation. (Default: detected for optimal performance)\",\n advanced=True,\n ),\n IntInput(\n name=\"repeat_last_n\",\n display_name=\"Repeat Last N\",\n info=\"How far back the model looks to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)\",\n advanced=True,\n ),\n FloatInput(\n name=\"repeat_penalty\",\n display_name=\"Repeat Penalty\",\n info=\"Penalty for repetitions in generated text. (Default: 1.1)\",\n advanced=True,\n ),\n FloatInput(name=\"tfs_z\", display_name=\"TFS Z\", info=\"Tail free sampling value. (Default: 1)\", advanced=True),\n IntInput(name=\"timeout\", display_name=\"Timeout\", info=\"Timeout for the request stream.\", advanced=True),\n IntInput(\n name=\"top_k\", display_name=\"Top K\", info=\"Limits token selection to top K. (Default: 40)\", advanced=True\n ),\n FloatInput(name=\"top_p\", display_name=\"Top P\", info=\"Works together with top-k. (Default: 0.9)\", advanced=True),\n BoolInput(name=\"verbose\", display_name=\"Verbose\", info=\"Whether to print out response text.\", advanced=True),\n MessageTextInput(\n name=\"tags\",\n display_name=\"Tags\",\n info=\"Comma-separated list of tags to add to the run trace.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"stop_tokens\",\n display_name=\"Stop Tokens\",\n info=\"Comma-separated list of tokens to signal the model to stop generating text.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"system\", display_name=\"System\", info=\"System to use for generating text.\", advanced=True\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Tool Model Enabled\",\n info=\"Whether to enable tool calling in the model.\",\n value=True,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"template\", display_name=\"Template\", info=\"Template to use for generating text.\", advanced=True\n ),\n *LCModelComponent.get_base_inputs(),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n # Mapping mirostat settings to their corresponding values\n mirostat_options = {\"Mirostat\": 1, \"Mirostat 2.0\": 2}\n\n # Default to 0 for 'Disabled'\n mirostat_value = mirostat_options.get(self.mirostat, 0)\n\n # Set mirostat_eta and mirostat_tau to None if mirostat is disabled\n if mirostat_value == 0:\n mirostat_eta = None\n mirostat_tau = None\n else:\n mirostat_eta = self.mirostat_eta\n mirostat_tau = self.mirostat_tau\n\n # Mapping system settings to their corresponding values\n llm_params = {\n \"base_url\": self.base_url,\n \"model\": self.model_name,\n \"mirostat\": mirostat_value,\n \"format\": self.format,\n \"metadata\": self.metadata,\n \"tags\": self.tags.split(\",\") if self.tags else None,\n \"mirostat_eta\": mirostat_eta,\n \"mirostat_tau\": mirostat_tau,\n \"num_ctx\": self.num_ctx or None,\n \"num_gpu\": self.num_gpu or None,\n \"num_thread\": self.num_thread or None,\n \"repeat_last_n\": self.repeat_last_n or None,\n \"repeat_penalty\": self.repeat_penalty or None,\n \"temperature\": self.temperature or None,\n \"stop\": self.stop_tokens.split(\",\") if self.stop_tokens else None,\n \"system\": self.system,\n \"tfs_z\": self.tfs_z or None,\n \"timeout\": self.timeout or None,\n \"top_k\": self.top_k or None,\n \"top_p\": self.top_p or None,\n \"verbose\": self.verbose,\n \"template\": self.template,\n }\n\n # Remove parameters with None values\n llm_params = {k: v for k, v in llm_params.items() if v is not None}\n\n try:\n output = ChatOllama(**llm_params)\n except Exception as e:\n msg = (\n \"Unable to connect to the Ollama API. \",\n \"Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.\",\n )\n raise ValueError(msg) from e\n\n return output\n\n async def is_valid_ollama_url(self, url: str) -> bool:\n try:\n async with httpx.AsyncClient() as client:\n return (await client.get(urljoin(url, \"api/tags\"))).status_code == HTTP_STATUS_OK\n except httpx.RequestError:\n return False\n\n async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):\n if field_name == \"mirostat\":\n if field_value == \"Disabled\":\n build_config[\"mirostat_eta\"][\"advanced\"] = True\n build_config[\"mirostat_tau\"][\"advanced\"] = True\n build_config[\"mirostat_eta\"][\"value\"] = None\n build_config[\"mirostat_tau\"][\"value\"] = None\n\n else:\n build_config[\"mirostat_eta\"][\"advanced\"] = False\n build_config[\"mirostat_tau\"][\"advanced\"] = False\n\n if field_value == \"Mirostat 2.0\":\n build_config[\"mirostat_eta\"][\"value\"] = 0.2\n build_config[\"mirostat_tau\"][\"value\"] = 10\n else:\n build_config[\"mirostat_eta\"][\"value\"] = 0.1\n build_config[\"mirostat_tau\"][\"value\"] = 5\n\n if field_name in {\"base_url\", \"model_name\"}:\n if build_config[\"base_url\"].get(\"load_from_db\", False):\n base_url_value = await self.get_variables(build_config[\"base_url\"].get(\"value\", \"\"), \"base_url\")\n else:\n base_url_value = build_config[\"base_url\"].get(\"value\", \"\")\n\n if not await self.is_valid_ollama_url(base_url_value):\n # Check if any URL in the list is valid\n valid_url = \"\"\n check_urls = URL_LIST\n if self.base_url:\n check_urls = [self.base_url, *URL_LIST]\n for url in check_urls:\n if await self.is_valid_ollama_url(url):\n valid_url = url\n break\n if valid_url != \"\":\n build_config[\"base_url\"][\"value\"] = valid_url\n else:\n msg = \"No valid Ollama URL found.\"\n raise ValueError(msg)\n if field_name in {\"model_name\", \"base_url\", \"tool_model_enabled\"}:\n if await self.is_valid_ollama_url(self.base_url):\n tool_model_enabled = build_config[\"tool_model_enabled\"].get(\"value\", False) or self.tool_model_enabled\n build_config[\"model_name\"][\"options\"] = await self.get_models(\n self.base_url, tool_model_enabled=tool_model_enabled\n )\n elif await self.is_valid_ollama_url(build_config[\"base_url\"].get(\"value\", \"\")):\n tool_model_enabled = build_config[\"tool_model_enabled\"].get(\"value\", False) or self.tool_model_enabled\n build_config[\"model_name\"][\"options\"] = await self.get_models(\n build_config[\"base_url\"].get(\"value\", \"\"), tool_model_enabled=tool_model_enabled\n )\n else:\n build_config[\"model_name\"][\"options\"] = []\n if field_name == \"keep_alive_flag\":\n if field_value == \"Keep\":\n build_config[\"keep_alive\"][\"value\"] = \"-1\"\n build_config[\"keep_alive\"][\"advanced\"] = True\n elif field_value == \"Immediately\":\n build_config[\"keep_alive\"][\"value\"] = \"0\"\n build_config[\"keep_alive\"][\"advanced\"] = True\n else:\n build_config[\"keep_alive\"][\"advanced\"] = False\n\n return build_config\n\n async def get_models(self, base_url_value: str, *, tool_model_enabled: bool | None = None) -> list[str]:\n \"\"\"Fetches a list of models from the Ollama API that do not have the \"embedding\" capability.\n\n Args:\n base_url_value (str): The base URL of the Ollama API.\n tool_model_enabled (bool | None, optional): If True, filters the models further to include\n only those that support tool calling. Defaults to None.\n\n Returns:\n list[str]: A list of model names that do not have the \"embedding\" capability. If\n `tool_model_enabled` is True, only models supporting tool calling are included.\n\n Raises:\n ValueError: If there is an issue with the API request or response, or if the model\n names cannot be retrieved.\n \"\"\"\n try:\n # Normalize the base URL to avoid the repeated \"/\" at the end\n base_url = base_url_value.rstrip(\"/\") + \"/\"\n\n # Ollama REST API to return models\n tags_url = urljoin(base_url, \"api/tags\")\n\n # Ollama REST API to return model capabilities\n show_url = urljoin(base_url, \"api/show\")\n\n async with httpx.AsyncClient() as client:\n # Fetch available models\n tags_response = await client.get(tags_url)\n tags_response.raise_for_status()\n models = tags_response.json()\n if asyncio.iscoroutine(models):\n models = await models\n await logger.adebug(f\"Available models: {models}\")\n\n # Filter models that are NOT embedding models\n model_ids = []\n for model in models[self.JSON_MODELS_KEY]:\n model_name = model[self.JSON_NAME_KEY]\n await logger.adebug(f\"Checking model: {model_name}\")\n\n payload = {\"model\": model_name}\n show_response = await client.post(show_url, json=payload)\n show_response.raise_for_status()\n json_data = show_response.json()\n if asyncio.iscoroutine(json_data):\n json_data = await json_data\n capabilities = json_data.get(self.JSON_CAPABILITIES_KEY, [])\n await logger.adebug(f\"Model: {model_name}, Capabilities: {capabilities}\")\n\n if self.DESIRED_CAPABILITY in capabilities and (\n not tool_model_enabled or self.TOOL_CALLING_CAPABILITY in capabilities\n ):\n model_ids.append(model_name)\n\n except (httpx.RequestError, ValueError) as e:\n msg = \"Could not get model names from Ollama.\"\n raise ValueError(msg) from e\n\n return model_ids\n",
- "fileTypes": [],
- "file_path": "",
- "password": false,
- "name": "code",
- "advanced": true,
- "dynamic": true,
- "info": "",
- "load_from_db": false,
- "title_case": false
- },
- "format": {
- "tool_mode": false,
- "trace_as_input": true,
- "trace_as_metadata": true,
- "load_from_db": false,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "format",
- "value": "",
- "display_name": "Format",
- "advanced": true,
- "input_types": [
- "Message"
- ],
- "dynamic": false,
- "info": "Specify the format of the output (e.g., json).",
- "title_case": false,
- "type": "str",
- "_input_type": "MessageTextInput"
- },
- "input_value": {
- "trace_as_input": true,
- "tool_mode": false,
- "trace_as_metadata": true,
- "load_from_db": false,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "input_value",
- "value": "",
- "display_name": "Input",
- "advanced": false,
- "input_types": [
- "Message"
- ],
- "dynamic": false,
- "info": "",
- "title_case": false,
- "type": "str",
- "_input_type": "MessageInput"
- },
- "metadata": {
- "tool_mode": false,
- "trace_as_input": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "metadata",
- "value": {},
- "display_name": "Metadata",
- "advanced": true,
- "dynamic": false,
- "info": "Metadata to add to the run trace.",
- "title_case": false,
- "type": "dict",
- "_input_type": "DictInput"
- },
- "mirostat": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "options": [
- "Disabled",
- "Mirostat",
- "Mirostat 2.0"
- ],
- "options_metadata": [],
- "combobox": false,
- "dialog_inputs": {},
- "toggle": false,
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "mirostat",
- "value": "Disabled",
- "display_name": "Mirostat",
- "advanced": true,
- "dynamic": false,
- "info": "Enable/disable Mirostat sampling for controlling perplexity.",
- "real_time_refresh": true,
- "title_case": false,
- "external_options": {},
- "type": "str",
- "_input_type": "DropdownInput"
- },
- "mirostat_eta": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "mirostat_eta",
- "value": "",
- "display_name": "Mirostat Eta",
- "advanced": true,
- "dynamic": false,
- "info": "Learning rate for Mirostat algorithm. (Default: 0.1)",
- "title_case": false,
- "type": "float",
- "_input_type": "FloatInput"
- },
- "mirostat_tau": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "mirostat_tau",
- "value": "",
- "display_name": "Mirostat Tau",
- "advanced": true,
- "dynamic": false,
- "info": "Controls the balance between coherence and diversity of the output. (Default: 5.0)",
- "title_case": false,
- "type": "float",
- "_input_type": "FloatInput"
- },
- "model_name": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "options": [],
- "options_metadata": [],
- "combobox": false,
- "dialog_inputs": {},
- "toggle": false,
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "model_name",
- "value": "",
- "display_name": "Model Name",
- "advanced": false,
- "dynamic": false,
- "info": "Refer to https://ollama.com/library for more models.",
- "real_time_refresh": true,
- "refresh_button": true,
- "title_case": false,
- "external_options": {},
- "type": "str",
- "_input_type": "DropdownInput"
- },
- "num_ctx": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "num_ctx",
- "value": "",
- "display_name": "Context Window Size",
- "advanced": true,
- "dynamic": false,
- "info": "Size of the context window for generating tokens. (Default: 2048)",
- "title_case": false,
- "type": "int",
- "_input_type": "IntInput"
- },
- "num_gpu": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "num_gpu",
- "value": "",
- "display_name": "Number of GPUs",
- "advanced": true,
- "dynamic": false,
- "info": "Number of GPUs to use for computation. (Default: 1 on macOS, 0 to disable)",
- "title_case": false,
- "type": "int",
- "_input_type": "IntInput"
- },
- "num_thread": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "num_thread",
- "value": "",
- "display_name": "Number of Threads",
- "advanced": true,
- "dynamic": false,
- "info": "Number of threads to use during computation. (Default: detected for optimal performance)",
- "title_case": false,
- "type": "int",
- "_input_type": "IntInput"
- },
- "repeat_last_n": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "repeat_last_n",
- "value": "",
- "display_name": "Repeat Last N",
- "advanced": true,
- "dynamic": false,
- "info": "How far back the model looks to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)",
- "title_case": false,
- "type": "int",
- "_input_type": "IntInput"
- },
- "repeat_penalty": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "repeat_penalty",
- "value": "",
- "display_name": "Repeat Penalty",
- "advanced": true,
- "dynamic": false,
- "info": "Penalty for repetitions in generated text. (Default: 1.1)",
- "title_case": false,
- "type": "float",
- "_input_type": "FloatInput"
- },
- "stop_tokens": {
- "tool_mode": false,
- "trace_as_input": true,
- "trace_as_metadata": true,
- "load_from_db": false,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "stop_tokens",
- "value": "",
- "display_name": "Stop Tokens",
- "advanced": true,
- "input_types": [
- "Message"
- ],
- "dynamic": false,
- "info": "Comma-separated list of tokens to signal the model to stop generating text.",
- "title_case": false,
- "type": "str",
- "_input_type": "MessageTextInput"
- },
- "stream": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "stream",
- "value": false,
- "display_name": "Stream",
- "advanced": true,
- "dynamic": false,
- "info": "Stream the response from the model. Streaming works only in Chat.",
- "title_case": false,
- "type": "bool",
- "_input_type": "BoolInput"
- },
- "system": {
- "tool_mode": false,
- "trace_as_input": true,
- "trace_as_metadata": true,
- "load_from_db": false,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "system",
- "value": "",
- "display_name": "System",
- "advanced": true,
- "input_types": [
- "Message"
- ],
- "dynamic": false,
- "info": "System to use for generating text.",
- "title_case": false,
- "type": "str",
- "_input_type": "MessageTextInput"
- },
- "system_message": {
- "tool_mode": false,
- "trace_as_input": true,
- "multiline": true,
- "trace_as_metadata": true,
- "load_from_db": false,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "system_message",
- "value": "",
- "display_name": "System Message",
- "advanced": false,
- "input_types": [
- "Message"
- ],
- "dynamic": false,
- "info": "System message to pass to the model.",
- "title_case": false,
- "copy_field": false,
- "type": "str",
- "_input_type": "MultilineInput"
- },
- "tags": {
- "tool_mode": false,
- "trace_as_input": true,
- "trace_as_metadata": true,
- "load_from_db": false,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "tags",
- "value": "",
- "display_name": "Tags",
- "advanced": true,
- "input_types": [
- "Message"
- ],
- "dynamic": false,
- "info": "Comma-separated list of tags to add to the run trace.",
- "title_case": false,
- "type": "str",
- "_input_type": "MessageTextInput"
- },
- "temperature": {
- "tool_mode": false,
- "min_label": "",
- "max_label": "",
- "min_label_icon": "",
- "max_label_icon": "",
- "slider_buttons": false,
- "slider_buttons_options": [],
- "slider_input": false,
- "range_spec": {
- "step_type": "float",
- "min": 0,
- "max": 1,
- "step": 0.01
- },
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "temperature",
- "value": 0.1,
- "display_name": "Temperature",
- "advanced": true,
- "dynamic": false,
- "info": "",
- "title_case": false,
- "type": "slider",
- "_input_type": "SliderInput"
- },
- "template": {
- "tool_mode": false,
- "trace_as_input": true,
- "trace_as_metadata": true,
- "load_from_db": false,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "template",
- "value": "",
- "display_name": "Template",
- "advanced": true,
- "input_types": [
- "Message"
- ],
- "dynamic": false,
- "info": "Template to use for generating text.",
- "title_case": false,
- "type": "str",
- "_input_type": "MessageTextInput"
- },
- "tfs_z": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "tfs_z",
- "value": "",
- "display_name": "TFS Z",
- "advanced": true,
- "dynamic": false,
- "info": "Tail free sampling value. (Default: 1)",
- "title_case": false,
- "type": "float",
- "_input_type": "FloatInput"
- },
- "timeout": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "timeout",
- "value": "",
- "display_name": "Timeout",
- "advanced": true,
- "dynamic": false,
- "info": "Timeout for the request stream.",
- "title_case": false,
- "type": "int",
- "_input_type": "IntInput"
- },
- "tool_model_enabled": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "tool_model_enabled",
- "value": true,
- "display_name": "Tool Model Enabled",
- "advanced": false,
- "dynamic": false,
- "info": "Whether to enable tool calling in the model.",
- "real_time_refresh": true,
- "title_case": false,
- "type": "bool",
- "_input_type": "BoolInput"
- },
- "top_k": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "top_k",
- "value": "",
- "display_name": "Top K",
- "advanced": true,
- "dynamic": false,
- "info": "Limits token selection to top K. (Default: 40)",
- "title_case": false,
- "type": "int",
- "_input_type": "IntInput"
- },
- "top_p": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "top_p",
- "value": "",
- "display_name": "Top P",
- "advanced": true,
- "dynamic": false,
- "info": "Works together with top-k. (Default: 0.9)",
- "title_case": false,
- "type": "float",
- "_input_type": "FloatInput"
- },
- "verbose": {
- "tool_mode": false,
- "trace_as_metadata": true,
- "list": false,
- "list_add_label": "Add More",
- "required": false,
- "placeholder": "",
- "show": true,
- "name": "verbose",
- "value": false,
- "display_name": "Verbose",
- "advanced": true,
- "dynamic": false,
- "info": "Whether to print out response text.",
- "title_case": false,
- "type": "bool",
- "_input_type": "BoolInput"
- }
- },
- "description": "Generate text using Ollama Local LLMs.",
- "icon": "Ollama",
- "base_classes": [
- "LanguageModel",
- "Message"
- ],
- "display_name": "Ollama",
- "documentation": "",
- "minimized": false,
- "custom_fields": {},
- "output_types": [],
- "pinned": false,
- "conditional_paths": [],
- "frozen": false,
- "outputs": [
- {
- "types": [
- "Message"
- ],
- "name": "text_output",
- "display_name": "Model Response",
- "method": "text_response",
- "value": "__UNDEFINED__",
- "cache": true,
- "required_inputs": null,
- "allows_loop": false,
- "group_outputs": false,
- "options": null,
- "tool_mode": true
- },
- {
- "types": [
- "LanguageModel"
- ],
- "selected": "LanguageModel",
- "name": "model_output",
- "display_name": "Language Model",
- "method": "build_model",
- "value": "__UNDEFINED__",
- "cache": true,
- "required_inputs": null,
- "allows_loop": false,
+ "data": {
+ "node": {
+ "template": {
+ "_type": "Component",
+ "base_url": {
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "base_url",
+ "value": "OLLAMA_BASE_URL",
+ "display_name": "Base URL",
+ "advanced": false,
+ "input_types": ["Message"],
+ "dynamic": false,
+ "info": "Endpoint of the Ollama API.",
+ "real_time_refresh": true,
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "code": {
+ "type": "code",
+ "required": true,
+ "placeholder": "",
+ "list": false,
+ "show": true,
+ "multiline": true,
+ "value": "import asyncio\nfrom typing import Any\nfrom urllib.parse import urljoin\n\nimport httpx\nfrom langchain_ollama import ChatOllama\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.utils.util import transform_localhost_url\n\nHTTP_STATUS_OK = 200\n\n\nclass ChatOllamaComponent(LCModelComponent):\n display_name = \"Ollama\"\n description = \"Generate text using Ollama Local LLMs.\"\n icon = \"Ollama\"\n name = \"OllamaModel\"\n\n # Define constants for JSON keys\n JSON_MODELS_KEY = \"models\"\n JSON_NAME_KEY = \"name\"\n JSON_CAPABILITIES_KEY = \"capabilities\"\n DESIRED_CAPABILITY = \"completion\"\n TOOL_CALLING_CAPABILITY = \"tools\"\n\n inputs = [\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"Endpoint of the Ollama API.\",\n value=\"\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=[],\n info=\"Refer to https://ollama.com/library for more models.\",\n refresh_button=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n MessageTextInput(\n name=\"format\", display_name=\"Format\", info=\"Specify the format of the output (e.g., json).\", advanced=True\n ),\n DictInput(name=\"metadata\", display_name=\"Metadata\", info=\"Metadata to add to the run trace.\", advanced=True),\n DropdownInput(\n name=\"mirostat\",\n display_name=\"Mirostat\",\n options=[\"Disabled\", \"Mirostat\", \"Mirostat 2.0\"],\n info=\"Enable/disable Mirostat sampling for controlling perplexity.\",\n value=\"Disabled\",\n advanced=True,\n real_time_refresh=True,\n ),\n FloatInput(\n name=\"mirostat_eta\",\n display_name=\"Mirostat Eta\",\n info=\"Learning rate for Mirostat algorithm. (Default: 0.1)\",\n advanced=True,\n ),\n FloatInput(\n name=\"mirostat_tau\",\n display_name=\"Mirostat Tau\",\n info=\"Controls the balance between coherence and diversity of the output. (Default: 5.0)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_ctx\",\n display_name=\"Context Window Size\",\n info=\"Size of the context window for generating tokens. (Default: 2048)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_gpu\",\n display_name=\"Number of GPUs\",\n info=\"Number of GPUs to use for computation. (Default: 1 on macOS, 0 to disable)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_thread\",\n display_name=\"Number of Threads\",\n info=\"Number of threads to use during computation. (Default: detected for optimal performance)\",\n advanced=True,\n ),\n IntInput(\n name=\"repeat_last_n\",\n display_name=\"Repeat Last N\",\n info=\"How far back the model looks to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)\",\n advanced=True,\n ),\n FloatInput(\n name=\"repeat_penalty\",\n display_name=\"Repeat Penalty\",\n info=\"Penalty for repetitions in generated text. (Default: 1.1)\",\n advanced=True,\n ),\n FloatInput(name=\"tfs_z\", display_name=\"TFS Z\", info=\"Tail free sampling value. (Default: 1)\", advanced=True),\n IntInput(name=\"timeout\", display_name=\"Timeout\", info=\"Timeout for the request stream.\", advanced=True),\n IntInput(\n name=\"top_k\", display_name=\"Top K\", info=\"Limits token selection to top K. (Default: 40)\", advanced=True\n ),\n FloatInput(name=\"top_p\", display_name=\"Top P\", info=\"Works together with top-k. (Default: 0.9)\", advanced=True),\n BoolInput(name=\"verbose\", display_name=\"Verbose\", info=\"Whether to print out response text.\", advanced=True),\n MessageTextInput(\n name=\"tags\",\n display_name=\"Tags\",\n info=\"Comma-separated list of tags to add to the run trace.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"stop_tokens\",\n display_name=\"Stop Tokens\",\n info=\"Comma-separated list of tokens to signal the model to stop generating text.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"system\", display_name=\"System\", info=\"System to use for generating text.\", advanced=True\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Tool Model Enabled\",\n info=\"Whether to enable tool calling in the model.\",\n value=True,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"template\", display_name=\"Template\", info=\"Template to use for generating text.\", advanced=True\n ),\n *LCModelComponent.get_base_inputs(),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n # Mapping mirostat settings to their corresponding values\n mirostat_options = {\"Mirostat\": 1, \"Mirostat 2.0\": 2}\n\n # Default to 0 for 'Disabled'\n mirostat_value = mirostat_options.get(self.mirostat, 0)\n\n # Set mirostat_eta and mirostat_tau to None if mirostat is disabled\n if mirostat_value == 0:\n mirostat_eta = None\n mirostat_tau = None\n else:\n mirostat_eta = self.mirostat_eta\n mirostat_tau = self.mirostat_tau\n\n transformed_base_url = transform_localhost_url(self.base_url)\n # Mapping system settings to their corresponding values\n llm_params = {\n \"base_url\": transformed_base_url,\n \"model\": self.model_name,\n \"mirostat\": mirostat_value,\n \"format\": self.format,\n \"metadata\": self.metadata,\n \"tags\": self.tags.split(\",\") if self.tags else None,\n \"mirostat_eta\": mirostat_eta,\n \"mirostat_tau\": mirostat_tau,\n \"num_ctx\": self.num_ctx or None,\n \"num_gpu\": self.num_gpu or None,\n \"num_thread\": self.num_thread or None,\n \"repeat_last_n\": self.repeat_last_n or None,\n \"repeat_penalty\": self.repeat_penalty or None,\n \"temperature\": self.temperature or None,\n \"stop\": self.stop_tokens.split(\",\") if self.stop_tokens else None,\n \"system\": self.system,\n \"tfs_z\": self.tfs_z or None,\n \"timeout\": self.timeout or None,\n \"top_k\": self.top_k or None,\n \"top_p\": self.top_p or None,\n \"verbose\": self.verbose,\n \"template\": self.template,\n }\n\n # Remove parameters with None values\n llm_params = {k: v for k, v in llm_params.items() if v is not None}\n\n try:\n output = ChatOllama(**llm_params)\n except Exception as e:\n msg = (\n \"Unable to connect to the Ollama API. \",\n \"Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.\",\n )\n raise ValueError(msg) from e\n\n return output\n\n async def is_valid_ollama_url(self, url: str) -> bool:\n try:\n async with httpx.AsyncClient() as client:\n url = transform_localhost_url(url)\n return (await client.get(urljoin(url, \"api/tags\"))).status_code == HTTP_STATUS_OK\n except httpx.RequestError:\n return False\n\n async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):\n if field_name == \"mirostat\":\n if field_value == \"Disabled\":\n build_config[\"mirostat_eta\"][\"advanced\"] = True\n build_config[\"mirostat_tau\"][\"advanced\"] = True\n build_config[\"mirostat_eta\"][\"value\"] = None\n build_config[\"mirostat_tau\"][\"value\"] = None\n\n else:\n build_config[\"mirostat_eta\"][\"advanced\"] = False\n build_config[\"mirostat_tau\"][\"advanced\"] = False\n\n if field_value == \"Mirostat 2.0\":\n build_config[\"mirostat_eta\"][\"value\"] = 0.2\n build_config[\"mirostat_tau\"][\"value\"] = 10\n else:\n build_config[\"mirostat_eta\"][\"value\"] = 0.1\n build_config[\"mirostat_tau\"][\"value\"] = 5\n\n if field_name in {\"base_url\", \"model_name\"} and not await self.is_valid_ollama_url(self.base_url):\n msg = \"Ollama is not running on the provided base URL. Please start Ollama and try again.\"\n raise ValueError(msg)\n if field_name in {\"model_name\", \"base_url\", \"tool_model_enabled\"}:\n if await self.is_valid_ollama_url(self.base_url):\n tool_model_enabled = build_config[\"tool_model_enabled\"].get(\"value\", False) or self.tool_model_enabled\n build_config[\"model_name\"][\"options\"] = await self.get_models(\n self.base_url, tool_model_enabled=tool_model_enabled\n )\n else:\n build_config[\"model_name\"][\"options\"] = []\n if field_name == \"keep_alive_flag\":\n if field_value == \"Keep\":\n build_config[\"keep_alive\"][\"value\"] = \"-1\"\n build_config[\"keep_alive\"][\"advanced\"] = True\n elif field_value == \"Immediately\":\n build_config[\"keep_alive\"][\"value\"] = \"0\"\n build_config[\"keep_alive\"][\"advanced\"] = True\n else:\n build_config[\"keep_alive\"][\"advanced\"] = False\n\n return build_config\n\n async def get_models(self, base_url_value: str, *, tool_model_enabled: bool | None = None) -> list[str]:\n \"\"\"Fetches a list of models from the Ollama API that do not have the \"embedding\" capability.\n\n Args:\n base_url_value (str): The base URL of the Ollama API.\n tool_model_enabled (bool | None, optional): If True, filters the models further to include\n only those that support tool calling. Defaults to None.\n\n Returns:\n list[str]: A list of model names that do not have the \"embedding\" capability. If\n `tool_model_enabled` is True, only models supporting tool calling are included.\n\n Raises:\n ValueError: If there is an issue with the API request or response, or if the model\n names cannot be retrieved.\n \"\"\"\n try:\n # Normalize the base URL to avoid the repeated \"/\" at the end\n base_url = base_url_value.rstrip(\"/\") + \"/\"\n base_url = transform_localhost_url(base_url)\n\n # Ollama REST API to return models\n tags_url = urljoin(base_url, \"api/tags\")\n\n # Ollama REST API to return model capabilities\n show_url = urljoin(base_url, \"api/show\")\n\n async with httpx.AsyncClient() as client:\n # Fetch available models\n tags_response = await client.get(tags_url)\n tags_response.raise_for_status()\n models = tags_response.json()\n if asyncio.iscoroutine(models):\n models = await models\n await logger.adebug(f\"Available models: {models}\")\n\n # Filter models that are NOT embedding models\n model_ids = []\n for model in models[self.JSON_MODELS_KEY]:\n model_name = model[self.JSON_NAME_KEY]\n await logger.adebug(f\"Checking model: {model_name}\")\n\n payload = {\"model\": model_name}\n show_response = await client.post(show_url, json=payload)\n show_response.raise_for_status()\n json_data = show_response.json()\n if asyncio.iscoroutine(json_data):\n json_data = await json_data\n capabilities = json_data.get(self.JSON_CAPABILITIES_KEY, [])\n await logger.adebug(f\"Model: {model_name}, Capabilities: {capabilities}\")\n\n if self.DESIRED_CAPABILITY in capabilities and (\n not tool_model_enabled or self.TOOL_CALLING_CAPABILITY in capabilities\n ):\n model_ids.append(model_name)\n\n except (httpx.RequestError, ValueError) as e:\n msg = \"Could not get model names from Ollama.\"\n raise ValueError(msg) from e\n\n return model_ids\n",
+ "fileTypes": [],
+ "file_path": "",
+ "password": false,
+ "name": "code",
+ "advanced": true,
+ "dynamic": true,
+ "info": "",
+ "load_from_db": false,
+ "title_case": false
+ },
+ "format": {
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "format",
+ "value": "",
+ "display_name": "Format",
+ "advanced": true,
+ "input_types": ["Message"],
+ "dynamic": false,
+ "info": "Specify the format of the output (e.g., json).",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "input_value": {
+ "trace_as_input": true,
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "input_value",
+ "value": "",
+ "display_name": "Input",
+ "advanced": false,
+ "input_types": ["Message"],
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageInput"
+ },
+ "metadata": {
+ "tool_mode": false,
+ "trace_as_input": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "metadata",
+ "value": {},
+ "display_name": "Metadata",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Metadata to add to the run trace.",
+ "title_case": false,
+ "type": "dict",
+ "_input_type": "DictInput"
+ },
+ "mirostat": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "options": ["Disabled", "Mirostat", "Mirostat 2.0"],
+ "options_metadata": [],
+ "combobox": false,
+ "dialog_inputs": {},
+ "toggle": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "mirostat",
+ "value": "Disabled",
+ "display_name": "Mirostat",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Enable/disable Mirostat sampling for controlling perplexity.",
+ "real_time_refresh": true,
+ "title_case": false,
+ "external_options": {},
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "mirostat_eta": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "mirostat_eta",
+ "value": "",
+ "display_name": "Mirostat Eta",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Learning rate for Mirostat algorithm. (Default: 0.1)",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ },
+ "mirostat_tau": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "mirostat_tau",
+ "value": "",
+ "display_name": "Mirostat Tau",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Controls the balance between coherence and diversity of the output. (Default: 5.0)",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ },
+ "model_name": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "options": ["gpt-oss:20b", "qwen3:4b"],
+ "options_metadata": [],
+ "combobox": false,
+ "dialog_inputs": {},
+ "toggle": false,
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "model_name",
+ "value": "",
+ "display_name": "Model Name",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Refer to https://ollama.com/library for more models.",
+ "real_time_refresh": true,
+ "refresh_button": true,
+ "title_case": false,
+ "external_options": {},
+ "type": "str",
+ "_input_type": "DropdownInput"
+ },
+ "num_ctx": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "num_ctx",
+ "value": "",
+ "display_name": "Context Window Size",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Size of the context window for generating tokens. (Default: 2048)",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "num_gpu": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "num_gpu",
+ "value": "",
+ "display_name": "Number of GPUs",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Number of GPUs to use for computation. (Default: 1 on macOS, 0 to disable)",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "num_thread": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "num_thread",
+ "value": "",
+ "display_name": "Number of Threads",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Number of threads to use during computation. (Default: detected for optimal performance)",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "repeat_last_n": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "repeat_last_n",
+ "value": "",
+ "display_name": "Repeat Last N",
+ "advanced": true,
+ "dynamic": false,
+ "info": "How far back the model looks to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "repeat_penalty": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "repeat_penalty",
+ "value": "",
+ "display_name": "Repeat Penalty",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Penalty for repetitions in generated text. (Default: 1.1)",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ },
+ "stop_tokens": {
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "stop_tokens",
+ "value": "",
+ "display_name": "Stop Tokens",
+ "advanced": true,
+ "input_types": ["Message"],
+ "dynamic": false,
+ "info": "Comma-separated list of tokens to signal the model to stop generating text.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "stream": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "stream",
+ "value": false,
+ "display_name": "Stream",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Stream the response from the model. Streaming works only in Chat.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "system": {
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "system",
+ "value": "",
+ "display_name": "System",
+ "advanced": true,
+ "input_types": ["Message"],
+ "dynamic": false,
+ "info": "System to use for generating text.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "system_message": {
+ "tool_mode": false,
+ "trace_as_input": true,
+ "multiline": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "system_message",
+ "value": "",
+ "display_name": "System Message",
+ "advanced": false,
+ "input_types": ["Message"],
+ "dynamic": false,
+ "info": "System message to pass to the model.",
+ "title_case": false,
+ "copy_field": false,
+ "type": "str",
+ "_input_type": "MultilineInput"
+ },
+ "tags": {
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "tags",
+ "value": "",
+ "display_name": "Tags",
+ "advanced": true,
+ "input_types": ["Message"],
+ "dynamic": false,
+ "info": "Comma-separated list of tags to add to the run trace.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "temperature": {
+ "tool_mode": false,
+ "min_label": "",
+ "max_label": "",
+ "min_label_icon": "",
+ "max_label_icon": "",
+ "slider_buttons": false,
+ "slider_buttons_options": [],
+ "slider_input": false,
+ "range_spec": {
+ "step_type": "float",
+ "min": 0,
+ "max": 1,
+ "step": 0.01
+ },
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "temperature",
+ "value": 0.1,
+ "display_name": "Temperature",
+ "advanced": true,
+ "dynamic": false,
+ "info": "",
+ "title_case": false,
+ "type": "slider",
+ "_input_type": "SliderInput"
+ },
+ "template": {
+ "tool_mode": false,
+ "trace_as_input": true,
+ "trace_as_metadata": true,
+ "load_from_db": false,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "template",
+ "value": "",
+ "display_name": "Template",
+ "advanced": true,
+ "input_types": ["Message"],
+ "dynamic": false,
+ "info": "Template to use for generating text.",
+ "title_case": false,
+ "type": "str",
+ "_input_type": "MessageTextInput"
+ },
+ "tfs_z": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "tfs_z",
+ "value": "",
+ "display_name": "TFS Z",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Tail free sampling value. (Default: 1)",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ },
+ "timeout": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "timeout",
+ "value": "",
+ "display_name": "Timeout",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Timeout for the request stream.",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "tool_model_enabled": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "tool_model_enabled",
+ "value": true,
+ "display_name": "Tool Model Enabled",
+ "advanced": false,
+ "dynamic": false,
+ "info": "Whether to enable tool calling in the model.",
+ "real_time_refresh": true,
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ },
+ "top_k": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "top_k",
+ "value": "",
+ "display_name": "Top K",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Limits token selection to top K. (Default: 40)",
+ "title_case": false,
+ "type": "int",
+ "_input_type": "IntInput"
+ },
+ "top_p": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "top_p",
+ "value": "",
+ "display_name": "Top P",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Works together with top-k. (Default: 0.9)",
+ "title_case": false,
+ "type": "float",
+ "_input_type": "FloatInput"
+ },
+ "verbose": {
+ "tool_mode": false,
+ "trace_as_metadata": true,
+ "list": false,
+ "list_add_label": "Add More",
+ "required": false,
+ "placeholder": "",
+ "show": true,
+ "name": "verbose",
+ "value": false,
+ "display_name": "Verbose",
+ "advanced": true,
+ "dynamic": false,
+ "info": "Whether to print out response text.",
+ "title_case": false,
+ "type": "bool",
+ "_input_type": "BoolInput"
+ }
+ },
+ "description": "Generate text using Ollama Local LLMs.",
+ "icon": "Ollama",
+ "base_classes": ["LanguageModel", "Message"],
+ "display_name": "Ollama",
+ "documentation": "",
+ "minimized": false,
+ "custom_fields": {},
+ "output_types": [],
+ "pinned": false,
+ "conditional_paths": [],
+ "frozen": false,
+ "outputs": [
+ {
+ "types": ["Message"],
+ "name": "text_output",
+ "display_name": "Model Response",
+ "method": "text_response",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "required_inputs": null,
+ "allows_loop": false,
+ "group_outputs": false,
+ "options": null,
+ "tool_mode": true
+ },
+ {
+ "types": ["LanguageModel"],
+ "selected": "LanguageModel",
+ "name": "model_output",
+ "display_name": "Language Model",
+ "method": "build_model",
+ "value": "__UNDEFINED__",
+ "cache": true,
+ "required_inputs": null,
+ "allows_loop": false,
"group_outputs": false,
"options": null,
"tool_mode": true
@@ -638,45 +611,28 @@
"legacy": false,
"edited": false,
"metadata": {
- "keywords": [
- "model",
- "llm",
- "language model",
- "large language model"
- ],
+ "keywords": ["model", "llm", "language model", "large language model"],
"module": "lfx.components.ollama.ollama.ChatOllamaComponent",
- "code_hash": "54de3b5da388",
+ "code_hash": "79649147b972",
"dependencies": {
"total_dependencies": 3,
"dependencies": [
- {
- "name": "httpx",
- "version": "0.28.1"
- },
- {
- "name": "langchain_ollama",
- "version": "0.2.1"
- },
- {
- "name": "lfx",
- "version": null
- }
+ { "name": "httpx", "version": "0.28.1" },
+ { "name": "langchain_ollama", "version": "0.2.1" },
+ { "name": "lfx", "version": "0.1.12.dev32" }
]
}
},
"tool_mode": false,
- "last_updated": "2025-09-29T18:39:30.798Z",
+ "last_updated": "2025-10-29T20:37:13.232Z",
"official": false
},
"showNode": true,
"type": "OllamaModel",
- "id": "OllamaModel-8Re0J",
+ "id": "OllamaModel-0hTPe",
"selected_output": "model_output"
},
- "id": "OllamaModel-8Re0J",
- "position": {
- "x": 0,
- "y": 0
- },
+ "id": "OllamaModel-0hTPe",
+ "position": { "x": 0, "y": 0 },
"type": "genericNode"
-}
\ No newline at end of file
+}
diff --git a/flows/components/ollama_llm_text.json b/flows/components/ollama_llm_text.json
index 9b2b5482..eaf40f2a 100644
--- a/flows/components/ollama_llm_text.json
+++ b/flows/components/ollama_llm_text.json
@@ -17,9 +17,7 @@
"value": "OLLAMA_BASE_URL",
"display_name": "Base URL",
"advanced": false,
- "input_types": [
- "Message"
- ],
+ "input_types": ["Message"],
"dynamic": false,
"info": "Endpoint of the Ollama API.",
"real_time_refresh": true,
@@ -34,7 +32,7 @@
"list": false,
"show": true,
"multiline": true,
- "value": "import asyncio\nfrom typing import Any\nfrom urllib.parse import urljoin\n\nimport httpx\nfrom langchain_ollama import ChatOllama\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.base.models.ollama_constants import URL_LIST\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SliderInput\nfrom lfx.log.logger import logger\n\nHTTP_STATUS_OK = 200\n\n\nclass ChatOllamaComponent(LCModelComponent):\n display_name = \"Ollama\"\n description = \"Generate text using Ollama Local LLMs.\"\n icon = \"Ollama\"\n name = \"OllamaModel\"\n\n # Define constants for JSON keys\n JSON_MODELS_KEY = \"models\"\n JSON_NAME_KEY = \"name\"\n JSON_CAPABILITIES_KEY = \"capabilities\"\n DESIRED_CAPABILITY = \"completion\"\n TOOL_CALLING_CAPABILITY = \"tools\"\n\n inputs = [\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"Endpoint of the Ollama API.\",\n value=\"\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=[],\n info=\"Refer to https://ollama.com/library for more models.\",\n refresh_button=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n MessageTextInput(\n name=\"format\", display_name=\"Format\", info=\"Specify the format of the output (e.g., json).\", advanced=True\n ),\n DictInput(name=\"metadata\", display_name=\"Metadata\", info=\"Metadata to add to the run trace.\", advanced=True),\n DropdownInput(\n name=\"mirostat\",\n display_name=\"Mirostat\",\n options=[\"Disabled\", \"Mirostat\", \"Mirostat 2.0\"],\n info=\"Enable/disable Mirostat sampling for controlling perplexity.\",\n value=\"Disabled\",\n advanced=True,\n real_time_refresh=True,\n ),\n FloatInput(\n name=\"mirostat_eta\",\n display_name=\"Mirostat Eta\",\n info=\"Learning rate for Mirostat algorithm. (Default: 0.1)\",\n advanced=True,\n ),\n FloatInput(\n name=\"mirostat_tau\",\n display_name=\"Mirostat Tau\",\n info=\"Controls the balance between coherence and diversity of the output. (Default: 5.0)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_ctx\",\n display_name=\"Context Window Size\",\n info=\"Size of the context window for generating tokens. (Default: 2048)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_gpu\",\n display_name=\"Number of GPUs\",\n info=\"Number of GPUs to use for computation. (Default: 1 on macOS, 0 to disable)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_thread\",\n display_name=\"Number of Threads\",\n info=\"Number of threads to use during computation. (Default: detected for optimal performance)\",\n advanced=True,\n ),\n IntInput(\n name=\"repeat_last_n\",\n display_name=\"Repeat Last N\",\n info=\"How far back the model looks to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)\",\n advanced=True,\n ),\n FloatInput(\n name=\"repeat_penalty\",\n display_name=\"Repeat Penalty\",\n info=\"Penalty for repetitions in generated text. (Default: 1.1)\",\n advanced=True,\n ),\n FloatInput(name=\"tfs_z\", display_name=\"TFS Z\", info=\"Tail free sampling value. (Default: 1)\", advanced=True),\n IntInput(name=\"timeout\", display_name=\"Timeout\", info=\"Timeout for the request stream.\", advanced=True),\n IntInput(\n name=\"top_k\", display_name=\"Top K\", info=\"Limits token selection to top K. (Default: 40)\", advanced=True\n ),\n FloatInput(name=\"top_p\", display_name=\"Top P\", info=\"Works together with top-k. (Default: 0.9)\", advanced=True),\n BoolInput(name=\"verbose\", display_name=\"Verbose\", info=\"Whether to print out response text.\", advanced=True),\n MessageTextInput(\n name=\"tags\",\n display_name=\"Tags\",\n info=\"Comma-separated list of tags to add to the run trace.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"stop_tokens\",\n display_name=\"Stop Tokens\",\n info=\"Comma-separated list of tokens to signal the model to stop generating text.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"system\", display_name=\"System\", info=\"System to use for generating text.\", advanced=True\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Tool Model Enabled\",\n info=\"Whether to enable tool calling in the model.\",\n value=True,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"template\", display_name=\"Template\", info=\"Template to use for generating text.\", advanced=True\n ),\n *LCModelComponent.get_base_inputs(),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n # Mapping mirostat settings to their corresponding values\n mirostat_options = {\"Mirostat\": 1, \"Mirostat 2.0\": 2}\n\n # Default to 0 for 'Disabled'\n mirostat_value = mirostat_options.get(self.mirostat, 0)\n\n # Set mirostat_eta and mirostat_tau to None if mirostat is disabled\n if mirostat_value == 0:\n mirostat_eta = None\n mirostat_tau = None\n else:\n mirostat_eta = self.mirostat_eta\n mirostat_tau = self.mirostat_tau\n\n # Mapping system settings to their corresponding values\n llm_params = {\n \"base_url\": self.base_url,\n \"model\": self.model_name,\n \"mirostat\": mirostat_value,\n \"format\": self.format,\n \"metadata\": self.metadata,\n \"tags\": self.tags.split(\",\") if self.tags else None,\n \"mirostat_eta\": mirostat_eta,\n \"mirostat_tau\": mirostat_tau,\n \"num_ctx\": self.num_ctx or None,\n \"num_gpu\": self.num_gpu or None,\n \"num_thread\": self.num_thread or None,\n \"repeat_last_n\": self.repeat_last_n or None,\n \"repeat_penalty\": self.repeat_penalty or None,\n \"temperature\": self.temperature or None,\n \"stop\": self.stop_tokens.split(\",\") if self.stop_tokens else None,\n \"system\": self.system,\n \"tfs_z\": self.tfs_z or None,\n \"timeout\": self.timeout or None,\n \"top_k\": self.top_k or None,\n \"top_p\": self.top_p or None,\n \"verbose\": self.verbose,\n \"template\": self.template,\n }\n\n # Remove parameters with None values\n llm_params = {k: v for k, v in llm_params.items() if v is not None}\n\n try:\n output = ChatOllama(**llm_params)\n except Exception as e:\n msg = (\n \"Unable to connect to the Ollama API. \",\n \"Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.\",\n )\n raise ValueError(msg) from e\n\n return output\n\n async def is_valid_ollama_url(self, url: str) -> bool:\n try:\n async with httpx.AsyncClient() as client:\n return (await client.get(urljoin(url, \"api/tags\"))).status_code == HTTP_STATUS_OK\n except httpx.RequestError:\n return False\n\n async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):\n if field_name == \"mirostat\":\n if field_value == \"Disabled\":\n build_config[\"mirostat_eta\"][\"advanced\"] = True\n build_config[\"mirostat_tau\"][\"advanced\"] = True\n build_config[\"mirostat_eta\"][\"value\"] = None\n build_config[\"mirostat_tau\"][\"value\"] = None\n\n else:\n build_config[\"mirostat_eta\"][\"advanced\"] = False\n build_config[\"mirostat_tau\"][\"advanced\"] = False\n\n if field_value == \"Mirostat 2.0\":\n build_config[\"mirostat_eta\"][\"value\"] = 0.2\n build_config[\"mirostat_tau\"][\"value\"] = 10\n else:\n build_config[\"mirostat_eta\"][\"value\"] = 0.1\n build_config[\"mirostat_tau\"][\"value\"] = 5\n\n if field_name in {\"base_url\", \"model_name\"}:\n if build_config[\"base_url\"].get(\"load_from_db\", False):\n base_url_value = await self.get_variables(build_config[\"base_url\"].get(\"value\", \"\"), \"base_url\")\n else:\n base_url_value = build_config[\"base_url\"].get(\"value\", \"\")\n\n if not await self.is_valid_ollama_url(base_url_value):\n # Check if any URL in the list is valid\n valid_url = \"\"\n check_urls = URL_LIST\n if self.base_url:\n check_urls = [self.base_url, *URL_LIST]\n for url in check_urls:\n if await self.is_valid_ollama_url(url):\n valid_url = url\n break\n if valid_url != \"\":\n build_config[\"base_url\"][\"value\"] = valid_url\n else:\n msg = \"No valid Ollama URL found.\"\n raise ValueError(msg)\n if field_name in {\"model_name\", \"base_url\", \"tool_model_enabled\"}:\n if await self.is_valid_ollama_url(self.base_url):\n tool_model_enabled = build_config[\"tool_model_enabled\"].get(\"value\", False) or self.tool_model_enabled\n build_config[\"model_name\"][\"options\"] = await self.get_models(\n self.base_url, tool_model_enabled=tool_model_enabled\n )\n elif await self.is_valid_ollama_url(build_config[\"base_url\"].get(\"value\", \"\")):\n tool_model_enabled = build_config[\"tool_model_enabled\"].get(\"value\", False) or self.tool_model_enabled\n build_config[\"model_name\"][\"options\"] = await self.get_models(\n build_config[\"base_url\"].get(\"value\", \"\"), tool_model_enabled=tool_model_enabled\n )\n else:\n build_config[\"model_name\"][\"options\"] = []\n if field_name == \"keep_alive_flag\":\n if field_value == \"Keep\":\n build_config[\"keep_alive\"][\"value\"] = \"-1\"\n build_config[\"keep_alive\"][\"advanced\"] = True\n elif field_value == \"Immediately\":\n build_config[\"keep_alive\"][\"value\"] = \"0\"\n build_config[\"keep_alive\"][\"advanced\"] = True\n else:\n build_config[\"keep_alive\"][\"advanced\"] = False\n\n return build_config\n\n async def get_models(self, base_url_value: str, *, tool_model_enabled: bool | None = None) -> list[str]:\n \"\"\"Fetches a list of models from the Ollama API that do not have the \"embedding\" capability.\n\n Args:\n base_url_value (str): The base URL of the Ollama API.\n tool_model_enabled (bool | None, optional): If True, filters the models further to include\n only those that support tool calling. Defaults to None.\n\n Returns:\n list[str]: A list of model names that do not have the \"embedding\" capability. If\n `tool_model_enabled` is True, only models supporting tool calling are included.\n\n Raises:\n ValueError: If there is an issue with the API request or response, or if the model\n names cannot be retrieved.\n \"\"\"\n try:\n # Normalize the base URL to avoid the repeated \"/\" at the end\n base_url = base_url_value.rstrip(\"/\") + \"/\"\n\n # Ollama REST API to return models\n tags_url = urljoin(base_url, \"api/tags\")\n\n # Ollama REST API to return model capabilities\n show_url = urljoin(base_url, \"api/show\")\n\n async with httpx.AsyncClient() as client:\n # Fetch available models\n tags_response = await client.get(tags_url)\n tags_response.raise_for_status()\n models = tags_response.json()\n if asyncio.iscoroutine(models):\n models = await models\n await logger.adebug(f\"Available models: {models}\")\n\n # Filter models that are NOT embedding models\n model_ids = []\n for model in models[self.JSON_MODELS_KEY]:\n model_name = model[self.JSON_NAME_KEY]\n await logger.adebug(f\"Checking model: {model_name}\")\n\n payload = {\"model\": model_name}\n show_response = await client.post(show_url, json=payload)\n show_response.raise_for_status()\n json_data = show_response.json()\n if asyncio.iscoroutine(json_data):\n json_data = await json_data\n capabilities = json_data.get(self.JSON_CAPABILITIES_KEY, [])\n await logger.adebug(f\"Model: {model_name}, Capabilities: {capabilities}\")\n\n if self.DESIRED_CAPABILITY in capabilities and (\n not tool_model_enabled or self.TOOL_CALLING_CAPABILITY in capabilities\n ):\n model_ids.append(model_name)\n\n except (httpx.RequestError, ValueError) as e:\n msg = \"Could not get model names from Ollama.\"\n raise ValueError(msg) from e\n\n return model_ids\n",
+ "value": "import asyncio\nfrom typing import Any\nfrom urllib.parse import urljoin\n\nimport httpx\nfrom langchain_ollama import ChatOllama\n\nfrom lfx.base.models.model import LCModelComponent\nfrom lfx.field_typing import LanguageModel\nfrom lfx.field_typing.range_spec import RangeSpec\nfrom lfx.io import BoolInput, DictInput, DropdownInput, FloatInput, IntInput, MessageTextInput, SliderInput\nfrom lfx.log.logger import logger\nfrom lfx.utils.util import transform_localhost_url\n\nHTTP_STATUS_OK = 200\n\n\nclass ChatOllamaComponent(LCModelComponent):\n display_name = \"Ollama\"\n description = \"Generate text using Ollama Local LLMs.\"\n icon = \"Ollama\"\n name = \"OllamaModel\"\n\n # Define constants for JSON keys\n JSON_MODELS_KEY = \"models\"\n JSON_NAME_KEY = \"name\"\n JSON_CAPABILITIES_KEY = \"capabilities\"\n DESIRED_CAPABILITY = \"completion\"\n TOOL_CALLING_CAPABILITY = \"tools\"\n\n inputs = [\n MessageTextInput(\n name=\"base_url\",\n display_name=\"Base URL\",\n info=\"Endpoint of the Ollama API.\",\n value=\"\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"model_name\",\n display_name=\"Model Name\",\n options=[],\n info=\"Refer to https://ollama.com/library for more models.\",\n refresh_button=True,\n real_time_refresh=True,\n ),\n SliderInput(\n name=\"temperature\",\n display_name=\"Temperature\",\n value=0.1,\n range_spec=RangeSpec(min=0, max=1, step=0.01),\n advanced=True,\n ),\n MessageTextInput(\n name=\"format\", display_name=\"Format\", info=\"Specify the format of the output (e.g., json).\", advanced=True\n ),\n DictInput(name=\"metadata\", display_name=\"Metadata\", info=\"Metadata to add to the run trace.\", advanced=True),\n DropdownInput(\n name=\"mirostat\",\n display_name=\"Mirostat\",\n options=[\"Disabled\", \"Mirostat\", \"Mirostat 2.0\"],\n info=\"Enable/disable Mirostat sampling for controlling perplexity.\",\n value=\"Disabled\",\n advanced=True,\n real_time_refresh=True,\n ),\n FloatInput(\n name=\"mirostat_eta\",\n display_name=\"Mirostat Eta\",\n info=\"Learning rate for Mirostat algorithm. (Default: 0.1)\",\n advanced=True,\n ),\n FloatInput(\n name=\"mirostat_tau\",\n display_name=\"Mirostat Tau\",\n info=\"Controls the balance between coherence and diversity of the output. (Default: 5.0)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_ctx\",\n display_name=\"Context Window Size\",\n info=\"Size of the context window for generating tokens. (Default: 2048)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_gpu\",\n display_name=\"Number of GPUs\",\n info=\"Number of GPUs to use for computation. (Default: 1 on macOS, 0 to disable)\",\n advanced=True,\n ),\n IntInput(\n name=\"num_thread\",\n display_name=\"Number of Threads\",\n info=\"Number of threads to use during computation. (Default: detected for optimal performance)\",\n advanced=True,\n ),\n IntInput(\n name=\"repeat_last_n\",\n display_name=\"Repeat Last N\",\n info=\"How far back the model looks to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)\",\n advanced=True,\n ),\n FloatInput(\n name=\"repeat_penalty\",\n display_name=\"Repeat Penalty\",\n info=\"Penalty for repetitions in generated text. (Default: 1.1)\",\n advanced=True,\n ),\n FloatInput(name=\"tfs_z\", display_name=\"TFS Z\", info=\"Tail free sampling value. (Default: 1)\", advanced=True),\n IntInput(name=\"timeout\", display_name=\"Timeout\", info=\"Timeout for the request stream.\", advanced=True),\n IntInput(\n name=\"top_k\", display_name=\"Top K\", info=\"Limits token selection to top K. (Default: 40)\", advanced=True\n ),\n FloatInput(name=\"top_p\", display_name=\"Top P\", info=\"Works together with top-k. (Default: 0.9)\", advanced=True),\n BoolInput(name=\"verbose\", display_name=\"Verbose\", info=\"Whether to print out response text.\", advanced=True),\n MessageTextInput(\n name=\"tags\",\n display_name=\"Tags\",\n info=\"Comma-separated list of tags to add to the run trace.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"stop_tokens\",\n display_name=\"Stop Tokens\",\n info=\"Comma-separated list of tokens to signal the model to stop generating text.\",\n advanced=True,\n ),\n MessageTextInput(\n name=\"system\", display_name=\"System\", info=\"System to use for generating text.\", advanced=True\n ),\n BoolInput(\n name=\"tool_model_enabled\",\n display_name=\"Tool Model Enabled\",\n info=\"Whether to enable tool calling in the model.\",\n value=True,\n real_time_refresh=True,\n ),\n MessageTextInput(\n name=\"template\", display_name=\"Template\", info=\"Template to use for generating text.\", advanced=True\n ),\n *LCModelComponent.get_base_inputs(),\n ]\n\n def build_model(self) -> LanguageModel: # type: ignore[type-var]\n # Mapping mirostat settings to their corresponding values\n mirostat_options = {\"Mirostat\": 1, \"Mirostat 2.0\": 2}\n\n # Default to 0 for 'Disabled'\n mirostat_value = mirostat_options.get(self.mirostat, 0)\n\n # Set mirostat_eta and mirostat_tau to None if mirostat is disabled\n if mirostat_value == 0:\n mirostat_eta = None\n mirostat_tau = None\n else:\n mirostat_eta = self.mirostat_eta\n mirostat_tau = self.mirostat_tau\n\n transformed_base_url = transform_localhost_url(self.base_url)\n # Mapping system settings to their corresponding values\n llm_params = {\n \"base_url\": transformed_base_url,\n \"model\": self.model_name,\n \"mirostat\": mirostat_value,\n \"format\": self.format,\n \"metadata\": self.metadata,\n \"tags\": self.tags.split(\",\") if self.tags else None,\n \"mirostat_eta\": mirostat_eta,\n \"mirostat_tau\": mirostat_tau,\n \"num_ctx\": self.num_ctx or None,\n \"num_gpu\": self.num_gpu or None,\n \"num_thread\": self.num_thread or None,\n \"repeat_last_n\": self.repeat_last_n or None,\n \"repeat_penalty\": self.repeat_penalty or None,\n \"temperature\": self.temperature or None,\n \"stop\": self.stop_tokens.split(\",\") if self.stop_tokens else None,\n \"system\": self.system,\n \"tfs_z\": self.tfs_z or None,\n \"timeout\": self.timeout or None,\n \"top_k\": self.top_k or None,\n \"top_p\": self.top_p or None,\n \"verbose\": self.verbose,\n \"template\": self.template,\n }\n\n # Remove parameters with None values\n llm_params = {k: v for k, v in llm_params.items() if v is not None}\n\n try:\n output = ChatOllama(**llm_params)\n except Exception as e:\n msg = (\n \"Unable to connect to the Ollama API. \",\n \"Please verify the base URL, ensure the relevant Ollama model is pulled, and try again.\",\n )\n raise ValueError(msg) from e\n\n return output\n\n async def is_valid_ollama_url(self, url: str) -> bool:\n try:\n async with httpx.AsyncClient() as client:\n url = transform_localhost_url(url)\n return (await client.get(urljoin(url, \"api/tags\"))).status_code == HTTP_STATUS_OK\n except httpx.RequestError:\n return False\n\n async def update_build_config(self, build_config: dict, field_value: Any, field_name: str | None = None):\n if field_name == \"mirostat\":\n if field_value == \"Disabled\":\n build_config[\"mirostat_eta\"][\"advanced\"] = True\n build_config[\"mirostat_tau\"][\"advanced\"] = True\n build_config[\"mirostat_eta\"][\"value\"] = None\n build_config[\"mirostat_tau\"][\"value\"] = None\n\n else:\n build_config[\"mirostat_eta\"][\"advanced\"] = False\n build_config[\"mirostat_tau\"][\"advanced\"] = False\n\n if field_value == \"Mirostat 2.0\":\n build_config[\"mirostat_eta\"][\"value\"] = 0.2\n build_config[\"mirostat_tau\"][\"value\"] = 10\n else:\n build_config[\"mirostat_eta\"][\"value\"] = 0.1\n build_config[\"mirostat_tau\"][\"value\"] = 5\n\n if field_name in {\"base_url\", \"model_name\"} and not await self.is_valid_ollama_url(self.base_url):\n msg = \"Ollama is not running on the provided base URL. Please start Ollama and try again.\"\n raise ValueError(msg)\n if field_name in {\"model_name\", \"base_url\", \"tool_model_enabled\"}:\n if await self.is_valid_ollama_url(self.base_url):\n tool_model_enabled = build_config[\"tool_model_enabled\"].get(\"value\", False) or self.tool_model_enabled\n build_config[\"model_name\"][\"options\"] = await self.get_models(\n self.base_url, tool_model_enabled=tool_model_enabled\n )\n else:\n build_config[\"model_name\"][\"options\"] = []\n if field_name == \"keep_alive_flag\":\n if field_value == \"Keep\":\n build_config[\"keep_alive\"][\"value\"] = \"-1\"\n build_config[\"keep_alive\"][\"advanced\"] = True\n elif field_value == \"Immediately\":\n build_config[\"keep_alive\"][\"value\"] = \"0\"\n build_config[\"keep_alive\"][\"advanced\"] = True\n else:\n build_config[\"keep_alive\"][\"advanced\"] = False\n\n return build_config\n\n async def get_models(self, base_url_value: str, *, tool_model_enabled: bool | None = None) -> list[str]:\n \"\"\"Fetches a list of models from the Ollama API that do not have the \"embedding\" capability.\n\n Args:\n base_url_value (str): The base URL of the Ollama API.\n tool_model_enabled (bool | None, optional): If True, filters the models further to include\n only those that support tool calling. Defaults to None.\n\n Returns:\n list[str]: A list of model names that do not have the \"embedding\" capability. If\n `tool_model_enabled` is True, only models supporting tool calling are included.\n\n Raises:\n ValueError: If there is an issue with the API request or response, or if the model\n names cannot be retrieved.\n \"\"\"\n try:\n # Normalize the base URL to avoid the repeated \"/\" at the end\n base_url = base_url_value.rstrip(\"/\") + \"/\"\n base_url = transform_localhost_url(base_url)\n\n # Ollama REST API to return models\n tags_url = urljoin(base_url, \"api/tags\")\n\n # Ollama REST API to return model capabilities\n show_url = urljoin(base_url, \"api/show\")\n\n async with httpx.AsyncClient() as client:\n # Fetch available models\n tags_response = await client.get(tags_url)\n tags_response.raise_for_status()\n models = tags_response.json()\n if asyncio.iscoroutine(models):\n models = await models\n await logger.adebug(f\"Available models: {models}\")\n\n # Filter models that are NOT embedding models\n model_ids = []\n for model in models[self.JSON_MODELS_KEY]:\n model_name = model[self.JSON_NAME_KEY]\n await logger.adebug(f\"Checking model: {model_name}\")\n\n payload = {\"model\": model_name}\n show_response = await client.post(show_url, json=payload)\n show_response.raise_for_status()\n json_data = show_response.json()\n if asyncio.iscoroutine(json_data):\n json_data = await json_data\n capabilities = json_data.get(self.JSON_CAPABILITIES_KEY, [])\n await logger.adebug(f\"Model: {model_name}, Capabilities: {capabilities}\")\n\n if self.DESIRED_CAPABILITY in capabilities and (\n not tool_model_enabled or self.TOOL_CALLING_CAPABILITY in capabilities\n ):\n model_ids.append(model_name)\n\n except (httpx.RequestError, ValueError) as e:\n msg = \"Could not get model names from Ollama.\"\n raise ValueError(msg) from e\n\n return model_ids\n",
"fileTypes": [],
"file_path": "",
"password": false,
@@ -59,9 +57,7 @@
"value": "",
"display_name": "Format",
"advanced": true,
- "input_types": [
- "Message"
- ],
+ "input_types": ["Message"],
"dynamic": false,
"info": "Specify the format of the output (e.g., json).",
"title_case": false,
@@ -82,9 +78,7 @@
"value": "",
"display_name": "Input",
"advanced": false,
- "input_types": [
- "Message"
- ],
+ "input_types": ["Message"],
"dynamic": false,
"info": "",
"title_case": false,
@@ -112,11 +106,7 @@
"mirostat": {
"tool_mode": false,
"trace_as_metadata": true,
- "options": [
- "Disabled",
- "Mirostat",
- "Mirostat 2.0"
- ],
+ "options": ["Disabled", "Mirostat", "Mirostat 2.0"],
"options_metadata": [],
"combobox": false,
"dialog_inputs": {},
@@ -175,7 +165,7 @@
"model_name": {
"tool_mode": false,
"trace_as_metadata": true,
- "options": [],
+ "options": ["gpt-oss:20b", "qwen3:4b"],
"options_metadata": [],
"combobox": false,
"dialog_inputs": {},
@@ -300,9 +290,7 @@
"value": "",
"display_name": "Stop Tokens",
"advanced": true,
- "input_types": [
- "Message"
- ],
+ "input_types": ["Message"],
"dynamic": false,
"info": "Comma-separated list of tokens to signal the model to stop generating text.",
"title_case": false,
@@ -341,9 +329,7 @@
"value": "",
"display_name": "System",
"advanced": true,
- "input_types": [
- "Message"
- ],
+ "input_types": ["Message"],
"dynamic": false,
"info": "System to use for generating text.",
"title_case": false,
@@ -365,9 +351,7 @@
"value": "",
"display_name": "System Message",
"advanced": false,
- "input_types": [
- "Message"
- ],
+ "input_types": ["Message"],
"dynamic": false,
"info": "System message to pass to the model.",
"title_case": false,
@@ -389,9 +373,7 @@
"value": "",
"display_name": "Tags",
"advanced": true,
- "input_types": [
- "Message"
- ],
+ "input_types": ["Message"],
"dynamic": false,
"info": "Comma-separated list of tags to add to the run trace.",
"title_case": false,
@@ -440,9 +422,7 @@
"value": "",
"display_name": "Template",
"advanced": true,
- "input_types": [
- "Message"
- ],
+ "input_types": ["Message"],
"dynamic": false,
"info": "Template to use for generating text.",
"title_case": false,
@@ -561,10 +541,7 @@
},
"description": "Generate text using Ollama Local LLMs.",
"icon": "Ollama",
- "base_classes": [
- "LanguageModel",
- "Message"
- ],
+ "base_classes": ["LanguageModel", "Message"],
"display_name": "Ollama",
"documentation": "",
"minimized": false,
@@ -575,9 +552,7 @@
"frozen": false,
"outputs": [
{
- "types": [
- "Message"
- ],
+ "types": ["Message"],
"selected": "Message",
"name": "text_output",
"display_name": "Model Response",
@@ -591,9 +566,7 @@
"tool_mode": true
},
{
- "types": [
- "LanguageModel"
- ],
+ "types": ["LanguageModel"],
"selected": "LanguageModel",
"name": "model_output",
"display_name": "Language Model",
@@ -639,45 +612,28 @@
"legacy": false,
"edited": false,
"metadata": {
- "keywords": [
- "model",
- "llm",
- "language model",
- "large language model"
- ],
+ "keywords": ["model", "llm", "language model", "large language model"],
"module": "lfx.components.ollama.ollama.ChatOllamaComponent",
- "code_hash": "54de3b5da388",
+ "code_hash": "79649147b972",
"dependencies": {
"total_dependencies": 3,
"dependencies": [
- {
- "name": "httpx",
- "version": "0.28.1"
- },
- {
- "name": "langchain_ollama",
- "version": "0.2.1"
- },
- {
- "name": "lfx",
- "version": null
- }
+ { "name": "httpx", "version": "0.28.1" },
+ { "name": "langchain_ollama", "version": "0.2.1" },
+ { "name": "lfx", "version": "0.1.12.dev32" }
]
}
},
"tool_mode": false,
- "last_updated": "2025-09-29T18:39:30.798Z",
+ "last_updated": "2025-10-29T20:37:13.232Z",
"official": false
},
"showNode": true,
"type": "OllamaModel",
- "id": "OllamaModel-8Re0J",
+ "id": "OllamaModel-0hTPe",
"selected_output": "text_output"
},
- "id": "OllamaModel-8Re0J",
- "position": {
- "x": 0,
- "y": 0
- },
+ "id": "OllamaModel-0hTPe",
+ "position": { "x": 0, "y": 0 },
"type": "genericNode"
-}
\ No newline at end of file
+}
diff --git a/frontend/components/logo/dog-icon.tsx b/frontend/components/logo/dog-icon.tsx
index ca53eabb..613bcd92 100644
--- a/frontend/components/logo/dog-icon.tsx
+++ b/frontend/components/logo/dog-icon.tsx
@@ -7,29 +7,29 @@ const DogIcon = ({ disabled = false, stroke, ...props }: DogIconProps) => {
// CSS for the stepped animation states
const animationCSS = `
- .state1 { animation: showState1 600ms infinite; }
- .state2 { animation: showState2 600ms infinite; }
- .state3 { animation: showState3 600ms infinite; }
- .state4 { animation: showState4 600ms infinite; }
+ .state1 { animation: showDogState1 600ms infinite; }
+ .state2 { animation: showDogState2 600ms infinite; }
+ .state3 { animation: showDogState3 600ms infinite; }
+ .state4 { animation: showDogState4 600ms infinite; }
- @keyframes showState1 {
+ @keyframes showDogState1 {
0%, 24.99% { opacity: 1; }
25%, 100% { opacity: 0; }
}
- @keyframes showState2 {
+ @keyframes showDogState2 {
0%, 24.99% { opacity: 0; }
25%, 49.99% { opacity: 1; }
50%, 100% { opacity: 0; }
}
- @keyframes showState3 {
+ @keyframes showDogState3 {
0%, 49.99% { opacity: 0; }
50%, 74.99% { opacity: 1; }
75%, 100% { opacity: 0; }
}
- @keyframes showState4 {
+ @keyframes showDogState4 {
0%, 74.99% { opacity: 0; }
75%, 100% { opacity: 1; }
}
diff --git a/frontend/lib/upload-utils.ts b/frontend/lib/upload-utils.ts
index 48d7ddfd..febefa60 100644
--- a/frontend/lib/upload-utils.ts
+++ b/frontend/lib/upload-utils.ts
@@ -194,21 +194,6 @@ export async function uploadFile(
raw: uploadIngestJson,
};
- window.dispatchEvent(
- new CustomEvent("fileUploaded", {
- detail: {
- file,
- result: {
- file_id: fileId,
- file_path: filePath,
- run: runJson,
- deletion: deletionJson,
- unified: true,
- },
- },
- })
- );
-
return result;
} catch (error) {
window.dispatchEvent(
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index c724fde9..d890e136 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -42,6 +42,7 @@
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-hook-form": "^7.65.0",
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^15.6.1",
@@ -8348,6 +8349,21 @@
"react": "^19.1.1"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.65.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.65.0.tgz",
+ "integrity": "sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
+ }
+ },
"node_modules/react-icons": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index fd6fc0cb..517e36da 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -43,6 +43,7 @@
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-hook-form": "^7.65.0",
"react-icons": "^5.5.0",
"react-markdown": "^10.1.0",
"react-syntax-highlighter": "^15.6.1",
diff --git a/frontend/src/app/api/mutations/useUpdateFlowSettingMutation.ts b/frontend/src/app/api/mutations/useUpdateFlowSettingMutation.ts
deleted file mode 100644
index e789af48..00000000
--- a/frontend/src/app/api/mutations/useUpdateFlowSettingMutation.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import {
- type UseMutationOptions,
- useMutation,
- useQueryClient,
-} from "@tanstack/react-query";
-
-interface UpdateFlowSettingVariables {
- llm_model?: string;
- system_prompt?: string;
- embedding_model?: string;
- table_structure?: boolean;
- ocr?: boolean;
- picture_descriptions?: boolean;
- chunk_size?: number;
- chunk_overlap?: number;
-}
-
-interface UpdateFlowSettingResponse {
- message: string;
-}
-
-export const useUpdateFlowSettingMutation = (
- options?: Omit<
- UseMutationOptions<
- UpdateFlowSettingResponse,
- Error,
- UpdateFlowSettingVariables
- >,
- "mutationFn"
- >,
-) => {
- const queryClient = useQueryClient();
-
- async function updateFlowSetting(
- variables: UpdateFlowSettingVariables,
- ): Promise {
- const response = await fetch("/api/settings", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(variables),
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error || "Failed to update settings");
- }
-
- return response.json();
- }
-
- return useMutation({
- mutationFn: updateFlowSetting,
- onSettled: () => {
- // Invalidate settings query to refetch updated data
- queryClient.invalidateQueries({ queryKey: ["settings"] });
- },
- ...options,
- });
-};
diff --git a/frontend/src/app/api/mutations/useUpdateSettingsMutation.ts b/frontend/src/app/api/mutations/useUpdateSettingsMutation.ts
new file mode 100644
index 00000000..6f75f4cb
--- /dev/null
+++ b/frontend/src/app/api/mutations/useUpdateSettingsMutation.ts
@@ -0,0 +1,72 @@
+import {
+ type UseMutationOptions,
+ useMutation,
+ useQueryClient,
+} from "@tanstack/react-query";
+import type { Settings } from "../queries/useGetSettingsQuery";
+
+export interface UpdateSettingsRequest {
+ // Agent settings
+ llm_model?: string;
+ system_prompt?: string;
+
+ // Knowledge settings
+ chunk_size?: number;
+ chunk_overlap?: number;
+ table_structure?: boolean;
+ ocr?: boolean;
+ picture_descriptions?: boolean;
+ embedding_model?: string;
+
+ // Provider settings
+ model_provider?: string;
+ api_key?: string;
+ endpoint?: string;
+ project_id?: string;
+}
+
+export interface UpdateSettingsResponse {
+ message: string;
+ settings: Settings;
+}
+
+export const useUpdateSettingsMutation = (
+ options?: Omit<
+ UseMutationOptions,
+ "mutationFn"
+ >
+) => {
+ const queryClient = useQueryClient();
+
+ async function updateSettings(
+ variables: UpdateSettingsRequest
+ ): Promise {
+ const response = await fetch("/api/settings", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(variables),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}));
+ throw new Error(errorData.error || "Failed to update settings");
+ }
+
+ return response.json();
+ }
+
+ return useMutation({
+ mutationFn: updateSettings,
+ onSuccess: (...args) => {
+ queryClient.invalidateQueries({
+ queryKey: ["settings"],
+ refetchType: "all"
+ });
+ options?.onSuccess?.(...args);
+ },
+ onError: options?.onError,
+ onSettled: options?.onSettled,
+ });
+};
diff --git a/frontend/src/app/api/queries/useGetModelsQuery.ts b/frontend/src/app/api/queries/useGetModelsQuery.ts
index 3a5eb77e..bd175c86 100644
--- a/frontend/src/app/api/queries/useGetModelsQuery.ts
+++ b/frontend/src/app/api/queries/useGetModelsQuery.ts
@@ -53,8 +53,6 @@ export const useGetOpenAIModelsQuery = (
{
queryKey: ["models", "openai", params],
queryFn: getOpenAIModels,
- retry: 2,
- enabled: !!params?.apiKey,
staleTime: 0, // Always fetch fresh data
gcTime: 0, // Don't cache results
...options,
@@ -89,7 +87,6 @@ export const useGetOllamaModelsQuery = (
{
queryKey: ["models", "ollama", params],
queryFn: getOllamaModels,
- retry: 2,
staleTime: 0, // Always fetch fresh data
gcTime: 0, // Don't cache results
...options,
@@ -130,8 +127,6 @@ export const useGetIBMModelsQuery = (
{
queryKey: ["models", "ibm", params],
queryFn: getIBMModels,
- retry: 2,
- enabled: !!params?.endpoint && !!params?.apiKey && !!params?.projectId, // Only run if all required params are provided
staleTime: 0, // Always fetch fresh data
gcTime: 0, // Don't cache results
...options,
diff --git a/frontend/src/app/api/queries/useGetSettingsQuery.ts b/frontend/src/app/api/queries/useGetSettingsQuery.ts
index 0f090299..2e21d22d 100644
--- a/frontend/src/app/api/queries/useGetSettingsQuery.ts
+++ b/frontend/src/app/api/queries/useGetSettingsQuery.ts
@@ -26,6 +26,9 @@ export interface Settings {
edited?: boolean;
provider?: {
model_provider?: string;
+ // Note: api_key is never returned by the backend for security reasons
+ endpoint?: string;
+ project_id?: string;
};
knowledge?: KnowledgeSettings;
agent?: AgentSettings;
diff --git a/frontend/src/app/chat/components/assistant-message.tsx b/frontend/src/app/chat/components/assistant-message.tsx
index 2ed8c168..9abcc7e8 100644
--- a/frontend/src/app/chat/components/assistant-message.tsx
+++ b/frontend/src/app/chat/components/assistant-message.tsx
@@ -46,7 +46,7 @@ export function AssistantMessage({
>
+
(
fileInputRef.current?.click();
},
}));
-
+
const handleFilePickerChange = (e: React.ChangeEvent) => {
const files = e.target.files;
if (files && files.length > 0) {
@@ -92,242 +91,247 @@ export const ChatInput = forwardRef(
};
return (
-