UN-2896 [MISC] Deprecate and remove the LLMWhisperer V1 adapter - #2260
UN-2896 [MISC] Deprecate and remove the LLMWhisperer V1 adapter#2260Deepak-Kesavan wants to merge 4 commits into
Conversation
Adds a single DEPRECATED_ADAPTERS registry that drives every guard, seeded with LLMWhisperer V1, and deletes the V1 adapter package it retires. Guards (adapter-type agnostic, so future deprecations are a one-line entry): - excluded from supported_adapters, so it cannot be picked for creation - POST /adapter/ and /test_adapters/ reject a deprecated adapter_id - profile manager rejects pointing a profile at one; existing profiles on a deprecated adapter stay editable in their other fields - platform-service rejects execution off the is_available column, which is every SDK adapter lookup's single choke point Backfill migration marks existing V1 instances unavailable across all orgs (0003 used .first(), which marked only one row per adapter). Removes the V1 package, its icon, its dead env vars (POLL_INTERVAL, MAX_POLLS, STATUS_RETRIES -- V2 uses WAIT_TIMEOUT/MAX_RETRIES/RETRY_*) and the workflow-execution plumbing that forwarded them into tool containers. Claude-Session: https://claude.ai/code/session_01DXuiGyUwXyU1EVQBeMHppe
- platform-service: the adapter_instance route's blanket `except Exception` re-wrapped every APIError as a 500, so both the new deprecation error and the pre-existing "not found" reported as server errors and logged a traceback. Re-raise APIError untouched, as the neighbouring route does. - Move the adapter_id check into AdapterInstanceSerializer.validate: adapter_id is writable, so update/partial_update could set a deprecated id that create rejected. - Default profile creation and the project-import warning gated on is_usable alone, letting a deprecated default land in a new profile without passing through the serializer. - set_default_triad accepted a deprecated adapter as a user default straight from the API. - New is_adapter_selectable() states the rule once: usable, available, and not deprecated. - DefaultTriad: disable deprecated options instead of dropping them. Filtering emptied adapterList for an org whose adapters are all deprecated, which gated the effect that loads the current defaults, and left the current default rendering as a bare UUID. Claude-Session: https://claude.ai/code/session_01DXuiGyUwXyU1EVQBeMHppe
|
| Filename | Overview |
|---|---|
| backend/adapter_processor_v2/deprecated_adapters.py | Introduces the central deprecated-adapter registry, metadata helpers, and shared selectability predicate. |
| backend/adapter_processor_v2/adapter_processor.py | Excludes deprecated adapters from creation surfaces and validates default-triad selections. |
| backend/adapter_processor_v2/serializers.py | Rejects deprecated adapter IDs on writes and consistently exposes deprecation state in API representations. |
| backend/adapter_processor_v2/migrations/0007_deprecate_llmwhisperer_v1.py | Marks all existing LLMWhisperer V1 instances unavailable with frozen deprecation metadata. |
| backend/prompt_studio/prompt_profile_manager_v2/serializers.py | Prevents profiles from being newly pointed at unavailable or deprecated adapters while allowing unchanged legacy selections. |
| platform-service/src/unstract/platform_service/helper/adapter_instance.py | Rejects execution of unavailable adapter instances at the shared database lookup boundary. |
| platform-service/src/unstract/platform_service/controller/platform.py | Preserves intentional API error status codes and messages from adapter lookup failures. |
| frontend/src/helpers/GetStaticData.js | Adds a shared helper for excluding deprecated adapters from selectable and configured-adapter sets. |
| workers/executor/executors/legacy_executor.py | Updates legacy execution behavior to align with removal of the V1 adapter and its runtime configuration. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
R[Deprecated adapter registry] --> O[Supported-adapter listing and schema]
R --> C[Create and test validation]
R --> S[Profile and default selection]
M[Data migration marks V1 unavailable] --> E[Platform-service execution gate]
O --> B[Deprecated adapter cannot be offered]
C --> B2[Deprecated adapter cannot be created or tested]
S --> B3[Deprecated adapter cannot be newly selected]
E --> B4[Deprecated adapter cannot execute]
M --> V[Existing instance remains visible as deprecated]
Reviews (3): Last reviewed commit: "Merge branch 'main' into UN-2896-remove-..." | Re-trigger Greptile
Matches the repo's existing convention for this rule in data migrations.
Code reviewFound 1 issue:
unstract/backend/adapter_processor_v2/adapter_processor.py Lines 213 to 232 in c7624ae The same PR already solves this exact hazard in the sibling serializer, which skips unchanged values so "a profile already on a deprecated adapter stays editable in its other fields": It also contradicts unstract/backend/adapter_processor_v2/deprecated_adapters.py Lines 46 to 50 in c7624ae Frontend submitting all four values regardless of which one changed: unstract/frontend/src/components/settings/default-triad/DefaultTriad.jsx Lines 134 to 148 in c7624ae 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
chandrasekharan-zipstack
left a comment
There was a problem hiding this comment.
@Deepak-Kesavan have we also discussed on whether we will rename the LLMW v2 adapter since this v1 will no longer be visible?
There was a problem hiding this comment.
@Deepak-Kesavan remember to raise a cloud PR for these
Critical Issues (2 found)
File: adapter_processor.py, set_default_triad method pk-zipstack's review comment is correct and this is not yet fixed in the PR. The frontend submits all four defaults on every save. When migration 0007 marks a user's X2TEXT default as deprecated, attempting to change any The ProfileManagerSerializer.validate() correctly handles this with: But set_default_triad blindly validates every submitted value. Fix: compare each submitted PK against the currently stored default and skip unchanged ones: if default_triad.get(AdapterKeys.LLM_DEFAULT, None): This also violates the is_adapter_selectable() docstring which explicitly states "Existing selections are not re-validated."
File: serializers.py, _add_deprecation_info() rep[AdapterKeys.IS_DEPRECATED] = not is_available An adapter with is_available=False for non-deprecation reasons (e.g., manually disabled, usage-exhausted) will be flagged as IS_DEPRECATED=True in API responses. This is semantically wrong and could confuse frontend logic Fix: use the actual deprecation check: Important Issues (3 found)
File: deprecated_adapters.py, get_deprecation_message() The message format is: The name appears twice and "deprecated" + "retired" are redundant. Consider simplifying to just return the reason field directly, or restructuring the message template.
File: serializers.py, AdapterInstanceSerializer.to_representation() except Exception as e: This should use a module-level logger. Every other module in the PR declares logger = logging.getLogger(name) at the top. The serializers module lacks this. Add it at module level and remove the inline import.
File: platform-service/.../adapter_instance.py The platform-service rejection message is: Unlike the Django side, it doesn't mention the specific replacement (LLMWhisperer V2). Since platform-service can't access the Django registry, consider storing the replacement name in deprecation_metadata (which migration Suggestions (4 found)
AdapterProcessor.get_icon() returns AdapterKeys.UNAVAILABLE_ICON ("
The test suite thoroughly covers the registry, schema rejection, and serializer validation, but doesn't test the set_default_triad path. Add a test that sets up a UserDefaultAdapter with a deprecated adapter as one
is_adapter_selectable checks adapter.is_usable and adapter.is_available, but _FakeAdapter.init always sets these. Consider adding a case where the adapter object exists but one of these attributes is None to verify the
As noted in the PR review, the three removed env vars (ADAPTER_LLMW_POLL_INTERVAL, ADAPTER_LLMW_MAX_POLLS, ADAPTER_LLMW_STATUS_RETRIES) still exist in the unstract-cloud Helm charts. A companion PR should be filed. |
Frontend Lint Report (Biome)✅ All checks passed! No linting or formatting issues found. |
|
Unstract test resultsPer-group results
Critical paths
|



What
Retires the LLMWhisperer V1 text extractor and adds the machinery that makes retiring an adapter actually stick.
DEPRECATED_ADAPTERSregistry (backend/adapter_processor_v2/deprecated_adapters.py) — a single source that drives every guard, seeded with LLMWhisperer V1 only.Why
UN-2896 was closed once and reopened — V1 was still present in 0.173.1. The reason it came back is that the previous attempt had no enforcement:
is_available/deprecation_metadataand the frontend deprecated-badge UI already landed in #1677, but nothing stopped a deprecated adapter from being offered, created, selected, or executed. This PR wires up the half that was missing, then removes the adapter it retires.The existing seeding also had a latent bug: migration
0003used.filter(...).first(), so it marked at most one row per adapter id — every other org's instance stayedis_available=True.How
A registry entry is the whole deprecation. Four guards read it, all adapter-type agnostic, so LLM / embedding / vector-DB deprecations later are a one-line entry with no new code:
adapter_processor.get_all_supported_adapters+get_json_schemaAdapterInstanceSerializer.validate(coverscreateandupdate/partial_update, sinceadapter_idis writable) +AdapterViewSet.testProfileManagerSerializer.validate,set_default_triad, default-profile creationplatform-serviceget_adapter_instance_from_db— the single choke point every SDK adapter lookup passes throughis_adapter_selectable()states the rule once: usable, available, and not deprecated.Deliberately still visible: Settings › Adapters keeps listing deprecated instances (badged, edit/share disabled) so users can find and delete them. Only the four selection surfaces filter them. Editing a profile already on a deprecated adapter still works —
validateskips unchanged values — so users aren't locked out of their other fields.Also fixed while in here: the
adapter_instanceroute's blanketexcept Exceptionre-wrapped everyAPIErroras a 500, so both the new deprecation error and the pre-existing "adapter not found" surfaced as server errors with a logged traceback. Now re-raised untouched, matching the neighbouring route.Can this PR break any existing features. If yes, please list possible items. If no, please explain why.
Intended, breaking by design: an org still using LLMWhisperer V1 can no longer run it. That is the ticket. The failure is now a clear message ("LLMWhisperer has been deprecated. Please switch to the LLMWhisperer V2 text extractor") instead of an opaque SDK registry miss. Existing V1 adapter rows, profiles and workflows are not deleted — they stay visible so users can see what to migrate.
Checked and not broken:
is_available=False. The new execution gate keys off that column, so it was worth confirming nothing legitimate is caught. All five ids seeded by migration0003(noOpLlm,noOpEmbedding,palm×2,qdrantfastembed) are already absent from SDK1 — verified against the live registry. ThenoOpadapters still shipping (noOpX2text,noOpVectorDb) have different ids and are not in that list.is_availablecolumn predates this PR ([FEAT] - Handle Deprecate Adapters Not Supported In SDK1 #1677) and ships in the same release train as platform-service, so the newSELECTcannot hit a schema without it.llmwhisperer-clientstays insdk1/pyproject.toml— V2 imports it asunstract.llmwhisperer.client_v2.POLL_INTERVAL/MAX_POLLS/STATUS_RETRIESwere read only by V1; V2 usesWAIT_TIMEOUT/MAX_RETRIES/RETRY_MIN_WAIT/RETRY_MAX_WAIT, all untouched.STATUS_RETRIEShad no consumer anywhere.adapterOptions()already re-appends a currently-selected adapter missing from the list as a disabled option, so a profile on V1 renders its name, greyed out — not a bare UUID.Database Migrations
adapter_processor_v2/0007_deprecate_llmwhisperer_v1.py— data migration, reversible. Marks all LLMWhisperer V1 instancesis_available=Falsewith deprecation metadata, using.update()across every org. No schema change.Env Config
Removed (V1-only, now unread):
backend/sample.env—ADAPTER_LLMW_POLL_INTERVAL,ADAPTER_LLMW_MAX_POLLS,ADAPTER_LLMW_STATUS_RETRIESworkers/sample.env—ADAPTER_LLMW_POLL_INTERVAL,ADAPTER_LLMW_MAX_POLLSunstract/workflow-execution— dropped fromToolRuntimeVariableand from the vars forwarded into tool containersRelevant Docs
Related Issues or PRs
[FEAT] Handle Deprecate Adapters Not Supported In SDK1), which addedis_available/deprecation_metadataand the frontend badge but no enforcement.Dependencies Versions
No dependency changes.
llmwhisperer-client>=2.8.1retained for V2.Notes on Testing
Automated — new
backend/adapter_processor_v2/tests/test_deprecated_adapters.py(13 tests) asserts, for every registry entry, that it is absent from the SDK registry, excluded from the supported-adapter listing, refused a JSON schema, and rejected by the serializer; plus theis_adapter_selectabletruth table. The SDK-registry assertion is the regression guard that fails if V1 is ever re-added — the specific failure this ticket hit.adapter_processor_v2+prompt_studio: 137 passedworkers/tests/test_legacy_executor_extract.py: 18 passedManually verified the SDK registry now resolves only V2:
Not yet done: no dev-cluster deploy, so the UI surfaces (Default Triad disabled option, profile dropdowns, the deprecated badge on an actual V1 row) have not been exercised against a live org that has a V1 adapter configured. Worth a pass before merge if a test org can be pointed at one.
Screenshots
n/a — no new UI; existing deprecated-adapter styling from #1677 is reused.
Checklist
I have read and understood the Contribution Guidelines.