feat: configure deep search llm models
This commit is contained in:
+84
-14
@@ -5,10 +5,12 @@ import {
|
||||
buildFallbackResearchPlan,
|
||||
canonicalizeUrl,
|
||||
createDeepSearchEngine,
|
||||
createPortalGatewayResearchLlmResolver,
|
||||
extractEvidence,
|
||||
htmlToPlainText,
|
||||
readResearchSource,
|
||||
} from './deep-search-engine.mjs';
|
||||
import { executeDeepSearchLlmGateway } from './deep-search-llm-gateway.mjs';
|
||||
import { createDeepSearchHttpServer } from './deep-search-server.mjs';
|
||||
import { createDeepSearchStore } from './deep-search-store.mjs';
|
||||
|
||||
@@ -133,19 +135,23 @@ This model-generated analysis is intentionally long enough to pass report valida
|
||||
const engine = createDeepSearchEngine({
|
||||
store,
|
||||
idFactory: () => 'task-llm',
|
||||
llm: {
|
||||
async plan() {
|
||||
return {
|
||||
research_plan: [{
|
||||
goal: 'Primary evidence',
|
||||
queries: ['official evidence', 'independent evidence'],
|
||||
sources: ['web'],
|
||||
}],
|
||||
};
|
||||
},
|
||||
async synthesize() {
|
||||
return llmReport;
|
||||
},
|
||||
llmResolver: async ({ providerKeyId, model }) => {
|
||||
assert.equal(providerKeyId, 'provider-key-1');
|
||||
assert.equal(model, 'research-model');
|
||||
return {
|
||||
async plan() {
|
||||
return {
|
||||
research_plan: [{
|
||||
goal: 'Primary evidence',
|
||||
queries: ['official evidence', 'independent evidence'],
|
||||
sources: ['web'],
|
||||
}],
|
||||
};
|
||||
},
|
||||
async synthesize() {
|
||||
return llmReport;
|
||||
},
|
||||
};
|
||||
},
|
||||
memorySink: async (payload) => {
|
||||
memoryPayload = payload;
|
||||
@@ -172,18 +178,75 @@ This model-generated analysis is intentionally long enough to pass report valida
|
||||
question: 'Evaluate the tested claim',
|
||||
depth: 'quick',
|
||||
userId: 'memory-user',
|
||||
llmProviderKeyId: 'provider-key-1',
|
||||
llmModel: 'research-model',
|
||||
});
|
||||
const task = await waitFor(() => {
|
||||
const current = engine.getTask('task-llm');
|
||||
return current.status === 'completed' ? current : null;
|
||||
});
|
||||
assert.equal(task.report, llmReport);
|
||||
assert.equal(task.llmProviderKeyId, 'provider-key-1');
|
||||
assert.equal(task.llmModel, 'research-model');
|
||||
assert.ok(task.events.some((event) => event.type === 'llm_selected'));
|
||||
assert.equal(memoryPayload.userId, 'memory-user');
|
||||
assert.equal(memoryPayload.taskId, 'task-llm');
|
||||
assert.ok(task.events.some((event) => event.type === 'memory_saved'));
|
||||
store.close();
|
||||
});
|
||||
|
||||
test('Deep Search portal LLM gateway keeps provider secrets inside the portal', async () => {
|
||||
let request = null;
|
||||
const unauthorized = await executeDeepSearchLlmGateway({
|
||||
llmProviderService: {},
|
||||
expectedSecret: 'expected',
|
||||
providedSecret: 'wrong',
|
||||
input: {},
|
||||
});
|
||||
assert.equal(unauthorized.status, 401);
|
||||
const authorized = await executeDeepSearchLlmGateway({
|
||||
expectedSecret: 'expected',
|
||||
providedSecret: 'expected',
|
||||
input: {
|
||||
providerKeyId: 'key-1',
|
||||
model: 'model-1',
|
||||
messages: [{ role: 'user', content: 'Plan research' }],
|
||||
},
|
||||
llmProviderService: {
|
||||
async createChatCompletion(input) {
|
||||
request = input;
|
||||
return {
|
||||
ok: true,
|
||||
providerKeyId: 'key-1',
|
||||
providerId: 'custom',
|
||||
model: 'model-1',
|
||||
reply: '{"research_plan":[]}',
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(authorized.status, 200);
|
||||
assert.equal(request.providerKeyId, 'key-1');
|
||||
assert.equal(request.model, 'model-1');
|
||||
|
||||
const resolver = createPortalGatewayResearchLlmResolver({
|
||||
endpoint: 'http://portal.local/api/internal/deep-search/llm',
|
||||
secret: 'expected',
|
||||
fetchImpl: async (_url, options) => {
|
||||
const body = JSON.parse(options.body);
|
||||
assert.equal(options.headers.authorization, 'Bearer expected');
|
||||
assert.equal(body.providerKeyId, 'key-1');
|
||||
return new Response(JSON.stringify({ ok: true, reply: '{"research_plan":[]}' }), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
},
|
||||
});
|
||||
assert.equal(resolver.configured, true);
|
||||
const resolved = await resolver({ providerKeyId: 'key-1', model: 'model-1' });
|
||||
assert.deepEqual(await resolved.plan('Question', 'quick'), { research_plan: [] });
|
||||
});
|
||||
|
||||
test('Deep Search supports cancellation while a provider is running', async () => {
|
||||
const store = createDeepSearchStore({ databasePath: ':memory:' });
|
||||
const engine = createDeepSearchEngine({
|
||||
@@ -239,7 +302,12 @@ test('Deep Search HTTP service exposes health, async start, status, search, and
|
||||
const started = await fetch(`${base}/v1/research`, {
|
||||
method: 'POST',
|
||||
headers: { ...headers, 'x-user-id': 'user-a' },
|
||||
body: JSON.stringify({ question: 'Evaluate Deep Search', depth: 'quick' }),
|
||||
body: JSON.stringify({
|
||||
question: 'Evaluate Deep Search',
|
||||
depth: 'quick',
|
||||
llm_provider_key_id: 'key-http',
|
||||
llm_model: 'model-http',
|
||||
}),
|
||||
}).then((response) => response.json());
|
||||
assert.equal(started.task_id, 'task-http');
|
||||
assert.equal((await fetch(`${base}/v1/research/${started.task_id}`, {
|
||||
@@ -253,5 +321,7 @@ test('Deep Search HTTP service exposes health, async start, status, search, and
|
||||
return task.status === 'completed' ? task : null;
|
||||
});
|
||||
assert.equal(completed.progress, 100);
|
||||
assert.equal(completed.llmProviderKeyId, 'key-http');
|
||||
assert.equal(completed.llmModel, 'model-http');
|
||||
assert.ok(completed.sources.every((source) => !Object.hasOwn(source, 'content')));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user