| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- 'use strict';
- const assert = require('assert/strict');
- const { planResponse, plannerInstruction } = require('../mcp/src/core/response-quality-planner');
- const { planSalesMove } = require('../mcp/src/core/agent-sales-move-planner');
- const {
- buildResponseGenerationStrategy,
- responseGenerationInstruction,
- } = require('../mcp/src/core/response-generation-strategy');
- const { QiweiAgentRuntime } = require('../mcp/src/core/agent-runtime');
- function buildPlan({ inboundContent, knownFields = [], missingFields = [], profile = {}, citations = [], toolTrace = [] }) {
- const responsePlan = planResponse({ inboundContent, profile, citations, toolTrace });
- const salesMovePlan = planSalesMove({
- scenario: responsePlan.scenario,
- riskLevel: responsePlan.requireHumanHandoff ? 'high' : 'none',
- riskTypes: responsePlan.riskTypes,
- requiresHuman: responsePlan.requireHumanHandoff,
- knownFields,
- missingFields,
- });
- const generationPlan = buildResponseGenerationStrategy({
- inboundContent,
- responsePlan,
- salesMovePlan,
- profile,
- citations,
- toolTrace,
- });
- return {
- responsePlan,
- salesMovePlan,
- generationPlan,
- instruction: responseGenerationInstruction(generationPlan),
- };
- }
- async function main() {
- const comparison = buildPlan({
- inboundContent: '两套差20万,我先看哪套?',
- knownFields: ['daily_commute', 'option_a_price', 'option_b_price', 'option_tradeoffs'],
- missingFields: ['living_room_is_hard_requirement', 'floor_preference', 'viewing_time'],
- profile: { daily_commute: true },
- });
- assert.equal(comparison.generationPlan.customerQuestion.text, '两套差20万,我先看哪套?');
- assert.equal(comparison.generationPlan.directAnswer.position, 'opening');
- assert.equal(comparison.generationPlan.directAnswer.answerBeforeQuestion, true);
- assert.equal(comparison.generationPlan.judgment.mode, 'bounded_tradeoff');
- assert.equal(comparison.generationPlan.singleNextStep.maxCount, 1);
- assert.equal(comparison.generationPlan.singleNextStep.maxQuestions, 1);
- assert.equal(comparison.generationPlan.singleNextStep.targetField, 'living_room_is_hard_requirement');
- assert.match(comparison.instruction, /先给有依据的结论或倾向/);
- assert.match(comparison.instruction, /倾向、取舍或理由/);
- assert(comparison.instruction.indexOf('1. 先答') < comparison.instruction.indexOf('2. 再判断'));
- assert(comparison.instruction.indexOf('2. 再判断') < comparison.instruction.indexOf('3. 唯一步'));
- const knownConstraints = buildPlan({
- inboundContent: '中上楼层可以,就按前面的条件看。',
- knownFields: ['budget', 'region', 'layout', 'floor'],
- missingFields: ['budget', 'region', 'layout', 'commute_priority'],
- });
- assert.equal(knownConstraints.salesMovePlan.singleNextStep.targetField, 'commute_priority');
- assert(!['budget', 'region', 'layout'].includes(knownConstraints.salesMovePlan.singleNextStep.targetField));
- assert.equal(knownConstraints.generationPlan.singleNextStep.maxQuestions, 1);
- const missingEvidence = buildPlan({
- inboundContent: '这套现在挂牌价是多少,值不值?',
- knownFields: ['purpose'],
- missingFields: ['property_id', 'price_evidence'],
- });
- assert.equal(missingEvidence.generationPlan.directAnswer.evidenceBoundary, 'direction_with_explicit_unknowns');
- assert.equal(missingEvidence.generationPlan.judgment.evidenceLimited, true);
- assert.match(missingEvidence.instruction, /先给当前能成立的方向判断/);
- assert.match(missingEvidence.instruction, /不编造具体产品或库存事实/);
- const riskBoundary = buildPlan({
- inboundContent: '这个房子的产权和税费能确认吗?',
- knownFields: ['property_id'],
- missingFields: ['ownership_evidence', 'tax_evidence'],
- });
- assert.equal(riskBoundary.salesMovePlan.primaryMove, 'handoff');
- assert.equal(riskBoundary.generationPlan.singleNextStep.kind, 'human_boundary');
- assert.equal(riskBoundary.generationPlan.singleNextStep.evidenceRequired, true);
- assert.equal(riskBoundary.generationPlan.constraints.preserveFactAndActionGates, true);
- assert.match(riskBoundary.instruction, /没有匹配凭证时/);
- assert.match(plannerInstruction(riskBoundary.responsePlan), /只有存在真实交接凭证时/);
- const noReply = buildPlan({ inboundContent: '好的' });
- assert.equal(noReply.generationPlan.directAnswer.required, false);
- assert.equal(noReply.generationPlan.singleNextStep.maxCount, 0);
- let capturedSystem = '';
- const runtime = new QiweiAgentRuntime({
- config: {
- provider: 'fixture',
- model: 'fixture',
- maxToolRounds: 1,
- qualityPassScore: 82,
- requiredProfileFields: ['living_room_is_hard_requirement', 'floor_preference', 'viewing_time'],
- salesGuidelines: [],
- businessGoal: '先解决客户当前选择,再推进一个决定性输入',
- stagePlaybook: '比较阶段先给倾向与理由',
- },
- knowledge: { contextText: () => '', search: () => [] },
- modelClient: {
- async complete(messages) {
- capturedSystem = String(messages.find(message => message.role === 'system')?.content || '');
- return {
- content: JSON.stringify({
- reply: '按您每天通勤,先看离地铁近的那套。多出的20万如果能换来稳定通勤,优先级通常高于暂时更大的客厅;客厅空间是不是硬要求?',
- confidence: 0.92,
- intent: 'compare_options',
- reason: '先给通勤取舍,再确认一个决定性条件。',
- requiresHuman: false,
- profileUpdates: {},
- tasks: [],
- alerts: [],
- }),
- };
- },
- },
- });
- const runtimeResult = await runtime.run({
- conversation: { id: 'sc-gap-01-fixture', mode: 'review' },
- messages: [
- { direction: 'inbound', content: '我每天通勤,A离地铁近但客厅小,B离地铁远但客厅大。' },
- { direction: 'inbound', content: '两套差20万,我先看哪套?' },
- ],
- profile: {
- daily_commute: true,
- option_a_price: 'customer_provided',
- option_b_price: 'customer_provided',
- option_tradeoffs: 'customer_provided',
- },
- inboundContent: '两套差20万,我先看哪套?',
- });
- assert.match(capturedSystem, /客户本轮真正要解决的问题:两套差20万,我先看哪套/);
- assert(capturedSystem.indexOf('1. 先答') < capturedSystem.indexOf('3. 唯一步'));
- assert.equal(runtimeResult.responseGenerationPlan.directAnswer.required, true);
- assert.equal(runtimeResult.responseGenerationPlan.singleNextStep.targetField, 'living_room_is_hard_requirement');
- assert.match(runtimeResult.content, /^按您每天通勤,先看/);
- process.stdout.write(`${JSON.stringify({
- status: 'ok',
- cases: 5,
- fixtureRuntimeIntegrations: 1,
- assertions: 33,
- contract: ['customerQuestion', 'directAnswer', 'judgment', 'singleNextStep'],
- }, null, 2)}\n`);
- }
- main().catch(error => {
- process.stderr.write(`${error.stack || error.message}\n`);
- process.exit(1);
- });
|