set embedder output dimensions for LocalAI and Gemini (gemini-embedding-001) (#4980)
This commit is contained in:
parent
1f93753058
commit
1c91d369c3
@ -1,3 +1,6 @@
|
|||||||
|
import { Info } from "@phosphor-icons/react";
|
||||||
|
import { Tooltip } from "react-tooltip";
|
||||||
|
|
||||||
const DEFAULT_MODELS = [
|
const DEFAULT_MODELS = [
|
||||||
{
|
{
|
||||||
id: "gemini-embedding-001",
|
id: "gemini-embedding-001",
|
||||||
@ -7,6 +10,7 @@ const DEFAULT_MODELS = [
|
|||||||
|
|
||||||
export default function GeminiOptions({ settings }) {
|
export default function GeminiOptions({ settings }) {
|
||||||
return (
|
return (
|
||||||
|
<div className="w-full flex flex-col gap-y-6">
|
||||||
<div className="w-full flex flex-col gap-y-4">
|
<div className="w-full flex flex-col gap-y-4">
|
||||||
<div className="w-full flex items-center gap-[36px] mt-1.5">
|
<div className="w-full flex items-center gap-[36px] mt-1.5">
|
||||||
<div className="flex flex-col w-60">
|
<div className="flex flex-col w-60">
|
||||||
@ -18,7 +22,9 @@ export default function GeminiOptions({ settings }) {
|
|||||||
name="GeminiEmbeddingApiKey"
|
name="GeminiEmbeddingApiKey"
|
||||||
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||||
placeholder="Gemini API Key"
|
placeholder="Gemini API Key"
|
||||||
defaultValue={settings?.GeminiEmbeddingApiKey ? "*".repeat(20) : ""}
|
defaultValue={
|
||||||
|
settings?.GeminiEmbeddingApiKey ? "*".repeat(20) : ""
|
||||||
|
}
|
||||||
required={true}
|
required={true}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
@ -50,5 +56,48 @@ export default function GeminiOptions({ settings }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col w-60">
|
||||||
|
<div
|
||||||
|
data-tooltip-id="embedding-output-dimensions-tooltip"
|
||||||
|
className="flex gap-x-1 items-center mb-3"
|
||||||
|
>
|
||||||
|
<label className="text-white text-sm font-semibold block">
|
||||||
|
Output dimensions
|
||||||
|
</label>
|
||||||
|
<Info
|
||||||
|
size={16}
|
||||||
|
className="text-theme-text-secondary cursor-pointer"
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
id="embedding-output-dimensions-tooltip"
|
||||||
|
place="top"
|
||||||
|
delayShow={300}
|
||||||
|
className="tooltip !text-xs !opacity-100"
|
||||||
|
style={{
|
||||||
|
maxWidth: "250px",
|
||||||
|
whiteSpace: "normal",
|
||||||
|
wordWrap: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
The number of dimensions the resulting output embeddings should have
|
||||||
|
if it supports multiple dimensions output.
|
||||||
|
<br />
|
||||||
|
<br /> Leave blank to use the default dimensions for the selected
|
||||||
|
model.
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="EmbeddingOutputDimensions"
|
||||||
|
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||||
|
placeholder="Assume default dimensions"
|
||||||
|
min={1}
|
||||||
|
onScroll={(e) => e.target.blur()}
|
||||||
|
defaultValue={settings?.EmbeddingOutputDimensions}
|
||||||
|
required={false}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,20 +30,70 @@ export default function LocalAiOptions({ settings }) {
|
|||||||
apiKey={apiKey}
|
apiKey={apiKey}
|
||||||
basePath={basePath.value}
|
basePath={basePath.value}
|
||||||
/>
|
/>
|
||||||
|
<div className="flex flex-col w-60">
|
||||||
|
<div className="flex flex-col gap-y-1 mb-2">
|
||||||
|
<div className="flex gap-x-1 items-center">
|
||||||
|
<label className="text-white text-sm font-semibold block">
|
||||||
|
Local AI API Key
|
||||||
|
</label>
|
||||||
|
<Info
|
||||||
|
size={16}
|
||||||
|
data-tooltip-id="localai-api-key-tooltip"
|
||||||
|
className="text-theme-text-secondary cursor-pointer"
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
id="localai-api-key-tooltip"
|
||||||
|
place="top"
|
||||||
|
delayShow={300}
|
||||||
|
className="tooltip !text-xs !opacity-100"
|
||||||
|
style={{
|
||||||
|
maxWidth: "250px",
|
||||||
|
whiteSpace: "normal",
|
||||||
|
wordWrap: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
The API key for the LocalAI server (if applicable).
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="LocalAiApiKey"
|
||||||
|
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||||
|
placeholder="sk-mysecretkey"
|
||||||
|
defaultValue={settings?.LocalAiApiKey ? "*".repeat(20) : ""}
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
onChange={(e) => setApiKeyValue(e.target.value)}
|
||||||
|
onBlur={() => setApiKey(apiKeyValue)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full flex items-center gap-[36px] mt-1.5">
|
||||||
<div className="flex flex-col w-60">
|
<div className="flex flex-col w-60">
|
||||||
<div
|
<div
|
||||||
data-tooltip-place="top"
|
data-tooltip-place="top"
|
||||||
data-tooltip-id="max-embedding-chunk-length-tooltip"
|
data-tooltip-id="max-embedding-chunk-length-tooltip"
|
||||||
className="flex gap-x-1 items-center mb-3"
|
className="flex gap-x-1 items-center mb-3"
|
||||||
>
|
>
|
||||||
|
<label className="text-white text-sm font-semibold block">
|
||||||
|
Max embedding chunk length
|
||||||
|
</label>
|
||||||
<Info
|
<Info
|
||||||
size={16}
|
size={16}
|
||||||
className="text-theme-text-secondary cursor-pointer"
|
className="text-theme-text-secondary cursor-pointer"
|
||||||
/>
|
/>
|
||||||
<label className="text-white text-sm font-semibold block">
|
<Tooltip
|
||||||
Max embedding chunk length
|
id="max-embedding-chunk-length-tooltip"
|
||||||
</label>
|
place="top"
|
||||||
<Tooltip id="max-embedding-chunk-length-tooltip">
|
delayShow={300}
|
||||||
|
className="tooltip !text-xs !opacity-100"
|
||||||
|
style={{
|
||||||
|
maxWidth: "250px",
|
||||||
|
whiteSpace: "normal",
|
||||||
|
wordWrap: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
Maximum length of text chunks, in characters, for embedding.
|
Maximum length of text chunks, in characters, for embedding.
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -59,23 +109,47 @@ export default function LocalAiOptions({ settings }) {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col w-60">
|
<div className="flex flex-col w-60">
|
||||||
<div className="flex flex-col gap-y-1 mb-2">
|
<div
|
||||||
<label className="text-white text-sm font-semibold flex items-center gap-x-2">
|
data-tooltip-id="embedding-output-dimensions-tooltip"
|
||||||
Local AI API Key{" "}
|
className="flex gap-x-1 items-center mb-3"
|
||||||
<p className="!text-xs !italic !font-thin">optional</p>
|
>
|
||||||
|
<label className="text-white text-sm font-semibold block">
|
||||||
|
Output dimensions
|
||||||
</label>
|
</label>
|
||||||
|
<Info
|
||||||
|
size={16}
|
||||||
|
className="text-theme-text-secondary cursor-pointer"
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
id="embedding-output-dimensions-tooltip"
|
||||||
|
place="top"
|
||||||
|
delayShow={300}
|
||||||
|
className="tooltip !text-xs !opacity-100"
|
||||||
|
style={{
|
||||||
|
maxWidth: "250px",
|
||||||
|
whiteSpace: "normal",
|
||||||
|
wordWrap: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
The number of dimensions the resulting output embeddings should
|
||||||
|
have if it supports multiple dimensions output.
|
||||||
|
<br />
|
||||||
|
<br /> Leave blank to use the default dimensions for the selected
|
||||||
|
model.
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="number"
|
||||||
name="LocalAiApiKey"
|
name="EmbeddingOutputDimensions"
|
||||||
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
|
||||||
placeholder="sk-mysecretkey"
|
placeholder="Assume default dimensions"
|
||||||
defaultValue={settings?.LocalAiApiKey ? "*".repeat(20) : ""}
|
min={1}
|
||||||
|
onScroll={(e) => e.target.blur()}
|
||||||
|
defaultValue={settings?.EmbeddingOutputDimensions}
|
||||||
|
required={false}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
spellCheck={false}
|
|
||||||
onChange={(e) => setApiKeyValue(e.target.value)}
|
|
||||||
onBlur={() => setApiKey(apiKeyValue)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -233,6 +233,8 @@ const SystemSettings = {
|
|||||||
embeddingEngine === "native"
|
embeddingEngine === "native"
|
||||||
? NativeEmbedder._getEmbeddingModel()
|
? NativeEmbedder._getEmbeddingModel()
|
||||||
: process.env.EMBEDDING_MODEL_PREF,
|
: process.env.EMBEDDING_MODEL_PREF,
|
||||||
|
EmbeddingOutputDimensions:
|
||||||
|
process.env.EMBEDDING_OUTPUT_DIMENSIONS || null,
|
||||||
EmbeddingModelMaxChunkLength:
|
EmbeddingModelMaxChunkLength:
|
||||||
process.env.EMBEDDING_MODEL_MAX_CHUNK_LENGTH,
|
process.env.EMBEDDING_MODEL_MAX_CHUNK_LENGTH,
|
||||||
OllamaEmbeddingBatchSize: process.env.OLLAMA_EMBEDDING_BATCH_SIZE || 1,
|
OllamaEmbeddingBatchSize: process.env.OLLAMA_EMBEDDING_BATCH_SIZE || 1,
|
||||||
|
|||||||
@ -23,7 +23,10 @@ class GeminiEmbedder {
|
|||||||
// https://ai.google.dev/gemini-api/docs/models/gemini#text-embedding-and-embedding
|
// https://ai.google.dev/gemini-api/docs/models/gemini#text-embedding-and-embedding
|
||||||
this.embeddingMaxChunkLength = MODEL_MAP[this.model] || 2_048;
|
this.embeddingMaxChunkLength = MODEL_MAP[this.model] || 2_048;
|
||||||
this.log(
|
this.log(
|
||||||
`Initialized with ${this.model} - Max Size: ${this.embeddingMaxChunkLength}`
|
`Initialized with ${this.model} - Max Size: ${this.embeddingMaxChunkLength}` +
|
||||||
|
(this.outputDimensions
|
||||||
|
? ` - Output Dimensions: ${this.outputDimensions}`
|
||||||
|
: " Assuming default output dimensions")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,6 +34,16 @@ class GeminiEmbedder {
|
|||||||
console.log(`\x1b[36m[${this.className}]\x1b[0m ${text}`, ...args);
|
console.log(`\x1b[36m[${this.className}]\x1b[0m ${text}`, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get outputDimensions() {
|
||||||
|
if (
|
||||||
|
process.env.EMBEDDING_OUTPUT_DIMENSIONS &&
|
||||||
|
!isNaN(process.env.EMBEDDING_OUTPUT_DIMENSIONS) &&
|
||||||
|
process.env.EMBEDDING_OUTPUT_DIMENSIONS > 0
|
||||||
|
)
|
||||||
|
return parseInt(process.env.EMBEDDING_OUTPUT_DIMENSIONS);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Embeds a single text input
|
* Embeds a single text input
|
||||||
* @param {string|string[]} textInput - The text to embed
|
* @param {string|string[]} textInput - The text to embed
|
||||||
@ -62,6 +75,7 @@ class GeminiEmbedder {
|
|||||||
.create({
|
.create({
|
||||||
model: this.model,
|
model: this.model,
|
||||||
input: chunk,
|
input: chunk,
|
||||||
|
dimensions: this.outputDimensions,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
resolve({ data: result?.data, error: null });
|
resolve({ data: result?.data, error: null });
|
||||||
|
|||||||
@ -7,7 +7,9 @@ class LocalAiEmbedder {
|
|||||||
if (!process.env.EMBEDDING_MODEL_PREF)
|
if (!process.env.EMBEDDING_MODEL_PREF)
|
||||||
throw new Error("No embedding model was set.");
|
throw new Error("No embedding model was set.");
|
||||||
|
|
||||||
|
this.className = "LocalAiEmbedder";
|
||||||
const { OpenAI: OpenAIApi } = require("openai");
|
const { OpenAI: OpenAIApi } = require("openai");
|
||||||
|
this.model = process.env.EMBEDDING_MODEL_PREF;
|
||||||
this.openai = new OpenAIApi({
|
this.openai = new OpenAIApi({
|
||||||
baseURL: process.env.EMBEDDING_BASE_PATH,
|
baseURL: process.env.EMBEDDING_BASE_PATH,
|
||||||
apiKey: process.env.LOCAL_AI_API_KEY ?? null,
|
apiKey: process.env.LOCAL_AI_API_KEY ?? null,
|
||||||
@ -16,6 +18,27 @@ class LocalAiEmbedder {
|
|||||||
// Limit of how many strings we can process in a single pass to stay with resource or network limits
|
// Limit of how many strings we can process in a single pass to stay with resource or network limits
|
||||||
this.maxConcurrentChunks = 50;
|
this.maxConcurrentChunks = 50;
|
||||||
this.embeddingMaxChunkLength = maximumChunkLength();
|
this.embeddingMaxChunkLength = maximumChunkLength();
|
||||||
|
|
||||||
|
this.log(
|
||||||
|
`Initialized with ${this.model} - Max Size: ${this.embeddingMaxChunkLength}` +
|
||||||
|
(this.outputDimensions
|
||||||
|
? ` - Output Dimensions: ${this.outputDimensions}`
|
||||||
|
: " Assuming default output dimensions")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(text, ...args) {
|
||||||
|
console.log(`\x1b[36m[${this.className}]\x1b[0m ${text}`, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
get outputDimensions() {
|
||||||
|
if (
|
||||||
|
process.env.EMBEDDING_OUTPUT_DIMENSIONS &&
|
||||||
|
!isNaN(process.env.EMBEDDING_OUTPUT_DIMENSIONS) &&
|
||||||
|
process.env.EMBEDDING_OUTPUT_DIMENSIONS > 0
|
||||||
|
)
|
||||||
|
return parseInt(process.env.EMBEDDING_OUTPUT_DIMENSIONS);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async embedTextInput(textInput) {
|
async embedTextInput(textInput) {
|
||||||
@ -32,8 +55,9 @@ class LocalAiEmbedder {
|
|||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
this.openai.embeddings
|
this.openai.embeddings
|
||||||
.create({
|
.create({
|
||||||
model: process.env.EMBEDDING_MODEL_PREF,
|
model: this.model,
|
||||||
input: chunk,
|
input: chunk,
|
||||||
|
dimensions: this.outputDimensions,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
resolve({ data: result?.data, error: null });
|
resolve({ data: result?.data, error: null });
|
||||||
|
|||||||
@ -307,6 +307,10 @@ const KEY_MAPPING = {
|
|||||||
envKey: "EMBEDDING_MODEL_MAX_CHUNK_LENGTH",
|
envKey: "EMBEDDING_MODEL_MAX_CHUNK_LENGTH",
|
||||||
checks: [nonZero],
|
checks: [nonZero],
|
||||||
},
|
},
|
||||||
|
EmbeddingOutputDimensions: {
|
||||||
|
envKey: "EMBEDDING_OUTPUT_DIMENSIONS",
|
||||||
|
checks: [],
|
||||||
|
},
|
||||||
OllamaEmbeddingBatchSize: {
|
OllamaEmbeddingBatchSize: {
|
||||||
envKey: "OLLAMA_EMBEDDING_BATCH_SIZE",
|
envKey: "OLLAMA_EMBEDDING_BATCH_SIZE",
|
||||||
checks: [nonZero],
|
checks: [nonZero],
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user