| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- const path = require('path');
- const { okResult } = require('../../core/result-envelope');
- const { parseBrief } = require('./brief-parser');
- const { SAMPLE_CREATORS } = require('./sample-data');
- const { searchLiveCreators } = require('./live-provider');
- const { applyMemory } = require('./preference-memory');
- const { attachReferenceBaselines } = require('./reference-baselines');
- const { enrichReferenceBaselines } = require('./reference-enrichment-provider');
- const { attachReferenceStyleFingerprints } = require('./reference-style-fingerprint');
- const { attachEvidenceCards, loadMultimodalEvidence } = require('./multimodal-evidence');
- const { enrichCriteriaWithExperience } = require('./experience-rules');
- const { enrichHomepageEvidence } = require('./homepage-evidence-provider');
- const { rankCreators } = require('./ranker');
- const { buildSourcingReport } = require('./report');
- const { createReporter } = require('./progress-reporter');
- async function runTihaoSourcing(input = {}) {
- const collectionMode = resolveCollectionMode(input);
- const effectiveInput = collectionMode === 'live' ? withLiveSmallRunDefaults(input) : input;
- const outputDir = path.resolve(input.output || input.outputDir || `outputs/tihao-sourcing-${Date.now()}`);
- const reporter = createReporter(input, { outputDir });
- try {
- return await runTihaoSourcingInner(effectiveInput, collectionMode, outputDir, reporter);
- } catch (error) {
- await reporter.fail(error);
- throw error;
- }
- }
- async function runTihaoSourcingInner(input, collectionMode, outputDir, reporter) {
- const effectiveInput = input;
- let criteria = parseBrief(input);
- criteria = applyMemory(criteria, effectiveInput.memory || effectiveInput.memoryPath);
- await reporter.start(criteria, input);
- const enrichment = await enrichReferenceBaselines(criteria, effectiveInput);
- criteria = attachReferenceBaselines(criteria, {
- ...effectiveInput,
- referenceBaselines: [
- ...(Array.isArray(effectiveInput.referenceBaselines) ? effectiveInput.referenceBaselines : []),
- ...enrichment.baselines
- ],
- referenceProviderStatus: enrichment.status
- });
- criteria = attachReferenceStyleFingerprints(criteria, effectiveInput, {
- status: enrichment.status,
- referenceStyleFingerprints: enrichment.referenceStyleFingerprints || [],
- referenceResources: enrichment.referenceResources || []
- });
- criteria = enrichCriteriaWithExperience(criteria);
- let creators = SAMPLE_CREATORS;
- let warnings = [...(enrichment.warnings || [])];
- let recallRecords = [];
- if (collectionMode === 'live') {
- const liveResult = await searchLiveCreators(criteria, effectiveInput);
- if (liveResult.status !== 'ok') {
- await reporter.blocked(liveResult.status || 'live_error', '真实检索未通过', 20);
- return liveResult;
- }
- creators = liveResult.creators.length ? liveResult.creators : [];
- recallRecords = liveResult.recallRecords || [];
- warnings = [...warnings, ...(liveResult.warnings || [])];
- if (creators.length === 0) {
- warnings.push('live 模式没有返回候选,建议放宽关键词、检查 company,或先用 sample 模式校准 brief。');
- if (!effectiveInput.allowSampleFallback) {
- await reporter.blocked('no_live_samples', 'live 检索无候选', 30);
- return okResult({
- status: 'no_live_samples',
- assistantMessage: [
- '真实 live 检索已完成,但没有返回可用候选。',
- '',
- `检索平台:${criteria.platforms.map(labelPlatform).join('、')}`,
- `关键词:${criteria.keywords.join('、') || '未解析到关键词'}`,
- '',
- '建议下一步:',
- '- 确认 company objectId 是否需要传入;',
- '- 换一个更宽的关键词,例如品类词而不是品牌词;',
- '- 先用 sample 模式校准 Brief,再扩大 live 检索页数。'
- ].join('\n'),
- summary: {
- brand: criteria.brand,
- collectionMode,
- total: 0,
- targetCount: criteria.targetCount || 0
- },
- data: { criteria: { ...criteria, recallRecords } },
- warnings,
- errors: [],
- nextActions: [
- '传入 company 后重跑 live',
- '改用更宽关键词重跑 live',
- '设置 allowSampleFallback=true 生成演示名单'
- ]
- });
- }
- creators = SAMPLE_CREATORS.filter(item => criteria.platforms.includes(item.platform));
- }
- }
- await reporter.step('search', '候选检索完成', 45);
- const homepageEvidence = await enrichHomepageEvidence(criteria, creators, effectiveInput);
- creators = homepageEvidence.creators || creators;
- warnings = [...warnings, ...(homepageEvidence.warnings || [])];
- criteria = {
- ...criteria,
- homepageEvidenceStatus: buildHomepageEvidenceStatus(homepageEvidence.status || {})
- };
- const preliminaryCandidates = rankCreators(creators, criteria);
- const multimodalEvidence = await loadMultimodalEvidence(criteria, preliminaryCandidates, effectiveInput);
- warnings = [...warnings, ...(multimodalEvidence.warnings || [])];
- criteria = {
- ...criteria,
- evidenceCards: multimodalEvidence.evidenceCards || [],
- evidenceStatus: buildEvidenceStatus(multimodalEvidence.evidenceCards || [], multimodalEvidence.providerStatus),
- recallRecords,
- referenceResources: [
- ...(criteria.referenceResources || []),
- ...(multimodalEvidence.referenceResources || [])
- ]
- };
- criteria = attachReferenceStyleFingerprints(criteria, effectiveInput, {
- providerStatus: multimodalEvidence.providerStatus,
- referenceStyleFingerprints: multimodalEvidence.referenceStyleFingerprints || [],
- referenceResources: multimodalEvidence.referenceResources || []
- });
- criteria = enrichCriteriaWithExperience(criteria);
- await reporter.step('rank', '排序与证据完成', 75);
- const candidates = attachEvidenceCards(preliminaryCandidates, criteria.evidenceCards);
- const report = buildSourcingReport({
- criteria,
- candidates,
- collectionMode,
- outputDir,
- warnings
- });
- await reporter.complete(report);
- return okResult({
- assistantMessage: report.assistantMessage,
- summary: report.summary,
- data: report.data,
- files: report.files,
- warnings,
- nextActions: report.nextActions || [
- '商务复核强推荐账号的主页、报价有效期和近期内容',
- '把客户剔除/偏好反馈写入 tihao_preference_update',
- collectionMode === 'sample' ? '确认 SOP 后切换 live 小规模检索' : '确认额度后逐步扩大 keywordLimit/pagesPerKeyword'
- ]
- });
- }
- function resolveCollectionMode(input = {}) {
- const explicitMode = input.collectionMode || input.collection_mode;
- return explicitMode === 'sample' ? 'sample' : 'live';
- }
- function withLiveSmallRunDefaults(input = {}) {
- return {
- ...input,
- keywordLimit: input.keywordLimit || input.keyword_limit || 1,
- pagesPerKeyword: input.pagesPerKeyword || input.pages_per_keyword || 1
- };
- }
- function buildEvidenceStatus(evidenceCards, providerStatus = {}) {
- return {
- cardCount: evidenceCards.length,
- status: evidenceCards.length ? 'evidence_cards_loaded' : 'no_multimodal_evidence',
- providerRequested: Boolean(providerStatus.requested),
- providerStatus: providerStatus.status || 'not_requested',
- providerHttpStatus: providerStatus.httpStatus || 0,
- providerWarning: providerStatus.warning || '',
- note: providerStatus.warning || (evidenceCards.length
- ? '已加载多模态证据卡,可用于帧图/ASR/视觉复核。'
- : '本轮未加载多模态证据卡。')
- };
- }
- function buildHomepageEvidenceStatus(status = {}) {
- return {
- requested: Boolean(status.requested),
- status: status.status || 'not_requested',
- providerStatus: status.status || 'not_requested',
- source: status.source || '',
- httpStatus: status.httpStatus || 0,
- evidenceCount: status.evidenceCount || 0,
- note: status.note || status.warning || ''
- };
- }
- function labelPlatform(platform) {
- return {
- xiaohongshu: '小红书',
- douyin: '抖音',
- bilibili: 'B站'
- }[platform] || platform;
- }
- module.exports = {
- runTihaoSourcing,
- resolveCollectionMode,
- withLiveSmallRunDefaults
- };
|