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