nv-ingest 2025.10.30.dev20251030__py3-none-any.whl → 2025.11.2.dev20251102__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nv-ingest might be problematic. Click here for more details.

@@ -12,6 +12,7 @@ import logging
12
12
  import os
13
13
  import time
14
14
  import uuid
15
+ import random
15
16
 
16
17
  from fastapi import APIRouter, Request, Response
17
18
  from fastapi import HTTPException
@@ -44,6 +45,42 @@ router = APIRouter()
44
45
 
45
46
  DEFAULT_PDF_SPLIT_PAGE_COUNT = 32
46
47
 
48
+ # Default QoS thresholds (pages). Tunable via environment variables:
49
+ # QOS_MAX_PAGES_MICRO, QOS_MAX_PAGES_SMALL, QOS_MAX_PAGES_MEDIUM
50
+ _QOS_DEFAULTS = {
51
+ "micro": 8,
52
+ "small": 64,
53
+ "medium": 256,
54
+ }
55
+
56
+
57
+ def get_qos_tier_for_page_count(page_count: int) -> str:
58
+ """
59
+ Select QoS tier for a document based on its total page count.
60
+ Tiers: 'micro', 'small', 'medium', 'large', 'default'
61
+ Thresholds can be tuned via environment variables:
62
+ - QOS_MAX_PAGES_MICRO (default: 4)
63
+ - QOS_MAX_PAGES_SMALL (default: 16)
64
+ - QOS_MAX_PAGES_MEDIUM (default: 64)
65
+ Anything above MEDIUM is 'large'. Non-positive page_count returns 'default'.
66
+ """
67
+ try:
68
+ micro_max = int(os.getenv("QOS_MAX_PAGES_MICRO", str(_QOS_DEFAULTS["micro"])))
69
+ small_max = int(os.getenv("QOS_MAX_PAGES_SMALL", str(_QOS_DEFAULTS["small"])))
70
+ medium_max = int(os.getenv("QOS_MAX_PAGES_MEDIUM", str(_QOS_DEFAULTS["medium"])))
71
+ except ValueError:
72
+ micro_max, small_max, medium_max = _QOS_DEFAULTS["micro"], _QOS_DEFAULTS["small"], _QOS_DEFAULTS["medium"]
73
+
74
+ if page_count <= 0:
75
+ return "default"
76
+ if page_count <= micro_max:
77
+ return "micro"
78
+ if page_count <= small_max:
79
+ return "small"
80
+ if page_count <= medium_max:
81
+ return "medium"
82
+ return "large"
83
+
47
84
 
48
85
  def get_pdf_split_page_count(client_override: Optional[int] = None) -> int:
