7.2 KiB
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-ai-pipeline | 01 | ai |
|
|
|
|
|
|
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 tracingIntakeResult— 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
AIClientinterface withAnalyzePhotos(ctx, IntakeRequest) (*IntakeResult, error)TierClient— production implementation wrappinggo-openai; tier-routing viaopenai.DefaultConfig(key) + config.BaseURL;context.WithTimeouton every call (T-02-03 DoS mitigation)- JSON parse failure returns zero-confidence
IntakeResult(not error) — orchestrator escalates
internal/ai/mock.go
MockAIClient— records allIntakeRequestcalls inCallsslice; returns configurableFixedResult/FixedErrorHighConfidenceResult()— Raspberry Pi 4 fixture at 0.95 confidenceLowConfidenceResult()— 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.AIConfigfield added withmapstructure:"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.jsonas override without conflicting with primaryconfig.jsonloading
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)— usesInt32As*oneOf helpers required by go-netbox v4 constructorDeleteDevice(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,Int32AsDeviceWithConfigContextRequestSitehelpers; calledreq.SetName(name)separately;SetAssetTagtakes plainstringnotNullableString. - 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.jsonas template (placeholder key), addedai_config.local.jsonto.gitignoreas 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:
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