feat: remove intent-detect-v3 model

This commit is contained in:
顾捷晔
2026-07-12 13:44:00 +08:00
parent 6e095a6ce5
commit 58ab622e11
10 changed files with 116 additions and 421 deletions
@@ -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...");
@@ -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("excludes preference — intent detects modelPreference when excluding models", async () => {
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",
"Not qwen, recommend a model suitable for text generation",
"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("mode: excludes — excluding a family populates excludes array", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(ADVISOR_ROUTES, [
"advisor",
"recommend",
"--dry-run",
"--message",
"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",
@@ -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,
+3 -79
View File
@@ -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
* `<tags>` / `
</think>
` 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 `<tags>label</tags>` + `
</think>
`
* 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.`;
}
+35 -212
View File
@@ -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.
* Build a ModelPreference from the extraction model's raw output.
* Returns undefined when no valid preference data is present.
*/
interface IntentDetectResult {
mode: PreferenceMode;
targets: string[];
excludes: string[];
complexity: "single" | "pipeline";
}
function extractModelPreference(
raw: Record<string, unknown> | undefined,
): ModelPreference | undefined {
if (!raw || typeof raw !== "object") return undefined;
/**
* 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 = /<tags>\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<string, unknown> | null {
const re = /<tool_call>\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<string, unknown>;
}
if (
parsed &&
typeof parsed === "object" &&
"arguments" in (parsed as Record<string, unknown>)
) {
return (parsed as Record<string, unknown>).arguments as Record<string, unknown>;
}
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 `<tags>mode</tags>` for classification
* plus a `classify_intent` tool_call carrying targets/excludes/complexity.
* Returns null on any failure; caller falls back to extraction model fields.
*/
async function detectIntentMode(
client: Client,
input: string,
intentDetectBaseUrl?: string,
): Promise<IntentDetectResult | null> {
// 意图识别模型可指向独立 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<DashScopeIntentDetectResponse>({
path: url,
method: "POST",
body,
timeout: INTENT_DETECT_TIMEOUT,
});
const text = response.output?.choices?.[0]?.message?.content ?? "";
// 1. Extract mode from <tags>
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 <tool_call>
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;
}
}
/**
* 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,
mode,
targets: targets.length > 0 ? targets : undefined,
excludes: excludes.length > 0 ? 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<IntentProfile> {
const detectPromise = detectIntentMode(client, input, opts?.intentDetectBaseUrl);
export async function analyzeIntent(client: Client, input: string): Promise<IntentProfile> {
const url = chatPath();
const body = {
model: INTENT_EXTRACTION_MODEL,
@@ -199,71 +62,31 @@ export async function analyzeIntent(
temperature: 0,
};
const extractionPromise = client.requestJson<ChatResponse>({
let response: ChatResponse;
try {
response = await client.requestJson<ChatResponse>({
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,
};
} 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<string, unknown> | 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,
+30 -24
View File
@@ -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<string, ModelProfile>,
intent?: IntentProfile,
_topK: number,
_modelMap: Map<string, ModelProfile>,
_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<string>();
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(
-13
View File
@@ -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";
-2
View File
@@ -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,
-10
View File
@@ -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
-45
View File
@@ -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 {