49
86
  """
@@ -702,6 +739,51 @@ def _build_aggregated_response(
702
739
  return aggregated_result
703
740
 
704
741
 
742
+ # ---------------------------------------------------------------------------
743
+ # Bursty submission helpers (fairness without long-lived in-flight tasks)
744
+ # ---------------------------------------------------------------------------
745
+
746
+
747
+ def _get_submit_burst_params() -> Tuple[int, int, int]:
748
+ """
749
+ Returns (burst_size, pause_ms, jitter_ms) from environment with sane defaults.
750
+ - V2_SUBMIT_BURST_SIZE (default: 16)
751
+ - V2_SUBMIT_BURST_PAUSE_MS (default: 25)
752
+ - V2_SUBMIT_BURST_JITTER_MS (default: 10)
753
+ """
754
+ burst_size = int(os.getenv("V2_SUBMIT_BURST_SIZE", "16"))
755
+ pause_ms = int(os.getenv("V2_SUBMIT_BURST_PAUSE_MS", "50"))
756
+ jitter_ms = int(os.getenv("V2_SUBMIT_BURST_JITTER_MS", "15"))
757
+
758
+ return max(1, burst_size), max(0, pause_ms), max(0, jitter_ms)
759
+
760
+
761
+ async def _submit_subjobs_in_bursts(
762
+ items: List[Tuple[str, MessageWrapper]],
763
+ ingest_service: "INGEST_SERVICE_T",
764
+ *,
765
+ burst_size: int,
766
+ pause_ms: int,
767
+ jitter_ms: int,
768
+ ) -> None:
769
+ """
770
+ Submit subjobs in sequential bursts and await each burst to completion.
771
+ This avoids keeping a large number of pending tasks in the REST handler
772
+ and allows other concurrent requests to interleave enqueue work between bursts.
773
+ """
774
+ for offset in range(0, len(items), burst_size):
775
+ burst = items[offset : offset + burst_size]
776
+ tasks = [ingest_service.submit_job(wrapper, subjob_id) for (subjob_id, wrapper) in burst]
777
+ # Propagate any errors from this burst
778
+ await asyncio.gather(*tasks)
779
+
780
+ # Pause with jitter to yield to other request handlers before next burst
781
+ if offset + burst_size < len(items):
782
+ delay_ms = pause_ms + (random.randint(0, jitter_ms) if jitter_ms > 0 else 0)
783
+ if delay_ms > 0:
784
+ await asyncio.sleep(delay_ms / 1000.0)
785
+
786
+
705
787
  # POST /v2/submit_job
706
788
  @router.post(
707
789
  "/submit_job",
@@ -752,22 +834,24 @@ async def submit_job_v2(
752
834
  pdf_content = base64.b64decode(payloads[0])
753
835
  page_count = get_pdf_page_count(pdf_content)
754
836
  pdf_page_count_cache = page_count # Cache for later use
837
+ qos_tier = get_qos_tier_for_page_count(page_count)
755
838
  pages_per_chunk = get_pdf_split_page_count(client_override=client_split_page_count)
756
839
 
757
840
  # Split if the document has more pages than our chunk size
758
841
  if page_count > pages_per_chunk:
759
842
  logger.warning(
760
- "Splitting PDF %s into %s-page chunks (total pages: %s)",
843
+ "Splitting PDF %s into %s-page chunks (total pages: %s) -> (qos_tier: %s)",
761
844
  original_source_name,
762
845
  pages_per_chunk,
763
846
  page_count,
847
+ qos_tier,
764
848
  )
765
849
 
766
850
  chunks = split_pdf_to_chunks(pdf_content, pages_per_chunk)
767
851
 
768
852
  subjob_ids: List[str] = []
769
853
  subjob_descriptors: List[Dict[str, Any]] = []
770
- submission_tasks = []
854
+ submission_items: List[Tuple[str, MessageWrapper]] = []
771
855
 
772
856
  try:
773
857
  parent_uuid = uuid.UUID(parent_job_id)
@@ -788,7 +872,19 @@ async def submit_job_v2(
788
872
  original_source_id=original_source_id,
789
873
  original_source_name=original_source_name,
790
874
  )
791
- submission_tasks.append(ingest_service.submit_job(subjob_wrapper, subjob_id))
875
+
876
+ # Inject QoS routing hint into subjob routing_options (keeps API and service loosely coupled)
877
+ try:
878
+ sub_spec = json.loads(subjob_wrapper.payload)
879
+ routing_opts = sub_spec.get("routing_options") or {}
880
+ routing_opts["queue_hint"] = qos_tier
881
+ sub_spec["routing_options"] = routing_opts
882
+ subjob_wrapper = MessageWrapper(payload=json.dumps(sub_spec))
883
+ except Exception:
884
+ # Best-effort; if we cannot inject, fall back to default routing
885
+ pass
886
+
887
+ submission_items.append((subjob_id, subjob_wrapper))
792
888
  subjob_ids.append(subjob_id)
793
889
  subjob_descriptors.append(
794
890
  {
@@ -800,8 +896,15 @@ async def submit_job_v2(
800
896
  }
801
897
  )
802
898
 
803
- if submission_tasks:
804
- await asyncio.gather(*submission_tasks)
899
+ if submission_items:
900
+ burst_size, pause_ms, jitter_ms = _get_submit_burst_params()
901
+ await _submit_subjobs_in_bursts(
902
+ submission_items,
903
+ ingest_service,
904
+ burst_size=burst_size,
905
+ pause_ms=pause_ms,
906
+ jitter_ms=jitter_ms,
907
+ )
805
908
 
806
909
  parent_metadata: Dict[str, Any] = {
807
910
  "total_pages": page_count,
@@ -829,6 +932,16 @@ async def submit_job_v2(
829
932
  if "tracing_options" not in job_spec_dict:
830
933
  job_spec_dict["tracing_options"] = {"trace": True}
831
934
  job_spec_dict["tracing_options"]["trace_id"] = str(current_trace_id)
935
+ # If this was a PDF and we computed page_count, route the single job using the same QoS tier
936
+ try:
937
+ if (
938
+ document_types
939
+ and document_types[0].lower() == "pdf"
940
+ and "queue_hint" not in (job_spec_dict.get("routing_options") or {})
941
+ ):
942
+ job_spec_dict.setdefault("routing_options", {})["queue_hint"] = qos_tier
943
+ except Exception:
944
+ pass
832
945
  updated_job_spec = MessageWrapper(payload=json.dumps(job_spec_dict))
833
946
 
834
947
  span.add_event("Submitting as single job (no split needed)")
@@ -19,20 +19,45 @@ logger = logging.getLogger(__name__)
19
19
 
20
20
 
21
21
  def _safe_log(level: int, msg: str) -> None:
22
- """Best-effort logging that won't crash during interpreter shutdown."""
22
+ """Best-effort logging that won't emit handler tracebacks on closed streams.
23
+
24
+ Temporarily disables logging.raiseExceptions to prevent the logging module
25
+ from printing "--- Logging error ---" to stderr if a handler's stream is
26
+ already closed (common during process teardown). Falls back to writing to
27
+ sys.__stderr__ if available.
28
+ """
23
29
  try:
