Skip to content

Commit 3c6926b

Browse files
author
Bob Strahan
committed
Merge branch 'develop' v0.3.2
2 parents 9255e4d + ee4fcf3 commit 3c6926b

File tree

26 files changed

+734
-127
lines changed

26 files changed

+734
-127
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@ SPDX-License-Identifier: MIT-0
55

66
## [Unreleased]
77

8+
## [0.3.2]
9+
10+
### Added
11+
12+
- **Cost Estimator UI Feature for Context Grouping and Subtotals**
13+
- Added context grouping functionality to organize cost estimates by logical categories (e.g. OCR, Classification, etc.)
14+
- Implemented subtotal calculations for better cost breakdown visualization
15+
16+
- **DynamoDB Caching for Resilient Classification**
17+
- Added optional DynamoDB caching to the multimodal page-level classification service to improve efficiency and resilience
18+
- Cache successful page classification results to avoid redundant processing during retries when some pages fail due to throttling
19+
- Exception-safe caching preserves successful work even when individual threads or the overall process fails
20+
- Configurable via `cache_table` parameter or `CLASSIFICATION_CACHE_TABLE` environment variable
21+
- Cache entries scoped to document ID and workflow execution ARN with automatic TTL cleanup (24 hours)
22+
- Significant cost reduction and improved retry performance for large multi-page documents
23+
24+
### Fixed
25+
- "Use as Evaluation Baseline" incorrectly sets document status back to QUEUED. It should remain as COMPLETED.
26+
27+
828
## [0.3.1]
929

1030
### Added

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.1
1+
0.3.2

config_library/pattern-2/few_shot_example_with_multimodal_page_classification/config.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ classes:
7474
"signature": "Will E. Clark",
7575
"cc": null,
7676
"reference_number": "TNJB 0008497"
77-
imagePath: config_library/pattern-2/few_shot_example/example-images/letter1.jpg
77+
imagePath: config_library/pattern-2/few_shot_example_with_multimodal_page_classification/example-images/letter1.jpg
7878
- classPrompt: This is an example of the class 'letter'
7979
name: Letter2
8080
attributesPrompt: |-
@@ -89,7 +89,7 @@ classes:
8989
"signature": "Bill",
9090
"cc": null,
9191
"reference_number": null
92-
imagePath: config_library/pattern-2/few_shot_example/example-images/letter2.png
92+
imagePath: config_library/pattern-2/few_shot_example_with_multimodal_page_classification/example-images/letter2.png
9393
- name: form
9494
description: >-
9595
A structured document with labeled fields, checkboxes, or blanks requiring
@@ -464,7 +464,7 @@ classes:
464464
"priority": null,
465465
"thread_id": null,
466466
"message_id": null
467-
imagePath: config_library/pattern-2/few_shot_example/example-images/email1.jpg
467+
imagePath: config_library/pattern-2/few_shot_example_with_multimodal_page_classification/example-images/email1.jpg
468468
- name: questionnaire
469469
description: >-
470470
A survey instrument containing numbered questions with multiple choice,
@@ -636,7 +636,7 @@ classes:
636636
"account_name": ["Checking", "Savings"],
637637
"account_number": ["003525801543","352580154336"],
638638
"transactions": [{"Date": "2/6/2020", "Description": "Food Purchase - AnyCompany Restaurant - 1194989245", "Amount": "-171"}]
639-
imagePath: config_library/pattern-2/few_shot_example/example-images/bank-statement-pages/
639+
imagePath: config_library/pattern-2/few_shot_example_with_multimodal_page_classification/example-images/bank-statement-pages/
640640

641641
classification:
642642
classificationMethod: multimodalPageLevelClassification
@@ -672,11 +672,11 @@ classification:
672672
{CLASS_NAMES_AND_DESCRIPTIONS}
673673
674674
675-
<few_shot_examples>
675+
<few_shot_example_with_multimodal_page_classifications>
676676
677-
{FEW_SHOT_EXAMPLES}
677+
{few_shot_example_with_multimodal_page_classificationS}
678678
679-
</few_shot_examples>
679+
</few_shot_example_with_multimodal_page_classifications>
680680
681681
682682
<<CACHEPOINT>>

