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.
- nv_ingest/api/v2/ingest.py +118 -5
- nv_ingest/framework/orchestration/process/termination.py +49 -9
- nv_ingest/framework/orchestration/ray/stages/sources/message_broker_task_source.py +41 -8
- nv_ingest/framework/util/service/impl/ingest/redis_ingest_service.py +22 -1
- nv_ingest/pipeline/default_pipeline_impl.py +20 -20
- {nv_ingest-2025.10.30.dev20251030.dist-info → nv_ingest-2025.11.2.dev20251102.dist-info}/METADATA +1 -1
- {nv_ingest-2025.10.30.dev20251030.dist-info → nv_ingest-2025.11.2.dev20251102.dist-info}/RECORD +10 -10
- {nv_ingest-2025.10.30.dev20251030.dist-info → nv_ingest-2025.11.2.dev20251102.dist-info}/WHEEL +0 -0
- {nv_ingest-2025.10.30.dev20251030.dist-info → nv_ingest-2025.11.2.dev20251102.dist-info}/licenses/LICENSE +0 -0
- {nv_ingest-2025.10.30.dev20251030.dist-info → nv_ingest-2025.11.2.dev20251102.dist-info}/top_level.txt +0 -0
nv_ingest/api/v2/ingest.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
804
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
28
|
-
import sys
|
|
45
|
+
import logging as _logging # re-import safe even if earlier failed
|
|
29
46
|
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
93
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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=
|
|
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:
|
|
431
|
+
queue_size: 4
|
|
432
432
|
|
|
433
433
|
# Document Extractors
|
|
434
434
|
- from: "metadata_injector"
|
|
435
435
|
to: "pdf_extractor"
|
|
436
|
-
queue_size:
|
|
436
|
+
queue_size: 8
|
|
437
437
|
- from: "pdf_extractor"
|
|
438
438
|
to: "audio_extractor"
|
|
439
|
-
queue_size:
|
|
439
|
+
queue_size: 4
|
|
440
440
|
- from: "audio_extractor"
|
|
441
441
|
to: "docx_extractor"
|
|
442
|
-
queue_size:
|
|
442
|
+
queue_size: 4
|
|
443
443
|
- from: "docx_extractor"
|
|
444
444
|
to: "pptx_extractor"
|
|
445
|
-
queue_size:
|
|
445
|
+
queue_size: 4
|
|
446
446
|
- from: "pptx_extractor"
|
|
447
447
|
to: "image_extractor"
|
|
448
|
-
queue_size:
|
|
448
|
+
queue_size: 4
|
|
449
449
|
- from: "image_extractor"
|
|
450
450
|
to: "html_extractor"
|
|
451
|
-
queue_size:
|
|
451
|
+
queue_size: 4
|
|
452
452
|
- from: "html_extractor"
|
|
453
453
|
to: "infographic_extractor"
|
|
454
|
-
queue_size:
|
|
454
|
+
queue_size: 4
|
|
455
455
|
|
|
456
456
|
# Primitive Extractors
|
|
457
457
|
- from: "infographic_extractor"
|
|
458
458
|
to: "table_extractor"
|
|
459
|
-
queue_size:
|
|
459
|
+
queue_size: 4
|
|
460
460
|
- from: "table_extractor"
|
|
461
461
|
to: "chart_extractor"
|
|
462
|
-
queue_size:
|
|
462
|
+
queue_size: 4
|
|
463
463
|
- from: "chart_extractor"
|
|
464
464
|
to: "image_filter"
|
|
465
|
-
queue_size:
|
|
465
|
+
queue_size: 4
|
|
466
466
|
|
|
467
467
|
# Primitive Mutators
|
|
468
468
|
- from: "image_filter"
|
|
469
469
|
to: "image_dedup"
|
|
470
|
-
queue_size:
|
|
470
|
+
queue_size: 4
|
|
471
471
|
- from: "image_dedup"
|
|
472
472
|
to: "text_splitter"
|
|
473
|
-
queue_size:
|
|
473
|
+
queue_size: 4
|
|
474
474
|
|
|
475
475
|
# Primitive Transforms
|
|
476
476
|
- from: "text_splitter"
|
|
477
477
|
to: "image_caption"
|
|
478
|
-
queue_size:
|
|
478
|
+
queue_size: 4
|
|
479
479
|
- from: "image_caption"
|
|
480
480
|
to: "text_embedder"
|
|
481
|
-
queue_size:
|
|
481
|
+
queue_size: 4
|
|
482
482
|
- from: "text_embedder"
|
|
483
483
|
to: "image_storage"
|
|
484
|
-
queue_size:
|
|
484
|
+
queue_size: 4
|
|
485
485
|
|
|
486
486
|
# Primitive Storage
|
|
487
487
|
- from: "image_storage"
|
|
488
488
|
to: "embedding_storage"
|
|
489
|
-
queue_size:
|
|
489
|
+
queue_size: 4
|
|
490
490
|
- from: "embedding_storage"
|
|
491
491
|
to: "broker_response"
|
|
492
|
-
queue_size:
|
|
492
|
+
queue_size: 4
|
|
493
493
|
|
|
494
494
|
# Response and Telemetry
|
|
495
495
|
- from: "broker_response"
|
|
496
496
|
to: "otel_tracer"
|
|
497
|
-
queue_size:
|
|
497
|
+
queue_size: 4
|
|
498
498
|
- from: "otel_tracer"
|
|
499
499
|
to: "default_drain"
|
|
500
|
-
queue_size:
|
|
500
|
+
queue_size: 4
|
|
501
501
|
|
|
502
502
|
# Pipeline Runtime Configuration
|
|
503
503
|
pipeline:
|
{nv_ingest-2025.10.30.dev20251030.dist-info → nv_ingest-2025.11.2.dev20251102.dist-info}/RECORD
RENAMED
|
@@ -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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
121
|
-
nv_ingest-2025.
|
|
122
|
-
nv_ingest-2025.
|
|
123
|
-
nv_ingest-2025.
|
|
124
|
-
nv_ingest-2025.
|
|
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,,
|
{nv_ingest-2025.10.30.dev20251030.dist-info → nv_ingest-2025.11.2.dev20251102.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|