feat: make internal AI optional and add capabilities endpoint#113
Open
jansitarski wants to merge 5 commits into
Open
feat: make internal AI optional and add capabilities endpoint#113jansitarski wants to merge 5 commits into
jansitarski wants to merge 5 commits into
Conversation
…abilities Add AI_INTERNAL_ENABLED (master, default true) plus AI_VISION_ENABLED and AI_TEXT_ENABLED, which inherit the master when unset; effective flags resolve to vision = internal and vision, text = internal and text, with a false master forcing both off. Expose the effective state at GET /api/v1/capabilities so an external agent can decide whether to drive tagging/suggestions/pairings itself, and degrade GET /health/ai to "disabled" instead of probing endpoints when AI is off. Defaults preserve current behavior, so existing deployments are unaffected.
Add AIDisabledError and require_internal_ai(capability), and guard both get_ai_service() and AIService.__init__ so a client is never constructed against absent configuration. Per-capability call sites guard with require_internal_ai; the constructor check is the backstop. Booting with internal AI off and no AI_BASE_URL/AI_API_KEY/models set now succeeds cleanly.
Enforce the switches everywhere the internal model is reached, so no client is
built and no provider is called when a capability is off:
- recommendation and pairing services guard text first, so deferral is
unconditional rather than shadowed by location/weather/item validation;
- the tagging worker skips when vision is off and leaves the item ready
(untagged, usable) instead of error;
- the worker skips AI init and the health probe at startup when AI is off;
- the scheduled-notification worker treats AIDisabledError as a clean skip
rather than a retried failure;
- POST /outfits/suggest and POST /pairings/generate/{id} map AIDisabledError to
a typed 503 ("deferred to an external agent") instead of a 500 or a hang.
Cover the flag-resolution truth table; the disabled guard at get_ai_service, AIService.__init__, and require_internal_ai (including vision-off/text-on isolation); the tagging worker skip and startup skip; the /capabilities and /health/ai endpoints on and off; and the typed 503 for suggest/pairings. Also assert generate_recommendation and generate_pairings defer up front (before location/item validation), locking the guard ordering.
The AI_INTERNAL_ENABLED / AI_VISION_ENABLED / AI_TEXT_ENABLED switches were documented in .env.example and the README but never passed to the containers, so the documented "disable internal AI" path was inert under both docker compose and Kubernetes. Forward them to the backend and worker in docker-compose.yml and docker-compose.prod.yml (defaulting to on), and add them to the k8s ConfigMap and the backend/worker Deployments as optional refs so existing ConfigMaps without the keys still start. Defaults preserve current behavior.
10 tasks
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.
Description
Wardrowbe produces item tagging, outfit suggestions, and pairings through its internal LLM, with no way to turn that off or supply those results externally. This is PR 1 of 4 that lets the backend run with internal AI disabled and defer that work to an external agent (e.g. an MCP server such as saya6k/mcp-wardrowbe or jansitarski/wardrowbe-mcp) while every endpoint keeps working. The design is provider-agnostic. Everything is additive and defaults to internal AI on, so existing deployments are unaffected.
What this PR does
AI_INTERNAL_ENABLED(master, defaulttrue) plusAI_VISION_ENABLED/AI_TEXT_ENABLED, which inherit the master when unset (afalsemaster forces both off). Wired through.env.example, both compose files, and the k8s manifests so the switch actually reaches the containers.require_internal_ai(...)raises before any client is constructed;get_ai_service()andAIService.__init__are backstops. With AI off the app boots cleanly with noAI_BASE_URL/AI_API_KEY/models set.ready(untagged); the worker skips AI init at startup; scheduled notifications skip cleanly;POST /outfits/suggestandPOST /pairings/generate/{id}return a typed503("deferred to an external agent") instead of a 500 or a hang.GET /api/v1/capabilitiesreports the effective state (public, no user data);GET /health/aidegrades todisabledwhen off.{ "ai": { "vision": false, "text": false }, "features": { "external_tagging": true, "external_suggestions": true, "external_pairings": true }, "version": "1.0.0" }Roadmap — this is PR 1 of 4
Each PR is additive, independently reviewable, and defaults to current behavior:
GET /capabilities.tagging_status/tagged_by/tagged_atstate, anauto_tagingest flag plus the enqueue-site guard (so vision-off uploads are never queued), a?tagging_status=pendingwork queue, and write-back/retag with server-derived agent identity.503with well-formed deferred responses, and add agent-authoring for suggestions and pairings (source=agent).Related Issue
Related to #99 (Built-in MCP server support). This series exposes the API surface any external MCP needs; it does not add a built-in server.
Type of Change
Checklist
Testing
Full backend suite — 300 tests, all passing against Postgres + Redis. Coverage includes the flag-resolution truth table; the guards (per-capability, the vision-off/text-on isolation case, and the
__init__backstop); the tagging worker skip and startup skip;/capabilitiesand/health/aion and off; the typed503for suggest/pairings; and fail-fast ordering (generate_recommendation/generate_pairingsdefer before any location/item validation).The two background-removal tests are excluded — they require the optional
rembgdependency and are unrelated to this change.Test Environment
Tests Performed
AI_INTERNAL_ENABLED=false): no AI client is constructed; uploads landready(untagged);POST /outfits/suggestandPOST /pairings/generate/{id}return the deferred503;GET /api/v1/capabilitiesreportsai.vision/ai.textasfalse.Additional Notes
versionis hardcoded"1.0.0"(matching the existing FastAPI app version), kept hardcoded to avoid pulling release-please version wiring into this PR.featuresflags are statictrue: they describe the external-agent surface the series delivers; the dedicatedsource=agentauthoring paths land in PRs 2–3.require_internal_ai("vision")is implemented and tested but currently enforced through the worker's direct check; it's the hook PR 2 uses at the enqueue site.