mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-24 20:52:11 +00:00
improved chunking pipeline and handling of expert settings
This commit is contained in:
parent
2829051e4c
commit
89f7156fdf
@ -3817,6 +3817,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERIV1USERNAMEPASSWORDEXPORTDIALOG:
|
|||||||
-- Cancel
|
-- Cancel
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERIV1USERNAMEPASSWORDEXPORTDIALOG::T900713019"] = "Cancel"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCEERIV1USERNAMEPASSWORDEXPORTDIALOG::T900713019"] = "Cancel"
|
||||||
|
|
||||||
|
-- Hide Expert Settings
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1108876344"] = "Hide Expert Settings"
|
||||||
|
|
||||||
|
-- Optional expert settings for how this data source is split before embedding.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1133561850"] = "Optional expert settings for how this data source is split before embedding."
|
||||||
|
|
||||||
-- Describe what data this directory contains to help the AI select it.
|
-- Describe what data this directory contains to help the AI select it.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1136409150"] = "Describe what data this directory contains to help the AI select it."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1136409150"] = "Describe what data this directory contains to help the AI select it."
|
||||||
|
|
||||||
@ -3826,6 +3832,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1265737624"
|
|||||||
-- Selected base directory for this data source
|
-- Selected base directory for this data source
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1312296210"] = "Selected base directory for this data source"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1312296210"] = "Selected base directory for this data source"
|
||||||
|
|
||||||
|
-- Maximum number of tokens per chunk for this data source. Use 0 to use the embedding provider setting.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1529596777"] = "Maximum number of tokens per chunk for this data source. Use 0 to use the embedding provider setting."
|
||||||
|
|
||||||
-- Description
|
-- Description
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1725856265"] = "Description"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1725856265"] = "Description"
|
||||||
|
|
||||||
@ -3838,24 +3847,48 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1847791252"
|
|||||||
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.
|
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1948697886"] = "In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T1948697886"] = "In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method."
|
||||||
|
|
||||||
|
-- The data source max chunk length must be less than or equal to the embedding provider limit ({0}).
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2166221195"] = "The data source max chunk length must be less than or equal to the embedding provider limit ({0})."
|
||||||
|
|
||||||
|
-- Number of tokens repeated at the start of the next chunk. Use 0 to disable overlap.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2263831524"] = "Number of tokens repeated at the start of the next chunk. Use 0 to disable overlap."
|
||||||
|
|
||||||
-- Add
|
-- Add
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Add"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2646845972"] = "Add"
|
||||||
|
|
||||||
|
-- Chunk overlap length
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2801813827"] = "Chunk overlap length"
|
||||||
|
|
||||||
-- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.
|
-- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"] = "The embedding you selected runs locally or in your organization. Your data is not sent to the cloud."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2814869210"] = "The embedding you selected runs locally or in your organization. Your data is not sent to the cloud."
|
||||||
|
|
||||||
-- Embedding
|
-- Embedding
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2838542994"] = "Embedding"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T2838542994"] = "Embedding"
|
||||||
|
|
||||||
|
-- Please enter 0 or a positive max chunk length.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3130373769"] = "Please enter 0 or a positive max chunk length."
|
||||||
|
|
||||||
|
-- Please enter 0 or a positive overlap length.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3242265813"] = "Please enter 0 or a positive overlap length."
|
||||||
|
|
||||||
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
|
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3359366900"] = "For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3359366900"] = "For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc."
|
||||||
|
|
||||||
|
-- Show Expert Settings
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3361153305"] = "Show Expert Settings"
|
||||||
|
|
||||||
|
-- Max chunk length
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T3621670500"] = "Max chunk length"
|
||||||
|
|
||||||
-- Your security policy
|
-- Your security policy
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T4081226330"] = "Your security policy"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T4081226330"] = "Your security policy"
|
||||||
|
|
||||||
-- Select the base directory
|
-- Select the base directory
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T562479068"] = "Select the base directory"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T562479068"] = "Select the base directory"
|
||||||
|
|
||||||
|
-- The overlap must be smaller than the effective max chunk length.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T57492807"] = "The overlap must be smaller than the effective max chunk length."
|
||||||
|
|
||||||
-- Data Source Name
|
-- Data Source Name
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T813773421"] = "Data Source Name"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYDIALOG::T813773421"] = "Data Source Name"
|
||||||
|
|
||||||
@ -3934,9 +3967,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T4438734
|
|||||||
-- The directory chosen for the data source exists.
|
-- The directory chosen for the data source exists.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T445858624"] = "The directory chosen for the data source exists."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALDIRECTORYINFODIALOG::T445858624"] = "The directory chosen for the data source exists."
|
||||||
|
|
||||||
|
-- Hide Expert Settings
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1108876344"] = "Hide Expert Settings"
|
||||||
|
|
||||||
|
-- Optional expert settings for how this data source is split before embedding.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1133561850"] = "Optional expert settings for how this data source is split before embedding."
|
||||||
|
|
||||||
-- Select a file for this data source. The content of this file will be processed for the data source.
|
-- Select a file for this data source. The content of this file will be processed for the data source.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1190880267"] = "Select a file for this data source. The content of this file will be processed for the data source."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1190880267"] = "Select a file for this data source. The content of this file will be processed for the data source."
|
||||||
|
|
||||||
|
-- Maximum number of tokens per chunk for this data source. Use 0 to use the embedding provider setting.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1529596777"] = "Maximum number of tokens per chunk for this data source. Use 0 to use the embedding provider setting."
|
||||||
|
|
||||||
-- Description
|
-- Description
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1725856265"] = "Description"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1725856265"] = "Description"
|
||||||
|
|
||||||
@ -3949,9 +3991,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1847791252"] = "
|
|||||||
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.
|
-- In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1948697886"] = "In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T1948697886"] = "In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method."
|
||||||
|
|
||||||
|
-- The data source max chunk length must be less than or equal to the embedding provider limit ({0}).
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2166221195"] = "The data source max chunk length must be less than or equal to the embedding provider limit ({0})."
|
||||||
|
|
||||||
|
-- Number of tokens repeated at the start of the next chunk. Use 0 to disable overlap.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2263831524"] = "Number of tokens repeated at the start of the next chunk. Use 0 to disable overlap."
|
||||||
|
|
||||||
-- Add
|
-- Add
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2646845972"] = "Add"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2646845972"] = "Add"
|
||||||
|
|
||||||
|
-- Chunk overlap length
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2801813827"] = "Chunk overlap length"
|
||||||
|
|
||||||
-- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.
|
-- The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2814869210"] = "The embedding you selected runs locally or in your organization. Your data is not sent to the cloud."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2814869210"] = "The embedding you selected runs locally or in your organization. Your data is not sent to the cloud."
|
||||||
|
|
||||||
@ -3961,15 +4012,30 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2838542994"] = "
|
|||||||
-- Describe what data this file contains to help the AI select it.
|
-- Describe what data this file contains to help the AI select it.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2859265837"] = "Describe what data this file contains to help the AI select it."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T2859265837"] = "Describe what data this file contains to help the AI select it."
|
||||||
|
|
||||||
|
-- Please enter 0 or a positive max chunk length.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3130373769"] = "Please enter 0 or a positive max chunk length."
|
||||||
|
|
||||||
|
-- Please enter 0 or a positive overlap length.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3242265813"] = "Please enter 0 or a positive overlap length."
|
||||||
|
|
||||||
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
|
-- For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3359366900"] = "For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3359366900"] = "For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc."
|
||||||
|
|
||||||
|
-- Show Expert Settings
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3361153305"] = "Show Expert Settings"
|
||||||
|
|
||||||
|
-- Max chunk length
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3621670500"] = "Max chunk length"
|
||||||
|
|
||||||
-- Select the file
|
-- Select the file
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3740148848"] = "Select the file"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T3740148848"] = "Select the file"
|
||||||
|
|
||||||
-- Your security policy
|
-- Your security policy
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T4081226330"] = "Your security policy"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T4081226330"] = "Your security policy"
|
||||||
|
|
||||||
|
-- The overlap must be smaller than the effective max chunk length.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T57492807"] = "The overlap must be smaller than the effective max chunk length."
|
||||||
|
|
||||||
-- Data Source Name
|
-- Data Source Name
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T813773421"] = "Data Source Name"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::DATASOURCELOCALFILEDIALOG::T813773421"] = "Data Source Name"
|
||||||
|
|
||||||
@ -4132,12 +4198,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGMETHODDIALOG::T662524223"] = "A lin
|
|||||||
-- Cancel
|
-- Cancel
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGMETHODDIALOG::T900713019"] = "Cancel"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGMETHODDIALOG::T900713019"] = "Cancel"
|
||||||
|
|
||||||
|
-- Hide Expert Settings
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1108876344"] = "Hide Expert Settings"
|
||||||
|
|
||||||
-- Failed to store the API key in the operating system. The message was: {0}. Please try again.
|
-- Failed to store the API key in the operating system. The message was: {0}. Please try again.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1122745046"] = "Failed to store the API key in the operating system. The message was: {0}. Please try again."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1122745046"] = "Failed to store the API key in the operating system. The message was: {0}. Please try again."
|
||||||
|
|
||||||
-- API Key
|
-- API Key
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1324664716"] = "API Key"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1324664716"] = "API Key"
|
||||||
|
|
||||||
|
-- Please be aware: This section is for experts only. For cloud providers, the selected tokenizer and chunk settings may not match the real embedding model limits exactly.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1345053261"] = "Please be aware: This section is for experts only. For cloud providers, the selected tokenizer and chunk settings may not match the real embedding model limits exactly."
|
||||||
|
|
||||||
-- Create account
|
-- Create account
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1356621346"] = "Create account"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1356621346"] = "Create account"
|
||||||
|
|
||||||
@ -4159,18 +4231,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1847791252"] = "Up
|
|||||||
-- Failed to load the API key from the operating system. The message was: {0}. You might ignore this message and provide the API key again.
|
-- Failed to load the API key from the operating system. The message was: {0}. You might ignore this message and provide the API key again.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1870831108"] = "Failed to load the API key from the operating system. The message was: {0}. You might ignore this message and provide the API key again."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T1870831108"] = "Failed to load the API key from the operating system. The message was: {0}. You might ignore this message and provide the API key again."
|
||||||
|
|
||||||
|
-- Maximum number of tokens sent to the embedding model per chunk. The default is 16,384.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2064265557"] = "Maximum number of tokens sent to the embedding model per chunk. The default is 16,384."
|
||||||
|
|
||||||
-- Model
|
-- Model
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2189814010"] = "Model"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2189814010"] = "Model"
|
||||||
|
|
||||||
|
-- Embedding batch size
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2209963239"] = "Embedding batch size"
|
||||||
|
|
||||||
-- (Optional) API Key
|
-- (Optional) API Key
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2331453405"] = "(Optional) API Key"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2331453405"] = "(Optional) API Key"
|
||||||
|
|
||||||
-- Invalid tokenizer:
|
-- Invalid tokenizer:
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2448302543"] = "Invalid tokenizer:"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2448302543"] = "Invalid tokenizer:"
|
||||||
|
|
||||||
-- Maximum number of tokens sent to the embedding model per chunk.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2544519277"] = "Maximum number of tokens sent to the embedding model per chunk."
|
|
||||||
|
|
||||||
-- Add
|
-- Add
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Add"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Add"
|
||||||
|
|
||||||
@ -4192,12 +4267,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2961294165"] = "To
|
|||||||
-- Please enter a token limit greater than 0.
|
-- Please enter a token limit greater than 0.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3316544737"] = "Please enter a token limit greater than 0."
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3316544737"] = "Please enter a token limit greater than 0."
|
||||||
|
|
||||||
|
-- Show Expert Settings
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
|
||||||
|
|
||||||
|
-- How many chunks are sent to the embedding provider at once. The default is 1.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3780233303"] = "How many chunks are sent to the embedding provider at once. The default is 1."
|
||||||
|
|
||||||
-- Choose a custom tokenizer here
|
-- Choose a custom tokenizer here
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3787466119"] = "Choose a custom tokenizer here"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T3787466119"] = "Choose a custom tokenizer here"
|
||||||
|
|
||||||
-- For better embeddings and less storage usage, it's recommended to use a custom tokenizer to enable a more accurate token count.
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T4126312157"] = "For better embeddings and less storage usage, it's recommended to use a custom tokenizer to enable a more accurate token count."
|
|
||||||
|
|
||||||
-- Model selection
|
-- Model selection
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection"
|
||||||
|
|
||||||
@ -4207,6 +4285,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T504465522"] = "We
|
|||||||
-- Host
|
-- Host
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Host"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Host"
|
||||||
|
|
||||||
|
-- Please enter an embedding batch size greater than 0.
|
||||||
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T840259907"] = "Please enter an embedding batch size greater than 0."
|
||||||
|
|
||||||
-- Provider
|
-- Provider
|
||||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Provider"
|
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Provider"
|
||||||
|
|
||||||
|
|||||||
@ -81,6 +81,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
|
|||||||
{ x => x.DataHost, embeddingProvider.Host },
|
{ x => x.DataHost, embeddingProvider.Host },
|
||||||
{ x => x.DataTokenizerPath, embeddingProvider.TokenizerPath },
|
{ x => x.DataTokenizerPath, embeddingProvider.TokenizerPath },
|
||||||
{ x => x.DataTokenLimit, embeddingProvider.EffectiveTokenLimit },
|
{ x => x.DataTokenLimit, embeddingProvider.EffectiveTokenLimit },
|
||||||
|
{ x => x.DataEmbeddingBatchSize, embeddingProvider.EffectiveEmbeddingBatchSize },
|
||||||
};
|
};
|
||||||
|
|
||||||
var dialogReference = await this.DialogService.ShowAsync<EmbeddingProviderDialog>(T("Edit Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
|
var dialogReference = await this.DialogService.ShowAsync<EmbeddingProviderDialog>(T("Edit Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||||
|
|||||||
@ -67,6 +67,45 @@
|
|||||||
@T("The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.")
|
@T("The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.")
|
||||||
</MudJustifiedText>
|
</MudJustifiedText>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<MudStack Class="mb-3">
|
||||||
|
<MudButton OnClick="@this.ToggleExpertSettings" Variant="Variant.Text" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Tune">
|
||||||
|
@(this.showExpertSettings ? T("Hide Expert Settings") : T("Show Expert Settings"))
|
||||||
|
</MudButton>
|
||||||
|
<MudDivider/>
|
||||||
|
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
|
||||||
|
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||||
|
@T("Optional expert settings for how this data source is split before embedding.")
|
||||||
|
</MudJustifiedText>
|
||||||
|
<MudNumericField
|
||||||
|
T="int"
|
||||||
|
@bind-Value="@this.dataMaxChunkTokenLength"
|
||||||
|
Label="@T("Max chunk length")"
|
||||||
|
Class="mb-3"
|
||||||
|
Min="0"
|
||||||
|
Immediate="@true"
|
||||||
|
Validation="@this.ValidateMaxChunkTokenLength"
|
||||||
|
HelperText="@T("Maximum number of tokens per chunk for this data source. Use 0 to use the embedding provider setting.")"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.DataObject"
|
||||||
|
AdornmentColor="Color.Info"
|
||||||
|
/>
|
||||||
|
<MudNumericField
|
||||||
|
T="int"
|
||||||
|
@bind-Value="@this.dataChunkOverlapTokenLength"
|
||||||
|
Label="@T("Chunk overlap length")"
|
||||||
|
Min="0"
|
||||||
|
Immediate="@true"
|
||||||
|
Validation="@this.ValidateChunkOverlapTokenLength"
|
||||||
|
HelperText="@T("Number of tokens repeated at the start of the next chunk. Use 0 to disable overlap.")"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.CompareArrows"
|
||||||
|
AdornmentColor="Color.Info"
|
||||||
|
/>
|
||||||
|
</MudCollapse>
|
||||||
|
</MudStack>
|
||||||
}
|
}
|
||||||
|
|
||||||
<ManagePandocDependency IntroText="@T("For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.")"/>
|
<ManagePandocDependency IntroText="@T("For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.")"/>
|
||||||
@ -99,4 +138,4 @@
|
|||||||
}
|
}
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</MudDialog>
|
</MudDialog>
|
||||||
|
|||||||
@ -41,7 +41,10 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
|
|||||||
private bool dataUserAcknowledgedCloudEmbedding;
|
private bool dataUserAcknowledgedCloudEmbedding;
|
||||||
private string dataEmbeddingId = string.Empty;
|
private string dataEmbeddingId = string.Empty;
|
||||||
private string dataPath = string.Empty;
|
private string dataPath = string.Empty;
|
||||||
|
private int dataMaxChunkTokenLength;
|
||||||
|
private int dataChunkOverlapTokenLength;
|
||||||
private ushort dataMaxMatches = 10;
|
private ushort dataMaxMatches = 10;
|
||||||
|
private bool showExpertSettings;
|
||||||
private DataSourceSecurity dataSecurityPolicy;
|
private DataSourceSecurity dataSecurityPolicy;
|
||||||
|
|
||||||
// We get the form reference from Blazor code to validate it manually:
|
// We get the form reference from Blazor code to validate it manually:
|
||||||
@ -77,8 +80,11 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
|
|||||||
this.dataDescription = this.DataSource.Description;
|
this.dataDescription = this.DataSource.Description;
|
||||||
this.dataEmbeddingId = this.DataSource.EmbeddingId;
|
this.dataEmbeddingId = this.DataSource.EmbeddingId;
|
||||||
this.dataPath = this.DataSource.Path;
|
this.dataPath = this.DataSource.Path;
|
||||||
|
this.dataMaxChunkTokenLength = this.DataSource.MaxChunkTokenLength;
|
||||||
|
this.dataChunkOverlapTokenLength = this.DataSource.ChunkOverlapTokenLength;
|
||||||
this.dataSecurityPolicy = this.DataSource.SecurityPolicy;
|
this.dataSecurityPolicy = this.DataSource.SecurityPolicy;
|
||||||
this.dataMaxMatches = this.DataSource.MaxMatches;
|
this.dataMaxMatches = this.DataSource.MaxMatches;
|
||||||
|
this.showExpertSettings = this.dataMaxChunkTokenLength > 0 || this.dataChunkOverlapTokenLength > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
@ -96,8 +102,10 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private bool SelectedCloudEmbedding => this.SettingsManager.ConfigurationData.EmbeddingProviders
|
private EmbeddingProvider? SelectedEmbedding => this.SettingsManager.ConfigurationData.EmbeddingProviders
|
||||||
.FirstOrDefault(x => x.Id == this.dataEmbeddingId) is { IsSelfHosted: false };
|
.FirstOrDefault(x => x.Id == this.dataEmbeddingId);
|
||||||
|
|
||||||
|
private bool SelectedCloudEmbedding => this.SelectedEmbedding is { IsSelfHosted: false };
|
||||||
|
|
||||||
private DataSourceLocalDirectory CreateDataSource() => new()
|
private DataSourceLocalDirectory CreateDataSource() => new()
|
||||||
{
|
{
|
||||||
@ -108,6 +116,8 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
|
|||||||
Type = DataSourceType.LOCAL_DIRECTORY,
|
Type = DataSourceType.LOCAL_DIRECTORY,
|
||||||
EmbeddingId = this.dataEmbeddingId,
|
EmbeddingId = this.dataEmbeddingId,
|
||||||
Path = this.dataPath,
|
Path = this.dataPath,
|
||||||
|
MaxChunkTokenLength = this.dataMaxChunkTokenLength,
|
||||||
|
ChunkOverlapTokenLength = this.dataChunkOverlapTokenLength,
|
||||||
SecurityPolicy = this.dataSecurityPolicy,
|
SecurityPolicy = this.dataSecurityPolicy,
|
||||||
MaxMatches = this.dataMaxMatches,
|
MaxMatches = this.dataMaxMatches,
|
||||||
};
|
};
|
||||||
@ -125,4 +135,34 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel() => this.MudDialog.Cancel();
|
private void Cancel() => this.MudDialog.Cancel();
|
||||||
}
|
|
||||||
|
private string? ValidateMaxChunkTokenLength(int maxChunkTokenLength)
|
||||||
|
{
|
||||||
|
if (maxChunkTokenLength < 0)
|
||||||
|
return T("Please enter 0 or a positive max chunk length.");
|
||||||
|
|
||||||
|
var providerMaxChunkTokenLength = this.SelectedEmbedding?.EffectiveTokenLimit ?? EmbeddingProvider.DEFAULT_TOKEN_LIMIT;
|
||||||
|
if (maxChunkTokenLength > providerMaxChunkTokenLength)
|
||||||
|
return string.Format(T("The data source max chunk length must be less than or equal to the embedding provider limit ({0})."), providerMaxChunkTokenLength);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? ValidateChunkOverlapTokenLength(int chunkOverlapTokenLength)
|
||||||
|
{
|
||||||
|
if (chunkOverlapTokenLength < 0)
|
||||||
|
return T("Please enter 0 or a positive overlap length.");
|
||||||
|
|
||||||
|
var effectiveMaxChunkTokenLength = this.dataMaxChunkTokenLength > 0
|
||||||
|
? this.dataMaxChunkTokenLength
|
||||||
|
: this.SelectedEmbedding?.EffectiveTokenLimit ?? EmbeddingProvider.DEFAULT_TOKEN_LIMIT;
|
||||||
|
if (chunkOverlapTokenLength >= effectiveMaxChunkTokenLength)
|
||||||
|
return T("The overlap must be smaller than the effective max chunk length.");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleExpertSettings() => this.showExpertSettings = !this.showExpertSettings;
|
||||||
|
|
||||||
|
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
|
||||||
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@
|
|||||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||||
@T("In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.")
|
@T("In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.")
|
||||||
</MudJustifiedText>
|
</MudJustifiedText>
|
||||||
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.GetSelectedCloudEmbedding">
|
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingId">
|
||||||
@foreach (var embedding in this.AvailableEmbeddings)
|
@foreach (var embedding in this.AvailableEmbeddings)
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@embedding.Value">
|
<MudSelectItem Value="@embedding.Value">
|
||||||
@ -67,6 +67,45 @@
|
|||||||
@T("The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.")
|
@T("The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.")
|
||||||
</MudJustifiedText>
|
</MudJustifiedText>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<MudStack Class="mb-3">
|
||||||
|
<MudButton OnClick="@this.ToggleExpertSettings" Variant="Variant.Text" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Tune">
|
||||||
|
@(this.showExpertSettings ? T("Hide Expert Settings") : T("Show Expert Settings"))
|
||||||
|
</MudButton>
|
||||||
|
<MudDivider/>
|
||||||
|
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
|
||||||
|
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||||
|
@T("Optional expert settings for how this data source is split before embedding.")
|
||||||
|
</MudJustifiedText>
|
||||||
|
<MudNumericField
|
||||||
|
T="int"
|
||||||
|
@bind-Value="@this.dataMaxChunkTokenLength"
|
||||||
|
Label="@T("Max chunk length")"
|
||||||
|
Class="mb-3"
|
||||||
|
Min="0"
|
||||||
|
Immediate="@true"
|
||||||
|
Validation="@this.ValidateMaxChunkTokenLength"
|
||||||
|
HelperText="@T("Maximum number of tokens per chunk for this data source. Use 0 to use the embedding provider setting.")"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.DataObject"
|
||||||
|
AdornmentColor="Color.Info"
|
||||||
|
/>
|
||||||
|
<MudNumericField
|
||||||
|
T="int"
|
||||||
|
@bind-Value="@this.dataChunkOverlapTokenLength"
|
||||||
|
Label="@T("Chunk overlap length")"
|
||||||
|
Min="0"
|
||||||
|
Immediate="@true"
|
||||||
|
Validation="@this.ValidateChunkOverlapTokenLength"
|
||||||
|
HelperText="@T("Number of tokens repeated at the start of the next chunk. Use 0 to disable overlap.")"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.CompareArrows"
|
||||||
|
AdornmentColor="Color.Info"
|
||||||
|
/>
|
||||||
|
</MudCollapse>
|
||||||
|
</MudStack>
|
||||||
}
|
}
|
||||||
|
|
||||||
<ManagePandocDependency IntroText="@T("For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.")"/>
|
<ManagePandocDependency IntroText="@T("For some data types, such as Office files, MindWork AI Studio requires the open-source application Pandoc.")"/>
|
||||||
@ -99,4 +138,4 @@
|
|||||||
}
|
}
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</MudDialog>
|
</MudDialog>
|
||||||
|
|||||||
@ -41,7 +41,10 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
|
|||||||
private bool dataUserAcknowledgedCloudEmbedding;
|
private bool dataUserAcknowledgedCloudEmbedding;
|
||||||
private string dataEmbeddingId = string.Empty;
|
private string dataEmbeddingId = string.Empty;
|
||||||
private string dataFilePath = string.Empty;
|
private string dataFilePath = string.Empty;
|
||||||
|
private int dataMaxChunkTokenLength;
|
||||||
|
private int dataChunkOverlapTokenLength;
|
||||||
private ushort dataMaxMatches = 10;
|
private ushort dataMaxMatches = 10;
|
||||||
|
private bool showExpertSettings;
|
||||||
private DataSourceSecurity dataSecurityPolicy;
|
private DataSourceSecurity dataSecurityPolicy;
|
||||||
|
|
||||||
// We get the form reference from Blazor code to validate it manually:
|
// We get the form reference from Blazor code to validate it manually:
|
||||||
@ -77,8 +80,11 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
|
|||||||
this.dataDescription = this.DataSource.Description;
|
this.dataDescription = this.DataSource.Description;
|
||||||
this.dataEmbeddingId = this.DataSource.EmbeddingId;
|
this.dataEmbeddingId = this.DataSource.EmbeddingId;
|
||||||
this.dataFilePath = this.DataSource.FilePath;
|
this.dataFilePath = this.DataSource.FilePath;
|
||||||
|
this.dataMaxChunkTokenLength = this.DataSource.MaxChunkTokenLength;
|
||||||
|
this.dataChunkOverlapTokenLength = this.DataSource.ChunkOverlapTokenLength;
|
||||||
this.dataSecurityPolicy = this.DataSource.SecurityPolicy;
|
this.dataSecurityPolicy = this.DataSource.SecurityPolicy;
|
||||||
this.dataMaxMatches = this.DataSource.MaxMatches;
|
this.dataMaxMatches = this.DataSource.MaxMatches;
|
||||||
|
this.showExpertSettings = this.dataMaxChunkTokenLength > 0 || this.dataChunkOverlapTokenLength > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
await base.OnInitializedAsync();
|
await base.OnInitializedAsync();
|
||||||
@ -96,8 +102,10 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private bool SelectedCloudEmbedding => this.SettingsManager.ConfigurationData.EmbeddingProviders
|
private EmbeddingProvider? SelectedEmbedding => this.SettingsManager.ConfigurationData.EmbeddingProviders
|
||||||
.FirstOrDefault(x => x.Id == this.dataEmbeddingId) is { IsSelfHosted: false };
|
.FirstOrDefault(x => x.Id == this.dataEmbeddingId);
|
||||||
|
|
||||||
|
private bool SelectedCloudEmbedding => this.SelectedEmbedding is { IsSelfHosted: false };
|
||||||
|
|
||||||
private DataSourceLocalFile CreateDataSource() => new()
|
private DataSourceLocalFile CreateDataSource() => new()
|
||||||
{
|
{
|
||||||
@ -108,6 +116,8 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
|
|||||||
Type = DataSourceType.LOCAL_FILE,
|
Type = DataSourceType.LOCAL_FILE,
|
||||||
EmbeddingId = this.dataEmbeddingId,
|
EmbeddingId = this.dataEmbeddingId,
|
||||||
FilePath = this.dataFilePath,
|
FilePath = this.dataFilePath,
|
||||||
|
MaxChunkTokenLength = this.dataMaxChunkTokenLength,
|
||||||
|
ChunkOverlapTokenLength = this.dataChunkOverlapTokenLength,
|
||||||
SecurityPolicy = this.dataSecurityPolicy,
|
SecurityPolicy = this.dataSecurityPolicy,
|
||||||
MaxMatches = this.dataMaxMatches,
|
MaxMatches = this.dataMaxMatches,
|
||||||
};
|
};
|
||||||
@ -125,4 +135,34 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Cancel() => this.MudDialog.Cancel();
|
private void Cancel() => this.MudDialog.Cancel();
|
||||||
}
|
|
||||||
|
private string? ValidateMaxChunkTokenLength(int maxChunkTokenLength)
|
||||||
|
{
|
||||||
|
if (maxChunkTokenLength < 0)
|
||||||
|
return T("Please enter 0 or a positive max chunk length.");
|
||||||
|
|
||||||
|
var providerMaxChunkTokenLength = this.SelectedEmbedding?.EffectiveTokenLimit ?? EmbeddingProvider.DEFAULT_TOKEN_LIMIT;
|
||||||
|
if (maxChunkTokenLength > providerMaxChunkTokenLength)
|
||||||
|
return string.Format(T("The data source max chunk length must be less than or equal to the embedding provider limit ({0})."), providerMaxChunkTokenLength);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? ValidateChunkOverlapTokenLength(int chunkOverlapTokenLength)
|
||||||
|
{
|
||||||
|
if (chunkOverlapTokenLength < 0)
|
||||||
|
return T("Please enter 0 or a positive overlap length.");
|
||||||
|
|
||||||
|
var effectiveMaxChunkTokenLength = this.dataMaxChunkTokenLength > 0
|
||||||
|
? this.dataMaxChunkTokenLength
|
||||||
|
: this.SelectedEmbedding?.EffectiveTokenLimit ?? EmbeddingProvider.DEFAULT_TOKEN_LIMIT;
|
||||||
|
if (chunkOverlapTokenLength >= effectiveMaxChunkTokenLength)
|
||||||
|
return T("The overlap must be smaller than the effective max chunk length.");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleExpertSettings() => this.showExpertSettings = !this.showExpertSettings;
|
||||||
|
|
||||||
|
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
|
||||||
|
}
|
||||||
|
|||||||
@ -122,35 +122,55 @@
|
|||||||
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
|
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
|
||||||
AdornmentColor="Color.Info"
|
AdornmentColor="Color.Info"
|
||||||
Validation="@this.providerValidation.ValidatingInstanceName"
|
Validation="@this.providerValidation.ValidatingInstanceName"
|
||||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
|
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
|
||||||
@if (this.DataLLMProvider != LLMProviders.NONE)
|
@if (this.DataLLMProvider != LLMProviders.NONE)
|
||||||
{
|
{
|
||||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
<MudStack>
|
||||||
@T("For better embeddings and less storage usage, it's recommended to use a custom tokenizer to enable a more accurate token count.")
|
<MudButton OnClick="@this.ToggleExpertSettings">
|
||||||
</MudJustifiedText>
|
@(this.showExpertSettings ? T("Hide Expert Settings") : T("Show Expert Settings"))
|
||||||
<MudNumericField
|
</MudButton>
|
||||||
T="int"
|
<MudDivider />
|
||||||
@bind-Value="@this.DataTokenLimit"
|
<MudCollapse Expanded="@this.showExpertSettings" Class="@this.GetExpertStyles">
|
||||||
Label="@T("Token limit")"
|
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||||
Class="mb-3"
|
@T("Please be aware: This section is for experts only. For cloud providers, the selected tokenizer and chunk settings may not match the real embedding model limits exactly.")
|
||||||
Min="1"
|
</MudJustifiedText>
|
||||||
Immediate="@true"
|
<MudNumericField
|
||||||
Adornment="Adornment.Start"
|
T="int"
|
||||||
AdornmentIcon="@Icons.Material.Filled.Numbers"
|
@bind-Value="@this.DataTokenLimit"
|
||||||
AdornmentColor="Color.Info"
|
Label="@T("Token limit")"
|
||||||
Validation="@this.ValidateTokenLimit"
|
Class="mb-3"
|
||||||
HelperText="@T("Maximum number of tokens sent to the embedding model per chunk.")"/>
|
Min="1"
|
||||||
<SelectFile
|
Immediate="@true"
|
||||||
File="@this.dataFilePath"
|
Adornment="Adornment.Start"
|
||||||
FileChanged="@this.OnDataFilePathChanged"
|
AdornmentIcon="@Icons.Material.Filled.Numbers"
|
||||||
Label="@T("Selected file path for the custom tokenizer")"
|
AdornmentColor="Color.Info"
|
||||||
FileDialogTitle="@T("Choose a custom tokenizer here")"
|
Validation="@this.ValidateTokenLimit"
|
||||||
Filter="[FileTypes.JSON]"
|
HelperText="@T("Maximum number of tokens sent to the embedding model per chunk. The default is 16,384.")"/>
|
||||||
IsClearable="@true"
|
<MudNumericField
|
||||||
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
|
T="int"
|
||||||
ErrorText="@(this.dataCustomTokenizerValidationIssue)"
|
@bind-Value="@this.DataEmbeddingBatchSize"
|
||||||
Validation="@this.providerValidation.ValidatingCustomTokenizer"
|
Label="@T("Embedding batch size")"
|
||||||
OnClear="@this.ClearPathTokenizer" />
|
Class="mb-3"
|
||||||
|
Min="1"
|
||||||
|
Immediate="@true"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.FormatListNumbered"
|
||||||
|
AdornmentColor="Color.Info"
|
||||||
|
Validation="@this.ValidateEmbeddingBatchSize"
|
||||||
|
HelperText="@T("How many chunks are sent to the embedding provider at once. The default is 1.")"/>
|
||||||
|
<SelectFile
|
||||||
|
File="@this.dataFilePath"
|
||||||
|
FileChanged="@this.OnDataFilePathChanged"
|
||||||
|
Label="@T("Selected file path for the custom tokenizer")"
|
||||||
|
FileDialogTitle="@T("Choose a custom tokenizer here")"
|
||||||
|
Filter="[FileTypes.JSON]"
|
||||||
|
IsClearable="@true"
|
||||||
|
Error="@(!string.IsNullOrWhiteSpace(this.dataCustomTokenizerValidationIssue))"
|
||||||
|
ErrorText="@(this.dataCustomTokenizerValidationIssue)"
|
||||||
|
Validation="@this.providerValidation.ValidatingCustomTokenizer"
|
||||||
|
OnClear="@this.ClearPathTokenizer" />
|
||||||
|
</MudCollapse>
|
||||||
|
</MudStack>
|
||||||
}
|
}
|
||||||
</MudForm>
|
</MudForm>
|
||||||
@if (this.dataStoreWasAttempted)
|
@if (this.dataStoreWasAttempted)
|
||||||
|
|||||||
@ -76,6 +76,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int DataTokenLimit { get; set; } = EmbeddingProvider.DEFAULT_TOKEN_LIMIT;
|
public int DataTokenLimit { get; set; } = EmbeddingProvider.DEFAULT_TOKEN_LIMIT;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int DataEmbeddingBatchSize { get; set; } = EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
private RustService RustService { get; init; } = null!;
|
private RustService RustService { get; init; } = null!;
|
||||||
@ -101,6 +104,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
private string dataCustomTokenizerValidationIssue = string.Empty;
|
private string dataCustomTokenizerValidationIssue = string.Empty;
|
||||||
private Task dataTokenizerValidationTask = Task.CompletedTask;
|
private Task dataTokenizerValidationTask = Task.CompletedTask;
|
||||||
private bool dataStoreWasAttempted;
|
private bool dataStoreWasAttempted;
|
||||||
|
private bool showExpertSettings;
|
||||||
private int dataTokenizerValidationRevision;
|
private int dataTokenizerValidationRevision;
|
||||||
|
|
||||||
// We get the form reference from Blazor code to validate it manually:
|
// We get the form reference from Blazor code to validate it manually:
|
||||||
@ -151,6 +155,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
IsEnterpriseConfiguration = false,
|
IsEnterpriseConfiguration = false,
|
||||||
EnterpriseConfigurationPluginId = Guid.Empty,
|
EnterpriseConfigurationPluginId = Guid.Empty,
|
||||||
TokenizerPath = this.dataFilePath,
|
TokenizerPath = this.dataFilePath,
|
||||||
|
EmbeddingBatchSize = this.DataEmbeddingBatchSize,
|
||||||
TokenLimit = this.DataTokenLimit,
|
TokenLimit = this.DataTokenLimit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -173,6 +178,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
{
|
{
|
||||||
this.dataEditingPreviousInstanceName = this.DataName.ToLowerInvariant();
|
this.dataEditingPreviousInstanceName = this.DataName.ToLowerInvariant();
|
||||||
this.dataFilePath = this.DataTokenizerPath;
|
this.dataFilePath = this.DataTokenizerPath;
|
||||||
|
this.showExpertSettings = !string.IsNullOrWhiteSpace(this.DataTokenizerPath)
|
||||||
|
|| this.DataTokenLimit != EmbeddingProvider.DEFAULT_TOKEN_LIMIT
|
||||||
|
|| this.DataEmbeddingBatchSize != EmbeddingProvider.DEFAULT_EMBEDDING_BATCH_SIZE;
|
||||||
|
|
||||||
// When using self-hosted embedding, we must copy the model name:
|
// When using self-hosted embedding, we must copy the model name:
|
||||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
||||||
@ -289,6 +297,14 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string? ValidateEmbeddingBatchSize(int embeddingBatchSize)
|
||||||
|
{
|
||||||
|
if (embeddingBatchSize < 1)
|
||||||
|
return T("Please enter an embedding batch size greater than 0.");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void Cancel() => this.MudDialog.Cancel();
|
private void Cancel() => this.MudDialog.Cancel();
|
||||||
|
|
||||||
private async Task OnAPIKeyChanged(string apiKey)
|
private async Task OnAPIKeyChanged(string apiKey)
|
||||||
@ -411,4 +427,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
|||||||
};
|
};
|
||||||
|
|
||||||
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
|
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
|
||||||
|
|
||||||
|
private void ToggleExpertSettings() => this.showExpertSettings = !this.showExpertSettings;
|
||||||
|
|
||||||
|
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
@attribute [Route(Routes.EMBEDDINGS)]
|
@attribute [Route(Routes.EMBEDDINGS)]
|
||||||
@inherits MSGComponentBase
|
@inherits MSGComponentBase
|
||||||
|
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3" Class="pr-2 pb-4" Style="height: 100%; min-height: 0; overflow-y: auto;">
|
||||||
<MudPaper Class="pa-4 border-dashed border rounded-lg" Elevation="0">
|
<MudPaper Class="pa-4 border-dashed border rounded-lg" Elevation="0">
|
||||||
<MudText Typo="Typo.h5">@T("Background embeddings")</MudText>
|
<MudText Typo="Typo.h5">@T("Background embeddings")</MudText>
|
||||||
<MudText Typo="Typo.body1" Class="mt-2">
|
<MudText Typo="Typo.body1" Class="mt-2">
|
||||||
|
|||||||
@ -137,7 +137,10 @@ CONFIG["EMBEDDING_PROVIDERS"] = {}
|
|||||||
-- -- ["TokenizerPath"] = "",
|
-- -- ["TokenizerPath"] = "",
|
||||||
--
|
--
|
||||||
-- -- Optional: maximum number of tokens per embedding chunk. If omitted, AI Studio uses its default.
|
-- -- Optional: maximum number of tokens per embedding chunk. If omitted, AI Studio uses its default.
|
||||||
-- -- ["TokenLimit"] = 8191,
|
-- -- ["TokenLimit"] = 16384,
|
||||||
|
--
|
||||||
|
-- -- Optional: number of chunks sent to the embedding provider in one request. If omitted, AI Studio sends one chunk per request.
|
||||||
|
-- -- ["EmbeddingBatchSize"] = 1,
|
||||||
--
|
--
|
||||||
-- ["Model"] = {
|
-- ["Model"] = {
|
||||||
-- ["Id"] = "<the model ID, e.g., nomic-embed-text>",
|
-- ["Id"] = "<the model ID, e.g., nomic-embed-text>",
|
||||||
|
|||||||
@ -32,6 +32,12 @@ public readonly record struct DataSourceLocalDirectory : IInternalDataSource
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string EmbeddingId { get; init; } = Guid.Empty.ToString();
|
public string EmbeddingId { get; init; } = Guid.Empty.ToString();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int MaxChunkTokenLength { get; init; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int ChunkOverlapTokenLength { get; init; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public DataSourceSecurity SecurityPolicy { get; init; } = DataSourceSecurity.NOT_SPECIFIED;
|
public DataSourceSecurity SecurityPolicy { get; init; } = DataSourceSecurity.NOT_SPECIFIED;
|
||||||
|
|||||||
@ -32,6 +32,12 @@ public readonly record struct DataSourceLocalFile : IInternalDataSource
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string EmbeddingId { get; init; } = Guid.Empty.ToString();
|
public string EmbeddingId { get; init; } = Guid.Empty.ToString();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int MaxChunkTokenLength { get; init; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int ChunkOverlapTokenLength { get; init; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public DataSourceSecurity SecurityPolicy { get; init; } = DataSourceSecurity.NOT_SPECIFIED;
|
public DataSourceSecurity SecurityPolicy { get; init; } = DataSourceSecurity.NOT_SPECIFIED;
|
||||||
|
|||||||
@ -22,10 +22,11 @@ public sealed record EmbeddingProvider(
|
|||||||
string Hostname = "http://localhost:1234",
|
string Hostname = "http://localhost:1234",
|
||||||
Host Host = Host.NONE,
|
Host Host = Host.NONE,
|
||||||
string TokenizerPath = "",
|
string TokenizerPath = "",
|
||||||
int TokenLimit = 8_191) : ConfigurationBaseObject, ISecretId
|
int EmbeddingBatchSize = 0,
|
||||||
|
int TokenLimit = 0) : ConfigurationBaseObject, ISecretId
|
||||||
{
|
{
|
||||||
public const int DEFAULT_TOKEN_LIMIT = 8_191;
|
public const int DEFAULT_TOKEN_LIMIT = 8192;
|
||||||
private const int ASSUMED_TOKEN_LIMIT_SAFETY_PERCENT = 80;
|
public const int DEFAULT_EMBEDDING_BATCH_SIZE = 1;
|
||||||
|
|
||||||
private static readonly ILogger<EmbeddingProvider> LOGGER = Program.LOGGER_FACTORY.CreateLogger<EmbeddingProvider>();
|
private static readonly ILogger<EmbeddingProvider> LOGGER = Program.LOGGER_FACTORY.CreateLogger<EmbeddingProvider>();
|
||||||
|
|
||||||
@ -59,14 +60,7 @@ public sealed record EmbeddingProvider(
|
|||||||
public int EffectiveTokenLimit => this.TokenLimit > 0 ? this.TokenLimit : DEFAULT_TOKEN_LIMIT;
|
public int EffectiveTokenLimit => this.TokenLimit > 0 ? this.TokenLimit : DEFAULT_TOKEN_LIMIT;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool UsesAssumedTokenSizing => string.IsNullOrWhiteSpace(this.TokenizerPath)
|
public int EffectiveEmbeddingBatchSize => this.EmbeddingBatchSize > 0 ? this.EmbeddingBatchSize : DEFAULT_EMBEDDING_BATCH_SIZE;
|
||||||
|| this.TokenLimit <= 0
|
|
||||||
|| this.TokenLimit == DEFAULT_TOKEN_LIMIT;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public int EffectiveChunkTokenLimit => this.UsesAssumedTokenSizing
|
|
||||||
? Math.Max(1, (int)Math.Ceiling(this.EffectiveTokenLimit * ASSUMED_TOKEN_LIMIT_SAFETY_PERCENT / 100d) - 1)
|
|
||||||
: this.EffectiveTokenLimit;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -129,6 +123,13 @@ public sealed record EmbeddingProvider(
|
|||||||
tokenLimit = DEFAULT_TOKEN_LIMIT;
|
tokenLimit = DEFAULT_TOKEN_LIMIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var embeddingBatchSize = DEFAULT_EMBEDDING_BATCH_SIZE;
|
||||||
|
if (table.TryGetValue("EmbeddingBatchSize", out var embeddingBatchSizeValue) && (!embeddingBatchSizeValue.TryRead<int>(out embeddingBatchSize) || embeddingBatchSize < 1))
|
||||||
|
{
|
||||||
|
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid embedding batch size. Falling back to {DEFAULT_EMBEDDING_BATCH_SIZE}. (Plugin ID: {configPluginId})");
|
||||||
|
embeddingBatchSize = DEFAULT_EMBEDDING_BATCH_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
provider = new EmbeddingProvider
|
provider = new EmbeddingProvider
|
||||||
{
|
{
|
||||||
Num = 0, // will be set later by the PluginConfigurationObject
|
Num = 0, // will be set later by the PluginConfigurationObject
|
||||||
@ -142,6 +143,7 @@ public sealed record EmbeddingProvider(
|
|||||||
Hostname = hostname,
|
Hostname = hostname,
|
||||||
Host = host,
|
Host = host,
|
||||||
TokenizerPath = tokenizerPath,
|
TokenizerPath = tokenizerPath,
|
||||||
|
EmbeddingBatchSize = embeddingBatchSize,
|
||||||
TokenLimit = tokenLimit,
|
TokenLimit = tokenLimit,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -218,6 +220,7 @@ public sealed record EmbeddingProvider(
|
|||||||
|
|
||||||
["TokenizerPath"] = "{{this.TokenizerPath}}",
|
["TokenizerPath"] = "{{this.TokenizerPath}}",
|
||||||
["TokenLimit"] = {{this.EffectiveTokenLimit}},
|
["TokenLimit"] = {{this.EffectiveTokenLimit}},
|
||||||
|
["EmbeddingBatchSize"] = {{this.EffectiveEmbeddingBatchSize}},
|
||||||
|
|
||||||
["Host"] = "{{this.Host}}",
|
["Host"] = "{{this.Host}}",
|
||||||
["Hostname"] = "{{LuaTools.EscapeLuaString(this.Hostname)}}",
|
["Hostname"] = "{{LuaTools.EscapeLuaString(this.Hostname)}}",
|
||||||
|
|||||||
@ -6,4 +6,15 @@ public interface IInternalDataSource : IDataSource
|
|||||||
/// The unique identifier of the embedding method used by this internal data source.
|
/// The unique identifier of the embedding method used by this internal data source.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string EmbeddingId { get; init; }
|
public string EmbeddingId { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional maximum number of tokens per embedding chunk for this data source.
|
||||||
|
/// A value of 0 means the embedding provider's setting is used.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxChunkTokenLength { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Optional number of tokens to overlap between consecutive chunks.
|
||||||
|
/// </summary>
|
||||||
|
public int ChunkOverlapTokenLength { get; init; }
|
||||||
}
|
}
|
||||||
@ -25,118 +25,137 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
UNSUPPORTED,
|
UNSUPPORTED,
|
||||||
}
|
}
|
||||||
|
|
||||||
private async IAsyncEnumerable<string> StreamEmbeddingChunksAsync(string filePath, EmbeddingProvider embeddingProvider, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken token)
|
private sealed record ExtractedFileContent(string Text, IReadOnlyList<string> SourceSegments);
|
||||||
|
|
||||||
|
private sealed record ChunkingOptions(int MaxChunkTokenLength, int OverlapTokenLength);
|
||||||
|
|
||||||
|
private sealed record ChunkingStrategy(string Name, IReadOnlyList<ChunkingRule> Rules);
|
||||||
|
|
||||||
|
private sealed record ChunkingRule(string Name, Func<string, IReadOnlyList<string>, IReadOnlyList<string>>? Split);
|
||||||
|
|
||||||
|
private async IAsyncEnumerable<string> StreamEmbeddingChunksAsync(string filePath, IDataSource dataSource, EmbeddingProvider embeddingProvider, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken token)
|
||||||
{
|
{
|
||||||
|
var options = this.GetChunkingOptions(dataSource, embeddingProvider);
|
||||||
|
var strategy = this.GetChunkingStrategy(filePath);
|
||||||
|
ExtractedFileContent content;
|
||||||
|
|
||||||
if (this.IsImageFilePath(filePath))
|
if (this.IsImageFilePath(filePath))
|
||||||
{
|
{
|
||||||
await foreach (var imageChunk in this.SplitChunkByEmbeddingTokenLimitAsync(this.BuildImageIndexText(filePath), embeddingProvider, token))
|
var imageIndexText = this.BuildImageIndexText(filePath);
|
||||||
yield return imageChunk;
|
content = new(imageIndexText, [imageIndexText]);
|
||||||
|
}
|
||||||
yield break;
|
else
|
||||||
|
{
|
||||||
|
content = await this.ReadExtractedFileContentAsync(filePath, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentChunk = new StringBuilder();
|
await foreach (var chunk in this.SplitByChunkingStrategyAsync(content, strategy, options, embeddingProvider, token))
|
||||||
|
yield return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ExtractedFileContent> ReadExtractedFileContentAsync(string filePath, CancellationToken token)
|
||||||
|
{
|
||||||
|
var segments = new List<string>();
|
||||||
|
|
||||||
await foreach (var segment in rustService.StreamArbitraryFileData(filePath, token: token))
|
await foreach (var segment in rustService.StreamArbitraryFileData(filePath, token: token))
|
||||||
{
|
{
|
||||||
var normalized = NormalizeChunkSegment(segment);
|
var normalized = NormalizeChunkSegment(segment);
|
||||||
if (string.IsNullOrWhiteSpace(normalized))
|
if (!string.IsNullOrWhiteSpace(normalized))
|
||||||
continue;
|
segments.Add(normalized);
|
||||||
|
|
||||||
if (currentChunk.Length > 0 && currentChunk.Length + normalized.Length + Environment.NewLine.Length > MAX_CHUNK_LENGTH)
|
|
||||||
{
|
|
||||||
if (currentChunk.Length >= MIN_CHUNK_LENGTH)
|
|
||||||
{
|
|
||||||
var chunk = currentChunk.ToString().Trim();
|
|
||||||
if (!string.IsNullOrWhiteSpace(chunk))
|
|
||||||
{
|
|
||||||
await foreach (var splitChunk in this.SplitChunkByEmbeddingTokenLimitAsync(chunk, embeddingProvider, token))
|
|
||||||
yield return splitChunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
var overlap = chunk.Length > CHUNK_OVERLAP_LENGTH
|
|
||||||
? chunk[^CHUNK_OVERLAP_LENGTH..]
|
|
||||||
: chunk;
|
|
||||||
|
|
||||||
currentChunk.Clear();
|
|
||||||
currentChunk.Append(overlap);
|
|
||||||
currentChunk.AppendLine();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
currentChunk.AppendLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentChunk.Append(normalized);
|
|
||||||
currentChunk.AppendLine();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalChunk = currentChunk.ToString().Trim();
|
return new(string.Join("\n", segments).Trim(), segments);
|
||||||
if (!string.IsNullOrWhiteSpace(finalChunk))
|
|
||||||
{
|
|
||||||
await foreach (var chunk in this.SplitChunkByEmbeddingTokenLimitAsync(finalChunk, embeddingProvider, token))
|
|
||||||
yield return chunk;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async IAsyncEnumerable<string> SplitChunkByEmbeddingTokenLimitAsync(string chunk, EmbeddingProvider embeddingProvider, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken token)
|
private async IAsyncEnumerable<string> SplitByChunkingStrategyAsync(ExtractedFileContent content, ChunkingStrategy strategy, ChunkingOptions options, EmbeddingProvider embeddingProvider, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken token)
|
||||||
{
|
{
|
||||||
var tokenLimit = embeddingProvider.EffectiveChunkTokenLimit;
|
await foreach (var chunk in this.SplitTextByRulesAsync(content.Text, content.SourceSegments, strategy, 0, options, embeddingProvider, token))
|
||||||
var tokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, chunk, token);
|
|
||||||
if (tokenCount <= tokenLimit)
|
|
||||||
{
|
|
||||||
yield return chunk;
|
yield return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async IAsyncEnumerable<string> SplitTextByRulesAsync(
|
||||||
|
string text,
|
||||||
|
IReadOnlyList<string> sourceSegments,
|
||||||
|
ChunkingStrategy strategy,
|
||||||
|
int ruleIndex,
|
||||||
|
ChunkingOptions options,
|
||||||
|
EmbeddingProvider embeddingProvider,
|
||||||
|
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken token)
|
||||||
|
{
|
||||||
|
text = text.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
var tokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, text, token);
|
||||||
|
if (tokenCount <= options.MaxChunkTokenLength)
|
||||||
|
{
|
||||||
|
yield return text;
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (embeddingProvider.UsesAssumedTokenSizing)
|
if (ruleIndex >= strategy.Rules.Count)
|
||||||
{
|
{
|
||||||
logger.LogDebug(
|
await foreach (var hardChunk in this.SplitTextByHardCutAsync(text, options, embeddingProvider, token))
|
||||||
"Using conservative embedding chunk limit {ChunkTokenLimit} for provider '{EmbeddingProviderName}' because tokenizer or token limit sizing is assumed. ConfiguredTokenLimit={ConfiguredTokenLimit}.",
|
yield return hardChunk;
|
||||||
tokenLimit,
|
|
||||||
embeddingProvider.Name,
|
yield break;
|
||||||
embeddingProvider.EffectiveTokenLimit);
|
}
|
||||||
|
|
||||||
|
var rule = strategy.Rules[ruleIndex];
|
||||||
|
if (rule.Split is null)
|
||||||
|
{
|
||||||
|
await foreach (var hardChunk in this.SplitTextByHardCutAsync(text, options, embeddingProvider, token))
|
||||||
|
yield return hardChunk;
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var units = NormalizeSplitUnits(rule.Split(text, sourceSegments), text);
|
||||||
|
if (units.Count <= 1)
|
||||||
|
{
|
||||||
|
await foreach (var chunk in this.SplitTextByRulesAsync(text, sourceSegments, strategy, ruleIndex + 1, options, embeddingProvider, token))
|
||||||
|
yield return chunk;
|
||||||
|
|
||||||
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogDebug(
|
logger.LogDebug(
|
||||||
"Splitting an embedding chunk for provider '{EmbeddingProviderName}' because it has {TokenCount} tokens and the configured limit is {TokenLimit}.",
|
"Splitting content for embedding provider '{EmbeddingProviderName}' with strategy '{ChunkingStrategy}' and rule '{ChunkingRule}'. TokenCount={TokenCount}, MaxChunkTokenLength={MaxChunkTokenLength}.",
|
||||||
embeddingProvider.Name,
|
embeddingProvider.Name,
|
||||||
|
strategy.Name,
|
||||||
|
rule.Name,
|
||||||
tokenCount,
|
tokenCount,
|
||||||
tokenLimit);
|
options.MaxChunkTokenLength);
|
||||||
|
|
||||||
await foreach (var splitChunk in this.SplitTextByTokenLimitAsync(chunk, embeddingProvider, tokenLimit, token))
|
|
||||||
yield return splitChunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async IAsyncEnumerable<string> SplitTextByTokenLimitAsync(string text, EmbeddingProvider embeddingProvider, int tokenLimit, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken token)
|
|
||||||
{
|
|
||||||
var units = SplitTextIntoTokenUnits(text);
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
|
|
||||||
while (index < units.Count)
|
while (index < units.Count)
|
||||||
{
|
{
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var unitCount = await this.FindLargestUnitCountWithinTokenLimitAsync(units, index, embeddingProvider, tokenLimit, token);
|
var unitCount = await this.FindLargestUnitCountWithinMaxChunkLengthAsync(units, index, embeddingProvider, options.MaxChunkTokenLength, token);
|
||||||
if (unitCount > 0)
|
if (unitCount > 0)
|
||||||
{
|
{
|
||||||
var chunk = string.Concat(units.Skip(index).Take(unitCount)).Trim();
|
var chunk = string.Concat(units.Skip(index).Take(unitCount)).Trim();
|
||||||
if (!string.IsNullOrWhiteSpace(chunk))
|
if (!string.IsNullOrWhiteSpace(chunk))
|
||||||
yield return chunk;
|
yield return chunk;
|
||||||
|
|
||||||
index += unitCount;
|
var nextIndex = index + unitCount;
|
||||||
|
if (nextIndex >= units.Count)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
index = await this.CalculateNextStartIndexAsync(units, index, nextIndex, options, embeddingProvider, token);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await foreach (var splitUnit in this.SplitOversizedTextUnitByTokenLimitAsync(units[index], embeddingProvider, tokenLimit, token))
|
await foreach (var splitUnit in this.SplitTextByRulesAsync(units[index], [units[index]], strategy, ruleIndex + 1, options, embeddingProvider, token))
|
||||||
yield return splitUnit;
|
yield return splitUnit;
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> FindLargestUnitCountWithinTokenLimitAsync(IReadOnlyList<string> units, int startIndex, EmbeddingProvider embeddingProvider, int tokenLimit, CancellationToken token)
|
private async Task<int> FindLargestUnitCountWithinMaxChunkLengthAsync(IReadOnlyList<string> units, int startIndex, EmbeddingProvider embeddingProvider, int maxChunkTokenLength, CancellationToken token)
|
||||||
{
|
{
|
||||||
var low = 1;
|
var low = 1;
|
||||||
var high = units.Count - startIndex;
|
var high = units.Count - startIndex;
|
||||||
@ -149,7 +168,7 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
var mid = low + (high - low) / 2;
|
var mid = low + (high - low) / 2;
|
||||||
var candidate = string.Concat(units.Skip(startIndex).Take(mid)).Trim();
|
var candidate = string.Concat(units.Skip(startIndex).Take(mid)).Trim();
|
||||||
var tokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, candidate, token);
|
var tokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, candidate, token);
|
||||||
if (tokenCount <= tokenLimit)
|
if (tokenCount <= maxChunkTokenLength)
|
||||||
{
|
{
|
||||||
best = mid;
|
best = mid;
|
||||||
low = mid + 1;
|
low = mid + 1;
|
||||||
@ -161,7 +180,38 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async IAsyncEnumerable<string> SplitOversizedTextUnitByTokenLimitAsync(string text, EmbeddingProvider embeddingProvider, int tokenLimit, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken token)
|
private async Task<int> CalculateNextStartIndexAsync(IReadOnlyList<string> units, int chunkStartIndex, int chunkEndIndex, ChunkingOptions options, EmbeddingProvider embeddingProvider, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (options.OverlapTokenLength <= 0)
|
||||||
|
return chunkEndIndex;
|
||||||
|
|
||||||
|
var bestStartIndex = chunkEndIndex;
|
||||||
|
var bestDistance = int.MaxValue;
|
||||||
|
|
||||||
|
for (var candidateStartIndex = chunkEndIndex - 1; candidateStartIndex > chunkStartIndex; candidateStartIndex--)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var candidate = string.Concat(units.Skip(candidateStartIndex).Take(chunkEndIndex - candidateStartIndex)).Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(candidate))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var tokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, candidate, token);
|
||||||
|
var distance = Math.Abs(tokenCount - options.OverlapTokenLength);
|
||||||
|
if (distance < bestDistance)
|
||||||
|
{
|
||||||
|
bestStartIndex = candidateStartIndex;
|
||||||
|
bestDistance = distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenCount >= options.OverlapTokenLength && bestStartIndex < chunkEndIndex)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestStartIndex <= chunkStartIndex ? chunkEndIndex : bestStartIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async IAsyncEnumerable<string> SplitTextByHardCutAsync(string text, ChunkingOptions options, EmbeddingProvider embeddingProvider, [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken token)
|
||||||
{
|
{
|
||||||
var startIndex = 0;
|
var startIndex = 0;
|
||||||
while (startIndex < text.Length)
|
while (startIndex < text.Length)
|
||||||
@ -177,7 +227,7 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
var mid = low + (high - low) / 2;
|
var mid = low + (high - low) / 2;
|
||||||
var candidate = text[startIndex..mid].Trim();
|
var candidate = text[startIndex..mid].Trim();
|
||||||
var tokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, candidate, token);
|
var tokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, candidate, token);
|
||||||
if (tokenCount <= tokenLimit)
|
if (tokenCount <= options.MaxChunkTokenLength)
|
||||||
{
|
{
|
||||||
bestEndIndex = mid;
|
bestEndIndex = mid;
|
||||||
low = mid + 1;
|
low = mid + 1;
|
||||||
@ -190,17 +240,48 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
{
|
{
|
||||||
var smallestCandidate = text[startIndex..Math.Min(startIndex + 1, text.Length)].Trim();
|
var smallestCandidate = text[startIndex..Math.Min(startIndex + 1, text.Length)].Trim();
|
||||||
var smallestCandidateTokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, smallestCandidate, token);
|
var smallestCandidateTokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, smallestCandidate, token);
|
||||||
throw new InvalidOperationException($"The token limit for embedding provider '{embeddingProvider.Name}' is too low. The smallest possible split still has {smallestCandidateTokenCount} tokens, but the configured limit is {tokenLimit}.");
|
throw new InvalidOperationException($"The max chunk length for embedding provider '{embeddingProvider.Name}' is too low. The smallest possible split still has {smallestCandidateTokenCount} tokens, but the configured limit is {options.MaxChunkTokenLength}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var chunk = text[startIndex..bestEndIndex].Trim();
|
var chunk = text[startIndex..bestEndIndex].Trim();
|
||||||
if (!string.IsNullOrWhiteSpace(chunk))
|
if (!string.IsNullOrWhiteSpace(chunk))
|
||||||
yield return chunk;
|
yield return chunk;
|
||||||
|
|
||||||
startIndex = bestEndIndex;
|
if (bestEndIndex >= text.Length)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
startIndex = await this.CalculateHardCutOverlapStartIndexAsync(text, startIndex, bestEndIndex, options, embeddingProvider, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<int> CalculateHardCutOverlapStartIndexAsync(string text, int chunkStartIndex, int chunkEndIndex, ChunkingOptions options, EmbeddingProvider embeddingProvider, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (options.OverlapTokenLength <= 0 || chunkEndIndex - chunkStartIndex <= 1)
|
||||||
|
return chunkEndIndex;
|
||||||
|
|
||||||
|
var low = chunkStartIndex + 1;
|
||||||
|
var high = chunkEndIndex - 1;
|
||||||
|
var bestStartIndex = chunkEndIndex;
|
||||||
|
|
||||||
|
while (low <= high)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var mid = low + (high - low) / 2;
|
||||||
|
var candidate = text[mid..chunkEndIndex].Trim();
|
||||||
|
var tokenCount = await this.GetEmbeddingTokenCountAsync(embeddingProvider, candidate, token);
|
||||||
|
if (tokenCount <= options.OverlapTokenLength)
|
||||||
|
{
|
||||||
|
bestStartIndex = mid;
|
||||||
|
high = mid - 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
low = mid + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestStartIndex <= chunkStartIndex ? chunkEndIndex : bestStartIndex;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<int> GetEmbeddingTokenCountAsync(EmbeddingProvider embeddingProvider, string text, CancellationToken token)
|
private async Task<int> GetEmbeddingTokenCountAsync(EmbeddingProvider embeddingProvider, string text, CancellationToken token)
|
||||||
{
|
{
|
||||||
var response = await rustService.GetTokenCount(embeddingProvider.Name, embeddingProvider.TokenizerPath, text, token);
|
var response = await rustService.GetTokenCount(embeddingProvider.Name, embeddingProvider.TokenizerPath, text, token);
|
||||||
@ -211,7 +292,165 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
throw new InvalidOperationException($"Could not count tokens for embedding provider '{embeddingProvider.Name}'. {message}");
|
throw new InvalidOperationException($"Could not count tokens for embedding provider '{embeddingProvider.Name}'. {message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> SplitTextIntoTokenUnits(string text)
|
private ChunkingOptions GetChunkingOptions(IDataSource dataSource, EmbeddingProvider embeddingProvider)
|
||||||
|
{
|
||||||
|
var providerMaxChunkTokenLength = Math.Max(1, embeddingProvider.EffectiveTokenLimit);
|
||||||
|
var dataSourceMaxChunkTokenLength = dataSource is IInternalDataSource { MaxChunkTokenLength: > 0 } internalDataSource
|
||||||
|
? internalDataSource.MaxChunkTokenLength
|
||||||
|
: 0;
|
||||||
|
var maxChunkTokenLength = dataSourceMaxChunkTokenLength > 0
|
||||||
|
? Math.Min(dataSourceMaxChunkTokenLength, providerMaxChunkTokenLength)
|
||||||
|
: providerMaxChunkTokenLength;
|
||||||
|
|
||||||
|
var configuredOverlapTokenLength = dataSource is IInternalDataSource overlapDataSource
|
||||||
|
? overlapDataSource.ChunkOverlapTokenLength
|
||||||
|
: 0;
|
||||||
|
var overlapTokenLength = Math.Clamp(configuredOverlapTokenLength, 0, Math.Max(0, maxChunkTokenLength - 1));
|
||||||
|
|
||||||
|
return new(maxChunkTokenLength, overlapTokenLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChunkingStrategy GetChunkingStrategy(string filePath)
|
||||||
|
{
|
||||||
|
if (this.IsImageFilePath(filePath))
|
||||||
|
return new("image", [
|
||||||
|
new("Whitespace", SplitByWhitespace),
|
||||||
|
new("Hard cut", null),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (this.IsPresentationFilePath(filePath))
|
||||||
|
return new("presentation", [
|
||||||
|
new("Slide", SplitBySourceSegments),
|
||||||
|
new("Line break", SplitByLineBreaks),
|
||||||
|
new("Whitespace", SplitByWhitespace),
|
||||||
|
new("Hard cut", null),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (this.IsDelimitedTableFilePath(filePath) || this.IsSpreadsheetFilePath(filePath))
|
||||||
|
return new("table", [
|
||||||
|
new("Row or sheet", SplitBySourceSegments),
|
||||||
|
new("Line break", SplitByLineBreaks),
|
||||||
|
new("Whitespace", SplitByWhitespace),
|
||||||
|
new("Hard cut", null),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (this.IsSourceCodeFilePath(filePath))
|
||||||
|
return GetSourceCodeChunkingStrategy(filePath);
|
||||||
|
|
||||||
|
return new("document", [
|
||||||
|
new("Heading", SplitByDocumentHeadings),
|
||||||
|
new("Page or extracted section", SplitBySourceSegments),
|
||||||
|
new("Paragraph", SplitByParagraphs),
|
||||||
|
new("Line break", SplitByLineBreaks),
|
||||||
|
new("Whitespace", SplitByWhitespace),
|
||||||
|
new("Hard cut", null),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ChunkingStrategy GetSourceCodeChunkingStrategy(string filePath)
|
||||||
|
{
|
||||||
|
var rules = GetSourceCodeDelimiterRules(filePath).ToList();
|
||||||
|
rules.Add(new("Line break", SplitByLineBreaks));
|
||||||
|
rules.Add(new("Whitespace", SplitByWhitespace));
|
||||||
|
rules.Add(new("Hard cut", null));
|
||||||
|
|
||||||
|
return new("source-code", rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<ChunkingRule> GetSourceCodeDelimiterRules(string filePath) => Path.GetExtension(filePath).TrimStart('.') switch
|
||||||
|
{
|
||||||
|
_ => [],
|
||||||
|
};
|
||||||
|
|
||||||
|
private static List<string> NormalizeSplitUnits(IReadOnlyList<string> units, string fallbackText)
|
||||||
|
{
|
||||||
|
var result = units
|
||||||
|
.Where(unit => !string.IsNullOrWhiteSpace(unit))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return result.Count == 0 ? [fallbackText] : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> SplitBySourceSegments(string text, IReadOnlyList<string> sourceSegments)
|
||||||
|
{
|
||||||
|
return sourceSegments.Count > 1
|
||||||
|
? sourceSegments.Select(segment => segment + "\n").ToList()
|
||||||
|
: [text];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> SplitByDocumentHeadings(string text, IReadOnlyList<string> sourceSegments)
|
||||||
|
{
|
||||||
|
var lines = ReadLines(text);
|
||||||
|
if (lines.Count < 2)
|
||||||
|
return [text];
|
||||||
|
|
||||||
|
var result = new List<string>();
|
||||||
|
var segmentStart = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < lines.Count; i++)
|
||||||
|
{
|
||||||
|
var (lineStart, _, lineText) = lines[i];
|
||||||
|
if (lineStart == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var previousLine = i > 0 ? lines[i - 1].Text : string.Empty;
|
||||||
|
var nextLine = i + 1 < lines.Count ? lines[i + 1].Text : string.Empty;
|
||||||
|
if (!IsDocumentHeadingLine(lineText, previousLine, nextLine))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result.Add(text[segmentStart..lineStart]);
|
||||||
|
segmentStart = lineStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segmentStart == 0)
|
||||||
|
return [text];
|
||||||
|
|
||||||
|
result.Add(text[segmentStart..]);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> SplitByParagraphs(string text, IReadOnlyList<string> sourceSegments)
|
||||||
|
{
|
||||||
|
var matches = Regex.Matches(text, @"\n[ \t]*\n", RegexOptions.CultureInvariant);
|
||||||
|
if (matches.Count == 0)
|
||||||
|
return [text];
|
||||||
|
|
||||||
|
var result = new List<string>();
|
||||||
|
var start = 0;
|
||||||
|
foreach (Match match in matches)
|
||||||
|
{
|
||||||
|
var end = match.Index + match.Length;
|
||||||
|
result.Add(text[start..end]);
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < text.Length)
|
||||||
|
result.Add(text[start..]);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> SplitByLineBreaks(string text, IReadOnlyList<string> sourceSegments)
|
||||||
|
{
|
||||||
|
var result = new List<string>();
|
||||||
|
var start = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
if (text[i] != '\n')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result.Add(text[start..(i + 1)]);
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < text.Length)
|
||||||
|
result.Add(text[start..]);
|
||||||
|
|
||||||
|
return result.Count == 0 ? [text] : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> SplitByWhitespace(string text, IReadOnlyList<string> sourceSegments)
|
||||||
{
|
{
|
||||||
var matches = Regex.Matches(text, @"\S+\s*", RegexOptions.CultureInvariant);
|
var matches = Regex.Matches(text, @"\S+\s*", RegexOptions.CultureInvariant);
|
||||||
if (matches.Count == 0)
|
if (matches.Count == 0)
|
||||||
@ -220,6 +459,47 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
return matches.Cast<Match>().Select(match => match.Value).ToList();
|
return matches.Cast<Match>().Select(match => match.Value).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<(int Start, int End, string Text)> ReadLines(string text)
|
||||||
|
{
|
||||||
|
var result = new List<(int Start, int End, string Text)>();
|
||||||
|
var start = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
if (text[i] != '\n')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result.Add((start, i + 1, text[start..(i + 1)]));
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < text.Length)
|
||||||
|
result.Add((start, text.Length, text[start..]));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsDocumentHeadingLine(string line, string previousLine, string nextLine)
|
||||||
|
{
|
||||||
|
var trimmed = line.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(trimmed))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (Regex.IsMatch(trimmed, @"^#{1,6}\s+\S", RegexOptions.CultureInvariant))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(previousLine) || !string.IsNullOrWhiteSpace(nextLine))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (trimmed.Length is < 3 or > 120)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (trimmed.Contains("|", StringComparison.Ordinal) || trimmed.EndsWith(".", StringComparison.Ordinal))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return Regex.IsMatch(trimmed, @"^(\d+(\.\d+)*\.?\s+\S|(?i:chapter|section)\s+\S|[A-Z0-9][A-Z0-9 ,:;'/&()_-]{2,})$", RegexOptions.CultureInvariant);
|
||||||
|
}
|
||||||
|
|
||||||
private FileEnumerationResult GetInputFiles(IDataSource dataSource)
|
private FileEnumerationResult GetInputFiles(IDataSource dataSource)
|
||||||
{
|
{
|
||||||
var result = new FileEnumerationResult();
|
var result = new FileEnumerationResult();
|
||||||
@ -344,6 +624,37 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
return FileTypes.IsAllowedPath(filePath, FileTypes.IMAGE);
|
return FileTypes.IsAllowedPath(filePath, FileTypes.IMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsPresentationFilePath(string filePath)
|
||||||
|
{
|
||||||
|
return FileTypes.IsAllowedPath(filePath, FileTypes.POWER_POINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDelimitedTableFilePath(string filePath)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(filePath).TrimStart('.');
|
||||||
|
return RAG_DELIMITED_TABLE_FILE_EXTENSIONS.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSpreadsheetFilePath(string filePath)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(filePath).TrimStart('.');
|
||||||
|
return FileTypes.IsAllowedPath(filePath, FileTypes.EXCEL)
|
||||||
|
|| RAG_SPREADSHEET_FILE_EXTENSIONS.Contains(extension, StringComparer.OrdinalIgnoreCase)
|
||||||
|
|| RAG_SPREADSHEET_ADD_IN_FILE_EXTENSIONS.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSourceCodeFilePath(string filePath)
|
||||||
|
{
|
||||||
|
return !this.IsHtmlFilePath(filePath) && FileTypes.IsAllowedPath(filePath, FileTypes.SOURCE_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsHtmlFilePath(string filePath)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(filePath).TrimStart('.');
|
||||||
|
return extension.Equals("html", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals("htm", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsSupportedRagFilePath(string filePath)
|
private bool IsSupportedRagFilePath(string filePath)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(filePath).TrimStart('.');
|
var extension = Path.GetExtension(filePath).TrimStart('.');
|
||||||
@ -425,7 +736,7 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildEmbeddingSignature(EmbeddingProvider embeddingProvider)
|
private string BuildEmbeddingSignature(IDataSource dataSource, EmbeddingProvider embeddingProvider, ChunkingOptions chunkingOptions)
|
||||||
{
|
{
|
||||||
return string.Join('|',
|
return string.Join('|',
|
||||||
embeddingProvider.Id,
|
embeddingProvider.Id,
|
||||||
@ -435,7 +746,10 @@ public sealed partial class DataSourceEmbeddingService
|
|||||||
embeddingProvider.Hostname,
|
embeddingProvider.Hostname,
|
||||||
embeddingProvider.TokenizerPath,
|
embeddingProvider.TokenizerPath,
|
||||||
embeddingProvider.EffectiveTokenLimit,
|
embeddingProvider.EffectiveTokenLimit,
|
||||||
embeddingProvider.EffectiveChunkTokenLimit);
|
dataSource is IInternalDataSource internalDataSource ? internalDataSource.MaxChunkTokenLength : 0,
|
||||||
|
dataSource is IInternalDataSource overlapDataSource ? overlapDataSource.ChunkOverlapTokenLength : 0,
|
||||||
|
chunkingOptions.MaxChunkTokenLength,
|
||||||
|
chunkingOptions.OverlapTokenLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> BuildFingerprintAsync(FileInfo file, CancellationToken token)
|
private async Task<string> BuildFingerprintAsync(FileInfo file, CancellationToken token)
|
||||||
|
|||||||
@ -15,11 +15,6 @@ namespace AIStudio.Tools.Services;
|
|||||||
public sealed partial class DataSourceEmbeddingService(SettingsManager settingsManager, RustService rustService, DatabaseClientProvider databaseClientProvider, ILogger<DataSourceEmbeddingService> logger)
|
public sealed partial class DataSourceEmbeddingService(SettingsManager settingsManager, RustService rustService, DatabaseClientProvider databaseClientProvider, ILogger<DataSourceEmbeddingService> logger)
|
||||||
: BackgroundService
|
: BackgroundService
|
||||||
{
|
{
|
||||||
private const int MAX_CHUNK_LENGTH = 3_200;
|
|
||||||
private const int MIN_CHUNK_LENGTH = 800;
|
|
||||||
private const int CHUNK_OVERLAP_LENGTH = 320;
|
|
||||||
private const int EMBEDDING_BATCH_SIZE = 16;
|
|
||||||
|
|
||||||
private readonly Channel<string> queue = Channel.CreateUnbounded<string>();
|
private readonly Channel<string> queue = Channel.CreateUnbounded<string>();
|
||||||
private readonly ConcurrentDictionary<string, byte> queuedIds = new(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, byte> queuedIds = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly ConcurrentDictionary<string, byte> runningIds = new(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, byte> runningIds = new(StringComparer.OrdinalIgnoreCase);
|
||||||
@ -404,15 +399,16 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
|
|||||||
collectionName);
|
collectionName);
|
||||||
await this.DeleteFilePointsAsync(vectorStore, collectionName, file.FullName, token);
|
await this.DeleteFilePointsAsync(vectorStore, collectionName, file.FullName, token);
|
||||||
|
|
||||||
var batch = new List<(string Text, int ChunkIndex)>(EMBEDDING_BATCH_SIZE);
|
var embeddingBatchSize = embeddingProvider.EffectiveEmbeddingBatchSize;
|
||||||
|
var batch = new List<(string Text, int ChunkIndex)>(embeddingBatchSize);
|
||||||
var totalChunkCount = 0;
|
var totalChunkCount = 0;
|
||||||
|
|
||||||
await foreach (var chunk in this.StreamEmbeddingChunksAsync(file.FullName, embeddingProvider, token))
|
await foreach (var chunk in this.StreamEmbeddingChunksAsync(file.FullName, dataSource, embeddingProvider, token))
|
||||||
{
|
{
|
||||||
batch.Add((chunk, totalChunkCount));
|
batch.Add((chunk, totalChunkCount));
|
||||||
totalChunkCount++;
|
totalChunkCount++;
|
||||||
|
|
||||||
if (batch.Count >= EMBEDDING_BATCH_SIZE)
|
if (batch.Count >= embeddingBatchSize)
|
||||||
await this.FlushBatchAsync(embeddingState, vectorStore, dataSource, file, fingerprint, embeddingProvider, provider, manifest, collectionName, batch, token);
|
await this.FlushBatchAsync(embeddingState, vectorStore, dataSource, file, fingerprint, embeddingProvider, provider, manifest, collectionName, batch, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -612,7 +608,8 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
|
|||||||
EmbeddingStateClient embeddingState,
|
EmbeddingStateClient embeddingState,
|
||||||
CancellationToken token)
|
CancellationToken token)
|
||||||
{
|
{
|
||||||
var embeddingSignature = this.BuildEmbeddingSignature(embeddingProvider);
|
var chunkingOptions = this.GetChunkingOptions(dataSource, embeddingProvider);
|
||||||
|
var embeddingSignature = this.BuildEmbeddingSignature(dataSource, embeddingProvider, chunkingOptions);
|
||||||
var manifest = await embeddingState.GetManifestAsync(dataSource.Id, token);
|
var manifest = await embeddingState.GetManifestAsync(dataSource.Id, token);
|
||||||
|
|
||||||
if (!string.Equals(manifest.EmbeddingSignature, embeddingSignature, StringComparison.Ordinal))
|
if (!string.Equals(manifest.EmbeddingSignature, embeddingSignature, StringComparison.Ordinal))
|
||||||
@ -622,6 +619,12 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
|
|||||||
dataSource.Name,
|
dataSource.Name,
|
||||||
dataSource.Id,
|
dataSource.Id,
|
||||||
collectionName);
|
collectionName);
|
||||||
|
logger.LogDebug(
|
||||||
|
"Embedding signature mismatch for data source '{DataSourceName}' ({DataSourceId}). StoredSignature='{StoredEmbeddingSignature}', CurrentSignature='{CurrentEmbeddingSignature}'.",
|
||||||
|
dataSource.Name,
|
||||||
|
dataSource.Id,
|
||||||
|
manifest.EmbeddingSignature,
|
||||||
|
embeddingSignature);
|
||||||
await this.ResetPersistedStateAsync(dataSource.Name, dataSource.Id, vectorStore, embeddingState, token);
|
await this.ResetPersistedStateAsync(dataSource.Name, dataSource.Id, vectorStore, embeddingState, token);
|
||||||
manifest = await embeddingState.GetManifestAsync(dataSource.Id, token);
|
manifest = await embeddingState.GetManifestAsync(dataSource.Id, token);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user