feat(web): AI 意图规划置信度阈值与动作策略细化
- workbenchAiIntentPlannerModel 新增 WORKBENCH_AI_INTENT_CONFIDENCE_THRESHOLD 与 isLowConfidenceTravelApplicationPlan,shouldRequestWorkbenchAiIntentPlan 增加业务关键词前置过滤 - resolveExecutableTravelApplicationPlan 区分 requestedSubmit 与提交确认(submitRequiresConfirmation),autoSubmit 不再直接置真 - workbenchIntentActionPolicy 改用 policyDecision 路由(need_confirmation/query_candidates),透传 riskLevel/requiresSelection/requiresConfirmation - workbenchIntentFrameModel 补充 query 动作识别,usePersonalWorkbenchAiMode/useWorkbenchAiActionRouter/useWorkbenchAiApplicationPreviewFlow 接入低置信度与确认流程 - 更新 intent-planner-model/intent-frame-model/application-gate-model/fast-preview 测试
This commit is contained in:
@@ -252,7 +252,7 @@ test('application intent uses local preview instead of immediate orchestrator ca
|
||||
assert.match(buildLocalApplicationPreviewMessage(preview), /点击对应行即可直接编辑/)
|
||||
})
|
||||
|
||||
test('AI workbench routes compact travel direct-submit planner into application preview auto submit', () => {
|
||||
test('AI workbench routes compact travel direct-submit planner into preview with confirmation required', () => {
|
||||
assert.match(personalWorkbenchAiModeScript, /buildRuleFallbackWorkbenchAiIntentPlan/)
|
||||
assert.match(personalWorkbenchAiModeScript, /normalizeWorkbenchAiIntentPlan/)
|
||||
assert.match(personalWorkbenchAiModeScript, /resolveExecutableTravelApplicationPlan/)
|
||||
@@ -262,7 +262,7 @@ test('AI workbench routes compact travel direct-submit planner into application
|
||||
)
|
||||
assert.match(
|
||||
personalWorkbenchAiModeScript,
|
||||
/modelPlan = await stewardFlow\.resolveInlineExecutionPlan\(cleanPrompt, entry, files\)/
|
||||
/modelPlan = await stewardFlow\.resolveInlineExecutionPlan\(cleanPrompt, entry, files,\s*\{/
|
||||
)
|
||||
assert.match(
|
||||
personalWorkbenchAiModeScript,
|
||||
@@ -272,8 +272,11 @@ test('AI workbench routes compact travel direct-submit planner into application
|
||||
personalWorkbenchAiModeScript,
|
||||
/applicationFlow\.startAiApplicationPreview\([\s\S]*travelApplicationRequest\.expenseType[\s\S]*travelApplicationRequest\.expenseTypeLabel[\s\S]*travelApplicationRequest\.sourceText[\s\S]*ontologyFields:\s*travelApplicationRequest\.ontologyFields[\s\S]*autoSubmit:\s*travelApplicationRequest\.autoSubmit/
|
||||
)
|
||||
assert.match(personalWorkbenchAiModeScript, /requestedSubmit:\s*travelApplicationRequest\.requestedSubmit/)
|
||||
assert.match(personalWorkbenchAiModeScript, /submitRequiresConfirmation:\s*travelApplicationRequest\.submitRequiresConfirmation/)
|
||||
assert.doesNotMatch(personalWorkbenchAiModeScript, /fallbackIntentPlan/)
|
||||
assert.match(applicationPreviewFlowScript, /if \(options\.autoSubmit && normalizeApplicationPreview\(preview\)\.readyToSubmit\)/)
|
||||
assert.doesNotMatch(applicationPreviewFlowScript, /if \(options\.autoSubmit && normalizeApplicationPreview\(preview\)\.readyToSubmit\)/)
|
||||
assert.match(applicationPreviewFlowScript, /submitRequiresConfirmation:\s*Boolean\(options\.submitRequiresConfirmation\)/)
|
||||
assert.match(applicationPreviewFlowScript, /confirmed:\s*true/)
|
||||
assert.match(applicationPreviewFlowScript, /skipUserMessage:\s*true/)
|
||||
})
|
||||
|
||||
@@ -41,9 +41,11 @@ test('workbench application gate detects compact travel application direct submi
|
||||
expenseType: 'travel',
|
||||
expenseTypeLabel: '差旅费',
|
||||
sourceText: '去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交',
|
||||
autoSubmit: true
|
||||
autoSubmit: false,
|
||||
requestedSubmit: true
|
||||
})
|
||||
assert.equal(resolveInlineTravelApplicationRequest('去上海出差,辅助国网仿生产服务器部署,交通火车')?.autoSubmit, false)
|
||||
assert.equal(resolveInlineTravelApplicationRequest('去上海出差,辅助国网仿生产服务器部署,交通火车')?.requestedSubmit, false)
|
||||
assert.equal(resolveInlineTravelApplicationRequest('帮我查询上海差旅标准'), null)
|
||||
})
|
||||
|
||||
|
||||
@@ -12,11 +12,13 @@ import {
|
||||
WORKBENCH_AI_STEP_SUBMIT_APPLICATION,
|
||||
WORKBENCH_AI_STEP_VALIDATE_REQUIRED_FIELDS,
|
||||
buildRuleFallbackWorkbenchAiIntentPlan,
|
||||
isLowConfidenceTravelApplicationPlan,
|
||||
normalizeWorkbenchAiIntentPlan,
|
||||
resolveExecutableTravelApplicationPlan,
|
||||
shouldRequestWorkbenchAiIntentPlan
|
||||
} from '../src/composables/workbenchAiMode/workbenchAiIntentPlannerModel.js'
|
||||
import { buildInlineApplicationPreview } from '../src/composables/workbenchAiMode/workbenchAiApplicationPreviewModel.js'
|
||||
import { createWorkbenchAiMessageRuntime } from '../src/composables/workbenchAiMode/workbenchAiMessageModel.js'
|
||||
|
||||
const personalWorkbenchAiModeScript = readFileSync(
|
||||
fileURLToPath(new URL('../src/composables/workbenchAiMode/usePersonalWorkbenchAiMode.js', import.meta.url)),
|
||||
@@ -144,7 +146,9 @@ test('workbench AI intent planner detects compact travel save-draft variant befo
|
||||
sourceText: prompt,
|
||||
ontologyFields: {},
|
||||
autoSubmit: false,
|
||||
autoSaveDraft: true
|
||||
autoSaveDraft: true,
|
||||
requestedSubmit: false,
|
||||
submitRequiresConfirmation: false
|
||||
})
|
||||
})
|
||||
|
||||
@@ -178,7 +182,9 @@ test('workbench AI intent planner turns model fields and action into executable
|
||||
transport_mode: '火车'
|
||||
},
|
||||
autoSubmit: false,
|
||||
autoSaveDraft: true
|
||||
autoSaveDraft: true,
|
||||
requestedSubmit: false,
|
||||
submitRequiresConfirmation: false
|
||||
})
|
||||
})
|
||||
|
||||
@@ -220,7 +226,9 @@ test('workbench AI intent planner turns single application candidate flow into e
|
||||
transport_mode: '火车'
|
||||
},
|
||||
autoSubmit: false,
|
||||
autoSaveDraft: false
|
||||
autoSaveDraft: false,
|
||||
requestedSubmit: false,
|
||||
submitRequiresConfirmation: false
|
||||
})
|
||||
})
|
||||
|
||||
@@ -245,7 +253,7 @@ test('workbench AI application preview prefers model ontology fields over local
|
||||
assert.equal(preview.fields.transportMode, '火车')
|
||||
})
|
||||
|
||||
test('workbench AI intent planner rejects policy question and resolves executable application request', () => {
|
||||
test('workbench AI intent planner rejects policy question and requires confirmation for direct-submit request', () => {
|
||||
assert.equal(buildRuleFallbackWorkbenchAiIntentPlan('帮我查询上海差旅标准'), null)
|
||||
|
||||
const request = resolveExecutableTravelApplicationPlan(
|
||||
@@ -257,11 +265,30 @@ test('workbench AI intent planner rejects policy question and resolves executabl
|
||||
expenseTypeLabel: '差旅费',
|
||||
sourceText: '去上海出差,辅助国网仿生产服务器部署,交通火车,直接提交',
|
||||
ontologyFields: {},
|
||||
autoSubmit: true,
|
||||
autoSaveDraft: false
|
||||
autoSubmit: false,
|
||||
autoSaveDraft: false,
|
||||
requestedSubmit: true,
|
||||
submitRequiresConfirmation: true
|
||||
})
|
||||
})
|
||||
|
||||
test('workbench AI message runtime persists direct-submit confirmation metadata', () => {
|
||||
const { createInlineMessage, normalizeRuntimeMessage, serializeRuntimeMessage } = createWorkbenchAiMessageRuntime()
|
||||
const message = createInlineMessage('assistant', '申请核对表', {
|
||||
applicationPreview: { fields: { location: '上海' } },
|
||||
requestedSubmit: true,
|
||||
submitRequiresConfirmation: true
|
||||
})
|
||||
|
||||
const serialized = serializeRuntimeMessage(message)
|
||||
assert.equal(serialized.requestedSubmit, true)
|
||||
assert.equal(serialized.submitRequiresConfirmation, true)
|
||||
|
||||
const normalized = normalizeRuntimeMessage(serialized)
|
||||
assert.equal(normalized.requestedSubmit, true)
|
||||
assert.equal(normalized.submitRequiresConfirmation, true)
|
||||
})
|
||||
|
||||
test('workbench AI mode asks steward model plan before fallback execution', () => {
|
||||
assert.match(stewardFlowScript, /async function resolveInlineExecutionPlan\(prompt, entry = \{\}, files = \[\], options = \{\}\)/)
|
||||
assert.match(stewardFlowScript, /fetchStewardPlan\(planRequest/)
|
||||
@@ -274,8 +301,11 @@ test('workbench AI mode asks steward model plan before fallback execution', () =
|
||||
assert.match(personalWorkbenchAiModeScript, /resolveExecutableTravelApplicationPlan\(intentPlan\)/)
|
||||
assert.doesNotMatch(personalWorkbenchAiModeScript, /fallbackIntentPlan/)
|
||||
assert.match(personalWorkbenchAiModeScript, /autoSaveDraft:\s*travelApplicationRequest\.autoSaveDraft/)
|
||||
assert.match(personalWorkbenchAiModeScript, /requestedSubmit:\s*travelApplicationRequest\.requestedSubmit/)
|
||||
assert.match(personalWorkbenchAiModeScript, /submitRequiresConfirmation:\s*travelApplicationRequest\.submitRequiresConfirmation/)
|
||||
assert.match(personalWorkbenchAiModeScript, /ontologyFields:\s*travelApplicationRequest\.ontologyFields/)
|
||||
assert.match(applicationPreviewFlowScript, /options\.autoSaveDraft/)
|
||||
assert.doesNotMatch(applicationPreviewFlowScript, /options\.autoSubmit && normalizeApplicationPreview\(preview\)\.readyToSubmit/)
|
||||
assert.match(applicationPreviewFlowScript, /ontologyFields:\s*options\.ontologyFields/)
|
||||
assert.match(applicationPreviewFlowScript, /executeInlineApplicationPreviewAction\(AI_APPLICATION_ACTION_SAVE_DRAFT/)
|
||||
assert.match(applicationPreviewFlowScript, /buildInlineApplicationPreview\([\s\S]*ontologyFields:\s*options\.ontologyFields/)
|
||||
@@ -329,3 +359,72 @@ test('workbench AI mode reuses planning pending message for regular steward repl
|
||||
assert.match(stewardFlowScript, /const reusablePendingMessageId = String\(options\.pendingMessageId \|\| ''\)\.trim\(\)/)
|
||||
assert.match(stewardFlowScript, /reusablePendingMessageId \? replaceInlineMessage\(reusablePendingMessageId, pendingMessage\) : conversationMessages\.value\.push\(pendingMessage\)/)
|
||||
})
|
||||
|
||||
test('isLowConfidenceTravelApplicationPlan gates preview behind confirmation when model confidence is low', () => {
|
||||
const lowConfidenceModelPlan = normalizeWorkbenchAiIntentPlan({
|
||||
planning_source: 'llm_function_call',
|
||||
tasks: [{
|
||||
task_type: 'expense_application',
|
||||
assigned_agent: 'application_assistant',
|
||||
requested_action: 'preview',
|
||||
confidence: 0.4,
|
||||
ontology_fields: { location: '上海', reason: '部署' }
|
||||
}]
|
||||
}, { prompt: '上海那边好像要过去一趟搞部署' })
|
||||
|
||||
assert.equal(lowConfidenceModelPlan.source, WORKBENCH_AI_INTENT_SOURCE_MODEL)
|
||||
assert.equal(isLowConfidenceTravelApplicationPlan(lowConfidenceModelPlan), true)
|
||||
|
||||
const highConfidenceModelPlan = normalizeWorkbenchAiIntentPlan({
|
||||
planning_source: 'llm_function_call',
|
||||
tasks: [{
|
||||
task_type: 'expense_application',
|
||||
assigned_agent: 'application_assistant',
|
||||
requested_action: 'preview',
|
||||
confidence: 0.9,
|
||||
ontology_fields: { location: '上海' }
|
||||
}]
|
||||
}, { prompt: '去上海出差' })
|
||||
|
||||
assert.equal(isLowConfidenceTravelApplicationPlan(highConfidenceModelPlan), false)
|
||||
|
||||
const ruleFallbackPlan = buildRuleFallbackWorkbenchAiIntentPlan('去上海出差,辅助部署,交通火车')
|
||||
assert.equal(ruleFallbackPlan.source, WORKBENCH_AI_INTENT_SOURCE_RULE_FALLBACK)
|
||||
assert.equal(isLowConfidenceTravelApplicationPlan(ruleFallbackPlan), false)
|
||||
|
||||
const explicitSubmitLowConfidence = normalizeWorkbenchAiIntentPlan({
|
||||
planning_source: 'llm_function_call',
|
||||
tasks: [{
|
||||
task_type: 'expense_application',
|
||||
assigned_agent: 'application_assistant',
|
||||
requested_action: 'submit',
|
||||
confidence: 0.3,
|
||||
ontology_fields: { location: '上海' }
|
||||
}]
|
||||
}, { prompt: '直接提交' })
|
||||
|
||||
assert.equal(explicitSubmitLowConfidence.requestedAction, 'submit')
|
||||
assert.equal(isLowConfidenceTravelApplicationPlan(explicitSubmitLowConfidence), false)
|
||||
|
||||
assert.equal(isLowConfidenceTravelApplicationPlan(null), false)
|
||||
})
|
||||
|
||||
test('workbench AI mode routes low confidence travel application plan to confirmation prompt', () => {
|
||||
assert.match(personalWorkbenchAiModeScript, /isLowConfidenceTravelApplicationPlan\(intentPlan\)/)
|
||||
assert.match(personalWorkbenchAiModeScript, /function startModelPlannedTravelApplicationConfirmation\(/)
|
||||
assert.match(personalWorkbenchAiModeScript, /action_type:\s*'ai_application_confirm_intent'/)
|
||||
assert.match(personalWorkbenchAiModeScript, /需要确认:您是要发起出差申请吗/)
|
||||
})
|
||||
|
||||
test('shouldRequestWorkbenchAiIntentPlan skips chitchat and only triggers on business keywords', () => {
|
||||
assert.equal(shouldRequestWorkbenchAiIntentPlan('你好'), false)
|
||||
assert.equal(shouldRequestWorkbenchAiIntentPlan('谢谢'), false)
|
||||
assert.equal(shouldRequestWorkbenchAiIntentPlan('嗯'), false)
|
||||
assert.equal(shouldRequestWorkbenchAiIntentPlan('ok'), false)
|
||||
assert.equal(shouldRequestWorkbenchAiIntentPlan('1'), false)
|
||||
|
||||
assert.equal(shouldRequestWorkbenchAiIntentPlan('帮我查报销'), true)
|
||||
assert.equal(shouldRequestWorkbenchAiIntentPlan('我要出差'), true)
|
||||
assert.equal(shouldRequestWorkbenchAiIntentPlan('帮我查询上海差旅标准'), true)
|
||||
assert.equal(shouldRequestWorkbenchAiIntentPlan('删除3天前的草稿'), true)
|
||||
})
|
||||
|
||||
@@ -22,6 +22,12 @@ test('workbench intent frame resolves contextual draft deletion as confirm-only
|
||||
assert.equal(frame?.objectType, 'draft')
|
||||
assert.equal(frame?.targetMode, 'current_context')
|
||||
assert.equal(frame?.safetyLevel, 'confirm_required')
|
||||
assert.equal(frame?.riskLevel, 'high')
|
||||
assert.equal(frame?.requiresCandidateSearch, false)
|
||||
assert.equal(frame?.requiresSelection, false)
|
||||
assert.equal(frame?.requiresConfirmation, true)
|
||||
assert.equal(frame?.executionMode, 'need_confirmation')
|
||||
assert.equal(frame?.policyDecision, 'need_confirmation')
|
||||
assert.equal(frame?.filters.status?.label, '草稿')
|
||||
assert.equal(frame?.normalizedQuery, '我的草稿单据')
|
||||
})
|
||||
@@ -34,11 +40,19 @@ test('workbench intent frame sends filtered draft deletion to candidate search',
|
||||
assert.equal(frame?.objectType, 'draft')
|
||||
assert.equal(frame?.targetMode, 'filtered_candidates')
|
||||
assert.equal(frame?.safetyLevel, 'confirm_required')
|
||||
assert.equal(frame?.riskLevel, 'high')
|
||||
assert.equal(frame?.requiresCandidateSearch, true)
|
||||
assert.equal(frame?.requiresSelection, true)
|
||||
assert.equal(frame?.requiresConfirmation, true)
|
||||
assert.equal(frame?.executionMode, 'query_candidates')
|
||||
assert.equal(frame?.policyDecision, 'query_candidates')
|
||||
assert.equal(frame?.filters.timeRange?.start, '2026-06-21')
|
||||
assert.equal(frame?.filters.timeRange?.end, '2026-06-21')
|
||||
assert.equal(frame?.normalizedQuery, '我的 3天前 草稿单据')
|
||||
assert.equal(route.nextStep, 'query_candidates')
|
||||
assert.equal(route.queryPrompt, '我的 3天前 草稿单据')
|
||||
assert.equal(route.requiresSelection, true)
|
||||
assert.equal(route.requiresConfirmation, true)
|
||||
})
|
||||
|
||||
test('workbench intent frame preserves application draft deletion filters', () => {
|
||||
@@ -52,6 +66,11 @@ test('workbench intent frame preserves application draft deletion filters', () =
|
||||
assert.equal(frame?.filters.status?.label, '草稿')
|
||||
assert.equal(frame?.targetMode, 'filtered_candidates')
|
||||
assert.equal(frame?.safetyLevel, 'confirm_required')
|
||||
assert.equal(frame?.riskLevel, 'high')
|
||||
assert.equal(frame?.requiresCandidateSearch, true)
|
||||
assert.equal(frame?.requiresSelection, true)
|
||||
assert.equal(frame?.requiresConfirmation, true)
|
||||
assert.equal(frame?.executionMode, 'query_candidates')
|
||||
assert.equal(route.queryPrompt, '我的 草稿 申请单')
|
||||
assert.equal(queryIntent?.source, 'mine')
|
||||
assert.equal(queryIntent?.documentType, 'application')
|
||||
@@ -99,11 +118,19 @@ test('workbench intent frame resolves compliant no-risk approval request as filt
|
||||
assert.equal(frame?.objectType, 'application')
|
||||
assert.equal(frame?.targetMode, 'filtered_candidates')
|
||||
assert.equal(frame?.safetyLevel, 'confirm_required')
|
||||
assert.equal(frame?.riskLevel, 'high')
|
||||
assert.equal(frame?.requiresCandidateSearch, true)
|
||||
assert.equal(frame?.requiresSelection, true)
|
||||
assert.equal(frame?.requiresConfirmation, true)
|
||||
assert.equal(frame?.executionMode, 'query_candidates')
|
||||
assert.equal(frame?.policyDecision, 'query_candidates')
|
||||
assert.equal(frame?.filters.risk?.level, 'none')
|
||||
assert.equal(frame?.filters.documentType, 'application')
|
||||
assert.equal(frame?.normalizedQuery, '待我审核 无风险 申请单')
|
||||
assert.equal(route.nextStep, 'query_candidates')
|
||||
assert.equal(route.queryPrompt, '待我审核 无风险 申请单')
|
||||
assert.equal(route.requiresSelection, true)
|
||||
assert.equal(route.requiresConfirmation, true)
|
||||
})
|
||||
|
||||
test('workbench intent frame keeps approval policy questions out of document actions', () => {
|
||||
@@ -112,5 +139,30 @@ test('workbench intent frame keeps approval policy questions out of document act
|
||||
|
||||
assert.equal(frame?.action, 'ask_policy')
|
||||
assert.equal(frame?.safetyLevel, 'read_only')
|
||||
assert.equal(frame?.riskLevel, 'read_only')
|
||||
assert.equal(frame?.requiresCandidateSearch, false)
|
||||
assert.equal(frame?.requiresSelection, false)
|
||||
assert.equal(frame?.requiresConfirmation, false)
|
||||
assert.equal(frame?.executionMode, 'answer_only')
|
||||
assert.equal(frame?.policyDecision, 'answer_only')
|
||||
assert.equal(route.nextStep, 'pass_through')
|
||||
})
|
||||
|
||||
test('workbench intent frame keeps rules as policy guardrails instead of executable side effects', () => {
|
||||
const highRiskFrame = resolveWorkbenchIntentFrame('审核合规没有风险的申请', { today })
|
||||
const highRiskRoute = resolveWorkbenchIntentActionRoute(highRiskFrame)
|
||||
const queryFrame = resolveWorkbenchIntentFrame('查3天前的申请单', { today })
|
||||
const queryRoute = resolveWorkbenchIntentActionRoute(queryFrame)
|
||||
|
||||
assert.equal(highRiskFrame?.policyDecision, 'query_candidates')
|
||||
assert.equal(highRiskFrame?.requiresSelection, true)
|
||||
assert.equal(highRiskFrame?.requiresConfirmation, true)
|
||||
assert.notEqual(highRiskRoute.nextStep, 'execute_allowed')
|
||||
|
||||
assert.equal(queryFrame?.riskLevel, 'read_only')
|
||||
assert.equal(queryFrame?.requiresCandidateSearch, true)
|
||||
assert.equal(queryFrame?.requiresSelection, false)
|
||||
assert.equal(queryFrame?.requiresConfirmation, false)
|
||||
assert.equal(queryFrame?.policyDecision, 'query_candidates')
|
||||
assert.equal(queryRoute.nextStep, 'query_candidates')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user