24
- logger.log(level, msg)
30
+ import logging as _logging
31
+
32
+ prev = getattr(_logging, "raiseExceptions", True)
33
+ # Suppress handler errors being printed to stderr
34
+ _logging.raiseExceptions = False
35
+
36
+ # If there are no handlers, skip and use stderr fallback
37
+ if logger.handlers:
38
+ logger.log(level, msg)
39
+ return
25
40
  except Exception:
41
+ # Intentionally ignore and try stderr fallback
42
+ pass
43
+ finally:
26
44
  try:
27
- # Fallback to stderr if available
28
- import sys
45
+ import logging as _logging # re-import safe even if earlier failed
29
46
 
30
- if hasattr(sys, "__stderr__") and sys.__stderr__:
31
- sys.__stderr__.write(msg + "\n")
32
- sys.__stderr__.flush()
47
+ _logging.raiseExceptions = prev # type: ignore[name-defined]
33
48
  except Exception:
34
49
  pass
35
50
 
51
+ # Fallback to stderr if available
52
+ try:
53
+ import sys
54
+
55
+ if hasattr(sys, "__stderr__") and sys.__stderr__:
56
+ sys.__stderr__.write(msg + "\n")
57
+ sys.__stderr__.flush()
58
+ except Exception:
59
+ pass
60
+
36
61
 
37
62
  def kill_pipeline_process_group(process) -> None:
