import { type EmbeddingModelItemType } from '@fastgpt/global/core/ai/model.d'; import { getAIApi } from '../config'; import { countPromptTokens } from '../../../common/string/tiktoken/index'; import { EmbeddingTypeEnm } from '@fastgpt/global/core/ai/constants'; import { addLog } from '../../../common/system/log'; type GetVectorProps = { model: EmbeddingModelItemType; input: string[] | string; type?: `${EmbeddingTypeEnm}`; headers?: Record; }; // text to vector export async function getVectorsByText({ model, input, type, headers }: GetVectorProps) { if (!input) { return Promise.reject({ code: 500, message: 'input is empty' }); } const ai = getAIApi(); const formatInput = Array.isArray(input) ? input : [input]; let chunkSize = Number(model.batchSize || 1); chunkSize = isNaN(chunkSize) ? 1 : chunkSize; const chunks = []; for (let i = 0; i < formatInput.length; i += chunkSize) { chunks.push(formatInput.slice(i, i + chunkSize)); } try { // Process chunks sequentially let totalTokens = 0; const allVectors: number[][] = []; for (const chunk of chunks) { // input text to vector const result = await ai.embeddings .create( { ...model.defaultConfig, ...(type === EmbeddingTypeEnm.db && model.dbConfig), ...(type === EmbeddingTypeEnm.query && model.queryConfig), model: model.model, input: chunk }, model.requestUrl ? { path: model.requestUrl, headers: { ...(model.requestAuth ? { Authorization: `Bearer ${model.requestAuth}` } : {}), ...headers } } : { headers } ) .then(async (res) => { if (!res.data) { addLog.error('Embedding API is not responding', res); return Promise.reject('Embedding API is not responding'); } if (!res?.data?.[0]?.embedding) { console.log(res); // @ts-ignore return Promise.reject(res.data?.err?.message || 'Embedding API Error'); } const [tokens, vectors] = await Promise.all([ (async () => { if (res.usage) return res.usage.total_tokens; const tokens = await Promise.all(chunk.map((item) => countPromptTokens(item))); return tokens.reduce((sum, item) => sum + item, 0); })(), Promise.all(res.data.map((item) => formatVectors(item.embedding, model.normalization))) ]); return { tokens, vectors }; }); totalTokens += result.tokens; allVectors.push(...result.vectors); } return { tokens: totalTokens, vectors: allVectors }; } catch (error) { addLog.error(`Embedding Error`, error); return Promise.reject(error); } } export function formatVectors(vector: number[], normalization = false) { // normalization processing function normalizationVector(vector: number[]) { // Calculate the Euclidean norm (L2 norm) const norm = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0)); if (norm === 0) { return vector; } // Normalize the vector by dividing each component by the norm return vector.map((val) => val / norm); } // 超过上限,截断,并强制归一化 if (vector.length > 1536) { console.log( `The current vector dimension is ${vector.length}, and the vector dimension cannot exceed 1536. The first 1536 dimensions are automatically captured` ); return normalizationVector(vector.slice(0, 1536)); } else if (vector.length < 1536) { const vectorLen = vector.length; const zeroVector = new Array(1536 - vectorLen).fill(0); vector = vector.concat(zeroVector); } if (normalization) { return normalizationVector(vector); } return vector; }