Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions agent-manager-service/docs/api_v1_openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6509,6 +6509,29 @@ components:
$ref: "#/components/schemas/Configurations"
inputInterface:
$ref: "#/components/schemas/InputInterface"
modelConfig:
type: array
description: Optional LLM configurations to create atomically with the agent. Name and type are auto-generated.
items:
$ref: "#/components/schemas/ModelConfigRequest"

ModelConfigRequest:
type: object
required:
- envMappings
properties:
envMappings:
type: object
description: Map of environment names to their model configurations
additionalProperties:
$ref: "#/components/schemas/EnvModelConfigRequest"
minProperties: 1
environmentVariables:
type: array
description: Optional custom environment variable names exposed to the agent
uniqueItems: true
items:
$ref: "#/components/schemas/EnvironmentVariableConfig"
Comment on lines +6529 to +6534
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Keep environmentVariables validation docs consistent with the standalone model-config API.

uniqueItems: true only rejects identical {key, name} objects. The existing CreateAgentModelConfigRequest.environmentVariables docs already say duplicate keys are rejected with 400, but that rule is omitted here, so the same field shape now has two different documented contracts.

📝 Suggested doc fix
         environmentVariables:
           type: array
-          description: Optional custom environment variable names exposed to the agent
+          description: Optional custom environment variable names exposed to the agent. Duplicate keys are rejected with 400.
           uniqueItems: true
           items:
             $ref: "#/components/schemas/EnvironmentVariableConfig"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@agent-manager-service/docs/api_v1_openapi.yaml` around lines 6401 - 6406, The
environmentVariables array schema lacks the same duplicate-key validation note
as CreateAgentModelConfigRequest.environmentVariables; update the OpenAPI docs
for the environmentVariables property (the array under environmentVariables and
its items referencing EnvironmentVariableConfig) to document that duplicate keys
are rejected with a 400 error (in addition to uniqueItems: true), e.g., add a
description sentence or validation note stating "Duplicate environment variable
keys are rejected with 400" so the contract matches the standalone model-config
API.


UpdateAgentBasicInfoRequest:
type: object
Expand Down
28 changes: 4 additions & 24 deletions agent-manager-service/services/agent_configuration_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -793,9 +793,6 @@ func (s *agentConfigurationService) processEnvProxyUpdate(
envMapping models.EnvModelConfigRequest,
existingMapping *models.EnvAgentModelMapping,
orgName string,
existingVarNames map[string]string,
isExternalAgent bool,
firstEnvName string,
) (rollbackResource, error) {
s.logger.Info("Updating proxy configuration for environment",
"environment", envName,
Expand Down Expand Up @@ -876,26 +873,9 @@ func (s *agentConfigurationService) processEnvProxyUpdate(
}
}

// Internal-agent only: update Component CR env vars (proxy URL may have changed).
// The SecretReference is NOT updated here: the proxy handle is unchanged in Scenario B,
// so the KVPath is identical and the existing SecretReference already points to the correct secret.
if !isExternalAgent {
secretRefName := buildSecretRefName(config.Name, envName)
proxyURL := buildProxyURL(gateway.Vhost, updatedProxy.Configuration.Context)
envConfigTemplates, err := s.buildEnvironmentVariables(config.Name, varNamesToOverrides(existingVarNames))
if err != nil {
s.logger.Warn("failed to build env config templates in Scenario B", "err", err)
}
envVarsToInject := buildLLMEnvVars(envConfigTemplates, proxyURL, secretRefName)
if uvErr := s.ocClient.UpdateComponentEnvVars(ctx, orgName, config.ProjectName, config.AgentID, envVarsToInject); uvErr != nil {
s.logger.Error("failed to update Component CR env vars in Scenario B — Component CR in inconsistent state", "env", envName, "err", uvErr)
}
if firstEnvName != "" && envName == firstEnvName {
if rbErr := s.ocClient.UpdateReleaseBindingEnvVars(ctx, orgName, config.ProjectName, config.AgentID, firstEnvName, envVarsToInject); rbErr != nil {
s.logger.Warn("failed to patch ReleaseBinding in Scenario B", "env", envName, "err", rbErr)
}
}
}
// Scenario B preserves the proxy handle and context path, so the proxy URL and secret reference
// are identical to what is already injected. Skip Component CR and ReleaseBinding updates to
// avoid triggering an unnecessary agent pod restart.

return rollbackResource{providerUUID: providerUUID}, nil
}
Expand Down Expand Up @@ -1422,7 +1402,7 @@ func (s *agentConfigurationService) Update(ctx context.Context, configUUID uuid.
} else {
// Scenario B: same provider — update proxy config and redeploy. No DB TX needed.
rbRes, err := s.processEnvProxyUpdate(
ctx, existingConfig, env, envUUID, envName, envMapping, existingMapping, orgName, existingVarNames, isExternalAgent, firstEnvName)
ctx, existingConfig, env, envUUID, envName, envMapping, existingMapping, orgName)
if err != nil {
s.rollbackProxies(ctx, rollbackResources, orgName)
return nil, err
Expand Down
70 changes: 70 additions & 0 deletions agent-manager-service/services/agent_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,20 @@ func (s *agentManagerService) CreateAgent(ctx context.Context, orgName string, p
}
}

// Create LLM configurations (applies to both internal and external agents)
if len(req.ModelConfig) > 0 {
if err := s.createAgentLLMConfigs(ctx, orgName, projectName, req); err != nil {
s.logger.Error("Failed to create LLM configurations for agent", "agentName", req.Name, "error", err)
if hasSecrets {
s.cleanupSecretsOnRollback(ctx, secretLocation)
}
if errDeletion := s.ocClient.DeleteComponent(ctx, orgName, projectName, req.Name); errDeletion != nil {
s.logger.Error("Failed to rollback agent after LLM config failure", "agentName", req.Name, "error", errDeletion)
}
return err
}
Comment on lines +671 to +682
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Roll back created LLM configs on every failure path.

Once createAgentLLMConfigs has created the first config, any later error here only removes the component and secrets. That leaves orphaned ${agent}-llm-config* records behind, so a retry can fail even though the component was rolled back. Please track the created config IDs and delete them in each rollback branch so create-agent stays atomic.

}

// For internal agents, enable instrumentation (if enabled) and trigger initial build
if req.Provisioning.Type == string(utils.InternalAgent) {
s.logger.Debug("Component created successfully", "agentName", req.Name)
Expand Down Expand Up @@ -744,6 +758,62 @@ func (s *agentManagerService) triggerInitialBuild(ctx context.Context, orgName,
return nil
}

func (s *agentManagerService) createAgentLLMConfigs(
ctx context.Context, orgName, projectName string, req *spec.CreateAgentRequest,
) error {
for i, mc := range req.ModelConfig {
configName := fmt.Sprintf("%s-llm-config", req.Name)
if len(req.ModelConfig) > 1 {
configName = fmt.Sprintf("%s-llm-config-%d", req.Name, i+1)
}
createReq := models.CreateAgentModelConfigRequest{
Name: configName,
Type: "llm",
EnvMappings: convertEnvMappings(mc.EnvMappings),
EnvironmentVariables: convertEnvVars(mc.EnvironmentVariables),
}
if _, err := s.agentConfigurationService.Create(ctx, orgName, projectName, req.Name, createReq, "system"); err != nil {
return fmt.Errorf("failed to create LLM configuration %d: %w", i+1, err)
}
}
return nil
}

func convertEnvMappings(specMappings map[string]spec.EnvModelConfigRequest) map[string]models.EnvModelConfigRequest {
result := make(map[string]models.EnvModelConfigRequest, len(specMappings))
for env, m := range specMappings {
policies := make([]models.LLMPolicy, 0, len(m.Configuration.Policies))
for _, p := range m.Configuration.Policies {
paths := make([]models.LLMPolicyPath, 0, len(p.Paths))
for _, pp := range p.Paths {
paths = append(paths, models.LLMPolicyPath{
Path: pp.Path,
Methods: pp.Methods,
Params: pp.Params,
})
}
policies = append(policies, models.LLMPolicy{
Name: p.Name,
Version: p.Version,
Paths: paths,
})
}
result[env] = models.EnvModelConfigRequest{
ProviderName: m.ProviderName,
Configuration: models.EnvProviderConfiguration{Policies: policies},
}
}
return result
}

func convertEnvVars(specVars []spec.EnvironmentVariableConfig) []models.EnvironmentVariableConfig {
result := make([]models.EnvironmentVariableConfig, 0, len(specVars))
for _, v := range specVars {
result = append(result, models.EnvironmentVariableConfig{Name: v.Name, Key: v.Key})
}
return result
}

// toCreateAgentRequestWithSecrets creates a component request, handling secrets by using secretKeyRef
func (s *agentManagerService) toCreateAgentRequestWithSecrets(req *spec.CreateAgentRequest) client.CreateComponentRequest {
result := client.CreateComponentRequest{
Expand Down
37 changes: 37 additions & 0 deletions agent-manager-service/spec/model_create_agent_request.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

153 changes: 153 additions & 0 deletions agent-manager-service/spec/model_model_config_request.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions console/common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading