Promote tracker patterns to org third parties via worker

Manual moves of a non-extension TrackerPattern lacking a ThirdPartyID
now request mapping, which the tracker-mapping worker resolves with a
four-stage pipeline: exact common_third_party_id link, heuristic
ranking, agent disambiguation, and finally CreateFromCommon. Existing
fuzzy-matched org rows are tagged with common_third_party_id so the
next promotion takes the O(1) exact-link path.

The matching primitives live in pkg/thirdparty (RankCandidates,
LinkToCommon, CreateFromCommon, ScoredCandidate, threshold constants)
so the disambiguation agent and the heuristic share one candidate
type. Cookiebanner orchestrates them; cookie-banner-specific concerns
(pattern -> common-pattern -> common-party navigation, the EXTENSION
gate, and structured logs) stay in the worker.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-28 17:20:36 +02:00
parent 33ddba9770
commit a99a4dde14
12 changed files with 1491 additions and 94 deletions

View File

@@ -313,7 +313,7 @@ func (impl *Implm) Run(
return err
}
trackerMappingCfg, err := impl.buildTrackerMappingConfig(l, tp, r)
trackerMappingCfg, thirdPartyDisambiguationCfg, err := impl.buildTrackerMappingConfig(l, tp, r)
if err != nil {
return err
}
@@ -725,7 +725,7 @@ func (impl *Implm) Run(
},
)
trackerMappingWorker := cookiebanner.NewTrackerMappingWorker(pgClient, l, trackerMappingCfg)
trackerMappingWorker := cookiebanner.NewTrackerMappingWorker(pgClient, l, trackerMappingCfg, thirdPartyDisambiguationCfg)
trackerMappingWorkerCtx, stopTrackerMappingWorker := context.WithCancel(context.Background())
wg.Go(

View File

@@ -21,18 +21,28 @@ import (
"go.gearno.de/kit/log"
"go.opentelemetry.io/otel/trace"
"go.probo.inc/probo/pkg/cookiebanner"
"go.probo.inc/probo/pkg/thirdparty"
)
// buildTrackerMappingConfig wires the tracker-mapping agent. It is opt-in:
// deployments that do not set `llm.tracker-mapping.provider` get a zero
// config (nil LLM client) so the worker runs without agent fallback.
// buildTrackerMappingConfig wires the tracker-mapping agent (catalog
// identification) and the third-party disambiguation agent that the
// tracker-mapping worker uses to promote patterns to org ThirdParties.
// Both are opt-in: deployments that do not set
// `llm.tracker-mapping.provider` get zero configs (nil LLM client) so
// the worker runs without agent fallback.
//
// Both agents are sourced from the same `tracker-mapping` config slot
// because they share the LLM client, model, and lifecycle. The
// disambiguation agent has no Firecrawl/DB tools, so its config
// surface is narrower and it lives in the cross-domain pkg/thirdparty
// package.
func (impl *Implm) buildTrackerMappingConfig(
l *log.Logger,
tp trace.TracerProvider,
r prometheus.Registerer,
) (cookiebanner.TrackerMappingConfig, error) {
) (cookiebanner.TrackerMappingConfig, thirdparty.DisambiguationConfig, error) {
if impl.cfg.Agents.TrackerMapping.Provider == "" {
return cookiebanner.TrackerMappingConfig{}, nil
return cookiebanner.TrackerMappingConfig{}, thirdparty.DisambiguationConfig{}, nil
}
agentCfg, llmClient, err := impl.resolveAgentClient(
@@ -43,12 +53,19 @@ func (impl *Implm) buildTrackerMappingConfig(
r,
)
if err != nil {
return cookiebanner.TrackerMappingConfig{}, fmt.Errorf("cannot resolve tracker mapping agent client: %w", err)
return cookiebanner.TrackerMappingConfig{}, thirdparty.DisambiguationConfig{}, fmt.Errorf("cannot resolve tracker mapping agent client: %w", err)
}
return cookiebanner.TrackerMappingConfig{
mappingCfg := cookiebanner.TrackerMappingConfig{
LLMClient: llmClient,
Model: agentCfg.ModelName,
FirecrawlAPIKey: impl.cfg.Agents.Tools.FirecrawlAPIKey,
}, nil
}
disambiguationCfg := thirdparty.DisambiguationConfig{
LLMClient: llmClient,
Model: agentCfg.ModelName,
}
return mappingCfg, disambiguationCfg, nil
}