feat: configure deep search llm models

This commit is contained in:
john
2026-07-23 22:46:10 +08:00
parent be1e4c18c0
commit 784a10a592
11 changed files with 360 additions and 35 deletions
+84 -14
View File
@@ -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')));
});