Repository navigation
Sprint 4 [FIX] Backend fixes: UN-2900, UN-2902, UN-2953, UN-3038, UN-3133, UN-3176, UN-3333 #2257
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 1 commit
be9d774
da198d0
aa0df6f
aa4f086
f45d4e0
c830880
1f8a4a7
68eaec4
fc2962c
df75e37
07ed225
eb9ff6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…OCR threshold UN-2953: validate_adapter_permissions read adapter ids straight out of tool_meta and added them unconditionally, so a tool instance holding "" for an adapter id made the JSON schema validator compare "" against the UUID enum and raise. The error was logged but not handled, repeating every validation pass until the pod stopped answering health checks. Skips empty/missing ids and uses .get() so a missing key no longer raises KeyError. Also initialises adapter_id per iteration -- previously a disabled entry could re-add the previous loop's id. UN-3038: push_usage_details billed len(pdf.pages) for every PDF, ignoring the adapter's pages_to_extract range, so a 5-page extraction from a 100-page document was charged 100 pages. Narrows the count to the selected pages, handling ranges, open-ended ranges, overlaps and out-of-range values, and falling back to the full count when the setting is absent or unparseable so usage is never under-reported. UN-3333: adds word_confidence_threshold to the LLMWhisperer v2 adapter schema (number, default 0.3, 0.0-1.0) so it is configurable from the adapter UI. The parameter is implemented in the LLMWhisperer backend but was never exposed. Not added to the v1 schema, which predates the OCR tuning parameters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sXFBEu2GHatXq2CV2ShPF
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -486,33 +486,61 @@ def validate_adapter_permissions( | |
| adapter_ids: set[str] = set() | ||
|
|
||
| for llm in tool.properties.adapter.language_models: | ||
| adapter_id = None | ||
| if llm.is_enabled and llm.adapter_id: | ||
| adapter_id = tool_meta[llm.adapter_id] | ||
| adapter_id = tool_meta.get(llm.adapter_id) | ||
| elif llm.is_enabled: | ||
| adapter_id = tool_meta[AdapterPropertyKey.DEFAULT_LLM_ADAPTER_ID] | ||
| adapter_id = tool_meta.get( | ||
| AdapterPropertyKey.DEFAULT_LLM_ADAPTER_ID | ||
| ) | ||
|
|
||
| adapter_ids.add(adapter_id) | ||
| # UN-2953: a tool instance may carry "" for an adapter id. | ||
| # Adding it made the schema validator compare "" against the | ||
| # UUID enum and raise, once per validation pass. | ||
| if adapter_id: | ||
| adapter_ids.add(adapter_id) | ||
| for vdb in tool.properties.adapter.vector_stores: | ||
| adapter_id = None | ||
| if vdb.is_enabled and vdb.adapter_id: | ||
| adapter_id = tool_meta[vdb.adapter_id] | ||
| adapter_id = tool_meta.get(vdb.adapter_id) | ||
| elif vdb.is_enabled: | ||
| adapter_id = tool_meta[AdapterPropertyKey.DEFAULT_VECTOR_DB_ADAPTER_ID] | ||
| adapter_id = tool_meta.get( | ||
| AdapterPropertyKey.DEFAULT_VECTOR_DB_ADAPTER_ID | ||
| ) | ||
|
|
||
| adapter_ids.add(adapter_id) | ||
| # UN-2953: a tool instance may carry "" for an adapter id. | ||
| # Adding it made the schema validator compare "" against the | ||
| # UUID enum and raise, once per validation pass. | ||
| if adapter_id: | ||
| adapter_ids.add(adapter_id) | ||
| for embedding in tool.properties.adapter.embedding_services: | ||
| adapter_id = None | ||
| if embedding.is_enabled and embedding.adapter_id: | ||
| adapter_id = tool_meta[embedding.adapter_id] | ||
| adapter_id = tool_meta.get(embedding.adapter_id) | ||
| elif embedding.is_enabled: | ||
| adapter_id = tool_meta[AdapterPropertyKey.DEFAULT_EMBEDDING_ADAPTER_ID] | ||
| adapter_id = tool_meta.get( | ||
| AdapterPropertyKey.DEFAULT_EMBEDDING_ADAPTER_ID | ||
| ) | ||
|
|
||
| adapter_ids.add(adapter_id) | ||
| # UN-2953: a tool instance may carry "" for an adapter id. | ||
| # Adding it made the schema validator compare "" against the | ||
| # UUID enum and raise, once per validation pass. | ||
| if adapter_id: | ||
| adapter_ids.add(adapter_id) | ||
| for text_extractor in tool.properties.adapter.text_extractors: | ||
| adapter_id = None | ||
| if text_extractor.is_enabled and text_extractor.adapter_id: | ||
| adapter_id = tool_meta[text_extractor.adapter_id] | ||
| adapter_id = tool_meta.get(text_extractor.adapter_id) | ||
| elif text_extractor.is_enabled: | ||
| adapter_id = tool_meta[AdapterPropertyKey.DEFAULT_X2TEXT_ADAPTER_ID] | ||
| adapter_id = tool_meta.get( | ||
| AdapterPropertyKey.DEFAULT_X2TEXT_ADAPTER_ID | ||
| ) | ||
|
|
||
| adapter_ids.add(adapter_id) | ||
| # UN-2953: a tool instance may carry "" for an adapter id. | ||
| # Adding it made the schema validator compare "" against the | ||
| # UUID enum and raise, once per validation pass. | ||
| if adapter_id: | ||
| adapter_ids.add(adapter_id) | ||
|
|
||
| ToolInstanceHelper.validate_adapter_access(user=user, adapter_ids=adapter_ids) | ||
|
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. [High] [Lens 13 — Testing] — This permission-check rewrite ships with zero testsEvery
Fix — three cases against a stubbed
Confidence: High. |
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,8 +58,12 @@ | |
| "line_splitter_strategy": { | ||
| "type": "string", | ||
| "title": "Line Splitter Strategy", | ||
| "enum": ["left-priority", "mid-priority", "right-priority"], | ||
| "default":"left-priority", | ||
| "enum": [ | ||
| "left-priority", | ||
| "mid-priority", | ||
| "right-priority" | ||
| ], | ||
| "default": "left-priority", | ||
| "description": "An advanced option for customizing the line splitting process." | ||
| }, | ||
| "horizontal_stretch_factor": { | ||
|
|
@@ -93,6 +97,14 @@ | |
| "default": false, | ||
| "description": "States whether to reproduce horizontal lines in the document. Note: This parameter is not applicable if `mode` chosen is `native_text` and will not work if `mark_vertical_lines` is set to `false`." | ||
| }, | ||
| "word_confidence_threshold": { | ||
| "type": "number", | ||
| "title": "Word confidence threshold", | ||
| "default": 0.3, | ||
| "minimum": 0.0, | ||
| "maximum": 1.0, | ||
| "description": "Minimum OCR confidence a word must reach to be included in the extracted text. Lower this when words are dropped because the scan is faint or noisy. Note: This parameter is not applicable if `mode` chosen is `native_text`." | ||
| }, | ||
|
Comment on lines
+100
to
+107
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. [High] [Lens 1, 3, 7] —
|
||
| "tag": { | ||
| "type": "string", | ||
| "title": "Tag", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,6 +118,55 @@ def process( | |
| self.push_usage_details(input_file_path, mime_type, fs=fs) | ||
| return text_extraction_result | ||
|
|
||
| @staticmethod | ||
| def _parse_pages_to_extract(pages_to_extract: str, total_pages: int) -> int: | ||
| """Count the pages selected by an LLMWhisperer ``pages_to_extract`` spec. | ||
|
|
||
| The spec is a comma separated list of single pages and ranges, where a | ||
| range may be open ended (``50-`` means "page 50 to the end"). Pages are | ||
| 1-indexed and may overlap, so they are collected into a set and clamped | ||
| to the document length. An empty spec means "all pages". | ||
| """ | ||
| selected: set[int] = set() | ||
| for part in pages_to_extract.split(","): | ||
| part = part.strip() | ||
| if not part: | ||
| continue | ||
| if "-" in part: | ||
| start_str, _, end_str = part.partition("-") | ||
| try: | ||
| start = int(start_str) | ||
| except ValueError: | ||
| continue | ||
| end = total_pages | ||
| if end_str: | ||
| try: | ||
| end = int(end_str) | ||
| except ValueError: | ||
| continue | ||
| selected.update(range(max(start, 1), min(end, total_pages) + 1)) | ||
| else: | ||
| try: | ||
| page = int(part) | ||
| except ValueError: | ||
| continue | ||
| if 1 <= page <= total_pages: | ||
| selected.add(page) | ||
| return len(selected) | ||
|
Comment on lines
+130
to
+155
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. [High] [Lens 3, 16] — A partially-parseable
|
||
|
|
||
| def _get_billable_page_count(self, page_count: int) -> int: | ||
| """Narrow ``page_count`` to the pages the adapter will actually extract. | ||
|
|
||
| Falls back to the full count whenever the setting is absent, empty or | ||
| unparseable, so usage is never under-reported by a malformed value. | ||
| """ | ||
| config = getattr(self._x2text_instance, "config", None) or {} | ||
| pages_to_extract = str(config.get("pages_to_extract", "") or "").strip() | ||
| if not pages_to_extract: | ||
| return page_count | ||
| selected = self._parse_pages_to_extract(pages_to_extract, page_count) | ||
| return selected or page_count | ||
|
|
||
| def push_usage_details( | ||
| self, | ||
| input_file_path: str, | ||
|
|
@@ -133,6 +182,10 @@ def push_usage_details( | |
| with pdfplumber.open(pdf_contents) as pdf: | ||
| # calculate the number of pages | ||
| page_count = len(pdf.pages) | ||
| # UN-3038: when the adapter restricts extraction to a page range, | ||
| # only those pages are actually processed, so bill for them rather | ||
| # than for every page in the document. | ||
| page_count = self._get_billable_page_count(page_count) | ||
| Audit().push_page_usage_data( | ||
| platform_api_key=self._tool.get_env_or_die(ToolEnv.PLATFORM_API_KEY), | ||
| file_size=file_size, | ||
|
|
||
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.
[High] [Lens 16, 15] — This comment (repeated ×4, verbatim) names a mechanism that does not exist on this path, and hides the bugs actually fixed
Also at
:509-511,:523-525,:535-537.The comment says adding
"""made the schema validator compare""against the UUID enum and raise."adapter_idsis a function-local set whose only consumer isvalidate_adapter_access(:553) →AdapterInstance.objects.filter(id__in=adapter_ids). No schema validator, no enum on that path. The only validator in this file (:451) validatestool_meta— a different input — and runs after this function returns. A maintainer will go read the JSON schema and find nothing.The real failure was a Django UUID-coercion error (
AdapterInstance.idis aUUIDField,backend/adapter_processor_v2/models.py:63), or the oldKeyError.And the comment never mentions the two other bugs this diff silently fixes: the old code left
adapter_idunassigned whenis_enabledwasFalse, soadapter_ids.add(adapter_id)re-added the previous iteration's id (leaking across all four loops) or raisedUnboundLocalErroron the first. The newadapter_id = Noneinitialiser is the load-bearing change and is undocumented.Fix: one accurate comment above the loop group —
# Skip unset adapter ids: "" is not a valid UUID and breaks the id__in filter.Hoist the four near-identical loops into a helper.Confidence: High. Independently reached by three agents and a manual trace.