Skip to content

UN-2105 [FEAT] Include adapter name in error messages#1825

Merged
chandrasekharan-zipstack merged 19 commits intomainfrom
feature/include-adapter-name-in-errors
Mar 19, 2026
Merged

UN-2105 [FEAT] Include adapter name in error messages#1825
chandrasekharan-zipstack merged 19 commits intomainfrom
feature/include-adapter-name-in-errors

Conversation

@pk-zipstack
Copy link
Contributor

@pk-zipstack pk-zipstack commented Mar 5, 2026

What

  • Preserve the user-facing adapter instance name (e.g., "Unstract Trial LLM") from the platform service response and include it in SDK1 error messages across LLM, Embedding, VectorDB, and X2Text adapter consumers.

Why

  • When an adapter error occurs during workflow execution, the error message only showed the provider name (e.g., "azureopenai") or a raw instance UUID, making it difficult for users to identify which configured adapter caused the failure.
  • This was reported in UN-2105.

How

  • In PlatformHelper._get_adapter_configuration(), the adapter name was already fetched from the platform service but immediately discarded via pop(). Now it is stored back in the config dict under a private key _adapter_name.
  • Each adapter consumer (LLM, Embedding, VectorDB, X2Text) extracts this name during init and includes it in error messages.
    • Before: Error from LLM provider 'azureopenai': ...
    • After: Error from LLM adapter 'Unstract Trial LLM' (azureopenai): ...
  • Index key generation sites (index.py, utils/indexing.py, prompt-service index_v2.py) strip the _adapter_name key before hashing to maintain backward compatibility with existing document index keys.

Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • No. The _adapter_name key is stripped before index key hashing, so existing indexed documents are unaffected. The only user-visible change is richer error messages that now include the adapter instance name alongside the provider name.

Database Migrations

  • None

Env Config

  • None

Relevant Docs

  • None

Related Issues or PRs

Dependencies Versions

  • None

Notes on Testing

  • Configure an adapter with a known name (e.g., "My OpenAI Adapter")
  • Trigger an adapter error (e.g., invalid credentials)
  • Verify the error message includes the adapter instance name

Screenshots

N/A

Checklist

I have read and understood the Contribution Guidelines.

Preserve the user-facing adapter name (e.g., "Unstract Trial LLM")
from the platform service response and include it in error messages
across SDK1 adapter consumers (LLM, Embedding, VectorDB, X2Text).

Previously, adapter names were discarded during config retrieval
and errors only showed provider names or instance IDs, making it
hard to identify which adapter caused the issue.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Introduce Common.ADAPTER_NAME and Utils.strip_adapter_name; platform stores adapter-name metadata; adapters capture adapter names for richer error context; indexing and key-generation fetch and sanitize adapter configs to exclude adapter-name metadata from generated index keys.

Changes

Cohort / File(s) Summary
Constants
unstract/sdk1/src/unstract/sdk1/constants.py
Add Common.ADAPTER_NAME = "_adapter_name" to carry adapter-name metadata in adapter configs.
Common utils
unstract/sdk1/src/unstract/sdk1/utils/common.py
Add Utils.strip_adapter_name(*configs) to remove Common.ADAPTER_NAME from provided config dicts prior to hashing/serialization.
Platform config storage
unstract/sdk1/src/unstract/sdk1/platform.py
Include adapter name under Common.ADAPTER_NAME in the adapter_data payload returned by _get_adapter_configuration.
Adapter classes — capture adapter name & error context
unstract/sdk1/src/unstract/sdk1/llm.py, unstract/sdk1/src/unstract/sdk1/embedding.py, unstract/sdk1/src/unstract/sdk1/vector_db.py, unstract/sdk1/src/unstract/sdk1/x2txt.py
Pop Common.ADAPTER_NAME from adapter configs (default ""), store on instance (_adapter_name), and use it (with fallbacks) in logs and raised error messages.
Indexing / key generation — sanitize adapter configs
unstract/sdk1/src/unstract/sdk1/index.py, unstract/sdk1/src/unstract/sdk1/utils/indexing.py, prompt-service/src/unstract/prompt_service/core/index_v2.py
Retrieve vector_db/embedding/x2text configs via PlatformHelper, call Utils.strip_adapter_name(...), and use sanitized configs when constructing index keys so adapter-name does not affect hashes.
Public import adjustments
unstract/sdk1/src/unstract/sdk1/index.py, prompt-service/src/unstract/prompt_service/core/index_v2.py
Add Utils to imports where needed to access strip_adapter_name (no public API signature changes).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature: including adapter name in error messages, directly matching the primary objective of this PR.
Description check ✅ Passed The description is comprehensive and well-structured, covering all required template sections including What, Why, How, backward compatibility assurance, related issue reference, and testing guidance.
Docstring Coverage ✅ Passed Docstring coverage is 86.36% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/include-adapter-name-in-errors
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
prompt-service/src/unstract/prompt_service/core/index_v2.py (1)

