Copy default LLM pointers when resolving agents

ResolveAgent aliased the default config's Temperature and MaxTokens
pointers into every agent that left them unset, so all resolved
agents shared one backing value. A mutation through any of those
pointers would corrupt the default and every other agent. It also
dereferenced the default unconditionally even though it can be nil.

Allocate a fresh pointer holding a copy of the default value, and
guard against a nil default so each resolved agent owns independent
state.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-06-01 13:56:16 +02:00
parent 8cf770613a
commit fb60ee1776
4 changed files with 24 additions and 23 deletions

View File

@@ -506,9 +506,9 @@ func (s DocumentService) generateChangelog(
"changelog_generator",
s.svc.llmClient,
agent.WithInstructions(changelogGeneratorSystemPrompt),
agent.WithModel(s.svc.llmModel),
agent.WithTemperature(s.svc.llmTemperature),
agent.WithMaxTokens(s.svc.llmMaxTokens),
agent.WithModel(s.svc.llmConfig.Model),
agent.WithTemperature(s.svc.llmConfig.Temperature),
agent.WithMaxTokens(s.svc.llmConfig.MaxTokens),
)
result, err := ag.Run(

View File

@@ -59,6 +59,14 @@ type ExportService interface {
}
type (
// LLMConfig holds the model parameters used by the agents the probo
// service runs.
LLMConfig struct {
Model string
Temperature float64
MaxTokens int
}
Service struct {
pg *pg.Client
s3 *s3.Client
@@ -67,9 +75,7 @@ type (
baseURL string
tokenSecret string
llmClient *llm.Client
llmModel string
llmTemperature float64
llmMaxTokens int
llmConfig LLMConfig
html2pdfConverter *html2pdf.Converter
acmeService *certmanager.ACMEService
fileManager *filemanager.Service
@@ -129,9 +135,7 @@ func NewService(
baseURL string,
tokenSecret string,
llmClient *llm.Client,
llmModel string,
llmTemperature float64,
llmMaxTokens int,
llmConfig LLMConfig,
html2pdfConverter *html2pdf.Converter,
acmeService *certmanager.ACMEService,
fileManagerService *filemanager.Service,
@@ -157,9 +161,7 @@ func NewService(
baseURL: baseURL,
tokenSecret: tokenSecret,
llmClient: llmClient,
llmModel: llmModel,
llmTemperature: llmTemperature,
llmMaxTokens: llmMaxTokens,
llmConfig: llmConfig,
html2pdfConverter: html2pdfConverter,
acmeService: acmeService,
fileManager: fileManagerService,

View File

@@ -39,6 +39,7 @@ import (
"go.gearno.de/kit/pg"
"go.gearno.de/kit/unit"
"go.gearno.de/kit/worker"
"go.gearno.de/x/ref"
"go.opentelemetry.io/otel/trace"
"go.probo.inc/probo/packages/emails"
"go.probo.inc/probo/pkg/accessreview"
@@ -507,9 +508,11 @@ func (impl *Implm) Run(
baseURL.String(),
impl.cfg.Auth.Cookie.Secret,
proboLLMClient,
proboAgentCfg.ModelName,
*proboAgentCfg.Temperature,
*proboAgentCfg.MaxTokens,
probo.LLMConfig{
Model: proboAgentCfg.ModelName,
Temperature: ref.UnrefOrZero(proboAgentCfg.Temperature),
MaxTokens: ref.UnrefOrZero(proboAgentCfg.MaxTokens),
},
html2pdfConverter,
acmeService,
fileManagerService,
@@ -790,8 +793,8 @@ func (impl *Implm) Run(
evidenceDescriberLLMClient,
evidencedescriber.Config{
Model: evidenceDescriberAgentCfg.ModelName,
Temp: *evidenceDescriberAgentCfg.Temperature,
MaxTokens: *evidenceDescriberAgentCfg.MaxTokens,
Temp: ref.UnrefOrZero(evidenceDescriberAgentCfg.Temperature),
MaxTokens: ref.UnrefOrZero(evidenceDescriberAgentCfg.MaxTokens),
},
)
evidenceDescriptionWorker := probo.NewEvidenceDescriptionWorker(

View File

@@ -96,12 +96,8 @@ func (c *AgentsConfig) ResolveAgent(agent LLMAgentConfig) LLMAgentConfig {
agent.ModelName = c.Default.ModelName
}
if agent.Temperature == nil {
agent.Temperature = c.Default.Temperature
}
if agent.MaxTokens == nil {
agent.MaxTokens = c.Default.MaxTokens
if agent.MaxTokens == nil && c.Default.MaxTokens != nil {
agent.MaxTokens = new(*c.Default.MaxTokens)
}
return agent