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 7 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 centralized deprecation registry, metadata helpers, and adapter-selectability predicate. |
| backend/adapter_processor_v2/adapter_processor.py | Excludes deprecated adapters from discovery and validates changed default-triad selections while tolerating unchanged legacy references. |
| backend/adapter_processor_v2/migrations/0007_deprecate_llmwhisperer_v1.py | Marks all existing LLMWhisperer V1 instances unavailable with reversible deprecation metadata. |
| backend/adapter_processor_v2/serializers.py | Rejects deprecated adapter IDs on writes and consistently exposes effective deprecation state. |
| backend/prompt_studio/prompt_profile_manager_v2/serializers.py | Prevents profiles from being newly pointed to unavailable or registry-deprecated adapters. |
| platform-service/src/unstract/platform_service/helper/adapter_instance.py | Blocks execution of unavailable adapter instances at the shared database lookup boundary. |
| frontend/src/components/settings/default-triad/DefaultTriad.jsx | Keeps deprecated defaults visible while disabling them as selectable options. |
| unstract/sdk1/src/unstract/sdk1/adapters/x2text/llm_whisperer/src/llm_whisperer.py | Removes the retired LLMWhisperer V1 SDK adapter implementation. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
R[Deprecated adapter registry] --> L[Supported-adapter listing]
R --> C[Creation and test validation]
R --> P[Profile and default selection]
R --> M[Data migration]
M --> DB[(AdapterInstance is_available=false)]
DB --> E[Platform-service execution gate]
L -->|Excluded| U[User interfaces]
C -->|Rejected| X[Deprecated adapter request]
P -->|New selection rejected| X
E -->|Execution rejected with client error| X
DB -->|Existing rows retained| V[Visible with deprecated metadata]
Reviews (4): Last reviewed commit: "Merge remote-tracking branch 'origin/UN-..." | 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
There was a problem hiding this comment.
Raised: Zipstack/unstract-cloud#1760.
It drops ADAPTER_LLMW_POLL_INTERVAL, ADAPTER_LLMW_MAX_POLLS and ADAPTER_LLMW_STATUS_RETRIES from charts/unstract-platform/values.yaml and from docker (deprecated)/sample.backend.env. ADAPTER_LLMW_WAIT_TIMEOUT stays — V2 still resolves it, along with MAX_RETRIES, RETRY_MIN_WAIT and RETRY_MAX_WAIT.
Not a merge blocker for this PR either way: until that one lands the three values just sit in the pod env with no reader, and if it happens to merge first, V1's in-code defaults are identical to the values being removed.
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. |
The Default Triad UI submits all four defaults on every save, so validating every submitted value locked a user out of changing any default once one of theirs had been deprecated underneath them. Skip unchanged values, matching ProfileManagerSerializer.validate, and cover it with a regression test. Also drops the duplicated adapter name from the deprecation message and promotes the inline logger in serializers to module level.
The README still listed ADAPTER_LLMW_POLL_INTERVAL and ADAPTER_LLMW_MAX_POLLS, which only V1 ever read, and the WhispererEnv docstring quoted a 300s default where the code uses 900. Document the four vars V2 actually resolves.
…into UN-2896-remove-llmwhisperer-v1
Frontend Lint Report (Biome)✅ All checks passed! No linting or formatting issues found. |
|
Unstract test resultsPer-group results
Critical paths
|
|
@chandrasekharan-zipstack on the rename — not in this PR. It isn't in the ticket's scope and there's no real value in it, so let's keep it separate and decide on it later if we want to. For whenever we do pick it up, I checked what it would actually cost, and it's small:
The one argument against doing it at all: LLMWhisperer is externally versioned as a product — the v2 API, the |
|
@praveen-formido @harini-venkataraman thanks — the Fixed1.
Fixed by skipping values that match what's already stored, mirroring Covered by a new 3. Redundant deprecation message — fixed. It read "LLMWhisperer has been deprecated. LLMWhisperer V1 is retired…". 4. Inline 7. Missing Also found while re-checking this: the V2 adapter's README documented Not changing here2. 5. Platform-service message not naming the replacement. Reasonable, but it'd mean widening the SELECT to pull 6. 8. 9. Helm chart cleanup. Raised as Zipstack/unstract-cloud#1760. |
chandrasekharan-zipstack
left a comment
There was a problem hiding this comment.
I'd suggest to follow up the rename as a stacked PR on top of this if its low effort as you mentioned. The adapter names in certain parts would just say V2 and sound confusing - this rename might be an activity we'll do in the future anyway



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.