@askdb/studio 0.2.0-beta.36 → 0.2.0-beta.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -4
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +16 -7
- package/dist/bin.js.map +1 -1
- package/dist/client/assets/index-xswUSrtO.js +100 -0
- package/dist/client/index.html +1 -1
- package/dist/execute-registry.d.ts +2 -1
- package/dist/execute-registry.d.ts.map +1 -1
- package/dist/execute-registry.js +7 -2
- package/dist/execute-registry.js.map +1 -1
- package/dist/introspection.d.ts +6 -6
- package/dist/introspection.d.ts.map +1 -1
- package/dist/introspection.js +12 -69
- package/dist/introspection.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +106 -102
- package/dist/server.js.map +1 -1
- package/dist/setup.d.ts +3 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +29 -33
- package/dist/setup.js.map +1 -1
- package/dist/shared/api.d.ts +6 -3
- package/dist/shared/api.d.ts.map +1 -1
- package/package.json +35 -36
- package/dist/client/assets/index-TAdxvl0x.js +0 -101
package/dist/server.js
CHANGED
|
@@ -5,21 +5,20 @@ import { extname, relative, join, resolve, dirname, sep } from "node:path";
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { generateText as defaultGenerateText } from "ai";
|
|
8
|
-
import { bootstrapAskDbEnv, getAskDbRuntimeConfig } from "@askdb/config";
|
|
8
|
+
import { ASKDB_AI_PROVIDERS, bootstrapAskDbEnv, getAskDbRuntimeConfig, } from "@askdb/config";
|
|
9
9
|
import { createAiRegistry, resolveReasoningEffort, } from "@askdb/ai";
|
|
10
|
-
import { anthropicProvider } from "@askdb/ai-anthropic";
|
|
11
|
-
import { azureProvider } from "@askdb/ai-azure";
|
|
12
|
-
import { googleProvider } from "@askdb/ai-google";
|
|
13
|
-
import { openaiProvider } from "@askdb/ai-openai";
|
|
14
10
|
import { ask, formatSensitiveReference, isBuiltInDialectId, loadSchema, schemaHasSensitiveIdentifiers, SqlValidationError, validateSensitiveReferences, suggestEnrichment, tenantScopeSchema, normalizeTenantPolicy, parseTenantPolicyMarkdown, SchemaParseError, writeTenantPolicyMarkdown, tenantPolicyFrontmatterSchema, } from "@askdb/core";
|
|
15
|
-
import { buildSchemaIndex, chunkContentHash, chunkSchema, createAiSdkEmbedder, createFileStore, createMemoryStore, createPgvectorStore, createRetriever, loadChunkerSourcesFromDir, } from "@askdb/rag";
|
|
11
|
+
import { buildSchemaIndex, chunkContentHash, chunkSchema, aiSdkEmbedderId, createAiSdkEmbedder, createFileStore, createMemoryStore, createPgvectorStore, createRetriever, detectEmbeddingDimensions, loadChunkerSourcesFromDir, PgvectorDimensionMismatchError, } from "@askdb/rag";
|
|
16
12
|
import { buildDefaultTableBody, buildFrontmatter, buildSuggestionContext, buildSuggestionTarget, buildTableDraft, loadWorkspace, replaceH2Section, replaceTableDescription, saveConcepts, saveTable, } from "@askdb/enrich";
|
|
17
13
|
import { EXECUTE_DRIVER_REGISTRY, executeDialectFor, isDriverInstalled, isStudioExecuteProvider, validateExecuteSql, } from "./execute-registry.js";
|
|
18
14
|
import { lockfilePackageManager, packageManagerAddArgs, packageManagerSpawnSpec, } from "./package-manager.js";
|
|
19
15
|
import { resolveStudioIntrospectionPlan, runStudioIntrospection } from "./introspection.js";
|
|
20
16
|
import { probeSetupState, writeSetupConfig, SetupError } from "./setup.js";
|
|
21
17
|
import { checkApiRequest, checkHost, createStudioSessionToken, injectSessionToken, } from "./request-guard.js";
|
|
22
|
-
|
|
18
|
+
// Batteries-included surface: every built-in provider is registered, and each
|
|
19
|
+
// loads its @ai-sdk/* package only when first used, so env config alone
|
|
20
|
+
// selects the provider.
|
|
21
|
+
const ai = createAiRegistry();
|
|
23
22
|
const DEFAULT_CLIENT_DIR = fileURLToPath(new URL("./client/", import.meta.url));
|
|
24
23
|
let clientDirForTests;
|
|
25
24
|
/** @internal Tests only — serve client assets from another directory. */
|
|
@@ -31,7 +30,6 @@ function clientDir() {
|
|
|
31
30
|
}
|
|
32
31
|
const STUDIO_RAG_MOCK_DIMENSIONS = 64;
|
|
33
32
|
const STUDIO_RAG_MOCK_EMBEDDER_ID = `studio:mock-lexical-${STUDIO_RAG_MOCK_DIMENSIONS}`;
|
|
34
|
-
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
35
33
|
let studioPgvectorStoreFactoryForTests;
|
|
36
34
|
export function setStudioPgvectorStoreFactoryForTests(factory) {
|
|
37
35
|
studioPgvectorStoreFactoryForTests = factory;
|
|
@@ -224,8 +222,8 @@ export function serializeWorkspace(workspace) {
|
|
|
224
222
|
dialect,
|
|
225
223
|
warnings: workspace.warnings,
|
|
226
224
|
aiConfigured: Boolean(aiConfig),
|
|
227
|
-
model: aiConfig?.model ?? "
|
|
228
|
-
aiProvider: aiConfig?.provider ??
|
|
225
|
+
model: aiConfig?.model ?? rt.ai.language.model ?? "provider default",
|
|
226
|
+
aiProvider: aiConfig?.provider ?? rt.ai.language.provider,
|
|
229
227
|
tables: workspace.tables.map((table) => {
|
|
230
228
|
const draft = buildTableDraft(table.physical, table.parsed);
|
|
231
229
|
return {
|
|
@@ -373,7 +371,8 @@ function parseSetupConfigBody(body) {
|
|
|
373
371
|
throw new StudioHttpError(400, "Request body must be a JSON object.");
|
|
374
372
|
}
|
|
375
373
|
const databases = ["postgres", "mysql", "sqlite", "sqlserver", "prisma"];
|
|
376
|
-
|
|
374
|
+
// Every provider with an askdb.config.* branch; @askdb/client's provider-config-drift test asserts this matches @askdb/ai's built-in table.
|
|
375
|
+
const aiProviders = ASKDB_AI_PROVIDERS;
|
|
377
376
|
const ragStores = ["file", "memory", "pgvector"];
|
|
378
377
|
const executeProviders = ["postgres", "mysql", "sqlite", "sqlserver"];
|
|
379
378
|
if (typeof body.database !== "string" || !databases.includes(body.database)) {
|
|
@@ -659,14 +658,17 @@ async function getRagStatus(state) {
|
|
|
659
658
|
const sources = loadChunkerSourcesFromDir(state.schemaDir);
|
|
660
659
|
const chunkResult = chunkSchema(sources);
|
|
661
660
|
const lockPath = join(state.schemaDir, "schema.lock.json");
|
|
662
|
-
const lock =
|
|
661
|
+
const lock = resolveStudioRagStoreConfig(state).kind === "memory"
|
|
662
|
+
? state.ragMemoryIndex
|
|
663
|
+
: readOptionalJson(lockPath);
|
|
663
664
|
const currentHashes = Object.fromEntries(chunkResult.chunks.map((chunk) => [chunk.id, chunkContentHash(chunk.text)]));
|
|
664
665
|
const lockHashes = lock?.hashes ?? {};
|
|
665
666
|
const hashIds = Object.keys(currentHashes);
|
|
666
|
-
const store = await openStudioRagStore(state, config.dimensions);
|
|
667
|
+
const store = await openStudioRagStore(state, { dimensions: config.dimensions });
|
|
667
668
|
try {
|
|
668
669
|
const chunksIndexed = await countStudioRagStoreChunks(store, sources.schema.schemaId);
|
|
669
670
|
const stale = !lock ||
|
|
671
|
+
lock.incomplete === true ||
|
|
670
672
|
lock.embedderId !== config.embedderId ||
|
|
671
673
|
Object.keys(lockHashes).length !== hashIds.length ||
|
|
672
674
|
hashIds.some((id) => lockHashes[id] !== currentHashes[id]) ||
|
|
@@ -707,8 +709,9 @@ async function getRagStatus(state) {
|
|
|
707
709
|
updatedAt: lock?.updatedAt ?? null,
|
|
708
710
|
chunksTotal: chunkResult.chunks.length,
|
|
709
711
|
chunksIndexed,
|
|
710
|
-
|
|
711
|
-
|
|
712
|
+
// The width the last index build learned from the model, else the one configured.
|
|
713
|
+
dimensions: lock?.dimensions ?? config.dimensions ?? null,
|
|
714
|
+
expectedDimensions: config.dimensions ?? null,
|
|
712
715
|
sensitiveExcluded: chunkResult.stats.sensitiveExcluded,
|
|
713
716
|
sensitiveIncluded: chunkResult.stats.sensitiveIncluded,
|
|
714
717
|
files: fileArtifacts,
|
|
@@ -721,27 +724,40 @@ async function getRagStatus(state) {
|
|
|
721
724
|
async function indexRag(state) {
|
|
722
725
|
const config = resolveStudioRagEmbedderConfig();
|
|
723
726
|
if (!config.configured) {
|
|
724
|
-
throw new StudioHttpError(400, studioRagAiSdkKeyMissingMessage());
|
|
727
|
+
throw new StudioHttpError(400, studioRagAiSdkKeyMissingMessage(config));
|
|
725
728
|
}
|
|
726
729
|
const usage = createRequestUsageCollector();
|
|
727
730
|
clearIncompatibleRagStore(state, config);
|
|
728
731
|
const sources = loadChunkerSourcesFromDir(state.schemaDir);
|
|
729
|
-
const
|
|
732
|
+
const embedder = await createStudioRagEmbedder(config, usage);
|
|
730
733
|
let result;
|
|
734
|
+
let store;
|
|
731
735
|
try {
|
|
736
|
+
// A new pgvector table needs its width up front: the configured one, else the model's own.
|
|
737
|
+
const dimensions = config.dimensions ??
|
|
738
|
+
(resolveStudioRagStoreConfig(state).kind === "pgvector" ? await detectEmbeddingDimensions(embedder) : undefined);
|
|
739
|
+
store = await openStudioRagStore(state, { dimensions, provision: true });
|
|
732
740
|
result = await buildSchemaIndex({
|
|
733
741
|
schema: sources,
|
|
734
|
-
embedder
|
|
742
|
+
embedder,
|
|
735
743
|
store: store.store,
|
|
736
744
|
embedderId: config.embedderId,
|
|
737
745
|
lockFilePath: join(state.schemaDir, "schema.lock.json"),
|
|
738
746
|
});
|
|
747
|
+
if (store.kind === "memory") {
|
|
748
|
+
state.ragMemoryIndex = {
|
|
749
|
+
embedderId: config.embedderId,
|
|
750
|
+
updatedAt: new Date().toISOString(),
|
|
751
|
+
dimensions: store.store.describe?.().dimensions,
|
|
752
|
+
hashes: Object.fromEntries(result.chunks.map((chunk) => [chunk.id, chunkContentHash(chunk.text)])),
|
|
753
|
+
};
|
|
754
|
+
}
|
|
739
755
|
}
|
|
740
756
|
catch (error) {
|
|
741
757
|
throw formatStudioRagOperationError(error, config);
|
|
742
758
|
}
|
|
743
759
|
finally {
|
|
744
|
-
await store
|
|
760
|
+
await store?.dispose();
|
|
745
761
|
}
|
|
746
762
|
return {
|
|
747
763
|
status: await getRagStatus(state),
|
|
@@ -779,7 +795,7 @@ async function queryRag(state, query) {
|
|
|
779
795
|
async function createCurrentStudioRagIndex(state, action, usage) {
|
|
780
796
|
const config = resolveStudioRagEmbedderConfig();
|
|
781
797
|
if (!config.configured) {
|
|
782
|
-
throw new StudioHttpError(400, studioRagAiSdkKeyMissingMessage());
|
|
798
|
+
throw new StudioHttpError(400, studioRagAiSdkKeyMissingMessage(config));
|
|
783
799
|
}
|
|
784
800
|
const status = (await getRagStatus(state));
|
|
785
801
|
if (!status.hasIndex) {
|
|
@@ -791,7 +807,7 @@ async function createCurrentStudioRagIndex(state, action, usage) {
|
|
|
791
807
|
if (!status.schemaId || typeof status.chunksTotal !== "number") {
|
|
792
808
|
throw new StudioHttpError(500, "Studio RAG status is missing schema metadata.");
|
|
793
809
|
}
|
|
794
|
-
const store = await openStudioRagStore(state, config.dimensions);
|
|
810
|
+
const store = await openStudioRagStore(state, { dimensions: config.dimensions });
|
|
795
811
|
return {
|
|
796
812
|
config,
|
|
797
813
|
status: {
|
|
@@ -809,22 +825,26 @@ async function createCurrentStudioRagIndex(state, action, usage) {
|
|
|
809
825
|
}
|
|
810
826
|
function resolveStudioRagStoreConfig(state) {
|
|
811
827
|
const rt = getAskDbRuntimeConfig();
|
|
812
|
-
const kind = rt.
|
|
828
|
+
const kind = rt.rag.store;
|
|
813
829
|
if (kind === "memory")
|
|
814
830
|
return { kind };
|
|
815
831
|
if (kind === "file") {
|
|
816
|
-
const basePath = rt.
|
|
832
|
+
const basePath = rt.rag.storeConfig.file?.basePath?.trim();
|
|
817
833
|
return { kind, basePath: basePath ? resolve(basePath) : join(state.schemaDir, "schema") };
|
|
818
834
|
}
|
|
819
835
|
const connectionString = pickFlat(rt.flat, "ASKDB_PGVECTOR_URL");
|
|
820
836
|
return {
|
|
821
837
|
kind,
|
|
822
838
|
connectionString,
|
|
823
|
-
table: rt.
|
|
839
|
+
table: rt.rag.storeConfig.pgvector?.table?.trim() || undefined,
|
|
824
840
|
indexStrategy: pickFlat(rt.flat, "ASKDB_PGVECTOR_INDEX_STRATEGY"),
|
|
825
841
|
};
|
|
826
842
|
}
|
|
827
|
-
|
|
843
|
+
/**
|
|
844
|
+
* Opens the configured store. `provision` creates the pgvector table when it's missing, at
|
|
845
|
+
* `dimensions`; without it, a pgvector store only reads and writes an existing table.
|
|
846
|
+
*/
|
|
847
|
+
async function openStudioRagStore(state, { dimensions, provision = false }) {
|
|
828
848
|
const config = resolveStudioRagStoreConfig(state);
|
|
829
849
|
if (config.kind === "memory") {
|
|
830
850
|
state.ragMemoryStore ??= createMemoryStore();
|
|
@@ -852,10 +872,19 @@ async function openStudioRagStore(state, dimensions) {
|
|
|
852
872
|
const store = pgvectorFactory({
|
|
853
873
|
connectionString: config.connectionString,
|
|
854
874
|
table: config.table,
|
|
855
|
-
dimensions,
|
|
875
|
+
...(dimensions !== undefined ? { dimensions } : {}),
|
|
856
876
|
...(config.indexStrategy ? { indexStrategy: config.indexStrategy } : {}),
|
|
857
877
|
});
|
|
858
|
-
|
|
878
|
+
if (provision) {
|
|
879
|
+
try {
|
|
880
|
+
await store.ensureSchema();
|
|
881
|
+
}
|
|
882
|
+
catch (error) {
|
|
883
|
+
// The caller never receives this store, so close its pool here (a width refusal has opened it).
|
|
884
|
+
await store.close().catch(() => { });
|
|
885
|
+
throw error;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
859
888
|
return {
|
|
860
889
|
kind: "pgvector",
|
|
861
890
|
store,
|
|
@@ -867,6 +896,10 @@ async function openStudioRagStore(state, dimensions) {
|
|
|
867
896
|
};
|
|
868
897
|
}
|
|
869
898
|
async function countStudioRagStoreChunks(store, schemaId) {
|
|
899
|
+
// A pgvector table that doesn't exist yet holds nothing; the first index build creates it.
|
|
900
|
+
if (typeof store.store.tableDimensions === "function" && (await store.store.tableDimensions()) === undefined) {
|
|
901
|
+
return 0;
|
|
902
|
+
}
|
|
870
903
|
if (typeof store.store.count === "function") {
|
|
871
904
|
return store.store.count({ schemaId });
|
|
872
905
|
}
|
|
@@ -879,33 +912,9 @@ function pickFlat(flat, key) {
|
|
|
879
912
|
const value = flat[key];
|
|
880
913
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
|
|
881
914
|
}
|
|
882
|
-
function pickEnv(env, key) {
|
|
883
|
-
const v = env[key];
|
|
884
|
-
return typeof v === "string" && v.trim() !== "" ? v.trim() : undefined;
|
|
885
|
-
}
|
|
886
915
|
function resolveStudioRagEmbedderConfig() {
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
const explicitKind = pickEnv(base, "ASKDB_RAG_EMBEDDER");
|
|
890
|
-
const kind = explicitKind?.toLowerCase();
|
|
891
|
-
if (kind === "mock") {
|
|
892
|
-
return {
|
|
893
|
-
kind: "mock",
|
|
894
|
-
embedderId: STUDIO_RAG_MOCK_EMBEDDER_ID,
|
|
895
|
-
dimensions: STUDIO_RAG_MOCK_DIMENSIONS,
|
|
896
|
-
configured: true,
|
|
897
|
-
label: "Mock lexical",
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
if (kind !== undefined && kind !== "ai-sdk" && kind !== "openai") {
|
|
901
|
-
throw new StudioHttpError(400, `Unsupported Studio RAG embedder: ${kind}`);
|
|
902
|
-
}
|
|
903
|
-
const env = buildStudioRagEmbeddingEnv(kind, base);
|
|
904
|
-
const aiConfig = ai.resolveEmbeddingConfig(env, {
|
|
905
|
-
modelEnvVar: "ASKDB_RAG_EMBEDDER_MODEL",
|
|
906
|
-
modelDefault: DEFAULT_EMBEDDING_MODEL,
|
|
907
|
-
});
|
|
908
|
-
if (!aiConfig && kind === undefined) {
|
|
916
|
+
const embedding = getAskDbRuntimeConfig().ai.embedding;
|
|
917
|
+
if (!embedding) {
|
|
909
918
|
return {
|
|
910
919
|
kind: "mock",
|
|
911
920
|
embedderId: STUDIO_RAG_MOCK_EMBEDDER_ID,
|
|
@@ -914,50 +923,38 @@ function resolveStudioRagEmbedderConfig() {
|
|
|
914
923
|
label: "Mock lexical",
|
|
915
924
|
};
|
|
916
925
|
}
|
|
917
|
-
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
const
|
|
926
|
+
// The env holds the embedding connection only, so no other provider's key can reach it (#345).
|
|
927
|
+
const aiConfig = ai.resolveEmbeddingConfig(embedding.env);
|
|
928
|
+
// The adapter's canonical name (`foundry` resolves to `azure`) keeps existing index ids stable.
|
|
929
|
+
const provider = aiConfig?.provider ?? embedding.provider;
|
|
930
|
+
const model = embedding.model;
|
|
931
|
+
const dimensions = embedding.dimensions;
|
|
921
932
|
return {
|
|
922
933
|
kind: "ai-sdk",
|
|
923
934
|
provider,
|
|
924
|
-
|
|
935
|
+
configuredProvider: embedding.provider,
|
|
936
|
+
connection: embedding.connection,
|
|
937
|
+
embedderId: aiSdkEmbedderId({ provider, model, dimensions }),
|
|
925
938
|
dimensions,
|
|
926
939
|
configured: Boolean(aiConfig),
|
|
927
940
|
label: `AI SDK (${provider})`,
|
|
928
941
|
model,
|
|
929
942
|
baseUrl: aiConfig?.baseURL,
|
|
930
943
|
aiConfig,
|
|
931
|
-
|
|
944
|
+
// Only an explicit ai.embedding.dimensions is sent; otherwise the model's own width applies.
|
|
945
|
+
requestDimensions: embedding.dimensions,
|
|
932
946
|
};
|
|
933
947
|
}
|
|
934
|
-
function
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
...base,
|
|
939
|
-
...(kind === "openai" ? { ASKDB_AI_PROVIDER: "openai" } : {}),
|
|
940
|
-
...(apiKeyOverride ? { ASKDB_AI_API_KEY: apiKeyOverride } : {}),
|
|
941
|
-
...(baseUrlOverride ? { ASKDB_AI_BASE_URL: baseUrlOverride } : {}),
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
function fallbackStudioRagProvider(kind, base) {
|
|
945
|
-
if (kind === "openai")
|
|
946
|
-
return "openai";
|
|
947
|
-
const raw = (pickEnv(base, "ASKDB_AI_PROVIDER") ?? "").toLowerCase();
|
|
948
|
-
return raw === "azure" || raw === "azure-openai" || raw === "foundry"
|
|
949
|
-
? "azure"
|
|
950
|
-
: "openai";
|
|
951
|
-
}
|
|
952
|
-
function studioRagAiSdkKeyMissingMessage() {
|
|
953
|
-
return ("Studio RAG AI SDK embeddings require a configured AI provider key. " +
|
|
954
|
-
"Set ASKDB_AI_API_KEY or the provider-native key, or set ASKDB_RAG_EMBEDDER=mock for the local lexical embedder.");
|
|
948
|
+
function studioRagAiSdkKeyMissingMessage(config) {
|
|
949
|
+
return (`Studio RAG embeddings need an API key on the ai.embedding connection ` +
|
|
950
|
+
`("${config.connection}" in ai.providerConfig.${config.configuredProvider}). ` +
|
|
951
|
+
'Set it in askdb.config.*, or set rag.embedder: "mock" for the local lexical embedder.');
|
|
955
952
|
}
|
|
956
953
|
async function createStudioRagEmbedder(config, usage) {
|
|
957
954
|
if (config.kind === "mock")
|
|
958
955
|
return createStudioMockEmbedder(config.dimensions);
|
|
959
956
|
if (!config.aiConfig) {
|
|
960
|
-
throw new StudioHttpError(400, studioRagAiSdkKeyMissingMessage());
|
|
957
|
+
throw new StudioHttpError(400, studioRagAiSdkKeyMissingMessage(config));
|
|
961
958
|
}
|
|
962
959
|
const model = await ai.createEmbeddingModel(config.aiConfig, {
|
|
963
960
|
dimensions: config.requestDimensions,
|
|
@@ -977,6 +974,9 @@ async function createStudioRagEmbedder(config, usage) {
|
|
|
977
974
|
function formatStudioRagOperationError(error, config) {
|
|
978
975
|
if (error instanceof StudioHttpError)
|
|
979
976
|
return error;
|
|
977
|
+
if (error instanceof PgvectorDimensionMismatchError) {
|
|
978
|
+
return new StudioHttpError(409, studioRagWidthMismatchMessage(error, config));
|
|
979
|
+
}
|
|
980
980
|
if (config.kind === "mock") {
|
|
981
981
|
return new StudioHttpError(500, error instanceof Error ? error.message : String(error));
|
|
982
982
|
}
|
|
@@ -985,7 +985,7 @@ function formatStudioRagOperationError(error, config) {
|
|
|
985
985
|
return new StudioHttpError(500, error instanceof Error ? error.message : String(error));
|
|
986
986
|
}
|
|
987
987
|
const parts = [
|
|
988
|
-
`Studio RAG embedding request failed for provider ${config.
|
|
988
|
+
`Studio RAG embedding request failed for ai.embedding (provider ${config.configuredProvider}, connection ${config.connection}, model ${config.model}).`,
|
|
989
989
|
];
|
|
990
990
|
if (config.baseUrl)
|
|
991
991
|
parts.push(`Base URL: ${config.baseUrl}.`);
|
|
@@ -998,6 +998,20 @@ function formatStudioRagOperationError(error, config) {
|
|
|
998
998
|
parts.push(`Error: ${error.message}`);
|
|
999
999
|
return new StudioHttpError(502, parts.join(" "));
|
|
1000
1000
|
}
|
|
1001
|
+
/** An existing pgvector table of another width: say where the new width comes from and how to resolve it, in config terms. */
|
|
1002
|
+
function studioRagWidthMismatchMessage(error, config) {
|
|
1003
|
+
const mismatch = `pgvector table "${error.table}" holds ${error.tableDimensions}-dimension vectors, but`;
|
|
1004
|
+
const rebuild = `Drop the table (DROP TABLE "${error.table}";) and build the index again`;
|
|
1005
|
+
const newTable = "point rag.storeConfig.pgvector.table at a new table";
|
|
1006
|
+
if (config.kind === "mock") {
|
|
1007
|
+
return `${mismatch} the mock embedder writes ${error.dimensions}. ${rebuild}, or ${newTable}.`;
|
|
1008
|
+
}
|
|
1009
|
+
const source = config.dimensions !== undefined
|
|
1010
|
+
? `ai.embedding.dimensions asks for ${error.dimensions}`
|
|
1011
|
+
: `embedding model ${config.model} returns ${error.dimensions}`;
|
|
1012
|
+
return (`${mismatch} ${source}. ${rebuild}, ${newTable}, ` +
|
|
1013
|
+
`or set ai.embedding.dimensions: ${error.tableDimensions} if model ${config.model} supports that width.`);
|
|
1014
|
+
}
|
|
1001
1015
|
function findApiCallError(error) {
|
|
1002
1016
|
const queue = [error];
|
|
1003
1017
|
for (let i = 0; i < queue.length; i++) {
|
|
@@ -1032,30 +1046,20 @@ function clearIncompatibleRagStore(state, config) {
|
|
|
1032
1046
|
return;
|
|
1033
1047
|
const embeddingsJsonPath = `${store.basePath}.embeddings.json`;
|
|
1034
1048
|
const embeddings = readOptionalJson(embeddingsJsonPath);
|
|
1035
|
-
if (embeddings?.dimensions === undefined
|
|
1049
|
+
if (embeddings?.dimensions === undefined)
|
|
1050
|
+
return;
|
|
1051
|
+
// The file store refuses mixed widths. When the width isn't known up front, vectors from a
|
|
1052
|
+
// different embedder may not match it, so they go too.
|
|
1053
|
+
const lockPath = join(state.schemaDir, "schema.lock.json");
|
|
1054
|
+
const compatible = config.dimensions !== undefined
|
|
1055
|
+
? embeddings.dimensions === config.dimensions
|
|
1056
|
+
: readOptionalJson(lockPath)?.embedderId === config.embedderId;
|
|
1057
|
+
if (compatible)
|
|
1036
1058
|
return;
|
|
1037
|
-
for (const path of [
|
|
1038
|
-
`${store.basePath}.embeddings.json`,
|
|
1039
|
-
`${store.basePath}.embeddings.bin`,
|
|
1040
|
-
join(state.schemaDir, "schema.lock.json"),
|
|
1041
|
-
]) {
|
|
1059
|
+
for (const path of [`${store.basePath}.embeddings.json`, `${store.basePath}.embeddings.bin`, lockPath]) {
|
|
1042
1060
|
rmSync(path, { force: true });
|
|
1043
1061
|
}
|
|
1044
1062
|
}
|
|
1045
|
-
function defaultEmbeddingDimensions(model) {
|
|
1046
|
-
if (model === "text-embedding-3-large")
|
|
1047
|
-
return 3072;
|
|
1048
|
-
return 1536;
|
|
1049
|
-
}
|
|
1050
|
-
function readPositiveIntegerEnv(value) {
|
|
1051
|
-
if (value === undefined || value.trim() === "")
|
|
1052
|
-
return undefined;
|
|
1053
|
-
const parsed = Number(value);
|
|
1054
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
1055
|
-
throw new StudioHttpError(400, `Invalid Studio RAG embedding dimensions: ${value}`);
|
|
1056
|
-
}
|
|
1057
|
-
return parsed;
|
|
1058
|
-
}
|
|
1059
1063
|
function serializeRagResult(result) {
|
|
1060
1064
|
return {
|
|
1061
1065
|
id: result.id,
|