Skip to content

Commit d154822

Browse files
committed
feat(advisor): add recommendation engine and agent orchestration
Replace the legacy pipeline with intent-driven routing, scoring, ranking, and clarification. Preserve the advisor JSON contract through an adapter and cover cancellation and streaming behavior with offline tests.
1 parent f1e9ce9 commit d154822

55 files changed

Lines changed: 5983 additions & 2352 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import type {
2+
EngineIntentProfile,
3+
EngineRecommendation,
4+
EngineRecommendSuccess,
5+
RecommendedModel,
6+
} from "bailian-cli-core";
7+
8+
/**
9+
* recommend-adapter.ts — maps engine results onto the advisor-legacy JSON
10+
* contract so downstream consumers of `bl advisor recommend --output json`
11+
* see a stable shape:
12+
*
13+
* - single → { type: "single", recommendations: RecommendedModel[] }
14+
* - pipeline (multi-step) → { type: "pipeline", summary, steps: PipelineStep[] }
15+
* - top-level { intent, result, candidates } preserved
16+
*
17+
* Engine-only signals (confidence/slot/routingReason/degradations/specs) never
18+
* enter stdout JSON; they surface on stderr instead.
19+
*/
20+
21+
/** Adapt an engine Recommendation onto the advisor-legacy RecommendedModel shape. */
22+
function toRecommendedModel(rec: EngineRecommendation): RecommendedModel {
23+
return {
24+
model: rec.model,
25+
name: rec.name,
26+
reason: rec.reason,
27+
highlights: rec.highlights,
28+
contextWindow: rec.specs?.contextWindow,
29+
maxOutputTokens: rec.specs?.maxOutputTokens,
30+
docUrl: rec.docUrl,
31+
};
32+
}
33+
34+
/** Project the engine IntentProfile onto the advisor-legacy intent JSON shape. */
35+
function toLegacyIntent(intent: EngineIntentProfile): Record<string, unknown> {
36+
return {
37+
taskSummary: intent.taskSummary,
38+
scenarioHints: intent.scenarioHints,
39+
complexity: intent.complexity,
40+
inputModality: intent.inputModality,
41+
outputModality: intent.outputModality,
42+
requiredCapabilities: intent.requiredCapabilities,
43+
budget: intent.budget,
44+
qualityPreference: intent.qualityPreference,
45+
modelPreference:
46+
intent.modelPreference?.mode !== "unconstrained" ? intent.modelPreference : undefined,
47+
segments: intent.segments,
48+
};
49+
}
50+
51+
export interface AdaptedCommandOutput {
52+
result: { type: "single" | "pipeline"; [key: string]: unknown };
53+
intent: Record<string, unknown>;
54+
candidates: number;
55+
}
56+
57+
/** Map an engine success result onto the legacy advisor output payload. */
58+
export function adaptCommandOutput(result: EngineRecommendSuccess): AdaptedCommandOutput {
59+
let adapted: { type: "single" | "pipeline"; [key: string]: unknown };
60+
if (result.steps?.length) {
61+
adapted = {
62+
type: "pipeline",
63+
summary: result.steps.map((step) => step.step).join(" → "),
64+
steps: result.steps.map((step) => ({
65+
step: step.step,
66+
recommendations: step.recommendations.map(toRecommendedModel),
67+
})),
68+
...(result.warnings?.length ? { warnings: result.warnings } : {}),
69+
};
70+
} else {
71+
adapted = {
72+
type: "single",
73+
recommendations: result.recommendations.map(toRecommendedModel),
74+
};
75+
}
76+
return {
77+
result: adapted,
78+
intent: toLegacyIntent(result.intent),
79+
candidates: result.candidatePool,
80+
};
81+
}

0 commit comments

Comments
 (0)