fix(chat-intent): 隐式攻略/页面生成请求直接路由到 static-page-publish
短句如"苏州攻略页面"此前会被规则/LLM 误判为 product-campaign-page(先追问 促销信息),导致用户体验成"卡住不动"。规则层扩大页面生成意图识别,并新增 coercePageGenerationSkill 在 LLM 判为 product-campaign-page 但明显是内容页 时强制纠正为 static-page-publish,避免不必要的追问。同步更新两个技能文档 的适用边界说明。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+42
-11
@@ -3,6 +3,8 @@ import {
|
||||
buildWebNewsSkillPrompt,
|
||||
extractSelectedChatSkillName,
|
||||
hasExplicitChatSkillPrompt,
|
||||
isPageGenerationIntent,
|
||||
isProductCampaignIntent,
|
||||
} from './chat-skills.mjs';
|
||||
import { isDirectChatSessionId } from './direct-chat-service.mjs';
|
||||
import {
|
||||
@@ -111,6 +113,26 @@ function buildRealtimeInfoClassification() {
|
||||
}, { source: 'rule' });
|
||||
}
|
||||
|
||||
export function coercePageGenerationSkill(classification, text, { grantedSkills = [] } = {}) {
|
||||
if (!classification || classification.route !== CHAT_INTENT_ROUTE.AGENT) return classification;
|
||||
if (!isPageGenerationIntent(text) || isProductCampaignIntent(text)) return classification;
|
||||
if (grantedSkills.length > 0 && !grantedSkills.includes('static-page-publish')) return classification;
|
||||
|
||||
const shouldUsePublishSkill =
|
||||
!classification.suggestedSkill || classification.suggestedSkill === 'product-campaign-page';
|
||||
if (!shouldUsePublishSkill) return classification;
|
||||
|
||||
const reasonSuffix = classification.suggestedSkill === 'product-campaign-page'
|
||||
? '(内容页意图,改用 static-page-publish)'
|
||||
: '(内容页生成意图)';
|
||||
return {
|
||||
...classification,
|
||||
suggestedSkill: 'static-page-publish',
|
||||
agentBrief: '生成或更新 MindSpace 公开内容页面,并返回可访问链接;信息不足时使用合理默认,不要停在追问。',
|
||||
reason: `${classification.reason}${reasonSuffix}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function coerceRealtimeWebSkill(classification, text, { grantedSkills = [] } = {}) {
|
||||
if (!classification || classification.route !== CHAT_INTENT_ROUTE.AGENT) return classification;
|
||||
if (!isRealtimeInfoQuestion(text)) return classification;
|
||||
@@ -276,6 +298,10 @@ function buildRouterSystemPrompt(grantedSkills = []) {
|
||||
? '若走 agent_orchestration,可在 suggested_skill 中填写最匹配的 skill 名称(须来自上述列表),否则填 null。'
|
||||
: 'suggested_skill 通常填 null。',
|
||||
'',
|
||||
'skill 选择补充:',
|
||||
'- static-page-publish:攻略/游记/主题内容页、城市介绍、活动介绍、任何「做页面/生成链接」且无商品购买跳转需求。',
|
||||
'- product-campaign-page:仅当用户明确提供商品链接或要求购买按钮/电商转化时使用,不要用于旅游攻略或纯内容页。',
|
||||
'',
|
||||
'只输出 JSON,不要 markdown,不要解释:',
|
||||
'{"route":"direct_chat|agent_orchestration","confidence":0.0,"reason":"一句话","suggested_skill":null,"agent_brief":"给 Agent 的执行要点,direct_chat 时可为空"}',
|
||||
].join('\n');
|
||||
@@ -713,14 +739,15 @@ export function classifyWithRules({
|
||||
if (
|
||||
includeIntentPatterns &&
|
||||
normalized &&
|
||||
OBVIOUS_AGENT_PATTERNS.some((pattern) => pattern.test(normalized))
|
||||
(OBVIOUS_AGENT_PATTERNS.some((pattern) => pattern.test(normalized))
|
||||
|| isPageGenerationIntent(normalized))
|
||||
) {
|
||||
return finalizeRouterClassification(normalizeClassification({
|
||||
route: CHAT_INTENT_ROUTE.AGENT,
|
||||
confidence: 0.95,
|
||||
reason: '明确需要生成或发布页面/文件',
|
||||
suggested_skill: 'static-page-publish',
|
||||
agent_brief: '生成或更新 MindSpace 公开页面,并返回可访问链接。',
|
||||
agent_brief: '生成或更新 MindSpace 公开页面,并返回可访问链接;信息不足时使用合理默认,不要停在追问。',
|
||||
}, { source: 'rule' }), decisionContext);
|
||||
}
|
||||
if (includeIntentPatterns && normalized && isRealtimeInfoQuestion(normalized)) {
|
||||
@@ -848,12 +875,16 @@ export function createChatIntentRouter(options = {}) {
|
||||
sessionId,
|
||||
sessionMessageCount,
|
||||
};
|
||||
const finalizeWithRealtimeCoercion = (classification) => {
|
||||
const finalizeWithCoercion = (classification) => {
|
||||
if (!classification) return classification;
|
||||
const base = { ...classification };
|
||||
delete base.decision;
|
||||
return finalizeRouterClassification(
|
||||
coerceRealtimeWebSkill(base, text, { grantedSkills }),
|
||||
coercePageGenerationSkill(
|
||||
coerceRealtimeWebSkill(base, text, { grantedSkills }),
|
||||
text,
|
||||
{ grantedSkills },
|
||||
),
|
||||
decisionContext,
|
||||
);
|
||||
};
|
||||
@@ -867,9 +898,9 @@ export function createChatIntentRouter(options = {}) {
|
||||
includeIntentPatterns: true,
|
||||
llmRouterEnabled: Boolean(policy.enabled),
|
||||
});
|
||||
if (ruleResult) return finalizeWithRealtimeCoercion(ruleResult);
|
||||
if (ruleResult) return finalizeWithCoercion(ruleResult);
|
||||
if (!isEnabled()) {
|
||||
return finalizeWithRealtimeCoercion(normalizeClassification({
|
||||
return finalizeWithCoercion(normalizeClassification({
|
||||
route: policy.fallbackRoute,
|
||||
confidence: 0.5,
|
||||
reason: '意图路由未启用,走默认通道',
|
||||
@@ -905,7 +936,7 @@ export function createChatIntentRouter(options = {}) {
|
||||
'Chat intent router',
|
||||
);
|
||||
} catch (err) {
|
||||
return finalizeWithRealtimeCoercion(normalizeClassification({
|
||||
return finalizeWithCoercion(normalizeClassification({
|
||||
route: policy.fallbackRoute,
|
||||
confidence: 0,
|
||||
reason: err instanceof Error ? err.message : '意图路由失败,走默认通道',
|
||||
@@ -913,7 +944,7 @@ export function createChatIntentRouter(options = {}) {
|
||||
}, { source: 'fallback', fallbackRoute: policy.fallbackRoute }));
|
||||
}
|
||||
if (!completion?.ok) {
|
||||
return finalizeWithRealtimeCoercion(normalizeClassification({
|
||||
return finalizeWithCoercion(normalizeClassification({
|
||||
route: policy.fallbackRoute,
|
||||
confidence: 0,
|
||||
reason: completion?.message ?? '意图路由失败,走默认通道',
|
||||
@@ -923,7 +954,7 @@ export function createChatIntentRouter(options = {}) {
|
||||
|
||||
const parsed = parseRouterJson(completion.reply);
|
||||
if (!parsed) {
|
||||
return finalizeWithRealtimeCoercion(normalizeClassification({
|
||||
return finalizeWithCoercion(normalizeClassification({
|
||||
route: policy.fallbackRoute,
|
||||
confidence: 0,
|
||||
reason: '意图路由响应无法解析,走默认通道',
|
||||
@@ -931,7 +962,7 @@ export function createChatIntentRouter(options = {}) {
|
||||
}, { source: 'fallback', fallbackRoute: policy.fallbackRoute }));
|
||||
}
|
||||
|
||||
const classification = finalizeWithRealtimeCoercion({
|
||||
const classification = finalizeWithCoercion({
|
||||
...normalizeClassification(parsed, { source: 'llm', fallbackRoute: policy.fallbackRoute }),
|
||||
providerKeyId: completion.providerKeyId ?? policy.modelProviderKeyId ?? null,
|
||||
model: completion.model ?? policy.model ?? null,
|
||||
@@ -941,7 +972,7 @@ export function createChatIntentRouter(options = {}) {
|
||||
classification.route === CHAT_INTENT_ROUTE.DIRECT_CHAT &&
|
||||
classification.confidence < policy.minConfidence
|
||||
) {
|
||||
return finalizeWithRealtimeCoercion(normalizeClassification({
|
||||
return finalizeWithCoercion(normalizeClassification({
|
||||
...classification,
|
||||
route: policy.fallbackRoute,
|
||||
reason: `${classification.reason}(置信度 ${classification.confidence} 低于阈值,走默认通道)`,
|
||||
|
||||
Reference in New Issue
Block a user