feat(content): LLM-backed "Why this?" explainer for recommendations#586
Merged
Conversation
Adds an on-demand LLM rationale for each content recommendation card, cached per (project, target_ref, prompt_version) so repeat clicks are free. The heuristic classifier still produces the structured recommendation; this layer explains the reasoning and suggests concrete next steps in natural language. Backend: - New `recommendation_explanations` table + migration v62 (idx on project_id + targetRef + promptVersion unique). - `RecommendationExplanationDto` + `RecommendationExplainRequest` schemas in @ainyc/canonry-contracts. - `GET /projects/:name/content/recommendations/:targetRef/analysis` cache-only read (404 when no cached explanation exists). - `POST /projects/:name/content/recommendations/:targetRef/analyze` returns cached row or invokes injected `ExplainContentRecommendationFn`; supports `provider` / `model` / `forceRefresh` overrides. LLM wiring (api-routes stays LLM-agnostic): - `ExplainContentRecommendationFn` is dependency-injected via `ApiRoutesOptions.explainContentRecommendation`. Missing implementation → 503 PROVIDER_ERROR with operator-friendly message. - `packages/canonry` ships the pi-ai implementation (`createRecommendationExplainer`) using the new `analyze` capability tier from PR #585 — Claude → sonnet-4-6, OpenAI → gpt-5-mini, Gemini → 2.5-flash, Zai → glm-5-turbo. - Provider selection mirrors Aero: caller override → first-configured by priority → 502 PROVIDER_ERROR. Unknown override → 400. - pi-ai dollar cost converted to `costMillicents` (×100,000, rounded int) so totals stay drift-free. Tests: 29 new (route handlers + helper unit tests with mocked `complete()` — provider selection, override validation, cache hit/miss, forceRefresh overwrite, cost conversion, empty-response guard). Closes phase 1 of the LLM-augmented recommendation engine. Frontend "Why this?" panel ships in a follow-up PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 1 of the LLM-augmented recommendation engine. The heuristic classifier still produces the structured recommendation; this layer adds an on-demand LLM rationale for each content recommendation card that explains why it surfaced and what to do about it — cached per
(project, target_ref, prompt_version)so repeat clicks are free.GET /content/recommendations/:targetRef/analysis(cache-only read) +POST .../analyze(cached or fresh LLM call, withprovider/model/forceRefreshoverrides).api-routes— the route accepts an injectedExplainContentRecommendationFn. Canonry wires the pi-ai implementation using the newanalyzecapability tier from feat(agent): LLM capability tiers — per-provider model selection per task #585, so each provider picks an appropriately cheap-but-capable model (Claude → sonnet-4-6, OpenAI → gpt-5-mini, Gemini → 2.5-flash, Zai → glm-5-turbo).Frontend "Why this?" UI ships in a follow-up PR (see roadmap below).
Architecture notes
Cache key is
(projectId, targetRef, promptVersion). BumpingRECOMMENDATION_EXPLAIN_PROMPT_VERSIONinvalidates forward without touching the table.Test plan
pnpm -r typecheck— cleanpnpm run lint— 0 errors (101 pre-existing warnings, none in new files)npx vitest run— 259 files, 3114 passed (+29 new tests covering route handlers + helper unit tests with mockedcomplete(): provider selection, override validation, cache hit/miss, forceRefresh overwrite, dollar→millicent conversion, empty-response guard, missing-explainer 503 path)GET/POSTendpoints + DTOs flow through@ainyc/canonry-api-clientRoadmap (follow-ups)
Builds on #585 (capability tiers).
🤖 Generated with Claude Code