Skip to content

Commit 0e85777

Browse files
authored
Merge pull request #94 from modelstudioai/feat/recommend-new
feat: remove intent-detect-v3 model
2 parents 6e095a6 + 58ab622 commit 0e85777

10 files changed

Lines changed: 116 additions & 421 deletions

File tree

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,7 @@ export default defineCommand({
272272
return result;
273273
});
274274

275-
const analyzeIntentPromise = analyzeIntent(ctx.client, userInput, {
276-
intentDetectBaseUrl: settings.intentDetectBaseUrl,
277-
}).then((result) => {
275+
const analyzeIntentPromise = analyzeIntent(ctx.client, userInput).then((result) => {
278276
intentReady = true;
279277
if (!modelsReady) {
280278
spinner.update("Agent: Intent analyzed, loading model data...");

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

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
9696
expect(data.result?.recommendations?.[0]?.highlights?.length).toBeGreaterThan(0);
9797
}, 120_000);
9898

99-
// ---- Model preference: positive cases ----
99+
// ---- Mode coverage: all 4 modes ----
100100

101-
test("scoped preference — intent contains modelPreference.mode=scoped when family is specified", async () => {
101+
test("mode: scoped — family-scoped query sets mode=scoped with targets", async () => {
102102
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
103103
"advisor",
104104
"recommend",
@@ -112,39 +112,62 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
112112
const data = parseStdoutJson<{
113113
intent?: { modelPreference?: { mode?: string; targets?: string[] } };
114114
}>(stdout);
115-
// Model preference detection depends on LLM interpretation
116-
// Accept either "scoped" or "unconstrained" as valid
117-
const mode = data.intent?.modelPreference?.mode;
118-
expect(mode === "scoped" || mode === "unconstrained" || mode === undefined).toBe(true);
115+
const pref = data.intent?.modelPreference;
116+
expect(pref?.mode).toBe("scoped");
117+
expect(pref?.targets?.length).toBeGreaterThan(0);
118+
// Should contain "deepseek" (case-insensitive substring)
119+
expect(pref?.targets?.some((t) => t.toLowerCase().includes("deepseek"))).toBe(true);
119120
}, 60_000);
120121

121-
test("comparison preference — intent contains modelPreference.mode=comparison when comparing models", async () => {
122+
test("mode: comparison — comparing two models sets mode=comparison with both targets", async () => {
122123
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
123124
"advisor",
124125
"recommend",
125126
"--dry-run",
126127
"--message",
127-
"Which is better for code generation, qwen-max or deepseek-v3?",
128+
"Compare qwen-max and deepseek-v3 for legal contract review, high precision required",
128129
"--output",
129130
"json",
130131
]);
131132
expect(exitCode, stderr).toBe(0);
132133
const data = parseStdoutJson<{
133134
intent?: { modelPreference?: { mode?: string; targets?: string[] } };
134135
}>(stdout);
135-
// Model preference detection depends on LLM interpretation
136-
// Accept either "comparison" or "unconstrained" as valid
137-
const mode = data.intent?.modelPreference?.mode;
138-
expect(mode === "comparison" || mode === "unconstrained" || mode === undefined).toBe(true);
136+
const pref = data.intent?.modelPreference;
137+
expect(pref?.mode).toBe("comparison");
138+
expect(pref?.targets?.length).toBeGreaterThanOrEqual(2);
139+
const targetsLower = pref?.targets?.map((t) => t.toLowerCase()) ?? [];
140+
expect(targetsLower.some((t) => t.includes("qwen"))).toBe(true);
141+
expect(targetsLower.some((t) => t.includes("deepseek"))).toBe(true);
142+
}, 60_000);
143+
144+
test("mode: alternative — reference model query sets mode=alternative with target", async () => {
145+
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
146+
"advisor",
147+
"recommend",
148+
"--dry-run",
149+
"--message",
150+
"Something like qwen-max but cheaper, for text summarization",
151+
"--output",
152+
"json",
153+
]);
154+
expect(exitCode, stderr).toBe(0);
155+
const data = parseStdoutJson<{
156+
intent?: { modelPreference?: { mode?: string; targets?: string[] } };
157+
}>(stdout);
158+
const pref = data.intent?.modelPreference;
159+
expect(pref?.mode).toBe("alternative");
160+
expect(pref?.targets?.length).toBeGreaterThan(0);
161+
expect(pref?.targets?.some((t) => t.toLowerCase().includes("qwen"))).toBe(true);
139162
}, 60_000);
140163

141-
test("excludes preference — intent detects modelPreference when excluding models", async () => {
164+
test("mode: excludes — excluding a family populates excludes array", async () => {
142165
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
143166
"advisor",
144167
"recommend",
145168
"--dry-run",
146169
"--message",
147-
"Not qwen, recommend a model suitable for text generation",
170+
"Recommend a model for text generation, but not qwen",
148171
"--output",
149172
"json",
150173
]);
@@ -157,18 +180,12 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", ()
157180
};
158181
};
159182
}>(stdout);
160-
// Model preference detection depends on LLM interpretation
161-
// If excludes is detected, verify it contains qwen; otherwise accept as valid
162183
const pref = data.intent?.modelPreference;
163-
if (pref?.excludes && pref.excludes.length > 0) {
164-
expect(pref.excludes.some((e) => e.toLowerCase().includes("qwen"))).toBe(true);
165-
}
166-
// Test passes if exit code is 0, regardless of whether excludes was detected
184+
expect(pref?.excludes?.length).toBeGreaterThan(0);
185+
expect(pref?.excludes?.some((e) => e.toLowerCase().includes("qwen"))).toBe(true);
167186
}, 60_000);
168187

