Add LLM agent fallback for unmapped tracker patterns

When both pattern matching and domain matching fail to identify a
tracker, an opt-in LLM agent can now attempt identification using
internal database searches and optional web search. The agent returns
structured output (third party name, category, description, confidence)
and the worker auto-creates CommonThirdParty records when needed.

The feature is gated behind the `llm.tracker-mapping.provider` config
field; when unset the worker behaves exactly as before.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-15 15:57:06 +04:00
parent 80237eb39c
commit 0cc2b62cf2
9 changed files with 683 additions and 1 deletions

View File

@@ -344,6 +344,60 @@ LIMIT 1;
return &pattern, nil
}
type CommonTrackerPatternSearchResult struct {
Pattern string `db:"pattern"`
Description string `db:"description"`
TrackerType TrackerType `db:"tracker_type"`
ThirdPartyName *string `db:"third_party_name"`
Confidence float32 `db:"confidence"`
}
func (ps *CommonTrackerPatterns) FindByKeyword(
ctx context.Context,
conn pg.Querier,
fragment string,
limit int,
) ([]CommonTrackerPatternSearchResult, error) {
if limit <= 0 || limit > 20 {
limit = 10
}
q := `
SELECT
ctp.pattern,
ctp.description,
ctp.tracker_type,
ct.name AS third_party_name,
ctp.confidence
FROM
common_tracker_patterns ctp
LEFT JOIN common_third_parties ct ON ct.id = ctp.common_third_party_id
WHERE
ctp.pattern ILIKE '%' || @fragment || '%'
OR ctp.description ILIKE '%' || @fragment || '%'
ORDER BY
ctp.confidence DESC
LIMIT @limit;
`
args := pgx.StrictNamedArgs{
"fragment": fragment,
"limit": limit,
}
rows, err := conn.Query(ctx, q, args)
if err != nil {
return nil, fmt.Errorf("cannot search common tracker patterns: %w", err)
}
results, err := pgx.CollectRows(rows, pgx.RowToStructByName[CommonTrackerPatternSearchResult])
if err != nil {
return nil, fmt.Errorf("cannot collect common tracker pattern search results: %w", err)
}
return results, nil
}
func (ps *CommonTrackerPatterns) LoadByCommonThirdPartyID(
ctx context.Context,
conn pg.Querier,