Skip to content

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

Open
pk-zipstack wants to merge 18 commits intomainfrom
feature/include-adapter-name-in-errors
Open

UN-2105 [FEAT] Include adapter name in error messages#1825
pk-zipstack wants to merge 18 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
  • Post copyable unit tests in a comment
  • 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 improves error message quality by including user-facing adapter instance names (e.g., "Unstract Trial LLM") in SDK1 adapter error messages across all four adapter types (LLM, Embedding, VectorDB, X2Text). The approach is sound: _adapter_name is injected into the config dict by PlatformHelper._get_adapter_configuration(), extracted by each adapter's constructor, and stripped via Utils.strip_adapter_name() at all three index key hashing sites to maintain backward compatibility with existing document indexes.

Key observations:

  • LLM uses _get_adapter_info() returning "Name (provider)" format (e.g., "My LLM (azureopenai)"), but Embedding._get_adapter_info() calls self.adapter.get_name() instead of self.adapter.get_provider(), which may produce a different style of parenthetical context than intended.
  • VectorDB and X2Text use _adapter_name or _adapter_instance_id (UUID) as the fallback without provider info — this is less rich than LLM/Embedding but is consistent with these adapters not exposing a get_provider() equivalent.
  • Utils.strip_adapter_name correctly handles None configs and is applied in all three index key generation sites (index.py, utils/indexing.py, prompt-service/index_v2.py).
  • _adapter_name is not initialized in __init__ for VectorDB and X2Text, relying instead on getattr(..., "") in exception handlers — a minor robustness concern.
  • The _adapter_name key is only injected in the non-public adapter path (_get_adapter_configuration); public adapter configs loaded from environment variables will never have a name, so those errors will always fall back to UUID/provider.

Confidence Score: 4/5

  • This PR is safe to merge — the logic is sound, index key backward compatibility is preserved, and the only issues are minor inconsistencies in error message format.
  • The core mechanism is well-designed and correctly implemented: _adapter_name is injected in one place, popped in all four adapter constructors, and stripped at all three index key hashing sites. No functional regressions were found. A point is deducted because: (1) Embedding._get_adapter_info() calls get_name() instead of get_provider() as used by LLM, creating subtle inconsistency in the parenthetical context; (2) _adapter_name is not declared in __init__ for VectorDB/X2Text, leaving the class relying on getattr guards in exception handlers; and (3) the previous threads noted additional formatting inconsistencies in VectorDB/X2Text error messages that remain unresolved.
  • Pay close attention to embedding.py (get_name() vs get_provider() inconsistency) and vector_db.py/x2txt.py (uninitialized _adapter_name instance variable).

Important Files Changed

Filename Overview
unstract/sdk1/src/unstract/sdk1/platform.py Stores adapter name under a private _adapter_name key in the config dict instead of discarding it via pop(). Clean change with a clear comment explaining the key's lifecycle.
unstract/sdk1/src/unstract/sdk1/llm.py Adds _get_adapter_info() helper returning "Name (provider)" format. Error messages updated in complete(), stream_complete(), and acomplete(). The previous thread noted provider context is correctly included in all three methods.
unstract/sdk1/src/unstract/sdk1/embedding.py Adds _get_adapter_info() using adapter.get_name() (not get_provider() as in LLM), creating a subtle inconsistency in what appears in the parenthetical context of error messages.
unstract/sdk1/src/unstract/sdk1/vector_db.py Pops _adapter_name from config and uses it in error messages. Previous threads noted quote inconsistency and missing provider context compared to LLM/Embedding.
unstract/sdk1/src/unstract/sdk1/x2txt.py Mirrors vector_db.py pattern. Same quote inconsistency noted in previous threads. Uses getattr defensively in exception handler for cases where the assignment hasn't happened yet.
unstract/sdk1/src/unstract/sdk1/utils/common.py Adds strip_adapter_name(*configs) utility that pops _adapter_name in-place. Correctly guards against None configs. Clean, well-documented static method.
unstract/sdk1/src/unstract/sdk1/index.py Two changes: (1) AdapterError catch now uses _adapter_name or x2text_instance_id UUID as fallback — inconsistency vs. x2txt.py noted in previous thread; (2) generate_index_key correctly strips adapter name before hashing.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[PlatformHelper.get_adapter_config] --> B{Public adapter?}
    B -- Yes --> C[Load config from env vars\nNo display name added]
    B -- No --> D[Fetch from platform service\nStore display name in config dict]

    D --> E[Adapter Constructor\nLLM / Embedding / VectorDB / X2Text]
    C --> E

    E --> F[Pop display name from config\nStore as self._adapter_name]
    F --> G[Initialize adapter with\nremaining config]

    G --> H{Error occurs?}
    H -- Yes --> I[Build adapter_info string\nName + provider or UUID fallback]
    I --> J[Raise error with\nhuman-readable adapter identity]

    G --> K[generate_index_key called]
    K --> L[Fetch fresh configs\nfrom platform service]
    L --> M[Utils.strip_adapter_name\nRemoves display name key in-place]
    M --> N[Hash configs for\nbackward-compatible index key]