38
63
  """
@@ -74,7 +99,17 @@ def kill_pipeline_process_group(process) -> None:
74
99
 
75
100
  try:
76
101
  # Send graceful termination to the entire process group
77
- os.killpg(os.getpgid(pid), signal.SIGTERM)
102
+ try:
103
+ pgid = os.getpgid(pid)
104
+ except Exception:
105
+ # Process already gone
106
+ _safe_log(logging.DEBUG, f"Process group for PID {pid} not found during SIGTERM phase")
107
+ return
108
+ try:
109
+ os.killpg(pgid, signal.SIGTERM)
110
+ except ProcessLookupError:
111
+ _safe_log(logging.DEBUG, f"Process group for PID {pid} no longer exists (SIGTERM)")
112
+ return
78
113
 
79
114
  # If we have a Process handle, give it a chance to exit cleanly
80
115
  if proc is not None and hasattr(proc, "join"):
@@ -95,7 +130,12 @@ def kill_pipeline_process_group(process) -> None:
95
130
  if still_alive:
96
131
  _safe_log(logging.WARNING, "Process group did not terminate gracefully, using SIGKILL")
97
132
  try:
98
- os.killpg(os.getpgid(pid), signal.SIGKILL)
133
+ try:
134
+ pgid2 = os.getpgid(pid)
135
+ except Exception:
136
+ _safe_log(logging.DEBUG, f"Process group for PID {pid} vanished before SIGKILL")
137
+ return
138
+ os.killpg(pgid2, signal.SIGKILL)
99
139
  finally:
100
140
  if proc is not None and hasattr(proc, "join"):
101
141
  try:
@@ -30,6 +30,7 @@ from nv_ingest_api.internal.schemas.meta.ingest_job_schema import validate_inges
30
30
  from nv_ingest_api.util.message_brokers.simple_message_broker.simple_client import SimpleClient
31
31
  from nv_ingest_api.util.service_clients.redis.redis_client import RedisClient
32
32
  from nv_ingest_api.util.logging.sanitize import sanitize_for_logging
33
+ from nv_ingest_api.util.message_brokers.qos_scheduler import QosScheduler
33
34
 
34
35
  logger = logging.getLogger(__name__)
35
36
 
@@ -89,8 +90,10 @@ class MessageBrokerTaskSourceConfig(BaseModel):
89
90
 
90
91
  # Use the discriminated union for broker_client
91
92
  broker_client: Union[RedisClientConfig, SimpleClientConfig] = Field(..., discriminator="client_type")
92
- task_queue: str = Field(..., description="The name of the queue to fetch tasks from.")
93
- poll_interval: float = Field(default=0.1, gt=0, description="Polling interval in seconds.")
93
+ task_queue: str = Field(
94
+ ..., description="The base name of the queue to fetch tasks from. Derives sub-queues for fair scheduling."
95
+ )
96
+ poll_interval: float = Field(default=0.0, gt=0, description="Polling interval in seconds.")
94
97
 
95
98
 
96
99
  @ray.remote
@@ -134,7 +137,29 @@ class MessageBrokerTaskSourceStage(RayActorSourceStage):
134
137
  self._current_backoff_sleep: float = 0.0
135
138
  self._last_backoff_log_time: float = 0.0
136
139
 
137
- self._logger.debug("MessageBrokerTaskSourceStage initialized. Task queue: %s", self.task_queue)
140
+ # Initialize QoS scheduler. Use a simple base-queue strategy for SimpleClient.
141
+ strategy = "simple" if isinstance(self.client, SimpleClient) else "lottery"
142
+ self.scheduler = QosScheduler(
143
+ self.task_queue,
144
+ num_prefetch_threads=6, # one per category (no-op for simple strategy)
145
+ total_buffer_capacity=96, # e.g., ~16 per thread
146
+ prefetch_poll_interval=0.002, # faster polling for responsiveness
147
+ prefetch_non_immediate=True, # enable prefetch for non-immediate categories
148
+ strategy=strategy,
149
+ )
150
+
151
+ self._logger.info(
152
+ "MessageBrokerTaskSourceStage initialized. Base task queue: %s | Derived queues: %s",
153
+ self.task_queue,
154
+ {
155
+ "immediate": f"{self.task_queue}_immediate",
156
+ "micro": f"{self.task_queue}_micro",
157
+ "small": f"{self.task_queue}_small",
158
+ "medium": f"{self.task_queue}_medium",
159
+ "large": f"{self.task_queue}_large",
160
+ "default": f"{self.task_queue}",
161
+ },
162
+ )
138
163
 
139
164
  # --- Private helper methods ---
140
165
  def _create_client(self):
@@ -265,14 +290,21 @@ class MessageBrokerTaskSourceStage(RayActorSourceStage):
265
290
 
266
291
  return control_message
267
292
 
268
- def _fetch_message(self, timeout=100):
293
+ def _fetch_message(self, timeout=0):
269
294
  """
270
- Fetch a message from the message broker.
295
+ Fetch a message from the message broker using fair scheduling across derived queues.
296
+ This is a non-blocking sweep across all queues for the current scheduling cycle. If no
297
+ message is found across any queue, return None so the caller can sleep briefly.
271
298
  """
272
299
  try:
273
- job = self.client.fetch_message(self.task_queue, timeout)
300
+ # Use scheduler to fetch next. In simple strategy this will block up to poll_interval on base queue.
301
+ job = self.scheduler.fetch_next(self.client, timeout=self.config.poll_interval)
274
302
  if job is None:
275
- self._logger.debug("No message received from '%s'", self.task_queue)
303
+ self._logger.debug(
304
+ "No message received from derived queues for base "
305
+ "'%s' (immediate, micro, small, medium, large, default)",
306
+ self.task_queue,
307
+ )
276
308
  # Do not treat normal empty polls as failures
277
309
  self._fetch_failure_count = 0
278
310
  self._current_backoff_sleep = 0.0
@@ -336,7 +368,8 @@ class MessageBrokerTaskSourceStage(RayActorSourceStage):
336
368
  Instead of reading from an input edge, fetch a message from the broker.
337
369
  """
338
370
  self._logger.debug("read_input: calling _fetch_message()")
339
- job = self._fetch_message(timeout=100)
371
+ # Perform a non-blocking sweep across all queues for this cycle
372
+ job = self._fetch_message(timeout=0)
340
373
  if job is None:
341
374
  # Sleep for either the configured poll interval or the current backoff, whichever is larger
342
375
  sleep_time = max(self.config.poll_interval, getattr(self, "_current_backoff_sleep", 0.0))
@@ -218,12 +218,33 @@ class RedisIngestService(IngestServiceMeta):
218
218
  ttl_for_result: Optional[int] = (
219
219
  self._result_data_ttl_seconds if self._fetch_mode == FetchMode.NON_DESTRUCTIVE else None
220
220
  )
221
+ # Determine target queue based on optional QoS hint
222
+ queue_hint = None
223
+ try:
224
+ routing_opts = job_spec.get("routing_options") or {}
225
+ tracing_opts = job_spec.get("tracing_options") or {}
226
+ queue_hint = routing_opts.get("queue_hint") or tracing_opts.get("queue_hint")
227
+ except Exception:
228
+ queue_hint = None
229
+ allowed = {"default", "immediate", "micro", "small", "medium", "large"}
230
+ if isinstance(queue_hint, str) and queue_hint in allowed:
231
+ if queue_hint == "default":
232
+ channel_name = self._redis_task_queue
233
+ else:
234
+ channel_name = f"{self._redis_task_queue}_{queue_hint}"
235
+ else:
236
+ channel_name = self._redis_task_queue
237
+ logger.debug(
238
+ f"Submitting job {trace_id} to queue '{channel_name}' (hint={queue_hint}) "
239
+ f"with result TTL: {ttl_for_result}"
240
+ )
241
+
221
242
  logger.debug(
222
243
  f"Submitting job {trace_id} to queue '{self._redis_task_queue}' with result TTL: {ttl_for_result}"
223
244
  )
224
245
  await self._run_bounded_to_thread(
225
246
  self._ingest_client.submit_message,
226
- channel_name=self._redis_task_queue,
247
+ channel_name=channel_name,
227
248
  message=job_spec_json,
228
249
  ttl_seconds=ttl_for_result,
229
250
  )
@@ -428,76 +428,76 @@ edges:
428
428
  # Intake
429
429
  - from: "source_stage"
