From 58ab622e11db404878386809d9f032c868443dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=BE=E6=8D=B7=E6=99=94?= Date: Sun, 12 Jul 2026 13:44:00 +0800 Subject: [PATCH] feat: remove intent-detect-v3 model --- .../src/commands/advisor/recommend.ts | 4 +- .../tests/e2e/advisor-recommend.e2e.test.ts | 63 +++-- packages/core/src/advisor/constants/index.ts | 3 - .../core/src/advisor/constants/prompts.ts | 82 +----- packages/core/src/advisor/intent.ts | 261 +++--------------- packages/core/src/advisor/recall-semantic.ts | 54 ++-- packages/core/src/client/endpoints.ts | 13 - packages/core/src/config/loader.ts | 2 - packages/core/src/config/schema.ts | 10 - packages/core/src/types/api.ts | 45 --- 10 files changed, 116 insertions(+), 421 deletions(-) diff --git a/packages/commands/src/commands/advisor/recommend.ts b/packages/commands/src/commands/advisor/recommend.ts index 865919db..c848a511 100644 --- a/packages/commands/src/commands/advisor/recommend.ts +++ b/packages/commands/src/commands/advisor/recommend.ts @@ -272,9 +272,7 @@ export default defineCommand({ return result; }); - const analyzeIntentPromise = analyzeIntent(ctx.client, userInput, { - intentDetectBaseUrl: settings.intentDetectBaseUrl, - }).then((result) => { + const analyzeIntentPromise = analyzeIntent(ctx.client, userInput).then((result) => { intentReady = true; if (!modelsReady) { spinner.update("Agent: Intent analyzed, loading model data..."); diff --git a/packages/commands/tests/e2e/advisor-recommend.e2e.test.ts b/packages/commands/tests/e2e/advisor-recommend.e2e.test.ts index ec2fb2b7..7f228709 100644 --- a/packages/commands/tests/e2e/advisor-recommend.e2e.test.ts +++ b/packages/commands/tests/e2e/advisor-recommend.e2e.test.ts @@ -96,9 +96,9 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", () expect(data.result?.recommendations?.[0]?.highlights?.length).toBeGreaterThan(0); }, 120_000); - // ---- Model preference: positive cases ---- + // ---- Mode coverage: all 4 modes ---- - test("scoped preference — intent contains modelPreference.mode=scoped when family is specified", async () => { + test("mode: scoped — family-scoped query sets mode=scoped with targets", async () => { const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [ "advisor", "recommend", @@ -112,19 +112,20 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", () const data = parseStdoutJson<{ intent?: { modelPreference?: { mode?: string; targets?: string[] } }; }>(stdout); - // Model preference detection depends on LLM interpretation - // Accept either "scoped" or "unconstrained" as valid - const mode = data.intent?.modelPreference?.mode; - expect(mode === "scoped" || mode === "unconstrained" || mode === undefined).toBe(true); + const pref = data.intent?.modelPreference; + expect(pref?.mode).toBe("scoped"); + expect(pref?.targets?.length).toBeGreaterThan(0); + // Should contain "deepseek" (case-insensitive substring) + expect(pref?.targets?.some((t) => t.toLowerCase().includes("deepseek"))).toBe(true); }, 60_000); - test("comparison preference — intent contains modelPreference.mode=comparison when comparing models", async () => { + test("mode: comparison — comparing two models sets mode=comparison with both targets", async () => { const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [ "advisor", "recommend", "--dry-run", "--message", - "Which is better for code generation, qwen-max or deepseek-v3?", + "Compare qwen-max and deepseek-v3 for legal contract review, high precision required", "--output", "json", ]); @@ -132,19 +133,41 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", () const data = parseStdoutJson<{ intent?: { modelPreference?: { mode?: string; targets?: string[] } }; }>(stdout); - // Model preference detection depends on LLM interpretation - // Accept either "comparison" or "unconstrained" as valid - const mode = data.intent?.modelPreference?.mode; - expect(mode === "comparison" || mode === "unconstrained" || mode === undefined).toBe(true); + const pref = data.intent?.modelPreference; + expect(pref?.mode).toBe("comparison"); + expect(pref?.targets?.length).toBeGreaterThanOrEqual(2); + const targetsLower = pref?.targets?.map((t) => t.toLowerCase()) ?? []; + expect(targetsLower.some((t) => t.includes("qwen"))).toBe(true); + expect(targetsLower.some((t) => t.includes("deepseek"))).toBe(true); + }, 60_000); + + test("mode: alternative — reference model query sets mode=alternative with target", async () => { + const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [ + "advisor", + "recommend", + "--dry-run", + "--message", + "Something like qwen-max but cheaper, for text summarization", + "--output", + "json", + ]); + expect(exitCode, stderr).toBe(0); + const data = parseStdoutJson<{ + intent?: { modelPreference?: { mode?: string; targets?: string[] } }; + }>(stdout); + const pref = data.intent?.modelPreference; + expect(pref?.mode).toBe("alternative"); + expect(pref?.targets?.length).toBeGreaterThan(0); + expect(pref?.targets?.some((t) => t.toLowerCase().includes("qwen"))).toBe(true); }, 60_000); - test("excludes preference — intent detects modelPreference when excluding models", async () => { + test("mode: excludes — excluding a family populates excludes array", async () => { const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [ "advisor", "recommend", "--dry-run", "--message", - "Not qwen, recommend a model suitable for text generation", + "Recommend a model for text generation, but not qwen", "--output", "json", ]); @@ -157,18 +180,12 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: advisor recommend (DashScope)", () }; }; }>(stdout); - // Model preference detection depends on LLM interpretation - // If excludes is detected, verify it contains qwen; otherwise accept as valid const pref = data.intent?.modelPreference; - if (pref?.excludes && pref.excludes.length > 0) { - expect(pref.excludes.some((e) => e.toLowerCase().includes("qwen"))).toBe(true); - } - // Test passes if exit code is 0, regardless of whether excludes was detected + expect(pref?.excludes?.length).toBeGreaterThan(0); + expect(pref?.excludes?.some((e) => e.toLowerCase().includes("qwen"))).toBe(true); }, 60_000); - // ---- Model preference: negative cases ---- - - test("no preference — intent has no modelPreference or mode=unconstrained for generic queries", async () => { + test("mode: unconstrained — generic query has no modelPreference", async () => { const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [ "advisor", "recommend", diff --git a/packages/core/src/advisor/constants/index.ts b/packages/core/src/advisor/constants/index.ts index 8e00124d..8d8001d6 100644 --- a/packages/core/src/advisor/constants/index.ts +++ b/packages/core/src/advisor/constants/index.ts @@ -1,8 +1,5 @@ export { DEFAULT_INTENT } from "./defaults.ts"; export { - INTENT_DETECT_MODEL, - INTENT_DETECT_TOOL, - buildIntentDetectSystemPrompt, INTENT_EXTRACTION_MODEL, INTENT_SYSTEM_PROMPT, JSON_RETRY_HINT, diff --git a/packages/core/src/advisor/constants/prompts.ts b/packages/core/src/advisor/constants/prompts.ts index 7618e62e..731fff57 100644 --- a/packages/core/src/advisor/constants/prompts.ts +++ b/packages/core/src/advisor/constants/prompts.ts @@ -1,14 +1,9 @@ export const RANKING_MODEL = "qwen-flash"; /** - * Dedicated intent-detection model. Sub-100ms latency, designed for fast - * classification + tool routing. Provides mode/targets/excludes/complexity. - */ -export const INTENT_DETECT_MODEL = "tongyi-intent-detect-v3"; - -/** - * Rich field extraction model. Runs in parallel with detect-v3 to extract - * taskSummary, modalities, budget, qualityPreference, etc. + * Intent extraction model. Runs a single call to extract taskSummary, + * modalities, budget, qualityPreference, modelPreference, and all other + * structured fields from the user's input. */ export const INTENT_EXTRACTION_MODEL = "qwen3.6-flash"; @@ -193,74 +188,3 @@ The intent's modelPreference.targets is the reference model. ## Output Format {"type":"single","recommendations":[{"model":"model ID","reason":"alternative analysis","highlights":["differentiators"]}]}`; - -/** - * Tool definition for `tongyi-intent-detect-v3`. Serialized to a JSON string - * and embedded in the system prompt (NOT passed via the request body `tools` - * field — this model doesn't use OpenAI function-calling; it has its own - * `` / ` - - -` output format driven by the system prompt). - */ -export const INTENT_DETECT_TOOL = { - name: "classify_intent", - description: - "Classify the user's model recommendation intent. Extract the mode and any model/family names mentioned.", - parameters: { - type: "object", - properties: { - mode: { - type: "string", - enum: ["unconstrained", "scoped", "comparison", "alternative"], - description: "The detected intent mode.", - }, - targets: { - type: "array", - items: { type: "string" }, - description: - "Model or family names the user mentioned or wants to evaluate. Empty for unconstrained.", - }, - excludes: { - type: "array", - items: { type: "string" }, - description: - "Model or family names the user explicitly wants to exclude. Empty if none mentioned.", - }, - complexity: { - type: "string", - enum: ["single", "pipeline"], - description: - "Whether the task needs a single model or a multi-step pipeline. Default to single unless the user clearly describes chained steps.", - }, - }, - required: ["mode"], - }, -} as const; - -/** - * Build the system prompt for `tongyi-intent-detect-v3` following the official - * template from the model's documentation. The tools JSON is embedded in the - * prompt text — the model reads it from the system message, not from a separate - * `tools` request field. - * - * Official template: - * "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: - * {tools_string} - * Response in INTENT_MODE." - * - * `INTENT_MODE` tells the model to emit `label` + ` - - -` - * output. The tag carries the mode classification; the tool_call carries - * structured targets/excludes/complexity extracted from the user's prompt. - */ -export function buildIntentDetectSystemPrompt(): string { - const toolsString = JSON.stringify([INTENT_DETECT_TOOL], null, 2); - 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: -${toolsString} -Response in INTENT_MODE.`; -} diff --git a/packages/core/src/advisor/intent.ts b/packages/core/src/advisor/intent.ts index 85529f90..a0141562 100644 --- a/packages/core/src/advisor/intent.ts +++ b/packages/core/src/advisor/intent.ts @@ -1,18 +1,11 @@ -import { chatPath, intentDetectEndpoint } from "../client/endpoints.ts"; +import { chatPath } from "../client/endpoints.ts"; import type { Client } from "../client/client.ts"; -import type { ChatResponse, DashScopeIntentDetectResponse } from "../types/api.ts"; +import type { ChatResponse } from "../types/api.ts"; import { Complexities } from "./types.ts"; import type { IntentProfile, ModelPreference, PreferenceMode } from "./types.ts"; -import { - INTENT_DETECT_MODEL, - buildIntentDetectSystemPrompt, - INTENT_EXTRACTION_MODEL, - INTENT_SYSTEM_PROMPT, -} from "./constants/prompts.ts"; +import { INTENT_EXTRACTION_MODEL, INTENT_SYSTEM_PROMPT } from "./constants/prompts.ts"; import { DEFAULT_INTENT } from "./constants/defaults.ts"; -// ---- tongyi-intent-detect-v3: fast mode classification via DashScope native API - const VALID_MODES: readonly PreferenceMode[] = [ "unconstrained", "scoped", @@ -20,174 +13,44 @@ const VALID_MODES: readonly PreferenceMode[] = [ "alternative", ]; -/** Seconds per attempt; http.ts multiplies by 1000 -> ms. */ -const INTENT_DETECT_TIMEOUT = 10; - -/** - * Result of the fast intent-detect pass. Only the fields that - * `tongyi-intent-detect-v3` reliably extracts -- the remaining IntentProfile - * fields are still filled by the qwen3.6-flash extraction path. - */ -interface IntentDetectResult { - mode: PreferenceMode; - targets: string[]; - excludes: string[]; - complexity: "single" | "pipeline"; -} - -/** - * Parse the tags block from the detect model's response. - * Returns the trimmed tag content, or "" when no tag is found. - */ -function parseTags(content: string): string { - const re = /\s*([\s\S]*?)\s*<\/tags>/i; - const match = content.match(re); - return match ? match[1].trim() : ""; -} - -/** - * Parse the tool_call block from the detect model's response. - * Returns the first tool call's arguments, or null when not found. - */ -function parseToolCall(content: string): Record | null { - const re = /\s*([\s\S]*?)\s*<\/tool_call>/i; - const match = content.match(re); - if (!match) return null; - try { - const parsed = JSON.parse(match[1]); - if (Array.isArray(parsed) && parsed.length > 0 && parsed[0].arguments) { - return parsed[0].arguments as Record; - } - if ( - parsed && - typeof parsed === "object" && - "arguments" in (parsed as Record) - ) { - return (parsed as Record).arguments as Record; - } - return null; - } catch { - return null; - } -} - -/** - * Extract string[] helper for safe array extraction from unknown values. - */ -function safeStringArray(value: unknown): string[] { - if (!Array.isArray(value)) return []; - return value.filter((v): v is string => typeof v === "string"); -} - /** - * Call `tongyi-intent-detect-v3` via DashScope native API for fast classification. - * - * Uses INTENT_MODE: the model emits `mode` for classification - * plus a `classify_intent` tool_call carrying targets/excludes/complexity. - * Returns null on any failure; caller falls back to extraction model fields. + * Build a ModelPreference from the extraction model's raw output. + * Returns undefined when no valid preference data is present. */ -async function detectIntentMode( - client: Client, - input: string, - intentDetectBaseUrl?: string, -): Promise { - // 意图识别模型可指向独立 region/workspace;未配置时落回模型域 baseUrl。 - const url = intentDetectEndpoint(intentDetectBaseUrl ?? client.baseUrl); - - // Build system prompt following the official template: - // tools JSON is embedded in the prompt text, NOT passed via request body. - const systemPrompt = buildIntentDetectSystemPrompt(); - - // DashScope-native request shape: { model, input, parameters } - const body = { - model: INTENT_DETECT_MODEL, - input: { - messages: [ - { role: "system" as const, content: systemPrompt }, - { role: "user" as const, content: input }, - ], - }, - parameters: { - result_format: "message" as const, - max_tokens: 512, - temperature: 0, - }, - }; - - try { - const response = await client.requestJson({ - path: url, - method: "POST", - body, - timeout: INTENT_DETECT_TIMEOUT, - }); - - const text = response.output?.choices?.[0]?.message?.content ?? ""; +function extractModelPreference( + raw: Record | undefined, +): ModelPreference | undefined { + if (!raw || typeof raw !== "object") return undefined; - // 1. Extract mode from - const tag = parseTags(text); - const mode: PreferenceMode = VALID_MODES.includes(tag as PreferenceMode) - ? (tag as PreferenceMode) + const mode: PreferenceMode = + typeof raw.mode === "string" && VALID_MODES.includes(raw.mode as PreferenceMode) + ? (raw.mode as PreferenceMode) : "unconstrained"; - // 2. Extract structured fields from - const args = parseToolCall(text); - const targets = args ? safeStringArray(args.targets) : []; - const excludes = args ? safeStringArray(args.excludes) : []; - const rawComplexity = args?.complexity; - const complexity = rawComplexity === "pipeline" ? ("pipeline" as const) : ("single" as const); + const targets = Array.isArray(raw.targets) + ? (raw.targets as unknown[]).filter((v): v is string => typeof v === "string") + : []; + const excludes = Array.isArray(raw.excludes) + ? (raw.excludes as unknown[]).filter((v): v is string => typeof v === "string") + : []; - return { mode, targets, excludes, complexity }; - } catch { - // detect-v3 failure is non-fatal: caller falls back to extraction model fields - return null; - } + return { + mode, + targets: targets.length > 0 ? targets : undefined, + excludes: excludes.length > 0 ? excludes : undefined, + }; } -/** - * Merge detect-v3 and extraction model results into a single ModelPreference. - * detect-v3 wins on mode/targets/excludes; extraction model is the fallback. - */ -function buildModelPreference( - detect: IntentDetectResult | null, - extractionFallback?: { mode: PreferenceMode; targets: string[]; excludes: string[] }, -): ModelPreference | undefined { - if (detect) { - return { - mode: detect.mode, - targets: detect.targets.length > 0 ? detect.targets : undefined, - excludes: detect.excludes.length > 0 ? detect.excludes : undefined, - }; - } - if (extractionFallback) { - return { - mode: extractionFallback.mode, - targets: extractionFallback.targets.length > 0 ? extractionFallback.targets : undefined, - excludes: extractionFallback.excludes.length > 0 ? extractionFallback.excludes : undefined, - }; - } - return undefined; -} - -// ---- Main entry: parallel detect-v3 + qwen3.6-flash ------------------------ - /** * Analyze the user's input to produce an IntentProfile. * - * Two LLM calls run in parallel: - * 1. `tongyi-intent-detect-v3` (DashScope native) -- fast mode/targets/excludes/complexity - * 2. `qwen3.6-flash` -- rich field extraction (taskSummary, modalities, budget, etc.) + * Calls `qwen3.6-flash` via the OpenAI-compatible chat endpoint to extract + * structured intent fields: taskSummary, modalities, capabilities, budget, + * qualityPreference, modelPreference (mode/targets/excludes), and more. * - * detect-v3 takes priority for mode/targets/excludes/complexity; - * the extraction model fills everything else. + * On failure, degrades gracefully to DEFAULT_INTENT with confidence 0. */ -export async function analyzeIntent( - client: Client, - input: string, - opts?: { intentDetectBaseUrl?: string }, -): Promise { - const detectPromise = detectIntentMode(client, input, opts?.intentDetectBaseUrl); - +export async function analyzeIntent(client: Client, input: string): Promise { const url = chatPath(); const body = { model: INTENT_EXTRACTION_MODEL, @@ -199,71 +62,31 @@ export async function analyzeIntent( temperature: 0, }; - const extractionPromise = client.requestJson({ - path: url, - method: "POST", - body, - timeout: 30, - }); - - const [detectResult, extractionResponse] = await Promise.all([ - detectPromise, - extractionPromise.catch(() => null), - ]); - - // If extraction model failed, use detect-v3 result + defaults - if (!extractionResponse) { - return { - ...DEFAULT_INTENT, - modelPreference: buildModelPreference(detectResult), - complexity: - detectResult?.complexity === "pipeline" ? Complexities.Pipeline : Complexities.Single, - }; + let response: ChatResponse; + try { + response = await client.requestJson({ + path: url, + method: "POST", + body, + timeout: 30, + }); + } catch { + return { ...DEFAULT_INTENT }; } - const text = extractionResponse.choices?.[0]?.message?.content ?? ""; + const text = response.choices?.[0]?.message?.content ?? ""; const jsonMatch = text.match(/\{[\s\S]*\}/); if (!jsonMatch) { - return { - ...DEFAULT_INTENT, - confidence: detectResult ? 1 : 0, - modelPreference: buildModelPreference(detectResult), - complexity: - detectResult?.complexity === "pipeline" ? Complexities.Pipeline : Complexities.Single, - }; + return { ...DEFAULT_INTENT }; } const parsed = JSON.parse(jsonMatch[0]); - // Extraction model's mode/targets/excludes (fallback when detect-v3 is null) const rawPref = parsed.modelPreference as Record | undefined; - const extractionMode: PreferenceMode = - rawPref && typeof rawPref === "object" && typeof rawPref.mode === "string" - ? VALID_MODES.includes(rawPref.mode as PreferenceMode) - ? (rawPref.mode as PreferenceMode) - : "unconstrained" - : "unconstrained"; - const extractionTargets: string[] = - rawPref && typeof rawPref === "object" && Array.isArray(rawPref.targets) - ? (rawPref.targets as string[]) - : []; - const extractionExcludes: string[] = - rawPref && typeof rawPref === "object" && Array.isArray(rawPref.excludes) - ? (rawPref.excludes as string[]) - : []; + const modelPreference = extractModelPreference(rawPref); - // Merge: detect-v3 wins, extraction model fills gaps - const modelPreference = buildModelPreference(detectResult, { - mode: extractionMode, - targets: extractionTargets, - excludes: extractionExcludes, - }); - - // Extraction model complexity, but detect-v3 pipeline tag overrides - const extractionComplexity = - parsed.complexity === Complexities.Pipeline ? Complexities.Pipeline : Complexities.Single; const complexity = - detectResult?.complexity === "pipeline" ? Complexities.Pipeline : extractionComplexity; + parsed.complexity === Complexities.Pipeline ? Complexities.Pipeline : Complexities.Single; return { complexity, diff --git a/packages/core/src/advisor/recall-semantic.ts b/packages/core/src/advisor/recall-semantic.ts index 16ddcab3..fb931ed3 100644 --- a/packages/core/src/advisor/recall-semantic.ts +++ b/packages/core/src/advisor/recall-semantic.ts @@ -61,12 +61,28 @@ function normalizeStr(value: string): string { function matchesTarget(model: ModelProfile, target: string): boolean { const needle = normalizeStr(target); if (!needle) return false; - // exact normalized match on id/name wins (resolves "qwen max" → "qwen-max") + + // Tier 1: exact normalized match on model id or display name if (normalizeStr(model.model) === needle || normalizeStr(model.name) === needle) return true; - // otherwise normalized substring across identifier-ish fields - return [model.model, model.name, model.family, model.familyName, model.provider].some((field) => - field ? normalizeStr(field).includes(needle) : false, - ); + + // Tier 2: suffix match for provider-prefix model ids + // e.g. "siliconflow/deepseek-v3" → suffix "deepseek-v3" → normalize → "deepseekv3" + const modelId = model.model; + const slashIdx = modelId.lastIndexOf("/"); + if (slashIdx >= 0) { + const suffix = normalizeStr(modelId.slice(slashIdx + 1)); + if (suffix === needle) return true; + } + + // Tier 3: substring match only on family / familyName + // e.g. target "deepseek" matches family "DeepSeek" → normalize → "deepseek" + // but target "deepseek-v3" does NOT match family "DeepSeek" because + // "deepseekv3".includes("deepseek") is the wrong direction (needle ⊃ field). + return [model.family, model.familyName].some((field) => { + if (!field) return false; + const normalized = normalizeStr(field); + return normalized.length > 0 && needle.includes(normalized); + }); } function matchesAnyTarget(model: ModelProfile, targets: string[]): boolean { @@ -287,16 +303,18 @@ function recallScoped( function recallComparison( models: ModelProfile[], - embeddings: ModelEmbedding[], - queryVector: number[], + _embeddings: ModelEmbedding[], + _queryVector: number[], preference: ModelPreference, - topK: number, - modelMap: Map, - intent?: IntentProfile, + _topK: number, + _modelMap: Map, + _intent?: IntentProfile, ): ScoredCandidate[] { const targets = preference.targets ?? []; - // user-named models are forced in (bypass hard gate), priority 1.0 + // Comparison mode: only return the user-specified models (bypass hard gate). + // No fusion-ranked fillers — the user explicitly asked to compare these models, + // so extra candidates would only give the LLM ranker room to substitute them. const forced: ScoredCandidate[] = []; const forcedIds = new Set(); for (const profile of models) { @@ -306,19 +324,7 @@ function recallComparison( } } - const remaining = Math.max(0, topK - forced.length); - if (remaining > 0) { - const candidatePool = models.filter((profile) => !forcedIds.has(profile.model)); - const poolIds = filterWithFallback(candidatePool, intent); - const extra = rankByFusion(embeddings, queryVector, poolIds, remaining, modelMap, intent); - for (const cand of extra) { - forced.push(cand); - } - } - - // clamp in case many targets matched beyond topK (forced are first, so they - // are preserved up to topK and extras drop first) - return forced.slice(0, Math.max(0, topK)); + return forced; } function recallAlternative( diff --git a/packages/core/src/client/endpoints.ts b/packages/core/src/client/endpoints.ts index 44951785..55369aea 100644 --- a/packages/core/src/client/endpoints.ts +++ b/packages/core/src/client/endpoints.ts @@ -6,19 +6,6 @@ export function chatPath(): string { return "/compatible-mode/v1/chat/completions"; } -// ---- Intent Detect (DashScope Native) ---- - -/** - * DashScope-native text-generation endpoint for `tongyi-intent-detect-v3`. - * This model does not use the OpenAI-compatible chat endpoint — it requires - * the native `{ model, input, parameters }` request shape with - * `result_format: "message"` and returns a `{ output, usage, request_id }` - * envelope. - */ -export function intentDetectEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v1/services/aigc/text-generation/generation`; -} - // ---- Image Generation (DashScope) ---- export function imagePath(): string { return "/api/v1/services/aigc/image-generation/generation"; diff --git a/packages/core/src/config/loader.ts b/packages/core/src/config/loader.ts index 9674e2e2..e59dc798 100644 --- a/packages/core/src/config/loader.ts +++ b/packages/core/src/config/loader.ts @@ -61,8 +61,6 @@ export function buildSettings(s: ResolutionSources): Settings { return { configPath: getConfigPath(), - intentDetectBaseUrl: - file.intent_detect_base_url || env.DASHSCOPE_INTENT_DETECT_BASE_URL || undefined, output: detectOutputFormat(flags.output || env.DASHSCOPE_OUTPUT || file.output), outputExplicit: Boolean(flags.output || env.DASHSCOPE_OUTPUT || file.output), outputDir: file.output_dir || undefined, diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index b4bf0a49..b76b9316 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -23,12 +23,6 @@ export interface ConfigFile { /** Alibaba Cloud OpenAPI AccessKey secret from `bl auth login --open-api`. */ access_key_secret?: string; base_url?: string; - /** - * Dedicated base URL for the intent-detect model (tongyi-intent-detect-v3). - * Allows pointing the intent API at a different region/workspace than the - * main chat endpoint. Falls back to `base_url` when not set. - */ - intent_detect_base_url?: string; output?: "text" | "json"; output_dir?: string; timeout?: number; @@ -84,8 +78,6 @@ export function parseConfigFile(raw: unknown): ConfigFile { ) out.access_key_secret = obj.openapi_access_key_secret; if (typeof obj.base_url === "string" && isHttpUrl(obj.base_url)) out.base_url = obj.base_url; - if (typeof obj.intent_detect_base_url === "string" && isHttpUrl(obj.intent_detect_base_url)) - out.intent_detect_base_url = obj.intent_detect_base_url; if (typeof obj.output === "string" && VALID_OUTPUTS.has(obj.output)) out.output = obj.output as ConfigFile["output"]; if (typeof obj.output_dir === "string" && obj.output_dir.length > 0) @@ -132,8 +124,6 @@ export interface Identity { */ export interface Settings { configPath?: string; - /** Dedicated base URL for intent-detect model; falls back to the model baseUrl at call site. */ - intentDetectBaseUrl?: string; output: "text" | "json"; /** * Whether `output` came from an explicit source (flag/env/file) rather than diff --git a/packages/core/src/types/api.ts b/packages/core/src/types/api.ts index 698a603b..3fa00929 100644 --- a/packages/core/src/types/api.ts +++ b/packages/core/src/types/api.ts @@ -108,51 +108,6 @@ export interface StreamChunk { }; } -// ---- Intent Detect (DashScope Native) ---- - -/** - * Request body for `tongyi-intent-detect-v3` via the DashScope-native - * text-generation endpoint. Uses `{ model, input, parameters }` shape — - * NOT the OpenAI `{ model, messages }` shape. - */ -export interface DashScopeIntentDetectRequest { - model: string; - input: { - messages: Array<{ - role: "system" | "user" | "assistant"; - content: string; - }>; - }; - parameters?: { - result_format?: "message"; - max_tokens?: number; - temperature?: number; - }; -} - -/** - * Response envelope from the DashScope-native text-generation endpoint with - * `result_format: "message"`. The model's output lives under `output.choices`, - * mirroring the OpenAI shape but nested one level deeper. - */ -export interface DashScopeIntentDetectResponse { - output: { - choices?: Array<{ - finish_reason: string; - message: { - role: string; - content: string; - }; - }>; - }; - usage?: { - total_tokens?: number; - input_tokens?: number; - output_tokens?: number; - }; - request_id: string; -} - // ---- Image (DashScope) ---- export interface DashScopeImageRequest {