HTS MCP
Skip to Content
ArchitectureSemantic Graph

Semantic Graph

The knowledge graph connects tariff codes with classified semantic relationships. It’s built in two phases: edge generation (KNN) and edge classification (LLM).

Phase 1: Edge Generation

KNN with pgvector

Uses CROSS JOIN LATERAL for efficient batch KNN:

SELECT sb.code AS source_code, nn.code AS target_code, 1 - (sb.embedding <=> nn.embedding) AS similarity, ROW_NUMBER() OVER ( PARTITION BY sb.code ORDER BY sb.embedding <=> nn.embedding ) AS rank FROM source_batch sb CROSS JOIN LATERAL ( SELECT e.code FROM hts_embeddings e WHERE e.code != sb.code ORDER BY e.embedding <=> sb.embedding LIMIT :k ) nn WHERE 1 - (sb.embedding <=> nn.embedding) >= :min_similarity

Configuration

ParameterDefaultDescription
k30Nearest neighbors per code
min_similarity0.65Cosine similarity threshold
levelhts8Embedding level
variantfullEmbedding variant

Output

Candidates are stored in hts_edge_candidates with:

  • Source/target codes
  • Similarity score
  • Source/target chapters and headings (for filtering)
  • Run ID and timestamp

Typically generates 100,000–150,000 candidates for ~13,000 HTS8 codes.

Phase 2: Edge Classification

Async LLM Pipeline

The classifier processes candidates through an async pipeline:

Fetch unclassified → Chunk → Semaphore-controlled async LLM calls → Validate → Batch flush
  1. Fetch: LEFT JOIN to hts_semantic_edges finds candidates not yet classified (resume support)
  2. Chunk: Candidates are processed in chunks of 1,000 with configurable delay between chunks
  3. LLM call: Each candidate pair is sent to GPT-5.4 Nano with a structured JSON schema
  4. Validate: Response is parsed and validated (type must be in allowed set, confidence 0-1)
  5. Flush: Classified edges are batch-inserted to hts_semantic_edges

Concurrency Control

semaphore = asyncio.Semaphore(concurrency) # default: 200 async with semaphore: response = await openai.chat.completions.create(...)

Between chunks, a configurable delay (default: 1s) provides rate-limit pacing.

Resume Behavior

If the pipeline is interrupted:

  • Completed classifications are already flushed to the database
  • Re-running picks up from where it stopped
  • The LEFT JOIN ensures only unclassified candidates are fetched

Relationship Types

The LLM classifies each edge into one of 10 types:

TypeDefinitionExample
material_affinitySame material, different formsRaw steel → Steel bars
functional_similaritySame purpose, different materialsPlastic containers → Glass containers
manufacturing_processSimilar production methodsHot-rolled steel → Cold-rolled steel
end_useComplementary productsScrews → Screw drivers
substitutionBorderline classificationFresh beef → Chilled beef
component_assemblyPart-to-wholeEngine parts → Complete engines
cross_category_bridgeUnexpected cross-chapter linkTextile machinery → Textiles
abstractionHierarchical parent-childHeading → Subheading
weak_associationTangentially relatedLow-value edges
no_meaningful_relationshipFalse positive from KNNFiltered out

Classification Output

Each classified edge includes rich metadata:

FieldPurpose
confidenceHow certain the LLM is (0-1)
reasoning2 sentences explaining the relationship
key_differentiatorWhat distinguishes the two codes (max 8 words)
classification_clarityHow clear-cut the classification was (0-1)
inclusionary/exclusionary termsTerms that define/exclude each code
relationship_haiku5-7-5 syllable creative summary
bidirectionalWhether the relationship is symmetric

Graph Statistics

After a full run, typical distribution:

CategoryPercentageDescription
Meaningful relationships~40%Material, functional, manufacturing, end use, substitution, component
Cross-category bridges~2%High-value cross-chapter connections
Weak/none~55%Filtered out in most queries
ErrorsLess than 0.1%Parse failures, timeouts