430
430
  to: "metadata_injector"
431
- queue_size: 32
431
+ queue_size: 4
432
432
 
433
433
  # Document Extractors
434
434
  - from: "metadata_injector"
435
435
  to: "pdf_extractor"
436
- queue_size: 32
436
+ queue_size: 8
437
437
  - from: "pdf_extractor"
438
438
  to: "audio_extractor"
439
- queue_size: 32
439
+ queue_size: 4
440
440
  - from: "audio_extractor"
441
441
  to: "docx_extractor"
442
- queue_size: 32
442
+ queue_size: 4
443
443
  - from: "docx_extractor"
444
444
  to: "pptx_extractor"
445
- queue_size: 32
445
+ queue_size: 4
446
446
  - from: "pptx_extractor"
447
447
  to: "image_extractor"
448
- queue_size: 32
448
+ queue_size: 4
449
449
  - from: "image_extractor"
450
450
  to: "html_extractor"
451
- queue_size: 32
451
+ queue_size: 4
452
452
  - from: "html_extractor"
453
453
  to: "infographic_extractor"
454
- queue_size: 32
454
+ queue_size: 4
455
455
 
456
456
  # Primitive Extractors
457
457
  - from: "infographic_extractor"
458
458
  to: "table_extractor"
459
- queue_size: 32
459
+ queue_size: 4
460
460
  - from: "table_extractor"
461
461
  to: "chart_extractor"
462
- queue_size: 32
462
+ queue_size: 4
463
463
  - from: "chart_extractor"
464
464
  to: "image_filter"
465
- queue_size: 32
465
+ queue_size: 4
466
466
 
467
467
  # Primitive Mutators
468
468
  - from: "image_filter"
469
469
  to: "image_dedup"
470
- queue_size: 32
470
+ queue_size: 4
471
471
  - from: "image_dedup"
472
472
  to: "text_splitter"
473
- queue_size: 32
473
+ queue_size: 4
474
474
 
475
475
  # Primitive Transforms
476
476
  - from: "text_splitter"
477
477
  to: "image_caption"
478
- queue_size: 32
478
+ queue_size: 4
479
479
  - from: "image_caption"
480
480
  to: "text_embedder"
481
- queue_size: 32
481
+ queue_size: 4
482
482
  - from: "text_embedder"
483
483
  to: "image_storage"
484
- queue_size: 32
484
+ queue_size: 4
485
485
 
486
486
  # Primitive Storage
487
487
  - from: "image_storage"
488
488
  to: "embedding_storage"
489
- queue_size: 32
489
+ queue_size: 4
490
490
  - from: "embedding_storage"
491
491
  to: "broker_response"
492
- queue_size: 32
492
+ queue_size: 4
493
493
 
494
494
  # Response and Telemetry
495
495
  - from: "broker_response"
496
496
  to: "otel_tracer"
497
- queue_size: 32
497
+ queue_size: 4
498
498
  - from: "otel_tracer"
499
499
  to: "default_drain"
500
- queue_size: 32
500
+ queue_size: 4
501
501
 
502
502
  # Pipeline Runtime Configuration
503
503
  pipeline:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nv-ingest
3
- Version: 2025.10.30.dev20251030
3
+ Version: 2025.11.2.dev20251102
4
4
  Summary: Python module for multimodal document ingestion
5
5
  Author-email: Jeremy Dyer <jdyer@nvidia.com>
6
6
  License: Apache License
@@ -9,7 +9,7 @@ nv_ingest/api/v1/ingest.py,sha256=LWk3LN4lBd3uO8h30EN42g3LHCVcO00avVd5ohVK7NI,19
9
9
  nv_ingest/api/v1/metrics.py,sha256=ZGVRApYLnzc2f2C7wRgGd7deqiXan-jxfA-33a16clY,981
10
10
  nv_ingest/api/v2/README.md,sha256=VhpdjEmCyr3qIOhwqISFx9C5WezJFcxYc-NB9S98HMg,7562
