feat(api): orchestrator cutover — replace bandit with multi-agent pipeline (ADR-0013 step 6)
POST /recommend now calls ml/serving /recommend with pre-computed agent snippets + task context instead of /generate + /score/egreedy/v2. Falls back to a random signal candidate when ml/serving is unavailable. Removes: remotePolicy, fetchLlmCandidates, sendRewardWithRetry, candidateCache, pickPromptVersion. Feedback handler keeps inferReward + tipFeedback writes for observability; reward delivery to the bandit is gone. tipScores.policy is now 'orchestrator'; promptVersion is 'v4-orchestrator'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,7 @@
|
||||
* These can import directly from the module without any mocking.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { inferReward, dueAgeDays, pickPromptVersion } from '../recommender.js';
|
||||
import { config } from '../../config.js';
|
||||
import { inferReward, dueAgeDays } from '../recommender.js';
|
||||
|
||||
describe('inferReward', () => {
|
||||
it('dismiss → -1', () => expect(inferReward('dismiss', null)).toBe(-1.0));
|
||||
@@ -38,45 +37,3 @@ describe('dueAgeDays', () => {
|
||||
expect(dueAgeDays({ date: yesterday })).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pickPromptVersion', () => {
|
||||
// Save + restore the original env-driven config field across tests.
|
||||
let original: string;
|
||||
beforeEach(() => { original = config.TIP_PROMPT_VERSION; });
|
||||
afterEach(() => { (config as { TIP_PROMPT_VERSION: string }).TIP_PROMPT_VERSION = original; });
|
||||
|
||||
it('empty config → null (let ml/serving pick its default)', () => {
|
||||
(config as { TIP_PROMPT_VERSION: string }).TIP_PROMPT_VERSION = '';
|
||||
expect(pickPromptVersion()).toBeNull();
|
||||
});
|
||||
|
||||
it('whitespace-only config → null', () => {
|
||||
(config as { TIP_PROMPT_VERSION: string }).TIP_PROMPT_VERSION = ' ';
|
||||
expect(pickPromptVersion()).toBeNull();
|
||||
});
|
||||
|
||||
it('single value → that value', () => {
|
||||
(config as { TIP_PROMPT_VERSION: string }).TIP_PROMPT_VERSION = 'v2-mentor';
|
||||
expect(pickPromptVersion()).toBe('v2-mentor');
|
||||
});
|
||||
|
||||
it('comma-separated → uniformly samples from the set', () => {
|
||||
(config as { TIP_PROMPT_VERSION: string }).TIP_PROMPT_VERSION = 'v1,v2-mentor,v3-few-shot';
|
||||
const seen = new Set<string>();
|
||||
// With 100 trials, the chance of missing any of 3 buckets is (2/3)^100 ≈ 0 — test is reliable.
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const picked = pickPromptVersion();
|
||||
expect(picked).not.toBeNull();
|
||||
seen.add(picked!);
|
||||
}
|
||||
expect(seen).toEqual(new Set(['v1', 'v2-mentor', 'v3-few-shot']));
|
||||
});
|
||||
|
||||
it('trims whitespace around comma-separated entries', () => {
|
||||
(config as { TIP_PROMPT_VERSION: string }).TIP_PROMPT_VERSION = ' v1 , v2-mentor ';
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const picked = pickPromptVersion()!;
|
||||
expect(['v1', 'v2-mentor']).toContain(picked);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user