Skip to content

Commit c54f6a6

Browse files
committed
feat: model recommend use english prompt & output
1 parent 14fc293 commit c54f6a6

5 files changed

Lines changed: 266 additions & 267 deletions

File tree

packages/cli/src/commands/advisor/recommend.ts

Lines changed: 66 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -29,41 +29,41 @@ function formatContextWindow(tokens: number): string {
2929
}
3030

3131
const MODALITY_LABELS: Record<string, string> = {
32-
Text: "文本",
33-
Image: "图片",
34-
Video: "视频",
35-
Audio: "音频",
32+
Text: "Text",
33+
Image: "Image",
34+
Video: "Video",
35+
Audio: "Audio",
3636
};
3737
const CAPABILITY_LABELS: Record<string, string> = {
38-
TG: "文本生成",
39-
VU: "视觉理解",
40-
IG: "图像生成",
41-
VG: "视频生成",
42-
TTS: "语音合成",
43-
ASR: "语音识别",
44-
Reasoning: "推理",
38+
TG: "Text Gen",
39+
VU: "Vision",
40+
IG: "Image Gen",
41+
VG: "Video Gen",
42+
TTS: "Text-to-Speech",
43+
ASR: "Speech-to-Text",
44+
Reasoning: "Reasoning",
4545
};
4646
const BUDGET_LABELS: Record<string, string> = {
47-
low: "低成本优先",
48-
medium: "适中",
49-
high: "高投入",
47+
low: "Cost-Effective",
48+
medium: "Balanced",
49+
high: "High Investment",
5050
};
5151
const QUALITY_LABELS: Record<string, string> = {
52-
flagship: "旗舰优先",
53-
balanced: "均衡",
54-
"cost-optimized": "性价比优先",
52+
flagship: "Flagship",
53+
balanced: "Balanced",
54+
"cost-optimized": "Value",
5555
};
5656
const PREFERENCE_MODE_LABELS: Record<string, string> = {
57-
scoped: "限定范围",
58-
comparison: "对比评估",
59-
alternative: "替代推荐",
57+
scoped: "Scoped",
58+
comparison: "Comparison",
59+
alternative: "Alternative",
6060
};
6161

