homelabby/.planning/phases/02-ai-pipeline/02-01-SUMMARY.md
Mikkel Georgsen 3eed2e9c63 docs(02-01): complete AI package foundation plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 05:46:05 +00:00

7.2 KiB

phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
02-ai-pipeline 01 ai
go
ai
openai
netbox
tdd
config
requires provides affects
internal/netbox.Client
internal/config.Config
internal/ai.AIClient
internal/ai.TierClient
internal/ai.MockAIClient
internal/netbox.CreateDevice
internal/config.Config
cmd/hwlab
added patterns
github.com/sashabaranov/go-openai v1.41.2
interface-with-mock
tier-routing-via-baseurl
viper-merge-config
oneOf-FK-helpers
created modified
internal/ai/types.go
internal/ai/client.go
internal/ai/mock.go
internal/ai/client_test.go
internal/ai/prompts/intake.go
ai_config.json
go.mod
go.sum
internal/netbox/client.go
internal/netbox/client_test.go
internal/config/config.go
internal/config/config_test.go
.gitignore
id summary
AI-CLIENT-01 AIClient is an interface so TierClient and MockAIClient are interchangeable — all downstream plans depend on this contract, not the concrete type
id summary
AI-CLIENT-02 TierClient tier-routing via openai.DefaultConfig + BaseURL override — single go-openai client factory, swap endpoint per tier from config
id summary
AI-CONFIG-01 ai_config.json merged via separate viper instance to avoid SetConfigName collision with primary config.json
id summary
NB-CREATE-01 go-netbox v4 CreateDevice uses Int32AsDeviceBayTemplateRequestDeviceType / Int32AsDeviceWithConfigContextRequest{Role,Site} oneOf helpers — plain int32 not accepted by constructor
id summary
SEC-01 ai_config.json committed as template with REPLACE_WITH_OPENROUTER_KEY placeholder; ai_config.local.json added to .gitignore for real keys (T-02-01)
duration completed tasks_completed files_created files_modified
~4 minutes 2026-04-10T05:45:16Z 2 6 7

Phase 2 Plan 01: AI Package Foundation Summary

One-liner: go-openai v1.41.2 installed; AIClient interface with TierClient (BaseURL tier-routing) and MockAIClient test double; intake prompt template; Config extended with AIConfig; NetBox CreateDevice added using go-netbox v4 oneOf FK helpers.

What Was Built

internal/ai/types.go

Domain types decoupled from go-openai:

  • IntakeRequest — 1-3 base64 photos + job UUID for tracing
  • IntakeResult — structured AI output (model, manufacturer, category, specs, confidence, etc.)
  • TierConfig — per-tier provider config (BaseURL, APIKey, Model, TimeoutSeconds)
  • AIConfig — orchestration config (Tier1, Tier2, ConfidenceThreshold, QuickAddEnabled, QuickAddThreshold)

internal/ai/client.go

  • AIClient interface with AnalyzePhotos(ctx, IntakeRequest) (*IntakeResult, error)
  • TierClient — production implementation wrapping go-openai; tier-routing via openai.DefaultConfig(key) + config.BaseURL; context.WithTimeout on every call (T-02-03 DoS mitigation)
  • JSON parse failure returns zero-confidence IntakeResult (not error) — orchestrator escalates

internal/ai/mock.go

  • MockAIClient — records all IntakeRequest calls in Calls slice; returns configurable FixedResult/FixedError
  • HighConfidenceResult() — Raspberry Pi 4 fixture at 0.95 confidence
  • LowConfidenceResult() — unknown device fixture at 0.40 confidence

internal/ai/prompts/intake.go

  • BuildIntakePrompt(photoCount int) — JSON-extraction prompt with exact schema, no markdown/fences instruction