169-
// ---- Model preference: negative cases ----
170-
171-
test("no preference — intent has no modelPreference or mode=unconstrained for generic queries", async () => {
188+
test("mode: unconstrained — generic query has no modelPreference", async () => {
172189
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
173190
"advisor",
174191
"recommend",

packages/core/src/advisor/constants/index.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
export { DEFAULT_INTENT } from "./defaults.ts";
22
export {
3-
INTENT_DETECT_MODEL,
4-
INTENT_DETECT_TOOL,
5-
buildIntentDetectSystemPrompt,
63
INTENT_EXTRACTION_MODEL,
74
INTENT_SYSTEM_PROMPT,
85
JSON_RETRY_HINT,

packages/core/src/advisor/constants/prompts.ts

Lines changed: 3 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
export const RANKING_MODEL = "qwen-flash";
22

33
/**
4-
* Dedicated intent-detection model. Sub-100ms latency, designed for fast
5-
* classification + tool routing. Provides mode/targets/excludes/complexity.
6-
*/
7-
export const INTENT_DETECT_MODEL = "tongyi-intent-detect-v3";
8-
9-
/**
10-
* Rich field extraction model. Runs in parallel with detect-v3 to extract
11-
* taskSummary, modalities, budget, qualityPreference, etc.
4+
* Intent extraction model. Runs a single call to extract taskSummary,
5+
* modalities, budget, qualityPreference, modelPreference, and all other
6+
* structured fields from the user's input.
127
*/
138
export const INTENT_EXTRACTION_MODEL = "qwen3.6-flash";
149

@@ -193,74 +188,3 @@ The intent's modelPreference.targets is the reference model.
193188
194189
## Output Format
195190
{"type":"single","recommendations":[{"model":"model ID","reason":"alternative analysis","highlights":["differentiators"]}]}`;
196-
197-
/**
198-
* Tool definition for `tongyi-intent-detect-v3`. Serialized to a JSON string
199-
* and embedded in the system prompt (NOT passed via the request body `tools`
200-
* field — this model doesn't use OpenAI function-calling; it has its own
201-
* `<tags>` / `
202-
</think>
203-
204-
` output format driven by the system prompt).
205-
*/
206-
export const INTENT_DETECT_TOOL = {
207-
name: "classify_intent",
208-
description:
209-
"Classify the user's model recommendation intent. Extract the mode and any model/family names mentioned.",
210-
parameters: {
211-
type: "object",
212-
properties: {
213-
mode: {
214-
type: "string",
215-
enum: ["unconstrained", "scoped", "comparison", "alternative"],
216-
description: "The detected intent mode.",
217-
},
218-
targets: {
219-
type: "array",
220-
items: { type: "string" },
221-
description:
222-
"Model or family names the user mentioned or wants to evaluate. Empty for unconstrained.",
223-
},
224-
excludes: {
225-
type: "array",
226-
items: { type: "string" },
227-
description:
228-
"Model or family names the user explicitly wants to exclude. Empty if none mentioned.",
229-
},
230-
complexity: {
231-
type: "string",
232-
enum: ["single", "pipeline"],
233-
description:
234-
"Whether the task needs a single model or a multi-step pipeline. Default to single unless the user clearly describes chained steps.",
235-
},
236-
},
237-
required: ["mode"],
238-
},
239-
} as const;
240-
241-
/**
242-
* Build the system prompt for `tongyi-intent-detect-v3` following the official
243-
* template from the model's documentation. The tools JSON is embedded in the
244-
* prompt text — the model reads it from the system message, not from a separate
245-
* `tools` request field.
246-
*
247-
* Official template:
248-
* "You are Qwen, created by Alibaba Cloud. You are a helpful assistant.
249-
* You may call one or more tools to assist with the user query.
250-
* The tools you can use are as follows:
251-
* {tools_string}
252-
* Response in INTENT_MODE."
253-
*
254-
* `INTENT_MODE` tells the model to emit `<tags>label</tags>` + `
255-
</think>
256-
257-
`
258-
* output. The tag carries the mode classification; the tool_call carries
259-
* structured targets/excludes/complexity extracted from the user's prompt.
260-
*/
261-
export function buildIntentDetectSystemPrompt(): string {
262-
const toolsString = JSON.stringify([INTENT_DETECT_TOOL], null, 2);
263-
return `You are Qwen, created by Alibaba Cloud. You are a helpful assistant. You may call one or more tools to assist with the user query. The tools you can use are as follows:
264-
${toolsString}
265-
Response in INTENT_MODE.`;
266-
}

0 commit comments

Comments
 (0)