Loading

Comments Outside Diff (2)

  1. unstract/sdk1/src/unstract/sdk1/embedding.py, line 103-108 (link)

    _get_adapter_info uses get_name() instead of get_provider()

    LLM._get_adapter_info() calls self.adapter.get_provider() (e.g., "azureopenai"), while Embedding._get_adapter_info() calls self.adapter.get_name(). These methods return different things — get_name() typically returns the adapter's module/class name (e.g., "litellm-embedding"), while get_provider() returns the upstream vendor name.

    This means error messages for embedding failures will have a different style of parenthetical context than LLM errors, undermining the consistency goal of the PR. Consider aligning Embedding._get_adapter_info() to call self.adapter.get_provider() (if it exists on embedding adapters) or at minimum document why get_name() is intentionally used here.

  2. unstract/sdk1/src/unstract/sdk1/vector_db.py, line 83-84 (link)

    _adapter_name not initialised in __init__

    _adapter_name is only assigned inside _get_vector_db(). If any code path accesses self._adapter_name before _get_vector_db() is called (or if a subclass overrides initialization), it will raise an AttributeError rather than the safe empty-string default that the getattr(..., "") guard in the exception handler is designed to protect against.

    Declaring it in __init__ with a sensible default makes the class contract explicit and avoids relying on getattr as a workaround:

    # In __init__
    self._adapter_name: str = ""

    The same pattern applies to x2txt.py's X2Text.__init__.

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

Comment:
**`_get_adapter_info` uses `get_name()` instead of `get_provider()`**

`LLM._get_adapter_info()` calls `self.adapter.get_provider()` (e.g., `"azureopenai"`), while `Embedding._get_adapter_info()` calls `self.adapter.get_name()`. These methods return different things — `get_name()` typically returns the adapter's module/class name (e.g., `"litellm-embedding"`), while `get_provider()` returns the upstream vendor name.

This means error messages for embedding failures will have a different style of parenthetical context than LLM errors, undermining the consistency goal of the PR. Consider aligning `Embedding._get_adapter_info()` to call `self.adapter.get_provider()` (if it exists on embedding adapters) or at minimum document why `get_name()` is intentionally used here.

```suggestion
    def _get_adapter_info(self) -> str:
        """Build a display string identifying this adapter for errors."""
        name = self.adapter.get_provider()
        if self._adapter_name:
            return f"{self._adapter_name} ({name})"
        return name
```

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

---

This is a comment left during a code review.
Path: unstract/sdk1/src/unstract/sdk1/vector_db.py
Line: 83-84

Comment:
**`_adapter_name` not initialised in `__init__`**

`_adapter_name` is only assigned inside `_get_vector_db()`. If any code path accesses `self._adapter_name` before `_get_vector_db()` is called (or if a subclass overrides initialization), it will raise an `AttributeError` rather than the safe empty-string default that the `getattr(..., "")` guard in the exception handler is designed to protect against.

Declaring it in `__init__` with a sensible default makes the class contract explicit and avoids relying on `getattr` as a workaround:

```python
# In __init__
self._adapter_name: str = ""
```

The same pattern applies to `x2txt.py`'s `X2Text.__init__`.

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

Last reviewed commit: 68fb787

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

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