lib/idp_common_pkg/idp_common/bedrock/client.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ def __call__(
7878
top_k: Optional[Union[float, str]] = None,
7979
top_p: Optional[Union[float, str]] = None,
8080
max_tokens: Optional[Union[int, str]] = None,
81-
max_retries: Optional[int] = None
81+
max_retries: Optional[int] = None,
82+
context: str = "Unspecified"
8283
) -> Dict[str, Any]:
8384
"""
8485
Make the instance callable with the same signature as the original function.
@@ -109,7 +110,8 @@ def __call__(
109110
top_k=top_k,
110111
top_p=top_p,
111112
max_tokens=max_tokens,
112-
max_retries=effective_max_retries
113+
max_retries=effective_max_retries,
114+
context=context
113115
)
114116

115117
def _preprocess_content_for_cachepoint(self, content: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
@@ -177,7 +179,8 @@ def invoke_model(
177179
top_k: Optional[Union[float, str]] = 5,
178180
top_p: Optional[Union[float, str]] = 0.1,
179181
max_tokens: Optional[Union[int, str]] = None,
180-
max_retries: Optional[int] = None
182+
max_retries: Optional[int] = None,
183+
context: str = "Unspecified"
181184
) -> Dict[str, Any]:
182185
"""
183186
Invoke a Bedrock model with retry logic.
@@ -335,7 +338,8 @@ def invoke_model(
335338
converse_params=converse_params,
336339
retry_count=0,
337340
max_retries=effective_max_retries,
338-
request_start_time=request_start_time
341+
request_start_time=request_start_time,
342+
context=context
339343
)
340344