11
11
  nv_ingest/api/v2/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
12
- nv_ingest/api/v2/ingest.py,sha256=fH-qpcHiWCoEC8fJowidoHOJ4BdovhK2CNYP0URFPYI,43834
12
+ nv_ingest/api/v2/ingest.py,sha256=CFLRw9y0N0AklQWsH1wYDHUjxrfkvOmE97aFcaBViWw,48525
13
13
  nv_ingest/framework/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
14
14
  nv_ingest/framework/orchestration/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
15
15
  nv_ingest/framework/orchestration/execution/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
@@ -20,7 +20,7 @@ nv_ingest/framework/orchestration/process/dependent_services.py,sha256=s0j_rsFtC
20
20
  nv_ingest/framework/orchestration/process/execution.py,sha256=P1kzpYV23e4QYrKw9Td1TCZK3CK1ENVqqnI_axRCqBk,19814
21
21
  nv_ingest/framework/orchestration/process/lifecycle.py,sha256=L5NDwnzSMQPGjqJDC8jC75L1YqWey-dtK8N_HgBzb0E,8001
22
22
  nv_ingest/framework/orchestration/process/strategies.py,sha256=Q1Q04PPseF775omeS0FoXfK187NiS_bbqTaaJRwzKn8,7972
23
- nv_ingest/framework/orchestration/process/termination.py,sha256=_aI2ZzCasGfqwu0fcvufOlr1BGAay_Noxq5pAu67gv4,3593
23
+ nv_ingest/framework/orchestration/process/termination.py,sha256=PAogFeW0FATFS6Mcp_UkZgq_SbWV18RtdZN-0NbComw,5042
24
24
  nv_ingest/framework/orchestration/ray/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
25
25
  nv_ingest/framework/orchestration/ray/edges/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
26
26
  nv_ingest/framework/orchestration/ray/edges/async_queue_edge.py,sha256=PQliU_kyGbO9o42njpb8FrDMLrbLqwZzmBNXifxyG5Y,2312
@@ -61,7 +61,7 @@ nv_ingest/framework/orchestration/ray/stages/sinks/__init__.py,sha256=wQSlVx3T14
61
61
  nv_ingest/framework/orchestration/ray/stages/sinks/default_drain.py,sha256=_USW1Vq8G2Wn-QFdPfFQCrtKG46hHeJvkEGbBxdpbVM,1488
62
62
  nv_ingest/framework/orchestration/ray/stages/sinks/message_broker_task_sink.py,sha256=QcvMQXIJ7EWIxty76Mo5Xv38Oj6X2KuS8qXQlf7E1uA,11676
63
63
  nv_ingest/framework/orchestration/ray/stages/sources/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
64
- nv_ingest/framework/orchestration/ray/stages/sources/message_broker_task_source.py,sha256=Qm9XtTNX2CcUAlZRw33BS3Ql0djcsMGp52FPA2zHu3Q,22340
64
+ nv_ingest/framework/orchestration/ray/stages/sources/message_broker_task_source.py,sha256=LrqaWpWyuiAHlpXWKYSyHZJBFegGXfNlpCXrucbK5NM,24067
65
65
  nv_ingest/framework/orchestration/ray/stages/storage/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
66
66
  nv_ingest/framework/orchestration/ray/stages/storage/image_storage.py,sha256=WZN_-3Li-izDaPtk8IMrtn2os1ckT3U8Rb2PsfOWrcI,4009
67
67
  nv_ingest/framework/orchestration/ray/stages/storage/store_embeddings.py,sha256=EUtwhSDf-qGLVEhWEInr1VaLsvpcHUSyzCmHQVai-Ps,3547
@@ -103,7 +103,7 @@ nv_ingest/framework/util/flow_control/udf_intercept.py,sha256=zQ9uuCcHLEd0P52Eiw
103
103
  nv_ingest/framework/util/service/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
104
104
  nv_ingest/framework/util/service/impl/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
