mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-28 08:49:38 +02:00
fix(cli): resolve embedding provider explicitly and surface lane status in sl search (#192)
* feat(cli): add tryUseManagedLocalEmbeddingsDaemon for read-only callers * feat(cli): add resolveProjectEmbeddingProvider helper * fix(cli): wire sl search through resolveProjectEmbeddingProvider so semantic lane works * fix(cli): wire wiki/knowledge search through resolveProjectEmbeddingProvider * feat(cli): surface embeddings-unavailable status when sl search returns empty * refactor(cli): route admin reindex through resolveProjectEmbeddingProvider * refactor: pass embeddingProvider into ingest/scan instead of resolving inside @ktx/context * refactor(mcp): resolve embedding provider in CLI factory, pass into context ports * refactor(context): delete MANAGED_SENTENCE_TRANSFORMERS_BASE_URL sentinel * refactor(cli): delete sentinel-based managed-embeddings indirection * chore: scrub stale managed-embeddings sentinel references from tests and smoke script * chore: unexport unused EmbeddingResolutionMode alias * fix(cli): force pathPrefix="" when targeting the managed embeddings daemon The managed daemon serves /embeddings/compute directly. The default pathPrefix in @ktx/llm is /api, so omitting sentenceTransformers from ktx.yaml produced /api/embeddings/compute -> 404. The resolver now sets pathPrefix='' explicitly when wiring the managed daemon URL, matching what the daemon actually exposes.
This commit is contained in:
parent
56a967278a
commit
9d92c79988
36 changed files with 750 additions and 442 deletions
|
|
@ -77,12 +77,24 @@ describe('runKtxSl', () => {
|
|||
expect(validateIo.stdout()).toContain('Valid semantic-layer source: warehouse/orders');
|
||||
|
||||
const listIo = makeIo();
|
||||
await expect(runKtxSl({ command: 'list', projectDir, connectionId: 'warehouse' }, listIo.io)).resolves.toBe(0);
|
||||
await expect(
|
||||
runKtxSl({ command: 'list', projectDir, connectionId: 'warehouse', cliVersion: '0.0.0-test' }, listIo.io),
|
||||
).resolves.toBe(0);
|
||||
expect(listIo.stdout()).toContain('warehouse\torders\tcolumns=1\tmeasures=0\tjoins=0');
|
||||
|
||||
const searchIo = makeIo();
|
||||
await expect(
|
||||
runKtxSl({ command: 'search', projectDir, connectionId: 'warehouse', query: 'order', json: true }, searchIo.io),
|
||||
runKtxSl(
|
||||
{
|
||||
command: 'search',
|
||||
projectDir,
|
||||
connectionId: 'warehouse',
|
||||
query: 'order',
|
||||
json: true,
|
||||
cliVersion: '0.0.0-test',
|
||||
},
|
||||
searchIo.io,
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
expect(JSON.parse(searchIo.stdout())).toMatchObject({
|
||||
kind: 'list',
|
||||
|
|
@ -106,7 +118,14 @@ describe('runKtxSl', () => {
|
|||
const searchIo = makeIo();
|
||||
await expect(
|
||||
runKtxSl(
|
||||
{ command: 'search', projectDir, connectionId: 'warehouse', query: 'order', output: 'pretty' },
|
||||
{
|
||||
command: 'search',
|
||||
projectDir,
|
||||
connectionId: 'warehouse',
|
||||
query: 'order',
|
||||
output: 'pretty',
|
||||
cliVersion: '0.0.0-test',
|
||||
},
|
||||
searchIo.io,
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
|
|
@ -136,7 +155,14 @@ describe('runKtxSl', () => {
|
|||
const listIo = makeIo();
|
||||
await expect(
|
||||
runKtxSl(
|
||||
{ command: 'search', projectDir, connectionId: 'warehouse', query: 'paid', json: true },
|
||||
{
|
||||
command: 'search',
|
||||
projectDir,
|
||||
connectionId: 'warehouse',
|
||||
query: 'paid',
|
||||
json: true,
|
||||
cliVersion: '0.0.0-test',
|
||||
},
|
||||
listIo.io,
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
|
|
@ -575,7 +601,7 @@ joins: []
|
|||
|
||||
const listIo = makeIo();
|
||||
const code = await runKtxSl(
|
||||
{ command: 'list', projectDir, connectionId: 'warehouse', output: 'json' },
|
||||
{ command: 'list', projectDir, connectionId: 'warehouse', output: 'json', cliVersion: '0.0.0-test' },
|
||||
listIo.io,
|
||||
);
|
||||
expect(code).toBe(0);
|
||||
|
|
@ -601,13 +627,80 @@ joins: []
|
|||
});
|
||||
});
|
||||
|
||||
it('search prints embeddings status when results are empty', async () => {
|
||||
const stderr: string[] = [];
|
||||
const io = {
|
||||
stdout: { write: (_chunk: string) => {} },
|
||||
stderr: {
|
||||
write: (chunk: string) => {
|
||||
stderr.push(chunk);
|
||||
},
|
||||
},
|
||||
};
|
||||
const projectDir = join(tempDir, 'empty-status');
|
||||
const project = await initKtxProject({ projectDir });
|
||||
await expect(
|
||||
runKtxSl(
|
||||
{
|
||||
command: 'search',
|
||||
projectDir: project.projectDir,
|
||||
query: 'nope',
|
||||
cliVersion: '0.5.0',
|
||||
},
|
||||
io,
|
||||
{
|
||||
loadProject: async () => project,
|
||||
resolveEmbeddingProvider: async () => ({
|
||||
kind: 'managed-unavailable',
|
||||
reason: 'managed embeddings daemon is not running',
|
||||
}),
|
||||
searchLocalSlSources: async () => [],
|
||||
},
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
expect(stderr.join('')).toMatch(/embeddings: unavailable/);
|
||||
expect(stderr.join('')).toMatch(/managed embeddings daemon is not running/);
|
||||
});
|
||||
|
||||
it('passes a managed-daemon-backed embedding service into the search', async () => {
|
||||
const projectDir = join(tempDir, 'resolver-project');
|
||||
const project = await initKtxProject({ projectDir });
|
||||
const search = vi.fn(async () => []);
|
||||
const searchIo = makeIo();
|
||||
await expect(
|
||||
runKtxSl(
|
||||
{
|
||||
command: 'search',
|
||||
projectDir: project.projectDir,
|
||||
query: 'income',
|
||||
cliVersion: '0.5.0',
|
||||
json: true,
|
||||
},
|
||||
searchIo.io,
|
||||
{
|
||||
loadProject: async () => project,
|
||||
resolveEmbeddingProvider: async () => ({
|
||||
kind: 'managed-running',
|
||||
provider: { id: 'fake' } as never,
|
||||
baseUrl: 'http://127.0.0.1:51234',
|
||||
}),
|
||||
searchLocalSlSources: search,
|
||||
},
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
expect(search).toHaveBeenCalledWith(
|
||||
project,
|
||||
expect.objectContaining({ embeddingService: expect.any(Object) }),
|
||||
);
|
||||
});
|
||||
|
||||
it('emits sl list with grouping and Clack-style framing when output=pretty', async () => {
|
||||
const projectDir = join(tempDir, 'project');
|
||||
await seedSlSource({ projectDir });
|
||||
|
||||
const listIo = makeIo();
|
||||
const code = await runKtxSl(
|
||||
{ command: 'list', projectDir, connectionId: 'warehouse', output: 'pretty' },
|
||||
{ command: 'list', projectDir, connectionId: 'warehouse', output: 'pretty', cliVersion: '0.0.0-test' },
|
||||
listIo.io,
|
||||
);
|
||||
expect(code).toBe(0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue