Skip to content

feat: typed AI configs, defaults, and root-level tools#9

Open
mattrmc1 wants to merge 5 commits into
mainfrom
mmccarthy/AIC-2927/go-sdk-aiconf-foundation
Open

feat: typed AI configs, defaults, and root-level tools#9
mattrmc1 wants to merge 5 commits into
mainfrom
mmccarthy/AIC-2927/go-sdk-aiconf-foundation

Conversation

@mattrmc1

@mattrmc1 mattrmc1 commented Jul 7, 2026

Copy link
Copy Markdown

Summary

Introduces typed config types (AICompletionConfig, AIAgentConfig, AIJudgeConfig) backed by a shared aiConfigBase, along with typed defaults, ToolConfig for root-level tool definitions, and a noop Evaluator stub. The existing Config type is preserved as a deprecated alias for backward compatibility.

Shared base via aiConfigBase

All three typed configs embed aiConfigBase, which holds common fields and exposes:

func (b *aiConfigBase) Key() string
func (b *aiConfigBase) Enabled() bool
func (b *aiConfigBase) Model() ModelConfig        // defensive copy
func (b *aiConfigBase) Provider() ProviderConfig
func (b *aiConfigBase) Tools() map[string]ToolConfig  // defensive copy
func (b *aiConfigBase) CreateTracker() *Tracker
func (b *aiConfigBase) Evaluator() *Evaluator     // never nil

Two new public types — ModelConfig (Name, Parameters, Custom) and ProviderConfig (Name) — replace the previous pattern of accessor methods like ModelName(), ProviderName(), ModelParam().

Typed config types

  • AICompletionConfig — embeds aiConfigBase, adds Messages(), JudgeConfiguration(), AsLdValue(). Retains deprecated accessors (VariationKey(), Version(), ModelName(), ProviderName(), ModelParam(), CustomModelParam(), Mode(), EvaluationMetricKey(), EvaluationMetricKeys()) for backward compatibility.
  • AIAgentConfig — embeds aiConfigBase, adds Instructions(), JudgeConfiguration().
  • AIJudgeConfig — embeds aiConfigBase, adds Messages(), EvaluationMetricKey().

Backward compatibility

// Deprecated: Use AICompletionConfig, AIAgentConfig, or AIJudgeConfig instead.
type Config = AICompletionConfig

ConfigBuilder is preserved with deprecation markers. Its Build() now constructs an AICompletionConfig internally. All existing callers of CompletionConfig() continue to work unchanged.

Typed defaults with immutable-copy builder

Three default types (AICompletionConfigDefault, AIAgentConfigDefault, AIJudgeConfigDefault) use value-receiver With* methods that return copies:

def := ldai.NewAIAgentConfigDefault().
    WithInstructions("You are a helpful agent").
    WithModelName("gpt-4o").
    WithModelParam("temperature", ldvalue.Float64(0.7)).
    WithTool(datamodel.Tool{Name: "search"}).
    WithJudgeConfiguration(jc)

All three defaults have WithModelParam and WithCustomModelParam. AIAgentConfigDefault additionally has WithTool and WithJudgeConfiguration. Each provides Disabled() and AsLdValue().

ToolConfig — root-level tool definitions

New ToolConfig type with Name(), Description(), Type(), Parameters(), CustomParameters() — all map accessors return defensive copies via maps.Clone. Backed by a new datamodel.Tool wire type with tools and instructions fields added to datamodel.Config.

model.parameters.tools[] (the LLM-passable array) is preserved verbatim — the raw datamodel.Config is kept on AICompletionConfig for AsLdValue() marshaling.

evaluateConfig rewrite

client.go's evaluateConfig no longer uses ConfigBuilder. It constructs AICompletionConfig directly, parsing root-level tools via toolConfigFromWire, cloning model params/custom via maps.Clone, and setting key on both the happy path and returnDefault.

Noop evaluator stub

Evaluator is a struct stub in evaluator.go. newNoopEvaluator() returns a non-nil instance so that Evaluator() on all typed configs is safe to call. Full evaluator implementation deferred to Task 05.

Test plan

  • go test -race ./... passes
  • AICompletionConfig: messages, model, provider, tools, key, enabled, tracker, evaluator all accessible
  • AIAgentConfig: instructions, judgeConfiguration accessible
  • AIJudgeConfig: messages, evaluationMetricKey accessible
  • Deprecated Config alias: existing CompletionConfig() call site works unchanged
  • Deprecated ConfigBuilder.Build() produces a valid AICompletionConfig
  • ToolConfig accessors return correct values; mutating returned maps does not affect config
  • model.parameters.tools[] preserved verbatim in AsLdValue() output
  • All three defaults: Disabled() produces enabled=false; AsLdValue() marshals correctly
  • WithModelParam/WithCustomModelParam present and functional on all three defaults
  • WithTool/WithJudgeConfiguration functional on AIAgentConfigDefault
  • Key() returns the flag key on configs from both evaluateConfig and returnDefault
  • Evaluator() returns non-nil noop evaluator on all typed configs

Note

Medium Risk
Public API surface and config evaluation paths change substantially, though backward compatibility is preserved via aliases and deprecated accessors; incorrect handling of tools vs model.parameters.tools could affect LLM payloads.

Overview
Replaces the single Config type with mode-specific AICompletionConfig, AIAgentConfig, and AIJudgeConfig, all embedding shared aiConfigBase (Key, Model/Provider structs, root Tools(), CreateTracker, non-nil Evaluator()). Config remains a deprecated type alias to AICompletionConfig; ConfigBuilder.Build() now produces AICompletionConfig with a retained raw wire payload for AsLdValue().

Adds immutable With* default types for completion, agent, and judge fallbacks, plus ToolConfig backed by new wire tools and instructions on datamodel.Config. evaluateConfig no longer uses ConfigBuilder—it builds AICompletionConfig directly, parses root tools via toolConfigFromWire, sets key on defaults, and keeps model.parameters.tools[] untouched in raw. A noop Evaluator stub is wired everywhere; judge’s Config interface assertion targets AICompletionConfig.

Reviewed by Cursor Bugbot for commit 1536ec7. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread ldai/ai_config_base.go Outdated
Comment thread ldai/ai_config_base.go Outdated
@mattrmc1 mattrmc1 changed the title feat: typed AI configs, ToolConfig, and typed defaults feat: typed AI configs, defaults, and root-level tools Jul 7, 2026
@mattrmc1 mattrmc1 marked this pull request as ready for review July 7, 2026 16:14
@mattrmc1 mattrmc1 requested review from a team as code owners July 7, 2026 16:14

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1536ec7. Configure here.

return 1
}
return c.version
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Version() conflates explicit zero with unset version

Medium Severity

Version() checks c.version == 0 to return 1, but the old code checked c.c.Meta.Version == nil. In evaluateConfig, a wire version of 0 (*int pointing to 0) is stored as version = 0, making it indistinguishable from "version not set." The old code correctly returned 0 for an explicit wire version of 0. This changes behavior for tracking events where version is emitted via the Tracker's trackData.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1536ec7. Configure here.

Comment thread ldai/client.go
// returnDefault sets a tracker factory on a copy of def (so CreateTracker always works) and
// returns the resulting Config. Used for all error-path returns in evaluateConfig.
func (c *Client) returnDefault(key string, context ldcontext.Context, def Config) Config {
def.key = key

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

returnDefault doesn't ensure non-nil evaluator

Low Severity

returnDefault sets key and trackerFactory on the default but does not set evaluator. The Evaluator() accessor documents "Never returns nil," but if a caller passes a directly constructed AICompletionConfig{} (without using Disabled() or Build()), the returned config will have a nil evaluator, violating the contract and risking nil-pointer panics downstream.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1536ec7. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant