feat(monitoring): exclude qwen from content citation resolution
Skip article alias resolution and content-citation aggregates when the platform is qwen, and hide the content-citation badge/meta in the tracking detail view, since qwen's source URLs are not reliable enough to map to brand articles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2072,7 +2072,7 @@ func (s *MonitoringCallbackService) processTaskResult(ctx context.Context, task
|
||||
return nil, err
|
||||
}
|
||||
if runStatus == "succeeded" {
|
||||
facts, buildErr := s.buildCitationFacts(ctx, tx, task.TenantID, sourceInputs)
|
||||
facts, buildErr := s.buildCitationFacts(ctx, tx, task.TenantID, task.AIPlatformID, sourceInputs)
|
||||
if buildErr != nil {
|
||||
return nil, buildErr
|
||||
}
|
||||
@@ -2385,37 +2385,42 @@ func shouldUseMonitoringLLMParse(req MonitoringTaskResultRequest) bool {
|
||||
return !hasCompleteMonitoringParsePayload(req)
|
||||
}
|
||||
|
||||
func (s *MonitoringCallbackService) buildCitationFacts(ctx context.Context, tx pgx.Tx, tenantID int64, inputs []MonitoringSourceItem) ([]monitoringCitationFact, error) {
|
||||
func (s *MonitoringCallbackService) buildCitationFacts(ctx context.Context, tx pgx.Tx, tenantID int64, platformID string, inputs []MonitoringSourceItem) ([]monitoringCitationFact, error) {
|
||||
if len(inputs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
normalizedURLs := make([]string, 0, len(inputs))
|
||||
seenURLs := make(map[string]struct{}, len(inputs))
|
||||
for _, item := range inputs {
|
||||
key := strings.TrimSpace(monitoringStringValue(item.NormalizedURL))
|
||||
if key == "" {
|
||||
key = normalizeCitationURL(item.URL)
|
||||
resolveContentCitations := monitoringPlatformResolvesContentCitations(platformID)
|
||||
aliasMap := map[string]monitoringAliasResolution{}
|
||||
if resolveContentCitations {
|
||||
normalizedURLs := make([]string, 0, len(inputs))
|
||||
seenURLs := make(map[string]struct{}, len(inputs))
|
||||
for _, item := range inputs {
|
||||
key := strings.TrimSpace(monitoringStringValue(item.NormalizedURL))
|
||||
if key == "" {
|
||||
key = normalizeCitationURL(item.URL)
|
||||
}
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seenURLs[key]; ok {
|
||||
continue
|
||||
}
|
||||
seenURLs[key] = struct{}{}
|
||||
normalizedURLs = append(normalizedURLs, key)
|
||||
}
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seenURLs[key]; ok {
|
||||
continue
|
||||
}
|
||||
seenURLs[key] = struct{}{}
|
||||
normalizedURLs = append(normalizedURLs, key)
|
||||
}
|
||||
|
||||
aliasMap, err := s.loadAliasResolutions(ctx, tx, tenantID, normalizedURLs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var err error
|
||||
aliasMap, err = s.loadAliasResolutions(ctx, tx, tenantID, normalizedURLs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]monitoringCitationFact, 0, len(inputs))
|
||||
seenFacts := make(map[string]struct{}, len(inputs))
|
||||
for _, input := range inputs {
|
||||
fact, ok := buildMonitoringCitationFact(input, aliasMap)
|
||||
fact, ok := buildMonitoringCitationFact(input, aliasMap, resolveContentCitations)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -2552,11 +2557,11 @@ func (s *MonitoringCallbackService) loadExistingRunSummary(ctx context.Context,
|
||||
if err := s.monitoringPool.QueryRow(ctx, `
|
||||
SELECT
|
||||
COUNT(*)::int AS citation_source_count,
|
||||
COUNT(*) FILTER (WHERE article_id IS NOT NULL)::int AS content_citation_count
|
||||
COUNT(*) FILTER (WHERE article_id IS NOT NULL AND $3::text <> 'qwen')::int AS content_citation_count
|
||||
FROM monitoring_citation_facts
|
||||
WHERE tenant_id = $1
|
||||
AND run_id = $2
|
||||
`, task.TenantID, runID.Int64).Scan(&citationSourceCount, &contentCitationCount); err != nil {
|
||||
`, task.TenantID, runID.Int64, normalizeMonitoringPlatformID(task.AIPlatformID)).Scan(&citationSourceCount, &contentCitationCount); err != nil {
|
||||
return nil, 0, 0, response.ErrInternal(50041, "citation_lookup_failed", "failed to inspect existing citation facts")
|
||||
}
|
||||
|
||||
@@ -2683,6 +2688,15 @@ func monitoringPlatformUsesDedicatedCitationPanel(platformID string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func monitoringPlatformResolvesContentCitations(platformID string) bool {
|
||||
switch normalizeMonitoringPlatformID(platformID) {
|
||||
case "qwen":
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func newMonitoringTaskResultEnvelope(task *monitoringCollectTask, installationID string, receivedAt time.Time, req MonitoringTaskResultRequest) monitoringTaskResultEnvelope {
|
||||
return monitoringTaskResultEnvelope{
|
||||
Version: "v1",
|
||||
@@ -2741,7 +2755,7 @@ func extractMonitoringSourceItems(value interface{}) []MonitoringSourceItem {
|
||||
return result
|
||||
}
|
||||
|
||||
func buildMonitoringCitationFact(input MonitoringSourceItem, aliasMap map[string]monitoringAliasResolution) (monitoringCitationFact, bool) {
|
||||
func buildMonitoringCitationFact(input MonitoringSourceItem, aliasMap map[string]monitoringAliasResolution, resolveContentCitations bool) (monitoringCitationFact, bool) {
|
||||
rawURL := strings.TrimSpace(input.URL)
|
||||
normalizedURL := strings.TrimSpace(monitoringStringValue(input.NormalizedURL))
|
||||
if normalizedURL == "" {
|
||||
@@ -2791,14 +2805,18 @@ func buildMonitoringCitationFact(input MonitoringSourceItem, aliasMap map[string
|
||||
siteKey = registrableDomain
|
||||
}
|
||||
|
||||
articleID := cloneInt64(input.ArticleID)
|
||||
publishRecordID := cloneInt64(input.PublishRecordID)
|
||||
if alias, ok := aliasMap[normalizedURL]; ok {
|
||||
if articleID == nil {
|
||||
articleID = cloneInt64(alias.ArticleID)
|
||||
}
|
||||
if publishRecordID == nil {
|
||||
publishRecordID = cloneInt64(alias.PublishRecordID)
|
||||
var articleID *int64
|
||||
var publishRecordID *int64
|
||||
if resolveContentCitations {
|
||||
articleID = cloneInt64(input.ArticleID)
|
||||
publishRecordID = cloneInt64(input.PublishRecordID)
|
||||
if alias, ok := aliasMap[normalizedURL]; ok {
|
||||
if articleID == nil {
|
||||
articleID = cloneInt64(alias.ArticleID)
|
||||
}
|
||||
if publishRecordID == nil {
|
||||
publishRecordID = cloneInt64(alias.PublishRecordID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -346,6 +346,44 @@ func TestBuildMonitoringRawPayloadQwenIncludesSearchResultsInCitationInputs(t *t
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMonitoringCitationFactCanDisableContentResolution(t *testing.T) {
|
||||
aliasArticleID := int64(42)
|
||||
aliasPublishRecordID := int64(84)
|
||||
normalizedURL := "https://example.com/article"
|
||||
input := MonitoringSourceItem{
|
||||
URL: "https://example.com/article?utm_source=test#section",
|
||||
NormalizedURL: &normalizedURL,
|
||||
}
|
||||
aliasMap := map[string]monitoringAliasResolution{
|
||||
normalizedURL: {
|
||||
ArticleID: &aliasArticleID,
|
||||
PublishRecordID: &aliasPublishRecordID,
|
||||
},
|
||||
}
|
||||
|
||||
resolvedFact, ok := buildMonitoringCitationFact(input, aliasMap, true)
|
||||
if !ok {
|
||||
t.Fatal("buildMonitoringCitationFact() with content resolution returned !ok")
|
||||
}
|
||||
if resolvedFact.ArticleID == nil || *resolvedFact.ArticleID != aliasArticleID {
|
||||
t.Fatalf("resolved ArticleID = %v, want %d", resolvedFact.ArticleID, aliasArticleID)
|
||||
}
|
||||
if resolvedFact.PublishRecordID == nil || *resolvedFact.PublishRecordID != aliasPublishRecordID {
|
||||
t.Fatalf("resolved PublishRecordID = %v, want %d", resolvedFact.PublishRecordID, aliasPublishRecordID)
|
||||
}
|
||||
|
||||
sourceOnlyFact, ok := buildMonitoringCitationFact(input, aliasMap, false)
|
||||
if !ok {
|
||||
t.Fatal("buildMonitoringCitationFact() without content resolution returned !ok")
|
||||
}
|
||||
if sourceOnlyFact.ArticleID != nil {
|
||||
t.Fatalf("source-only ArticleID = %v, want nil", sourceOnlyFact.ArticleID)
|
||||
}
|
||||
if sourceOnlyFact.PublishRecordID != nil {
|
||||
t.Fatalf("source-only PublishRecordID = %v, want nil", sourceOnlyFact.PublishRecordID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMonitoringRawPayloadDeepSeekIncludesSearchResultsInCitationInputs(t *testing.T) {
|
||||
searchTitle := "搜索网页结果"
|
||||
|
||||
|
||||
@@ -214,6 +214,7 @@ func (s *MonitoringCallbackService) loadMonitoringBrandDailyAggregate(ctx contex
|
||||
WHERE cf.tenant_id = $1
|
||||
AND cf.brand_id = $2
|
||||
AND cf.article_id IS NOT NULL
|
||||
AND r.ai_platform_id <> 'qwen'
|
||||
AND r.collector_type = $3
|
||||
AND r.business_date = $4::date
|
||||
AND r.status = 'succeeded'
|
||||
|
||||
@@ -2438,8 +2438,8 @@ func (s *MonitoringService) loadCitationRanking(
|
||||
citation_counts AS (
|
||||
SELECT
|
||||
r.ai_platform_id,
|
||||
COUNT(DISTINCT cf.run_id) FILTER (WHERE cf.article_id IS NOT NULL) AS cited_answer_count,
|
||||
COUNT(DISTINCT cf.article_id) FILTER (WHERE cf.article_id IS NOT NULL) AS cited_article_count
|
||||
COUNT(DISTINCT cf.run_id) FILTER (WHERE cf.article_id IS NOT NULL AND r.ai_platform_id <> 'qwen') AS cited_answer_count,
|
||||
COUNT(DISTINCT cf.article_id) FILTER (WHERE cf.article_id IS NOT NULL AND r.ai_platform_id <> 'qwen') AS cited_article_count
|
||||
FROM question_monitor_runs r
|
||||
JOIN monitoring_citation_facts cf
|
||||
ON cf.tenant_id = r.tenant_id AND cf.run_id = r.id
|
||||
@@ -2562,6 +2562,7 @@ func (s *MonitoringService) loadCitedArticles(
|
||||
WHERE cf.tenant_id = $1
|
||||
AND cf.brand_id = $2
|
||||
AND cf.article_id IS NOT NULL
|
||||
AND r.ai_platform_id <> 'qwen'
|
||||
AND r.collector_type = $3
|
||||
AND r.status = 'succeeded'
|
||||
AND r.business_date BETWEEN $4::date AND $5::date
|
||||
@@ -2878,7 +2879,7 @@ func (s *MonitoringService) loadQuestionCitationAnalysis(ctx context.Context, te
|
||||
COALESCE(tm.site_key, gm.site_key, cf.site_key) AS site_key,
|
||||
COALESCE(tm.registrable_domain, gm.registrable_domain, cf.site_key, cf.registrable_domain) AS site_domain,
|
||||
COUNT(cf.id) AS citation_count,
|
||||
COUNT(*) FILTER (WHERE cf.article_id IS NOT NULL) AS content_citation_count
|
||||
COUNT(*) FILTER (WHERE cf.article_id IS NOT NULL AND r.ai_platform_id <> 'qwen') AS content_citation_count
|
||||
FROM question_monitor_runs r
|
||||
JOIN monitoring_citation_facts cf
|
||||
ON cf.tenant_id = r.tenant_id AND cf.run_id = r.id
|
||||
@@ -3058,6 +3059,7 @@ func (s *MonitoringService) loadQuestionContentCitations(ctx context.Context, te
|
||||
AND r.status = 'succeeded'
|
||||
AND r.business_date BETWEEN $6::date AND $7::date
|
||||
AND cf.article_id IS NOT NULL
|
||||
AND r.ai_platform_id <> 'qwen'
|
||||
AND ($8::text[] IS NULL OR r.ai_platform_id = ANY($8))
|
||||
GROUP BY cf.article_id, r.ai_platform_id
|
||||
ORDER BY citation_count DESC, cf.article_id ASC
|
||||
|
||||
Reference in New Issue
Block a user