6262
function formatIntentSummary(intent: IntentProfile, noColor: boolean): string {
6363
const colorize = noColor ? new Chalk({ level: 0 }) : chalk;
6464

6565
const lines: string[] = [];
66-
lines.push(colorize.cyan.bold("需求理解"));
66+
lines.push(colorize.cyan.bold("Intent Analysis"));
6767

6868
if (intent.taskSummary) {
6969
lines.push("");
@@ -72,48 +72,48 @@ function formatIntentSummary(intent: IntentProfile, noColor: boolean): string {
7272

7373
if (intent.scenarioHints.length) {
7474
lines.push("");
75-
lines.push(`${colorize.dim("场景特征")} ${intent.scenarioHints.join(" · ")}`);
75+
lines.push(`${colorize.dim("Scenario")} ${intent.scenarioHints.join(" · ")}`);
7676
}
7777

7878
const inputLabels = intent.inputModality.map((mod) => MODALITY_LABELS[mod] ?? mod);
7979
const outputLabels = intent.outputModality.map((mod) => MODALITY_LABELS[mod] ?? mod);
8080
if (inputLabels.length || outputLabels.length) {
8181
lines.push("");
8282
const parts: string[] = [];
83-
if (inputLabels.length) parts.push(`${colorize.dim("输入")} ${inputLabels.join(", ")}`);
84-
if (outputLabels.length) parts.push(`${colorize.dim("输出")} ${outputLabels.join(", ")}`);
83+
if (inputLabels.length) parts.push(`${colorize.dim("Input")} ${inputLabels.join(", ")}`);
84+
if (outputLabels.length) parts.push(`${colorize.dim("Output")} ${outputLabels.join(", ")}`);
8585
lines.push(parts.join(" "));
8686
}
8787

8888
const capLabels = intent.requiredCapabilities.map((cap) => CAPABILITY_LABELS[cap] ?? cap);
8989
if (capLabels.length) {
90-
lines.push(`${colorize.dim("所需能力")} ${capLabels.join(", ")}`);
90+
lines.push(`${colorize.dim("Capabilities")} ${capLabels.join(", ")}`);
9191
}
9292

9393
const budgetLabel = BUDGET_LABELS[intent.budget] ?? intent.budget;
9494
const qualityLabel = QUALITY_LABELS[intent.qualityPreference] ?? intent.qualityPreference;
9595
lines.push("");
9696
lines.push(
97-
`${colorize.dim("预算倾向")} ${budgetLabel} ${colorize.dim("质量偏好")} ${qualityLabel}`,
97+
`${colorize.dim("Budget")} ${budgetLabel} ${colorize.dim("Quality")} ${qualityLabel}`,
9898
);
9999

100100
const preference = intent.modelPreference;
101101
if (preference && preference.mode !== "unconstrained") {
102102
lines.push("");
103103
const modeLabel = PREFERENCE_MODE_LABELS[preference.mode] ?? preference.mode;
104-
const prefParts = [colorize.dim("推荐模式") + ` ${colorize.yellow(modeLabel)}`];
104+
const prefParts = [colorize.dim("Mode") + ` ${colorize.yellow(modeLabel)}`];
105105
if (preference.targets?.length) {
106-
prefParts.push(colorize.dim("目标") + ` ${preference.targets.join(", ")}`);
106+
prefParts.push(colorize.dim("Targets") + ` ${preference.targets.join(", ")}`);
107107
}
108108
if (preference.excludes?.length) {
109-
prefParts.push(colorize.dim("排除") + ` ${preference.excludes.join(", ")}`);
109+
prefParts.push(colorize.dim("Excludes") + ` ${preference.excludes.join(", ")}`);
110110
}
111111
lines.push(prefParts.join(" "));
112112
}
113113

114114
if (intent.segments?.length) {
115115
lines.push("");
116-
lines.push(colorize.dim("任务拆解"));
116+
lines.push(colorize.dim("Pipeline"));
117117
for (const [idx, segment] of intent.segments.entries()) {
118118
const outMods = segment.outputModality.map((mod) => MODALITY_LABELS[mod] ?? mod).join(", ");
119119
lines.push(
@@ -131,19 +131,19 @@ function formatIntentSummary(intent: IntentProfile, noColor: boolean): string {
131131
});
132132
}
133133

134-
const RECOMMEND_LABELS = ["最佳推荐", "次优选择", "备选参考"];
134+
const RECOMMEND_LABELS = ["Best Pick", "Runner-Up", "Alternative"];
135135

136136
function renderCard(rec: RecommendedModel, index: number, colorize: ChalkInstance): string {
137137
const labelColors = [colorize.green.bold, colorize.blue.bold, colorize.magenta.bold];
138138
const colorFn = labelColors[index] ?? colorize.white.bold;
139-
const label = RECOMMEND_LABELS[index] ?? `推荐 #${index + 1}`;
139+
const label = RECOMMEND_LABELS[index] ?? `#${index + 1}`;
140140

141141
const lines: string[] = [];
142-
lines.push(colorFn(`⬢ 推荐 #${index + 1}${label}`));
142+
lines.push(colorFn(`⬢ #${index + 1}${label}`));
143143
lines.push("");
144144
lines.push(`${colorize.bold(rec.name)} ${colorize.dim(`(${rec.model})`)}`);
145145
lines.push("");
146-
lines.push(`${colorize.cyan("推荐理由")} ${rec.reason}`);
146+
lines.push(`${colorize.cyan("Why")} ${rec.reason}`);
147147

148148
if (rec.highlights.length) {
149149
lines.push("");
@@ -153,8 +153,8 @@ function renderCard(rec: RecommendedModel, index: number, colorize: ChalkInstanc
153153
}
154154

155155
const meta: string[] = [];
156-
if (rec.contextWindow) meta.push(`上下文 ${formatContextWindow(rec.contextWindow)}`);
157-
if (rec.maxOutputTokens) meta.push(`最大输出 ${formatContextWindow(rec.maxOutputTokens)}`);
156+
if (rec.contextWindow) meta.push(`Context ${formatContextWindow(rec.contextWindow)}`);
157+
if (rec.maxOutputTokens) meta.push(`Max Output ${formatContextWindow(rec.maxOutputTokens)}`);
158158
if (meta.length) {
159159
lines.push("");
160160
lines.push(colorize.dim(meta.join(" · ")));
@@ -163,7 +163,7 @@ function renderCard(rec: RecommendedModel, index: number, colorize: ChalkInstanc
163163
const docLink = buildDocLink(rec.docUrl);
164164
if (docLink) {
165165
lines.push("");
166-
lines.push(colorize.dim(`文档 ${docLink}`));
166+
lines.push(colorize.dim(`Docs ${docLink}`));
167167
}
168168

169169
return boxen(lines.join("\n"), {
@@ -183,7 +183,7 @@ function formatSingleResult(results: RecommendedModel[], noColor: boolean): stri
183183
function formatPipelineResult(summary: string, steps: PipelineStep[], noColor: boolean): string {
184184
const colorize = noColor ? new Chalk({ level: 0 }) : chalk;
185185
const lines: string[] = [];
186-
lines.push(` ${colorize.yellow.bold("⚡ 组合方案")} ${summary}`);
186+
lines.push(` ${colorize.yellow.bold("⚡ Pipeline")} ${summary}`);
187187

188188
for (const [stepIdx, { step, recommendations, warnings }] of steps.entries()) {
189189
lines.push("");
@@ -247,31 +247,31 @@ export default defineCommand({
247247

248248
if (!userInput.trim()) {
249249
if (isInteractive({ nonInteractive: config.nonInteractive })) {
250-
const hint = await promptText({ message: "描述你的需求:" });
250+
const hint = await promptText({ message: "Describe your requirement:" });
251251
if (!hint) {
252-
process.stderr.write("已取消。\n");
252+
process.stderr.write("Cancelled.\n");
253253
process.exit(1);
254254
}
255255
userInput = hint;
256256
} else {
257-
failIfMissing("message", 'bl advisor recommend "你的需求"');
257+
failIfMissing("message", 'bl advisor recommend "your requirement"');
258258
}
259259
}
260260

261261
const top = 3;
262262
const format = detectOutputFormat(config.output);
263263

264264
const modelsOptions: GetModelsOptions = {
265-
onPrepareStart: () => process.stderr.write("初始化中...\n"),
265+
onPrepareStart: () => process.stderr.write("Initializing model data...\n"),
266266
};
267-
process.stderr.write("正在分析需求...\n");
267+
process.stderr.write("Analyzing your request...\n");
268268
const [allModels, intent] = await Promise.all([
269269
getModels(config, modelsOptions),
270270
analyzeIntent(config, userInput),
271271
]);
272272

273273
if (intent.confidence === 0) {
274-
process.stderr.write("需求分析超时,使用默认参数继续...\n");
274+
process.stderr.write("Intent analysis timed out, using defaults...\n");
275275
} else {
276276
process.stderr.write("\n");
277277
}
@@ -297,20 +297,39 @@ export default defineCommand({
297297
}
298298

299299
// Stage 3: LLM Ranking
300-
const spinner = createSpinner("正在推荐最佳模型...");
300+
const spinner = createSpinner("Recommending best models...");
301301
spinner.start();
302302

303303
const result = await rankModels(config, candidates, intent, userInput, top);
304304

305305
spinner.stop();
306306

307307
if (isEmptyResult(result)) {
308-
emitBare("暂无满足该需求的模型。");
308+
emitBare("No suitable models found for this request.");
309309
return;
310310
}
311311

312312
if (format !== "text") {
313-
emitResult(result, format);
313+
emitResult(
314+
{
315+
intent: {
316+
taskSummary: intent.taskSummary,
317+
scenarioHints: intent.scenarioHints,
318+
complexity: intent.complexity,
319+
inputModality: intent.inputModality,
320+
outputModality: intent.outputModality,
321+
requiredCapabilities: intent.requiredCapabilities,
322+
budget: intent.budget,
323+
qualityPreference: intent.qualityPreference,
324+
modelPreference:
325+
intent.modelPreference?.mode !== "unconstrained" ? intent.modelPreference : undefined,
326+
segments: intent.segments,
327+
},
328+
result,
329+
candidates: candidates.length,
330+
},
331+
format,
332+
);
314333
return;
315334
}
316335

packages/cli/tests/e2e/advisor-recommend.e2e.test.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,21 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend(DashScope)",
6464
]);
6565
expect(exitCode, stderr).toBe(0);
6666
const data = parseStdoutJson<{
67-
type?: string;
68-
recommendations?: Array<{
69-
model?: string;
70-
name?: string;
71-
reason?: string;
72-
}>;
67+
intent?: { taskSummary?: string };
68+
result?: {
69+
type?: string;
70+
recommendations?: Array<{
71+
model?: string;
72+
name?: string;
73+
reason?: string;
74+
}>;
75+
};
76+
candidates?: number;
7377
}>(stdout);
74-
expect(data.type).toBe("single");
75-
expect(data.recommendations?.length).toBeGreaterThan(0);
76-
expect(data.recommendations?.[0]?.model).toBeDefined();
77-
expect(data.recommendations?.[0]?.reason).toBeDefined();
78+
expect(data.result?.type).toBe("single");
79+
expect(data.result?.recommendations?.length).toBeGreaterThan(0);
80+
expect(data.result?.recommendations?.[0]?.model).toBeDefined();
81+
expect(data.result?.recommendations?.[0]?.reason).toBeDefined();
7882
}, 120_000);
7983

8084
// ---- 模型偏好:正例 ----

0 commit comments

Comments
 (0)