docs(07-02): complete natural language search plan summary
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7db093c696
commit
712a0a39b8
1 changed files with 118 additions and 0 deletions
118
.planning/phases/07-research-agent-search/07-02-SUMMARY.md
Normal file
118
.planning/phases/07-research-agent-search/07-02-SUMMARY.md
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
---
|
||||||
|
phase: 07-research-agent-search
|
||||||
|
plan: "02"
|
||||||
|
subsystem: search
|
||||||
|
tags: [search, nlp, ai, frontend, go, react]
|
||||||
|
dependency_graph:
|
||||||
|
requires: [07-01]
|
||||||
|
provides: [search-endpoint, nl-search-ui]
|
||||||
|
affects: [DashboardPage, FilterBar, api-router]
|
||||||
|
tech_stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- NL query sanitize → LLM extraction → in-memory filter (no raw text to NetBox)
|
||||||
|
- TDD: failing tests written first, handler implemented to green
|
||||||
|
- Debounced NL input (400ms) with TanStack Query (enabled guard on length > 2)
|
||||||
|
key_files:
|
||||||
|
created:
|
||||||
|
- internal/api/handlers/search.go
|
||||||
|
- internal/api/handlers/search_test.go
|
||||||
|
modified:
|
||||||
|
- internal/api/router.go
|
||||||
|
- cmd/hwlab/main.go
|
||||||
|
- web/src/lib/api.ts
|
||||||
|
- web/src/components/inventory/FilterBar.tsx
|
||||||
|
- web/src/pages/DashboardPage.tsx
|
||||||
|
decisions:
|
||||||
|
- Used SearchNetBoxClient and SearchAIClient narrow interfaces in search.go for testability without importing ai package in tests
|
||||||
|
- extractJSON helper strips markdown code fences from LLM response before JSON parse
|
||||||
|
- NL search row placed below existing filter row (additive, not replacing) to keep local filter intact
|
||||||
|
- displayLoading/displayItems derived state avoids duplicating isLoading/searchLoading logic
|
||||||
|
metrics:
|
||||||
|
duration: "~12 minutes"
|
||||||
|
completed: "2026-04-10"
|
||||||
|
tasks_completed: 2
|
||||||
|
files_changed: 7
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 07 Plan 02: Natural Language Search Summary
|
||||||
|
|
||||||
|
Natural language inventory search via Tier 1 LLM (Gemma 4): user query sanitized, translated to structured filter params (catalog_status + name_contains), applied in-memory against ListDevices(200), returned as InventoryItem JSON; dashboard gains a debounced NL search input with volt accent styling and fallback to substring match on LLM parse failure.
|
||||||
|
|
||||||
|
## Tasks Completed
|
||||||
|
|
||||||
|
| Task | Name | Commit | Files |
|
||||||
|
|------|------|--------|-------|
|
||||||
|
| 1 | SearchHandler — NL query to NetBox filter | 9db7707 | search.go, search_test.go, router.go, main.go |
|
||||||
|
| 2 | Frontend NL search bar + api.ts wiring | 7db093c | api.ts, FilterBar.tsx, DashboardPage.tsx |
|
||||||
|
|
||||||
|
## What Was Built
|
||||||
|
|
||||||
|
### Backend (Task 1)
|
||||||
|
|
||||||
|
`internal/api/handlers/search.go` provides `SearchHandler` with `SearchDevices(w, r)`:
|
||||||
|
|
||||||
|
1. Validates `q` param — returns 400 if empty
|
||||||
|
2. Sanitizes query: strips non-printable chars, truncates to 200 runes (T-07-05)
|
||||||
|
3. Calls `tier1.TextComplete` with an extraction prompt requesting `{"catalog_status","name_contains","tag"}` JSON
|
||||||
|
4. Calls `extractJSON` to strip markdown fences before parse (LLM sometimes wraps in code blocks)
|
||||||
|
5. On parse failure: logs warning, falls back to `NameContains = rawQuery` (never 500)
|
||||||
|
6. `tag` field logged as not implemented and ignored (MVP)
|
||||||
|
7. Fetches `ListDevices(ctx, 200)`, applies in-memory filter, encodes result
|
||||||
|
|
||||||
|
Router wired: `GET /api/search` with nil-guard 503 fallback. `main.go` constructs `handlers.NewSearchHandler(nbClient, tier1)` and passes to `NewRouter`.
|
||||||
|
|
||||||
|
TDD: 4 tests written before implementation — `TestSearch_EmptyQ`, `TestSearch_LLMParseFallback`, `TestSearch_CatalogStatusFilter`, `TestSearch_NameContainsAndStatus` — all pass.
|
||||||
|
|
||||||
|
### Frontend (Task 2)
|
||||||
|
|
||||||
|
`web/src/lib/api.ts`: `fetchSearch(q)` added — calls `GET /api/search?q=<encoded>`.
|
||||||
|
|
||||||
|
`FilterBar.tsx` restructured to two rows:
|
||||||
|
- Row 1: existing local text search + status select + item count + view toggle (unchanged)
|
||||||
|
- Row 2: NL input with `Sparkles` icon (volt/60 tint), `border-volt/20` → `focus:border-volt`, `Loader2` spinner when `nlSearchLoading`
|
||||||
|
- 400ms debounce via `useEffect + setTimeout` pattern; local `nlInputValue` state, propagates via `onNlQueryChange`
|
||||||
|
|
||||||
|
`DashboardPage.tsx`:
|
||||||
|
- `nlQuery` state + `useQuery({ queryKey: ['search', nlQuery], enabled: nlQuery.trim().length > 2 })`
|
||||||
|
- `displayItems = nlQuery > 2 ? searchResults : filtered` — local filter fully preserved when NL empty
|
||||||
|
- Loading/empty state messages adapt to NL vs local mode
|
||||||
|
|
||||||
|
## Threat Mitigations Applied
|
||||||
|
|
||||||
|
| Threat | Mitigation |
|
||||||
|
|--------|-----------|
|
||||||
|
| T-07-05 | `sanitizeQuery`: non-printable chars stripped, truncated to 200 runes |
|
||||||
|
| T-07-06 | Only `catalog_status`, `name_contains`, `tag` extracted; unknown keys ignored; fallback on parse failure |
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-added: extractJSON helper
|
||||||
|
|
||||||
|
**Found during:** Task 1 implementation
|
||||||
|
**Issue:** LLMs commonly wrap JSON responses in markdown code fences (` ```json ... ``` `), which breaks `json.Unmarshal` directly
|
||||||
|
**Fix:** Added `extractJSON(s string) string` that finds first `{` and last `}` to extract the JSON object before parsing
|
||||||
|
**Files modified:** internal/api/handlers/search.go
|
||||||
|
**Rule:** Rule 2 — missing critical functionality (robustness of LLM output parsing)
|
||||||
|
|
||||||
|
No other deviations — plan executed as specified.
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
None — all data paths are wired end-to-end.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
None — no new network endpoints or trust boundaries beyond what the plan specified.
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
- internal/api/handlers/search.go: exists
|
||||||
|
- internal/api/handlers/search_test.go: exists, 4 tests pass
|
||||||
|
- internal/api/router.go: GET /api/search wired
|
||||||
|
- web/src/lib/api.ts: fetchSearch exported
|
||||||
|
- web/src/pages/DashboardPage.tsx: nlQuery state + useQuery present
|
||||||
|
- web/src/components/inventory/FilterBar.tsx: NL input with debounce present
|
||||||
|
- go build ./...: clean
|
||||||
|
- npm run build: clean (built in 3.14s)
|
||||||
|
- Commits 9db7707 and 7db093c: verified in git log
|
||||||
Loading…
Add table
Reference in a new issue