-
Notifications
You must be signed in to change notification settings - Fork 614
[FIX] Add LLMCompat bridge class to fix retriever LLM compatibility with llama-index #1788
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0acab2b
5fc3ef8
dc57c53
0055521
0be9fb1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,223 @@ | ||
| # Investigation: `'LLM' object has no attribute 'predict'` | ||
|
|
||
| ## Error Context | ||
|
|
||
| ``` | ||
| ERROR: unstract.prompt_service.core.retrievers.keyword_table: | ||
| 'LLM' object has no attribute 'predict' | ||
|
|
||
| Traceback: | ||
| File "prompt_service/core/retrievers/keyword_table.py", line 48, in retrieve | ||
| keyword_index = KeywordTableIndex(...) | ||
| File "llama_index/core/indices/keyword_table/base.py", line 92, in __init__ | ||
| super().__init__(...) | ||
| File "llama_index/core/indices/base.py", line 79, in __init__ | ||
| index_struct = self.build_index_from_nodes(...) | ||
| File "llama_index/core/indices/keyword_table/base.py", line 182, in _build_index_from_nodes | ||
| response = self._llm.predict(...) | ||
| ``` | ||
|
|
||
| ## Root Cause | ||
|
|
||
| **Class type mismatch** between Unstract's SDK1 `LLM` and llama-index's `LLM`. | ||
|
|
||
| The prompt-service retrievers pass `unstract.sdk1.llm.LLM` (a plain Python class wrapping LiteLLM) to llama-index components that expect `llama_index.core.llms.llm.LLM` (a Pydantic model with `predict()`, `chat()`, `complete()`, etc.). | ||
|
|
||
| This is **NOT** a llama-index bug, **NOT** provider-specific, and **NOT** a version issue. | ||
|
|
||
| ## How We Got Here: SDK Migration | ||
|
|
||
| ### Old SDK (`unstract-sdk`) - Worked Correctly | ||
|
|
||
| ```python | ||
| # unstract-sdk/src/unstract/sdk/llm.py | ||
| from llama_index.core.llms import LLM as LlamaIndexLLM | ||
|
|
||
| class LLM: | ||
| def __init__(self, ...): | ||
| self._llm_instance: LlamaIndexLLM = None # Real llama-index LLM | ||
| self._initialise() | ||
|
|
||
| def _initialise(self): | ||
| self._llm_instance = self._get_llm(self._adapter_instance_id) | ||
|
|
||
| def _get_llm(self, adapter_instance_id) -> LlamaIndexLLM: | ||
| # Returns actual llama-index LLM from adapters via get_llm_instance() | ||
| llm_instance = self._llm_adapter_class.get_llm_instance() | ||
| return llm_instance | ||
| ``` | ||
|
|
||
| The old SDK obtained a real `llama_index.core.llms.LLM` instance from adapters. This instance had `predict()`, `chat()`, `complete()` through llama-index's class hierarchy: | ||
|
|
||
| ``` | ||
| BaseLLM -> LLM (has predict()) -> CustomLLM/FunctionCallingLLM -> Provider | ||
| ``` | ||
|
|
||
| ### New SDK1 (`unstract-sdk1`) - Breaks llama-index Integration | ||
|
|
||
| ```python | ||
| # unstract/sdk1/src/unstract/sdk1/llm.py | ||
| import litellm | ||
|
|
||
| class LLM: # Plain Python class, NOT llama-index's LLM | ||
| def complete(self, prompt, **kwargs): | ||
| response = litellm.completion(messages=messages, **completion_kwargs) | ||
| return {"response": response_object, ...} | ||
|
|
||
| def stream_complete(self, prompt, **kwargs): | ||
| for chunk in litellm.completion(messages=messages, stream=True, ...): | ||
| yield stream_response | ||
|
|
||
| async def acomplete(self, prompt, **kwargs): | ||
| response = await litellm.acompletion(messages=messages, ...) | ||
| return {"response": response_object} | ||
| ``` | ||
|
|
||
| SDK1's `LLM` calls `litellm.completion()` directly. It has **no inheritance** from any llama-index class, and therefore **no** `predict()`, `chat()`, or other llama-index LLM methods. | ||
|
|
||
| ## Where It Breaks | ||
|
|
||
| ### keyword_table.py (Confirmed) | ||
|
|
||
| ```python | ||
| # prompt-service/src/unstract/prompt_service/core/retrievers/keyword_table.py:48-52 | ||
| keyword_index = KeywordTableIndex( | ||
| nodes=[node.node for node in all_nodes], | ||
| show_progress=True, | ||
| llm=self.llm, # unstract.sdk1.llm.LLM, NOT llama_index.core.llms.llm.LLM | ||
| ) | ||
| ``` | ||
|
|
||
| llama-index's `KeywordTableIndex` stores this as `self._llm` and then calls: | ||
|
|
||
| ```python | ||
| # llama_index/core/indices/keyword_table/base.py:239 | ||
| response = self._llm.predict(self.keyword_extract_template, text=text) | ||
| ``` | ||
|
|
||
| Since `unstract.sdk1.llm.LLM` has no `predict()` method -> **AttributeError**. | ||
|
|
||
| ### The Two LLM Classes Side-by-Side | ||
|
|
||
| | Aspect | `llama_index.core.llms.llm.LLM` | `unstract.sdk1.llm.LLM` | | ||
| |--------|----------------------------------|--------------------------| | ||
| | Base class | `BaseLLM` (Pydantic model) | Plain Python `object` | | ||
| | `predict()` | Yes (line 589 of llm.py) | **No** | | ||
| | `chat()` | Yes (abstract, implemented by providers) | **No** | | ||
| | `complete()` | Yes (abstract, implemented by providers) | Yes, but different signature (returns dict, not `CompletionResponse`) | | ||
| | `stream_complete()` | Yes | Yes, but different signature | | ||
| | Used by | llama-index components internally | Unstract prompt-service for direct LLM calls | | ||
|
|
||
| ## All Affected Retrievers | ||
|
|
||
| The base retriever stores the SDK1 LLM: | ||
|
|
||
| ```python | ||
| # prompt-service/src/unstract/prompt_service/core/retrievers/base_retriever.py | ||
| from unstract.sdk1.llm import LLM | ||
|
|
||
| class BaseRetriever: | ||
| def __init__(self, ..., llm: LLM | None = None): | ||
| self.llm = llm if llm else None | ||
| ``` | ||
|
|
||
| All retrievers that pass `self.llm` to llama-index components are affected: | ||
|
|
||
| | Retriever | Passes SDK1 LLM to | Status | | ||
| |-----------|-------------------|--------| | ||
| | `KeywordTableRetriever` | `KeywordTableIndex(llm=self.llm)` | **Confirmed broken** (`'LLM' object has no attribute 'predict'`) | | ||
| | `SubquestionRetriever` | `as_query_engine(llm=self.llm)`, `SubQuestionQueryEngine.from_defaults(llm=self.llm)` | **Broken** (unexpected error, needs further investigation) | | ||
| | `FusionRetriever` | `QueryFusionRetriever(llm=self.llm)` | **Broken** (unexpected error, needs further investigation) | | ||
| | `RouterRetriever` | `LLMSingleSelector.from_defaults(llm=self.llm)`, `RouterQueryEngine.from_defaults(llm=self.llm)`, `as_query_engine(llm=self.llm)` | **Broken** (unexpected error, needs further investigation) | | ||
| | `SimpleRetriever` | Does NOT pass LLM to llama-index | **Works fine** | | ||
| | `AutomergingRetriever` | - | **Works fine** | | ||
| | `RecursiveRetrieval` | - | **Works fine** | | ||
|
|
||
| ## Online Research: llama-index GitHub Issues | ||
|
|
||
| The `predict` method has existed on `llama_index.core.llms.llm.LLM` since v0.10.x. We are on v0.13.2. Related GitHub issues confirm the pattern: | ||
|
|
||
| | Issue | Error | Root Cause | | ||
| |-------|-------|-----------| | ||
| | [#7093](https://github.com/run-llama/llama_index/issues/7093) | `HuggingFaceLLM has no attribute predict` | Wrong parameter name (`llm_predictor=` instead of `llm=`) | | ||
| | [#13958](https://github.com/run-llama/llama_index/issues/13958) | `AzureOpenAIMultiModal has no attribute predict` | MultiModal classes don't inherit from `LLM` | | ||
| | [#8906](https://github.com/run-llama/llama_index/issues/8906) | `AttributeError in LiteLLM` | Version compatibility issue between litellm and llama-index | | ||
|
|
||
| All confirm: the error occurs when a non-`llama_index.core.llms.llm.LLM` object is passed where llama-index expects one. | ||
|
|
||
| ## Proposed Fix Options | ||
|
|
||
| ### Option 1: Bridge Adapter (Recommended) | ||
|
|
||
| Create a class extending `llama_index.core.llms.CustomLLM` that delegates to SDK1's `LLM.complete()` internally. This makes it compatible with all llama-index components. | ||
|
|
||
| ```python | ||
| from llama_index.core.llms import CustomLLM, CompletionResponse, LLMMetadata | ||
|
|
||
| class SDK1LLMBridge(CustomLLM): | ||
| """Bridge between unstract.sdk1.llm.LLM and llama-index's LLM interface.""" | ||
|
|
||
| def __init__(self, sdk1_llm): | ||
| super().__init__() | ||
| self._sdk1_llm = sdk1_llm | ||
|
|
||
| @property | ||
| def metadata(self) -> LLMMetadata: | ||
| return LLMMetadata(model_name=self._sdk1_llm.get_model_name()) | ||
|
|
||
| def complete(self, prompt, **kwargs) -> CompletionResponse: | ||
| result = self._sdk1_llm.complete(prompt, **kwargs) | ||
| return CompletionResponse(text=result["response"].text) | ||
|
|
||
| def stream_complete(self, prompt, **kwargs): | ||
| for chunk in self._sdk1_llm.stream_complete(prompt, **kwargs): | ||
| yield CompletionResponse(text=chunk.text, delta=chunk.delta) | ||
| ``` | ||
|
|
||
| Then in retrievers, wrap before passing to llama-index: | ||
|
|
||
| ```python | ||
| from .llm_bridge import SDK1LLMBridge | ||
|
|
||
| llama_llm = SDK1LLMBridge(self.llm) | ||
| keyword_index = KeywordTableIndex(nodes=..., llm=llama_llm) | ||
| ``` | ||
|
Comment on lines
+150
to
+184
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Option 1 code example does not match the actual implementation. The example shows a single-layer bridge extending
If this document is retained, the code example should be updated to reflect the actual class hierarchy and naming so it doesn't mislead future readers. 🤖 Prompt for AI Agents |
||
|
|
||
| ### Option 2: Use `llama-index-llms-litellm` | ||
|
|
||
| Since SDK1 already has the LiteLLM model name and credentials, construct the official llama-index `LiteLLM` instance from those params: | ||
|
|
||
| ```python | ||
| from llama_index.llms.litellm import LiteLLM | ||
|
|
||
| llama_llm = LiteLLM(model=self.llm.get_model_name(), **self.llm.kwargs) | ||
| keyword_index = KeywordTableIndex(nodes=..., llm=llama_llm) | ||
| ``` | ||
|
|
||
| Requires adding `llama-index-llms-litellm` as a dependency. | ||
|
|
||
| ### Option 3: Quick Fix for KeywordTable Only | ||
|
|
||
| Use `SimpleKeywordTableIndex` instead of `KeywordTableIndex`. The "simple" variant uses regex-based keyword extraction and does NOT call `predict()`. | ||
|
|
||
| ```python | ||
| from llama_index.core.indices.keyword_table import SimpleKeywordTableIndex | ||
|
|
||
| keyword_index = SimpleKeywordTableIndex( | ||
| nodes=[node.node for node in all_nodes], | ||
| show_progress=True, | ||
| # No llm parameter needed | ||
| ) | ||
| ``` | ||
|
|
||
| **Limitation**: This only fixes `KeywordTableRetriever`. The other broken retrievers still need a solution. | ||
|
|
||
| ## Recommendation | ||
|
|
||
| **Option 1 (Bridge Adapter)** is recommended because: | ||
| - Fixes all affected retrievers with a single adapter class | ||
| - No new dependencies needed | ||
| - Keeps SDK1's LiteLLM-based architecture intact | ||
| - Can be applied at the `BaseRetriever` level so all retrievers benefit | ||
|
|
||
| The bridge could be created once in the base retriever or in a shared utility, so each retriever doesn't need to worry about the conversion. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| from collections.abc import Sequence | ||
| from typing import Any | ||
|
|
||
| from llama_index.core.base.llms.types import ( | ||
| ChatMessage, | ||
| ChatResponse, | ||
| ChatResponseAsyncGen, | ||
| ChatResponseGen, | ||
| CompletionResponse, | ||
| CompletionResponseAsyncGen, | ||
| CompletionResponseGen, | ||
| LLMMetadata, | ||
| MessageRole, | ||
| ) | ||
| from llama_index.core.llms.llm import LLM as LlamaIndexBaseLLM # noqa: N811 | ||
|
|
||
| from unstract.sdk1.llm import LLM, LLMCompat | ||
|
|
||
|
|
||
| class RetrieverLLM(LlamaIndexBaseLLM): | ||
| """Bridges SDK1's LLMCompat with llama-index's LLM for retriever use. | ||
|
|
||
| Llama-index's ``resolve_llm()`` asserts ``isinstance(llm, LLM)`` | ||
| where ``LLM`` is ``llama_index.core.llms.llm.LLM``. Since SDK1's | ||
| ``LLMCompat`` is a plain class without llama-index inheritance, | ||
| it fails this check. | ||
|
|
||
| ``RetrieverLLM`` inherits from llama-index's ``LLM`` base class | ||
| (passing the isinstance check) and delegates all LLM calls to an | ||
| internal ``LLMCompat`` instance. | ||
| """ | ||
|
|
||
| def __init__(self, llm: LLM, **kwargs: Any) -> None: # noqa: ANN401 | ||
| """Initialize with an SDK1 LLM instance.""" | ||
| super().__init__(**kwargs) | ||
| self._compat = LLMCompat( | ||
| adapter_id=llm._adapter_id, | ||
| adapter_metadata=llm._adapter_metadata, | ||
| adapter_instance_id=llm._adapter_instance_id, | ||
| tool=llm._tool, | ||
| usage_kwargs=llm._usage_kwargs, | ||
| capture_metrics=llm._capture_metrics, | ||
| ) | ||
|
Comment on lines
+33
to
+43
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n prompt-service/src/unstract/prompt_service/core/retrievers/retriever_llm.pyRepository: Zipstack/unstract Length of output: 5083 🏁 Script executed: # Check what LlamaIndexBaseLLM is and where it comes from
rg -n "class RetrieverLLM" -A 20 prompt-service/src/unstract/prompt_service/core/retrievers/retriever_llm.pyRepository: Zipstack/unstract Length of output: 1024 🏁 Script executed: # Check imports to see what LlamaIndexBaseLLM is
rg -n "LlamaIndexBaseLLM\|from.*LLM\|import.*LLM" prompt-service/src/unstract/prompt_service/core/retrievers/retriever_llm.pyRepository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Check if _compat is declared elsewhere in the class
rg -n "_compat" prompt-service/src/unstract/prompt_service/core/retrievers/retriever_llm.pyRepository: Zipstack/unstract Length of output: 447 🏁 Script executed: # Find llama-index LLM base class to see how it's configured
find . -path "*/site-packages/llama_index*" -name "*.py" -exec grep -l "class LLM" {} \; 2>/dev/null | head -5Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Check if there's a pyproject.toml or requirements to see llama-index version
fd -n "pyproject.toml|requirements.txt" prompt-service/ | head -5Repository: Zipstack/unstract Length of output: 291 🏁 Script executed: # Check if _compat attribute assignment works by looking for similar patterns in the codebase
rg -n "self\._[a-z_]+ = " --type=py | grep -v "test" | head -20Repository: Zipstack/unstract Length of output: 1970 🏁 Script executed: # Check if there are other Pydantic models in the codebase that set private attributes
rg -n "PrivateAttr\|model_config\|ConfigDict" --type=py | head -30Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Look for how llama-index is used in tests to understand if this pattern works
fd -n "test.*retriever" prompt-service/ | xargs cat 2>/dev/null | head -50Repository: Zipstack/unstract Length of output: 291 🏁 Script executed: # Check the requirements/pyproject to understand llama-index version
find prompt-service -name "pyproject.toml" -o -name "requirements*.txt" | xargs grep -i "llama" 2>/dev/nullRepository: Zipstack/unstract Length of output: 87 🏁 Script executed: # Look for how llama-index BaseModel is used elsewhere
rg "LlamaIndexBaseLLM|from llama_index" --type py -A 3 prompt-service/ | head -60Repository: Zipstack/unstract Length of output: 6123 🏁 Script executed: # Check if there are test files for RetrieverLLM
find prompt-service -type f -name "*test*retriever*" -o -name "*retriever*test*" | head -5Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Get the actual imports and check llama-index version in any lock files
find . -name "poetry.lock" -o -name "Pipfile.lock" -o -name "uv.lock" | xargs grep -A 5 'llama-index' 2>/dev/null | head -80Repository: Zipstack/unstract Length of output: 5650 🏁 Script executed: # Verify Pydantic handling by checking if llama-index LLM uses ConfigDict
python3 << 'EOF'
try:
from llama_index.core.llms.llm import LLM
import inspect
# Check if model_config exists
if hasattr(LLM, 'model_config'):
print("LLM has model_config:", LLM.model_config)
# Check base classes
print("LLM bases:", LLM.__bases__)
# Try to understand if it's a Pydantic model
if hasattr(LLM, '__fields__'):
print("Has __fields__ (Pydantic v1)")
if hasattr(LLM, 'model_fields'):
print("Has model_fields (Pydantic v2)")
# Check if we can set private attributes
test_instance = LLM()
test_instance._test_attr = "test"
print("Successfully set _test_attr on LLM instance")
print("_test_attr value:", test_instance._test_attr)
except Exception as e:
print(f"Error: {type(e).__name__}: {e}")
EOFRepository: Zipstack/unstract Length of output: 118 🏁 Script executed: # Check if the code actually runs by looking for any test files that use RetrieverLLM
find prompt-service -type f -name "*.py" -path "*/test*" -exec grep -l "RetrieverLLM" {} \;Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Check if there are integration tests or examples showing RetrieverLLM usage
rg "RetrieverLLM" --type py -B 2 -A 10Repository: Zipstack/unstract Length of output: 7975 🏁 Script executed: # Search for documentation about llama-index LLM Pydantic setup in comments/docstrings
rg -i "pydantic|privateattr|model_config" prompt-service/ --type py -B 2 -A 2Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Check the SDK1 LLM class to understand the private attributes being accessed
find . -path "*/unstract/sdk1/llm*" -type f | head -10Repository: Zipstack/unstract Length of output: 101 🏁 Script executed: # Look at what LLMCompat expects
rg "class LLMCompat" -A 30 --type pyRepository: Zipstack/unstract Length of output: 2505 🏁 Script executed: # Check if the noqa directives are actually suppressing violations
# Let's look for what ANN401 is
cat -n prompt-service/src/unstract/prompt_service/core/retrievers/retriever_llm.py | grep -E "(noqa|ANN401)" | head -20Repository: Zipstack/unstract Length of output: 592 🏁 Script executed: # Search for Pydantic PrivateAttr usage in the entire codebase to understand the pattern
rg "PrivateAttr" --type py -B 3 -A 3 | head -80Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Check the SDK1 LLM class definition to understand the private attributes
cat unstract/sdk1/src/unstract/sdk1/llm.py | head -100Repository: Zipstack/unstract Length of output: 2967 🏁 Script executed: # Look for any documentation or tests that verify RetrieverLLM works correctly
find prompt-service -type f -name "*.py" | xargs grep -l "_compat" 2>/dev/nullRepository: Zipstack/unstract Length of output: 136 🏁 Script executed: # Check if there's any test infrastructure that would validate RetrieverLLM works
find . -type f -name "*.py" -path "*/test*" -exec grep -l "llama_index\|RetrieverLLM" {} \; 2>/dev/null | head -10Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Check the actual SDK1 LLM class to see if those private attributes are really private
cat unstract/sdk1/src/unstract/sdk1/llm.py | grep -E "self\._adapter_id|self\._adapter_metadata|self\._adapter_instance_id|self\._tool|self\._usage_kwargs|self\._capture_metrics" | head -20Repository: Zipstack/unstract Length of output: 1199 🏁 Script executed: # Check if there are any __init__ definitions in SDK1 LLM that set these private attributes
rg "def __init__" unstract/sdk1/src/unstract/sdk1/llm.py -A 30 | head -60Repository: Zipstack/unstract Length of output: 2445 🏁 Script executed: # Verify: are the noqa directives in RetrieverLLM actually suppressing flake8-annotations violations?
# Check if removing them would trigger ANN401 warnings
python3 << 'EOF'
# ANN401 is from flake8-annotations and means: "Dynamically typed expressions (Any) are disallowed"
# When you use **kwargs: Any, you're using a dynamic type (Any) without specifying what the type should be
# So the noqa: ANN401 is suppressing this warning
# In the code:
# Line 33: def __init__(self, llm: LLM, **kwargs: Any) -> None: # noqa: ANN401
# The **kwargs: Any is the dynamic type expression being suppressed
# Lines 57, 72, 80, 88, 97, 112, 120, 128: All have **kwargs: Any # noqa: ANN401
# These are all legitimate uses of the noqa directive
# So the claim about "unused noqa directives" appears to be FALSE
# All 9 noqa: ANN401 directives are suppressing legitimate ANN401 violations
print("Analysis: All noqa: ANN401 directives are legitimate and not unused")
print("They are suppressing the ANN401 violation for '**kwargs: Any' type hints")
EOFRepository: Zipstack/unstract Length of output: 201 🏁 Script executed: # Let's check what the actual Pydantic configuration is in llama-index 0.13.2
# by looking for any Python documentation or checking if it's documented
web_search "llama-index 0.13.2 LLM Pydantic configuration private attributes"Repository: Zipstack/unstract Length of output: 111 🏁 Script executed: # Let me search online for llama-index 0.13.2 LLM implementation
curl -s "https://raw.githubusercontent.com/run-llama/llama_index/main/llama-index-core/llama_index/core/llms/llm.py" | head -150Repository: Zipstack/unstract Length of output: 4006 🏁 Script executed: # Alternative: check if there's any CI configuration or test runner that would validate this works
find . -type f \( -name "pytest.ini" -o -name "tox.ini" -o -name ".github" -o -name "conftest.py" \) | head -5Repository: Zipstack/unstract Length of output: 208 🏁 Script executed: # Let's look for any actual test files or CI config that tests RetrieverLLM
find . -path "*/.github/workflows/*.yml" -o -path "*/tests/*retriever*.py" 2>/dev/null | head -10Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Check if there are any runtime validation tests
find prompt-service -type f -name "*.py" | xargs grep -l "test.*retriever\|retriever.*test" 2>/dev/null | head -5Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Check if there's any evidence in tests or CI that validates this works
cat prompt-service/src/unstract/prompt_service/tests/conftest.py | head -50Repository: Zipstack/unstract Length of output: 817 🏁 Script executed: # Look for any actual instantiation or usage tests of RetrieverLLM
rg "RetrieverLLM\|get_llama_index_llm" prompt-service --type py -B 2 -A 5 | head -80Repository: Zipstack/unstract Length of output: 43 🏁 Script executed: # Verify the Pydantic configuration in llama-index by checking bridge
curl -s "https://raw.githubusercontent.com/run-llama/llama_index/main/llama-index-core/llama_index/core/bridge/pydantic.py" 2>/dev/null | head -50Repository: Zipstack/unstract Length of output: 1032 Declare While the current code functions in practice, 🔧 Suggested improvement+from pydantic import PrivateAttr
+
class RetrieverLLM(LlamaIndexBaseLLM):
+ _compat: LLMCompat = PrivateAttr()
+
def __init__(self, llm: LLM, **kwargs: Any) -> None: # noqa: ANN401
"""Initialize with an SDK1 LLM instance."""
super().__init__(**kwargs)
self._compat = LLMCompat(🧰 Tools🪛 Ruff (0.15.1)[warning] 33-33: Unused Remove unused (RUF100) 🤖 Prompt for AI Agents |
||
|
|
||
| @property | ||
| def metadata(self) -> LLMMetadata: | ||
| return LLMMetadata( | ||
| is_chat_model=True, | ||
| model_name=self._compat.get_model_name(), | ||
| ) | ||
|
|
||
| # ── Sync ───────────────────────────────────────────────────────────────── | ||
|
|
||
| def chat( | ||
| self, | ||
| messages: Sequence[ChatMessage], | ||
| **kwargs: Any, # noqa: ANN401 | ||
| ) -> ChatResponse: | ||
| result = self._compat.chat(messages, **kwargs) | ||
| return ChatResponse( | ||
| message=ChatMessage( | ||
| role=MessageRole.ASSISTANT, | ||
| content=result.message.content, | ||
| ), | ||
| raw=result.raw, | ||
| ) | ||
|
|
||
| def complete( | ||
| self, | ||
| prompt: str, | ||
| formatted: bool = False, | ||
| **kwargs: Any, # noqa: ANN401 | ||
| ) -> CompletionResponse: | ||
| result = self._compat.complete(prompt, formatted=formatted, **kwargs) | ||
| return CompletionResponse(text=result.text, raw=result.raw) | ||
|
|
||
| def stream_chat( | ||
| self, | ||
| messages: Sequence[ChatMessage], | ||
| **kwargs: Any, # noqa: ANN401 | ||
| ) -> ChatResponseGen: | ||
| raise NotImplementedError("stream_chat is not supported.") | ||
|
|
||
| def stream_complete( | ||
| self, | ||
| prompt: str, | ||
| formatted: bool = False, | ||
| **kwargs: Any, # noqa: ANN401 | ||
| ) -> CompletionResponseGen: | ||
| raise NotImplementedError("stream_complete is not supported.") | ||
|
|
||
| # ── Async ──────────────────────────────────────────────────────────────── | ||
|
|
||
| async def achat( | ||
| self, | ||
| messages: Sequence[ChatMessage], | ||
| **kwargs: Any, # noqa: ANN401 | ||
| ) -> ChatResponse: | ||
| result = await self._compat.achat(messages, **kwargs) | ||
| return ChatResponse( | ||
| message=ChatMessage( | ||
| role=MessageRole.ASSISTANT, | ||
| content=result.message.content, | ||
| ), | ||
| raw=result.raw, | ||
| ) | ||
|
|
||
| async def acomplete( | ||
| self, | ||
| prompt: str, | ||
| formatted: bool = False, | ||
| **kwargs: Any, # noqa: ANN401 | ||
| ) -> CompletionResponse: | ||
| result = await self._compat.acomplete(prompt, formatted=formatted, **kwargs) | ||
| return CompletionResponse(text=result.text, raw=result.raw) | ||
|
|
||
| async def astream_chat( | ||
| self, | ||
| messages: Sequence[ChatMessage], | ||
| **kwargs: Any, # noqa: ANN401 | ||
| ) -> ChatResponseAsyncGen: | ||
| raise NotImplementedError("astream_chat is not supported.") | ||
|
|
||
| async def astream_complete( | ||
| self, | ||
| prompt: str, | ||
| formatted: bool = False, | ||
| **kwargs: Any, # noqa: ANN401 | ||
| ) -> CompletionResponseAsyncGen: | ||
| raise NotImplementedError("astream_complete is not supported.") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing language specifiers on fenced code blocks (MD040).
The traceback block on Line 5 and the Python block on Line 52 both lack a language identifier, triggering markdownlint MD040 warnings.
📝 Proposed fix
Also applies to: 52-52
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 5-5: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents