Tune tracker workers and bound agent budgets

The tracker-mapping and common-pattern-enrichment workers ran with the
kit/worker defaults (interval 10s, max-concurrency 5 each) and dropped
the resolved per-agent max-tokens/temperature, so up to ten LLM
pipelines could run unbounded on one OpenAI client. The mapping worker
also held a FOR UPDATE transaction across the LLM and Firecrawl calls
while its DB search tools acquired a second pooled connection, risking
pool exhaustion under concurrency.

Plumb max-tokens, temperature, agent timeout, and per-worker max-turns
through TrackerAgentsConfig and DisambiguationConfig into all three
agent builders, replacing the hard-coded constants with config-fed
fields and package fallbacks. Expose worker interval, concurrency,
stale-after, agent timeout, and max-turns as config (env, Helm values,
deployment template) mirroring the evidence-describer pattern, and
apply them at registration.

Refactor Process into deterministic-read, agent (no transaction), and
persist phases so neither the mapping agent nor disambiguation runs
inside an open transaction, removing the row locks held across network
latency and the nested-connection pressure.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-29 16:37:32 +02:00
parent 50c5454681
commit 55302d18f0
17 changed files with 818 additions and 339 deletions

View File

@@ -38,19 +38,32 @@ const (
// described in the prompt.
disambiguationConfidenceThreshold = 0.6
// disambiguationTimeout caps a single disambiguation run. The
// agent has no tools and a single turn, so this is mostly a
// guard against a hung LLM provider, not a real budget.
disambiguationTimeout = 60 * time.Second
// defaultDisambiguationTimeout caps a single disambiguation run
// when the config supplies none. The agent has no tools and a
// single turn, so this is mostly a guard against a hung LLM
// provider, not a real budget.
defaultDisambiguationTimeout = 45 * time.Second
// defaultDisambiguationMaxTokens caps the agent's structured
// output when the config carries no max-tokens budget. The output
// is a single id plus a one-sentence rationale.
defaultDisambiguationMaxTokens = 512
)
// DisambiguationConfig configures the third-party disambiguation
// agent. The agent has no DB tools and no web-search tools: the
// candidate list is supplied entirely in the prompt and the agent
// only picks among it.
//
// MaxTokens and Temperature bound and steer the single LLM call, and
// Timeout caps a single run. Zero-valued fields fall back to package
// defaults.
type DisambiguationConfig struct {
LLMClient *llm.Client
Model string
LLMClient *llm.Client
Model string
MaxTokens *int
Temperature *float64
Timeout time.Duration
}
// DisambiguationResult is the structured output the disambiguation
@@ -75,15 +88,25 @@ func BuildDisambiguationAgent(
panic(fmt.Sprintf("thirdparty: cannot build disambiguation output type: %s", err))
}
return agent.New(
"third-party-disambiguation",
cfg.LLMClient,
maxTokens := defaultDisambiguationMaxTokens
if cfg.MaxTokens != nil && *cfg.MaxTokens > 0 {
maxTokens = *cfg.MaxTokens
}
opts := []agent.Option{
agent.WithInstructions(disambiguationPrompt),
agent.WithModel(cfg.Model),
agent.WithOutputType(outputType),
agent.WithMaxTurns(1),
agent.WithMaxTokens(maxTokens),
agent.WithLogger(logger),
)
}
if cfg.Temperature != nil {
opts = append(opts, agent.WithTemperature(*cfg.Temperature))
}
return agent.New("third-party-disambiguation", cfg.LLMClient, opts...)
}
// Disambiguate runs the agent against the given catalog third party
@@ -102,14 +125,19 @@ func Disambiguate(
commonParty coredata.CommonThirdParty,
commonDomains coredata.CommonThirdPartyDomains,
candidates []ScoredCandidate,
timeout time.Duration,
) (*gid.GID, error) {
if a == nil || len(candidates) == 0 {
return nil, nil
}
if timeout <= 0 {
timeout = defaultDisambiguationTimeout
}
prompt := buildDisambiguationPrompt(commonParty, commonDomains, candidates)
agentCtx, cancel := context.WithTimeout(ctx, disambiguationTimeout)
agentCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
result, err := agent.RunTyped[DisambiguationResult](