105
105
  nv_ingest/framework/util/service/impl/ingest/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
106
- nv_ingest/framework/util/service/impl/ingest/redis_ingest_service.py,sha256=ZUvdGBjSfBCBq94AIUkpSWoaw6zZO3jbRSq8-VqBgwM,22826
106
+ nv_ingest/framework/util/service/impl/ingest/redis_ingest_service.py,sha256=59P-BMWnFY37GJm5w23-TMxgLhiZGZpJogC0gjDBaTA,23835
107
107
  nv_ingest/framework/util/service/meta/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
108
108
  nv_ingest/framework/util/service/meta/ingest/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
109
109
  nv_ingest/framework/util/service/meta/ingest/ingest_service_meta.py,sha256=QS3uNxWBl5dIcmIpJKNe8_TLcTUuN2vcKyHeAwa-eSo,1589
@@ -111,14 +111,14 @@ nv_ingest/framework/util/telemetry/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusX
111
111
  nv_ingest/framework/util/telemetry/global_stats.py,sha256=nq65pEEdiwjAfGiqsxG1CeQMC96O3CfQxsZuGFCY-ds,4554
112
112
  nv_ingest/pipeline/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
113
113
  nv_ingest/pipeline/default_libmode_pipeline_impl.py,sha256=MiyKe8RS18PNYwEVvrASiHFpynR_BavOe0hhVnUdbEc,15618
114
- nv_ingest/pipeline/default_pipeline_impl.py,sha256=m1m5iK9zs91zF-I_kixJvbHsNzFena7QxnNAkS8P0OM,15368
114
+ nv_ingest/pipeline/default_pipeline_impl.py,sha256=9YFS7KOlTxu9BS2Ql9SjjnnZScsg2CTSnHJpQniZhR4,15348
115
115
  nv_ingest/pipeline/ingest_pipeline.py,sha256=wHAJhqAM2s8nbY-8itVogmSU-yVN4PZONGWcKnhzgfg,17794
116
116
  nv_ingest/pipeline/pipeline_schema.py,sha256=rLZZz2It2o2hVNWrZUJU8CarrqRei1fho3ZEMkkoBcg,17940
117
117
  nv_ingest/pipeline/config/__init__.py,sha256=wQSlVx3T14ZgQAt-EPzEczQusXVW0W8yynnUaFFGE3s,143
118
118
  nv_ingest/pipeline/config/loaders.py,sha256=75Yr9WYO7j7ghvKTnYLfZXQZEH3J3VEZo5J4TunC_Us,7590
119
119
  nv_ingest/pipeline/config/replica_resolver.py,sha256=3zjh8gmepEYORFZRM4inq7GoBW0YL3gzUDiixUugjzQ,8899
120
- nv_ingest-2025.10.30.dev20251030.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
121
- nv_ingest-2025.10.30.dev20251030.dist-info/METADATA,sha256=SO4iO-GZH8mongpExTOXuwYw4T6L1kDmaI2wwof1J5o,15122
122
- nv_ingest-2025.10.30.dev20251030.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
123
- nv_ingest-2025.10.30.dev20251030.dist-info/top_level.txt,sha256=sjb0ajIsgn3YgftSjZHlYO0HjYAIIhNuXG_AmywCvaU,10
124
- nv_ingest-2025.10.30.dev20251030.dist-info/RECORD,,
120
+ nv_ingest-2025.11.2.dev20251102.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
121
+ nv_ingest-2025.11.2.dev20251102.dist-info/METADATA,sha256=PX-wHW1FYeqOgCE42S6XTVyeOaA7dcocCs40mXn12l0,15121
122
+ nv_ingest-2025.11.2.dev20251102.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
123
+ nv_ingest-2025.11.2.dev20251102.dist-info/top_level.txt,sha256=sjb0ajIsgn3YgftSjZHlYO0HjYAIIhNuXG_AmywCvaU,10
124
+ nv_ingest-2025.11.2.dev20251102.dist-info/RECORD,,