Skip to content

Commit 50d5ed2

Browse files
committed
fix(multimodal): smooth rough edges
Signed-off-by: Anupam Kumar <kyteinsky@gmail.com>
1 parent 205c345 commit 50d5ed2

4 files changed

Lines changed: 46 additions & 19 deletions

File tree

context_chat_backend/chain/ingest/doc_loader.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from pypdf.errors import FileNotDecryptedError as PdfFileNotDecryptedError
2121
from striprtf import striprtf
2222

23-
from ...types import IndexingException, SourceItem, TaskProcException, TaskProcFatalException
23+
from ...types import IndexingException, SourceItem, TaskProcClientException, TaskProcException, TaskProcFatalException
2424
from .task_proc import (
2525
OCR_TASK_TYPE,
2626
SPEECH_TO_TEXT_TASK_TYPE,
@@ -35,7 +35,7 @@
3535
logger = logging.getLogger('ccb.doc_loader')
3636
PDF_IMAGES_BATCH_SIZE = 25
3737
PDF_IMAGES_MAX_SIZE = 100 * 1024 * 1024 # 100 MiB in bytes
38-
# todo: reduce the frequency of this check?
38+
# todo: cache it across processes?
3939
IS_OCR_AVAILABLE = is_task_type_available(OCR_TASK_TYPE)
4040
IS_STT_AVAILABLE = is_task_type_available(SPEECH_TO_TEXT_TASK_TYPE)
4141

@@ -60,7 +60,7 @@ def _load_pdf(file: BytesIO, source: SourceItem) -> str:
6060

6161
for page in pdf_reader.pages:
6262
text = page.extract_text().strip()
63-
ocr_outputs = []
63+
page_ocr_outputs = []
6464

6565
if IS_OCR_AVAILABLE:
6666
for i in range(0, len(page.images), PDF_IMAGES_BATCH_SIZE):
@@ -87,12 +87,12 @@ def _load_pdf(file: BytesIO, source: SourceItem) -> str:
8787
try:
8888
ocr_tp_outputs = asyncio.run(do_ocr(source.userIds[0], file_ids_map.values())) # pyright: ignore[reportArgumentType]
8989
asyncio.run(delete_temp_files(file_ids_map.values())) # pyright: ignore[reportArgumentType]
90-
except TaskProcFatalException:
90+
except TaskProcFatalException as e:
9191
# task type is not present anymore, flip the task type indicator manually
9292
# for this complete batched injest process
9393
logger.warning(
9494
'The OCR provider disappeared mid-operation, disabling OCR for this batch of'
95-
f' documents including {source.reference}'
95+
f' documents including {source.reference}: {e}'
9696
)
9797
IS_OCR_AVAILABLE = False
9898
break
@@ -101,11 +101,11 @@ def _load_pdf(file: BytesIO, source: SourceItem) -> str:
101101
continue
102102

103103
# for each image batch
104-
ocr_outputs += ocr_tp_outputs
104+
page_ocr_outputs += ocr_tp_outputs
105105

106-
# for each page
106+
# for each page, append text then its OCR outputs to preserve document order
107107
output.append(text)
108-
output += ocr_outputs
108+
output += page_ocr_outputs
109109

110110
return '\n\n'.join(output)
111111

@@ -192,6 +192,7 @@ def decode_source(source: SourceItem) -> str:
192192
IndexingException
193193
'''
194194

195+
global IS_OCR_AVAILABLE
195196
io_obj: BytesIO | None = None
196197
try:
197198
# .pot files are powerpoint templates but also plain text files,
@@ -200,16 +201,36 @@ def decode_source(source: SourceItem) -> str:
200201
raise IndexingException('PowerPoint template files (.pot) are not supported')
201202

202203
try:
203-
if IS_OCR_AVAILABLE and source.type.startswith('image/'):
204-
return asyncio.run(do_ocr(source.userIds[0], [source.file_id]))[0]
205-
if IS_STT_AVAILABLE and source.type.startswith('audio/'):
206-
return asyncio.run(do_transcription(source.userIds[0], source.file_id))
207-
except TaskProcException as e:
208-
# todo: convert this to error obj return
209-
# todo: short circuit all other ocr/transcription files when a fatal error arrives
210-
# todo: maybe with a global ttl, with a retryable tag
204+
if source.type.startswith('image/'):
205+
if IS_OCR_AVAILABLE:
206+
return asyncio.run(do_ocr(source.userIds[0], [source.file_id]))[0]
207+
raise IndexingException(
208+
f'Image file ({source.reference}) cannot be processed since OCR task type is not present'
209+
)
210+
if source.type.startswith('audio/'):
211+
if IS_STT_AVAILABLE:
212+
return asyncio.run(do_transcription(source.userIds[0], source.file_id))
213+
raise IndexingException(
214+
f'Audio file ({source.reference}) cannot be processed since Speech-to-Text task type is not present'
215+
)
216+
except TaskProcFatalException as e:
217+
logger.warning(
218+
'The OCR provider disappeared mid-operation, disabling OCR for this batch of'
219+
f' documents including {source.reference}: {e}'
220+
)
221+
IS_OCR_AVAILABLE = False
222+
raise IndexingException( # noqa: B904
223+
f'Image file ({source.reference}) cannot be processed since OCR task type is not present'
224+
)
225+
except TaskProcClientException as e:
211226
logger.warning(f'OCR task failed for source file ({source.reference}): {e}')
212227
raise IndexingException(f'OCR task failed for source file ({source.reference}): {e}') # noqa: B904
228+
except TaskProcException as e:
229+
logger.warning(f'OCR task failed for source file ({source.reference}), it will be retried: {e}')
230+
raise IndexingException( # noqa: B904
231+
f'OCR task failed for source file ({source.reference}), it will be retried: {e}',
232+
retryable=True,
233+
)
213234
except ValueError:
214235
# should not happen
215236
logger.warning(f'Unexpected ValueError for source file ({source.reference})')

context_chat_backend/chain/ingest/injest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515

1616
from ...dyn_loader import VectorDBLoader
1717
from ...mimetype_list import AUDIO_MIMETYPES, IMAGE_MIMETYPES, SUPPORTED_MIMETYPES
18+
from ...types import FILES_PROVIDER_ID, IndexingError, IndexingException, ReceivedFileItem, SourceItem, TConfig
1819
from ...vectordb.base import BaseVectorDB
1920
from ...vectordb.types import DbException, SafeDbException, UpdateAccessOp
20-
from ...types import FILES_PROVIDER_ID, IndexingError, IndexingException, ReceivedFileItem, SourceItem, TConfig
2121
from ..types import InDocument
2222
from .doc_loader import decode_source
2323
from .doc_splitter import get_splitter_for
@@ -311,7 +311,7 @@ def _sources_to_indocuments(
311311
def _increase_access_for_existing_sources(
312312
vectordb: BaseVectorDB,
313313
existing_sources: Mapping[int, SourceItem | ReceivedFileItem]
314-
) -> Mapping[int, IndexingError | None]:
314+
) -> dict[int, IndexingError | None]:
315315
'''
316316
update userIds for existing sources
317317
allow the userIds as additional users, not as the only users
@@ -352,7 +352,7 @@ def _process_sources(
352352
vectordb: BaseVectorDB,
353353
config: TConfig,
354354
sources: Mapping[int, SourceItem | ReceivedFileItem]
355-
) -> Mapping[int, IndexingError | None]:
355+
) -> dict[int, IndexingError | None]:
356356
'''
357357
Processes the sources and adds them to the vectordb.
358358
Returns the list of source ids that were successfully added and those that need to be retried.

context_chat_backend/chain/ingest/task_proc.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ async def __get_task_result(user_id: str, task: Task) -> Any:
226226
raise TaskProcException('Failed to parse TaskProcessing task result') from e
227227

228228
if task.status != 'STATUS_SUCCESSFUL':
229+
# it's not possible to know if the document is unprocessable or the provider is having a temporary issue
230+
# so we retry it
229231
raise TaskProcException(
230232
f'TaskProcessing task id {task.id} failed with status {task.status}'
231233
f' after waiting {now_waiting_for} seconds',

context_chat_backend/types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ class TaskProcFatalException(TaskProcException):
150150
...
151151

152152

153+
class TaskProcClientException(TaskProcException):
154+
...
155+
156+
153157
class AppRole(str, Enum):
154158
NORMAL = 'normal'
155159
INDEXING = 'indexing'

0 commit comments

Comments
 (0)