docs(02-01): complete AI package foundation plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8c03780230
commit
3eed2e9c63
1 changed files with 152 additions and 0 deletions
152
.planning/phases/02-ai-pipeline/02-01-SUMMARY.md
Normal file
152
.planning/phases/02-ai-pipeline/02-01-SUMMARY.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
---
|
||||
phase: 02-ai-pipeline
|
||||
plan: "01"
|
||||
subsystem: ai
|
||||
tags: [go, ai, openai, netbox, tdd, config]
|
||||
dependency_graph:
|
||||
requires: [internal/netbox.Client, internal/config.Config]
|
||||
provides: [internal/ai.AIClient, internal/ai.TierClient, internal/ai.MockAIClient, internal/netbox.CreateDevice]
|
||||
affects: [internal/config.Config, cmd/hwlab]
|
||||
tech_stack:
|
||||
added: [github.com/sashabaranov/go-openai v1.41.2]
|
||||
patterns: [interface-with-mock, tier-routing-via-baseurl, viper-merge-config, oneOf-FK-helpers]
|
||||
key_files:
|
||||
created:
|
||||
- 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
|
||||
modified:
|
||||
- go.mod
|
||||
- go.sum
|
||||
- internal/netbox/client.go
|
||||
- internal/netbox/client_test.go
|
||||
- internal/config/config.go
|
||||
- internal/config/config_test.go
|
||||
- .gitignore
|
||||
decisions:
|
||||
- id: AI-CLIENT-01
|
||||
summary: "AIClient is an interface so TierClient and MockAIClient are interchangeable — all downstream plans depend on this contract, not the concrete type"
|
||||
- id: AI-CLIENT-02
|
||||
summary: "TierClient tier-routing via openai.DefaultConfig + BaseURL override — single go-openai client factory, swap endpoint per tier from config"
|
||||
- id: AI-CONFIG-01
|
||||
summary: "ai_config.json merged via separate viper instance to avoid SetConfigName collision with primary config.json"
|
||||
- id: NB-CREATE-01
|
||||
summary: "go-netbox v4 CreateDevice uses Int32AsDeviceBayTemplateRequestDeviceType / Int32AsDeviceWithConfigContextRequest{Role,Site} oneOf helpers — plain int32 not accepted by constructor"
|
||||
- id: SEC-01
|
||||
summary: "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)"
|
||||
metrics:
|
||||
duration: "~4 minutes"
|
||||
completed: "2026-04-10T05:45:16Z"
|
||||
tasks_completed: 2
|
||||
files_created: 6
|
||||
files_modified: 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
|
||||
Loading…
Add table
Reference in a new issue