internal/config/config.go

  • Config.AI ai.AIConfig field added with mapstructure:"ai" tag
  • Viper defaults for both tiers, confidence threshold, quick-add settings
  • Env bindings: HWLAB_AI_TIER1_*, HWLAB_AI_TIER2_*, HWLAB_AI_CONFIDENCE_THRESHOLD, HWLAB_AI_QUICK_ADD_ENABLED
  • Secondary viper instance merges ai_config.json as override without conflicting with primary config.json loading

ai_config.json

Template config file committed to repo with placeholder REPLACE_WITH_OPENROUTER_KEY for Tier2.

internal/netbox/client.go — CreateDevice + DeleteDevice

  • CreateDevice(ctx, name, assetTag, deviceTypeID, roleID, siteID int32) (int64, error) — uses Int32As* oneOf helpers required by go-netbox v4 constructor
  • DeleteDevice(ctx, id int64) error — for integration test cleanup

Test Results

Test Package Result
TestCreateDeviceValidation internal/netbox PASS
TestCreateDeviceLive internal/netbox SKIP (placeholder token)
TestMockAIClient internal/ai PASS
TestMockAIClientError internal/ai PASS
TestTierClientConstruction internal/ai PASS
TestAIConfigDefaults internal/config PASS
TestLoadDefaults internal/config PASS
TestLoadEnvOverride internal/config PASS
TestLoadNetBoxURL internal/config PASS

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] go-netbox v4 WritableDeviceWithConfigContextRequest constructor signature mismatch

  • Found during: Task 1 GREEN phase
  • Issue: Plan used nb.NewWritableDeviceWithConfigContextRequest(name, roleID, siteID, deviceTypeID) (4 args with name first). Actual go-netbox v4 signature is (deviceType DeviceBayTemplateRequestDeviceType, role DeviceWithConfigContextRequestRole, site DeviceWithConfigContextRequestSite) — 3 args with oneOf wrapper types, no name parameter.
  • Fix: Used Int32AsDeviceBayTemplateRequestDeviceType, Int32AsDeviceWithConfigContextRequestRole, Int32AsDeviceWithConfigContextRequestSite helpers; called req.SetName(name) separately; SetAssetTag takes plain string not NullableString.
  • Files modified: internal/netbox/client.go
  • Commit: 6040ecc

2. [Rule 2 - Security] ai_config.local.json added to .gitignore (T-02-01)

  • Found during: Task 2 — threat model review
  • Issue: T-02-01 requires gitignoring ai_config.json for real API keys, but the plan also requires committing the template. These are contradictory for the same filename.
  • Fix: Committed ai_config.json as template (placeholder key), added ai_config.local.json to .gitignore as the pattern for operator's real keys. Documented in file comment.
  • Files modified: .gitignore
  • Commit: 8c03780

Known Stubs

None — no UI rendering paths or data sources in this plan. All types are concrete and wired to real go-openai client or mock.

Threat Flags

Flag File Description
threat_flag: information_disclosure internal/ai/client.go TierClient logs no API keys (T-02-04 accepted) — verified: no log.Printf of TierConfig.APIKey

Self-Check

Files created:

  • internal/ai/types.go: FOUND
  • internal/ai/client.go: FOUND
  • internal/ai/mock.go: FOUND
  • internal/ai/client_test.go: FOUND
  • internal/ai/prompts/intake.go: FOUND
  • ai_config.json: FOUND

Commits:

  • 6040ecc: Task 1 (go-openai install + CreateDevice)
  • 8c03780: Task 2 (AI package + config extension)

go build ./...: PASS go test ./internal/ai/...: PASS (3/3) go test ./internal/config/...: PASS (4/4) go test ./internal/netbox/... -run TestCreateDevice: PASS (1 pass, 1 skip) grep sashabaranov/go-openai go.mod: FOUND v1.41.2 ls internal/ai/: types.go client.go mock.go client_test.go prompts/ ls internal/ai/prompts/: intake.go ls ai_config.json: FOUND

Self-Check: PASSED