Skip to main content

Comparison Generator

The comparison generator creates structured "X vs Y" comparison pages for items within a work. It combines web research, AI analysis, and scored dimensions to produce detailed comparison content.

Architecture

The comparison system spans two packages:

LocationPurpose
packages/agent/src/comparison-generator/Core service and generation logic
packages/plugins/comparison-generator/Plugin manifest and settings schema

Core Service

ComparisonGenerationService orchestrates the full comparison workflow. It is a NestJS injectable service that depends on:

  • AiFacadeService -- structured AI output and text generation
  • SearchFacadeService -- web search for research data
  • ContentExtractorFacadeService -- web page content extraction
  • GitFacadeService -- reading and writing to data repositories

Supporting Modules

The comparison/ subwork contains pure functions and focused modules:

ModulePurpose
pair-selector.tsSelects the next uncompared pair of items
comparison-researcher.tsResearches two items via web search and extraction
comparison-writer.tsGenerates comparison content via AI

Comparison Workflow

1. Pair Selection

The selectNextPair() function picks the best pair to compare next:

  • Groups items by category.
  • Prioritizes same-category pairs (more meaningful comparisons).
  • Skips already-generated pairs tracked in comparison_state.generated_pairs.
  • Respects minItemsForComparison (minimum items in a category to enable comparisons) and maxComparisons limits.

Pair keys are order-independent: buildPairKey('vercel', 'netlify') always produces netlify--vercel (alphabetical order).

2. Research Phase

The researchPair() function gathers context about both items:

  • Performs web searches for each item individually and as a comparison query.
  • Extracts content from the most relevant result URLs.
  • Returns combined research context for the AI generation step.

3. AI Generation

The generateComparison() function produces structured output:

Structured JSON output (via Zod schema):

{
title: string, // e.g., "Vercel vs Netlify"
summary: string, // Brief comparison overview
verdict: string, // Overall assessment
verdict_winner: 'item_a' | 'item_b' | 'tie',
dimensions: [{
name: string, // e.g., "Performance"
item_a_summary: string,
item_b_summary: string,
item_a_score: number, // 1-10
item_b_score: number, // 1-10
winner: 'item_a' | 'item_b' | 'tie',
}],
}

Markdown content -- a human-readable comparison article.

Extended analysis (optional) -- deeper analysis when extended_analysis is enabled in plugin settings.

4. Persistence

Results are written to the data repository:

  • Comparison metadata as YAML (comparisons/{slug}.yml).
  • Markdown content (comparisons/{slug}.md).
  • Optional extended analysis markdown.
  • Config updated with comparison state tracking.
  • Changes committed and pushed to Git.

Plugin Settings

The comparison generator is controlled via the comparison-generator plugin, which provides these settings:

SettingDefaultDescription
max_comparisons_modelimitedlimited or unlimited
max_comparisons10Maximum comparison count when limited
min_items_for_comparison3Minimum items in a category to enable comparisons
ai_provider(default)Override the AI provider for comparison generation
ai_model(default)Override the AI model
custom_prompt(none)Custom instructions appended to the comparison prompt
extended_analysisfalseGenerate extended analysis content

API Operations

The service exposes five operations:

MethodPurpose
generateNextComparison(workId, userId)Auto-select and generate the next comparison
generateManualComparison(workId, userId, itemA, itemB)Generate comparison for two specific items
listComparisons(workId, userId)List all comparisons for a work
getComparison(workId, userId, slug)Get a single comparison with markdown
deleteComparison(workId, userId, slug)Delete a comparison and update state

Manual vs Automatic Comparisons

  • Automatic: The system picks the best pair via selectNextPair(). Returns skipped if no pairs remain.
  • Manual: The user specifies two item slugs. Returns skipped if the comparison already exists.

Both flows share the same generation pipeline after pair selection.

State Tracking

Comparison state is persisted in the data repository's .works/works.yml under metadata.comparison_state:

metadata:
comparison_state:
generated_pairs:
- 'netlify--vercel'
- 'angular--react'
last_generated_at: '2025-01-15T10:30:00.000Z'
total_generated: 2

This prevents re-generating existing comparisons and enables the countRemainingPairs() function to report how many comparisons are left to generate.