341345
return result
@@ -346,7 +350,8 @@ def _invoke_with_retry(
346350
retry_count: int,
347351
max_retries: int,
348352
request_start_time: float,
349-
last_exception: Exception = None
353+
last_exception: Exception = None,
354+
context: str = "Unspecified"
350355
) -> Dict[str, Any]:
351356
"""
352357
Recursive helper method to handle retries for Bedrock invocation.
@@ -424,7 +429,7 @@ def _invoke_with_retry(
424429
response_with_metering = {
425430
"response": response,
426431
"metering": {
427-
f"bedrock/{converse_params['modelId']}": {
432+
f"{context}/bedrock/{converse_params['modelId']}": {
428433
**usage
429434
}
430435
}
@@ -470,7 +475,8 @@ def _invoke_with_retry(
470475
retry_count=retry_count + 1,
471476
max_retries=max_retries,
472477
request_start_time=request_start_time,
473-
last_exception=e
478+
last_exception=e,
479+
context=context
474480
)
475481
else:
476482
logger.error(f"Non-retryable Bedrock error: {error_code} - {error_message}")
@@ -838,6 +844,7 @@ def _sanitize_response_for_logging(self, response: Dict[str, Any]) -> Dict[str,
838844
top_p: Optional top_p parameter (float or string)
839845
max_tokens: Optional max_tokens parameter (int or string)
840846
max_retries: Optional override for the instance's max_retries setting
847+
context: Context prefix for metering key (default: "Unspecified")
841848
842849
Returns:
843850
Bedrock response object with metering information

lib/idp_common_pkg/idp_common/classification/README.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This module provides document classification capabilities for the IDP Accelerato
1616
- Structured data models for results
1717
- Grouping of pages into sections by classification
1818
- Comprehensive error handling and retry mechanisms
19+
- **DynamoDB caching for resilient page-level classification**
1920

2021
## Usage Example
2122

@@ -226,6 +227,136 @@ def handler(event, context):
226227
- `ClassificationResult`: Overall result of a classification operation
227228
- `Document`: Core document data model used throughout the IDP pipeline
228229

230+
## DynamoDB Caching for Resilient Classification
231+
232+
The classification service now supports optional DynamoDB caching to improve efficiency and resilience when processing documents with multiple pages. This feature addresses throttling scenarios where some pages succeed while others fail, avoiding the need to reclassify already successful pages on retry.
233+
234+
### How It Works
235+
236+
1. **Cache Check**: Before processing, the service checks for cached classification results for the document
237+
2. **Selective Processing**: Only pages without cached results are classified
238+
3. **Exception-Safe Caching**: Successful page results are cached even when other pages fail
239+
4. **Retry Efficiency**: Subsequent retries only process previously failed pages
240+
241+
### Configuration
242+
243+
#### Via Constructor Parameter
244+
```python
245+
from idp_common import classification, get_config
246+
247+
config = get_config()
248+
service = classification.ClassificationService(
249+
region="us-east-1",
250+
config=config,
251+
backend="bedrock",
252+
cache_table="classification-cache-table" # Enable caching
253+
)
254+
```
255+
256+
#### Via Environment Variable
257+
```bash
258+
export CLASSIFICATION_CACHE_TABLE=classification-cache-table
259+
```
260+
261+
```python
262+
# Cache table will be automatically detected from environment
263+
service = classification.ClassificationService(
264+
region="us-east-1",
265+
config=config,
266+
backend="bedrock"
267+
)
268+
```
269+
270+
### DynamoDB Table Schema
271+
272+
The cache uses the following DynamoDB table structure:
273+
274+
- **Primary Key (PK)**: `classcache#{document_id}#{workflow_execution_arn}`
275+
- **Sort Key (SK)**: `none`
276+
- **Attributes**:
277+
- `page_classifications` (String): JSON-encoded successful page results
278+
- `cached_at` (String): Unix timestamp of cache creation
279+
- `document_id` (String): Document identifier
280+
- `workflow_execution_arn` (String): Workflow execution ARN
281+
- `ExpiresAfter` (Number): TTL attribute for automatic cleanup (24 hours)
282+
283+
#### Example DynamoDB Item
284+
```json
285+
{
286+
"PK": "classcache#doc-123#arn:aws:states:us-east-1:123456789012:execution:MyWorkflow:abc-123",
287+
"SK": "none",
288+
"page_classifications": "{\"1\":{\"doc_type\":\"invoice\",\"confidence\":1.0,\"metadata\":{\"metering\":{...}},\"image_uri\":\"s3://...\",\"text_uri\":\"s3://...\",\"raw_text_uri\":\"s3://...\"},\"2\":{...}}",
289+
"cached_at": "1672531200",
290+
"document_id": "doc-123",
291+
"workflow_execution_arn": "arn:aws:states:us-east-1:123456789012:execution:MyWorkflow:abc-123",
292+
"ExpiresAfter": 1672617600
293+
}
294+
```
295+
296+
### Benefits
297+
298+
- **Cost Reduction**: Avoids redundant API calls to Bedrock/SageMaker for already-classified pages
299+
- **Improved Resilience**: Handles partial failures gracefully during concurrent processing
300+
- **Faster Retries**: Subsequent attempts only process failed pages, not the entire document
301+
- **Automatic Cleanup**: TTL ensures cache entries don't accumulate indefinitely
302+
- **Thread Safety**: Safe for concurrent page processing within the same document
303+
304+
### Example: Resilient Processing Flow
305+
306+
```python
307+
from idp_common import classification, get_config
308+
from idp_common.models import Document
309+
310+
config = get_config()
311+
service = classification.ClassificationService(
312+
region="us-east-1",
313+
config=config,
314+
backend="bedrock",
315+
cache_table="classification-cache-table"
316+
)
317+
318+
# Create document with 5 pages
319+
document = Document(
320+
id="doc-123",
321+
workflow_execution_arn="arn:aws:states:us-east-1:123456789012:execution:MyWorkflow:abc-123",
322+
pages={
323+
"1": {...},
324+
"2": {...},
325+
"3": {...},
326+
"4": {...},
327+
"5": {...}
328+
}
329+
)
330+
331+
try:
332+
# First attempt: pages 1,2,4 succeed, pages 3,5 fail due to throttling
333+
document = service.classify_document(document)
334+
except Exception as e:
335+
# Pages 1,2,4 are cached automatically before exception is raised
336+
print(f"Classification failed: {e}")
337+
338+
try:
339+
# Retry: only pages 3,5 are processed (1,2,4 loaded from cache)
340+
document = service.classify_document(document)
341+
print("Document classified successfully on retry")
342+
except Exception as e:
343+
print(f"Retry failed: {e}")
344+
```
345+
346+
### Cache Lifecycle
347+
348+
1. **Creation**: Cache entries are created when `classify_document()` completes successfully or encounters exceptions
349+
2. **Retrieval**: Cache is checked at the start of each `classify_document()` call
350+
3. **Update**: Cache entries are updated with new successful results from each processing attempt
351+
4. **Expiration**: Entries automatically expire after 24 hours via DynamoDB TTL
352+
353+
### Important Notes
354+
355+
- Caching only applies to the `classify_document()` method, not individual `classify_page()` calls
356+
- Cache entries are scoped to specific document and workflow execution combinations
357+
- Only successful page classifications (without errors in metadata) are cached
358+
- The cache is transparent - existing code continues to work without modifications
359+
229360
## Backend Options
230361

231362
### Bedrock Backend

0 commit comments

Comments
 (0)