100-109: ⚠️ Potential issue | 🟠 Major

Pre-existing issue: file_hash may be undefined.

Note: This is not introduced by this PR, but file_hash is only assigned on line 81 when file_info.file_hash is falsy. When file_info.file_hash is truthy, the variable file_hash used on line 101 will be undefined, causing a NameError.

This should likely be:

file_hash = file_info.file_hash
if not file_hash:
    file_hash = fs.get_hash_from_file(path=file_info.file_path)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@prompt-service/src/unstract/prompt_service/core/index_v2.py` around lines 100
- 109, The variable file_hash can be undefined when file_info.file_hash is
truthy; update the assignment logic in the code that populates index_key so that
file_hash is always set from file_info.file_hash first (e.g., file_hash =
file_info.file_hash) and only call
fs.get_hash_from_file(path=file_info.file_path) when that value is falsy, then
use that ensured file_hash when building index_key (references: file_hash,
file_info, fs.get_hash_from_file, index_key, self.chunking_config.chunk_size/
chunk_overlap).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@unstract/sdk1/src/unstract/sdk1/embedding.py`:
- Around line 100-105: The async embedding error paths still call
parse_litellm_err with only provider values; update those calls to include the
adapter display string like the sync path does. Compute adapter_info using
self._adapter_name and self.adapter.get_name() (same logic as the shown
adapter_info variable) and pass adapter_info into parse_litellm_err instead of
the provider-only argument in all async error raises (search for
parse_litellm_err in this module, e.g., the async embedding functions around the
current raise and the other occurrences referenced). Ensure the raise statements
use "raise parse_litellm_err(e, adapter_info) from e" so adapter context is
included.

In `@unstract/sdk1/src/unstract/sdk1/llm.py`:
- Around line 266-273: The acomplete error path is building messages that only
include the provider name and drops the adapter instance name; update the error
construction in the acomplete handler to use the same adapter_info logic as
elsewhere (use self._adapter_name when present, formatted as
"'{self._adapter_name}' ({self.adapter.get_provider()})", else fall back to
"'{self.adapter.get_provider()}'") and use that adapter_info when composing
error_msg (same change should be applied to the other occurrence around lines
346-353) so async callers receive the adapter instance name in the error text.

---

Outside diff comments:
In `@prompt-service/src/unstract/prompt_service/core/index_v2.py`:
- Around line 100-109: The variable file_hash can be undefined when
file_info.file_hash is truthy; update the assignment logic in the code that
populates index_key so that file_hash is always set from file_info.file_hash
first (e.g., file_hash = file_info.file_hash) and only call
fs.get_hash_from_file(path=file_info.file_path) when that value is falsy, then
use that ensured file_hash when building index_key (references: file_hash,
file_info, fs.get_hash_from_file, index_key, self.chunking_config.chunk_size/
chunk_overlap).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ff0f61a0-7b62-4b96-ae66-eecf6097d69f

📥 Commits

Reviewing files that changed from the base of the PR and between e0c2af0 and 6b9dd9f.

📒 Files selected for processing (9)
  • prompt-service/src/unstract/prompt_service/core/index_v2.py
  • unstract/sdk1/src/unstract/sdk1/constants.py
  • unstract/sdk1/src/unstract/sdk1/embedding.py
  • unstract/sdk1/src/unstract/sdk1/index.py
  • unstract/sdk1/src/unstract/sdk1/llm.py
  • unstract/sdk1/src/unstract/sdk1/platform.py
  • unstract/sdk1/src/unstract/sdk1/utils/indexing.py
  • unstract/sdk1/src/unstract/sdk1/vector_db.py
  • unstract/sdk1/src/unstract/sdk1/x2txt.py

pk-zipstack and others added 3 commits March 12, 2026 09:36
Simplify adapter_info construction using or-chaining instead of
ternary conditionals to bring cognitive complexity back within the
SonarCloud limit of 15.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
unstract/sdk1/src/unstract/sdk1/embedding.py (1)

141-156: ⚠️ Potential issue | 🟠 Major

Async embedding errors still drop the adapter instance name.

Line 142 and Line 155 still pass provider-only values to parse_litellm_err(), so async callers miss the adapter-name context that the sync paths now include.

Proposed fix
         except Exception as e:
-            provider_name = f"{self.adapter.get_name()}"
-            raise parse_litellm_err(e, provider_name) from e
+            adapter_info = self._adapter_name or self.adapter.get_name()
+            raise parse_litellm_err(e, adapter_info) from e
@@
         except Exception as e:
-            provider_name = f"{self.adapter.get_name()}"
-            raise parse_litellm_err(e, provider_name) from e
+            adapter_info = self._adapter_name or self.adapter.get_name()
+            raise parse_litellm_err(e, adapter_info) from e
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@unstract/sdk1/src/unstract/sdk1/embedding.py` around lines 141 - 156, The
async method get_aembeddings constructs and passes only a provider string to
parse_litellm_err, losing the adapter-instance context; change its exception
handler to mirror the sync path by building the same adapter-aware identifier
used elsewhere (use the same expression the sync code uses to include the
adapter instance name) and pass that identifier into parse_litellm_err(e,
<adapter-aware-identifier>) instead of the current provider_name so async
callers receive the adapter name too.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@unstract/sdk1/src/unstract/sdk1/embedding.py`:
- Around line 141-156: The async method get_aembeddings constructs and passes
only a provider string to parse_litellm_err, losing the adapter-instance
context; change its exception handler to mirror the sync path by building the
same adapter-aware identifier used elsewhere (use the same expression the sync
code uses to include the adapter instance name) and pass that identifier into
parse_litellm_err(e, <adapter-aware-identifier>) instead of the current
provider_name so async callers receive the adapter name too.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6d1355c0-09a2-4691-9f8b-1307219df5a3

📥 Commits

Reviewing files that changed from the base of the PR and between 6b9dd9f and e1d4b1d.

📒 Files selected for processing (3)
  • unstract/sdk1/src/unstract/sdk1/embedding.py
  • unstract/sdk1/src/unstract/sdk1/index.py
  • unstract/sdk1/src/unstract/sdk1/llm.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • unstract/sdk1/src/unstract/sdk1/llm.py

pk-zipstack and others added 3 commits March 12, 2026 09:52
Apply same or-chaining simplification to LLM complete() and
stream_complete() error handlers to stay within SonarCloud's
cognitive complexity limit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
unstract/sdk1/src/unstract/sdk1/llm.py (1)

271-275: Consider extracting _adapter_info() helper and including provider context.

The error format shows either adapter name or provider, but the PR objectives suggest showing both: "Error from LLM adapter 'Unstract Trial LLM' (azureopenai): ...". This gives users the friendly name while retaining technical context for debugging.

Additionally, this logic is duplicated at lines 348 and 408. A helper method would improve consistency and maintainability.

♻️ Proposed refactor

Add a helper method to the class:

def _adapter_info(self) -> str:
    """Return formatted adapter identifier for error messages."""
    if self._adapter_name:
        return f"'{self._adapter_name}' ({self.adapter.get_provider()})"
    return f"'{self.adapter.get_provider()}'"

Then replace all three occurrences (lines 271-274, 348-351, 408-411):

-            adapter_info = self._adapter_name or self.adapter.get_provider()
-            error_msg = (
-                f"Error from LLM adapter '{adapter_info}': "
-                f"{strip_litellm_prefix(str(e))}"
-            )
+            error_msg = (
+                f"Error from LLM adapter {self._adapter_info()}: "
+                f"{strip_litellm_prefix(str(e))}"
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@unstract/sdk1/src/unstract/sdk1/llm.py` around lines 271 - 275, Extract a new
instance helper method named _adapter_info(self) that returns a formatted
identifier combining the friendly adapter name and provider, e.g. when
self._adapter_name exists return f"'{self._adapter_name}'
({self.adapter.get_provider()})" else return f"'{self.adapter.get_provider()}'";
then replace the duplicated logic that builds adapter_info (the blocks using
self._adapter_name and self.adapter.get_provider()) with calls to
this._adapter_info() in the LLM class so all error messages use the same
formatted string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@unstract/sdk1/src/unstract/sdk1/llm.py`:
- Around line 271-275: Extract a new instance helper method named
_adapter_info(self) that returns a formatted identifier combining the friendly
adapter name and provider, e.g. when self._adapter_name exists return
f"'{self._adapter_name}' ({self.adapter.get_provider()})" else return
f"'{self.adapter.get_provider()}'"; then replace the duplicated logic that
builds adapter_info (the blocks using self._adapter_name and
self.adapter.get_provider()) with calls to this._adapter_info() in the LLM class
so all error messages use the same formatted string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4f01a13-8eee-448d-92e6-286ea4f3abf8

📥 Commits

Reviewing files that changed from the base of the PR and between e1d4b1d and 7591cd4.

📒 Files selected for processing (1)
  • unstract/sdk1/src/unstract/sdk1/llm.py

Copy link
Contributor

@chandrasekharan-zipstack chandrasekharan-zipstack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for the most part, address the comment to reduce code duplication

pk-zipstack and others added 2 commits March 13, 2026 11:27
Move the repeated adapter-name stripping logic into
Utils.strip_adapter_name() in sdk1/utils/common.py, replacing
inline for-loops in index key generation across sdk1/index.py,
sdk1/utils/indexing.py, and prompt-service/core/index_v2.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR enriches adapter error messages by preserving the user-facing adapter instance name (e.g., "Unstract Trial LLM") from the platform service response and including it alongside the provider name in errors raised by LLM, Embedding, VectorDB, and X2Text adapter consumers. The core mechanism — storing adapter_name under a private _adapter_name key in the config dict, then popping it in each consumer's __init__ — is clean and well-contained. Three separate index-key generation sites (index.py, utils/indexing.py, index_v2.py) are consistently updated to strip _adapter_name before hashing, correctly preserving backward compatibility with existing indexed documents.

Key observations:

  • Utils.strip_adapter_name() correctly handles None configs and is idempotent for configs that never carried the key (e.g., public adapters loaded from env vars)
  • All three error-raising sites in llm.py (complete, stream_complete, acomplete) are updated consistently
  • vector_db.py imports Common from unstract.sdk1.constants (which carries ADAPTER_NAME), while x2txt.py and embedding.py correctly alias it as SdkCommon to avoid shadowing the adapters-level Common
  • x2txt.py's _get_x2text() does not raise an error when x2text_adapter_id is absent from the adapter registry — it silently returns None, leaving _x2text_instance unset and causing a later AttributeError that bypasses the AdapterError handler added in index.py. vector_db.py already raises SdkError in the equivalent code path; x2txt.py should match that behavior (see inline comment)

Confidence Score: 4/5

  • Safe to merge — the only user-visible change is richer error messages; index-key backward compatibility is correctly preserved by stripping the new key before hashing.
  • The implementation is consistent across all four adapter consumers and all three index-key generation sites. The one P1 issue (silent None return in _get_x2text for unsupported adapter IDs) is a pre-existing bug that the PR doesn't introduce or worsen, but the new error-handling code in index.py would silently fail to surface the enriched message for that path. The core feature works correctly for the common case.
  • unstract/sdk1/src/unstract/sdk1/x2txt.py — the unsupported-adapter-ID path silently returns None instead of raising a structured error.

Important Files Changed

Filename Overview
unstract/sdk1/src/unstract/sdk1/platform.py Stores the previously-discarded adapter_name back into the config dict under the private key _adapter_name (from Common.ADAPTER_NAME). The key is added after adapter_name and adapter_type are popped, so existing callers that relied on those fields being absent are unaffected. Clean, minimal change.
unstract/sdk1/src/unstract/sdk1/llm.py Extracts _adapter_name via pop() from the fetched config and adds _get_adapter_info() that combines _adapter_name with get_provider(). All three error-raising sites (complete, stream_complete, acomplete) consistently use this helper.
unstract/sdk1/src/unstract/sdk1/embedding.py Adds _get_adapter_info() mirroring the LLM helper, but uses adapter.get_name() instead of adapter.get_provider(). All three embedding call sites use the helper consistently. The LLM vs Embedding inconsistency (get_provider vs get_name) was already flagged in a prior thread.
unstract/sdk1/src/unstract/sdk1/vector_db.py Pops _adapter_name from the config and uses it in both the stream log and the raised VectorDBError. The getattr(self, "_adapter_name", "") guard correctly handles the case where the attribute was not yet set when an early exception fires. Quote-style inconsistency vs LLM/Embedding was already noted in a prior thread.
unstract/sdk1/src/unstract/sdk1/x2txt.py Pops _adapter_name early in _get_x2text() and uses it in error paths. Contains a pre-existing silent-None-return issue when x2text_adapter_id is not in the registry (see comment), though the PR's error-info extraction itself is correct.
unstract/sdk1/src/unstract/sdk1/index.py Two changes: (1) extract_text catches AdapterError and uses getattr(x2text, "_adapter_name", "") or x2text_instance_id as the fallback — correctly safe since _adapter_name may not be set on the instance if init failed early. (2) index-key generation fetches fresh configs, strips _adapter_name, then hashes — correct for backward compatibility.
unstract/sdk1/src/unstract/sdk1/utils/common.py Adds strip_adapter_name(*configs) to Utils. The if config: guard safely skips None entries, and pop(..., None) is idempotent when the key is absent (e.g., for public adapters whose configs don't carry _adapter_name).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[PlatformHelper._get_adapter_configuration] --> B[Add _adapter_name to config dict]
    B --> C{Consumer}

    C --> D[Adapter init path\nLLM / Embedding / VectorDB / X2Text]
    D --> E[pop _adapter_name from config\nstore as self._adapter_name]
    E --> F[Pass remaining config to\nunderlying adapter impl]
    F --> G{Error during operation?}
    G -->|Yes| H[_get_adapter_info builds\nName plus provider string]
    H --> I[Raise enriched error message]
    G -->|No| J[Normal operation]

    C --> K[Index key generation path\nindex.py / indexing.py / index_v2.py]
    K --> L[Fetch 3 fresh configs\nvector_db, embedding, x2text]
    L --> M[Utils.strip_adapter_name\nremoves _adapter_name from all 3]
    M --> N[Build index_key dict\nwithout _adapter_name]
    N --> O[Hash index_key\nbackward-compatible result]
Loading

Comments Outside Diff (1)

  1. unstract/sdk1/src/unstract/sdk1/x2txt.py, line 62-86 (link)

    Silent None return when adapter ID is not registered

    When x2text_adapter_id is not present in self._x2text_adapters, the function exits the try block normally — no exception is raised and no value is explicitly returned — so Python implicitly returns None. The except block at line 88 is never entered.

    Back in _initialise():

    self._x2text_instance = self._get_x2text()   # receives None

    A subsequent call to x2text.process() then calls self._x2text_instance.process(...) on None, producing an AttributeError. This AttributeError is not caught by the except AdapterError guard in index.py, so the richer error message introduced by this PR is never surfaced for this failure mode.

    vector_db.py already handles the equivalent case correctly by raising SdkError when the adapter ID is unsupported (line 93). The same pattern should be applied here:

    x2text_adapter_id = x2text_config.get(Common.ADAPTER_ID)
    if x2text_adapter_id not in self._x2text_adapters:
        raise X2TextError(f"X2Text adapter not supported: {x2text_adapter_id}")
    x2text_adapter = self._x2text_adapters[x2text_adapter_id][
        Common.METADATA
    ][Common.ADAPTER]

    This ensures the error is raised inside the try block, the except at line 88 picks it up, and the new adapter_info-enriched X2TextError message is emitted.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: unstract/sdk1/src/unstract/sdk1/x2txt.py
Line: 62-86

Comment:
**Silent `None` return when adapter ID is not registered**

When `x2text_adapter_id` is not present in `self._x2text_adapters`, the function exits the `try` block normally — no exception is raised and no value is explicitly returned — so Python implicitly returns `None`. The `except` block at line 88 is never entered.

Back in `_initialise()`:
```python
self._x2text_instance = self._get_x2text()   # receives None
```
A subsequent call to `x2text.process()` then calls `self._x2text_instance.process(...)` on `None`, producing an `AttributeError`. This `AttributeError` is not caught by the `except AdapterError` guard in `index.py`, so the richer error message introduced by this PR is never surfaced for this failure mode.

`vector_db.py` already handles the equivalent case correctly by raising `SdkError` when the adapter ID is unsupported (line 93). The same pattern should be applied here:

```python
x2text_adapter_id = x2text_config.get(Common.ADAPTER_ID)
if x2text_adapter_id not in self._x2text_adapters:
    raise X2TextError(f"X2Text adapter not supported: {x2text_adapter_id}")
x2text_adapter = self._x2text_adapters[x2text_adapter_id][
    Common.METADATA
][Common.ADAPTER]
```

This ensures the error is raised inside the `try` block, the `except` at line 88 picks it up, and the new `adapter_info`-enriched `X2TextError` message is emitted.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "Merge branch 'main' ..."

pk-zipstack and others added 2 commits March 13, 2026 12:51
- LLM: Extract _get_adapter_info() helper that preserves both
  adapter name and provider in errors (e.g. "Unstract Trial LLM
  (azureopenai)") instead of dropping the provider via or-chaining.
  Also keeps the conditional out of nested except blocks to avoid
  SonarCloud cognitive complexity issues.

- VectorDB/X2Text: Fix inconsistent quoting — move quotes into the
  template string so both named adapters and UUID fallbacks are
  quoted consistently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
unstract/sdk1/src/unstract/sdk1/vector_db.py (1)

90-90: Inconsistent constant import for ADAPTER_NAME.

This file uses Common.ADAPTER_NAME (imported from unstract.sdk1.constants at line 17), while x2txt.py and llm.py use SdkCommon.ADAPTER_NAME (an alias for the same import). Both resolve to the same constant, so functionality is correct, but consider using consistent aliasing across the codebase for maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@unstract/sdk1/src/unstract/sdk1/vector_db.py` at line 90, The file uses
Common.ADAPTER_NAME while other modules (x2txt.py and llm.py) use the SdkCommon
alias; update unstract/sdk1/vector_db.py to import the constants module with the
same alias used elsewhere (e.g., "from unstract.sdk1 import constants as
SdkCommon") and replace the usage self._adapter_name =
vector_db_config.pop(Common.ADAPTER_NAME, "") with the consistent
SdkCommon.ADAPTER_NAME symbol so imports and references match across the
codebase.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@unstract/sdk1/src/unstract/sdk1/vector_db.py`:
- Line 90: The file uses Common.ADAPTER_NAME while other modules (x2txt.py and
llm.py) use the SdkCommon alias; update unstract/sdk1/vector_db.py to import the
constants module with the same alias used elsewhere (e.g., "from unstract.sdk1
import constants as SdkCommon") and replace the usage self._adapter_name =
vector_db_config.pop(Common.ADAPTER_NAME, "") with the consistent
SdkCommon.ADAPTER_NAME symbol so imports and references match across the
codebase.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcb93c3b-5664-4c5e-9a2b-5aa81725df64

📥 Commits

Reviewing files that changed from the base of the PR and between cb09ec3 and 2f54dd2.

📒 Files selected for processing (3)
  • unstract/sdk1/src/unstract/sdk1/llm.py
  • unstract/sdk1/src/unstract/sdk1/vector_db.py
  • unstract/sdk1/src/unstract/sdk1/x2txt.py

pk-zipstack and others added 2 commits March 13, 2026 13:00
Add _get_adapter_info() helper to Embedding (mirroring LLM) so
errors show both adapter name and provider when available, e.g.
"My Embedding (openai)" instead of just one or the other.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pk-zipstack and others added 2 commits March 13, 2026 13:14
Fall back to x2text_instance_id (UUID) instead of the generic
adapter type name, matching the pattern used by VectorDB and
X2Text init error handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Contributor

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 63 passed, 0 failed (63 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{63}}$$ $$\textcolor{#23d18b}{\tt{63}}$$

@sonarqubecloud
Copy link

@chandrasekharan-zipstack chandrasekharan-zipstack merged commit a20d7a0 into main Mar 19, 2026
8 checks passed
@chandrasekharan-zipstack chandrasekharan-zipstack deleted the feature/include-adapter-name-in-errors branch March 19, 2026 08:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants