From 9d411e45a9d509c2d10a7cf1620ee4ee2364210e Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Tue, 24 Feb 2026 01:54:48 +0530 Subject: [PATCH 01/52] fix(observability): use hardcoded GenAI attribute keys instead of deprecated SpanAttributes (#377) Signed-off-by: Swapnil Masurekar --- .../langchain_callback_handler.py | 53 ++++---- .../test_langchain_callback_handler.py | 119 ++++++++++++------ 2 files changed, 111 insertions(+), 61 deletions(-) diff --git a/src/nvidia_rag/utils/observability/langchain_callback_handler.py b/src/nvidia_rag/utils/observability/langchain_callback_handler.py index dcdd7ba23..a35a71bab 100644 --- a/src/nvidia_rag/utils/observability/langchain_callback_handler.py +++ b/src/nvidia_rag/utils/observability/langchain_callback_handler.py @@ -40,6 +40,17 @@ from .otel_metrics import OtelMetrics +# Hardcoded attribute keys (replacing deprecated SpanAttributes constants) +GEN_AI_PROMPTS = "gen_ai.prompt" +GEN_AI_COMPLETIONS = "gen_ai.completion" +LLM_REQUEST_MODEL = "gen_ai.request.model" +LLM_RESPONSE_MODEL = "gen_ai.response.model" +# Missing in opentelemetry.semconv_ai SpanAttributes (use llm.* to match existing semconv) +LLM_REQUEST_MAX_TOKENS = "llm.request.max_tokens" +LLM_REQUEST_TEMPERATURE = "llm.request.temperature" +LLM_REQUEST_TOP_P = "llm.request.top_p" +LLM_SYSTEM = "llm.system" + class Config: exception_logger = None @@ -137,9 +148,9 @@ def _set_request_params(span, kwargs, span_holder: SpanHolder): else: model = "unknown" - span.set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model) + span.set_attribute(LLM_REQUEST_MODEL, model) # response is not available for LLM requests (as opposed to chat) - span.set_attribute(SpanAttributes.LLM_RESPONSE_MODEL, model) + span.set_attribute(LLM_RESPONSE_MODEL, model) if "invocation_params" in kwargs: params = ( @@ -150,13 +161,11 @@ def _set_request_params(span, kwargs, span_holder: SpanHolder): _set_span_attribute( span, - SpanAttributes.LLM_REQUEST_MAX_TOKENS, + LLM_REQUEST_MAX_TOKENS, params.get("max_tokens") or params.get("max_new_tokens"), ) - _set_span_attribute( - span, SpanAttributes.LLM_REQUEST_TEMPERATURE, params.get("temperature") - ) - _set_span_attribute(span, SpanAttributes.LLM_REQUEST_TOP_P, params.get("top_p")) + _set_span_attribute(span, LLM_REQUEST_TEMPERATURE, params.get("temperature")) + _set_span_attribute(span, LLM_REQUEST_TOP_P, params.get("top_p")) def _set_llm_request( @@ -171,11 +180,11 @@ def _set_llm_request( if should_send_prompts(): for i, msg in enumerate(prompts): span.set_attribute( - f"{SpanAttributes.LLM_PROMPTS}.{i}.role", + f"{GEN_AI_PROMPTS}.{i}.role", "user", ) span.set_attribute( - f"{SpanAttributes.LLM_PROMPTS}.{i}.content", + f"{GEN_AI_PROMPTS}.{i}.content", msg, ) @@ -207,18 +216,18 @@ def _set_chat_request( for message in messages: for msg in message: span.set_attribute( - f"{SpanAttributes.LLM_PROMPTS}.{i}.role", + f"{GEN_AI_PROMPTS}.{i}.role", _message_type_to_role(msg.type), ) # if msg.content is string if isinstance(msg.content, str): span.set_attribute( - f"{SpanAttributes.LLM_PROMPTS}.{i}.content", + f"{GEN_AI_PROMPTS}.{i}.content", msg.content, ) else: span.set_attribute( - f"{SpanAttributes.LLM_PROMPTS}.{i}.content", + f"{GEN_AI_PROMPTS}.{i}.content", json.dumps(msg.content, cls=CallbackFilteredJSONEncoder), ) i += 1 @@ -252,7 +261,7 @@ def _set_chat_response(span: Span, response: LLMResult) -> None: ) total_tokens = input_tokens + output_tokens - prefix = f"{SpanAttributes.LLM_COMPLETIONS}.{i}" + prefix = f"{GEN_AI_COMPLETIONS}.{i}" if hasattr(generation, "text") and generation.text != "": span.set_attribute( f"{prefix}.content", @@ -317,11 +326,11 @@ def _set_chat_response(span: Span, response: LLMResult) -> None: if input_tokens > 0 or output_tokens > 0 or total_tokens > 0: span.set_attribute( - SpanAttributes.LLM_USAGE_PROMPT_TOKENS, + "gen_ai.usage.input_tokens", input_tokens, ) span.set_attribute( - SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, + "gen_ai.usage.output_tokens", output_tokens, ) span.set_attribute( @@ -462,7 +471,7 @@ def _create_llm_span( entity_path=entity_path, metadata=metadata, ) - span.set_attribute(SpanAttributes.LLM_SYSTEM, "Langchain") + span.set_attribute(LLM_SYSTEM, "Langchain") span.set_attribute(SpanAttributes.LLM_REQUEST_TYPE, request_type.value) return span @@ -650,10 +659,10 @@ def on_llm_end( "model_name" ) or response.llm_output.get("model_id") if model_name is not None: - span.set_attribute(SpanAttributes.LLM_RESPONSE_MODEL, model_name) + span.set_attribute(LLM_RESPONSE_MODEL, model_name) if self.spans[run_id].request_model is None: - span.set_attribute(SpanAttributes.LLM_REQUEST_MODEL, model_name) + span.set_attribute(LLM_REQUEST_MODEL, model_name) token_usage = (response.llm_output or {}).get("token_usage") or ( response.llm_output or {} @@ -673,12 +682,8 @@ def on_llm_end( prompt_tokens + completion_tokens ) - _set_span_attribute( - span, SpanAttributes.LLM_USAGE_PROMPT_TOKENS, prompt_tokens - ) - _set_span_attribute( - span, SpanAttributes.LLM_USAGE_COMPLETION_TOKENS, completion_tokens - ) + _set_span_attribute(span, "gen_ai.usage.input_tokens", prompt_tokens) + _set_span_attribute(span, "gen_ai.usage.output_tokens", completion_tokens) _set_span_attribute( span, SpanAttributes.LLM_USAGE_TOTAL_TOKENS, total_tokens ) diff --git a/tests/unit/test_observability/test_langchain_callback_handler.py b/tests/unit/test_observability/test_langchain_callback_handler.py index 054f5d18d..80511caab 100644 --- a/tests/unit/test_observability/test_langchain_callback_handler.py +++ b/tests/unit/test_observability/test_langchain_callback_handler.py @@ -6,6 +6,12 @@ import pytest from langchain_core.messages import AIMessageChunk from langchain_core.outputs import Generation, LLMResult +from opentelemetry.semconv_ai import ( + LLMRequestTypeValues, + SpanAttributes, + SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY, + TraceloopSpanKindValues, +) class SpanMock: @@ -58,7 +64,9 @@ def handler(): def test_on_chat_model_start_sets_input_words_and_prompts(handler): - from nvidia_rag.utils.observability.langchain_callback_handler import SpanAttributes + from nvidia_rag.utils.observability.langchain_callback_handler import ( + GEN_AI_PROMPTS, + ) run_id = uuid4() messages = [ @@ -78,44 +86,8 @@ def test_on_chat_model_start_sets_input_words_and_prompts(handler): assert handler.total_input_words == 4 # span should be created and attributes recorded assert run_id in handler.spans - span = handler.spans[run_id].span - # Check that at least one prompt attribute key prefix was used - prompt_prefix = f"{SpanAttributes.LLM_PROMPTS}." - prompt_keys = [k for k, _ in span.attributes if k.startswith(prompt_prefix)] - assert len(prompt_keys) >= 2 - - -def test_on_llm_start_and_end_sets_token_usage_and_ends_span(handler): - run_id = uuid4() - handler.on_llm_start( - serialized={"kwargs": {"name": "llm"}}, - prompts=["What is ML?"], - run_id=run_id, - ) - # Build a minimal valid LLMResult - gen = Generation( - text="Answer", - generation_info={"finish_reason": "stop"}, - ) - - llm_result = LLMResult( - generations=[[gen]], - llm_output={ - "model_name": "test-model", - "usage": {"prompt_tokens": 5, "completion_tokens": 7, "total_tokens": 12}, - }, - ) - - handler.on_llm_end(response=llm_result, run_id=run_id) - - span = handler.spans[run_id].span - # Verify span ended - assert span.ended is True - # Verify some token usage attributes were set from llm_output usage - attr_keys = [k for k, _ in span.attributes] - assert any("usage" in k.lower() for k in attr_keys) def test_on_chain_end_updates_avg_words_per_chunk(handler): @@ -156,3 +128,76 @@ def test_on_chain_end_updates_llm_tokens(handler): # Expect update_llm_tokens called with input words from chat (3) and output words (2) assert handler.metrics.token_calls[-1] == (3, 2) + + +# SpanAttributes from opentelemetry.semconv_ai still used in langchain_callback_handler.py. +# Missing/deprecated ones (LLM_REQUEST_MODEL, LLM_RESPONSE_MODEL, LLM_REQUEST_MAX_TOKENS, +# LLM_REQUEST_TEMPERATURE, LLM_REQUEST_TOP_P, LLM_SYSTEM) are hardcoded in the handler. +SPAN_ATTRIBUTES_USED = [ + "LLM_REQUEST_FUNCTIONS", + "LLM_USAGE_TOTAL_TOKENS", + "TRACELOOP_WORKFLOW_NAME", + "TRACELOOP_ENTITY_PATH", + "TRACELOOP_SPAN_KIND", + "TRACELOOP_ENTITY_NAME", + "LLM_REQUEST_TYPE", + "TRACELOOP_ENTITY_INPUT", + "TRACELOOP_ENTITY_OUTPUT", +] + + +def test_semconv_ai_span_attributes_exist_and_not_deprecated(): + """Ensure all SpanAttributes used in langchain_callback_handler exist and are non-empty strings.""" + for attr_name in SPAN_ATTRIBUTES_USED: + assert hasattr( + SpanAttributes, attr_name + ), f"SpanAttributes.{attr_name} is missing or was removed from opentelemetry.semconv_ai" + value = getattr(SpanAttributes, attr_name) + assert isinstance( + value, str + ), f"SpanAttributes.{attr_name} should be a string, got {type(value).__name__}" + assert ( + len(value) > 0 + ), f"SpanAttributes.{attr_name} is empty (possibly deprecated or placeholder)" + + +def test_semconv_ai_llm_request_type_values_used(): + """Ensure LLMRequestTypeValues used in the handler (CHAT, COMPLETION) exist.""" + assert hasattr(LLMRequestTypeValues, "CHAT") + assert hasattr(LLMRequestTypeValues, "COMPLETION") + assert isinstance(LLMRequestTypeValues.CHAT.value, str) + assert isinstance(LLMRequestTypeValues.COMPLETION.value, str) + + +def test_semconv_ai_traceloop_span_kind_values_used(): + """Ensure TraceloopSpanKindValues used in the handler (WORKFLOW, TASK, TOOL) exist.""" + for kind in ("WORKFLOW", "TASK", "TOOL"): + assert hasattr( + TraceloopSpanKindValues, kind + ), f"TraceloopSpanKindValues.{kind} is missing" + assert isinstance(getattr(TraceloopSpanKindValues, kind).value, str) + + +def test_langchain_callback_handler_imports_and_constants(): + """Import the handler module and verify hardcoded attribute constants and semconv_ai key.""" + from nvidia_rag.utils.observability.langchain_callback_handler import ( + GEN_AI_COMPLETIONS, + GEN_AI_PROMPTS, + LLM_REQUEST_MAX_TOKENS, + LLM_REQUEST_MODEL, + LLM_REQUEST_TEMPERATURE, + LLM_REQUEST_TOP_P, + LLM_RESPONSE_MODEL, + LLM_SYSTEM, + ) + + assert GEN_AI_PROMPTS == "gen_ai.prompt" + assert GEN_AI_COMPLETIONS == "gen_ai.completion" + assert LLM_REQUEST_MODEL == "gen_ai.request.model" + assert LLM_RESPONSE_MODEL == "gen_ai.response.model" + assert LLM_REQUEST_MAX_TOKENS == "llm.request.max_tokens" + assert LLM_REQUEST_TEMPERATURE == "llm.request.temperature" + assert LLM_REQUEST_TOP_P == "llm.request.top_p" + assert LLM_SYSTEM == "llm.system" + assert SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY is not None + assert isinstance(SUPPRESS_LANGUAGE_MODEL_INSTRUMENTATION_KEY, str) From 467f86a0d00f2d0219888e7ea13792b442faf05a Mon Sep 17 00:00:00 2001 From: niyatisingal Date: Thu, 26 Feb 2026 13:07:00 +0530 Subject: [PATCH 02/52] Fixing nemoguardrails config (#382) Signed-off-by: Niyati Singal --- .../nemoguardrails/config-store/nemoguard_cloud/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/compose/nemoguardrails/config-store/nemoguard_cloud/config.yml b/deploy/compose/nemoguardrails/config-store/nemoguard_cloud/config.yml index 1e014fc9f..17200db05 100644 --- a/deploy/compose/nemoguardrails/config-store/nemoguard_cloud/config.yml +++ b/deploy/compose/nemoguardrails/config-store/nemoguard_cloud/config.yml @@ -17,5 +17,7 @@ rails: - content safety check input $model=content_safety - topic safety check input $model=topic_control output: + streaming: + enabled: true flows: - content safety check output $model=content_safety \ No newline at end of file From 5e1b9ed38d8c0bbdb932b6db3be9a7272453e8e7 Mon Sep 17 00:00:00 2001 From: kumar-punit Date: Thu, 26 Feb 2026 13:45:14 +0530 Subject: [PATCH 03/52] Added MIG Slice Support for rtx6000pro (#379) * Added MIG Slice support for RTX 6000 pro Signed-off-by: Punit Kumar * Changed to default config in MIG slicing in rtx6000pro config --------- Signed-off-by: Punit Kumar Co-authored-by: niyatisingal --- .../{mig-config.yaml => mig-config-h100.yaml} | 0 .../helm/mig-slicing/mig-config-rtx6000.yaml | 26 ++++ .../{values-mig.yaml => values-mig-h100.yaml} | 0 .../helm/mig-slicing/values-mig-rtx6000.yaml | 114 ++++++++++++++++++ docs/mig-deployment.md | 40 +++--- docs/vlm.md | 2 +- 6 files changed, 160 insertions(+), 22 deletions(-) rename deploy/helm/mig-slicing/{mig-config.yaml => mig-config-h100.yaml} (100%) create mode 100644 deploy/helm/mig-slicing/mig-config-rtx6000.yaml rename deploy/helm/mig-slicing/{values-mig.yaml => values-mig-h100.yaml} (100%) create mode 100644 deploy/helm/mig-slicing/values-mig-rtx6000.yaml diff --git a/deploy/helm/mig-slicing/mig-config.yaml b/deploy/helm/mig-slicing/mig-config-h100.yaml similarity index 100% rename from deploy/helm/mig-slicing/mig-config.yaml rename to deploy/helm/mig-slicing/mig-config-h100.yaml diff --git a/deploy/helm/mig-slicing/mig-config-rtx6000.yaml b/deploy/helm/mig-slicing/mig-config-rtx6000.yaml new file mode 100644 index 000000000..14272b497 --- /dev/null +++ b/deploy/helm/mig-slicing/mig-config-rtx6000.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: custom-mig-config +data: + config.yaml: | + version: v1 + mig-configs: + all-disabled: + - devices: all + mig-enabled: false + + custom-rtx6000-4x1g24-2x1g24-1x2g48-1x4g96: + - devices: [0] + mig-enabled: true + mig-devices: + "1g.24gb": 4 + - devices: [1] + mig-enabled: true + mig-devices: + "1g.24gb": 2 + "2g.48gb": 1 + - devices: [2] + mig-enabled: true + mig-devices: + "4g.96gb": 1 diff --git a/deploy/helm/mig-slicing/values-mig.yaml b/deploy/helm/mig-slicing/values-mig-h100.yaml similarity index 100% rename from deploy/helm/mig-slicing/values-mig.yaml rename to deploy/helm/mig-slicing/values-mig-h100.yaml diff --git a/deploy/helm/mig-slicing/values-mig-rtx6000.yaml b/deploy/helm/mig-slicing/values-mig-rtx6000.yaml new file mode 100644 index 000000000..e7ae285da --- /dev/null +++ b/deploy/helm/mig-slicing/values-mig-rtx6000.yaml @@ -0,0 +1,114 @@ +# MIG-optimized resource configuration for RAG Blueprint +# This file only overrides GPU resource requirements to use MIG slices + +# NV-Ingest configuration +nv-ingest: + # Milvus - uses 1g.24gb MIG slice + milvus: + standalone: + resources: + limits: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + requests: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + + # NV-Ingest NIM Operator overrides + nimOperator: + # Page Elements - uses 1g.24gb + page_elements: + resources: + limits: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + requests: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + storage: + pvc: + storageClass: "" + + # Graphic Elements - uses 1g.24gb + graphic_elements: + resources: + limits: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + requests: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + storage: + pvc: + storageClass: "" + + # Table Structure - uses 1g.24gb + table_structure: + resources: + limits: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + requests: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + storage: + pvc: + storageClass: "" + + # OCR - uses 2g.48gb (larger slice) + nemoretriever_ocr_v1: + resources: + limits: + nvidia.com/gpu: "0" + nvidia.com/mig-2g.48gb: 1 + requests: + nvidia.com/gpu: "0" + nvidia.com/mig-2g.48gb: 1 + storage: + pvc: + storageClass: "" +# Main NIM Operator overrides for MIG +nimOperator: + # LLM - uses 4g.96gb + nim-llm: + resources: + limits: + nvidia.com/gpu: "0" + nvidia.com/mig-4g.96gb: 1 + requests: + nvidia.com/gpu: "0" + nvidia.com/mig-4g.96gb: 1 + storage: + pvc: + storageClass: "" + model: + engine: tensorrt_llm + precision: "fp8" + qosProfile: "throughput" + tensorParallelism: "1" + gpus: + - product: "rtx6000_blackwell_sv" + # Embedding - uses 1g.24gb + nvidia-nim-llama-32-nv-embedqa-1b-v2: + resources: + limits: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + requests: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + storage: + pvc: + storageClass: "" + # Reranking - uses 1g.24gb + nvidia-nim-llama-32-nv-rerankqa-1b-v2: + resources: + limits: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + requests: + nvidia.com/gpu: "0" + nvidia.com/mig-1g.24gb: 1 + storage: + pvc: + storageClass: "" diff --git a/docs/mig-deployment.md b/docs/mig-deployment.md index bc4793ab1..f7eebdf3d 100644 --- a/docs/mig-deployment.md +++ b/docs/mig-deployment.md @@ -15,10 +15,10 @@ refer to the [MIG Supported Hardware List](https://docs.nvidia.com/datacenter/te Before you deploy, verify that you have the following: -* A Kubernetes cluster with NVIDIA H100 GPUs +* A Kubernetes cluster with NVIDIA H100 or RTX PRO 6000 GPUs :::{note} - This section showcases MIG support for `NVIDIA H100 80GB HBM3` GPU. The MIG profiles used in the `mig-config.yaml` are specific to this GPU. + This section showcases MIG support for `NVIDIA H100 80GB HBM3` GPU. The MIG profiles used in the `mig-config-h100.yaml` are specific to this GPU. Refer to the [MIG User Guide](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/) for MIG profiles of other GPU types. ::: @@ -99,7 +99,7 @@ For monitoring deployment progress, refer to [Deploy on Kubernetes with Helm](./ ## Step 2: Apply the MIG configuration -Edit the MIG configuration file [`mig-config.yaml`](../deploy/helm/mig-slicing/mig-config.yaml) to adjust the slicing pattern as needed. +Edit the MIG configuration file [`mig-config-h100.yaml`](../deploy/helm/mig-slicing/mig-config-h100.yaml) to adjust the slicing pattern as needed. The following example enables a custom configuration with mixed MIG slice sizes on the same GPU. @@ -139,7 +139,7 @@ data: Apply the custom MIG configuration configMap to the node and update the ClusterPolicy, by running the following code. ```bash -kubectl apply -n nvidia-gpu-operator -f mig-slicing/mig-config.yaml +kubectl apply -n nvidia-gpu-operator -f mig-slicing/mig-config-h100.yaml kubectl patch clusterpolicies.nvidia.com/cluster-policy \ --type='json' \ -p='[{"op":"replace", "path":"/spec/migManager/config/name", "value":"custom-mig-config"}]' @@ -151,6 +151,17 @@ Label the node with MIG configuration, by running the following code. kubectl label nodes nvidia.com/mig.config=custom-7x1g10-2x1g20-1x3g40-1x7g80 --overwrite ``` +:::{important} +**For NVIDIA RTX6000 Pro Deployments:** + +Use [`mig-config-rtx6000.yaml`](../deploy/helm/mig-slicing/mig-config-rtx6000.yaml) instead: + +```bash +kubectl apply -n nvidia-gpu-operator -f mig-slicing/mig-config-rtx6000.yaml +kubectl label nodes nvidia.com/mig.config=custom-rtx6000-4x1g24-2x1g24-1x2g48-1x4g96 --overwrite +``` +::: + Verify that the MIG configuration is successfully applied, by running the following code. ```bash @@ -179,34 +190,21 @@ helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprin --password "${NGC_API_KEY}" \ --set imagePullSecret.password=$NGC_API_KEY \ --set ngcApiSecret.password=$NGC_API_KEY \ - -f mig-slicing/values-mig.yaml + -f mig-slicing/values-mig-h100.yaml ``` :::{important} **For NVIDIA RTX6000 Pro Deployments:** -If you are deploying on NVIDIA RTX6000 Pro GPUs (instead of H100 GPUs), you need to configure the NIM LLM model profile. The required configuration is already present but commented out in the [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml) file. - -Uncomment and modify the following section under `nimOperator.nim-llm.model` in [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml): -```yaml -model: - engine: tensorrt_llm - precision: "fp8" - qosProfile: "throughput" - tensorParallelism: "1" - gpus: - - product: "rtx6000_blackwell_sv" -``` +If you are deploying on NVIDIA RTX6000 Pro GPUs (instead of H100 GPUs), use [`values-mig-rtx6000.yaml`](../deploy/helm/mig-slicing/values-mig-rtx6000.yaml) and [`mig-config-rtx6000.yaml`](../deploy/helm/mig-slicing/mig-config-rtx6000.yaml) which include the RTX6000-specific MIG profiles and NIM LLM model configuration. -Then install using the modified values.yaml along with MIG values: ```sh helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.4.0.tgz \ --username '$oauthtoken' \ --password "${NGC_API_KEY}" \ --set imagePullSecret.password=$NGC_API_KEY \ --set ngcApiSecret.password=$NGC_API_KEY \ - -f values.yaml \ - -f mig-slicing/values-mig.yaml + -f mig-slicing/values-mig-rtx6000.yaml ``` ::: @@ -303,7 +301,7 @@ GPU 3: NVIDIA H100 80GB HBM3 (UUID: ...) * Ensure you have the correct MIG strategy (`mixed`) configured. * Verify that `nvidia.com/mig.config.state` is `success` before deploying. -* Customize `values-mig.yaml` to specify the correct MIG GPU resource requests for each pod. +* Customize `values-mig-h100.yaml` or `values-mig-rtx6000.yaml` to specify the correct MIG GPU resource requests for each pod. diff --git a/docs/vlm.md b/docs/vlm.md index 4a61b2f52..64c6af176 100644 --- a/docs/vlm.md +++ b/docs/vlm.md @@ -124,7 +124,7 @@ Continue with [Deploy with Docker (NVIDIA-Hosted Models)](deploy-docker-nvidia-h ## Enable VLM with Helm :::{note} -**GPU requirements for Helm**: VLM uses the same GPU normally assigned to LLM (GPU 1). With MIG slicing, assign a dedicated MIG slice to the VLM—see [mig-deployment.md](mig-deployment.md) and [values-mig.yaml](../deploy/helm/mig-slicing/values-mig.yaml). To run both VLM and LLM simultaneously, an additional GPU is required. +**GPU requirements for Helm**: VLM uses the same GPU normally assigned to LLM (GPU 1). With MIG slicing, assign a dedicated MIG slice to the VLM—see [mig-deployment.md](mig-deployment.md) and [values-mig-h100.yaml](../deploy/helm/mig-slicing/values-mig-h100.yaml) or [values-mig-rtx6000.yaml](../deploy/helm/mig-slicing/values-mig-rtx6000.yaml). To run both VLM and LLM simultaneously, an additional GPU is required. ::: 1. In [values.yaml](../deploy/helm/nvidia-blueprint-rag/values.yaml), under the `rag-server` `envVars` section, set: From 685c1d511142a5b6f0f3240ef2f7d4d8702d453a Mon Sep 17 00:00:00 2001 From: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:25:45 +0530 Subject: [PATCH 04/52] Port release-v2.4.0 to release-v2.5.0 and update container versions (#385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changes to docs per bug 5767861 (#328) * Updated launchable with v2.4.0 tag (#318) * updated support matrix (#321) * Document the end‑to‑end flow from query to answer and show how to measure time spent in each stage of the RAG pipeline. (#317) * adding oberservablility * Update docs/debugging.md Co-authored-by: nkmcalli * Update docs/observability.md Co-authored-by: nkmcalli * Add query-to-answer-pipeline doc and observability/debugging updates * Trigger CI * getting build to kick in for observability file * Fix typos in query-to-answer-pipeline.md and ensure file in PR for link check * get rid of PULL_REQUEST_SUMMARY --------- Co-authored-by: nkmcalli * fixed files associated with build (#322) * Add multimodal query integration tests to CI pipeline * changes to docs per bug 5767861 * updated files per bug 5880717 (#327) * updated files per bug 5880717 * Update CONTRIBUTING.md * Update README.md * Update python-client.md * Update readme.md * Update readme.md * Update docs/deploy-helm.md Co-authored-by: nkmcalli * Update docs/deploy-helm.md Co-authored-by: nkmcalli --------- Co-authored-by: rkharwar-nv Co-authored-by: nkmcalli Co-authored-by: Pranjal Doshi Co-authored-by: nv-pranjald <150428320+nv-pranjald@users.noreply.github.com> * Fix workflow rule and doc bugs (#331) * Revert back milvus version in conf.md to v2.6.5 * Modify workflow to run on any branch * Fix workflow push rule to run on protected branches * Add files via upload (#326) Found an error in the Q&A section where images in the citation were not being printed. * Doc bug fixes (#339) * updated helm instructions (#333) * updated helm instructions * Update deploy-helm.md * fix broken image link (#334) * Add release note for Audio model deployment on Kubernetes on RTX‑6000 Pro is not supported in this release.heiss/5863956a (#335) * Add release note for Audio model deployment on Kubernetes on RTX‑6000 Pro is not supported in this release. * Add release note for Audio model deployment on Kubernetes on RTX‑6000 Pro is not supported in this release. * Fix broken image link in observability file * Fix CPU seach with GPU index doc * Fix VLLM profile instruction for nemotron-3-nano --------- Co-authored-by: Kurt Heiss * Updated troubleshoot documentation for Elasticsearch connection timeout (#341) Signed-off-by: Swapnil Masurekar * updated path to image files so that html output is rendered correctly (#363) * Updated helm instructions for mig-deployment prerequisites (#364) * Updated helm instructions for mig-deployment * Update mig-deployment.md * Doc enhancement for noteboook (#361) * Doc enhancement for noteboook * Update release notes * Update launchable.ipynb (#365) Updated branch name State name changed from "FAILURE"->"FAILED" * Fix typo in release notes --------- Co-authored-by: rkharwar-nv * fixed links in deploy-helm and mig-deploymnent (#367) * update artifacts to GA version for v2.4.0 release (#359) * updated files according to style guide (#369) * Revert deploy-helm and mig-deployment to pre-11a31a4 versions (#372) * Fix release date in changelog (#373) * Bump up version to 2.5.0 --------- Signed-off-by: Swapnil Masurekar Co-authored-by: Kurt Heiss Co-authored-by: rkharwar-nv Co-authored-by: nkmcalli Co-authored-by: Pranjal Doshi Co-authored-by: nv-pranjald <150428320+nv-pranjald@users.noreply.github.com> Co-authored-by: Swapnil Masurekar --- ci/publish_wheel.sh | 4 +- .../docker-compose-ingestor-server.yaml | 2 +- deploy/compose/docker-compose-rag-server.yaml | 4 +- deploy/helm/nvidia-blueprint-rag/Chart.yaml | 4 +- deploy/helm/nvidia-blueprint-rag/values.yaml | 6 +-- deploy/workbench/compose.yaml | 6 +-- docs/conf.py | 2 +- docs/deploy-helm.md | 8 ++-- docs/mig-deployment.md | 8 ++-- docs/notebooks.md | 48 ------------------- docs/observability.md | 17 ++++--- docs/project.json | 2 +- docs/release-notes.md | 4 +- docs/text_only_ingest.md | 2 +- docs/versions1.json | 4 +- examples/rag_react_agent/uv.lock | 2 +- notebooks/launchable.ipynb | 2 +- notebooks/rag_library_usage.ipynb | 3 +- pyproject.toml | 2 +- .../test_cases/multimodal_query.py | 2 +- uv.lock | 2 +- 21 files changed, 43 insertions(+), 91 deletions(-) diff --git a/ci/publish_wheel.sh b/ci/publish_wheel.sh index f59165fee..711abb462 100755 --- a/ci/publish_wheel.sh +++ b/ci/publish_wheel.sh @@ -25,8 +25,8 @@ if [ -n "$ARTIFACTORY_VERSION" ]; then echo "Using custom Artifactory version: $ARTIFACTORY_VERSION" ARTIFACTORY_VERSION_FINAL=$ARTIFACTORY_VERSION else - echo "Using default Artifactory version: 2.4.0.dev" - ARTIFACTORY_VERSION_FINAL="2.4.0.dev" + echo "Using default Artifactory version: 2.5.0.dev" + ARTIFACTORY_VERSION_FINAL="2.5.0.dev" fi # Build first wheel for GitLab Package Registry diff --git a/deploy/compose/docker-compose-ingestor-server.yaml b/deploy/compose/docker-compose-ingestor-server.yaml index 964a9c02c..fec1fb5aa 100644 --- a/deploy/compose/docker-compose-ingestor-server.yaml +++ b/deploy/compose/docker-compose-ingestor-server.yaml @@ -3,7 +3,7 @@ services: # Main ingestor server which is responsible for ingestion ingestor-server: container_name: ingestor-server - image: nvcr.io/nvstaging/blueprint/ingestor-server:${TAG:-2.4.0} + image: nvcr.io/nvstaging/blueprint/ingestor-server:${TAG:-2.5.0} build: # Set context to repo's root directory context: ../../ diff --git a/deploy/compose/docker-compose-rag-server.yaml b/deploy/compose/docker-compose-rag-server.yaml index b3e20808f..b0c7d25bd 100644 --- a/deploy/compose/docker-compose-rag-server.yaml +++ b/deploy/compose/docker-compose-rag-server.yaml @@ -3,7 +3,7 @@ services: # Main orchestrator server which stiches together all calls to different services to fulfill the user request rag-server: container_name: rag-server - image: nvcr.io/nvstaging/blueprint/rag-server:${TAG:-2.4.0} + image: nvcr.io/nvstaging/blueprint/rag-server:${TAG:-2.5.0} build: # Set context to repo's root directory context: ../../ @@ -211,7 +211,7 @@ services: # Sample UI container which interacts with APIs exposed by rag-server container rag-frontend: container_name: rag-frontend - image: nvcr.io/nvstaging/blueprint/rag-frontend:${TAG:-2.4.0} + image: nvcr.io/nvstaging/blueprint/rag-frontend:${TAG:-2.5.0} build: # Set context to repo's root directory context: ../../frontend diff --git a/deploy/helm/nvidia-blueprint-rag/Chart.yaml b/deploy/helm/nvidia-blueprint-rag/Chart.yaml index a8a459279..639869e53 100644 --- a/deploy/helm/nvidia-blueprint-rag/Chart.yaml +++ b/deploy/helm/nvidia-blueprint-rag/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: v2.4.0 +appVersion: v2.5.0 dependencies: - condition: nv-ingest.enabled name: nv-ingest @@ -24,4 +24,4 @@ dependencies: description: An end to end Helm chart for the NVIDIA RAG Blueprint name: nvidia-blueprint-rag type: application -version: v2.4.0 +version: v2.5.0 diff --git a/deploy/helm/nvidia-blueprint-rag/values.yaml b/deploy/helm/nvidia-blueprint-rag/values.yaml index 47ef09b68..a02fef3e4 100644 --- a/deploy/helm/nvidia-blueprint-rag/values.yaml +++ b/deploy/helm/nvidia-blueprint-rag/values.yaml @@ -57,7 +57,7 @@ apiKeysSecret: # -- RAG server container image image: repository: nvcr.io/nvstaging/blueprint/rag-server - tag: "2.4.0" + tag: "2.5.0" pullPolicy: Always # -- RAG server service configuration @@ -290,7 +290,7 @@ ingestor-server: image: repository: nvcr.io/nvstaging/blueprint/ingestor-server - tag: "2.4.0" + tag: "2.5.0" pullPolicy: Always # -- Service config for ingestor-server @@ -454,7 +454,7 @@ frontend: image: repository: nvcr.io/nvstaging/blueprint/rag-frontend pullPolicy: IfNotPresent - tag: "2.4.0" + tag: "2.5.0" imagePullSecret: name: "ngc-secret" diff --git a/deploy/workbench/compose.yaml b/deploy/workbench/compose.yaml index 04cfdd2e2..1e75d07b7 100644 --- a/deploy/workbench/compose.yaml +++ b/deploy/workbench/compose.yaml @@ -200,7 +200,7 @@ services: # Main ingestor server which is responsible for ingestion ingestor-server: container_name: ingestor-server - image: nvcr.io/nvstaging/blueprint/ingestor-server:${TAG:-2.4.0} + image: nvcr.io/nvstaging/blueprint/ingestor-server:${TAG:-2.5.0} build: # Set context to repo's root directory context: ../../ @@ -432,7 +432,7 @@ services: # Main orchestrator server which stiches together all calls to different services to fulfill the user request rag-server: container_name: rag-server - image: nvcr.io/nvstaging/blueprint/rag-server:${TAG:-2.4.0} + image: nvcr.io/nvstaging/blueprint/rag-server:${TAG:-2.5.0} build: # Set context to repo's root directory context: ../../ @@ -569,7 +569,7 @@ services: # Sample UI container which interacts with APIs exposed by rag-server container rag-frontend: container_name: rag-frontend - image: nvcr.io/nvstaging/blueprint/rag-frontend:${TAG:-2.4.0} + image: nvcr.io/nvstaging/blueprint/rag-frontend:${TAG:-2.5.0} build: # Set context to repo's root directory context: ../../frontend diff --git a/docs/conf.py b/docs/conf.py index f0ffa9e07..0bc42fd7b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,7 +26,7 @@ project = " NVIDIA-RAG-blueprint" copyright = "2025, NVIDIA Corporation" author = "NVIDIA Corporation" -release = "2.4.0" +release = "2.5.0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/deploy-helm.md b/docs/deploy-helm.md index bf8e792c5..f0686c6ca 100644 --- a/docs/deploy-helm.md +++ b/docs/deploy-helm.md @@ -37,7 +37,7 @@ Plan for additional space if you are enabling persistence for multiple services. 4. Verify that you have Kubernetes v1.34.2 installed and running on Ubuntu 22.04/24.04. For more information, see [Kubernetes documentation](https://kubernetes.io/docs/setup/) and [NVIDIA Cloud Native Stack](https://github.com/NVIDIA/cloud-native-stack). -5. Verify that you have installed Helm 3. To install Helm 3 (and avoid Helm 4), follow the official Helm v3 installation instructions for your platform, for example by using the `get-helm-3` script described in the [Helm documentation](https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3). +5. Verify that you have installed Helm 3. To install Helm 3 (and avoid Helm 4), follow the official Helm v3 installation instructions for your platform, for example by using the `get-helm-3` script described in the [Helm documentation](https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3). 6. Verify that you have a default storage class available in the cluster for PVC provisioning. One option is the local path provisioner by Rancher. Refer to the [installation](https://github.com/rancher/local-path-provisioner?tab=readme-ov-file#installation) section of the README in the GitHub repository. @@ -87,7 +87,7 @@ To deploy End-to-End RAG Server and Ingestor Server, use the following procedure 2. Install the Helm chart by running the following command. ```sh - helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.4.0.tgz \ + helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.5.0.tgz \ --username '$oauthtoken' \ --password "${NGC_API_KEY}" \ --set imagePullSecret.password=$NGC_API_KEY \ @@ -112,7 +112,7 @@ To deploy End-to-End RAG Server and Ingestor Server, use the following procedure Then install using the modified values.yaml: ```sh - helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.4.0.tgz \ + helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.5.0.tgz \ --username '$oauthtoken' \ --password "${NGC_API_KEY}" \ --set imagePullSecret.password=$NGC_API_KEY \ @@ -250,7 +250,7 @@ Port-forwarding is provided as a quick method to try out the UI. However, large To change an existing deployment, after you modify the [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml) file, run the following code. ```sh -helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.4.0.tgz \ +helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.5.0.tgz \ --username '$oauthtoken' \ --password "${NGC_API_KEY}" \ --set imagePullSecret.password=$NGC_API_KEY \ diff --git a/docs/mig-deployment.md b/docs/mig-deployment.md index f7eebdf3d..878681713 100644 --- a/docs/mig-deployment.md +++ b/docs/mig-deployment.md @@ -35,9 +35,9 @@ For monitoring deployment progress, refer to [Deploy on Kubernetes with Helm](./ 3. Verify that you have the NGC CLI available on your client computer. You can download the CLI from . -4. Verify that you have Kubernetes v1.34.2 installed and running on Ubuntu 22.04/24.04. For more information, see [Kubernetes documentation](https://kubernetes.io/docs/setup/) and [NVIDIA Cloud Native Stack 17.0](https://github.com/NVIDIA/cloud-native-stack/tree/17.0). +4. Verify that you have Kubernetes v1.34.2 installed and running on Ubuntu 22.04/24.04. For more information, see [Kubernetes documentation](https://kubernetes.io/docs/setup/) and [NVIDIA Cloud Native Stack 17.0](https://github.com/NVIDIA/cloud-native-stack/tree/25.12.0). -5. Verify that you have installed Helm 3 or later (Helm v3.20.0 recommended). For installation instructions, see [Helm Installation](https://helm.sh/docs/intro/install). +5. Verify that you have installed Helm 3. To install Helm 3 (and avoid Helm 4), follow the official Helm v3 installation instructions for your platform, for example by using the `get-helm-3` script described in the [Helm documentation](https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3). 6. Verify that you have a default storage class available in the cluster for PVC provisioning. One option is the local path provisioner by Rancher. Refer to the [installation](https://github.com/rancher/local-path-provisioner?tab=readme-ov-file#installation) section of the README in the GitHub repository. @@ -185,7 +185,7 @@ You should see output similar to the following. Run the following code to install the RAG Blueprint Helm Chart. ```bash -helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.4.0.tgz \ +helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.5.0.tgz \ --username '$oauthtoken' \ --password "${NGC_API_KEY}" \ --set imagePullSecret.password=$NGC_API_KEY \ @@ -199,7 +199,7 @@ helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprin If you are deploying on NVIDIA RTX6000 Pro GPUs (instead of H100 GPUs), use [`values-mig-rtx6000.yaml`](../deploy/helm/mig-slicing/values-mig-rtx6000.yaml) and [`mig-config-rtx6000.yaml`](../deploy/helm/mig-slicing/mig-config-rtx6000.yaml) which include the RTX6000-specific MIG profiles and NIM LLM model configuration. ```sh -helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.4.0.tgz \ +helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.5.0.tgz \ --username '$oauthtoken' \ --password "${NGC_API_KEY}" \ --set imagePullSecret.password=$NGC_API_KEY \ diff --git a/docs/notebooks.md b/docs/notebooks.md index a88952f79..7a7f50eee 100644 --- a/docs/notebooks.md +++ b/docs/notebooks.md @@ -125,54 +125,6 @@ Use the following notebook for cloud deployment scenarios. - [launchable.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/launchable.ipynb) – A deployment-ready notebook intended to run in a [Brev environment](https://console.brev.dev/environment/new). To learn more about Brev, refer to [Brev](https://docs.nvidia.com/brev/latest/about-brev.html). Follow the instructions for running Jupyter notebooks in a cloud-based environment based on the hardware requirements specified in the launchable. - -## Set Up the Notebook Environment - -To run a notebook, use the following procedure with [uv](https://docs.astral.sh/uv/) - a fast Python package manager. - -> **Note**: Python version **3.11 or higher** is required. - -1. Install uv (if not already installed): - - ```bash - curl -LsSf https://astral.sh/uv/0.8.12/install.sh | sh - ``` - -2. Create and activate a virtual environment: - - ```bash - uv venv --python=python3.12 - source .venv/bin/activate - ``` - -3. Install JupyterLab: - - ```bash - uv pip install jupyterlab - ``` - -4. Start JupyterLab: - - ```bash - jupyter lab --allow-root --ip=0.0.0.0 --NotebookApp.token='' --port=8889 --no-browser - ``` - -### Set-up Notes -- Ensure that API keys and credentials are correctly set up before you run a notebook. -- Modify endpoints or request parameters as necessary to match your specific use case. -- For the custom VDB operator notebook, ensure that Docker is available for running OpenSearch services. - - - -## Run a Notebook - -After you set up your notebook environment, to run a notebook, use the following procedure. - -1. Access JupyterLab by opening a browser and navigating to `http://:8889`. -2. Navigate to the notebook and run the cells sequentially. - - - ## Related Topics - [Get Started](deploy-docker-self-hosted.md) diff --git a/docs/observability.md b/docs/observability.md index 0c4bb2665..587c6a70e 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -45,13 +45,13 @@ Use the following procedure to enable observability with Docker. After tracing is enabled and the system is running, you can **view the traces** in **Zipkin** by opening: -

- -

+```{image} assets/zipkin_ui.png +:width: 750px +:align: center +``` Open the Zipkin UI at: **http://localhost:9411** - ## View Metrics in Grafana As part of the tracing, the RAG service also exports metrics like API request counts, LLM prompt and completion token count and words per chunk. @@ -104,11 +104,10 @@ After tracing is enabled and running, you can view inputs and outputs of differe 3. Similarly, you can view inputs and outputs for sub stages within the workflows by clicking on a substage and finding the `traceloop.entity.input` and `traceloop.entity.ouput` rows. -

- -

- - +```{image} assets/zipkin_ui_labelled.png +:width: 750px +:align: center +``` ## Enable Observability with Helm diff --git a/docs/project.json b/docs/project.json index 66344b5d0..9b67aad99 100644 --- a/docs/project.json +++ b/docs/project.json @@ -1,4 +1,4 @@ { "name": "NVIDIA-RAG-blueprint", - "version": "2.4.0" + "version": "2.5.0" } \ No newline at end of file diff --git a/docs/release-notes.md b/docs/release-notes.md index 96c48a121..c0a359206 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,7 +8,7 @@ This documentation contains the release notes for [NVIDIA RAG Blueprint](readme. -## Release 2.4.0 (26-02-TBD) +## Release 2.4.0 (2026-02-20) This release adds new features to the RAG pipeline for supporting agent workflows and enhances generations with VLMs augmenting multimodal input. @@ -19,7 +19,7 @@ This release contains the following key changes: - Updated NIMs and code to support [NVIDIA Ingest 26.01 release](https://docs.nvidia.com/nemo/retriever/latest/extraction/releasenotes-nv-ingest/). - Added support for non-NIM models including OpenAI, models hosted on AWS and Azure, OSS models, and others. Supported through service-specific API keys. For details, refer to [Get an API Key](api-key.md). - The RAG Blueprint now uses [nemoretriever-ocr-v1](https://build.nvidia.com/nvidia/nemoretriever-ocr-v1/modelcard) as the default OCR model. For details, refer to [NeMo Retriever OCR Configuration Guide](nemoretriever-ocr.md). -- The Vision-Language Model (VLM) inference feature now uses the model [nemotron-nano-12b-v2-vl](https://build.nvidia.com/nvidia/nemotron-nano-12b-v2-vl/modelcard). For details, refer to [VLM for Generation](vlm.md). +- Improved VLM based generation support. The Vision-Language Model (VLM) inference feature now uses the model [nemotron-nano-12b-v2-vl](https://build.nvidia.com/nvidia/nemotron-nano-12b-v2-vl/modelcard). For details, refer to [VLM for Generation](vlm.md). - User interface improvements including catalog display, image and text query, and others. For details, refer to [User Interface](user-interface.md). - Added ingestion metrics endpoint support with OpenTelemetry (OTEL) for monitoring document uploads, elements ingested, and pages processed. For details, refer to [Observability](observability.md). - Support image and text as input query. For details, refer to [Multimodal Query Support](multimodal-query.md). diff --git a/docs/text_only_ingest.md b/docs/text_only_ingest.md index 784c08978..625416090 100644 --- a/docs/text_only_ingest.md +++ b/docs/text_only_ingest.md @@ -113,7 +113,7 @@ Additionally, ensure that **table extraction**, **chart extraction**, and **imag 2. Then use the modified [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml) file in your Helm upgrade command: ```bash -helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.4.0.tgz \ +helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.5.0.tgz \ --username '$oauthtoken' \ --password "${NGC_API_KEY}" \ --values deploy/helm/nvidia-blueprint-rag/values.yaml \ diff --git a/docs/versions1.json b/docs/versions1.json index d0731c374..67cecce82 100644 --- a/docs/versions1.json +++ b/docs/versions1.json @@ -1,8 +1,8 @@ [ { "preferred": true, - "version": "2.4.0", - "url": "../2.4.0/" + "version": "2.5.0", + "url": "../2.5.0/" }, { "version": "2.3.0", diff --git a/examples/rag_react_agent/uv.lock b/examples/rag_react_agent/uv.lock index 0554ef787..244c06a23 100644 --- a/examples/rag_react_agent/uv.lock +++ b/examples/rag_react_agent/uv.lock @@ -1950,7 +1950,7 @@ wheels = [ [[package]] name = "nvidia-rag" -version = "2.4.0.dev0" +version = "2.5.0.dev0" source = { editable = "../../" } dependencies = [ { name = "anyio" }, diff --git a/notebooks/launchable.ipynb b/notebooks/launchable.ipynb index 8e5779af2..ee56c3447 100644 --- a/notebooks/launchable.ipynb +++ b/notebooks/launchable.ipynb @@ -914,7 +914,7 @@ "import subprocess\n", "\n", "REPO_URL = \"https://github.com/NVIDIA-AI-Blueprints/rag.git\"\n", - "BRANCH = \"release-v2.4.0\"\n", + "BRANCH = \"release-v2.5.0\"\n", "#BRANCH = \"develop\"\n", "# Check if we're already in the rag repo (look for deploy/compose)\n", "if os.path.exists(\"deploy/compose\"):\n", diff --git a/notebooks/rag_library_usage.ipynb b/notebooks/rag_library_usage.ipynb index 01f07fb4a..83fb0f3b0 100644 --- a/notebooks/rag_library_usage.ipynb +++ b/notebooks/rag_library_usage.ipynb @@ -75,7 +75,8 @@ "outputs": [], "source": [ "# Option A: Install from PyPI (recommended)\n", - "# Uncomment the line below to install from PyPI\n", + "# Uncomment the line below to install from PyPI.\n", + "# Note: This will require a restart of the kernel after installation if you are using this notebook in a JupyterLab session.\n", "# !uv pip install nvidia-rag[all]\n", "\n", "# Option B: Install from source in development mode (for contributors)\n", diff --git a/pyproject.toml b/pyproject.toml index 09b647403..80567fb75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "nvidia_rag" -version = "2.4.0.dev" +version = "2.5.0.dev" description = "This blueprint serves as a reference solution for a foundational Retrieval Augmented Generation (RAG) pipeline." readme = "README.md" license = "Apache-2.0" diff --git a/tests/integration/test_cases/multimodal_query.py b/tests/integration/test_cases/multimodal_query.py index b888b8c7c..b38925211 100644 --- a/tests/integration/test_cases/multimodal_query.py +++ b/tests/integration/test_cases/multimodal_query.py @@ -95,7 +95,7 @@ 2. Deploy or upgrade the chart: - helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.4.0-rc1.tgz \\ + helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvidia/blueprint/charts/nvidia-blueprint-rag-v2.5.0-rc1.tgz \\ --username '$oauthtoken' \\ --password "${NGC_API_KEY}" \\ --set imagePullSecret.password=$NGC_API_KEY \\ diff --git a/uv.lock b/uv.lock index dbbf310f2..7c267bf95 100644 --- a/uv.lock +++ b/uv.lock @@ -1796,7 +1796,7 @@ wheels = [ [[package]] name = "nvidia-rag" -version = "2.4.0.dev0" +version = "2.5.0.dev0" source = { virtual = "." } dependencies = [ { name = "anyio" }, From 1944a7e340080097e0b0550a773e96246d205ad4 Mon Sep 17 00:00:00 2001 From: Minh Nguyen Date: Fri, 27 Feb 2026 13:16:58 +0700 Subject: [PATCH 05/52] feat: Event-Driven Document/Video Ingestion Pipeline (#351) * feat: add rag_event_ingest example - event-driven document/video ingestion pipeline - Kafka consumer that monitors MinIO object storage for new uploads - Routes documents to RAG Ingestor, videos to VSS for analysis - Docker Compose deployment for Kafka, MinIO, and consumer - Jupyter notebook for end-to-end deployment and testing - Sample test data (PDF document, MP4 video) tracked via Git LFS Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: polish rag_event_ingest notebook - fix sections, descriptions, TOC Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * refactor: consolidate Setup into single cell - clone, deps, API keys Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * refactor: inline check_rag/vss/aidp_status into their usage cells Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * docs: add markdown description before every code cell Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: notebook is standalone entry point, clones RAG repo to ~/rag Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: set COLLECTION_NAME, load .env, simplify query_rag, add expected logs Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: left-align markdown tables in notebook Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: use HTML tables to force left alignment Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: replace API Keys markdown table with HTML for left alignment Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * feat: add storage verification, RAG frontend hints, and configurable consumer prompts - Add verify_file_in_storage() helper to confirm files landed in MinIO - Merge storage verification into document/video ingestion checks - Add RAG Frontend UI link (port 8090) to query sections - Make Kafka consumer VSS prompts configurable via env vars in docker-compose - Install git/git-lfs in notebook setup cell - Index cells in Deploy Continuous Ingestion section Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: skip RAG clone if directory already exists Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: url Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * feat: add continuous ingestion notebook for video and document pipeline Add rag_event_ingest.ipynb notebook that provides an end-to-end walkthrough for: - Deploying NVIDIA RAG stack (NIMs, Milvus, Ingestor, RAG Server) - Deploying NVIDIA VSS stack (VLM, LLM, Embedding, Reranker NIMs) - Deploying continuous ingestion pipeline (Kafka, MinIO, Kafka Consumer) - Configurable video analysis prompts for the Kafka consumer - Uploading documents and videos to MinIO with storage verification - Verifying ingestion via consumer logs - Querying ingested content via RAG API or Frontend UI Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: gpu Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: add ensurepip, fix VSS tag to v2.4.1, use GPUs 2-3 for VSS, update hw req to 4 GPUs Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * fix: tag Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * feat: resolve comment * fix: patch VSS config to use host-mapped ports for shared RAG embedding/reranker The via-server runs on the local_deployment_single_gpu_default network, not nvidia-rag, so it cannot resolve nemoretriever-embedding-ms or nemoretriever-ranking-ms. Route through host.docker.internal with the correct host-mapped ports instead (9080 for embedding, 1976 for reranker). Co-authored-by: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor --------- Signed-off-by: Minh Nguyen Co-authored-by: anngu Co-authored-by: Cursor --- .gitattributes | 2 + examples/README.md | 12 + ...at We Learned from Seattle's 29-13 win.pdf | Bin 0 -> 132 bytes ...triots - Super Bowl LX Game Highlights.mp4 | 3 + .../deploy/docker-compose.yaml | 177 +++ .../kafka_consumer/Dockerfile | 12 + .../kafka_consumer/config/__init__.py | 201 +++ .../kafka_consumer/config/constants.py | 410 ++++++ .../kafka_consumer/config/settings.py | 199 +++ .../kafka_consumer/consumer.py | 204 +++ .../kafka_consumer/handlers/__init__.py | 6 + .../kafka_consumer/handlers/base.py | 43 + .../kafka_consumer/handlers/document.py | 89 ++ .../kafka_consumer/handlers/video.py | 118 ++ .../rag_event_ingest/kafka_consumer/main.py | 49 + .../kafka_consumer/models/__init__.py | 4 + .../kafka_consumer/models/events.py | 138 ++ .../kafka_consumer/requirements.txt | 4 + .../rag_event_ingest/kafka_consumer/router.py | 104 ++ .../kafka_consumer/services/__init__.py | 8 + .../services/document_indexer.py | 298 ++++ .../kafka_consumer/services/storage.py | 195 +++ .../kafka_consumer/services/video_analyzer.py | 326 +++++ notebooks/rag_event_ingest.ipynb | 1224 +++++++++++++++++ 24 files changed, 3826 insertions(+) create mode 100644 examples/rag_event_ingest/data/documents/Seahawks-Patriots in Super Bowl LX_ What We Learned from Seattle's 29-13 win.pdf create mode 100644 examples/rag_event_ingest/data/videos/Seattle Seahawks vs New England Patriots - Super Bowl LX Game Highlights.mp4 create mode 100644 examples/rag_event_ingest/deploy/docker-compose.yaml create mode 100644 examples/rag_event_ingest/kafka_consumer/Dockerfile create mode 100644 examples/rag_event_ingest/kafka_consumer/config/__init__.py create mode 100644 examples/rag_event_ingest/kafka_consumer/config/constants.py create mode 100644 examples/rag_event_ingest/kafka_consumer/config/settings.py create mode 100644 examples/rag_event_ingest/kafka_consumer/consumer.py create mode 100644 examples/rag_event_ingest/kafka_consumer/handlers/__init__.py create mode 100644 examples/rag_event_ingest/kafka_consumer/handlers/base.py create mode 100644 examples/rag_event_ingest/kafka_consumer/handlers/document.py create mode 100644 examples/rag_event_ingest/kafka_consumer/handlers/video.py create mode 100644 examples/rag_event_ingest/kafka_consumer/main.py create mode 100644 examples/rag_event_ingest/kafka_consumer/models/__init__.py create mode 100644 examples/rag_event_ingest/kafka_consumer/models/events.py create mode 100644 examples/rag_event_ingest/kafka_consumer/requirements.txt create mode 100644 examples/rag_event_ingest/kafka_consumer/router.py create mode 100644 examples/rag_event_ingest/kafka_consumer/services/__init__.py create mode 100644 examples/rag_event_ingest/kafka_consumer/services/document_indexer.py create mode 100644 examples/rag_event_ingest/kafka_consumer/services/storage.py create mode 100644 examples/rag_event_ingest/kafka_consumer/services/video_analyzer.py create mode 100644 notebooks/rag_event_ingest.ipynb diff --git a/.gitattributes b/.gitattributes index c8d189184..0a7e469ce 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ data/dataset.zip filter=lfs diff=lfs merge=lfs -text data/ filter=lfs diff=lfs merge=lfs -text +examples/rag_event_ingest/data/**/*.mp4 filter=lfs diff=lfs merge=lfs -text +examples/rag_event_ingest/data/**/*.pdf filter=lfs diff=lfs merge=lfs -text diff --git a/examples/README.md b/examples/README.md index 0d56c4781..2e8d54c1c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,6 +8,7 @@ This directory contains example integrations and extensions for NVIDIA RAG. |---------|-------------|---------------| | [rag_react_agent](./rag_react_agent/) | Integration with [NeMo Agent Toolkit (NAT)](https://github.com/NVIDIA/NeMo-Agent-Toolkit) providing RAG query and search capabilities for agent workflows | [README](./rag_react_agent/README.md) | | [nvidia_rag_mcp](./nvidia_rag_mcp/) | MCP (Model Context Protocol) server and client for exposing NVIDIA RAG capabilities to MCP-compatible applications | [Documentation](../docs/mcp.md) | +| [rag_event_ingest](./rag_event_ingest/) | Automated document & video ingestion from object storage (MinIO) via Kafka, with VSS video analysis | [Notebook](../notebooks/rag_event_ingest.ipynb) | ## rag_react_agent @@ -27,3 +28,14 @@ This example provides an MCP server and client that exposes NVIDIA RAG and Inges - Manage collections and documents in the vector database See the [MCP documentation](../docs/mcp.md) for detailed setup and usage instructions. + +## rag_event_ingest + +This example deploys an event-driven ingestion pipeline that monitors MinIO object storage for new file uploads via Kafka events. Documents are indexed through the RAG Ingestor, while videos are analyzed by NVIDIA VSS (Video Search & Summarization). All content becomes queryable through the RAG Agent. + +Components: +- **kafka_consumer/** - Event-driven consumer that routes files to RAG or VSS based on file type +- **deploy/** - Docker Compose for Kafka, MinIO, and the consumer +- **data/** - Sample documents and videos for testing + +See the [notebook](../notebooks/rag_event_ingest.ipynb) for step-by-step deployment and testing. diff --git a/examples/rag_event_ingest/data/documents/Seahawks-Patriots in Super Bowl LX_ What We Learned from Seattle's 29-13 win.pdf b/examples/rag_event_ingest/data/documents/Seahawks-Patriots in Super Bowl LX_ What We Learned from Seattle's 29-13 win.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3d750564d9c52cf8f6ed6fdfe29574c0d1e62c12 GIT binary patch literal 132 zcmWN`xe>!45CFiODrmss_(9$99EKU&XnaV4tGA*J_QEfX`Qd%7gLWaFV?ADtw%dN@ zt<1Ne1BvQeg3Z=f}3 O(J&f6 + /bin/sh -c " + echo 'Waiting for MinIO...'; + sleep 5; + + echo 'Setting up MinIO...'; + mc alias set minio http://minio-source-1:9000 minioadmin minioadmin; + mc mb --ignore-existing minio/aidp-bucket; + mc event add minio/aidp-bucket arn:minio:sqs::AIDP:kafka --event put,delete || true; + + echo 'MinIO setup complete!'; + echo 'Bucket: aidp-bucket on minio-source-1'; + + echo 'Keeping container alive for mc commands...'; + tail -f /dev/null + " + networks: + - nvidia-rag + + # ============================================================================= + # KAFKA CONSUMER (Event-driven Ingestion) + # ============================================================================= + kafka-consumer: + build: + context: ../kafka_consumer + dockerfile: Dockerfile + image: kafka-consumer:local + container_name: kafka-consumer + depends_on: + kafka: + condition: service_healthy + minio-source-1: + condition: service_healthy + environment: + # Kafka + - KAFKA_BOOTSTRAP_SERVERS=kafka:9092 + - KAFKA_TOPIC=${KAFKA_TOPIC:-aidp-topic} + - CONSUMER_GROUP=${CONSUMER_GROUP:-nvingest-consumer-group} + # MinIO + - MINIO_ENDPOINT=minio-source-1:9000 + - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin} + - MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin} + - MINIO_SECURE=false + - VSS_SERVER_URL=${VSS_SERVER_URL:-http://host.docker.internal:8110} + - VSS_TIMEOUT=${VSS_TIMEOUT:-1800} + - VSS_CHUNK_DURATION=${VSS_CHUNK_DURATION:-30} + - VSS_CHUNK_OVERLAP=${VSS_CHUNK_OVERLAP:-10} + - VSS_NUM_FRAMES_PER_CHUNK=${VSS_NUM_FRAMES_PER_CHUNK:-16} + - VSS_MAX_TOKENS=${VSS_MAX_TOKENS:-8192} + # RAG Ingestor + - INGESTOR_SERVER_URL=${INGESTOR_SERVER_URL:-http://ingestor-server:8082} + - COLLECTION_NAME=${COLLECTION_NAME:-aidp_bucket} + # Video Analysis Prompts (customizable via environment variables) + - "VSS_PROMPT=${VSS_PROMPT:-Analyze this sports video. TIMESTAMP FORMAT: Convert seconds to MM:SS (40s=00:40, 80s=01:20, 150s=02:30, 200s=03:20). Describe: key plays, scoring, turnovers, big gains, defensive stops, celebrations.}" + - "VSS_SYSTEM_PROMPT=${VSS_SYSTEM_PROMPT:-You are a sports broadcaster. CRITICAL: Convert all timestamps from seconds to MM:SS format. Examples: 40 seconds = 00:40, 90 seconds = 01:30, 200 seconds = 03:20, 350 seconds = 05:50. Focus on action, field position, execution, and game momentum.}" + - "VSS_CAPTION_SUMMARIZATION_PROMPT=${VSS_CAPTION_SUMMARIZATION_PROMPT:-Format: [MM:SS] Play description. CONVERT seconds to MM:SS (40s=00:40, 80s=01:20, 150s=02:30). Describe: what happened, how it was executed, the result. Be vivid like a TV broadcast.}" + - "VSS_SUMMARY_AGGREGATION_PROMPT=${VSS_SUMMARY_AGGREGATION_PROMPT:-Create game summary with MM:SS timestamps. CONVERT all times: 40s=00:40, 80s=01:20, 200s=03:20, 350s=05:50. Highlight scoring plays, turnovers, momentum shifts, spectacular plays. Write like a highlight reel narrator - vivid and exciting.}" + # Logging + - LOG_LEVEL=${LOG_LEVEL:-INFO} + restart: unless-stopped + networks: + - nvidia-rag + extra_hosts: + - "host.docker.internal:host-gateway" + +# ============================================================================= +# VOLUMES +# ============================================================================= +volumes: + kafka-data: + driver: local + minio-data: + driver: local + +# ============================================================================= +# NETWORKS +# ============================================================================= +networks: + nvidia-rag: + external: true + name: nvidia-rag diff --git a/examples/rag_event_ingest/kafka_consumer/Dockerfile b/examples/rag_event_ingest/kafka_consumer/Dockerfile new file mode 100644 index 000000000..d1ff4fc62 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . /app/ + +CMD ["python", "-u", "main.py"] diff --git a/examples/rag_event_ingest/kafka_consumer/config/__init__.py b/examples/rag_event_ingest/kafka_consumer/config/__init__.py new file mode 100644 index 000000000..3e38cf59c --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/config/__init__.py @@ -0,0 +1,201 @@ +# config/__init__.py +"""Configuration package for Kafka MinIO Consumer. + +Usage: + import config.settings as cfg + print(cfg.VSS_SERVER_URL) + + from config.constants import VIDEO_EXTENSIONS, DEST_VSS +""" + +# Settings (env vars) +from .settings import ( + # Kafka + KAFKA_BOOTSTRAP_SERVERS, + KAFKA_CONSUMER_GROUP, + KAFKA_TOPIC, + KAFKA_AUTO_OFFSET_RESET, + KAFKA_MAX_POLL_RECORDS, + KAFKA_MAX_POLL_INTERVAL_MS, + KAFKA_SESSION_TIMEOUT_MS, + KAFKA_HEARTBEAT_INTERVAL_MS, + # Services + INGESTOR_SERVER_URL, + VSS_SERVER_URL, + INGESTOR_TIMEOUT, + VSS_TIMEOUT, + VSS_UPLOAD_TIMEOUT, + VIDEO_INDEX_TIMEOUT, + # MinIO + MINIO_ENDPOINT, + MINIO_ACCESS_KEY, + MINIO_SECRET_KEY, + MINIO_SECURE, + MINIO_DEFAULT_COLLECTION, + MINIO_SOURCES, + # Features + ENABLE_MULTIMODAL_RAG, + ENABLE_IMAGE_PROCESSING, + ENABLE_AUDIO_PROCESSING, + VSS_EMBEDDING_ENABLED, + # Collection + EMBEDDING_DIMENSION, + CHUNK_SIZE, + CHUNK_OVERLAP, + # Logging + LOG_LEVEL, + LOG_FORMAT, + # History + HISTORY_FILE, + # VSS Prompts (configurable via env) + VSS_DEFAULT_PROMPT, + VSS_SYSTEM_PROMPT, + VSS_CAPTION_SUMMARIZATION_PROMPT, + VSS_SUMMARY_AGGREGATION_PROMPT, + # VSS Settings + VSS_CHUNK_DURATION, + VSS_CHUNK_OVERLAP, + VSS_NUM_FRAMES_PER_CHUNK, + VSS_MAX_TOKENS, + VSS_MODEL, + VSS_STREAM_ENABLED, + # API Endpoints (configurable via env) + API_INGESTOR_DOCUMENTS, + API_INGESTOR_COLLECTIONS, + API_INGESTOR_COLLECTION, + API_INGESTOR_STATUS, + API_VSS_FILES, + API_VSS_SUMMARIZE, +) + +# Constants +from .constants import ( + # File extensions + VIDEO_EXTENSIONS, + DOCUMENT_EXTENSIONS, + IMAGE_EXTENSIONS, + AUDIO_EXTENSIONS, + SKIP_EXTENSIONS, + # Content types + CONTENT_TYPE_MAP, + DEFAULT_CONTENT_TYPE, + # Routing + DEST_VSS, + DEST_RAG, + DEST_SKIP, + DEST_UNKNOWN, + # S3 Event fields + EVENT_NAME, + EVENT_RECORDS, + EVENT_S3, + EVENT_BUCKET, + EVENT_OBJECT, + EVENT_KEY, + EVENT_SIZE, + EVENT_ETAG, + EVENT_NAME_FIELD, + EVENT_FIRST_RECORD_INDEX, + EVENT_PREFIX_CREATED, + EVENT_PREFIX_REMOVED, + EVENT_TYPE_CREATE, + EVENT_TYPE_DELETE, + # Record field names (dataclass attributes) + FIELD_FILE_NAME, + FIELD_BUCKET, + FIELD_COLLECTION, + FIELD_STATUS, + FIELD_START_TIME, + FIELD_END_TIME, + FIELD_DURATION_SECONDS, + FIELD_ERROR_MESSAGE, + FIELD_TASK_ID, + # Record serialization output keys + RECORD_FILE_NAME, + RECORD_BUCKET, + RECORD_COLLECTION, + RECORD_START_TIME, + RECORD_END_TIME, + RECORD_DURATION, + RECORD_STATUS, + RECORD_ERROR, + RECORD_TASK_ID, + # Status + STATUS_PENDING, + STATUS_PROCESSING, + STATUS_FINISHED, + STATUS_FAILED, + STATUS_SKIPPED, + STATUS_DELETED, + STATUS_SUCCESS, + # Config keys (MinIO sources) + CFG_ENDPOINT, + CFG_ACCESS, + CFG_SECRET, + CFG_SECURE, + CFG_COLLECTION, + CFG_BUCKETS, + # API request fields (Ingestor) + FIELD_COLLECTION_NAME, + FIELD_BLOCKING, + FIELD_SPLIT_OPTIONS, + FIELD_CHUNK_SIZE, + FIELD_CHUNK_OVERLAP, + FIELD_GENERATE_SUMMARY, + FIELD_EMBEDDING_DIMENSION, + FIELD_TASK_ID, + # API request fields (VSS) + FIELD_ID, + FIELD_FILE, + FIELD_MODEL, + FIELD_PROMPT, + FIELD_SYSTEM_PROMPT, + FIELD_MAX_TOKENS, + FIELD_CHUNK_DURATION, + FIELD_CHUNK_OVERLAP_DURATION, + FIELD_NUM_FRAMES_PER_CHUNK, + FIELD_CAPTION_SUMMARIZATION_PROMPT, + FIELD_SUMMARY_AGGREGATION_PROMPT, + FIELD_PURPOSE, + FIELD_MEDIA_TYPE, + VALUE_VISION, + VALUE_VIDEO, + FIELD_STREAM, + RESP_DELTA, + RESP_DATA_PREFIX, + # API response fields + RESP_CONTENT, + RESP_RESPONSE, + RESP_TEXT, + RESP_CHOICES, + RESP_MESSAGE, + RESP_ERROR, + RESP_COLLECTIONS, + RESP_TASK_ID, + RESP_STATE, + RESP_RESULT, + RESP_FAILED_DOCUMENTS, + RESP_VALIDATION_ERRORS, + # Timeouts + TIMEOUT_DEFAULT, + TIMEOUT_UPLOAD, + TIMEOUT_VIDEO_UPLOAD, + TIMEOUT_TASK_CHECK, + TIMEOUT_MAX_TASK_WAIT, + VIDEO_ANALYSIS_POLL_INTERVAL, + VIDEO_ANALYSIS_MAX_WAIT, + VIDEO_DESCRIPTION_MIN_LENGTH, + # Kafka defaults + KAFKA_DEFAULT_TOPIC, + KAFKA_DEFAULT_CONSUMER_GROUP, + KAFKA_DEFAULT_AUTO_OFFSET_RESET, + KAFKA_DEFAULT_MAX_POLL_RECORDS, + KAFKA_DEFAULT_MAX_POLL_INTERVAL_MS, + KAFKA_DEFAULT_SESSION_TIMEOUT_MS, + KAFKA_DEFAULT_HEARTBEAT_INTERVAL_MS, + # Collection defaults + COLLECTION_EMBEDDING_DIMENSION, + COLLECTION_CHUNK_SIZE, + COLLECTION_CHUNK_OVERLAP, + COLLECTION_VIDEO_CHUNK_SIZE, + COLLECTION_VIDEO_CHUNK_OVERLAP, +) diff --git a/examples/rag_event_ingest/kafka_consumer/config/constants.py b/examples/rag_event_ingest/kafka_consumer/config/constants.py new file mode 100644 index 000000000..a90eee342 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/config/constants.py @@ -0,0 +1,410 @@ +# config/constants.py +"""Static constants that don't change at runtime. + +For configurable values from environment, see settings.py +""" + +# ==================== File Extensions ==================== + +VIDEO_EXTENSIONS = frozenset({ + '.mp4', '.mkv', '.avi', '.mov', '.wmv', + '.flv', '.webm', '.m4v', '.mpeg', '.mpg' +}) + +DOCUMENT_EXTENSIONS = frozenset({ + '.pdf', '.docx', '.doc', '.txt', '.md', '.rst', + '.html', '.htm', '.pptx', '.ppt', '.xlsx', '.xls', + '.csv', '.json', '.xml' +}) + +IMAGE_EXTENSIONS = frozenset({ + '.jpg', '.jpeg', '.png', '.gif', + '.webp', '.bmp', '.tiff', '.svg' +}) + +AUDIO_EXTENSIONS = frozenset({ + '.mp3', '.wav', '.flac', '.aac', + '.ogg', '.m4a', '.wma' +}) + +SKIP_EXTENSIONS = frozenset({ + '.tmp', '.log', '.bak', '.swp', '.DS_Store', + '.gitkeep', '.gitignore' +}) + + +# ==================== Content Types ==================== + +CONTENT_TYPE_MAP = { + # Documents + '.pdf': 'application/pdf', + '.txt': 'text/plain', + '.doc': 'application/msword', + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.html': 'text/html', + '.htm': 'text/html', + '.xml': 'application/xml', + '.json': 'application/json', + '.csv': 'text/csv', + '.md': 'text/markdown', + '.rst': 'text/x-rst', + '.ppt': 'application/vnd.ms-powerpoint', + '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.xls': 'application/vnd.ms-excel', + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + # Images + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.bmp': 'image/bmp', + '.tiff': 'image/tiff', + '.svg': 'image/svg+xml', + # Videos + '.mp4': 'video/mp4', + '.mkv': 'video/x-matroska', + '.avi': 'video/x-msvideo', + '.mov': 'video/quicktime', + '.wmv': 'video/x-ms-wmv', + '.flv': 'video/x-flv', + '.webm': 'video/webm', + '.m4v': 'video/x-m4v', + '.mpeg': 'video/mpeg', + '.mpg': 'video/mpeg', + # Audio + '.mp3': 'audio/mpeg', + '.wav': 'audio/wav', + '.flac': 'audio/flac', + '.aac': 'audio/aac', + '.ogg': 'audio/ogg', + '.m4a': 'audio/mp4', + '.wma': 'audio/x-ms-wma', +} + +DEFAULT_CONTENT_TYPE = 'application/octet-stream' + + +# ==================== Routing ==================== + +# Destinations +DEST_VSS = 'vss' +DEST_RAG = 'rag' +DEST_SKIP = 'skip' +DEST_UNKNOWN = 'unknown' + +# Route result keys +KEY_DESTINATION = 'destination' +KEY_FILE_TYPE = 'file_type' +KEY_EXTENSION = 'extension' +KEY_REASON = 'reason' + +# File types +FILE_TYPE_VIDEO = 'video' +FILE_TYPE_DOCUMENT = 'document' +FILE_TYPE_IMAGE = 'image' +FILE_TYPE_AUDIO = 'audio' +FILE_TYPE_SKIP = 'skip' +FILE_TYPE_UNKNOWN = 'unknown' + +# Config keys +CFG_VIDEO_EXTENSIONS = 'video_extensions' +CFG_DOCUMENT_EXTENSIONS = 'document_extensions' +CFG_IMAGE_EXTENSIONS = 'image_extensions' +CFG_AUDIO_EXTENSIONS = 'audio_extensions' +CFG_SKIP_EXTENSIONS = 'skip_extensions' +CFG_ENABLE_IMAGE_PROCESSING = 'enable_image_processing' +CFG_ENABLE_AUDIO_PROCESSING = 'enable_audio_processing' + + +# ==================== S3 Event Fields ==================== + +# Kafka S3 event structure +EVENT_NAME = 'EventName' +EVENT_RECORDS = 'Records' +EVENT_FIRST_RECORD_INDEX = 0 # S3 events typically contain single record +EVENT_S3 = 's3' +EVENT_BUCKET = 'bucket' +EVENT_OBJECT = 'object' +EVENT_KEY = 'key' +EVENT_SIZE = 'size' +EVENT_ETAG = 'eTag' +EVENT_NAME_FIELD = 'name' + +# Event type prefixes +EVENT_PREFIX_CREATED = 's3:ObjectCreated:' +EVENT_PREFIX_REMOVED = 's3:ObjectRemoved:' + +# Event type values +EVENT_TYPE_CREATE = 'create' +EVENT_TYPE_DELETE = 'delete' + + +# ==================== Record Fields ==================== + +# IngestionRecord field names (dataclass attributes) +FIELD_FILE_NAME = 'file_name' +FIELD_BUCKET = 'bucket' +FIELD_COLLECTION = 'collection' +FIELD_STATUS = 'status' +FIELD_START_TIME = 'start_time' +FIELD_END_TIME = 'end_time' +FIELD_DURATION_SECONDS = 'duration_seconds' +FIELD_ERROR_MESSAGE = 'error_message' +FIELD_TASK_ID = 'task_id' + +# IngestionRecord serialization output keys +RECORD_FILE_NAME = FIELD_FILE_NAME +RECORD_BUCKET = FIELD_BUCKET +RECORD_COLLECTION = FIELD_COLLECTION +RECORD_START_TIME = FIELD_START_TIME +RECORD_END_TIME = FIELD_END_TIME +RECORD_DURATION = FIELD_DURATION_SECONDS +RECORD_STATUS = FIELD_STATUS +RECORD_ERROR = FIELD_ERROR_MESSAGE +RECORD_TASK_ID = FIELD_TASK_ID + + +# ==================== Task Status ==================== + +STATUS_PENDING = 'PENDING' +STATUS_PROCESSING = 'PROCESSING' +STATUS_FINISHED = 'FINISHED' +STATUS_FAILED = 'FAILED' +STATUS_SKIPPED = 'SKIPPED' +STATUS_DELETED = 'DELETED' +STATUS_SUCCESS = 'SUCCESS' + + +# ==================== Config Keys ==================== + +# MinIO/S3 source config keys +CFG_ENDPOINT = 'endpoint' +CFG_ACCESS = 'access' +CFG_SECRET = 'secret' +CFG_SECURE = 'secure' +CFG_COLLECTION = 'collection' +CFG_BUCKETS = 'buckets' + + +# ==================== API Request Fields ==================== + +# Ingestor request fields +FIELD_COLLECTION_NAME = 'collection_name' +FIELD_BLOCKING = 'blocking' +FIELD_SPLIT_OPTIONS = 'split_options' +FIELD_CHUNK_SIZE = 'chunk_size' +FIELD_CHUNK_OVERLAP = 'chunk_overlap' +FIELD_GENERATE_SUMMARY = 'generate_summary' +FIELD_EMBEDDING_DIMENSION = 'embedding_dimension' +FIELD_TASK_ID = 'task_id' + +# VSS request fields +FIELD_ID = 'id' +FIELD_FILE = 'file' +FIELD_MODEL = 'model' +FIELD_PROMPT = 'prompt' +FIELD_SYSTEM_PROMPT = 'system_prompt' +FIELD_MAX_TOKENS = 'max_tokens' +FIELD_CHUNK_DURATION = 'chunk_duration' +FIELD_CHUNK_OVERLAP_DURATION = 'chunk_overlap_duration' +FIELD_NUM_FRAMES_PER_CHUNK = 'num_frames_per_chunk' +FIELD_CAPTION_SUMMARIZATION_PROMPT = 'caption_summarization_prompt' +FIELD_SUMMARY_AGGREGATION_PROMPT = 'summary_aggregation_prompt' +FIELD_PURPOSE = 'purpose' +FIELD_MEDIA_TYPE = 'media_type' + +# VSS request values +VALUE_VISION = 'vision' +VALUE_VIDEO = 'video' + +# VSS streaming fields +FIELD_STREAM = 'stream' +RESP_DELTA = 'delta' +RESP_DATA_PREFIX = 'data: ' + + +# ==================== API Response Fields ==================== + +# Common response fields +RESP_CONTENT = 'content' +RESP_RESPONSE = 'response' +RESP_TEXT = 'text' +RESP_CHOICES = 'choices' +RESP_MESSAGE = 'message' +RESP_ERROR = 'error' + +# Ingestor response fields +RESP_COLLECTIONS = 'collections' +RESP_TASK_ID = 'task_id' +RESP_STATE = 'state' +RESP_RESULT = 'result' +RESP_FAILED_DOCUMENTS = 'failed_documents' +RESP_VALIDATION_ERRORS = 'validation_errors' + + +# ==================== Timeouts (seconds) ==================== + +TIMEOUT_DEFAULT = 30 +TIMEOUT_UPLOAD = 600 +TIMEOUT_VIDEO_UPLOAD = 600 +TIMEOUT_VIDEO_INDEX = 300 +TIMEOUT_TASK_CHECK = 30 +TIMEOUT_MAX_TASK_WAIT = 300 + +# Video analysis polling +VIDEO_ANALYSIS_POLL_INTERVAL = 10 # seconds between polls +VIDEO_ANALYSIS_MAX_WAIT = 180 # max seconds to wait for analysis +VIDEO_DESCRIPTION_MIN_LENGTH = 200 # minimum chars for valid description + + +# ==================== Kafka Defaults ==================== + +KAFKA_DEFAULT_TOPIC = 'aidp-topic' +KAFKA_DEFAULT_CONSUMER_GROUP = 'nvingest-consumer-group' +KAFKA_DEFAULT_AUTO_OFFSET_RESET = 'earliest' +KAFKA_DEFAULT_MAX_POLL_RECORDS = 1 +KAFKA_DEFAULT_MAX_POLL_INTERVAL_MS = 600000 # 10 min +KAFKA_DEFAULT_SESSION_TIMEOUT_MS = 60000 # 60s +KAFKA_DEFAULT_HEARTBEAT_INTERVAL_MS = 20000 # 20s + + +# ==================== Collection Defaults ==================== + +COLLECTION_EMBEDDING_DIMENSION = 2048 +COLLECTION_CHUNK_SIZE = 512 +COLLECTION_CHUNK_OVERLAP = 150 +COLLECTION_VIDEO_CHUNK_SIZE = 1024 +COLLECTION_VIDEO_CHUNK_OVERLAP = 100 + + +# ==================== Environment Variable Keys ==================== + +# Kafka +ENV_KAFKA_BOOTSTRAP_SERVERS = 'KAFKA_BOOTSTRAP_SERVERS' +ENV_KAFKA_TOPIC = 'KAFKA_TOPIC' +ENV_CONSUMER_GROUP = 'CONSUMER_GROUP' +ENV_KAFKA_AUTO_OFFSET_RESET = 'KAFKA_AUTO_OFFSET_RESET' +ENV_KAFKA_MAX_POLL_RECORDS = 'KAFKA_MAX_POLL_RECORDS' +ENV_KAFKA_MAX_POLL_INTERVAL_MS = 'KAFKA_MAX_POLL_INTERVAL_MS' +ENV_KAFKA_SESSION_TIMEOUT_MS = 'KAFKA_SESSION_TIMEOUT_MS' +ENV_KAFKA_HEARTBEAT_INTERVAL_MS = 'KAFKA_HEARTBEAT_INTERVAL_MS' + +# Service URLs +ENV_INGESTOR_SERVER_URL = 'INGESTOR_SERVER_URL' +ENV_VSS_SERVER_URL = 'VSS_SERVER_URL' +ENV_INGESTOR_TIMEOUT = 'INGESTOR_TIMEOUT' +ENV_VSS_TIMEOUT = 'VSS_TIMEOUT' +ENV_VSS_UPLOAD_TIMEOUT = 'VSS_UPLOAD_TIMEOUT' +ENV_VIDEO_INDEX_TIMEOUT = 'VIDEO_INDEX_TIMEOUT' + +# API Endpoints +ENV_API_INGESTOR_DOCUMENTS = 'API_INGESTOR_DOCUMENTS' +ENV_API_INGESTOR_COLLECTIONS = 'API_INGESTOR_COLLECTIONS' +ENV_API_INGESTOR_COLLECTION = 'API_INGESTOR_COLLECTION' +ENV_API_INGESTOR_STATUS = 'API_INGESTOR_STATUS' +ENV_API_VSS_FILES = 'API_VSS_FILES' +ENV_API_VSS_SUMMARIZE = 'API_VSS_SUMMARIZE' + +# MinIO +ENV_MINIO_ENDPOINT = 'MINIO_ENDPOINT' +ENV_MINIO_ACCESS_KEY = 'MINIO_ACCESS_KEY' +ENV_MINIO_SECRET_KEY = 'MINIO_SECRET_KEY' +ENV_MINIO_SECURE = 'MINIO_SECURE' +ENV_COLLECTION_NAME = 'COLLECTION_NAME' +ENV_MINIO_SOURCES = 'MINIO_SOURCES' + +# Feature Flags +ENV_ENABLE_MULTIMODAL_RAG = 'ENABLE_MULTIMODAL_RAG' +ENV_ENABLE_IMAGE_PROCESSING = 'ENABLE_IMAGE_PROCESSING' +ENV_ENABLE_AUDIO_PROCESSING = 'ENABLE_AUDIO_PROCESSING' +ENV_VSS_EMBEDDING = 'VSS_EMBEDDING' + +# Collection Settings +ENV_EMBEDDING_DIMENSION = 'EMBEDDING_DIMENSION' +ENV_CHUNK_SIZE = 'CHUNK_SIZE' +ENV_CHUNK_OVERLAP = 'CHUNK_OVERLAP' + +# Logging +ENV_LOG_LEVEL = 'LOG_LEVEL' +ENV_LOG_FORMAT = 'LOG_FORMAT' + +# History +ENV_HISTORY_FILE = 'HISTORY_FILE' + +# VSS Settings +ENV_VSS_PROMPT = 'VSS_PROMPT' +ENV_VSS_SYSTEM_PROMPT = 'VSS_SYSTEM_PROMPT' +ENV_VSS_CAPTION_SUMMARIZATION_PROMPT = 'VSS_CAPTION_SUMMARIZATION_PROMPT' +ENV_VSS_SUMMARY_AGGREGATION_PROMPT = 'VSS_SUMMARY_AGGREGATION_PROMPT' +ENV_VSS_CHUNK_DURATION = 'VSS_CHUNK_DURATION' +ENV_VSS_CHUNK_OVERLAP = 'VSS_CHUNK_OVERLAP' +ENV_VSS_NUM_FRAMES_PER_CHUNK = 'VSS_NUM_FRAMES_PER_CHUNK' +ENV_VSS_MAX_TOKENS = 'VSS_MAX_TOKENS' +ENV_VSS_MODEL = 'VSS_MODEL' +ENV_VSS_STREAM_ENABLED = 'VSS_STREAM_ENABLED' + + +# ==================== API Endpoint Defaults ==================== + +DEFAULT_API_INGESTOR_DOCUMENTS = '/v1/documents' +DEFAULT_API_INGESTOR_COLLECTIONS = '/v1/collections' +DEFAULT_API_INGESTOR_COLLECTION = '/v1/collection' +DEFAULT_API_INGESTOR_STATUS = '/v1/status' +DEFAULT_API_VSS_FILES = '/files' +DEFAULT_API_VSS_SUMMARIZE = '/summarize' + + +# ==================== Logging Defaults ==================== + +DEFAULT_LOG_LEVEL = 'INFO' +DEFAULT_LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + + +# ==================== History Defaults ==================== + +DEFAULT_HISTORY_FILE = '/tmp/ingestion_history.jsonl' + + +# ==================== MinIO Defaults ==================== + +DEFAULT_COLLECTION_NAME = 'multimodal_data' + + +# ==================== VSS Defaults ==================== + +DEFAULT_VSS_MODEL = 'Cosmos-Reason2-8B' +DEFAULT_VSS_CHUNK_DURATION = 60 # seconds per chunk +DEFAULT_VSS_CHUNK_OVERLAP = 5 # overlap between chunks +DEFAULT_VSS_NUM_FRAMES_PER_CHUNK = 8 # frames per chunk for VLM +DEFAULT_VSS_MAX_TOKENS = 1024 + +# Default prompts for general videos +DEFAULT_VSS_PROMPT = ( + "Analyze this video. TIMESTAMP FORMAT: Convert seconds to MM:SS " + "(40s=00:40, 80s=01:20, 150s=02:30, 200s=03:20). " + "Describe: key events, important moments, actions, transitions, and notable scenes. " + "Capture the flow and progression of the content." +) + +DEFAULT_VSS_SYSTEM_PROMPT = ( + "You are a video analyst providing detailed descriptions. " + "CRITICAL: Convert all timestamps from seconds to MM:SS format. " + "Examples: 40 seconds = 00:40, 90 seconds = 01:30, 200 seconds = 03:20. " + "Focus on what you clearly see: actions, scenes, transitions, and key moments. " + "Be descriptive and engaging." +) + +DEFAULT_VSS_CAPTION_SUMMARIZATION_PROMPT = ( + "Format: [MM:SS] Description. CONVERT seconds to MM:SS " + "(40s=00:40, 80s=01:20, 150s=02:30). " + "Describe: what happened, how it occurred, the result. " + "Be specific about the action and context." +) + +DEFAULT_VSS_SUMMARY_AGGREGATION_PROMPT = ( + "Create a comprehensive video summary with MM:SS timestamps. " + "CONVERT all times: 40s=00:40, 80s=01:20, 200s=03:20, 350s=05:50. " + "Highlight: key events, transitions, important moments, and notable scenes. " + "Write in an engaging narrative style." +) diff --git a/examples/rag_event_ingest/kafka_consumer/config/settings.py b/examples/rag_event_ingest/kafka_consumer/config/settings.py new file mode 100644 index 000000000..69794c2a0 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/config/settings.py @@ -0,0 +1,199 @@ +# config/settings.py +"""Runtime settings loaded from environment variables.""" + +import os + +from .constants import ( + # Default values + KAFKA_DEFAULT_TOPIC, + KAFKA_DEFAULT_CONSUMER_GROUP, + KAFKA_DEFAULT_AUTO_OFFSET_RESET, + KAFKA_DEFAULT_MAX_POLL_RECORDS, + KAFKA_DEFAULT_MAX_POLL_INTERVAL_MS, + KAFKA_DEFAULT_SESSION_TIMEOUT_MS, + KAFKA_DEFAULT_HEARTBEAT_INTERVAL_MS, + TIMEOUT_UPLOAD, + TIMEOUT_VIDEO_UPLOAD, + TIMEOUT_VIDEO_INDEX, + COLLECTION_EMBEDDING_DIMENSION, + COLLECTION_CHUNK_SIZE, + COLLECTION_CHUNK_OVERLAP, + # API Endpoint defaults + DEFAULT_API_INGESTOR_DOCUMENTS, + DEFAULT_API_INGESTOR_COLLECTIONS, + DEFAULT_API_INGESTOR_COLLECTION, + DEFAULT_API_INGESTOR_STATUS, + DEFAULT_API_VSS_FILES, + DEFAULT_API_VSS_SUMMARIZE, + # Logging defaults + DEFAULT_LOG_LEVEL, + DEFAULT_LOG_FORMAT, + # History defaults + DEFAULT_HISTORY_FILE, + # MinIO defaults + DEFAULT_COLLECTION_NAME, + # VSS defaults + DEFAULT_VSS_MODEL, + DEFAULT_VSS_CHUNK_DURATION, + DEFAULT_VSS_CHUNK_OVERLAP, + DEFAULT_VSS_NUM_FRAMES_PER_CHUNK, + DEFAULT_VSS_MAX_TOKENS, + DEFAULT_VSS_PROMPT, + DEFAULT_VSS_SYSTEM_PROMPT, + DEFAULT_VSS_CAPTION_SUMMARIZATION_PROMPT, + DEFAULT_VSS_SUMMARY_AGGREGATION_PROMPT, + # Environment variable keys + ENV_KAFKA_BOOTSTRAP_SERVERS, + ENV_KAFKA_TOPIC, + ENV_CONSUMER_GROUP, + ENV_KAFKA_AUTO_OFFSET_RESET, + ENV_KAFKA_MAX_POLL_RECORDS, + ENV_KAFKA_MAX_POLL_INTERVAL_MS, + ENV_KAFKA_SESSION_TIMEOUT_MS, + ENV_KAFKA_HEARTBEAT_INTERVAL_MS, + ENV_INGESTOR_SERVER_URL, + ENV_VSS_SERVER_URL, + ENV_INGESTOR_TIMEOUT, + ENV_VSS_TIMEOUT, + ENV_VSS_UPLOAD_TIMEOUT, + ENV_VIDEO_INDEX_TIMEOUT, + ENV_API_INGESTOR_DOCUMENTS, + ENV_API_INGESTOR_COLLECTIONS, + ENV_API_INGESTOR_COLLECTION, + ENV_API_INGESTOR_STATUS, + ENV_API_VSS_FILES, + ENV_API_VSS_SUMMARIZE, + ENV_MINIO_ENDPOINT, + ENV_MINIO_ACCESS_KEY, + ENV_MINIO_SECRET_KEY, + ENV_MINIO_SECURE, + ENV_COLLECTION_NAME, + ENV_MINIO_SOURCES, + ENV_ENABLE_MULTIMODAL_RAG, + ENV_ENABLE_IMAGE_PROCESSING, + ENV_ENABLE_AUDIO_PROCESSING, + ENV_VSS_EMBEDDING, + ENV_EMBEDDING_DIMENSION, + ENV_CHUNK_SIZE, + ENV_CHUNK_OVERLAP, + ENV_LOG_LEVEL, + ENV_LOG_FORMAT, + ENV_HISTORY_FILE, + ENV_VSS_PROMPT, + ENV_VSS_SYSTEM_PROMPT, + ENV_VSS_CAPTION_SUMMARIZATION_PROMPT, + ENV_VSS_SUMMARY_AGGREGATION_PROMPT, + ENV_VSS_CHUNK_DURATION, + ENV_VSS_CHUNK_OVERLAP, + ENV_VSS_NUM_FRAMES_PER_CHUNK, + ENV_VSS_MAX_TOKENS, + ENV_VSS_MODEL, + ENV_VSS_STREAM_ENABLED, +) + + +# ==================== Helper Functions ==================== + +def _get_bool(key: str, default: bool = False) -> bool: + """Get boolean from environment variable.""" + return os.getenv(key, str(default)).lower() in ('true', '1', 'yes', 'on') + + +def _get_int(key: str, default: int) -> int: + """Get integer from environment variable.""" + try: + return int(os.getenv(key, str(default))) + except ValueError: + return default + + +# ==================== Kafka Settings ==================== + +KAFKA_BOOTSTRAP_SERVERS = os.getenv(ENV_KAFKA_BOOTSTRAP_SERVERS) # Required +KAFKA_CONSUMER_GROUP = os.getenv(ENV_CONSUMER_GROUP, KAFKA_DEFAULT_CONSUMER_GROUP) +KAFKA_TOPIC = os.getenv(ENV_KAFKA_TOPIC, KAFKA_DEFAULT_TOPIC) +KAFKA_AUTO_OFFSET_RESET = os.getenv(ENV_KAFKA_AUTO_OFFSET_RESET, KAFKA_DEFAULT_AUTO_OFFSET_RESET) +KAFKA_MAX_POLL_RECORDS = _get_int(ENV_KAFKA_MAX_POLL_RECORDS, KAFKA_DEFAULT_MAX_POLL_RECORDS) +KAFKA_MAX_POLL_INTERVAL_MS = _get_int(ENV_KAFKA_MAX_POLL_INTERVAL_MS, KAFKA_DEFAULT_MAX_POLL_INTERVAL_MS) +KAFKA_SESSION_TIMEOUT_MS = _get_int(ENV_KAFKA_SESSION_TIMEOUT_MS, KAFKA_DEFAULT_SESSION_TIMEOUT_MS) +KAFKA_HEARTBEAT_INTERVAL_MS = _get_int(ENV_KAFKA_HEARTBEAT_INTERVAL_MS, KAFKA_DEFAULT_HEARTBEAT_INTERVAL_MS) + + +# ==================== Service URLs ==================== + +INGESTOR_SERVER_URL = os.getenv(ENV_INGESTOR_SERVER_URL) # Required +VSS_SERVER_URL = os.getenv(ENV_VSS_SERVER_URL) # Required +INGESTOR_TIMEOUT = _get_int(ENV_INGESTOR_TIMEOUT, TIMEOUT_UPLOAD) +VSS_TIMEOUT = _get_int(ENV_VSS_TIMEOUT, TIMEOUT_VIDEO_UPLOAD) +VSS_UPLOAD_TIMEOUT = _get_int(ENV_VSS_UPLOAD_TIMEOUT, TIMEOUT_VIDEO_UPLOAD) +VIDEO_INDEX_TIMEOUT = _get_int(ENV_VIDEO_INDEX_TIMEOUT, TIMEOUT_VIDEO_INDEX) + +# API Endpoints - Ingestor Server +API_INGESTOR_DOCUMENTS = os.getenv(ENV_API_INGESTOR_DOCUMENTS, DEFAULT_API_INGESTOR_DOCUMENTS) +API_INGESTOR_COLLECTIONS = os.getenv(ENV_API_INGESTOR_COLLECTIONS, DEFAULT_API_INGESTOR_COLLECTIONS) +API_INGESTOR_COLLECTION = os.getenv(ENV_API_INGESTOR_COLLECTION, DEFAULT_API_INGESTOR_COLLECTION) +API_INGESTOR_STATUS = os.getenv(ENV_API_INGESTOR_STATUS, DEFAULT_API_INGESTOR_STATUS) + +# API Endpoints - VSS +API_VSS_FILES = os.getenv(ENV_API_VSS_FILES, DEFAULT_API_VSS_FILES) +API_VSS_SUMMARIZE = os.getenv(ENV_API_VSS_SUMMARIZE, DEFAULT_API_VSS_SUMMARIZE) + + +# ==================== MinIO Settings ==================== + +MINIO_ENDPOINT = os.getenv(ENV_MINIO_ENDPOINT) # Required +MINIO_ACCESS_KEY = os.getenv(ENV_MINIO_ACCESS_KEY) # Required +MINIO_SECRET_KEY = os.getenv(ENV_MINIO_SECRET_KEY) # Required +MINIO_SECURE = _get_bool(ENV_MINIO_SECURE, False) +# Single collection for all buckets - matches RAG server's COLLECTION_NAME +MINIO_DEFAULT_COLLECTION = os.getenv(ENV_COLLECTION_NAME, DEFAULT_COLLECTION_NAME) +MINIO_SOURCES = os.getenv(ENV_MINIO_SOURCES) # JSON config for multi-source + + +# ==================== Feature Flags ==================== + +ENABLE_MULTIMODAL_RAG = _get_bool(ENV_ENABLE_MULTIMODAL_RAG, True) +ENABLE_IMAGE_PROCESSING = _get_bool(ENV_ENABLE_IMAGE_PROCESSING, False) +ENABLE_AUDIO_PROCESSING = _get_bool(ENV_ENABLE_AUDIO_PROCESSING, False) +VSS_EMBEDDING_ENABLED = _get_bool(ENV_VSS_EMBEDDING, False) + + +# ==================== Collection Settings ==================== + +EMBEDDING_DIMENSION = _get_int(ENV_EMBEDDING_DIMENSION, COLLECTION_EMBEDDING_DIMENSION) +CHUNK_SIZE = _get_int(ENV_CHUNK_SIZE, COLLECTION_CHUNK_SIZE) +CHUNK_OVERLAP = _get_int(ENV_CHUNK_OVERLAP, COLLECTION_CHUNK_OVERLAP) + + +# ==================== Logging Settings ==================== + +LOG_LEVEL = os.getenv(ENV_LOG_LEVEL, DEFAULT_LOG_LEVEL) +LOG_FORMAT = os.getenv(ENV_LOG_FORMAT, DEFAULT_LOG_FORMAT) + + +# ==================== History Settings ==================== + +HISTORY_FILE = os.getenv(ENV_HISTORY_FILE, DEFAULT_HISTORY_FILE) + + +# ==================== VSS Settings ==================== + +# Default prompts for general videos (can be overridden via environment variables) +# Use environment variables for specific video types (sports, talks, tutorials, etc.) +VSS_DEFAULT_PROMPT = os.getenv(ENV_VSS_PROMPT, DEFAULT_VSS_PROMPT) +VSS_SYSTEM_PROMPT = os.getenv(ENV_VSS_SYSTEM_PROMPT, DEFAULT_VSS_SYSTEM_PROMPT) +VSS_CAPTION_SUMMARIZATION_PROMPT = os.getenv( + ENV_VSS_CAPTION_SUMMARIZATION_PROMPT, DEFAULT_VSS_CAPTION_SUMMARIZATION_PROMPT +) +VSS_SUMMARY_AGGREGATION_PROMPT = os.getenv( + ENV_VSS_SUMMARY_AGGREGATION_PROMPT, DEFAULT_VSS_SUMMARY_AGGREGATION_PROMPT +) + +# VSS chunking settings for long videos +VSS_CHUNK_DURATION = _get_int(ENV_VSS_CHUNK_DURATION, DEFAULT_VSS_CHUNK_DURATION) +VSS_CHUNK_OVERLAP = _get_int(ENV_VSS_CHUNK_OVERLAP, DEFAULT_VSS_CHUNK_OVERLAP) +VSS_NUM_FRAMES_PER_CHUNK = _get_int(ENV_VSS_NUM_FRAMES_PER_CHUNK, DEFAULT_VSS_NUM_FRAMES_PER_CHUNK) +VSS_MAX_TOKENS = _get_int(ENV_VSS_MAX_TOKENS, DEFAULT_VSS_MAX_TOKENS) +VSS_MODEL = os.getenv(ENV_VSS_MODEL, DEFAULT_VSS_MODEL) +VSS_STREAM_ENABLED = _get_bool(ENV_VSS_STREAM_ENABLED, True) + diff --git a/examples/rag_event_ingest/kafka_consumer/consumer.py b/examples/rag_event_ingest/kafka_consumer/consumer.py new file mode 100644 index 000000000..b78d3c322 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/consumer.py @@ -0,0 +1,204 @@ +# consumer.py +"""Kafka consumer for MinIO S3 events.""" + +import json +import logging +from datetime import datetime +from typing import Dict, Optional +from kafka import KafkaConsumer + +import config.settings as cfg +from pathlib import Path +from config.constants import DEST_RAG, DEST_SKIP, STATUS_FAILED, KEY_DESTINATION, KEY_FILE_TYPE, KEY_REASON +from router import FileRouter +from models.events import S3Event, HandlerResult, IngestionRecord +from handlers.base import BaseHandler +from services.storage import ObjectStorage + +logger = logging.getLogger(__name__) + + +class KafkaEventConsumer: + """Kafka consumer that routes MinIO events to handlers.""" + + def __init__( + self, + handlers: Dict[str, BaseHandler], + storage: ObjectStorage, + history_file: str = '/tmp/ingestion_history.jsonl' + ): + """Initialize Kafka consumer.""" + self.handlers = handlers + self.storage = storage + self.history_file = history_file + self.router = FileRouter() + + logger.info(f"Connecting to Kafka: {cfg.KAFKA_BOOTSTRAP_SERVERS}") + logger.info(f"Consumer group: {cfg.KAFKA_CONSUMER_GROUP}") + + self.kafka_consumer = KafkaConsumer( + cfg.KAFKA_TOPIC, + bootstrap_servers=cfg.KAFKA_BOOTSTRAP_SERVERS.split(','), + value_deserializer=lambda m: json.loads(m.decode('utf-8')), + group_id=cfg.KAFKA_CONSUMER_GROUP, + auto_offset_reset=cfg.KAFKA_AUTO_OFFSET_RESET, + enable_auto_commit=True, + max_poll_records=cfg.KAFKA_MAX_POLL_RECORDS, + max_poll_interval_ms=cfg.KAFKA_MAX_POLL_INTERVAL_MS, + session_timeout_ms=cfg.KAFKA_SESSION_TIMEOUT_MS, + heartbeat_interval_ms=cfg.KAFKA_HEARTBEAT_INTERVAL_MS + ) + + logger.info("Kafka consumer initialized") + logger.info(f"Registered handlers: {list(self.handlers.keys())}") + + def process_event(self, raw_event: dict) -> Optional[HandlerResult]: + """Process a single MinIO S3 event.""" + start_time = datetime.now() + event: Optional[S3Event] = None + result: Optional[HandlerResult] = None + + try: + logger.info(f"Received event: {json.dumps(raw_event, indent=2)}") + + event = S3Event.from_kafka_message( + raw_event, + collection_resolver=self.storage.get_collection_for_bucket + ) + + if not event: + logger.warning("Invalid event format, skipping") + return None + + logger.info(f"Processing: {event.bucket}/{event.key} ({event.size} bytes)") + + if event.event_type == 'delete': + result = self._handle_delete(event) + else: + result = self._handle_create(event) + + return result + + except (json.JSONDecodeError, KeyError, ValueError) as e: + logger.error(f"Invalid event data: {e}") + result = HandlerResult.failed_result(str(e)) + return result + + except (IOError, OSError) as e: + logger.error(f"Storage error: {e}") + result = HandlerResult.failed_result(str(e)) + return result + + finally: + if event: + self._save_record(event, result, start_time) + + def _handle_delete(self, event: S3Event) -> HandlerResult: + """Handle S3 delete event.""" + logger.info(f"🗑️ DELETE event for {event.key}") + + doc_handler = self.handlers.get(DEST_RAG) + if not doc_handler or not hasattr(doc_handler, 'indexer'): + return HandlerResult.failed_result("Delete failed - no indexer available") + + indexer = doc_handler.indexer + success = indexer.delete(event.key, event.collection) + + if self.router.is_video(event.key): + desc_filename = f"{event.key}_description.json" + desc_ok = indexer.delete(desc_filename, event.collection) + if desc_ok: + logger.info(f"✓ Deleted video description {desc_filename} from Milvus") + success = success or desc_ok + + if success: + logger.info(f"✓ Deleted {event.key} from Milvus") + return HandlerResult(success=True, status='DELETED') + + return HandlerResult.failed_result("Delete failed") + + def _handle_create(self, event: S3Event) -> HandlerResult: + """Handle S3 create event.""" + route_info = self.router.route(event.key) + destination = route_info[KEY_DESTINATION] + + logger.info(f"📁 {route_info[KEY_FILE_TYPE]} → {destination}") + + if destination == DEST_SKIP: + reason = route_info.get(KEY_REASON, 'Skipped by router') + logger.info(f"⏭️ Skipping: {reason}") + return HandlerResult.skipped_result(reason) + + handler = self.handlers.get(destination) + if not handler: + handler = self.handlers.get(DEST_RAG) + + if not handler: + return HandlerResult.failed_result(f"No handler for {destination}") + + return handler.handle(event) + + def _save_record(self, event: S3Event, result: Optional[HandlerResult], start_time: datetime): + """Save ingestion record to history file.""" + end_time = datetime.now() + duration = (end_time - start_time).total_seconds() + + record = IngestionRecord( + file_name=event.key, + bucket=event.bucket, + collection=event.collection, + status=result.status if result else STATUS_FAILED, + start_time=start_time, + end_time=end_time, + duration_seconds=duration, + error_message=result.error_message if result else None, + task_id=result.task_id if result else None + ) + + try: + with open(self.history_file, 'a') as f: + f.write(json.dumps(record.to_dict()) + '\n') + except (IOError, OSError) as e: + logger.error(f"Failed to save history: {e}") + + status_emoji = '✓' if record.status in ['SUCCESS', 'DELETED', 'SKIPPED'] else '✗' + logger.info( + f"{status_emoji} SUMMARY: {event.key} | " + f"Collection: {event.collection} | " + f"Duration: {duration:.2f}s | " + f"Status: {record.status}" + ) + + def run(self): + """Main consumer loop.""" + logger.info("Starting Kafka consumer loop...") + logger.info(f"Subscribed topics: {self.kafka_consumer.subscription()}") + logger.info("Waiting for messages...") + + try: + message_count = 0 + for message in self._poll_messages(): + message_count += 1 + logger.info( + f"[{message_count}] Message from " + f"partition {message.partition}, offset {message.offset}" + ) + self.process_event(message.value) + + except KeyboardInterrupt: + logger.info("Shutting down...") + finally: + self.kafka_consumer.close() + logger.info("Consumer closed") + + def _poll_messages(self): + """Generator that yields messages from Kafka.""" + while True: + msg_pack = self.kafka_consumer.poll(timeout_ms=5000, max_records=1) + + if not msg_pack: + logger.debug("No messages, continuing...") + continue + + for messages in msg_pack.values(): + yield from messages diff --git a/examples/rag_event_ingest/kafka_consumer/handlers/__init__.py b/examples/rag_event_ingest/kafka_consumer/handlers/__init__.py new file mode 100644 index 000000000..112493e8e --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/handlers/__init__.py @@ -0,0 +1,6 @@ +# Handlers package +from .base import BaseHandler +from .document import DocumentHandler +from .video import VideoHandler + +__all__ = ['BaseHandler', 'DocumentHandler', 'VideoHandler'] diff --git a/examples/rag_event_ingest/kafka_consumer/handlers/base.py b/examples/rag_event_ingest/kafka_consumer/handlers/base.py new file mode 100644 index 000000000..6745f2e09 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/handlers/base.py @@ -0,0 +1,43 @@ +# handlers/base.py +"""Base handler abstract class.""" + +from abc import ABC, abstractmethod +import logging + +from models.events import S3Event, HandlerResult + +logger = logging.getLogger(__name__) + + +class BaseHandler(ABC): + """Abstract base class for file handlers.""" + + @property + @abstractmethod + def name(self) -> str: + """Handler name for logging.""" + pass + + @abstractmethod + def handle(self, event: S3Event) -> HandlerResult: + """Process an S3 event. + + Args: + event: S3 event to process + + Returns: + HandlerResult with success status and optional task_id + """ + pass + + def log_start(self, event: S3Event): + """Log handler start.""" + logger.info(f"[{self.name}] Processing {event.bucket}/{event.key}") + + def log_success(self, event: S3Event, result: HandlerResult): + """Log successful handling.""" + logger.info(f"[{self.name}] ✓ {event.key} → {result.status}") + + def log_failure(self, event: S3Event, result: HandlerResult): + """Log failed handling.""" + logger.error(f"[{self.name}] ✗ {event.key}: {result.error_message}") diff --git a/examples/rag_event_ingest/kafka_consumer/handlers/document.py b/examples/rag_event_ingest/kafka_consumer/handlers/document.py new file mode 100644 index 000000000..1df03946d --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/handlers/document.py @@ -0,0 +1,89 @@ +# handlers/document.py +"""Handler for document files (PDF, DOCX, TXT, etc.).""" + +import logging + +import requests + +from .base import BaseHandler +from models.events import S3Event, HandlerResult +from services.storage import ObjectStorage +from services.document_indexer import DocumentIndexer + +logger = logging.getLogger(__name__) + + +class DocumentHandler(BaseHandler): + """Handler for document files - sends to RAG ingestor.""" + + def __init__(self, storage: ObjectStorage, indexer: DocumentIndexer): + """Initialize document handler. + + Args: + storage: Object storage for file downloads + indexer: Document indexer for RAG pipeline + """ + self.storage = storage + self.indexer = indexer + + @property + def name(self) -> str: + return "DocumentHandler" + + def handle(self, event: S3Event) -> HandlerResult: + """Process document file. + + 1. Delete existing entries (for updates) + 2. Download from MinIO + 3. Upload to ingestor + 4. Wait for completion + + Args: + event: S3 event with document info + + Returns: + HandlerResult with task_id for status tracking + """ + self.log_start(event) + + try: + # Step 1: Delete existing entries (handles updates) + logger.info(f"🔄 Checking for existing entries of {event.key}...") + self.indexer.delete(event.key, event.collection) + + # Step 2: Download from storage + logger.info(f"📥 Downloading from storage...") + file_data = self.storage.download(event.bucket, event.key) + + # Step 3: Upload to indexer + logger.info(f"📤 Sending to indexer...") + task_id = self.indexer.upload( + file_data=file_data, + filename=event.key, + collection=event.collection + ) + + if not task_id: + result = HandlerResult.failed_result("Indexer upload failed") + self.log_failure(event, result) + return result + + # Step 4: Wait for completion + logger.info(f"⏳ Waiting for indexing (task_id: {task_id})...") + success, message = self.indexer.check_status(task_id) + + if success: + result = HandlerResult.success_result(task_id=task_id) + self.log_success(event, result) + return result + else: + result = HandlerResult.failed_result(message, task_id=task_id) + self.log_failure(event, result) + return result + + except requests.RequestException as e: + logger.error(f"Network error processing document: {e}") + return HandlerResult.failed_result(str(e)) + except (IOError, OSError) as e: + logger.error(f"Storage error processing document: {e}") + return HandlerResult.failed_result(str(e)) diff --git a/examples/rag_event_ingest/kafka_consumer/handlers/video.py b/examples/rag_event_ingest/kafka_consumer/handlers/video.py new file mode 100644 index 000000000..9e5d73a34 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/handlers/video.py @@ -0,0 +1,118 @@ +# handlers/video.py +"""Handler for video files.""" + +import logging + +import requests + +from .base import BaseHandler +from models.events import S3Event, HandlerResult +from services.storage import ObjectStorage +from services.video_analyzer import VideoAnalyzer +from services.document_indexer import DocumentIndexer + +logger = logging.getLogger(__name__) + + +class VideoHandler(BaseHandler): + """Handler for video files - analyzes and indexes in vector store.""" + + def __init__( + self, + storage: ObjectStorage, + analyzer: VideoAnalyzer, + indexer: DocumentIndexer, + enable_multimodal_rag: bool = True + ): + """Initialize video handler. + + Args: + storage: Object storage for file downloads + analyzer: Video analyzer for VLM analysis + indexer: Document indexer for storing descriptions + enable_multimodal_rag: Whether to index video descriptions + """ + self.storage = storage + self.analyzer = analyzer + self.indexer = indexer + self.enable_multimodal_rag = enable_multimodal_rag + + @property + def name(self) -> str: + return "VideoHandler" + + def handle(self, event: S3Event) -> HandlerResult: + """Process video file. + + 1. Download from MinIO + 2. Upload to VSS + 3. (Optional) Get description and index in Milvus + + Args: + event: S3 event with video info + + Returns: + HandlerResult + """ + self.log_start(event) + + try: + # Step 1: Download from storage + logger.info(f"📥 Downloading video from storage...") + video_data = self.storage.download(event.bucket, event.key) + + # Step 2: Upload to VSS + logger.info(f"📤 Uploading to analyzer...") + success, video_name = self.analyzer.upload_video(video_data, event.key) + + if not success: + result = HandlerResult.failed_result("Video upload failed") + self.log_failure(event, result) + return result + + logger.info(f"✓ Video uploaded: {video_name}") + + # Step 3: Multi-modal RAG indexing (optional) + if self.enable_multimodal_rag and video_name: + self._index_video_description(event, video_name) + + result = HandlerResult.success_result() + self.log_success(event, result) + return result + + except requests.RequestException as e: + logger.error(f"Network error processing video: {e}") + return HandlerResult.failed_result(str(e)) + except (IOError, OSError) as e: + logger.error(f"Storage error processing video: {e}") + return HandlerResult.failed_result(str(e)) + + def _index_video_description(self, event: S3Event, video_name: str): + """Get video description from VSS and index in Milvus. + + Args: + event: S3 event + video_name: Video name (stem of filename) + """ + logger.info(f"🔄 Starting Multi-Modal RAG indexing...") + + # Get description using VSS /generate with detailed prompt + logger.info(f"📹 Generating description for video: {video_name}...") + description = self.analyzer.get_video_description(video_name) + + if not description: + logger.warning("Failed to get video description from VSS") + return + + # Index description in Milvus + success = self.indexer.index_video_description( + collection=event.collection, + video_id=video_name, + video_name=event.key, + description=description + ) + + if success: + logger.info(f"✓ Indexed video description for {event.key}") + else: + logger.warning(f"⚠ Failed to index video description") diff --git a/examples/rag_event_ingest/kafka_consumer/main.py b/examples/rag_event_ingest/kafka_consumer/main.py new file mode 100644 index 000000000..02e86c137 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/main.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# main.py +"""Entry point for Kafka MinIO consumer.""" + +import logging + +import config.settings as cfg +from config.constants import DEST_RAG, DEST_VSS +from services import ObjectStorage, DocumentIndexer, VideoAnalyzer +from handlers import DocumentHandler, VideoHandler +from consumer import KafkaEventConsumer + +logging.basicConfig( + level=getattr(logging, cfg.LOG_LEVEL, logging.INFO), + format=cfg.LOG_FORMAT +) +logger = logging.getLogger(__name__) + + +def main(): + """Initialize and run the Kafka consumer.""" + logger.info("=" * 60) + logger.info("Starting Kafka MinIO Consumer") + logger.info("=" * 60) + + # Initialize services + logger.info("Initializing services...") + storage = ObjectStorage() + indexer = DocumentIndexer(cfg.INGESTOR_SERVER_URL) + analyzer = VideoAnalyzer(cfg.VSS_SERVER_URL) + + # Initialize handlers + logger.info("Initializing handlers...") + handlers = { + DEST_RAG: DocumentHandler(storage, indexer), + DEST_VSS: VideoHandler(storage, analyzer, indexer, enable_multimodal_rag=cfg.ENABLE_MULTIMODAL_RAG), + } + + # Initialize consumer + logger.info("Initializing Kafka consumer...") + consumer = KafkaEventConsumer(handlers=handlers, storage=storage, history_file=cfg.HISTORY_FILE) + + # Run consumer loop + logger.info("Starting consumer loop...") + consumer.run() + + +if __name__ == '__main__': + main() diff --git a/examples/rag_event_ingest/kafka_consumer/models/__init__.py b/examples/rag_event_ingest/kafka_consumer/models/__init__.py new file mode 100644 index 000000000..2abce8a0d --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/models/__init__.py @@ -0,0 +1,4 @@ +# Models package +from .events import S3Event, HandlerResult, IngestionRecord + +__all__ = ['S3Event', 'HandlerResult', 'IngestionRecord'] diff --git a/examples/rag_event_ingest/kafka_consumer/models/events.py b/examples/rag_event_ingest/kafka_consumer/models/events.py new file mode 100644 index 000000000..4baf7112f --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/models/events.py @@ -0,0 +1,138 @@ +# models/events.py +"""Data models for Kafka consumer events and results.""" + +from dataclasses import dataclass, field, fields +from datetime import datetime +from typing import Any, Callable, ClassVar, Dict, Optional +from urllib.parse import unquote_plus + +from config.constants import ( + STATUS_SUCCESS, + STATUS_FAILED, + STATUS_SKIPPED, + # S3 Event fields + EVENT_NAME, + EVENT_RECORDS, + EVENT_FIRST_RECORD_INDEX, + EVENT_S3, + EVENT_BUCKET, + EVENT_OBJECT, + EVENT_KEY, + EVENT_SIZE, + EVENT_ETAG, + EVENT_NAME_FIELD, + EVENT_PREFIX_CREATED, + EVENT_PREFIX_REMOVED, + EVENT_TYPE_CREATE, + EVENT_TYPE_DELETE, + # Record field names (for transformers) + FIELD_START_TIME, + FIELD_END_TIME, + FIELD_DURATION_SECONDS, +) + + +@dataclass +class S3Event: + """Represents a MinIO S3 event from Kafka.""" + bucket: str + key: str + size: int + etag: str + event_type: str + collection: str = '' + + @classmethod + def from_kafka_message( + cls, + event: Dict[str, Any], + collection_resolver: Callable[[str], str] + ) -> Optional['S3Event']: + """Parse S3 event from Kafka message. + + Args: + event: Raw Kafka message value + collection_resolver: Function to resolve bucket -> collection name + """ + if EVENT_NAME not in event: + return None + + event_name = event[EVENT_NAME] + + if event_name.startswith(EVENT_PREFIX_CREATED): + event_type = EVENT_TYPE_CREATE + elif event_name.startswith(EVENT_PREFIX_REMOVED): + event_type = EVENT_TYPE_DELETE + else: + return None + + records = event.get(EVENT_RECORDS, []) + if not records: + return None + + record = records[EVENT_FIRST_RECORD_INDEX] + s3_data = record[EVENT_S3] + bucket = s3_data[EVENT_BUCKET][EVENT_NAME_FIELD] + obj_data = s3_data[EVENT_OBJECT] + key = unquote_plus(obj_data[EVENT_KEY]) + size = obj_data.get(EVENT_SIZE, 0) + etag = obj_data.get(EVENT_ETAG, '') + + return cls( + bucket=bucket, + key=key, + size=size, + etag=etag, + event_type=event_type, + collection=collection_resolver(bucket) + ) + + +@dataclass +class HandlerResult: + """Result from a handler execution.""" + success: bool + status: str # SUCCESS, FAILED, SKIPPED, DELETED + error_message: Optional[str] = None + task_id: Optional[str] = None # For RAG status tracking + + @classmethod + def success_result(cls, task_id: Optional[str] = None) -> 'HandlerResult': + return cls(success=True, status=STATUS_SUCCESS, task_id=task_id) + + @classmethod + def failed_result(cls, error: str, task_id: Optional[str] = None) -> 'HandlerResult': + return cls(success=False, status=STATUS_FAILED, error_message=error, task_id=task_id) + + @classmethod + def skipped_result(cls, reason: str) -> 'HandlerResult': + return cls(success=True, status=STATUS_SKIPPED, error_message=reason) + + +@dataclass +class IngestionRecord: + """Record of an ingestion operation for history tracking.""" + file_name: str + bucket: str + collection: str + status: str + start_time: datetime + end_time: datetime = field(default_factory=datetime.now) + duration_seconds: float = 0.0 + error_message: Optional[str] = None + task_id: Optional[str] = None + + _TRANSFORMERS: ClassVar[Dict[str, Callable]] = { + FIELD_START_TIME: lambda v: v.isoformat(), + FIELD_END_TIME: lambda v: v.isoformat(), + FIELD_DURATION_SECONDS: lambda v: round(v, 2), + } + + def to_dict(self) -> Dict[str, Any]: + """Convert to dictionary for JSON serialization.""" + result = {} + for f in fields(self): + value = getattr(self, f.name) + transform = self._TRANSFORMERS.get(f.name) + result[f.name] = transform(value) if transform else value + return result diff --git a/examples/rag_event_ingest/kafka_consumer/requirements.txt b/examples/rag_event_ingest/kafka_consumer/requirements.txt new file mode 100644 index 000000000..3f3818161 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/requirements.txt @@ -0,0 +1,4 @@ +kafka-python==2.0.2 +minio==7.2.0 +requests==2.31.0 +requests-toolbelt==1.0.0 diff --git a/examples/rag_event_ingest/kafka_consumer/router.py b/examples/rag_event_ingest/kafka_consumer/router.py new file mode 100644 index 000000000..df9432357 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/router.py @@ -0,0 +1,104 @@ +# router.py +"""File routing module for MinIO event processing.""" + +import logging +from pathlib import Path +from typing import Dict, Any, List, Set, Union + +from config.constants import ( + VIDEO_EXTENSIONS, + DOCUMENT_EXTENSIONS, + IMAGE_EXTENSIONS, + AUDIO_EXTENSIONS, + SKIP_EXTENSIONS, + DEST_VSS, + DEST_RAG, + DEST_SKIP, + KEY_DESTINATION, + KEY_FILE_TYPE, + KEY_EXTENSION, + KEY_REASON, + FILE_TYPE_VIDEO, + FILE_TYPE_DOCUMENT, + FILE_TYPE_IMAGE, + FILE_TYPE_AUDIO, + FILE_TYPE_SKIP, + FILE_TYPE_UNKNOWN, + CFG_VIDEO_EXTENSIONS, + CFG_DOCUMENT_EXTENSIONS, + CFG_IMAGE_EXTENSIONS, + CFG_AUDIO_EXTENSIONS, + CFG_SKIP_EXTENSIONS, + CFG_ENABLE_IMAGE_PROCESSING, + CFG_ENABLE_AUDIO_PROCESSING, +) + +logger = logging.getLogger(__name__) + + +class FileRouter: + """Routes files to appropriate processing services based on file type.""" + + def __init__(self, config: Union[Dict[str, Any], Any] = None): + """Initialize router with optional config overrides.""" + if config is None: + config = {} + elif hasattr(config, '__dataclass_fields__'): + config = { + CFG_VIDEO_EXTENSIONS: config.video_extensions, + CFG_DOCUMENT_EXTENSIONS: config.document_extensions, + CFG_IMAGE_EXTENSIONS: config.image_extensions, + CFG_AUDIO_EXTENSIONS: config.audio_extensions, + CFG_SKIP_EXTENSIONS: config.skip_extensions, + CFG_ENABLE_IMAGE_PROCESSING: config.enable_image_processing, + CFG_ENABLE_AUDIO_PROCESSING: config.enable_audio_processing, + } + + self.config = config + self.video_extensions = self._to_set(config.get(CFG_VIDEO_EXTENSIONS, VIDEO_EXTENSIONS)) + self.document_extensions = self._to_set(config.get(CFG_DOCUMENT_EXTENSIONS, DOCUMENT_EXTENSIONS)) + self.image_extensions = self._to_set(config.get(CFG_IMAGE_EXTENSIONS, IMAGE_EXTENSIONS)) + self.audio_extensions = self._to_set(config.get(CFG_AUDIO_EXTENSIONS, AUDIO_EXTENSIONS)) + self.skip_extensions = self._to_set(config.get(CFG_SKIP_EXTENSIONS, SKIP_EXTENSIONS)) + self.enable_image_processing = config.get(CFG_ENABLE_IMAGE_PROCESSING, False) + self.enable_audio_processing = config.get(CFG_ENABLE_AUDIO_PROCESSING, False) + + logger.info(f"FileRouter initialized - Video: {len(self.video_extensions)} types, " + f"Documents: {len(self.document_extensions)} types") + + @staticmethod + def _to_set(value: Union[List, Set, None]) -> Set[str]: + if value is None: + return set() + return set(value) if isinstance(value, (list, tuple)) else value + + def route(self, filename: str) -> dict: + """Determine routing destination for a file.""" + ext = Path(filename).suffix.lower() + + if ext in self.skip_extensions: + return {KEY_DESTINATION: DEST_SKIP, KEY_FILE_TYPE: FILE_TYPE_SKIP, KEY_EXTENSION: ext, KEY_REASON: 'File extension in skip list'} + + if ext in self.video_extensions: + return {KEY_DESTINATION: DEST_VSS, KEY_FILE_TYPE: FILE_TYPE_VIDEO, KEY_EXTENSION: ext} + + if ext in self.document_extensions: + return {KEY_DESTINATION: DEST_RAG, KEY_FILE_TYPE: FILE_TYPE_DOCUMENT, KEY_EXTENSION: ext} + + if ext in self.image_extensions: + if self.enable_image_processing: + return {KEY_DESTINATION: DEST_RAG, KEY_FILE_TYPE: FILE_TYPE_IMAGE, KEY_EXTENSION: ext} + return {KEY_DESTINATION: DEST_SKIP, KEY_FILE_TYPE: FILE_TYPE_IMAGE, KEY_EXTENSION: ext, KEY_REASON: 'Image processing not enabled'} + + if ext in self.audio_extensions: + if self.enable_audio_processing: + return {KEY_DESTINATION: DEST_RAG, KEY_FILE_TYPE: FILE_TYPE_AUDIO, KEY_EXTENSION: ext} + return {KEY_DESTINATION: DEST_SKIP, KEY_FILE_TYPE: FILE_TYPE_AUDIO, KEY_EXTENSION: ext, KEY_REASON: 'Audio processing not enabled'} + + return {KEY_DESTINATION: DEST_RAG, KEY_FILE_TYPE: FILE_TYPE_UNKNOWN, KEY_EXTENSION: ext, KEY_REASON: 'Unknown extension, attempting RAG ingestion'} + + def is_video(self, filename: str) -> bool: + return Path(filename).suffix.lower() in self.video_extensions + + def is_document(self, filename: str) -> bool: + return Path(filename).suffix.lower() in self.document_extensions diff --git a/examples/rag_event_ingest/kafka_consumer/services/__init__.py b/examples/rag_event_ingest/kafka_consumer/services/__init__.py new file mode 100644 index 000000000..24e2e98dd --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/services/__init__.py @@ -0,0 +1,8 @@ +# services/__init__.py +"""External service clients.""" + +from .storage import ObjectStorage +from .document_indexer import DocumentIndexer +from .video_analyzer import VideoAnalyzer + +__all__ = ['ObjectStorage', 'DocumentIndexer', 'VideoAnalyzer'] diff --git a/examples/rag_event_ingest/kafka_consumer/services/document_indexer.py b/examples/rag_event_ingest/kafka_consumer/services/document_indexer.py new file mode 100644 index 000000000..55923accb --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/services/document_indexer.py @@ -0,0 +1,298 @@ +# services/document_indexer.py +"""Document indexing service for RAG pipeline.""" + +import json +import logging +import time +from pathlib import Path +from typing import Optional, Tuple +import requests + +from config import ( + API_INGESTOR_DOCUMENTS, + API_INGESTOR_COLLECTIONS, + API_INGESTOR_COLLECTION, + API_INGESTOR_STATUS, + STATUS_PENDING, + STATUS_PROCESSING, + STATUS_FINISHED, + STATUS_FAILED, + TIMEOUT_DEFAULT, + TIMEOUT_MAX_TASK_WAIT, + VIDEO_INDEX_TIMEOUT, + COLLECTION_EMBEDDING_DIMENSION, + COLLECTION_CHUNK_SIZE, + COLLECTION_CHUNK_OVERLAP, + COLLECTION_VIDEO_CHUNK_SIZE, + COLLECTION_VIDEO_CHUNK_OVERLAP, + CONTENT_TYPE_MAP, + DEFAULT_CONTENT_TYPE, + FIELD_COLLECTION_NAME, + FIELD_BLOCKING, + FIELD_SPLIT_OPTIONS, + FIELD_CHUNK_SIZE, + FIELD_CHUNK_OVERLAP, + FIELD_GENERATE_SUMMARY, + FIELD_EMBEDDING_DIMENSION, + FIELD_TASK_ID, + RESP_COLLECTIONS, + RESP_TASK_ID, + RESP_STATE, + RESP_RESULT, + RESP_FAILED_DOCUMENTS, + RESP_VALIDATION_ERRORS, + RESP_MESSAGE, + RESP_ERROR, +) + +logger = logging.getLogger(__name__) + + +class DocumentIndexer: + """Indexes documents in vector store for RAG retrieval.""" + + def __init__(self, base_url: str, timeout: int = 600): + """Initialize document indexer.""" + self.base_url = base_url.rstrip('/') + self.timeout = timeout + self._created_collections: set = set() + + logger.info(f"DocumentIndexer initialized: {self.base_url}") + + def ensure_collection_exists(self, collection_name: str) -> bool: + """Create collection if it doesn't exist.""" + if collection_name in self._created_collections: + return True + + # Check if collection exists + try: + response = requests.get( + f'{self.base_url}{API_INGESTOR_COLLECTIONS}', + timeout=TIMEOUT_DEFAULT + ) + except requests.RequestException as e: + logger.error(f"Error checking collections: {e}") + return False + + if response.status_code == 200: + collections = response.json().get(RESP_COLLECTIONS, []) + if collection_name in collections: + logger.info(f"Collection '{collection_name}' already exists") + self._created_collections.add(collection_name) + return True + + # Create collection + logger.info(f"Creating collection '{collection_name}'...") + try: + create_response = requests.post( + f'{self.base_url}{API_INGESTOR_COLLECTION}', + json={ + FIELD_COLLECTION_NAME: collection_name, + FIELD_EMBEDDING_DIMENSION: COLLECTION_EMBEDDING_DIMENSION, + 'metadata_schema': [] + }, + headers={'Content-Type': 'application/json'}, + timeout=TIMEOUT_DEFAULT + ) + except requests.RequestException as e: + logger.error(f"Error creating collection: {e}") + return False + + if create_response.status_code in [200, 201]: + logger.info(f"✓ Collection '{collection_name}' created") + self._created_collections.add(collection_name) + return True + + logger.error(f"Failed to create collection: {create_response.status_code}") + return False + + def upload( + self, + file_data: bytes, + filename: str, + collection: str, + chunk_size: int = COLLECTION_CHUNK_SIZE, + chunk_overlap: int = COLLECTION_CHUNK_OVERLAP + ) -> Optional[str]: + """Upload document to ingestor server.""" + if not self.ensure_collection_exists(collection): + logger.error("Failed to ensure collection exists") + return None + + content_type = self._get_content_type(filename) + files = {'documents': (filename, file_data, content_type)} + + data_config = { + FIELD_COLLECTION_NAME: collection, + FIELD_BLOCKING: False, + FIELD_SPLIT_OPTIONS: { + FIELD_CHUNK_SIZE: chunk_size, + FIELD_CHUNK_OVERLAP: chunk_overlap + }, + FIELD_GENERATE_SUMMARY: False + } + + logger.info(f"Uploading to collection: {collection}") + try: + response = requests.post( + f'{self.base_url}{API_INGESTOR_DOCUMENTS}', + files=files, + data={'data': json.dumps(data_config)}, + timeout=self.timeout + ) + except requests.RequestException as e: + logger.error(f"Error uploading document: {e}") + return None + + if response.status_code in [200, 201, 202]: + result = response.json() + task_id = result.get(RESP_TASK_ID) + if task_id: + logger.info(f"✓ File uploaded, task_id: {task_id}") + return task_id + logger.error("No task_id in response") + return None + + logger.error(f"Upload failed: {response.status_code} - {response.text}") + return None + + def check_status(self, task_id: str, max_wait: int = TIMEOUT_MAX_TASK_WAIT) -> Tuple[bool, str]: + """Check task status and wait for completion.""" + start_time = time.time() + + while time.time() - start_time < max_wait: + try: + response = requests.get( + f'{self.base_url}{API_INGESTOR_STATUS}', + params={FIELD_TASK_ID: task_id}, + timeout=TIMEOUT_DEFAULT + ) + except requests.RequestException as e: + return False, str(e) + + if response.status_code != 200: + return False, f"Status check failed: {response.status_code}" + + result = response.json() + state = result.get(RESP_STATE, 'UNKNOWN') + + if state == STATUS_FAILED: + return False, result.get(RESP_ERROR, 'Unknown error') + + if state == STATUS_FINISHED: + return self._parse_finished_result(result) + + if state in [STATUS_PENDING, STATUS_PROCESSING]: + elapsed = int(time.time() - start_time) + if elapsed % 5 == 0: + logger.info(f"Task {task_id}: {state} ({elapsed}s)") + + time.sleep(1) + + return False, f"Timeout after {max_wait}s" + + def _parse_finished_result(self, result: dict) -> Tuple[bool, str]: + """Parse result from a finished task.""" + task_result = result.get(RESP_RESULT, {}) + failed_docs = task_result.get(RESP_FAILED_DOCUMENTS, []) + validation_errors = task_result.get(RESP_VALIDATION_ERRORS, []) + + if failed_docs or validation_errors: + return False, f"Failed: {failed_docs}, Errors: {validation_errors}" + return True, task_result.get(RESP_MESSAGE, 'Completed') + + def delete(self, filename: str, collection: str) -> bool: + """Delete document from collection.""" + logger.info(f"Deleting '{filename}' from '{collection}'") + + try: + response = requests.delete( + f'{self.base_url}{API_INGESTOR_DOCUMENTS}', + params={FIELD_COLLECTION_NAME: collection}, + json=[filename], + headers={'Content-Type': 'application/json'}, + timeout=TIMEOUT_DEFAULT + ) + except requests.RequestException as e: + logger.error(f"Error deleting document: {e}") + return False + + if response.status_code in [200, 201, 204]: + logger.info(f"Deleted '{filename}'") + return True + + logger.error(f"Delete failed: {response.status_code}") + return False + + def index_video_description( + self, + collection: str, + video_id: str, + video_name: str, + description: str + ) -> bool: + """Index video description in Milvus.""" + try: + # Ensure collection exists first + if not self.ensure_collection_exists(collection): + logger.error(f"Failed to ensure collection '{collection}' exists") + return False + + doc_filename = f"{video_name}_description.json" + + # Delete existing document first to allow re-indexing + self.delete(doc_filename, collection) + + content = f"""Video Name: {video_name} +Video ID: {video_id} +Source: VSS Analysis + +Description: +{description}""" + + doc_data = { + 'content': content, + 'metadata': { + 'content_type': 'video', + 'video_id': video_id, + 'video_name': video_name, + 'source': 'vss_analysis' + } + } + + files = { + 'documents': (doc_filename, json.dumps(doc_data).encode(), 'application/json') + } + + data_config = { + FIELD_COLLECTION_NAME: collection, + FIELD_BLOCKING: True, + FIELD_SPLIT_OPTIONS: { + FIELD_CHUNK_SIZE: COLLECTION_VIDEO_CHUNK_SIZE, + FIELD_CHUNK_OVERLAP: COLLECTION_VIDEO_CHUNK_OVERLAP + }, + FIELD_GENERATE_SUMMARY: False + } + + response = requests.post( + f'{self.base_url}{API_INGESTOR_DOCUMENTS}', + files=files, + data={'data': json.dumps(data_config)}, + timeout=VIDEO_INDEX_TIMEOUT + ) + + if response.status_code in [200, 201, 202]: + logger.info(f"✓ Video description indexed to '{collection}'") + return True + else: + logger.error(f"Failed to index video description: {response.status_code} - {response.text}") + return False + + except requests.RequestException as e: + logger.error(f"Error indexing video description: {e}") + return False + + def _get_content_type(self, filename: str) -> str: + """Get content type from filename.""" + ext = Path(filename).suffix.lower() + return CONTENT_TYPE_MAP.get(ext, DEFAULT_CONTENT_TYPE) diff --git a/examples/rag_event_ingest/kafka_consumer/services/storage.py b/examples/rag_event_ingest/kafka_consumer/services/storage.py new file mode 100644 index 000000000..8f50a1f7b --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/services/storage.py @@ -0,0 +1,195 @@ +# services/storage.py +"""S3-compatible object storage service.""" + +import io +import json +import logging +from abc import ABC, abstractmethod +from typing import Dict, Optional + +from minio import Minio +from minio.error import S3Error + +from config import ( + MINIO_ENDPOINT, + MINIO_ACCESS_KEY, + MINIO_SECRET_KEY, + MINIO_SECURE, + MINIO_DEFAULT_COLLECTION, + MINIO_SOURCES, + CFG_ENDPOINT, + CFG_ACCESS, + CFG_SECRET, + CFG_SECURE, + CFG_COLLECTION, + CFG_BUCKETS, +) + +logger = logging.getLogger(__name__) + + +# ============================================================================= +# Abstract Interface +# ============================================================================= + +class StorageBackend(ABC): + """Abstract interface for object storage operations. + + Implement this to add new backends (Azure Blob, GCS, etc.) + """ + + @abstractmethod + def download(self, bucket: str, key: str) -> bytes: + """Download file from storage.""" + pass + + @abstractmethod + def upload(self, bucket: str, key: str, data: bytes, content_type: Optional[str] = None) -> None: + """Upload file to storage.""" + pass + + @abstractmethod + def delete(self, bucket: str, key: str) -> None: + """Delete file from storage.""" + pass + + @abstractmethod + def exists(self, bucket: str, key: str) -> bool: + """Check if file exists.""" + pass + + +# ============================================================================= +# S3 Implementation +# ============================================================================= + +class S3Backend(StorageBackend): + """S3-compatible storage (MinIO, AWS S3, Wasabi, etc.).""" + + def __init__(self, client: Minio): + self._client = client + + @classmethod + def create( + cls, + endpoint: str, + access_key: str, + secret_key: str, + secure: bool = False, + ) -> 'S3Backend': + """Factory method to create S3 backend.""" + client = Minio(endpoint, access_key=access_key, secret_key=secret_key, secure=secure) + logger.info(f"Created S3 client: {endpoint}") + return cls(client) + + def download(self, bucket: str, key: str) -> bytes: + response = self._client.get_object(bucket, key) + try: + data = response.read() + finally: + response.close() + response.release_conn() + logger.info(f"Downloaded {bucket}/{key} ({len(data)} bytes)") + return data + + def upload(self, bucket: str, key: str, data: bytes, content_type: Optional[str] = None) -> None: + self._client.put_object( + bucket, key, io.BytesIO(data), + length=len(data), + content_type=content_type or 'application/octet-stream' + ) + logger.info(f"Uploaded {bucket}/{key}") + + def delete(self, bucket: str, key: str) -> None: + self._client.remove_object(bucket, key) + logger.info(f"Deleted {bucket}/{key}") + + def exists(self, bucket: str, key: str) -> bool: + try: + self._client.stat_object(bucket, key) + return True + except S3Error: + return False + + +# ============================================================================= +# Object Storage (Factory + Bucket Mapping) +# ============================================================================= + +class ObjectStorage: + """Object storage with bucket-to-collection mapping. + + Handles single or multiple S3 sources via configuration. + """ + + def __init__(self): + self._backends: Dict[str, StorageBackend] = {} + self._bucket_to_backend: Dict[str, str] = {} + self._bucket_to_collection: Dict[str, str] = {} + self._default_collection = MINIO_DEFAULT_COLLECTION + self._configure() + + def _configure(self): + if MINIO_SOURCES: + self._configure_multi_source(MINIO_SOURCES) + else: + self._configure_single_source() + + def _configure_single_source(self): + logger.info(f"Single S3 mode: {MINIO_ENDPOINT}") + self._backends['default'] = S3Backend.create( + MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_SECURE + ) + + def _configure_multi_source(self, sources_json: str): + config = json.loads(sources_json) + for name, src in config.items(): + self._configure_source(name, src) + + def _configure_source(self, name: str, src: dict): + """Configure a single S3 source and register its buckets.""" + logger.info(f"Configuring S3 source '{name}': {src[CFG_ENDPOINT]}") + + self._backends[name] = S3Backend.create( + src[CFG_ENDPOINT], + src.get(CFG_ACCESS, MINIO_ACCESS_KEY), + src.get(CFG_SECRET, MINIO_SECRET_KEY), + src.get(CFG_SECURE, False) + ) + + collection = src.get(CFG_COLLECTION, name.replace('-', '_')) + self._register_buckets(name, src.get(CFG_BUCKETS, []), collection) + + def _register_buckets(self, backend_name: str, buckets: list, collection: str): + """Register bucket-to-backend and bucket-to-collection mappings.""" + for bucket in buckets: + self._bucket_to_backend[bucket] = backend_name + self._bucket_to_collection[bucket] = collection + logger.info(f" {bucket} → {collection}") + + def _get_backend(self, bucket: str) -> StorageBackend: + if bucket in self._bucket_to_backend: + return self._backends[self._bucket_to_backend[bucket]] + return next(iter(self._backends.values())) + + def download(self, bucket: str, key: str) -> bytes: + return self._get_backend(bucket).download(bucket, key) + + def get_collection_for_bucket(self, bucket: str) -> str: + """Get collection name for bucket. + + Priority: + 1. Explicit mapping from MINIO_SOURCES config + 2. Default collection from COLLECTION_NAME env var + 3. Fallback: bucket name with hyphens → underscores + """ + # Check explicit mapping first + if bucket in self._bucket_to_collection: + return self._bucket_to_collection[bucket] + + # Use default collection if configured + if self._default_collection: + return self._default_collection + + # Fallback to bucket name conversion + return bucket.replace('-', '_') diff --git a/examples/rag_event_ingest/kafka_consumer/services/video_analyzer.py b/examples/rag_event_ingest/kafka_consumer/services/video_analyzer.py new file mode 100644 index 000000000..af7082550 --- /dev/null +++ b/examples/rag_event_ingest/kafka_consumer/services/video_analyzer.py @@ -0,0 +1,326 @@ +# services/video_analyzer.py +"""Video analysis service using VSS 2.4 API.""" + +import json +import re +import logging +from pathlib import Path +from typing import Optional, Tuple +import requests + +from config import ( + API_VSS_FILES, + API_VSS_SUMMARIZE, + CONTENT_TYPE_MAP, + DEFAULT_CONTENT_TYPE, + VSS_DEFAULT_PROMPT, + VSS_SYSTEM_PROMPT, + VSS_CAPTION_SUMMARIZATION_PROMPT, + VSS_SUMMARY_AGGREGATION_PROMPT, + VSS_CHUNK_DURATION, + VSS_CHUNK_OVERLAP, + VSS_NUM_FRAMES_PER_CHUNK, + VSS_MAX_TOKENS, + VSS_MODEL, + VSS_UPLOAD_TIMEOUT, + VSS_STREAM_ENABLED, + # API fields + FIELD_ID, + FIELD_FILE, + FIELD_MODEL, + FIELD_PROMPT, + FIELD_SYSTEM_PROMPT, + FIELD_MAX_TOKENS, + FIELD_CHUNK_DURATION, + FIELD_CHUNK_OVERLAP_DURATION, + FIELD_NUM_FRAMES_PER_CHUNK, + FIELD_CAPTION_SUMMARIZATION_PROMPT, + FIELD_SUMMARY_AGGREGATION_PROMPT, + FIELD_PURPOSE, + FIELD_MEDIA_TYPE, + FIELD_STREAM, + VALUE_VISION, + VALUE_VIDEO, + RESP_CONTENT, + RESP_RESPONSE, + RESP_TEXT, + RESP_CHOICES, + RESP_MESSAGE, + RESP_DELTA, + RESP_DATA_PREFIX, +) + +logger = logging.getLogger(__name__) + + +class VideoAnalyzer: + """Analyzes videos using VSS 2.4 /files and /summarize APIs.""" + + def __init__(self, base_url: str, timeout: int = 1800): + """Initialize video analyzer. + + Args: + base_url: VSS server URL + timeout: Timeout for summarization (default 30 min for long videos) + """ + self.base_url = base_url.rstrip('/') + self.timeout = timeout + + logger.info(f"VideoAnalyzer initialized (VSS 2.4): {self.base_url}") + + def upload_video(self, video_data: bytes, filename: str) -> Tuple[bool, Optional[str]]: + """Upload video to VSS using /files API. + + Args: + video_data: Video file bytes + filename: Original filename + + Returns: + Tuple of (success, file_id) + """ + file_name_sanitized = self._sanitize_filename(filename) + content_type = self._get_content_type(file_name_sanitized) + + logger.info(f"Uploading video to VSS: {file_name_sanitized} ({len(video_data)} bytes)") + + try: + response = requests.post( + f'{self.base_url}{API_VSS_FILES}', + files={FIELD_FILE: (file_name_sanitized, video_data, content_type)}, + data={FIELD_PURPOSE: VALUE_VISION, FIELD_MEDIA_TYPE: VALUE_VIDEO}, + timeout=VSS_UPLOAD_TIMEOUT + ) + except requests.RequestException as e: + logger.error(f"Error uploading video to VSS: {e}") + return False, None + + if response.status_code not in [200, 201]: + logger.error(f"Upload failed: {response.status_code}") + logger.error(f"Response: {response.text}") + return False, None + + result = response.json() + file_id = result.get(FIELD_ID) + + if not file_id: + logger.error(f"No file ID in response: {result}") + return False, None + + logger.info(f"✓ Video uploaded: {file_name_sanitized} (id: {file_id})") + return True, file_id + + def get_video_description(self, file_id: str) -> Optional[str]: + """Get video description using /summarize API. + + Uses streaming mode for real-time progress updates if enabled. + + Args: + file_id: File ID from upload + + Returns: + Video summary text, or None if failed + """ + if VSS_STREAM_ENABLED: + return self._get_description_streaming(file_id) + return self._get_description_blocking(file_id) + + def _build_summarize_payload(self, file_id: str, stream: bool = False) -> dict: + """Build the payload for summarize request.""" + payload = { + FIELD_ID: file_id, + FIELD_MODEL: VSS_MODEL, + FIELD_PROMPT: VSS_DEFAULT_PROMPT, + FIELD_SYSTEM_PROMPT: VSS_SYSTEM_PROMPT, + FIELD_MAX_TOKENS: VSS_MAX_TOKENS, + FIELD_STREAM: stream, + } + + # Add chunking for long videos + if VSS_CHUNK_DURATION > 0: + payload.update({ + FIELD_CHUNK_DURATION: VSS_CHUNK_DURATION, + FIELD_CHUNK_OVERLAP_DURATION: VSS_CHUNK_OVERLAP, + FIELD_NUM_FRAMES_PER_CHUNK: VSS_NUM_FRAMES_PER_CHUNK, + FIELD_CAPTION_SUMMARIZATION_PROMPT: VSS_CAPTION_SUMMARIZATION_PROMPT, + FIELD_SUMMARY_AGGREGATION_PROMPT: VSS_SUMMARY_AGGREGATION_PROMPT, + }) + + return payload + + def _get_description_blocking(self, file_id: str) -> Optional[str]: + """Get video description using blocking mode.""" + logger.info(f"Requesting summarization (blocking) for file: {file_id}") + + payload = self._build_summarize_payload(file_id, stream=False) + + if VSS_CHUNK_DURATION > 0: + logger.info(f"Using chunked processing: {VSS_CHUNK_DURATION}s chunks, " + f"{VSS_NUM_FRAMES_PER_CHUNK} frames/chunk") + + try: + response = requests.post( + f'{self.base_url}{API_VSS_SUMMARIZE}', + json=payload, + headers={'Content-Type': 'application/json'}, + timeout=self.timeout + ) + except requests.RequestException as e: + logger.error(f"Error requesting video summary: {e}") + return None + + if response.status_code != 200: + logger.error(f"Summarize failed: {response.status_code}") + logger.error(f"Response: {response.text}") + return None + + result = response.json() + content = self._extract_content(result) + + if not content: + logger.warning("No content in summarize response") + return None + + logger.info(f"✓ Got video summary ({len(content)} chars)") + return content + + def _get_description_streaming(self, file_id: str) -> Optional[str]: + """Get video description using streaming mode (Server-Sent Events).""" + logger.info(f"Requesting summarization (streaming) for file: {file_id}") + + payload = self._build_summarize_payload(file_id, stream=True) + + if VSS_CHUNK_DURATION > 0: + logger.info(f"Using chunked processing: {VSS_CHUNK_DURATION}s chunks, " + f"{VSS_NUM_FRAMES_PER_CHUNK} frames/chunk") + + try: + response = requests.post( + f'{self.base_url}{API_VSS_SUMMARIZE}', + json=payload, + headers={'Content-Type': 'application/json'}, + timeout=self.timeout, + stream=True # Enable streaming response + ) + except requests.RequestException as e: + logger.error(f"Error requesting video summary (streaming): {e}") + return None + + if response.status_code != 200: + logger.error(f"Summarize failed: {response.status_code}") + logger.error(f"Response: {response.text}") + return None + + # Collect streaming chunks + content = self._collect_stream_content(response) + + if not content: + logger.warning("No content in streaming response") + return None + + logger.info(f"✓ Got video summary via streaming ({len(content)} chars)") + return content + + def _collect_stream_content(self, response) -> Optional[str]: + """Collect content from streaming response (Server-Sent Events).""" + chunks = [] + chunk_count = 0 + + for line in response.iter_lines(decode_unicode=True): + if not line: + continue + + # SSE format: "data: {...json...}" + if line.startswith(RESP_DATA_PREFIX): + json_str = line[len(RESP_DATA_PREFIX):] + + # Handle [DONE] marker + if json_str.strip() == '[DONE]': + logger.info("Stream completed") + break + + try: + data = json.loads(json_str) + content = self._extract_stream_chunk(data) + if content: + chunks.append(content) + chunk_count += 1 + if chunk_count % 10 == 0: + logger.info(f"Received {chunk_count} chunks...") + elif chunk_count == 0: + # Debug: log first non-content chunk to understand format + logger.debug(f"SSE data (no content): {json_str[:200]}") + except json.JSONDecodeError as e: + logger.debug(f"SSE JSON parse error: {e}, line: {json_str[:100]}") + continue + else: + # Non-data line - could be event type or comment + if chunk_count == 0: + logger.debug(f"SSE non-data line: {line[:100]}") + + if not chunks: + logger.warning("No streaming chunks collected, checking full response") + return None + + logger.info(f"Collected {chunk_count} streaming chunks") + return ''.join(chunks) + + def _extract_stream_chunk(self, data: dict) -> Optional[str]: + """Extract content from a single streaming chunk.""" + choices = data.get(RESP_CHOICES, []) + if choices: + delta = choices[0].get(RESP_DELTA, {}) + content = delta.get(RESP_CONTENT) + if content: + return content + + message = choices[0].get(RESP_MESSAGE, {}) + content = message.get(RESP_CONTENT) + if content: + return content + + if RESP_CONTENT in data: + return data[RESP_CONTENT] + + if RESP_RESPONSE in data: + return data[RESP_RESPONSE] + + if RESP_TEXT in data: + return data[RESP_TEXT] + + return None + + def _extract_content(self, result: dict) -> Optional[str]: + """Extract text content from VSS response. + + returns OpenAI-compatible format: + { + "choices": [{"message": {"content": "..."}}] + } + """ + # Try OpenAI format first + choices = result.get(RESP_CHOICES, []) + if choices: + message = choices[0].get(RESP_MESSAGE, {}) + content = message.get(RESP_CONTENT) + if content: + return content.strip() + + if RESP_CONTENT in result: + return result[RESP_CONTENT].strip() + + if RESP_RESPONSE in result: + return result[RESP_RESPONSE].strip() + + return None + + def _sanitize_filename(self, filename: str) -> str: + """Sanitize filename for VSS compatibility.""" + name = Path(filename).name + file_name_sanitized = re.sub(r'[^A-Za-z0-9_.\-]', '_', name) + file_name_sanitized = re.sub(r'_+', '_', file_name_sanitized) + return file_name_sanitized + + def _get_content_type(self, filename: str) -> str: + """Get content type from filename.""" + ext = Path(filename).suffix.lower() + return CONTENT_TYPE_MAP.get(ext, DEFAULT_CONTENT_TYPE) diff --git a/notebooks/rag_event_ingest.ipynb b/notebooks/rag_event_ingest.ipynb new file mode 100644 index 000000000..372a6cfca --- /dev/null +++ b/notebooks/rag_event_ingest.ipynb @@ -0,0 +1,1224 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Video/Document Continuous Ingestion from Object Storage\n", + "\n", + "## Purpose\n", + "\n", + "This notebook demonstrates an **automated document and video ingestion pipeline** that:\n", + "\n", + "1. Monitors emulated object storage for new uploads via Kafka events\n", + "2. Routes files to appropriate AI services based on file type, currently supports document and video ingestion\n", + "5. Enables RAG Agent for semantic search and contextual Q&A over all ingested content\n", + "\n", + "## What Gets Deployed\n", + "\n", + "1. **NVIDIA RAG** - Document indexing, vector search, and AI-powered Q&A (NIMs, Milvus, Ingestor)\n", + "2. **NVIDIA VSS** - Video understanding and summarization (VLM, LLM NIMs, VSS Engine)\n", + "3. **Continuous Ingestion** - Event-driven ingestion pipeline (Kafka, MinIO, Consumer)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "### Hardware\n", + "- **GPU**: 4x RTX PRO 6000 Blackwell or 4x H100\n", + "\n", + "### Software (pre-installed required)\n", + "- Ubuntu 22.04 or later\n", + "- Docker 24.0+ with Docker Compose v2\n", + "- NVIDIA Driver 570+\n", + "- NVIDIA Container Toolkit\n", + "\n", + "### API Keys\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
KeyPurposeHow to Get
NGC_API_KEYDocker login, NIM deploymentsNGC Portal → Generate API Key
HF_TOKENDownload VSS modelsHuggingFace Tokens → Create token with Read access
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of Contents\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
SectionDescription
SetupClone repo, install deps, set API keys, load helpers
Deploy RAGNIMs, Vector DB, Ingestor, RAG Server
Deploy VSSClone VSS, deploy NIMs and VLM
Deploy Continuous IngestionKafka, MinIO, Consumer
TestingUpload documents & videos, query RAG
Clean UpStop services, clean data
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "- **RAG Blueprint**: [NVIDIA RAG Documentation](https://github.com/NVIDIA-AI-Blueprints/rag/blob/develop/docs/deploy-docker-self-hosted.md)\n", + "- **VSS**: [Video Search & Summarization Documentation](https://docs.nvidia.com/vss/latest/index.html)\n", + "- **NIM**: [NVIDIA NIM Documentation](https://docs.nvidia.com/nim/index.html)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup\n", + "\n", + "Clone the repository, configure API keys, and load helper functions.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Clone Repository\n", + "\n", + "Clone the RAG Blueprint repo to `~/rag`. This includes the consumer source code, deploy configs, and sample test data.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess, sys, os, shutil\n", + "\n", + "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", + "RAG_REPO_URL = \"https://github.com/NVIDIA-AI-Blueprints/rag.git\"\n", + "\n", + "# Ensure git-lfs is installed before any LFS operations\n", + "if not shutil.which(\"git-lfs\"):\n", + " print(\"[INSTALLING] git-lfs...\")\n", + " subprocess.run(\"sudo apt-get update && sudo apt-get install -y git-lfs && git lfs install\", shell=True, check=True)\n", + "else:\n", + " print(\"[OK] git-lfs found\")\n", + "\n", + "# Clone from correct branch (skip if already exists)\n", + "if not os.path.exists(RAG_REPO_DIR):\n", + " subprocess.run(f\"git clone {RAG_REPO_URL} {RAG_REPO_DIR}\", shell=True, check=True)\n", + "else:\n", + " print(f\"[OK] RAG repo already exists: {RAG_REPO_DIR}\")\n", + "subprocess.run(\"git lfs pull\", shell=True, cwd=RAG_REPO_DIR, check=True)\n", + "\n", + "# Verify\n", + "for path in [\"deploy/compose\", \"examples/rag_event_ingest/kafka_consumer\", \"examples/rag_event_ingest/data\"]:\n", + " status = \"[OK]\" if os.path.exists(os.path.join(RAG_REPO_DIR, path)) else \"[MISSING]\"\n", + " print(f\" {status} {path}\")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Install Dependencies\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! python3 -m ensurepip --upgrade" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Ensure pip is available (some minimal Python installs lack it)\n", + "subprocess.run([sys.executable, \"-m\", \"ensurepip\", \"--upgrade\"], capture_output=True)\n", + "\n", + "def check_install_system_pkg(cmd: str, install_cmd: str):\n", + " if shutil.which(cmd):\n", + " print(f\" [OK] {cmd} found\")\n", + " return True\n", + " print(f\" [INSTALLING] {cmd}...\")\n", + " result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True)\n", + " if result.returncode == 0:\n", + " print(f\" [OK] {cmd} installed\")\n", + " return True\n", + " print(f\" [ERROR] Failed to install {cmd}. Please install manually: {install_cmd}\")\n", + " return False\n", + "\n", + "check_install_system_pkg(\"git\", \"sudo apt-get update && sudo apt-get install -y git\")\n", + "\n", + "# Install Python packages\n", + "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\", \"aiohttp\", \"requests\", \"python-dotenv\", \"pyyaml\"])\n", + "print(\"[OK] Ready\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Set API Keys\n", + "\n", + "Configure NGC and HuggingFace API keys for NIM deployments and model downloads.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "\n", + "def set_api_key(env_var: str, prompt: str, required: bool = True):\n", + " if os.environ.get(env_var):\n", + " print(f\" [OK] {env_var} already set ({os.environ[env_var][:10]}...)\")\n", + " return True\n", + " key = getpass.getpass(prompt)\n", + " if key:\n", + " os.environ[env_var] = key\n", + " print(f\" [OK] {env_var} set\")\n", + " return True\n", + " if required:\n", + " print(f\" [ERROR] {env_var} is required\")\n", + " return False\n", + " print(f\" [SKIP] {env_var} (optional)\")\n", + " return True\n", + "\n", + "set_api_key(\"NGC_API_KEY\", \"Enter NGC_API_KEY (starts with 'nvapi-'): \", required=True)\n", + "set_api_key(\"HF_TOKEN\", \"Enter HF_TOKEN (optional, press Enter to skip): \", required=False)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Helper Functions\n", + "\n", + "Shared utilities for deployment, file upload, status checks, and RAG queries.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install dependencies\n", + "import sys\n", + "!{sys.executable} -m pip install -q minio aiohttp requests python-dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os, sys, json, re, subprocess, time, socket, asyncio\n", + "import aiohttp, requests\n", + "from typing import List, Optional, Dict\n", + "\n", + "try:\n", + " from minio import Minio\n", + " from minio.error import S3Error\n", + "except ImportError:\n", + " subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\"])\n", + " from minio import Minio\n", + " from minio.error import S3Error\n", + "\n", + "# =============================================================================\n", + "# CONFIGURATION\n", + "# =============================================================================\n", + "\n", + "# Paths relative to RAG repo root\n", + "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", + "EXAMPLE_DIR = os.path.join(RAG_REPO_DIR, \"examples/rag_event_ingest\")\n", + "AIDP_COMPOSE_FILE = os.path.join(EXAMPLE_DIR, \"deploy/docker-compose.yaml\")\n", + "DATA_DIR = os.path.join(EXAMPLE_DIR, \"data\")\n", + "RAG_SERVER_URL = \"http://localhost:8081\"\n", + "INGESTOR_URL = \"http://localhost:8082\"\n", + "\n", + "VSS_DIR = os.path.expanduser(\"~/video-search-and-summarization\")\n", + "VSS_UI_PORT = 9110\n", + "VSS_API_PORT = 8110\n", + "VSS_LLM_PORT = 8107\n", + "VSS_EMBED_PORT = 8106\n", + "VSS_RERANK_PORT = 8105\n", + "LOCAL_NIM_CACHE = os.path.expanduser(\"~/.cache/nim\")\n", + "\n", + "MINIO_ENDPOINT = \"localhost:9201\"\n", + "MINIO_ACCESS_KEY = \"minioadmin\"\n", + "MINIO_SECRET_KEY = \"minioadmin\"\n", + "MINIO_BUCKET = \"aidp-bucket\"\n", + "MINIO_COLLECTION = \"aidp_bucket\"\n", + "MINIO_CONSOLE_PORT = 9211\n", + "\n", + "# =============================================================================\n", + "# SHARED UTILITIES\n", + "# =============================================================================\n", + "\n", + "def run_command(cmd: str, capture: bool = False) -> Optional[str]:\n", + " \"\"\"Execute a shell command and print it.\"\"\"\n", + " print(f\"$ {cmd}\")\n", + " result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)\n", + " return result.stdout if capture else None\n", + "\n", + "def get_host_ip() -> str:\n", + " \"\"\"Get host IP address for external access URLs.\"\"\"\n", + " try:\n", + " s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n", + " s.connect((\"8.8.8.8\", 80))\n", + " ip = s.getsockname()[0]\n", + " s.close()\n", + " return ip\n", + " except OSError:\n", + " return \"localhost\"\n", + "\n", + "def get_minio_client() -> Minio:\n", + " \"\"\"Create MinIO client for AIDP bucket operations.\"\"\"\n", + " return Minio(MINIO_ENDPOINT, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False)\n", + "\n", + "def upload_file(local_path: str, object_name: Optional[str] = None) -> bool:\n", + " \"\"\"Upload a local file to MinIO AIDP bucket.\"\"\"\n", + " if not os.path.exists(local_path):\n", + " print(f\"[ERROR] File not found: {local_path}\")\n", + " return False\n", + " obj = object_name or os.path.basename(local_path)\n", + " try:\n", + " client = get_minio_client()\n", + " if not client.bucket_exists(MINIO_BUCKET):\n", + " client.make_bucket(MINIO_BUCKET)\n", + " client.fput_object(MINIO_BUCKET, obj, local_path)\n", + " print(f\"[OK] Uploaded: {obj}\")\n", + " return True\n", + " except S3Error as e:\n", + " print(f\"[ERROR] {e}\")\n", + " return False\n", + "\n", + "def verify_file_in_storage(object_name: str, bucket: str = MINIO_BUCKET) -> bool:\n", + " \"\"\"Check if a file exists in MinIO bucket and print verification status.\"\"\"\n", + " try:\n", + " client = get_minio_client()\n", + " stat = client.stat_object(bucket, object_name)\n", + " print(f\"[OK] File verified in storage:\")\n", + " print(f\" Bucket: {bucket}\")\n", + " print(f\" Object: {object_name}\")\n", + " print(f\" Size: {stat.size:,} bytes\")\n", + " print(f\" Modified: {stat.last_modified}\")\n", + " return True\n", + " except S3Error as e:\n", + " print(f\"[ERROR] File not found in storage: {object_name}\")\n", + " print(f\" Error: {e}\")\n", + " return False\n", + "\n", + "def get_consumer_logs(lines: int = 30) -> None:\n", + " \"\"\"Show recent Kafka consumer logs.\"\"\"\n", + " run_command(f\"docker logs kafka-consumer --tail {lines}\")\n", + "\n", + "async def query_rag(question: str, collection: str = None) -> Optional[str]:\n", + " \"\"\"Query RAG system and print the answer.\"\"\"\n", + " coll = collection or MINIO_COLLECTION\n", + " print(f\"Q: {question}\\nCollection: {coll}\\n\" + \"-\" * 40)\n", + "\n", + " payload = {\n", + " \"messages\": [{\"role\": \"user\", \"content\": question}],\n", + " \"use_knowledge_base\": True,\n", + " \"collection_names\": [coll],\n", + " }\n", + " try:\n", + " async with aiohttp.ClientSession() as session:\n", + " async with session.post(\n", + " f\"{RAG_SERVER_URL}/generate\", json=payload,\n", + " timeout=aiohttp.ClientTimeout(total=120),\n", + " ) as resp:\n", + " text = await resp.text()\n", + " # Parse SSE response: extract content from each \"data: {...}\" line\n", + " chunks = []\n", + " for line in text.split(\"\\n\"):\n", + " if not line.startswith(\"data: \") or line[6:] == \"[DONE]\":\n", + " continue\n", + " try:\n", + " msg = json.loads(line[6:]).get(\"choices\", [{}])[0].get(\"message\", {})\n", + " if msg.get(\"content\"):\n", + " chunks.append(msg[\"content\"])\n", + " except json.JSONDecodeError:\n", + " pass\n", + " answer = \"\".join(chunks)\n", + " print(f\"Answer: {answer}\")\n", + " return answer\n", + " except aiohttp.ClientError as e:\n", + " print(f\"[ERROR] {e}\")\n", + " return None\n", + "\n", + "print(f\"[OK] Helpers loaded | Host IP: {get_host_ip()}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy NVIDIA RAG\n", + "\n", + "Deploy the NVIDIA RAG: NIMs (LLM, Embedding, Reranker), Milvus vector database, Ingestor server, and RAG server.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ngc_key = os.environ.get(\"NGC_API_KEY\")\n", + "if not ngc_key:\n", + " raise RuntimeError(\"NGC_API_KEY not set! Run the API keys cell first.\")\n", + "\n", + "os.chdir(RAG_REPO_DIR)\n", + "\n", + "# Set env vars needed by docker compose\n", + "os.environ[\"NGC_API_KEY\"] = ngc_key\n", + "os.environ[\"USERID\"] = f\"{os.getuid()}:{os.getgid()}\"\n", + "os.environ[\"COLLECTION_NAME\"] = MINIO_COLLECTION\n", + "\n", + "# Load RAG .env defaults (MODEL_DIRECTORY, etc.)\n", + "from dotenv import load_dotenv\n", + "env_file = os.path.join(RAG_REPO_DIR, \"deploy/compose/.env\")\n", + "if os.path.exists(env_file):\n", + " load_dotenv(env_file, override=False)\n", + "\n", + "# Login to nvcr.io\n", + "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", + " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "# Deploy components\n", + "for label, compose_file in [\n", + " (\"NIMs\", \"deploy/compose/nims.yaml\"),\n", + " (\"Vector DB\", \"deploy/compose/vectordb.yaml\"),\n", + "]:\n", + " print(f\"Deploying {label}...\")\n", + " run_command(f\"docker compose -f {compose_file} up -d\")\n", + "\n", + "print(\"Waiting 30s for Milvus...\")\n", + "time.sleep(30)\n", + "\n", + "for label, compose_file in [\n", + " (\"Ingestor\", \"deploy/compose/docker-compose-ingestor-server.yaml\"),\n", + " (\"RAG Server\", \"deploy/compose/docker-compose-rag-server.yaml\"),\n", + "]:\n", + " print(f\"Deploying {label}...\")\n", + " run_command(f\"docker compose -f {compose_file} up -d\")\n", + "\n", + "ip = get_host_ip()\n", + "print(f\"\\nRAG deployed: http://{ip}:8081 (server) | http://{ip}:8082 (ingestor) | http://{ip}:8090 (UI)\")\n", + "print(f\"COLLECTION_NAME: {MINIO_COLLECTION}\")\n", + "print(\"Wait 2-5 minutes for NIMs to load models, then run the status check cell.\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify RAG services are healthy. Wait 2-5 minutes for NIMs to load models.\n", + "\n", + "The deployment status should be:\n", + "```\n", + "NAMES STATUS\n", + "rag-frontend Up About a minute\n", + "rag-server Up About a minute\n", + "ingestor-server Up About a minute\n", + "milvus-standalone Up 2 minutes (healthy)\n", + "milvus-etcd Up 2 minutes (healthy)\n", + "milvus-minio Up 2 minutes (healthy)\n", + "nim-llm-ms Up 2 minutes (healthy)\n", + "nemoretriever-embedding-ms Up 2 minutes (healthy)\n", + "nemoretriever-ranking-ms Up 2 minutes (healthy)\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check service status and print access URLs\n", + "print(\"Wait 2-5 minutes for services to become healthy.\")\n", + "print(\"Run this cell again after waiting.\\n\")\n", + "\n", + "ip = get_host_ip()\n", + "for name, port, path in [\n", + " (\"RAG Server\", 8081, \"/health\"), (\"Ingestor\", 8082, \"/health\"),\n", + " (\"Frontend\", 8090, \"/\"), (\"Milvus\", 19530, \"/v1/vector/collections\"),\n", + "]:\n", + " try:\n", + " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", + " except requests.ConnectionError:\n", + " s = \"[DOWN]\"\n", + " except requests.Timeout:\n", + " s = \"[TIMEOUT]\"\n", + " print(f\" {s} {name}: http://{ip}:{port}\")\n", + "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(rag|milvus|ingestor|nim|nemoretriever|rerankqa|NAMES)'\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy NVIDIA VSS\n", + "\n", + "Deploy the NVIDIA VSS: NIMs (LLM, Embedding, Reranker) and VLM for video analysis.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# VSS deployment configuration\n", + "VSS_REPO_URL = \"https://github.com/NVIDIA-AI-Blueprints/video-search-and-summarization.git\"\n", + "VSS_TAG = \"v2.4.1\" # VSS Blueprint version (must match VIA_IMAGE tag)\n", + "VSS_GPU_DEVICE = 2 # GPU for VSS LLM NIM\n", + "VSS_VLM_GPU_DEVICE = 3 # GPU for VLM (via-server with Cosmos-Reason2)\n", + "\n", + "# Only deploy LLM for VSS; embedding and reranker are shared from RAG stack\n", + "# RAG NIMs on nvidia-rag network: nemoretriever-embedding-ms:8000, nemoretriever-ranking-ms:8000\n", + "NIM_IMAGES = {\n", + " \"vss-llm\": (\"nvcr.io/nim/meta/llama-3.1-8b-instruct:1.12.0\", VSS_LLM_PORT),\n", + "}\n", + "\n", + "ngc_key = os.environ.get(\"NGC_API_KEY\", \"\")\n", + "hf_token = os.environ.get(\"HF_TOKEN\", \"\")\n", + "if not ngc_key:\n", + " raise RuntimeError(\"NGC_API_KEY not set!\")\n", + "\n", + "# Docker login\n", + "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", + " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "# Clone VSS repo with correct release tag\n", + "if not os.path.exists(VSS_DIR):\n", + " print(f\"Cloning {VSS_REPO_URL} (tag: {VSS_TAG})...\")\n", + " subprocess.run(f\"git clone --branch {VSS_TAG} --depth 1 {VSS_REPO_URL} {VSS_DIR}\", shell=True)\n", + "else:\n", + " print(f\"[OK] VSS repo exists: {VSS_DIR}\")\n", + "\n", + "# Deploy VSS LLM NIM container\n", + "os.makedirs(LOCAL_NIM_CACHE, exist_ok=True)\n", + "for name, (image, port) in NIM_IMAGES.items():\n", + " subprocess.run(f\"docker rm -f {name} 2>/dev/null\", shell=True, capture_output=True)\n", + " cmd = f\"\"\"docker run -d --name {name} \\\n", + " -u $(id -u) --gpus '\"device={VSS_GPU_DEVICE}\"' --shm-size=16GB \\\n", + " --network nvidia-rag -e NGC_API_KEY={ngc_key} \\\n", + " -v \"{LOCAL_NIM_CACHE}:/opt/nim/.cache\" \\\n", + " -p {port}:8000 -e NIM_LOW_MEMORY_MODE=1 -e NIM_RELAX_MEM_CONSTRAINTS=1 \\\n", + " {image}\"\"\"\n", + " result = subprocess.run(cmd, shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + " status = \"[OK]\" if result.returncode == 0 else \"[ERROR]\"\n", + " print(f\" {status} {name} -> port {port}\")\n", + "\n", + "# Deploy VSS application (VLM on separate GPU)\n", + "vss_deploy_dir = f\"{VSS_DIR}/deploy/docker/local_deployment_single_gpu\"\n", + "env_content = f\"\"\"NGC_API_KEY={ngc_key}\n", + "HF_TOKEN={hf_token}\n", + "VIA_IMAGE=nvcr.io/nvidia/blueprint/vss-engine:2.4.1\n", + "FRONTEND_PORT={VSS_UI_PORT}\n", + "BACKEND_PORT={VSS_API_PORT}\n", + "MILVUS_DB_HTTP_PORT=19091\n", + "MILVUS_DB_GRPC_PORT=29530\n", + "MINIO_PORT=9002\n", + "MINIO_WEBUI_PORT=9003\n", + "GRAPH_DB_USERNAME=neo4j\n", + "GRAPH_DB_PASSWORD=password\n", + "ARANGO_DB_USERNAME=arangodb\n", + "ARANGO_DB_PASSWORD=password\n", + "CA_RAG_CONFIG=./config.yaml\n", + "GUARDRAILS_CONFIG=./guardrails\n", + "NVIDIA_VISIBLE_DEVICES={VSS_VLM_GPU_DEVICE}\n", + "VLM_MODEL_TO_USE=cosmos-reason2\n", + "MODEL_PATH=git:https://huggingface.co/nvidia/Cosmos-Reason2-8B\n", + "VLLM_GPU_MEMORY_UTILIZATION=0.4\n", + "VLM_MAX_MODEL_LEN=20480\n", + "DISABLE_GUARDRAILS=true\n", + "DISABLE_CV_PIPELINE=true\n", + "ENABLE_AUDIO=false\n", + "\"\"\"\n", + "with open(f\"{vss_deploy_dir}/.env\", \"w\") as f:\n", + " f.write(env_content)\n", + "\n", + "# Patch config.yaml: LLM points to vss-llm, embedding/reranker reuse RAG NIMs\n", + "config_file = f\"{vss_deploy_dir}/config.yaml\"\n", + "if os.path.exists(config_file):\n", + " cfg = open(config_file).read()\n", + " cfg = re.sub(r\"http://[^:]+:8007/v1\", f\"http://host.docker.internal:{VSS_LLM_PORT}/v1\", cfg)\n", + " cfg = re.sub(r\"http://[^:]+:8006/v1\", \"http://host.docker.internal:9080/v1\", cfg)\n", + " cfg = re.sub(r\"http://[^:]+:8005/v1\", \"http://host.docker.internal:1976/v1\", cfg)\n", + " open(config_file, \"w\").write(cfg)\n", + "\n", + "cmd = f\"cd {vss_deploy_dir} && set -a && source .env && set +a && docker compose up -d\"\n", + "subprocess.run(cmd, shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "ip = get_host_ip()\n", + "print(f\"\\nVSS deployed: http://{ip}:{VSS_UI_PORT} (UI) | http://{ip}:{VSS_API_PORT} (API)\")\n", + "print(\"Embedding/Reranker: shared from RAG stack (nemoretriever-embedding-ms, nemoretriever-ranking-ms)\")\n", + "print(\"Wait 2-5 minutes for NIMs to load models, then run the status check cell.\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify VSS services are healthy. Wait 2-5 minutes for NIMs to load models.\n", + "\n", + "> **Note**: Embedding and reranker NIMs are shared from the RAG stack (`nemoretriever-embedding-ms`, `nemoretriever-ranking-ms`) — no separate VSS containers needed.\n", + "\n", + "The deployment status should be:\n", + "```\n", + "NAMES STATUS\n", + "local_deployment_single_gpu-via-server-1 Up About a minute\n", + "local_deployment_single_gpu-elasticsearch-1 Up About a minute\n", + "local_deployment_single_gpu-graph-db-1 Up About a minute\n", + "local_deployment_single_gpu-minio-1 Up About a minute\n", + "local_deployment_single_gpu-arango-db-1 Up About a minute\n", + "local_deployment_single_gpu-milvus-standalone-1 Up About a minute (healthy)\n", + "vss-llm Up About a minute\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check service status and print access URLs\n", + "ip = get_host_ip()\n", + "for name, port, path in [\n", + " (\"VSS UI\", VSS_UI_PORT, \"/\"), (\"VSS API\", VSS_API_PORT, \"/\"),\n", + " (\"VSS LLM NIM\", VSS_LLM_PORT, \"/v1/health/ready\"),\n", + " (\"Embedding (shared from RAG)\", 9080, \"/v1/health/ready\"),\n", + " (\"Reranker (shared from RAG)\", 1976, \"/v1/health/ready\"),\n", + "]:\n", + " try:\n", + " requests.get(f\"http://localhost:{port}{path}\", timeout=10)\n", + " s = \"[OK]\"\n", + " except requests.ConnectionError:\n", + " s = \"[DOWN]\"\n", + " except requests.Timeout:\n", + " s = \"[TIMEOUT]\"\n", + " print(f\" {s} {name}: http://{ip}:{port}\")\n", + "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(vss|via|local_deployment|NAMES)'\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy Continuous Ingestion from emulated object storage\n", + "\n", + "Deploy the Continuous Ingestion: Kafka message broker, MinIO object storage, and Kafka consumer for automated ingestion.\n", + "\n", + "## 1. Configure Video Analysis Prompts\n", + "\n", + "Customize the prompts used by the Kafka consumer when processing videos through VSS.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Consumer prompts for video analysis - customize these for your video content\n", + "# These are passed to the Kafka consumer which sends them to VSS for video summarization\n", + "\n", + "CONSUMER_VSS_PROMPT = \"\"\"Analyze this sports video. TIMESTAMP FORMAT: Convert seconds to MM:SS (40s=00:40, 80s=01:20, 150s=02:30, 200s=03:20). \\\n", + "Describe: key plays, scoring, turnovers, big gains, defensive stops, celebrations.\"\"\"\n", + "\n", + "CONSUMER_VSS_SYSTEM_PROMPT = \"\"\"You are a sports broadcaster. CRITICAL: Convert all timestamps from seconds to MM:SS format. \\\n", + "Examples: 40 seconds = 00:40, 90 seconds = 01:30, 200 seconds = 03:20, 350 seconds = 05:50. Focus on action, field position, execution, and game momentum.\"\"\"\n", + "\n", + "CONSUMER_VSS_CAPTION_SUMMARIZATION_PROMPT = \"\"\"Format: [MM:SS] Play description. CONVERT seconds to MM:SS (40s=00:40, 80s=01:20, 150s=02:30). \\\n", + "Describe: what happened, how it was executed, the result. Be vivid like a TV broadcast.\"\"\"\n", + "\n", + "CONSUMER_VSS_SUMMARY_AGGREGATION_PROMPT = \"\"\"Create game summary with MM:SS timestamps. CONVERT all times: 40s=00:40, 80s=01:20, 200s=03:20, 350s=05:50. \\\n", + "Highlight scoring plays, turnovers, momentum shifts, spectacular plays. Write like a highlight reel narrator - vivid and exciting.\"\"\"\n", + "\n", + "print(\"Consumer VSS prompts configured:\")\n", + "print(f\" VSS_PROMPT: {len(CONSUMER_VSS_PROMPT)} chars\")\n", + "print(f\" VSS_SYSTEM_PROMPT: {len(CONSUMER_VSS_SYSTEM_PROMPT)} chars\")\n", + "print(f\" VSS_CAPTION_SUMMARIZATION_PROMPT: {len(CONSUMER_VSS_CAPTION_SUMMARIZATION_PROMPT)} chars\")\n", + "print(f\" VSS_SUMMARY_AGGREGATION_PROMPT: {len(CONSUMER_VSS_SUMMARY_AGGREGATION_PROMPT)} chars\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Deploy Services\n", + "\n", + "Deploy Kafka, MinIO, and the Kafka consumer with custom prompts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify prerequisites\n", + "net_check = subprocess.run(\"docker network inspect nvidia-rag\", shell=True, capture_output=True)\n", + "if net_check.returncode != 0:\n", + " raise RuntimeError(\"nvidia-rag network not found. Deploy RAG first.\")\n", + "\n", + "ngc_key = os.environ.get(\"NGC_API_KEY\", \"\")\n", + "if not ngc_key:\n", + " raise RuntimeError(\"NGC_API_KEY not set!\")\n", + "\n", + "host_ip = get_host_ip()\n", + "\n", + "# Set environment variables for docker compose (including custom prompts)\n", + "os.environ[\"HOST_IP\"] = host_ip\n", + "os.environ[\"VSS_SERVER_URL\"] = f\"http://{host_ip}:{VSS_API_PORT}\"\n", + "os.environ[\"VSS_PROMPT\"] = CONSUMER_VSS_PROMPT\n", + "os.environ[\"VSS_SYSTEM_PROMPT\"] = CONSUMER_VSS_SYSTEM_PROMPT\n", + "os.environ[\"VSS_CAPTION_SUMMARIZATION_PROMPT\"] = CONSUMER_VSS_CAPTION_SUMMARIZATION_PROMPT\n", + "os.environ[\"VSS_SUMMARY_AGGREGATION_PROMPT\"] = CONSUMER_VSS_SUMMARY_AGGREGATION_PROMPT\n", + "\n", + "# Login + pull + build\n", + "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", + " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "compose = f\"docker compose -f {AIDP_COMPOSE_FILE}\"\n", + "subprocess.run(f\"{compose} pull --ignore-pull-failures\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "subprocess.run(f\"{compose} up -d --build\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "print(f\"Continuous Ingestion deployed:\")\n", + "print(f\" Kafka UI: http://{host_ip}:8080\")\n", + "print(f\" MinIO Console: http://{host_ip}:{MINIO_CONSOLE_PORT}\")\n", + "print(f\" Credentials: minioadmin / minioadmin\")\n", + "print(f\" Custom prompts: ✓ passed to consumer\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify continuous ingestion services are running.\n", + "\n", + "The deployment status should be:\n", + "```\n", + "NAMES STATUS\n", + "kafka-consumer Up About a minute\n", + "aidp-kafka-ui Up About a minute\n", + "aidp-minio-mc Up About a minute\n", + "aidp-minio Up About a minute (healthy)\n", + "kafka Up About a minute (healthy)\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check service status and print access URLs\n", + "ip = get_host_ip()\n", + "for name, port, path in [\n", + " (\"Kafka UI\", 8080, \"/\"),\n", + " (\"MinIO Console\", MINIO_CONSOLE_PORT, \"/\"),\n", + "]:\n", + " try:\n", + " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", + " except requests.ConnectionError:\n", + " s = \"[DOWN]\"\n", + " except requests.Timeout:\n", + " s = \"[TIMEOUT]\"\n", + " print(f\" {s} {name}: http://{ip}:{port}\")\n", + "\n", + "# Check kafka-consumer container status\n", + "result = subprocess.run(\"docker inspect -f '{{.State.Status}}' kafka-consumer 2>/dev/null\",\n", + " shell=True, capture_output=True, text=True)\n", + "status = result.stdout.strip()\n", + "s = \"[OK]\" if status == \"running\" else \"[DOWN]\"\n", + "print(f\" {s} Kafka Consumer: {status or 'not found'}\")\n", + "\n", + "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(kafka|minio|NAMES)'\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Testing\n", + "\n", + "Test the deployment by uploading documents and videos, then querying via RAG.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Document Upload\n", + "\n", + "Upload a PDF document to MinIO, which triggers automatic ingestion via Kafka consumer.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1 Upload to Storage\n", + "\n", + "Upload the document to MinIO object storage.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Sample documents are included in the repo under examples/rag_event_ingest/data/\n", + "pdf_path = os.path.join(DATA_DIR, \"documents\", \"Seahawks-Patriots in Super Bowl LX_ What We Learned from Seattle's 29-13 win.pdf\")\n", + "upload_file(pdf_path, \"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Verify Document Ingestion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check consumer logs to verify document processing status.\n", + "\n", + "The logs should show the document being picked up and successfully ingested:\n", + "```\n", + "services.document_indexer - INFO - Task ...: PENDING (0s)\n", + "services.document_indexer - INFO - Task ...: PENDING (5s)\n", + "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", + "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify file landed in object storage\n", + "verify_file_in_storage(\"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.3 Verify Document Ingestion\n", + "\n", + "Check consumer logs to verify document processing status.\n", + "\n", + "The logs should show the document being picked up and successfully ingested:\n", + "```\n", + "services.document_indexer - INFO - Task ...: PENDING (0s)\n", + "services.document_indexer - INFO - Task ...: PENDING (5s)\n", + "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", + "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check consumer logs for ingestion status\n", + "print(\"Waiting for document processing...\")\n", + "get_consumer_logs(50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.4 Query Document via RAG\n", + "\n", + "You can query the ingested document either **programmatically** below or via the **RAG Frontend UI**.\n", + "\n", + "> **💡 RAG Frontend**: Open `http://:8090` in your browser for an interactive Q&A interface.\n", + "> Make sure to select the collection **`aidp_bucket`** in the UI.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Query the document\n", + "await query_rag(\"What was the final score and who won Super Bowl LX?\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ask another question about the document.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Query about key takeaways\n", + "await query_rag(\"What were the key lessons learned from Seattle's victory in Super Bowl LX?\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Video Upload\n", + "\n", + "Upload a video to MinIO, which triggers automatic ingestion via Kafka consumer → VSS for video analysis → RAG for indexing.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Upload to Storage\n", + "\n", + "Upload the video to MinIO object storage.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Sample videos are included in the repo under examples/rag_event_ingest/data/\n", + "video_path = os.path.join(DATA_DIR, \"videos\", \"Seattle Seahawks vs New England Patriots - Super Bowl LX Game Highlights.mp4\")\n", + "upload_file(video_path)\n", + "\n", + "print(\"\\nVideo processing takes longer than documents. Check consumer logs for progress.\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Verify Video Ingestion\n", + "\n", + "Check consumer logs to verify video processing status.\n", + "\n", + "The logs should show the video being picked up and processed by VSS:\n", + "```\n", + "handlers.video - INFO - [VideoHandler] Processing video: Seattle Seahawks vs New England Patriots - Super Bowl LX Game Highlights.mp4\n", + "services.video_analyzer - INFO - Submitting video to VSS...\n", + "services.video_analyzer - INFO - VSS processing complete\n", + "handlers.base - INFO - [VideoHandler] ✓ Seattle Seahawks...mp4 → SUCCESS\n", + "consumer - INFO - ✓ SUMMARY: Seattle Seahawks...mp4 | Collection: aidp_bucket | Duration: ~120s | Status: SUCCESS\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify video landed in object storage\n", + "video_filename = os.path.basename(video_path)\n", + "verify_file_in_storage(video_filename)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3 Verify Video Ingestion\n", + "\n", + "Check consumer logs to verify video processing status.\n", + "\n", + "The logs should show the video being picked up and processed by VSS:\n", + "```\n", + "handlers.video - INFO - [VideoHandler] Processing video: Seattle Seahawks vs New England Patriots - Super Bowl LX Game Highlights.mp4\n", + "services.video_analyzer - INFO - Submitting video to VSS...\n", + "services.video_analyzer - INFO - VSS processing complete\n", + "handlers.base - INFO - [VideoHandler] ✓ Seattle Seahawks...mp4 → SUCCESS\n", + "consumer - INFO - ✓ SUMMARY: Seattle Seahawks...mp4 | Collection: aidp_bucket | Duration: ~120s | Status: SUCCESS\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check consumer logs for ingestion status\n", + "print(\"Waiting for video processing...\")\n", + "get_consumer_logs(50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.4 Query Video via RAG\n", + "\n", + "Query the video content.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Query about the video content\n", + "await query_rag(\"Summarize the video content\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Query about a specific time range in the video.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Query about specific time range\n", + "await query_rag(\"What happened between 15:00 and 20:00?\", MINIO_COLLECTION)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Additional query: analyze key defensive plays and turnovers.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Defensive Analysis\n", + "await query_rag(\"Describe the key defensive plays and turnovers that impacted the game outcome.\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Additional query: identify critical momentum-changing plays in the second half.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Momentum Shifts\n", + "await query_rag(\"What were the critical momentum-changing plays in the second half of the game?\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.5 Delete Video and Verify Removal from RAG\n", + "\n", + "Delete the video from MinIO and confirm the ingested content is removed from the RAG collection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Delete video from MinIO bucket\n", + "video_filename = os.path.basename(video_path)\n", + "try:\n", + " client = get_minio_client()\n", + " client.remove_object(MINIO_BUCKET, video_filename)\n", + " print(f\"[OK] Deleted '{video_filename}' from MinIO bucket '{MINIO_BUCKET}'\")\n", + "except S3Error as e:\n", + " print(f\"[ERROR] Failed to delete: {e}\")\n", + "\n", + "# Wait for Kafka consumer to process the delete event\n", + "print(\"Waiting 15s for consumer to process delete event...\")\n", + "time.sleep(15)\n", + "get_consumer_logs(20)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify the video description document was removed from the ingestor\n", + "video_basename = os.path.basename(video_path)\n", + "desc_filename = f\"{video_basename}_description.json\"\n", + "\n", + "resp = requests.get(f\"{INGESTOR_URL}/documents\", params={\"collection_name\": MINIO_COLLECTION}, timeout=30)\n", + "if resp.status_code == 200:\n", + " docs = resp.json().get(\"documents\", [])\n", + " video_docs = [d for d in docs if desc_filename in str(d)]\n", + " if not video_docs:\n", + " print(f\"[OK] '{desc_filename}' removed from collection '{MINIO_COLLECTION}'\")\n", + " else:\n", + " print(f\"[WARN] '{desc_filename}' still found in collection\")\n", + "else:\n", + " print(f\"[INFO] Could not list documents (status {resp.status_code}), checking via query instead\")\n", + "\n", + "print(\"\\nQuerying RAG (answers may come from the PDF document, not the deleted video):\")\n", + "await query_rag(\"Summarize the video content\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Clean Up\n", + "\n", + "Stop all services and clean up ingested data.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Stop RAG Deployment\n", + "\n", + "Stop all RAG services (NIMs, Milvus, Ingestor, RAG server).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "os.chdir(RAG_REPO_DIR)\n", + "for f in [\n", + " \"deploy/compose/docker-compose-rag-server.yaml\",\n", + " \"deploy/compose/docker-compose-ingestor-server.yaml\",\n", + " \"deploy/compose/vectordb.yaml\",\n", + " \"deploy/compose/nims.yaml\",\n", + "]:\n", + " run_command(f\"docker compose -f {f} down\")\n", + "print(\"[OK] RAG stopped\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Stop VSS Deployment\n", + "\n", + "Stop all VSS services (NIMs, VLM, via-server).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "vss_deploy_dir = f\"{VSS_DIR}/deploy/docker/local_deployment_single_gpu\"\n", + "if os.path.exists(vss_deploy_dir):\n", + " subprocess.run(f\"cd {vss_deploy_dir} && set -a && source .env 2>/dev/null && set +a && docker compose down\",\n", + " shell=True, executable=\"/bin/bash\", capture_output=True)\n", + "subprocess.run(\"docker rm -f vss-llm 2>/dev/null\", shell=True, capture_output=True)\n", + "print(\"[OK] VSS stopped\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Stop Continuous ingestion Deployment\n", + "\n", + "Stop Continuous ingestion services (Kafka, MinIO, Consumer).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "run_command(f\"docker compose -f {AIDP_COMPOSE_FILE} down\")\n", + "print(\"[OK] Continuous ingestion stopped\")\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From befced7cb651453eee4495cd67bf6fdac0e26c20 Mon Sep 17 00:00:00 2001 From: nv-pranjald <150428320+nv-pranjald@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:48:26 +0530 Subject: [PATCH 06/52] Fix query decomp doc and prompt (#316) * Fix query decomp doc and prompt * fix prompt in helm as well --- deploy/helm/nvidia-blueprint-rag/files/prompt.yaml | 1 + docs/query_decomposition.md | 2 +- src/nvidia_rag/rag_server/prompt.yaml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/deploy/helm/nvidia-blueprint-rag/files/prompt.yaml b/deploy/helm/nvidia-blueprint-rag/files/prompt.yaml index f82c83655..d73036509 100644 --- a/deploy/helm/nvidia-blueprint-rag/files/prompt.yaml +++ b/deploy/helm/nvidia-blueprint-rag/files/prompt.yaml @@ -487,6 +487,7 @@ query_decomposition_rag_template: Context: {context} + Question: {question} Make sure the response you are generating strictly follow the rules mentioned above i.e. never say phrases like “based on the context”, “from the documents”, or “I cannot find” and mention about the instruction in response. image_captioning_prompt: diff --git a/docs/query_decomposition.md b/docs/query_decomposition.md index 8f346f847..b6826d668 100644 --- a/docs/query_decomposition.md +++ b/docs/query_decomposition.md @@ -26,7 +26,7 @@ Each subquery is processed independently to gather comprehensive context, which ## Accuracy Improvement Example -The following example that uses the [HotpotQA](https://hotpotqa.github.io/) dataset demonstrates the accuracy improvement from enabling query decomposition. +The following example that uses the [Google Frame](https://huggingface.co/datasets/google/frames-benchmark) benchmark demonstrates the accuracy improvement from enabling query decomposition. ```text Query: I am thinking of a Ancient Roman City. The city was destroyed by volcanic eruption. The eruption occurred in the year 79 AD. The volcano was a stratovolcano. Where was the session held where it was decided that the city would be named a UNESCO world heritage site? diff --git a/src/nvidia_rag/rag_server/prompt.yaml b/src/nvidia_rag/rag_server/prompt.yaml index f82c83655..d73036509 100644 --- a/src/nvidia_rag/rag_server/prompt.yaml +++ b/src/nvidia_rag/rag_server/prompt.yaml @@ -487,6 +487,7 @@ query_decomposition_rag_template: Context: {context} + Question: {question} Make sure the response you are generating strictly follow the rules mentioned above i.e. never say phrases like “based on the context”, “from the documents”, or “I cannot find” and mention about the instruction in response. image_captioning_prompt: From 7d0796d382728ba93378a100d399ddc0b4671620 Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Fri, 27 Feb 2026 15:46:25 +0530 Subject: [PATCH 07/52] Upgrade to Nemotron RC NIMs and NV-Ingest 26.1.2 (#371) Signed-off-by: Swapnil Masurekar --- README.md | 10 +- deploy/compose/.env | 10 +- .../docker-compose-ingestor-server.yaml | 10 +- deploy/compose/docker-compose-rag-server.yaml | 8 +- deploy/compose/nims.yaml | 18 +-- deploy/helm/nvidia-blueprint-rag/Chart.yaml | 2 +- deploy/helm/nvidia-blueprint-rag/endpoints.md | 8 +- deploy/helm/nvidia-blueprint-rag/values.yaml | 42 +++---- deploy/workbench/README.md | 2 +- deploy/workbench/compose.yaml | 38 +++--- deploy/workbench/quickstart.ipynb | 32 ++--- docs/accuracy_perf.md | 2 +- .../openapi_schema_rag_server.json | 8 +- docs/change-model.md | 18 +-- docs/debugging.md | 12 +- docs/deploy-docker-self-hosted.md | 12 +- docs/deploy-helm.md | 8 +- docs/mig-deployment.md | 4 +- docs/multi-collection-retrieval.md | 6 +- docs/nv-ingest-standalone.md | 6 +- docs/python-client.md | 16 +-- docs/retrieval-only-deployment.md | 10 +- docs/service-port-gpu-reference.md | 4 +- docs/text_only_ingest.md | 6 +- docs/troubleshooting.md | 8 +- .../store/__tests__/useSettingsStore.test.tsx | 8 +- notebooks/.env_library | 6 +- notebooks/building_rag_vdb_operator.ipynb | 14 +-- notebooks/config.yaml | 4 +- notebooks/image_input.ipynb | 6 +- notebooks/launchable.ipynb | 4 +- notebooks/nb_metadata.ipynb | 28 ++--- notebooks/rag_library_lite_usage.ipynb | 12 +- notebooks/rag_library_usage.ipynb | 16 +-- notebooks/retriever_api_usage.ipynb | 8 +- notebooks/summarization.ipynb | 16 +-- pyproject.toml | 8 +- src/nvidia_rag/utils/configuration.py | 4 +- tests/integration/notebook_test_config.yaml | 118 ++++++++++++++++++ tests/integration/test_cases/library_usage.py | 3 +- .../env_parity_exemptions.yaml | 4 +- .../test_compose_helm_parity.py | 4 +- .../test_rag_server/test_self_reflection.py | 2 +- tests/unit/test_utils/test_configuration.py | 4 +- tests/unit/test_utils/test_reranker.py | 8 +- uv.lock | 20 +-- variables.env | 10 +- 47 files changed, 362 insertions(+), 245 deletions(-) create mode 100644 tests/integration/notebook_test_config.yaml diff --git a/README.md b/README.md index edea2e72a..72c5ca5a3 100644 --- a/README.md +++ b/README.md @@ -105,9 +105,9 @@ This modular design ensures efficient query processing, accurate retrieval of in - [NVIDIA NIM llama-3_2-nv-embedqa-1b-v2](https://build.nvidia.com/nvidia/llama-3_2-nv-embedqa-1b-v2) - [NVIDIA NIM llama-3_2-nv-rerankqa-1b-v2](https://build.nvidia.com/nvidia/llama-3_2-nv-rerankqa-1b-v2) - - [NeMo Retriever Page Elements NIM](https://build.nvidia.com/nvidia/nemoretriever-page-elements-v3) - - [NeMo Retriever Table Structure NIM](https://build.nvidia.com/nvidia/nemoretriever-table-structure-v1) - - [NeMo Retriever Graphic Elements NIM](https://build.nvidia.com/nvidia/nemoretriever-graphic-elements-v1) + - [NeMo Retriever Page Elements NIM](https://build.nvidia.com/nvidia/nemotron-page-elements-v3) + - [NeMo Retriever Table Structure NIM](https://build.nvidia.com/nvidia/nemotron-table-structure-v1) + - [NeMo Retriever Graphic Elements NIM](https://build.nvidia.com/nvidia/nemotron-graphic-elements-v1) - [NeMo Retriever OCR NIM](https://build.nvidia.com/nvidia/nemoretriever-ocr) - Optional NIMs @@ -202,9 +202,9 @@ Use of the models in this blueprint is governed by the [NVIDIA AI Foundation Mod ## Terms of Use This blueprint is governed by the [NVIDIA Agreements | Enterprise Software | NVIDIA Software License Agreement](https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-software-license-agreement/) and the [NVIDIA Agreements | Enterprise Software | Product Specific Terms for AI Product](https://www.nvidia.com/en-us/agreements/enterprise-software/product-specific-terms-for-ai-products/). The models are governed by the [NVIDIA Agreements | Enterprise Software | NVIDIA Community Model License](https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-community-models-license/) and the [NVIDIA RAG dataset](./data/multimodal/) which is governed by the [NVIDIA Asset License Agreement](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/data/LICENSE.DATA). -The following models that are built with Llama are governed by the Llama 3.2 Community License Agreement: nvidia/llama-3.2-nv-embedqa-1b-v2 and nvidia/llama-3.2-nv-rerankqa-1b-v2 and llama-3.2-nemoretriever-1b-vlm-embed-v1. +The following models that are built with Llama are governed by the Llama 3.2 Community License Agreement: nvidia/llama-nemotron-embed-1b-v2 and nvidia/llama-nemotron-rerank-1b-v2 and llama-3.2-nemoretriever-1b-vlm-embed-v1. ## Additional Information -The [Llama 3.1 Community License Agreement](https://www.llama.com/llama3_1/license/) for the llama-3.1-nemotron-nano-vl-8b-v1, llama-3.1-nemoguard-8b-content-safety and llama-3.1-nemoguard-8b-topic-control models. The [Llama 3.2 Community License Agreement](https://www.llama.com/llama3_2/license/) for the nvidia/llama-3.2-nv-embedqa-1b-v2, nvidia/llama-3.2-nv-rerankqa-1b-v2 and llama-3.2-nemoretriever-1b-vlm-embed-v1 models. The [Llama 3.3 Community License Agreement](https://github.com/meta-llama/llama-models/blob/main/models/llama3_3/LICENSE) for the llama-3.3-nemotron-super-49b-v1.5 models. Built with Llama. Apache 2.0 for NVIDIA Ingest and for the nemoretriever-page-elements-v2, nemoretriever-table-structure-v1, nemoretriever-graphic-elements-v1, paddleocr and nemoretriever-ocr-v1 models. +The [Llama 3.1 Community License Agreement](https://www.llama.com/llama3_1/license/) for the llama-3.1-nemotron-nano-vl-8b-v1, llama-3.1-nemoguard-8b-content-safety and llama-3.1-nemoguard-8b-topic-control models. The [Llama 3.2 Community License Agreement](https://www.llama.com/llama3_2/license/) for the nvidia/llama-nemotron-embed-1b-v2, nvidia/llama-nemotron-rerank-1b-v2 and llama-3.2-nemoretriever-1b-vlm-embed-v1 models. The [Llama 3.3 Community License Agreement](https://github.com/meta-llama/llama-models/blob/main/models/llama3_3/LICENSE) for the llama-3.3-nemotron-super-49b-v1.5 models. Built with Llama. Apache 2.0 for NVIDIA Ingest and for the nemoretriever-page-elements-v2, nemotron-table-structure-v1, nemotron-graphic-elements-v1, paddleocr and nemoretriever-ocr-v1 models. diff --git a/deploy/compose/.env b/deploy/compose/.env index 9f6ccf796..cc80cfaf0 100644 --- a/deploy/compose/.env +++ b/deploy/compose/.env @@ -22,8 +22,8 @@ export NVIDIA_API_KEY=${NGC_API_KEY} export APP_LLM_SERVERURL=nim-llm:8000 export APP_FILTEREXPRESSIONGENERATOR_SERVERURL=nim-llm:8000 export SUMMARY_LLM_SERVERURL=nim-llm:8000 -export APP_EMBEDDINGS_SERVERURL=nemoretriever-embedding-ms:8000/v1 -export APP_RANKING_SERVERURL=nemoretriever-ranking-ms:8000 +export APP_EMBEDDINGS_SERVERURL=nemotron-embedding-ms:8000/v1 +export APP_RANKING_SERVERURL=nemotron-ranking-ms:8000 export OCR_GRPC_ENDPOINT=nemoretriever-ocr:8001 export OCR_HTTP_ENDPOINT=http://nemoretriever-ocr:8000/v1/infer export OCR_INFER_PROTOCOL=grpc @@ -50,11 +50,11 @@ export YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=grpc # export OCR_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr # export OCR_INFER_PROTOCOL=http # export OCR_MODEL_NAME=scene_text_ensemble -# export YOLOX_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3 +# export YOLOX_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3 # export YOLOX_INFER_PROTOCOL=http -# export YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1 +# export YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1 # export YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL=http -# export YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1 +# export YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1 # export YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=http # export APP_QUERYREWRITER_SERVERURL="" # export APP_QUERYREWRITER_MODELNAME="nvidia/llama-3.3-nemotron-super-49b-v1.5" diff --git a/deploy/compose/docker-compose-ingestor-server.yaml b/deploy/compose/docker-compose-ingestor-server.yaml index fec1fb5aa..be15c17dc 100644 --- a/deploy/compose/docker-compose-ingestor-server.yaml +++ b/deploy/compose/docker-compose-ingestor-server.yaml @@ -75,8 +75,8 @@ services: ##===Embedding Model specific configurations=== # url on which embedding model is hosted. If "", Nvidia hosted API is used - APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemoretriever-embedding-ms:8000/v1"} - APP_EMBEDDINGS_MODELNAME: ${APP_EMBEDDINGS_MODELNAME:-nvidia/llama-3.2-nv-embedqa-1b-v2} + APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemotron-embedding-ms:8000/v1"} + APP_EMBEDDINGS_MODELNAME: ${APP_EMBEDDINGS_MODELNAME:-nvidia/llama-nemotron-embed-1b-v2} # For VLM Embedding Model (Nemoretriever-1b-vlm-embed-v1) # APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemotron-vlm-embedding-ms:8000/v1"} # APP_EMBEDDINGS_MODELNAME: ${APP_EMBEDDINGS_MODELNAME:-nvidia/llama-nemotron-embed-vl-1b-v2} @@ -168,7 +168,7 @@ services: - "6379:6379" nv-ingest-ms-runtime: - image: nvcr.io/nvidia/nemo-microservices/nv-ingest:26.1.1 + image: nvcr.io/nvidia/nemo-microservices/nv-ingest:26.1.2 # cpuset: "0-15" # Uncomment to restrict this container to CPU cores 0–15 shm_size: 40gb # Should be at minimum 30% of assigned memory per Ray documentation volumes: @@ -234,13 +234,13 @@ services: - YOLOX_HTTP_ENDPOINT=${YOLOX_HTTP_ENDPOINT:-http://page-elements:8000/v1/infer} - YOLOX_INFER_PROTOCOL=${YOLOX_INFER_PROTOCOL:-grpc} # build.nvidia.com hosted yolox-graphics-elements endpoints. - #- YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1 + #- YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1 #- YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL=http - YOLOX_GRAPHIC_ELEMENTS_GRPC_ENDPOINT=${YOLOX_GRAPHIC_ELEMENTS_GRPC_ENDPOINT:-graphic-elements:8001} - YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=${YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT:-http://graphic-elements:8000/v1/infer} - YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL=${YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL:-grpc} # build.nvidia.com hosted yolox-table-elements endpoints. - #- YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1 + #- YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1 #- YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=http - YOLOX_TABLE_STRUCTURE_GRPC_ENDPOINT=${YOLOX_TABLE_STRUCTURE_GRPC_ENDPOINT:-table-structure:8001} - YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=${YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT:-http://table-structure:8000/v1/infer} diff --git a/deploy/compose/docker-compose-rag-server.yaml b/deploy/compose/docker-compose-rag-server.yaml index b0c7d25bd..9f7e59a12 100644 --- a/deploy/compose/docker-compose-rag-server.yaml +++ b/deploy/compose/docker-compose-rag-server.yaml @@ -94,8 +94,8 @@ services: ##===Embedding Model specific configurations=== # url on which embedding model is hosted. If "", Nvidia hosted API is used - APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemoretriever-embedding-ms:8000/v1"} - APP_EMBEDDINGS_MODELNAME: ${APP_EMBEDDINGS_MODELNAME:-nvidia/llama-3.2-nv-embedqa-1b-v2} + APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemotron-embedding-ms:8000/v1"} + APP_EMBEDDINGS_MODELNAME: ${APP_EMBEDDINGS_MODELNAME:-nvidia/llama-nemotron-embed-1b-v2} APP_EMBEDDINGS_DIMENSIONS: ${APP_EMBEDDINGS_DIMENSIONS:-2048} # For VLM Embedding Model (Nemoretriever-1b-vlm-embed-v1) # APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemotron-vlm-embedding-ms:8000/v1"} @@ -103,8 +103,8 @@ services: ##===Reranking Model specific configurations=== # url on which ranking model is hosted. If "", Nvidia hosted API is used - APP_RANKING_SERVERURL: ${APP_RANKING_SERVERURL-"nemoretriever-ranking-ms:8000"} - APP_RANKING_MODELNAME: ${APP_RANKING_MODELNAME:-"nvidia/llama-3.2-nv-rerankqa-1b-v2"} + APP_RANKING_SERVERURL: ${APP_RANKING_SERVERURL-"nemotron-ranking-ms:8000"} + APP_RANKING_MODELNAME: ${APP_RANKING_MODELNAME:-"nvidia/llama-nemotron-rerank-1b-v2"} ENABLE_RERANKER: ${ENABLE_RERANKER:-True} # Default score threshold for filtering documents by reranker relevance (0.0 to 1.0) RERANKER_SCORE_THRESHOLD: ${RERANKER_SCORE_THRESHOLD:-${RERANKER_CONFIDENCE_THRESHOLD:-0.0}} diff --git a/deploy/compose/nims.yaml b/deploy/compose/nims.yaml index f376d9a64..261a705bc 100644 --- a/deploy/compose/nims.yaml +++ b/deploy/compose/nims.yaml @@ -31,9 +31,9 @@ services: retries: 100 profiles: ["", "rag"] - nemoretriever-embedding-ms: - container_name: nemoretriever-embedding-ms - image: nvcr.io/nim/nvidia/llama-3.2-nv-embedqa-1b-v2:1.10.1 + nemotron-embedding-ms: + container_name: nemotron-embedding-ms + image: nvcr.io/nvstaging/nim/llama-nemotron-embed-1b-v2:1.12.0-rc.20260216115852-6fba925dcb63c190 volumes: - ${MODEL_DIRECTORY:-./}:/opt/nim/.cache ports: @@ -91,9 +91,9 @@ services: start_period: 10m profiles: ["vlm-embed", "vlm-ingest"] - nemoretriever-ranking-ms: - container_name: nemoretriever-ranking-ms - image: nvcr.io/nim/nvidia/llama-3.2-nv-rerankqa-1b-v2:1.8.0 + nemotron-ranking-ms: + container_name: nemotron-ranking-ms + image: nvcr.io/nvstaging/nim/llama-nemotron-rerank-1b-v2:1.10.0-rc.20260219005237-7e2370934c6f9b9c volumes: - ${MODEL_DIRECTORY:-./}:/opt/nim/.cache ports: @@ -119,7 +119,7 @@ services: profiles: ["", "rag", "vlm-generation"] page-elements: - image: ${YOLOX_IMAGE:-nvcr.io/nim/nvidia/nemoretriever-page-elements-v3}:${YOLOX_TAG:-1.7.0} + image: ${YOLOX_IMAGE:-nvcr.io/nvstaging/nim/nemotron-page-elements-v3}:${YOLOX_TAG:-1.8.0-rc.20260218170155-6b1b2aa9dfefc9c3} shm_size: 16gb ports: - "8000:8000" @@ -157,7 +157,7 @@ services: profiles: ["", "ingest", "vlm-ingest"] graphic-elements: - image: ${YOLOX_GRAPHIC_ELEMENTS_IMAGE:-nvcr.io/nim/nvidia/nemoretriever-graphic-elements-v1}:${YOLOX_GRAPHIC_ELEMENTS_TAG:-1.6.0} + image: ${YOLOX_GRAPHIC_ELEMENTS_IMAGE:-nvcr.io/nvstaging/nim/nemotron-graphic-elements-v1}:${YOLOX_GRAPHIC_ELEMENTS_TAG:-1.8.0-rc.20260218170144-35d5a3f270662864} shm_size: 16gb ports: - "8003:8000" @@ -183,7 +183,7 @@ services: profiles: ["", "ingest", "vlm-ingest"] table-structure: - image: ${YOLOX_TABLE_STRUCTURE_IMAGE:-nvcr.io/nim/nvidia/nemoretriever-table-structure-v1}:${YOLOX_TABLE_STRUCTURE_TAG:-1.6.0} + image: ${YOLOX_TABLE_STRUCTURE_IMAGE:-nvcr.io/nvstaging/nim/nemotron-table-structure-v1}:${YOLOX_TABLE_STRUCTURE_TAG:-1.8.0-rc.20260218170204-cca5cb850146dbbc} shm_size: 16gb ports: - "8006:8000" diff --git a/deploy/helm/nvidia-blueprint-rag/Chart.yaml b/deploy/helm/nvidia-blueprint-rag/Chart.yaml index 639869e53..afe7a5cbd 100644 --- a/deploy/helm/nvidia-blueprint-rag/Chart.yaml +++ b/deploy/helm/nvidia-blueprint-rag/Chart.yaml @@ -4,7 +4,7 @@ dependencies: - condition: nv-ingest.enabled name: nv-ingest repository: https://helm.ngc.nvidia.com/nvidia/nemo-microservices - version: 26.1.1 + version: 26.1.2 - condition: eck-elasticsearch.enabled name: eck-elasticsearch repository: https://helm.elastic.co diff --git a/deploy/helm/nvidia-blueprint-rag/endpoints.md b/deploy/helm/nvidia-blueprint-rag/endpoints.md index 7609053d5..e62b0d1fb 100644 --- a/deploy/helm/nvidia-blueprint-rag/endpoints.md +++ b/deploy/helm/nvidia-blueprint-rag/endpoints.md @@ -24,11 +24,11 @@ This document describes the configurable endpoints used by the RAG server and it ### Embedding Model - **APP_EMBEDDINGS_SERVERURL**: URL for the embedding model service (default: "nemo-retriever-embedding-ms:8000") -- **APP_EMBEDDINGS_MODELNAME**: Name of the embedding model (default: "nvidia/llama-3.2-nv-embedqa-1b-v2") +- **APP_EMBEDDINGS_MODELNAME**: Name of the embedding model (default: "nvidia/llama-nemotron-embed-1b-v2") ### Reranking Model - **APP_RANKING_SERVERURL**: URL for the ranking model service (default: "nemo-retriever-reranking-ms:8000") -- **APP_RANKING_MODELNAME**: Name of the ranking model (default: "nvidia/llama-3.2-nv-rerankqa-1b-v2") +- **APP_RANKING_MODELNAME**: Name of the ranking model (default: "nvidia/llama-nemotron-rerank-1b-v2") ### Reflection Model - **REFLECTION_LLM_SERVERURL**: URL for the reflection LLM service (default: "nim-llm:8000") @@ -42,8 +42,8 @@ This document describes the configurable endpoints used by the RAG server and it ### Model Configuration - **NEXT_PUBLIC_MODEL_NAME**: Name of the LLM model used in the frontend (default: "nvidia/llama-3.3-nemotron-super-49b-v1.5") -- **VITE_EMBEDDING_MODEL**: Name of the embedding model used in the frontend (default: "nvidia/llama-3.2-nv-embedqa-1b-v2") -- **VITE_RERANKER_MODEL**: Name of the reranker model used in the frontend (default: "nvidia/llama-3.2-nv-rerankqa-1b-v2") +- **VITE_EMBEDDING_MODEL**: Name of the embedding model used in the frontend (default: "nvidia/llama-nemotron-embed-1b-v2") +- **VITE_RERANKER_MODEL**: Name of the reranker model used in the frontend (default: "nvidia/llama-nemotron-rerank-1b-v2") ## Monitoring and Tracing Endpoints diff --git a/deploy/helm/nvidia-blueprint-rag/values.yaml b/deploy/helm/nvidia-blueprint-rag/values.yaml index a02fef3e4..b385b5296 100644 --- a/deploy/helm/nvidia-blueprint-rag/values.yaml +++ b/deploy/helm/nvidia-blueprint-rag/values.yaml @@ -183,14 +183,14 @@ envVars: ##===Embedding Model specific configurations=== # URL on which embedding model is hosted. If "", Nvidia hosted API is used - APP_EMBEDDINGS_SERVERURL: "nemoretriever-embedding-ms:8000/v1" - APP_EMBEDDINGS_MODELNAME: "nvidia/llama-3.2-nv-embedqa-1b-v2" + APP_EMBEDDINGS_SERVERURL: "nemotron-embedding-ms:8000/v1" + APP_EMBEDDINGS_MODELNAME: "nvidia/llama-nemotron-embed-1b-v2" APP_EMBEDDINGS_DIMENSIONS: "2048" ##===Reranking Model specific configurations=== # URL on which ranking model is hosted. If "", Nvidia hosted API is used - APP_RANKING_SERVERURL: "nemoretriever-ranking-ms:8000" - APP_RANKING_MODELNAME: "nvidia/llama-3.2-nv-rerankqa-1b-v2" + APP_RANKING_SERVERURL: "nemotron-ranking-ms:8000" + APP_RANKING_MODELNAME: "nvidia/llama-nemotron-rerank-1b-v2" ENABLE_RERANKER: "True" # Default score threshold for filtering documents by reranker relevance (0.0 to 1.0) RERANKER_SCORE_THRESHOLD: "0.0" @@ -349,8 +349,8 @@ ingestor-server: ## APP_EMBEDDINGS_APIKEY and SUMMARY_LLM_APIKEY are loaded from secrets automatically. # === Embeddings Configurations === - APP_EMBEDDINGS_SERVERURL: "nemoretriever-embedding-ms:8000/v1" - APP_EMBEDDINGS_MODELNAME: "nvidia/llama-3.2-nv-embedqa-1b-v2" + APP_EMBEDDINGS_SERVERURL: "nemotron-embedding-ms:8000/v1" + APP_EMBEDDINGS_MODELNAME: "nvidia/llama-nemotron-embed-1b-v2" APP_EMBEDDINGS_DIMENSIONS: "2048" # === NV-Ingest Configurations === @@ -716,10 +716,10 @@ nimOperator: enabled: true replicas: 1 service: - name: "nemoretriever-embedding-ms" + name: "nemotron-embedding-ms" image: - repository: nvcr.io/nim/nvidia/llama-3.2-nv-embedqa-1b-v2 - tag: "1.10.1" + repository: nvcr.io/nvstaging/nim/llama-nemotron-embed-1b-v2 + tag: "1.12.0-rc.20260216115852-6fba925dcb63c190" pullPolicy: IfNotPresent resources: limits: @@ -795,10 +795,10 @@ nimOperator: enabled: true replicas: 1 service: - name: "nemoretriever-ranking-ms" + name: "nemotron-ranking-ms" image: - repository: nvcr.io/nim/nvidia/llama-3.2-nv-rerankqa-1b-v2 - tag: "1.8.0" + repository: nvcr.io/nvstaging/nim/llama-nemotron-rerank-1b-v2 + tag: "1.10.0-rc.20260219005237-7e2370934c6f9b9c" pullPolicy: IfNotPresent resources: limits: @@ -870,7 +870,7 @@ nv-ingest: create: false image: repository: "nvcr.io/nvidia/nemo-microservices/nv-ingest" - tag: "26.1.1" + tag: "26.1.2" resources: limits: nvidia.com/gpu: 0 @@ -896,8 +896,8 @@ nv-ingest: RAY_num_server_call_thread: "1" RAY_worker_num_grpc_internal_threads: "1" - EMBEDDING_NIM_ENDPOINT: "http://nemoretriever-embedding-ms:8000/v1" - EMBEDDING_NIM_MODEL_NAME: "nvidia/llama-3.2-nv-embedqa-1b-v2" + EMBEDDING_NIM_ENDPOINT: "http://nemotron-embedding-ms:8000/v1" + EMBEDDING_NIM_MODEL_NAME: "nvidia/llama-nemotron-embed-1b-v2" MESSAGE_CLIENT_HOST: "rag-redis-master" MESSAGE_CLIENT_PORT: 6379 MESSAGE_CLIENT_TYPE: "redis" @@ -1049,8 +1049,8 @@ nv-ingest: tolerations: [] replicaCount: 1 image: - repository: nvcr.io/nim/nvidia/nemoretriever-graphic-elements-v1 - tag: "1.6.0" + repository: nvcr.io/nvstaging/nim/nemotron-graphic-elements-v1 + tag: "1.8.0-rc.20260218170144-35d5a3f270662864" env: - name: NIM_HTTP_API_PORT value: "8000" @@ -1082,8 +1082,8 @@ nv-ingest: tolerations: [] replicaCount: 1 image: - repository: nvcr.io/nim/nvidia/nemoretriever-page-elements-v3 - tag: "1.7.0" + repository: nvcr.io/nvstaging/nim/nemotron-page-elements-v3 + tag: "1.8.0-rc.20260218170155-6b1b2aa9dfefc9c3" env: - name: NIM_HTTP_API_PORT value: "8000" @@ -1133,8 +1133,8 @@ nv-ingest: tolerations: [] replicaCount: 1 image: - repository: nvcr.io/nim/nvidia/nemoretriever-table-structure-v1 - tag: "1.6.0" + repository: nvcr.io/nvstaging/nim/nemotron-table-structure-v1 + tag: "1.8.0-rc.20260218170204-cca5cb850146dbbc" env: - name: NIM_HTTP_API_PORT value: "8000" diff --git a/deploy/workbench/README.md b/deploy/workbench/README.md index 6d02a360e..179c32ec5 100644 --- a/deploy/workbench/README.md +++ b/deploy/workbench/README.md @@ -75,4 +75,4 @@ Use of the models in this blueprint is governed by the [NVIDIA AI Foundation Mod ## Terms of Use This blueprint is governed by the [NVIDIA Agreements | Enterprise Software | NVIDIA Software License Agreement](https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-software-license-agreement/) and the [NVIDIA Agreements | Enterprise Software | Product Specific Terms for AI Product](https://www.nvidia.com/en-us/agreements/enterprise-software/product-specific-terms-for-ai-products/). The models are governed by the [NVIDIA Agreements | Enterprise Software | NVIDIA Community Model License](https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-community-models-license/) and the [NVIDIA RAG dataset](https://github.com/NVIDIA-AI-Blueprints/rag/tree/v2.0.0/data/multimodal) which is governed by the [NVIDIA Asset License Agreement](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/data/LICENSE.DATA). -The following models that are built with Llama are governed by the [Llama 3.2 Community License Agreement](https://www.llama.com/llama3_2/license/): nvidia/llama-3.3-nemotron-super-49b-v1, nvidia/llama-3.2-nv-embedqa-1b-v2, and nvidia/llama-3.2-nv-rerankqa-1b-v2. +The following models that are built with Llama are governed by the [Llama 3.2 Community License Agreement](https://www.llama.com/llama3_2/license/): nvidia/llama-3.3-nemotron-super-49b-v1, nvidia/llama-nemotron-embed-1b-v2, and nvidia/llama-nemotron-rerank-1b-v2. diff --git a/deploy/workbench/compose.yaml b/deploy/workbench/compose.yaml index 1e75d07b7..aa9dd5713 100644 --- a/deploy/workbench/compose.yaml +++ b/deploy/workbench/compose.yaml @@ -28,9 +28,9 @@ services: retries: 100 profiles: ["local"] - nemoretriever-embedding-ms: - container_name: nemoretriever-embedding-ms - image: nvcr.io/nim/nvidia/llama-3.2-nv-embedqa-1b-v2:1.10.1 + nemotron-embedding-ms: + container_name: nemotron-embedding-ms + image: nvcr.io/nvstaging/nim/llama-nemotron-embed-1b-v2:1.12.0-rc.20260216115852-6fba925dcb63c190 volumes: - ${MODEL_DIRECTORY:-/tmp}:/opt/nim/.cache ports: @@ -58,9 +58,9 @@ services: start_period: 10m profiles: ["local"] - nemoretriever-ranking-ms: - container_name: nemoretriever-ranking-ms - image: nvcr.io/nim/nvidia/llama-3.2-nv-rerankqa-1b-v2:1.8.0 + nemotron-ranking-ms: + container_name: nemotron-ranking-ms + image: nvcr.io/nvstaging/nim/llama-nemotron-rerank-1b-v2:1.10.0-rc.20260219005237-7e2370934c6f9b9c volumes: - ${MODEL_DIRECTORY:-/tmp}:/opt/nim/.cache ports: @@ -86,7 +86,7 @@ services: profiles: ["local"] page-elements: - image: ${YOLOX_IMAGE:-nvcr.io/nim/nvidia/nemoretriever-page-elements-v3}:${YOLOX_TAG:-1.7.0} + image: ${YOLOX_IMAGE:-nvcr.io/nvstaging/nim/nemotron-page-elements-v3}:${YOLOX_TAG:-1.8.0-rc.20260218170155-6b1b2aa9dfefc9c3} ports: - "8000:8000" - "8001:8001" @@ -122,7 +122,7 @@ services: profiles: ["local"] graphic-elements: - image: ${YOLOX_GRAPHIC_ELEMENTS_IMAGE:-nvcr.io/nim/nvidia/nemoretriever-graphic-elements-v1}:${YOLOX_GRAPHIC_ELEMENTS_TAG:-1.6.0} + image: ${YOLOX_GRAPHIC_ELEMENTS_IMAGE:-nvcr.io/nvstaging/nim/nemotron-graphic-elements-v1}:${YOLOX_GRAPHIC_ELEMENTS_TAG:-1.8.0-rc.20260218170144-35d5a3f270662864} ports: - "8003:8000" - "8004:8001" @@ -147,7 +147,7 @@ services: profiles: ["local"] table-structure: - image: ${YOLOX_TABLE_STRUCTURE_IMAGE:-nvcr.io/nim/nvidia/nemoretriever-table-structure-v1}:${YOLOX_TABLE_STRUCTURE_TAG:-1.6.0} + image: ${YOLOX_TABLE_STRUCTURE_IMAGE:-nvcr.io/nvstaging/nim/nemotron-table-structure-v1}:${YOLOX_TABLE_STRUCTURE_TAG:-1.8.0-rc.20260218170204-cca5cb850146dbbc} ports: - "8006:8000" - "8007:8001" @@ -256,8 +256,8 @@ services: ##===Embedding Model specific configurations=== # url on which embedding model is hosted. If "", Nvidia hosted API is used - APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemoretriever-embedding-ms:8000/v1"} - APP_EMBEDDINGS_MODELNAME: ${APP_EMBEDDINGS_MODELNAME:-nvidia/llama-3.2-nv-embedqa-1b-v2} + APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemotron-embedding-ms:8000/v1"} + APP_EMBEDDINGS_MODELNAME: ${APP_EMBEDDINGS_MODELNAME:-nvidia/llama-nemotron-embed-1b-v2} APP_EMBEDDINGS_DIMENSIONS: ${APP_EMBEDDINGS_DIMENSIONS:-2048} ##===NV-Ingest Connection Configurations======= @@ -333,7 +333,7 @@ services: profiles: ["ingest"] nv-ingest-ms-runtime: - image: nvcr.io/nvidia/nemo-microservices/nv-ingest:26.1.1 + image: nvcr.io/nvidia/nemo-microservices/nv-ingest:26.1.2 # cpuset: "0-15" # Uncomment to restrict this container to CPU cores 0–15 shm_size: 40gb # Should be at minimum 30% of assigned memory per Ray documentation volumes: @@ -399,20 +399,20 @@ services: - REDIS_MORPHEUS_TASK_QUEUE=morpheus_task_queue # Self-hosted redis endpoints. # build.nvidia.com hosted yolox endpoints. - # - YOLOX_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3 + # - YOLOX_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3 # - YOLOX_INFER_PROTOCOL=http - YOLOX_PAGE_IMAGE_FORMAT=JPEG - YOLOX_GRPC_ENDPOINT=${YOLOX_GRPC_ENDPOINT:-page-elements:8001} - YOLOX_HTTP_ENDPOINT=${YOLOX_HTTP_ENDPOINT:-http://page-elements:8000/v1/infer} - YOLOX_INFER_PROTOCOL=${YOLOX_INFER_PROTOCOL:-grpc} # build.nvidia.com hosted yolox-graphics-elements endpoints. - #- YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1 + #- YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1 #- YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL=http - YOLOX_GRAPHIC_ELEMENTS_GRPC_ENDPOINT=${YOLOX_GRAPHIC_ELEMENTS_GRPC_ENDPOINT:-graphic-elements:8001} - YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=${YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT:-http://graphic-elements:8000/v1/infer} - YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL=${YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL:-grpc} # build.nvidia.com hosted yolox-table-elements endpoints. - #- YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1 + #- YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1 #- YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=http - YOLOX_TABLE_STRUCTURE_GRPC_ENDPOINT=${YOLOX_TABLE_STRUCTURE_GRPC_ENDPOINT:-table-structure:8001} - YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=${YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT:-http://table-structure:8000/v1/infer} @@ -495,13 +495,13 @@ services: ##===Embedding Model specific configurations=== # url on which embedding model is hosted. If "", Nvidia hosted API is used - APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemoretriever-embedding-ms:8000/v1"} - APP_EMBEDDINGS_MODELNAME: ${APP_EMBEDDINGS_MODELNAME:-nvidia/llama-3.2-nv-embedqa-1b-v2} + APP_EMBEDDINGS_SERVERURL: ${APP_EMBEDDINGS_SERVERURL-"nemotron-embedding-ms:8000/v1"} + APP_EMBEDDINGS_MODELNAME: ${APP_EMBEDDINGS_MODELNAME:-nvidia/llama-nemotron-embed-1b-v2} ##===Reranking Model specific configurations=== # url on which ranking model is hosted. If "", Nvidia hosted API is used - APP_RANKING_SERVERURL: ${APP_RANKING_SERVERURL-"nemoretriever-ranking-ms:8000"} - APP_RANKING_MODELNAME: ${APP_RANKING_MODELNAME:-"nvidia/llama-3.2-nv-rerankqa-1b-v2"} + APP_RANKING_SERVERURL: ${APP_RANKING_SERVERURL-"nemotron-ranking-ms:8000"} + APP_RANKING_MODELNAME: ${APP_RANKING_MODELNAME:-"nvidia/llama-nemotron-rerank-1b-v2"} ENABLE_RERANKER: ${ENABLE_RERANKER:-True} ##===VLM Model specific configurations=== diff --git a/deploy/workbench/quickstart.ipynb b/deploy/workbench/quickstart.ipynb index d9a15a71a..00c524aba 100644 --- a/deploy/workbench/quickstart.ipynb +++ b/deploy/workbench/quickstart.ipynb @@ -966,10 +966,10 @@ " \"enable_citations\": True,\n", " \"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", " \"llm_endpoint\": \"nim-llm:8000\",\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", - " \"embedding_endpoint\": \"nemoretriever-embedding-ms:8000/v1\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"reranker_endpoint\": \"nemoretriever-ranking-ms:8000\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", + " \"embedding_endpoint\": \"nemotron-embedding-ms:8000/v1\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"reranker_endpoint\": \"nemotron-ranking-ms:8000\",\n", " \"stop\": [],\n", "}\n", "\n", @@ -1030,10 +1030,10 @@ " \"enable_citations\": True,\n", " \"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", " \"llm_endpoint\": \"nim-llm:8000\",\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", - " \"embedding_endpoint\": \"nemoretriever-embedding-ms:8000/v1\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"reranker_endpoint\": \"nemoretriever-ranking-ms:8000\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", + " \"embedding_endpoint\": \"nemotron-embedding-ms:8000/v1\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"reranker_endpoint\": \"nemotron-ranking-ms:8000\",\n", " \"stop\": [],\n", "}\n", "\n", @@ -1175,10 +1175,10 @@ " ],\n", " \"enable_query_rewriting\": False,\n", " \"enable_reranker\": False,\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", - " \"embedding_endpoint\": \"nemoretriever-embedding-ms:8000/v1\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"reranker_endpoint\": \"nemoretriever-ranking-ms:8000\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", + " \"embedding_endpoint\": \"nemotron-embedding-ms:8000/v1\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"reranker_endpoint\": \"nemotron-ranking-ms:8000\",\n", "}\n", "\n", "\n", @@ -1233,10 +1233,10 @@ " ],\n", " \"enable_query_rewriting\": False,\n", " \"enable_reranker\": True,\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", - " \"embedding_endpoint\": \"nemoretriever-embedding-ms:8000/v1\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"reranker_endpoint\": \"nemoretriever-ranking-ms:8000\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", + " \"embedding_endpoint\": \"nemotron-embedding-ms:8000/v1\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"reranker_endpoint\": \"nemotron-ranking-ms:8000\",\n", "}\n", "\n", "\n", diff --git a/docs/accuracy_perf.md b/docs/accuracy_perf.md index 8ee491fa6..8367747ae 100644 --- a/docs/accuracy_perf.md +++ b/docs/accuracy_perf.md @@ -30,7 +30,7 @@ Change the setting if you want different behavior. | Name | Default | Description | Advantages | Disadvantages | |----------------------|------------|---------------------|----------------------|--------------------------| -| - `APP_LLM_MODELNAME`
- `APP_EMBEDDINGS_MODELNAME`
- `APP_RANKING_MODELNAME`
| See description | The default models are the following:
- `nvidia/llama-3.3-nemotron-super-49b-v1.5`
- `nvidia/llama-3.2-nv-embedqa-1b-v2`
- `nvidia/llama-3.2-nv-rerankqa-1b-v2`

You can use larger models. For details, refer to [Change the Inference or Embedding Model](change-model.md). | - Higher accuracy with better reasoning and a larger context length.
| - Slower response time.
- Higher inference cost.
- Higher GPU requirement.
| +| - `APP_LLM_MODELNAME`
- `APP_EMBEDDINGS_MODELNAME`
- `APP_RANKING_MODELNAME`
| See description | The default models are the following:
- `nvidia/llama-3.3-nemotron-super-49b-v1.5`
- `nvidia/llama-nemotron-embed-1b-v2`
- `nvidia/llama-nemotron-rerank-1b-v2`

You can use larger models. For details, refer to [Change the Inference or Embedding Model](change-model.md). | - Higher accuracy with better reasoning and a larger context length.
| - Slower response time.
- Higher inference cost.
- Higher GPU requirement.
| | `APP_VECTORSTORE_SEARCHTYPE` | `dense` | Set to `hybrid` to enable hybrid search. For details, refer to [Hybrid Search Support](hybrid_search.md). | - Can provide better retrieval accuracy for domain-specific content.
| - Can induce higher latency for large number of documents.
| | `ENABLE_GUARDRAILS` | `false` | Set to `true` to enable NeMo Guardrails. For details, refer to [Nemo Guardrails Support](nemo-guardrails.md). | - Applies input/output constraints for better safety and consistency.
| - Significant increased processing overhead for additional LLM calls.
- Needs additional GPUs to deploy guardrails-specific models locally.
| | `ENABLE_QUERYREWRITER` | `false` | Set to `true` to enable query rewriting. For details, refer to [Multi-Turn Conversation Support](multiturn.md). | - Enhances retrieval accuracy for multi-turn scenarios by rephrasing the query.
| - Adds an extra LLM call, increasing latency.
| diff --git a/docs/api_reference/openapi_schema_rag_server.json b/docs/api_reference/openapi_schema_rag_server.json index 63bbb4e33..5bcf2ec7d 100644 --- a/docs/api_reference/openapi_schema_rag_server.json +++ b/docs/api_reference/openapi_schema_rag_server.json @@ -707,7 +707,7 @@ "maxLength": 256, "title": "Embedding Model", "description": "Name of the embedding model used for vectorization.", - "default": "nvdev/nvidia/llama-3.2-nv-embedqa-1b-v2" + "default": "nvdev/nvidia/llama-nemotron-embed-1b-v2" }, "embedding_endpoint": { "type": "string", @@ -721,7 +721,7 @@ "maxLength": 256, "title": "Reranker Model", "description": "Name of the reranker model used for ranking results.", - "default": "nvidia/llama-3.2-nv-rerankqa-1b-v2" + "default": "nvidia/llama-nemotron-rerank-1b-v2" }, "reranker_endpoint": { "anyOf": [ @@ -1342,7 +1342,7 @@ "maxLength": 256, "title": "Embedding Model", "description": "Name of the embedding model used for vectorization.", - "default": "nvdev/nvidia/llama-3.2-nv-embedqa-1b-v2" + "default": "nvdev/nvidia/llama-nemotron-embed-1b-v2" }, "embedding_endpoint": { "anyOf": [ @@ -1363,7 +1363,7 @@ "maxLength": 256, "title": "Reranker Model", "description": "Name of the reranker model used for ranking results.", - "default": "nvidia/llama-3.2-nv-rerankqa-1b-v2" + "default": "nvidia/llama-nemotron-rerank-1b-v2" }, "reranker_endpoint": { "anyOf": [ diff --git a/docs/change-model.md b/docs/change-model.md index d0173462a..27aa80e28 100644 --- a/docs/change-model.md +++ b/docs/change-model.md @@ -77,7 +77,7 @@ Always use same embedding model or model having same tokinizers for both ingesti ### Configure Embedding Dimensions -The default embedding model (`nvidia/llama-3.2-nv-embedqa-1b-v2`) uses **2048 dimensions** by default. When changing to a different embedding model, you may need to update the dimensions to match the model's output. +The default embedding model (`nvidia/llama-nemotron-embed-1b-v2`) uses **2048 dimensions** by default. When changing to a different embedding model, you may need to update the dimensions to match the model's output. **Important:** Some embedding models have **fixed output dimensions** and do not accept a `dimensions` parameter. For example, `nvidia/nv-embedqa-e5-v5` always outputs 1024-dimensional embeddings. If you use such a model without configuring the dimensions, you may encounter an error like: @@ -124,13 +124,13 @@ You can specify the model for NVIDIA NIM containers to use in the [nims.yaml](.. image: nvcr.io/nim/: ... - nemoretriever-embedding-ms: - container_name: nemoretriever-embedding-ms + nemotron-embedding-ms: + container_name: nemotron-embedding-ms image: nvcr.io/nim/: - nemoretriever-ranking-ms: - container_name: nemoretriever-ranking-ms + nemotron-ranking-ms: + container_name: nemotron-ranking-ms image: nvcr.io/nim/: ``` @@ -173,11 +173,11 @@ Use this procedure to change models when you are running self-hosted NVIDIA NIM # === Embeddings === APP_EMBEDDINGS_MODELNAME: "" - APP_EMBEDDINGS_SERVERURL: "nemoretriever-embedding-ms:8000/v1" + APP_EMBEDDINGS_SERVERURL: "nemotron-embedding-ms:8000/v1" # === Reranker === APP_RANKING_MODELNAME: "" - APP_RANKING_SERVERURL: "nemoretriever-ranking-ms:8000" + APP_RANKING_SERVERURL: "nemotron-ranking-ms:8000" ``` 3. Configure the NIM microservices that host those models. Replace `:` with the image you selected (format `nvcr.io/nim/:`) in [values.yaml](../deploy/helm/nvidia-blueprint-rag/values.yaml). @@ -215,7 +215,7 @@ Use this procedure to change models when you are running self-hosted NVIDIA NIM enabled: true replicas: 1 service: - name: "nemoretriever-embedding-ms" + name: "nemotron-embedding-ms" image: # nvcr.io/nim/: repository: nvcr.io/nim/ @@ -237,7 +237,7 @@ Use this procedure to change models when you are running self-hosted NVIDIA NIM enabled: true replicas: 1 service: - name: "nemoretriever-ranking-ms" + name: "nemotron-ranking-ms" image: # nvcr.io/nim/: repository: nvcr.io/nim/ diff --git a/docs/debugging.md b/docs/debugging.md index 66a580419..a9c25debd 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -33,7 +33,7 @@ docker logs -f nim-llm-ms watch -n 10 'du -sh ~/.cache/model-cache/' # Check specific container resource usage -docker stats nim-llm-ms nemoretriever-embedding-ms nemoretriever-ranking-ms +docker stats nim-llm-ms nemotron-embedding-ms nemotron-ranking-ms ``` The expected timeline for Docker (Self-Hosted) deployment is the following: @@ -124,12 +124,12 @@ docker ps | grep -E "(ingestor-server|nv-ingest|nemoretriever-embedding|milvus|r milvus-standalone Up 36 minutes (healthy) milvus-minio Up 35 minutes (healthy) milvus-etcd Up 35 minutes (healthy) - nemoretriever-ranking-ms Up 38 minutes (healthy) + nemotron-ranking-ms Up 38 minutes (healthy) compose-page-elements-1 Up 38 minutes compose-nemoretriever-ocr-1 Up 38 minutes compose-graphic-elements-1 Up 38 minutes compose-table-structure-1 Up 38 minutes - nemoretriever-embedding-ms Up 38 minutes (healthy) + nemotron-embedding-ms Up 38 minutes (healthy) nim-llm-ms Up 38 minutes (healthy) ``` @@ -223,7 +223,7 @@ docker logs ingestor-server --tail 100 docker logs nv-ingest-ms-runtime --tail 100 # Check embedding service logs for model issues -docker logs nemoretriever-embedding-ms --tail 100 +docker logs nemotron-embedding-ms --tail 100 ``` ### 2. Common Ingestion Problems and Solutions @@ -245,7 +245,7 @@ docker logs milvus-standalone --tail 50 **Embedding Service Issues:** ```bash # Check embedding service logs -docker logs nemoretriever-embedding-ms --tail 100 +docker logs nemotron-embedding-ms --tail 100 # Verify GPU availability and memory nvidia-smi @@ -288,7 +288,7 @@ docker logs rag-server --tail 100 docker logs nim-llm-ms --tail 100 # Check ranking service logs for reranking errors -docker logs nemoretriever-ranking-ms --tail 100 +docker logs nemotron-ranking-ms --tail 100 ``` ### 2. Common Retrieval Problems and Solutions diff --git a/docs/deploy-docker-self-hosted.md b/docs/deploy-docker-self-hosted.md index 0efe64ea6..6607aac73 100644 --- a/docs/deploy-docker-self-hosted.md +++ b/docs/deploy-docker-self-hosted.md @@ -110,7 +110,7 @@ Use the following procedure to start all containers needed for this blueprint. USERID=$(id -u) docker compose -f deploy/compose/nims.yaml up -d ``` -5. Check the status of the deployment by running the following code. Wait until all services are up and the `nemoretriever-ranking-ms`, `nemoretriever-embedding-ms` and `nim-llm-ms` NIMs are in healthy state before proceeding further. +5. Check the status of the deployment by running the following code. Wait until all services are up and the `nemotron-ranking-ms`, `nemotron-embedding-ms` and `nim-llm-ms` NIMs are in healthy state before proceeding further. ```bash watch -n 2 'docker ps --format "table {{.Names}}\t{{.Status}}"' @@ -121,10 +121,10 @@ Use the following procedure to start all containers needed for this blueprint. NAMES STATUS nim-llm-ms Up 4 minutes (healthy) - nemoretriever-ranking-ms Up 4 minutes (healthy) + nemotron-ranking-ms Up 4 minutes (healthy) compose-graphic-elements-1 Up 4 minutes compose-page-elements-1 Up 4 minutes - nemoretriever-embedding-ms Up 4 minutes (healthy) + nemotron-embedding-ms Up 4 minutes (healthy) compose-nemoretriever-ocr-1 Up 4 minutes compose-table-structure-1 Up 4 minutes ``` @@ -253,10 +253,10 @@ Use the following procedure to start all containers needed for this blueprint. 340bc8210a0d milvus-minio Up 3 minutes (healthy) 0be702b87ad6 milvus-etcd Up 3 minutes (healthy) 62eabf1d9f65 nim-llm-ms Up 10 minutes (healthy) - fe2751bfa734 nemoretriever-ranking-ms Up 10 minutes (healthy) + fe2751bfa734 nemotron-ranking-ms Up 10 minutes (healthy) 7b5ddabf8be7 compose-graphic-elements-1 Up 10 minutes ecfaa5190302 compose-page-elements-1 Up 10 minutes - ea8c7fdf20d1 nemoretriever-embedding-ms Up 10 minutes (healthy) + ea8c7fdf20d1 nemotron-embedding-ms Up 10 minutes (healthy) 6d62008a9b42 compose-nemoretriever-ocr-1 Up 10 minutes 969b9f5c987c compose-table-structure-1 Up 10 minutes ``` @@ -337,7 +337,7 @@ After the first time you deploy the RAG Blueprint successfully, you can consider - For advanced users who need direct filesystem access to extraction results, refer to [Ingestor Server Volume Mounting](mount-ingestor-volume.md). -- A single NVIDIA A100-80GB or H100-80GB, B200 GPU can be used to start non-LLM NIMs (nemoretriever-embedding-ms, nemoretriever-ranking-ms, and ingestion services like page-elements, ocr, graphic-elements, and table-structure) for ingestion and RAG workflows. You can control which GPU is used for each service by setting these environment variables in `deploy/compose/.env` file before launching. For a complete list of all services and their default GPU assignments, see [Service Port and GPU Reference](service-port-gpu-reference.md). +- A single NVIDIA A100-80GB or H100-80GB, B200 GPU can be used to start non-LLM NIMs (nemotron-embedding-ms, nemotron-ranking-ms, and ingestion services like page-elements, ocr, graphic-elements, and table-structure) for ingestion and RAG workflows. You can control which GPU is used for each service by setting these environment variables in `deploy/compose/.env` file before launching. For a complete list of all services and their default GPU assignments, see [Service Port and GPU Reference](service-port-gpu-reference.md). ```bash EMBEDDING_MS_GPU_ID=0 diff --git a/docs/deploy-helm.md b/docs/deploy-helm.md index f0686c6ca..9e7a39217 100644 --- a/docs/deploy-helm.md +++ b/docs/deploy-helm.md @@ -146,11 +146,11 @@ To verify a deployment, use the following procedure. NAME READY STATUS RESTARTS AGE ingestor-server-6cc886bcdf-6rfwm 1/1 Running 0 54m milvus-standalone-7dd5db4755-ctqzg 1/1 Running 0 54m - nemoretriever-embedding-ms-86f75c8f65-dfhd2 1/1 Running 0 39m + nemotron-embedding-ms-86f75c8f65-dfhd2 1/1 Running 0 39m nemoretriever-graphic-elements-v1-67d9d65bdc-ftbkw 1/1 Running 0 33m nemoretriever-ocr-v1-78f56cddb9-f4852 1/1 Running 0 40m nemoretriever-page-elements-v3-56ddcf9b4b-qsg82 1/1 Running 0 49m - nemoretriever-ranking-ms-5ff774889f-fwrlm 1/1 Running 0 40m + nemotron-ranking-ms-5ff774889f-fwrlm 1/1 Running 0 40m nemoretriever-table-structure-v1-696c9f5665-l9sxn 1/1 Running 0 37m nim-llm-7cb9bdcc89-hwpkq 1/1 Running 0 11m nim-llm-cache-job-77hpc 0/1 Completed 0 94s @@ -209,11 +209,11 @@ To verify a deployment, use the following procedure. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingestor-server ClusterIP 10.107.12.217 8082/TCP 54m milvus ClusterIP 10.99.110.203 19530/TCP,9091/TCP 54m - nemoretriever-embedding-ms ClusterIP 10.104.99.15 8000/TCP,8001/TCP 54m + nemotron-embedding-ms ClusterIP 10.104.99.15 8000/TCP,8001/TCP 54m nemoretriever-graphic-elements-v1 ClusterIP 10.96.115.45 8000/TCP,8001/TCP 54m nemoretriever-ocr-v1 ClusterIP 10.100.107.215 8000/TCP,8001/TCP 54m nemoretriever-page-elements-v3 ClusterIP 10.102.237.196 8000/TCP,8001/TCP 54m - nemoretriever-ranking-ms ClusterIP 10.96.114.244 8000/TCP,8001/TCP 54m + nemotron-ranking-ms ClusterIP 10.96.114.244 8000/TCP,8001/TCP 54m nemoretriever-table-structure-v1 ClusterIP 10.107.227.139 8000/TCP,8001/TCP 54m nim-llm ClusterIP 10.104.60.155 8000/TCP,8001/TCP 54m rag-etcd ClusterIP 10.104.74.116 2379/TCP,2380/TCP 54m diff --git a/docs/mig-deployment.md b/docs/mig-deployment.md index 878681713..149c25366 100644 --- a/docs/mig-deployment.md +++ b/docs/mig-deployment.md @@ -233,14 +233,14 @@ You should see output similar to the following. Resource Requested Limit Allocatable Free nvidia.com/mig-1g.10gb (86%) 6.0 (86%) 6.0 7.0 1.0 ├─ milvus-standalone-... 1.0 1.0 -├─ nemoretriever-embedding-ms-... 1.0 1.0 +├─ nemotron-embedding-ms-... 1.0 1.0 ├─ rag-nv-ingest-... 1.0 1.0 ├─ nemoretriever-graphic-elements-v1-... 1.0 1.0 ├─ nemoretriever-page-elements-v3-... 1.0 1.0 └─ nemoretriever-table-structure-v1-... 1.0 1.0 nvidia.com/mig-1g.20gb (100%) 2.0 (100%) 2.0 2.0 0.0 -├─ nemoretriever-ranking-ms-... 1.0 1.0 +├─ nemotron-ranking-ms-... 1.0 1.0 └─ 1.0 1.0 nvidia.com/mig-3g.40gb (100%) 1.0 (100%) 1.0 1.0 0.0 diff --git a/docs/multi-collection-retrieval.md b/docs/multi-collection-retrieval.md index cd80c337f..07d05be12 100644 --- a/docs/multi-collection-retrieval.md +++ b/docs/multi-collection-retrieval.md @@ -38,10 +38,10 @@ The reranker settings are configured in `deploy/compose/docker-compose-rag-serve export ENABLE_RERANKER=True # Set reranker model (default is already configured) -export APP_RANKING_MODELNAME="nvidia/llama-3.2-nv-rerankqa-1b-v2" +export APP_RANKING_MODELNAME="nvidia/llama-nemotron-rerank-1b-v2" # Reranker service URL (default is already configured) -export APP_RANKING_SERVERURL="nemoretriever-ranking-ms:8000" +export APP_RANKING_SERVERURL="nemotron-ranking-ms:8000" ``` ### For Helm Deployment @@ -54,7 +54,7 @@ envVars: ENABLE_RERANKER: "True" # Reranker model name (default is already configured) - APP_RANKING_MODELNAME: "nvidia/llama-3.2-nv-rerankqa-1b-v2" + APP_RANKING_MODELNAME: "nvidia/llama-nemotron-rerank-1b-v2" # Reranker service URL (default is already configured) APP_RANKING_SERVERURL: "nemoretriever-reranking-ms:8000" diff --git a/docs/nv-ingest-standalone.md b/docs/nv-ingest-standalone.md index f09a37970..691a23607 100644 --- a/docs/nv-ingest-standalone.md +++ b/docs/nv-ingest-standalone.md @@ -118,10 +118,10 @@ ingestor = ingestor.split( ) ingestor = ingestor.embed( - # For self-hosted: "http://nemoretriever-embedding-ms:8000/v1" + # For self-hosted: "http://nemotron-embedding-ms:8000/v1" # For cloud (NVIDIA-hosted): "https://integrate.api.nvidia.com/v1" - endpoint_url="http://nemoretriever-embedding-ms:8000/v1", - model_name="nvidia/llama-3.2-nv-embedqa-1b-v2" + endpoint_url="http://nemotron-embedding-ms:8000/v1", + model_name="nvidia/llama-nemotron-embed-1b-v2" ) ingestor = ingestor.vdb_upload( diff --git a/docs/python-client.md b/docs/python-client.md index 73b432eaf..e052b5e91 100644 --- a/docs/python-client.md +++ b/docs/python-client.md @@ -155,12 +155,12 @@ Verify all containers are running and healthy. ```output NAMES STATUS -nemoretriever-ranking-ms Up ... (healthy) +nemotron-ranking-ms Up ... (healthy) compose-page-elements-1 Up ... compose-nemoretriever-ocr-1 Up ... compose-graphic-elements-1 Up ... compose-table-structure-1 Up ... -nemoretriever-embedding-ms Up ... (healthy) +nemotron-embedding-ms Up ... (healthy) nim-llm-ms Up ... (healthy) ``` @@ -176,19 +176,19 @@ nim-llm-ms Up ... (healthy) - os.environ["OCR_INFER_PROTOCOL"] = "http" os.environ["YOLOX_HTTP_ENDPOINT"] = ( - "https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3" + "https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3" ) - os.environ["YOLOX_INFER_PROTOCOL"] = "http" - os.environ["YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT"] = ( - "https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1" + "https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1" ) - os.environ["YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL"] = "http" - os.environ["YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT"] = ( - "https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1" + "https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1" ) os.environ["YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL"] = "http" @@ -247,7 +247,7 @@ if DEPLOYMENT_MODE == "cloud": config_ingestor.llm.server_url = "" # Empty uses NVIDIA API catalog config_ingestor.summarizer.server_url = "" # Empty uses NVIDIA API catalog else: - config_ingestor.embeddings.server_url = "http://nemoretriever-embedding-ms:8000/v1" + config_ingestor.embeddings.server_url = "http://nemotron-embedding-ms:8000/v1" ingestor = NvidiaRAGIngestor(config=config_ingestor) ``` @@ -357,11 +357,11 @@ from nvidia_rag.utils.configuration import NvidiaRAGConfig # "server_url": "", # }, # "embeddings": { -# "model_name": "nvidia/llama-3.2-nv-embedqa-1b-v2", +# "model_name": "nvidia/llama-nemotron-embed-1b-v2", # "server_url": "https://integrate.api.nvidia.com/v1", # }, # "ranking": { -# "model_name": "nvidia/llama-3.2-nv-rerankqa-1b-v2", +# "model_name": "nvidia/llama-nemotron-rerank-1b-v2", # "server_url": "", # }, # }) diff --git a/docs/retrieval-only-deployment.md b/docs/retrieval-only-deployment.md index 3cfc5d30a..7f7f94475 100644 --- a/docs/retrieval-only-deployment.md +++ b/docs/retrieval-only-deployment.md @@ -88,11 +88,11 @@ Choose one of the following options based on your deployment preference. Instead of starting all NIMs, use the `text-embed` profile to start only the embedding and reranking services: ```bash -USERID=$(id -u) docker compose -f deploy/compose/nims.yaml up -d nemoretriever-ranking-ms nemoretriever-embedding-ms +USERID=$(id -u) docker compose -f deploy/compose/nims.yaml up -d nemotron-ranking-ms nemotron-embedding-ms ``` :::{note} -The `text-embed` profile starts only `nemoretriever-embedding-ms` and `nemoretriever-ranking-ms `, which is sufficient for retrieval operations. The LLM NIM (`nim-llm-ms`) is not started, saving significant GPU memory. +The `text-embed` profile starts only `nemotron-embedding-ms` and `nemotron-ranking-ms `, which is sufficient for retrieval operations. The LLM NIM (`nim-llm-ms`) is not started, saving significant GPU memory. ::: Wait for the services to become healthy: @@ -105,8 +105,8 @@ Expected output: ```output NAMES STATUS -nemoretriever-ranking-ms Up 5 minutes (healthy) -nemoretriever-embedding-ms Up 5 minutes (healthy) +nemotron-ranking-ms Up 5 minutes (healthy) +nemotron-embedding-ms Up 5 minutes (healthy) ``` #### Option B: NVIDIA-Hosted NIMs @@ -308,7 +308,7 @@ This is expected behavior in retrieval-only mode. The `/generate` endpoint requi Check the embedding NIM logs: ```bash -docker logs nemoretriever-embedding-ms +docker logs nemotron-embedding-ms ``` Ensure the model cache directory has proper permissions: diff --git a/docs/service-port-gpu-reference.md b/docs/service-port-gpu-reference.md index 648d1bd32..f95c9925a 100644 --- a/docs/service-port-gpu-reference.md +++ b/docs/service-port-gpu-reference.md @@ -20,9 +20,9 @@ The following table provides a comprehensive reference of all services, their po | Service | Container Name | Host Port(s) | Container Port(s) | Default GPU ID | Environment Variable | Notes | |---------|---------------|--------------|-------------------|----------------|---------------------|-------| | LLM | `nim-llm-ms` | 8999 | 8000 | 1 | `LLM_MS_GPU_ID` | Main language model | -| Embedding | `nemoretriever-embedding-ms` | 9080 | 8000 | 0 | `EMBEDDING_MS_GPU_ID` | Text embeddings | +| Embedding | `nemotron-embedding-ms` | 9080 | 8000 | 0 | `EMBEDDING_MS_GPU_ID` | Text embeddings | | VLM Embedding | `nemotron-vlm-embedding-ms` | 9081 | 8000 | 0 | `VLM_EMBEDDING_MS_GPU_ID` | Vision-language embeddings (opt-in, profile: vlm-embed) | -| Ranking | `nemoretriever-ranking-ms` | 1976 | 8000 | 0 | `RANKING_MS_GPU_ID` | Reranking model | +| Ranking | `nemotron-ranking-ms` | 1976 | 8000 | 0 | `RANKING_MS_GPU_ID` | Reranking model | | VLM | `nemo-vlm-microservice` | 1977 | 8000 | 5 | `VLM_MS_GPU_ID` | Vision-language model (opt-in, profile: vlm-only, vlm-generation) | | Nemotron Parse | `compose-nemotron-parse-1` | 8015, 8016, 8017 | 8000, 8001, 8002 | 1 | `NEMOTRON_PARSE_MS_GPU_ID` | PDF parsing (opt-in, profile: nemotron-parse) | | RIVA ASR | `compose-audio-1` | 8021, 8022 | 50051, 9000 | 0 | `AUDIO_MS_GPU_ID` | Audio speech recognition (opt-in, profile: audio) | diff --git a/docs/text_only_ingest.md b/docs/text_only_ingest.md index 625416090..10cb6e9b1 100644 --- a/docs/text_only_ingest.md +++ b/docs/text_only_ingest.md @@ -43,8 +43,8 @@ You can enable text-only ingestion for the [NVIDIA RAG Blueprint](readme.md). Fo ```output NAMES STATUS - nemoretriever-ranking-ms Up 14 minutes (healthy) - nemoretriever-embedding-ms Up 14 minutes (healthy) + nemotron-ranking-ms Up 14 minutes (healthy) + nemotron-embedding-ms Up 14 minutes (healthy) nim-llm-ms Up 14 minutes (healthy) ``` @@ -70,7 +70,7 @@ In case you are [interacting with cloud hosted models](deploy-docker-nvidia-host export APP_EMBEDDINGS_SERVERURL="" export APP_LLM_SERVERURL="" export APP_RANKING_SERVERURL="" - export YOLOX_HTTP_ENDPOINT="https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3" + export YOLOX_HTTP_ENDPOINT="https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3" export YOLOX_INFER_PROTOCOL="http" ``` ::: diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 319056bf0..485ae84f1 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -87,10 +87,10 @@ During first-time deployments, large models are downloaded without visible progr docker logs -f nim-llm-ms # Monitor embedding service -docker logs -f nemoretriever-embedding-ms +docker logs -f nemotron-embedding-ms # Monitor ranking service -docker logs -f nemoretriever-ranking-ms +docker logs -f nemotron-ranking-ms ``` **Check disk usage to verify download progress:** @@ -105,7 +105,7 @@ watch -n 10 'du -sh ~/.cache/model-cache/' **Check container stats:** ```bash # View resource usage and verify containers are active -docker stats nim-llm-ms nemoretriever-embedding-ms nemoretriever-ranking-ms +docker stats nim-llm-ms nemotron-embedding-ms nemotron-ranking-ms ``` ### Kubernetes/Helm Deployments @@ -367,7 +367,7 @@ Adding this information may impact response accuracy, especially when partial in ## Helm Deployment Issues ### PVCs in Pending state (StorageClass issues) -If NIM Cache PVCs (e.g., `nemoretriever-embedding-ms-cache-pvc`) remain in `Pending` state, check if they are requesting a `storageClassName: default` that does not exist. +If NIM Cache PVCs (e.g., `nemotron-embedding-ms-cache-pvc`) remain in `Pending` state, check if they are requesting a `storageClassName: default` that does not exist. **Fix:** Ensure you have a default storage class. If using `local-path`, you can create an alias: ```yaml apiVersion: storage.k8s.io/v1 diff --git a/frontend/src/store/__tests__/useSettingsStore.test.tsx b/frontend/src/store/__tests__/useSettingsStore.test.tsx index 0d8165473..541451f20 100644 --- a/frontend/src/store/__tests__/useSettingsStore.test.tsx +++ b/frontend/src/store/__tests__/useSettingsStore.test.tsx @@ -52,7 +52,7 @@ const mockHealthResponse: HealthResponse = { status: 'healthy', latency_ms: 40, error: null, - model: 'nvidia/llama-3.2-nv-rerankqa-1b-v2', + model: 'nvidia/llama-nemotron-rerank-1b-v2', message: null, http_status: 200 } @@ -121,7 +121,7 @@ describe('useHealthInitialization', () => { const state = useSettingsStore.getState(); expect(state.model).toBe('meta/llama-3.1-8b-instruct'); expect(state.embeddingModel).toBe('nvidia/nv-embedqa-e5-v5'); - expect(state.rerankerModel).toBe('nvidia/llama-3.2-nv-rerankqa-1b-v2'); + expect(state.rerankerModel).toBe('nvidia/llama-nemotron-rerank-1b-v2'); }); // Verify endpoints are also set @@ -140,7 +140,7 @@ describe('useHealthInitialization', () => { llmEndpoint: 'http://llm:8000', embeddingModel: 'nvidia/nv-embedqa-e5-v5', embeddingEndpoint: 'http://embeddings:8001', - rerankerModel: 'nvidia/llama-3.2-nv-rerankqa-1b-v2', + rerankerModel: 'nvidia/llama-nemotron-rerank-1b-v2', rerankerEndpoint: 'http://reranker:8002' }) ); @@ -167,7 +167,7 @@ describe('useHealthInitialization', () => { expect(state.model).toBe('user-selected-llm-model'); expect(state.embeddingModel).toBe('user-selected-embedding-model'); // Should still populate undefined fields - expect(state.rerankerModel).toBe('nvidia/llama-3.2-nv-rerankqa-1b-v2'); + expect(state.rerankerModel).toBe('nvidia/llama-nemotron-rerank-1b-v2'); }); }); diff --git a/notebooks/.env_library b/notebooks/.env_library index a0d998752..eb5b68eb5 100644 --- a/notebooks/.env_library +++ b/notebooks/.env_library @@ -16,8 +16,8 @@ export MINIO_ACCESSKEY=minioadmin export MINIO_SECRETKEY=minioadmin # === Embedding Model specific configurations === -export APP_EMBEDDINGS_SERVERURL=nemoretriever-embedding-ms:8000/v1 -export APP_EMBEDDINGS_MODELNAME=nvidia/llama-3.2-nv-embedqa-1b-v2 +export APP_EMBEDDINGS_SERVERURL=nemotron-embedding-ms:8000/v1 +export APP_EMBEDDINGS_MODELNAME=nvidia/llama-nemotron-embed-1b-v2 export APP_EMBEDDINGS_DIMENSIONS=2048 # For VLM Embedding Model (Nemoretriever-1b-vlm-embed-v1) # export APP_EMBEDDINGS_SERVERURL=localhost:9081 @@ -85,7 +85,7 @@ export ENABLE_FILTER_GENERATOR=False # === Reranking Model specific configurations === export APP_RANKING_SERVERURL=localhost:1976 -export APP_RANKING_MODELNAME="nvidia/llama-3.2-nv-rerankqa-1b-v2" +export APP_RANKING_MODELNAME="nvidia/llama-nemotron-rerank-1b-v2" export ENABLE_RERANKER=True # === VLM Model specific configurations === diff --git a/notebooks/building_rag_vdb_operator.ipynb b/notebooks/building_rag_vdb_operator.ipynb index fec360d84..1852f1d9c 100644 --- a/notebooks/building_rag_vdb_operator.ipynb +++ b/notebooks/building_rag_vdb_operator.ipynb @@ -360,12 +360,12 @@ "Ensure all the below are running and healthy before proceeding further\n", "```output\n", "NAMES STATUS\n", - "nemoretriever-ranking-ms Up ... (healthy)\n", + "nemotron-ranking-ms Up ... (healthy)\n", "compose-page-elements-1 Up ...\n", "compose-nemoretriever-ocr-1 Up ...\n", "compose-graphic-elements-1 Up ...\n", "compose-table-structure-1 Up ...\n", - "nemoretriever-embedding-ms Up ... (healthy)\n", + "nemotron-embedding-ms Up ... (healthy)\n", "nim-llm-ms Up ... (healthy)\n", "```" ] @@ -390,15 +390,15 @@ "os.environ[\"OCR_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr\"\n", "os.environ[\"OCR_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3\"\n", ")\n", "os.environ[\"YOLOX_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1\"\n", ")\n", "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1\"\n", ")\n", "os.environ[\"YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL\"] = \"http\"" ] @@ -2081,13 +2081,13 @@ "\n", "# IMPORTANT: Two different embedding URLs are needed:\n", "# 1. config_ingestor.embeddings.server_url → Used by nv-ingest (runs in Docker)\n", - "# Must use Docker network hostname: nemoretriever-embedding-ms:8000\n", + "# Must use Docker network hostname: nemotron-embedding-ms:8000\n", "# 2. embedding_model for VDB operator → Used for queries (runs locally in notebook)\n", "# Must use localhost: localhost:9080\n", "\n", "if DEPLOYMENT_MODE == \"on_prem\":\n", " # nv-ingest runs inside Docker, needs Docker network hostname\n", - " config_ingestor.embeddings.server_url = \"http://nemoretriever-embedding-ms:8000/v1\"\n", + " config_ingestor.embeddings.server_url = \"http://nemotron-embedding-ms:8000/v1\"\n", "if DEPLOYMENT_MODE == \"cloud\":\n", " config_ingestor.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", " config_ingestor.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", diff --git a/notebooks/config.yaml b/notebooks/config.yaml index ae9551877..f5a8eb53b 100644 --- a/notebooks/config.yaml +++ b/notebooks/config.yaml @@ -55,14 +55,14 @@ filter_expression_generator: # Embedding Configuration embeddings: - model_name: "nvidia/llama-3.2-nv-embedqa-1b-v2" # Model for generating text embeddings + model_name: "nvidia/llama-nemotron-embed-1b-v2" # Model for generating text embeddings dimensions: 2048 # Dimensionality of the embedding vectors server_url: "http://localhost:9080/v1" # URL endpoint for embedding service (on-prem NIM default) # api_key: "" # Optional: API key for embeddings (overrides NVIDIA_API_KEY environment variable) # Ranking Configuration ranking: - model_name: "nvidia/llama-3.2-nv-rerankqa-1b-v2" # Model for reranking retrieved documents + model_name: "nvidia/llama-nemotron-rerank-1b-v2" # Model for reranking retrieved documents server_url: "http://localhost:1976" # URL endpoint for reranking service (on-prem NIM default) enable_reranker: true # Enable reranking of retrieved documents before generation # api_key: "" # Optional: API key for reranking (overrides NVIDIA_API_KEY environment variable) diff --git a/notebooks/image_input.ipynb b/notebooks/image_input.ipynb index 622ec849b..4f778a2fa 100644 --- a/notebooks/image_input.ipynb +++ b/notebooks/image_input.ipynb @@ -293,11 +293,11 @@ "os.environ[\"OCR_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr\"\n", "os.environ[\"OCR_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"OCR_MODEL_NAME\"] = \"scene_text_ensemble\"\n", - "os.environ[\"YOLOX_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3\"\n", + "os.environ[\"YOLOX_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3\"\n", "os.environ[\"YOLOX_INFER_PROTOCOL\"] = \"http\"\n", - "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1\"\n", + "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1\"\n", "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL\"] = \"http\"\n", - "os.environ[\"YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1\"\n", + "os.environ[\"YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1\"\n", "os.environ[\"YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"APP_NVINGEST_CAPTIONENDPOINTURL\"] = \"https://integrate.api.nvidia.com/v1/chat/completions\"\n", "\n", diff --git a/notebooks/launchable.ipynb b/notebooks/launchable.ipynb index ee56c3447..1bc8e793b 100644 --- a/notebooks/launchable.ipynb +++ b/notebooks/launchable.ipynb @@ -1295,10 +1295,10 @@ "902445432dde milvus-standalone Up 3 minutes\n", "340bc8210a0d milvus-minio Up 3 minutes (healthy)\n", "0be702b87ad6 milvus-etcd Up 3 minutes (healthy)\n", - "fe2751bfa734 nemoretriever-ranking-ms Up 10 minutes (healthy)\n", + "fe2751bfa734 nemotron-ranking-ms Up 10 minutes (healthy)\n", "7b5ddabf8be7 compose-graphic-elements-1 Up 10 minutes\n", "ecfaa5190302 compose-page-elements-1 Up 10 minutes\n", - "ea8c7fdf20d1 nemoretriever-embedding-ms Up 10 minutes (healthy)\n", + "ea8c7fdf20d1 nemotron-embedding-ms Up 10 minutes (healthy)\n", "6d62008a9b42 compose-nemoretriever-ocr-1 Up 10 minutes\n", "969b9f5c987c compose-table-structure-1 Up 10 minutes\n", "```\n", diff --git a/notebooks/nb_metadata.ipynb b/notebooks/nb_metadata.ipynb index ad79dd7c7..39203d07b 100644 --- a/notebooks/nb_metadata.ipynb +++ b/notebooks/nb_metadata.ipynb @@ -951,8 +951,8 @@ " \"enable_reranker\": True,\n", " \"enable_citations\": True,\n", " \"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", " # Provide url of the model endpoints if deployed elsewhere\n", " # \"llm_endpoint\": \"\",\n", " #\"embedding_endpoint\": \"\",\n", @@ -1004,8 +1004,8 @@ " \"enable_reranker\": True,\n", " \"enable_citations\": True,\n", " \"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", " # Provide url of the model endpoints if deployed elsewhere\n", " # \"llm_endpoint\": \"\",\n", " #\"embedding_endpoint\": \"\",\n", @@ -1119,8 +1119,8 @@ " \"enable_citations\": True,\n", " \"enable_filter_generator\": False, # Disable to use manual complex filter\n", " \"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", " \"stop\": [],\n", " \"filter_expr\": '(content_metadata[\"manufacturer\"] like \"%ford%\" and content_metadata[\"rating\"] > 4.0 and content_metadata[\"created_date\"] between \"2020-01-01\" and \"2024-12-31\" and content_metadata[\"is_public\"] == true) or (content_metadata[\"model\"] like \"%edge%\" and content_metadata[\"year\"] >= 2020 and content_metadata[\"tags\"] in [\"technology\", \"safety\", \"latest\"] and content_metadata[\"rating\"] >= 4.0)'\n", "}\n", @@ -1186,8 +1186,8 @@ " \"enable_reranker\": True,\n", " \"enable_citations\": True,\n", " \"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", " \"stop\": [],\n", " \"filter_expr\": 'array_contains(content_metadata[\"tags\"], \"eco-friendly\")'\n", "}\n", @@ -1257,8 +1257,8 @@ " \"enable_citations\": True,\n", " \"enable_filter_generator\": True, # 🎯 NEW FEATURE - Enable AI filter generation\n", " \"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", " \"stop\": [],\n", " \"filter_expr\": \"\" # Will be generated automatically by AI\n", "}\n", @@ -1323,8 +1323,8 @@ " \"enable_reranker\": False,\n", " \"enable_citations\": False,\n", " \"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", " # Provide url of the model endpoints if deployed elsewhere\n", " # \"llm_endpoint\": \"\",\n", " #\"embedding_endpoint\": \"\",\n", @@ -1391,8 +1391,8 @@ " \"enable_citations\": True,\n", " \"enable_filter_generator\": False,\n", " \"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", - " \"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " \"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + " \"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " \"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", " \"stop\": [],\n", " \"filter_expr\": 'content_metadata[\"nonexistent_field\"] == \"value\"' # This will cause an error\n", "}\n", diff --git a/notebooks/rag_library_lite_usage.ipynb b/notebooks/rag_library_lite_usage.ipynb index eb91c80b6..915eb00ca 100644 --- a/notebooks/rag_library_lite_usage.ipynb +++ b/notebooks/rag_library_lite_usage.ipynb @@ -36,7 +36,7 @@ "\n", "Install nv-ingest library using below command - **OR** - Run the cell below if Jupyter notebook is started in the same environment:\n", "```bash\n", - "uv pip install nv-ingest==26.1.1\n", + "uv pip install nv-ingest==26.1.2\n", "```" ] }, @@ -71,7 +71,7 @@ "# !uv pip install ../dist/nvidia_rag-*-py3-none-any.whl[all]\n", "\n", "# Install NV-Ingest library in the same environment to run NV-Ingest pipeline\n", - "!uv pip install nv-ingest==26.1.1" + "!uv pip install nv-ingest==26.1.2" ] }, { @@ -150,15 +150,15 @@ "os.environ[\"OCR_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr\"\n", "os.environ[\"OCR_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3\"\n", ")\n", "os.environ[\"YOLOX_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1\"\n", ")\n", "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1\"\n", ")\n", "os.environ[\"YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL\"] = \"http\"" ] @@ -292,7 +292,7 @@ "\n", "config_ingestor = NvidiaRAGConfig.from_yaml(\"config.yaml\")\n", "# You can update the config object to use different models and endpoints like below\n", - "# config_ingestor.embeddings.model_name = \"nvidia/llama-3.2-nv-embedqa-1b-v2\"\n", + "# config_ingestor.embeddings.model_name = \"nvidia/llama-nemotron-embed-1b-v2\"\n", "# config_ingestor.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", "\n", "# Set config for rag lite library mode\n", diff --git a/notebooks/rag_library_usage.ipynb b/notebooks/rag_library_usage.ipynb index 83fb0f3b0..894e82620 100644 --- a/notebooks/rag_library_usage.ipynb +++ b/notebooks/rag_library_usage.ipynb @@ -308,12 +308,12 @@ "Ensure all the below are running and healthy before proceeding further\n", "```output\n", "NAMES STATUS\n", - "nemoretriever-ranking-ms Up ... (healthy)\n", + "nemotron-ranking-ms Up ... (healthy)\n", "compose-page-elements-1 Up ...\n", "compose-nemoretriever-ocr-1 Up ...\n", "compose-graphic-elements-1 Up ...\n", "compose-table-structure-1 Up ...\n", - "nemoretriever-embedding-ms Up ... (healthy)\n", + "nemotron-embedding-ms Up ... (healthy)\n", "nim-llm-ms Up ... (healthy)\n", "```" ] @@ -338,15 +338,15 @@ "os.environ[\"OCR_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr\"\n", "os.environ[\"OCR_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3\"\n", ")\n", "os.environ[\"YOLOX_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1\"\n", ")\n", "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1\"\n", ")\n", "os.environ[\"YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL\"] = \"http\"" ] @@ -440,7 +440,7 @@ " config_ingestor.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", " config_ingestor.summarizer.server_url = \"\" # Empty uses NVIDIA API catalog\n", "else:\n", - " config_ingestor.embeddings.server_url = \"http://nemoretriever-embedding-ms:8000/v1\"\n", + " config_ingestor.embeddings.server_url = \"http://nemotron-embedding-ms:8000/v1\"\n", "ingestor = NvidiaRAGIngestor(config=config_ingestor)" ] }, @@ -625,11 +625,11 @@ "# \"server_url\": \"\",\n", "# },\n", "# \"embeddings\": {\n", - "# \"model_name\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + "# \"model_name\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", "# \"server_url\": \"https://integrate.api.nvidia.com/v1\",\n", "# },\n", "# \"ranking\": {\n", - "# \"model_name\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", + "# \"model_name\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", "# \"server_url\": \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-3_2-nv-rerankqa-1b-v2/reranking/v1\",\n", "# },\n", "# }\n", diff --git a/notebooks/retriever_api_usage.ipynb b/notebooks/retriever_api_usage.ipynb index 47466e3ed..9b52647ed 100644 --- a/notebooks/retriever_api_usage.ipynb +++ b/notebooks/retriever_api_usage.ipynb @@ -145,8 +145,8 @@ " \"filter_expr\": \"\",\n", " # Override model endpoints and details if needed\n", " #\"model\": \"nvidia/llama-3.3-nemotron-super-49b-v1.5\",\n", - " #\"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " #\"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + " #\"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " #\"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", " #\"llm_endpoint\": \"\",\n", " #\"embedding_endpoint\": \"\",\n", " #\"reranker_endpoint\": \"\",\n", @@ -271,8 +271,8 @@ " \"enable_query_rewriting\": False,\n", " \"enable_reranker\": True,\n", " # Override model endpoints and details if needed\n", - " #\"reranker_model\": \"nvidia/llama-3.2-nv-rerankqa-1b-v2\",\n", - " #\"embedding_model\": \"nvidia/llama-3.2-nv-embedqa-1b-v2\",\n", + " #\"reranker_model\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", + " #\"embedding_model\": \"nvidia/llama-nemotron-embed-1b-v2\",\n", " #\"embedding_endpoint\": \"\",\n", " #\"reranker_endpoint\": \"\",\n", "}\n", diff --git a/notebooks/summarization.ipynb b/notebooks/summarization.ipynb index f824f8e7e..d0c7ef285 100644 --- a/notebooks/summarization.ipynb +++ b/notebooks/summarization.ipynb @@ -388,12 +388,12 @@ "Ensure all the below are running and healthy before proceeding further\n", "```output\n", "NAMES STATUS\n", - "nemoretriever-ranking-ms Up ... (healthy)\n", + "nemotron-ranking-ms Up ... (healthy)\n", "compose-page-elements-1 Up ...\n", "compose-nemoretriever-ocr-1 Up ...\n", "compose-graphic-elements-1 Up ...\n", "compose-table-structure-1 Up ...\n", - "nemoretriever-embedding-ms Up ... (healthy)\n", + "nemotron-embedding-ms Up ... (healthy)\n", "nim-llm-ms Up ... (healthy)\n", "```" ] @@ -419,15 +419,15 @@ "os.environ[\"OCR_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr\"\n", "os.environ[\"OCR_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3\"\n", ")\n", "os.environ[\"YOLOX_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1\"\n", ")\n", "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL\"] = \"http\"\n", "os.environ[\"YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT\"] = (\n", - " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1\"\n", + " \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1\"\n", ")\n", "os.environ[\"YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL\"] = \"http\"" ] @@ -548,8 +548,8 @@ " config.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-3_2-nv-rerankqa-1b-v2/reranking/v1\"\n", " config.summarizer.server_url = \"\" # Empty uses NVIDIA API catalog\n", "else:\n", - " config.embeddings.server_url = \"nemoretriever-embedding-ms:8000/v1\"\n", - " config.ranking.server_url = \"nemoretriever-ranking-ms:8000\"\n", + " config.embeddings.server_url = \"nemotron-embedding-ms:8000/v1\"\n", + " config.ranking.server_url = \"nemotron-ranking-ms:8000\"\n", " config.summarizer.server_url = \"nim-llm:8000\"\n", " config.llm.server_url = \"nim-llm:8000\"\n", "\n", @@ -967,7 +967,7 @@ "else:\n", " os.environ[\"SUMMARY_LLM_SERVERURL\"] = \"nim-llm:8000\"\n", " os.environ[\"LLM_SERVER_URL\"] = \"nim-llm:8000\"\n", - " os.environ[\"APP_EMBEDDINGS_SERVERURL\"] = \"nemoretriever-embedding-ms:8000/v1\"\n", + " os.environ[\"APP_EMBEDDINGS_SERVERURL\"] = \"nemotron-embedding-ms:8000/v1\"\n", " print(\"✓ Configured for on-prem NIMs\")\n", "\n", "os.environ[\"LOGLEVEL\"] = \"INFO\"\n", diff --git a/pyproject.toml b/pyproject.toml index 80567fb75..6baa185b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,8 +58,8 @@ rag = [ ] ingest = [ # nv-ingest dependencies (required for ingestion operations) - "nv-ingest-api==26.1.1", - "nv-ingest-client==26.1.1", + "nv-ingest-api==26.1.2", + "nv-ingest-client==26.1.2", "tritonclient==2.57.0", # Other ingest dependencies "langchain-openai>=0.2", @@ -80,8 +80,8 @@ ingest = [ ] all = [ # nv-ingest dependencies (required for ingestion operations) - "nv-ingest-api==26.1.1", - "nv-ingest-client==26.1.1", + "nv-ingest-api==26.1.2", + "nv-ingest-client==26.1.2", "tritonclient==2.57.0", # RAG + Ingest dependencies "langchain-openai>=0.2", diff --git a/src/nvidia_rag/utils/configuration.py b/src/nvidia_rag/utils/configuration.py index 9019b7a84..853fe20d7 100644 --- a/src/nvidia_rag/utils/configuration.py +++ b/src/nvidia_rag/utils/configuration.py @@ -631,7 +631,7 @@ class EmbeddingConfig(_ConfigBase): """Embedding configuration.""" model_name: str = Field( - default="nvidia/llama-3.2-nv-embedqa-1b-v2", + default="nvidia/llama-nemotron-embed-1b-v2", env="APP_EMBEDDINGS_MODELNAME", description="Model for generating text embeddings", ) @@ -671,7 +671,7 @@ class RankingConfig(_ConfigBase): """Ranking configuration.""" model_name: str = Field( - default="nvidia/llama-3.2-nv-rerankqa-1b-v2", + default="nvidia/llama-nemotron-rerank-1b-v2", env="APP_RANKING_MODELNAME", description="Model for reranking retrieved documents", ) diff --git a/tests/integration/notebook_test_config.yaml b/tests/integration/notebook_test_config.yaml new file mode 100644 index 000000000..83bc001d5 --- /dev/null +++ b/tests/integration/notebook_test_config.yaml @@ -0,0 +1,118 @@ +# NVIDIA RAG Configuration +# This file contains configurable parameters with their values +# You can override any of these values, and they take precedence over environment variables + +# Vector Store Configuration +vector_store: + name: "milvus" # Name of the vector store backend (e.g., milvus, elasticsearch) + url: "http://localhost:19530" # URL endpoint for the vector store service + index_type: "GPU_CAGRA" # Type of vector index (e.g., GPU_CAGRA, IVF_FLAT) + search_type: "dense" # Type of search to perform (dense, hybrid) + enable_gpu_index: true # Enable GPU acceleration for index building + enable_gpu_search: true # Enable GPU acceleration for search operations + default_collection_name: "test_native" # Default collection/index name for storing vectors + +# NV-Ingest Configuration +nv_ingest: + message_client_hostname: "localhost" # Hostname for NV-Ingest message client + message_client_port: 7670 # Port for NV-Ingest message client + extract_text: true # Enable text extraction from documents + extract_infographics: false # Enable infographic extraction from documents + extract_tables: true # Enable table extraction from documents + extract_charts: true # Enable chart extraction from documents + extract_images: false # Enable image extraction from documents + pdf_extract_method: null # Method to use for PDF extraction + text_depth: "page" # Granularity level for text extraction (page, document) + chunk_size: 512 # Maximum size of text chunks in tokens + chunk_overlap: 150 # Number of overlapping tokens between chunks + caption_model_name: "nvidia/nemotron-nano-12b-v2-vl" # Model name for generating image captions + caption_endpoint_url: "http://localhost:1977/v1/chat/completions" # API endpoint for caption generation service + enable_pdf_splitter: true # Enable PDF page splitting during ingestion + +# LLM Configuration +llm: + server_url: "http://localhost:8999" # URL endpoint for the LLM inference service (on-prem NIM default) + model_name: "nvidia/llama-3.3-nemotron-super-49b-v1.5" # Name of the language model to use for generation + # api_key: "" # Optional: API key for LLM service (overrides NVIDIA_API_KEY environment variable) + parameters: + max_tokens: 32768 # Maximum number of tokens to generate in response + temperature: 0.0 # Sampling temperature for controlling randomness (0.0 = deterministic) + top_p: 1.0 # Nucleus sampling threshold for token selection + +# Query Rewriter Configuration +query_rewriter: + model_name: "nvidia/llama-3.3-nemotron-super-49b-v1.5" # Model for rewriting user queries to improve retrieval + server_url: "localhost:8999" # URL endpoint for query rewriter service + enable_query_rewriter: false # Enable automatic query rewriting before retrieval + # api_key: "" # Optional: API key for query rewriter (overrides NVIDIA_API_KEY environment variable) + +# Filter Expression Generator Configuration +filter_expression_generator: + model_name: "nvidia/llama-3.3-nemotron-super-49b-v1.5" # Model for generating metadata filter expressions from queries + server_url: "localhost:8999" # URL endpoint for filter expression generator service + enable_filter_generator: false # Enable automatic filter expression generation from natural language + # api_key: "" # Optional: API key for filter generator (overrides NVIDIA_API_KEY environment variable) + +# Embedding Configuration +embeddings: + model_name: "nvidia/llama-3.2-nv-embedqa-1b-v2" # Model for generating text embeddings + dimensions: 2048 # Dimensionality of the embedding vectors + server_url: "http://localhost:9080/v1" # URL endpoint for embedding service (on-prem NIM default) + # api_key: "" # Optional: API key for embeddings (overrides NVIDIA_API_KEY environment variable) + +# Ranking Configuration +ranking: + model_name: "nvidia/llama-3.2-nv-rerankqa-1b-v2" # Model for reranking retrieved documents + server_url: "http://localhost:1976" # URL endpoint for reranking service (on-prem NIM default) + enable_reranker: true # Enable reranking of retrieved documents before generation + # api_key: "" # Optional: API key for reranking (overrides NVIDIA_API_KEY environment variable) + +# Retriever Configuration +retriever: + top_k: 10 # Number of top documents to return after retrieval and reranking + vdb_top_k: 100 # Number of documents to retrieve from vector database before reranking + score_threshold: 0.25 # Minimum similarity score threshold for retrieved documents + +# Tracing Configuration +tracing: + enabled: false # Enable distributed tracing and metrics collection + otlp_http_endpoint: "http://localhost:4318/v1/traces" # OpenTelemetry HTTP endpoint for traces + otlp_grpc_endpoint: "grpc://localhost:4317" # OpenTelemetry gRPC endpoint for traces + +# Vision-Language Model Configuration +vlm: + server_url: "http://localhost:1977/v1" # URL endpoint for Vision-Language Model service + model_name: "nvidia/nemotron-nano-12b-v2-vl" # Vision-Language Model for processing images and text + # api_key: "" # Optional: API key for VLM service (overrides NVIDIA_API_KEY environment variable) + +# MinIO Configuration +minio: + endpoint: "localhost:9010" # MinIO object storage endpoint + access_key: "minioadmin" # MinIO access key for authentication + secret_key: "minioadmin" # MinIO secret key for authentication + +# Summarizer Configuration +summarizer: + model_name: "nvidia/llama-3.3-nemotron-super-49b-v1.5" # Model for generating document summaries + server_url: "localhost:8999" # URL endpoint for summarization service + max_chunk_length: 50000 # Maximum character length for chunks to summarize + chunk_overlap: 200 # Character overlap between chunks during summarization + temperature: 0.0 # Sampling temperature for summary generation + top_p: 1.0 # Nucleus sampling threshold for summary generation + # api_key: "" # Optional: API key for summarization (overrides NVIDIA_API_KEY environment variable) + +# Reflection Configuration +reflection: + enable_reflection: false # Enable self-reflection to improve answer quality + max_loops: 3 # Maximum number of reflection iterations + model_name: "nvidia/llama-3.3-nemotron-super-49b-v1.5" # Model for reflection and quality assessment + server_url: "" # URL endpoint for reflection service + context_relevance_threshold: 1 # Minimum relevance score for context to be considered useful + response_groundedness_threshold: 1 # Minimum groundedness score for response to be considered factual + # api_key: "" # Optional: API key for reflection (overrides NVIDIA_API_KEY environment variable) + +# Top-level Configuration Flags +enable_guardrails: false # Enable safety guardrails for input/output filtering +enable_citations: true # Include source citations in generated responses +enable_vlm_inference: false # Enable Vision-Language Model for multimodal queries +temp_dir: "./tmp-data/" # Temporary directory for file processing and storage \ No newline at end of file diff --git a/tests/integration/test_cases/library_usage.py b/tests/integration/test_cases/library_usage.py index d67ef4942..c562201ca 100644 --- a/tests/integration/test_cases/library_usage.py +++ b/tests/integration/test_cases/library_usage.py @@ -49,8 +49,7 @@ def _get_config(self): """Get or create shared config object with common settings""" if self._config is None: from nvidia_rag.utils.configuration import NvidiaRAGConfig - - config_path = Path(__file__).parent.parent.parent.parent / "notebooks" / "config.yaml" + config_path = Path(__file__).parent.parent.parent.parent / "tests" / "integration" / "notebook_test_config.yaml" self._config = NvidiaRAGConfig.from_yaml(str(config_path)) # Common configuration for all library tests diff --git a/tests/unit/test_compose_helm_parity/env_parity_exemptions.yaml b/tests/unit/test_compose_helm_parity/env_parity_exemptions.yaml index 80cea693c..ed9f45c06 100644 --- a/tests/unit/test_compose_helm_parity/env_parity_exemptions.yaml +++ b/tests/unit/test_compose_helm_parity/env_parity_exemptions.yaml @@ -53,8 +53,8 @@ ngcApiKeyPresenceExemptions: perService: nims.yaml: nim-llm: true - nemoretriever-embedding-ms: true - nemoretriever-ranking-ms: true + nemotron-embedding-ms: true + nemotron-ranking-ms: true vlm-ms: true diff --git a/tests/unit/test_compose_helm_parity/test_compose_helm_parity.py b/tests/unit/test_compose_helm_parity/test_compose_helm_parity.py index 713e50a6c..15a36bbbf 100644 --- a/tests/unit/test_compose_helm_parity/test_compose_helm_parity.py +++ b/tests/unit/test_compose_helm_parity/test_compose_helm_parity.py @@ -300,7 +300,7 @@ def test_compose_helm_image_and_env_parity(): "ngcAPIKey", ], }, - "nemoretriever-embedding-ms": { + "nemotron-embedding-ms": { "values_image_repo_path": [ "nimOperator", "nvidia-nim-llama-32-nv-embedqa-1b-v2", @@ -320,7 +320,7 @@ def test_compose_helm_image_and_env_parity(): "ngcAPIKey", ], }, - "nemoretriever-ranking-ms": { + "nemotron-ranking-ms": { "values_image_repo_path": [ "nimOperator", "nvidia-nim-llama-32-nv-rerankqa-1b-v2", diff --git a/tests/unit/test_rag_server/test_self_reflection.py b/tests/unit/test_rag_server/test_self_reflection.py index f7a6d54c6..6acf3b0e0 100644 --- a/tests/unit/test_rag_server/test_self_reflection.py +++ b/tests/unit/test_rag_server/test_self_reflection.py @@ -142,7 +142,7 @@ async def test_check_context_relevance(mocker): and structured prompts for consistent, reproducible reflection results. """ # Set up a local ranker for reranking documents - local_ranker = get_ranking_model(model="nvidia/llama-3.2-nv-rerankqa-1b-v2", url="") + local_ranker = get_ranking_model(model="nvidia/llama-nemotron-rerank-1b-v2", url="") # Create a mock VDBRag object mock_vdb_op = mocker.MagicMock(spec=VDBRag) diff --git a/tests/unit/test_utils/test_configuration.py b/tests/unit/test_utils/test_configuration.py index 3525664b7..e865b7326 100644 --- a/tests/unit/test_utils/test_configuration.py +++ b/tests/unit/test_utils/test_configuration.py @@ -197,7 +197,7 @@ def test_default_values(self): """Test default configuration values.""" config = EmbeddingConfig() - assert config.model_name == "nvidia/llama-3.2-nv-embedqa-1b-v2" + assert config.model_name == "nvidia/llama-nemotron-embed-1b-v2" assert config.model_engine == "nvidia-ai-endpoints" assert config.dimensions == 2048 assert config.server_url == "" @@ -210,7 +210,7 @@ def test_default_values(self): """Test default configuration values.""" config = RankingConfig() - assert config.model_name == "nvidia/llama-3.2-nv-rerankqa-1b-v2" + assert config.model_name == "nvidia/llama-nemotron-rerank-1b-v2" assert config.model_engine == "nvidia-ai-endpoints" assert config.server_url == "" assert config.enable_reranker is True diff --git a/tests/unit/test_utils/test_reranker.py b/tests/unit/test_utils/test_reranker.py index f012a2a5c..ac60c67fe 100644 --- a/tests/unit/test_utils/test_reranker.py +++ b/tests/unit/test_utils/test_reranker.py @@ -66,11 +66,11 @@ def test_get_ranking_model_nvidia_endpoints_with_model_name( mock_nvidia_rerank.return_value = mock_reranker result = _get_ranking_model( - "nvidia/llama-3.2-nv-rerankqa-1b-v2", "", 10, config=mock_config + "nvidia/llama-nemotron-rerank-1b-v2", "", 10, config=mock_config ) mock_nvidia_rerank.assert_called_once_with( - model="nvidia/llama-3.2-nv-rerankqa-1b-v2", + model="nvidia/llama-nemotron-rerank-1b-v2", api_key="test-api-key", top_n=10, truncate="END", @@ -297,7 +297,7 @@ def test_complete_ranking_workflow_with_url( # Test the workflow model = get_ranking_model( - "nvidia/llama-3.2-nv-rerankqa-1b-v2", "rerank-service:8080", 10 + "nvidia/llama-nemotron-rerank-1b-v2", "rerank-service:8080", 10 ) # Test that the model can be used @@ -325,7 +325,7 @@ def test_complete_ranking_workflow_api_catalog( mock_get_model.return_value = mock_reranker # Test the workflow - model = get_ranking_model("nvidia/llama-3.2-nv-rerankqa-1b-v2", "", 5) + model = get_ranking_model("nvidia/llama-nemotron-rerank-1b-v2", "", 5) # Test that the model can be used documents = ["doc1", "doc2"] diff --git a/uv.lock b/uv.lock index 7c267bf95..9e77bddac 100644 --- a/uv.lock +++ b/uv.lock @@ -1753,7 +1753,7 @@ wheels = [ [[package]] name = "nv-ingest-api" -version = "26.1.1" +version = "26.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backoff" }, @@ -1767,14 +1767,14 @@ dependencies = [ { name = "tritonclient" }, { name = "universal-pathlib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/eb/e0469e918d617175e1d3bcf952f0ca8e9b7756fce7817d5386ac4ddca154/nv_ingest_api-26.1.1.tar.gz", hash = "sha256:063d51f1d560bf03d7a595ff3ecebac1bffae45607cf6bd01e4fa8ca2265a884", size = 259532, upload-time = "2026-01-13T23:44:11.112Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/bd/e6e885cca94b89723468d4c32d52d30ccc0235ebe2f1db33b0605402d6b8/nv_ingest_api-26.1.2.tar.gz", hash = "sha256:fea08f9bda064938a5876f1610ef0b92c6a1e4943130c564f329b0c87efa3daf", size = 259604, upload-time = "2026-01-21T14:06:27.092Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/51/cd93750a1c5797d8d12e843bb13645b0930e4490f96ca49f458aeb641018/nv_ingest_api-26.1.1-py3-none-any.whl", hash = "sha256:e4f8b860765cedba72622782692e2ffc69a100fd61956e8b8a81a47e6c852d66", size = 357481, upload-time = "2026-01-13T23:44:07.943Z" }, + { url = "https://files.pythonhosted.org/packages/78/66/21e30e658578b7e5ab30857b99e9a0a5c91728ffdca13dadc3d3dba58b98/nv_ingest_api-26.1.2-py3-none-any.whl", hash = "sha256:8e7539a6b7d52afd821c0030e3197cfffddc011d26a8b093cf7b5ffa8addf02d", size = 357537, upload-time = "2026-01-21T14:06:24.321Z" }, ] [[package]] name = "nv-ingest-client" -version = "26.1.1" +version = "26.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "build" }, @@ -1789,9 +1789,9 @@ dependencies = [ { name = "setuptools" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/c4/ae5e2b00a8ffdfc1a3cf660ded68c188140ca433b22446adbb72ccfc455d/nv_ingest_client-26.1.1.tar.gz", hash = "sha256:26d6844eac946b4fdb8da2f5f1e77feb22b52ac21e6d772cf6b1c8c21cef4bb8", size = 126865, upload-time = "2026-01-13T23:44:15.615Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/62/386bc4a336b91df9c65afc18d905bb1fe3dd44ef1ce038895a701cba6035/nv_ingest_client-26.1.2.tar.gz", hash = "sha256:7ea4a35d4e7051031c273eb2b15170a0555462b702602e5c4fdce947bd39d446", size = 126061, upload-time = "2026-01-21T14:06:31.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/ea/042ad6d8ddfa887667159af8b473835bfcc5b9f51ba6e82ff14078e59a3e/nv_ingest_client-26.1.1-py3-none-any.whl", hash = "sha256:df9906c7021e6a1ae64140fbe7a345679b1cfad7dfa486442e38ca75d64f2b39", size = 147197, upload-time = "2026-01-13T23:44:12.761Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a0/6f082f42ba1ba4e3de751deee98099114405489389d82b840024fa26512e/nv_ingest_client-26.1.2-py3-none-any.whl", hash = "sha256:49ec81c2e2470509fc527d778d886caebe05a7f3012918eef2fd67922ea9b8b4", size = 146091, upload-time = "2026-01-21T14:06:28.586Z" }, ] [[package]] @@ -1924,10 +1924,10 @@ requires-dist = [ { name = "langchain-openai", marker = "extra == 'rag'", specifier = ">=0.2" }, { name = "lark", specifier = ">=1.2.2" }, { name = "minio", specifier = ">=7.2,<8.0" }, - { name = "nv-ingest-api", marker = "extra == 'all'", specifier = "==26.1.1" }, - { name = "nv-ingest-api", marker = "extra == 'ingest'", specifier = "==26.1.1" }, - { name = "nv-ingest-client", marker = "extra == 'all'", specifier = "==26.1.1" }, - { name = "nv-ingest-client", marker = "extra == 'ingest'", specifier = "==26.1.1" }, + { name = "nv-ingest-api", marker = "extra == 'all'", specifier = "==26.1.2" }, + { name = "nv-ingest-api", marker = "extra == 'ingest'", specifier = "==26.1.2" }, + { name = "nv-ingest-client", marker = "extra == 'all'", specifier = "==26.1.2" }, + { name = "nv-ingest-client", marker = "extra == 'ingest'", specifier = "==26.1.2" }, { name = "opentelemetry-api", marker = "extra == 'all'", specifier = ">=1.29,<2.0" }, { name = "opentelemetry-api", marker = "extra == 'ingest'", specifier = ">=1.29,<2.0" }, { name = "opentelemetry-api", marker = "extra == 'rag'", specifier = ">=1.29,<2.0" }, diff --git a/variables.env b/variables.env index 790e1ad7c..b9003e7b5 100644 --- a/variables.env +++ b/variables.env @@ -15,8 +15,8 @@ DOCKER_VOLUME_DIRECTORY=vectordb # ==== Endpoints for using on-prem NIMs ==== APP_LLM_SERVERURL=nim-llm:8000 -APP_EMBEDDINGS_SERVERURL=nemoretriever-embedding-ms:8000/v1 -APP_RANKING_SERVERURL=nemoretriever-ranking-ms:8000 +APP_EMBEDDINGS_SERVERURL=nemotron-embedding-ms:8000/v1 +APP_RANKING_SERVERURL=nemotron-ranking-ms:8000 OCR_GRPC_ENDPOINT=nemoretriever-ocr:8001 OCR_HTTP_ENDPOINT=http://nemoretriever-ocr:8000/v1/infer OCR_INFER_PROTOCOL=grpc @@ -35,11 +35,11 @@ YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=grpc # OCR_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr # OCR_INFER_PROTOCOL=http # OCR_MODEL_NAME=scene_text_ensemble -# YOLOX_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3 +# YOLOX_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3 # YOLOX_INFER_PROTOCOL=http -# YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-graphic-elements-v1 +# YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1 # YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL=http -# YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-table-structure-v1 +# YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1 # YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=http From a3c0438a00a15363b4cb0856a1ab896cd12fe459 Mon Sep 17 00:00:00 2001 From: kumar-punit Date: Fri, 27 Feb 2026 16:55:19 +0530 Subject: [PATCH 08/52] Preserve filename case in filter; fix syntax error examples to use double quotes (#337) --- docs/custom-metadata.md | 2 +- src/nvidia_rag/utils/metadata_validation.py | 19 ++++++++------- .../test_filter_validator.py | 24 +++++++++++++++++++ 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/docs/custom-metadata.md b/docs/custom-metadata.md index dba96e461..c02b80eeb 100644 --- a/docs/custom-metadata.md +++ b/docs/custom-metadata.md @@ -232,7 +232,7 @@ The system automatically manages certain metadata fields that are added to all c | Field Name | Type | Description | Auto-Populated | User Override | |------------|------|-------------|----------------|---------------| -| **`filename`** | `string` | Name of the uploaded file | ✅ RAG system | ✅ Yes - define in schema | +| **`filename`** | `string` | Name of the uploaded file (case-sensitive when filtering) | ✅ RAG system | ✅ Yes - define in schema | | **`page_number`** | `integer` | Page number where content appears (1-indexed) | ✅ nv-ingest | ✅ Yes - define in schema | | **`start_time`** | `integer` | Start timestamp in milliseconds for audio/video segments | ✅ nv-ingest | ✅ Yes - define in schema | | **`end_time`** | `integer` | End timestamp in milliseconds for audio/video segments | ✅ nv-ingest | ✅ Yes - define in schema | diff --git a/src/nvidia_rag/utils/metadata_validation.py b/src/nvidia_rag/utils/metadata_validation.py index e1bdef417..3ff404483 100644 --- a/src/nvidia_rag/utils/metadata_validation.py +++ b/src/nvidia_rag/utils/metadata_validation.py @@ -2164,7 +2164,10 @@ def comparison(self, args) -> str: logger.debug(f"[comparison] Failed to normalize datetime: {e}") value_val = str(value_token) elif field_info and is_string_type(field_info.type): - value_val = str(value_token).lower() + if field_name == "filename": + value_val = str(value_token) + else: + value_val = str(value_token).lower() else: value_val = str(value_token) else: @@ -2677,15 +2680,15 @@ def _get_error_context(filter_expr: str, error: UnexpectedInput) -> str: error_msg = ( f"Syntax error at line {line_num}, column {col_num}: '{snippet}'" ) - error_msg += "\n\nExamples of valid filter expressions:" - error_msg += "\n• content_metadata[\"title\"] == 'value'" - error_msg += "\n• content_metadata[\"title\"] = 'value'" + error_msg += "\n\nExamples of valid filter expressions (use double quotes for string values):" + error_msg += '\n• content_metadata["title"] == "value"' + error_msg += '\n• content_metadata["title"] = "value"' error_msg += '\n• content_metadata["rating"] > 5' - error_msg += "\n• content_metadata[\"category\"] like '%tech%'" - error_msg += "\n• content_metadata[\"tags\"] in ['important', 'urgent']" - error_msg += "\n• content_metadata[\"created_date\"] between '2024-01-01' and '2024-12-31'" + error_msg += '\n• content_metadata["category"] like "%tech%"' + error_msg += '\n• content_metadata["tags"] in ["important", "urgent"]' + error_msg += '\n• content_metadata["created_date"] between "2024-01-01" and "2024-12-31"' error_msg += '\n• content_metadata["is_public"] == true' - error_msg += '\n• content_metadata["file_size"] > 1000 and content_metadata["type"] == \'pdf\'' + error_msg += '\n• content_metadata["file_size"] > 1000 and content_metadata["type"] == "pdf"' return error_msg diff --git a/tests/unit/test_metadata_validation/test_filter_validator.py b/tests/unit/test_metadata_validation/test_filter_validator.py index 0bf174973..3f6b34759 100644 --- a/tests/unit/test_metadata_validation/test_filter_validator.py +++ b/tests/unit/test_metadata_validation/test_filter_validator.py @@ -3222,6 +3222,30 @@ def test_string_basic_operations(self, mock_config, string_schema): assert result["status"] is True assert "error_message" not in result + def test_filename_preserves_case_other_string_lowercased(self, mock_config): + """Filename filter preserves case; other string fields are lowercased for matching.""" + schema = MetadataSchema( + schema=[ + MetadataField(name="filename", type="string", required=False), + MetadataField(name="title", type="string", required=False), + ] + ) + parser = FilterExpressionParser(schema, mock_config) + + # Filename must preserve case (ingestion stores original case). + result = parser.process_filter_expression( + 'content_metadata["filename"] == "Report.PDF"' + ) + assert result["status"] is True + assert '"Report.PDF"' in result["processed_expression"] + + # Other string fields are normalized to lowercase. + result = parser.process_filter_expression( + 'content_metadata["title"] == "Technical"' + ) + assert result["status"] is True + assert '"technical"' in result["processed_expression"] + def test_string_like_operations(self, mock_config, string_schema): """Test string LIKE operations.""" parser = FilterExpressionParser(string_schema, mock_config) From 85d41b6ac63a71df1039708fe1ebea45aea8acc3 Mon Sep 17 00:00:00 2001 From: Kurt Heiss Date: Fri, 27 Feb 2026 09:42:57 -0800 Subject: [PATCH 09/52] conf.py fix (#391) * confirming presence of switcher text in conf.py file * docs: adjust conf.py for 2.5.0 --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 0bc42fd7b..6f95103b4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -74,8 +74,7 @@ "icon": "fa-brands fa-github", } ], - # Version switcher disabled: set "switcher": {"json_url": "...", "version_match": release} - # and ensure versions1.json is at the json_url path when using versioned doc deployments. + "switcher": {"json_url": "../versions1.json", "version_match": release}, "extra_head": { """ @@ -88,6 +87,7 @@ }, } + # Add any paths that contain custom static files (such as style sheets) here, html_css_files = ["swagger-nvidia.css"] From 8c4e6d4b2224058ef9467b73f1d364eda177328b Mon Sep 17 00:00:00 2001 From: kumar-punit Date: Mon, 2 Mar 2026 11:36:23 +0530 Subject: [PATCH 10/52] Added vdb serialization if parallel ingestion which helps in high concurrent batch ingestion having indexing issues (#389) --- src/nvidia_rag/ingestor_server/main.py | 28 +++- src/nvidia_rag/utils/vdb/vdb_ingest_base.py | 40 +++++- tests/unit/test_utils/test_configuration.py | 3 +- .../test_vdb/test_vdb_ingest_base.py | 129 ++++++++++++++++++ 4 files changed, 189 insertions(+), 11 deletions(-) create mode 100644 tests/unit/test_utils/test_vdb/test_vdb_ingest_base.py diff --git a/src/nvidia_rag/ingestor_server/main.py b/src/nvidia_rag/ingestor_server/main.py index f28750e38..88df2c957 100644 --- a/src/nvidia_rag/ingestor_server/main.py +++ b/src/nvidia_rag/ingestor_server/main.py @@ -94,6 +94,7 @@ from nvidia_rag.utils.summary_status_handler import SUMMARY_STATUS_HANDLER from nvidia_rag.utils.vdb import DEFAULT_DOCUMENT_INFO_COLLECTION, _get_vdb_op from nvidia_rag.utils.vdb.vdb_base import VDBRag +from nvidia_rag.utils.vdb.vdb_ingest_base import SerializedVDBWrapper # Initialize logger logger = logging.getLogger(__name__) @@ -752,10 +753,13 @@ async def __build_ingestion_response( uploaded_documents = [] for filepath in filepaths: if os.path.basename(filepath) not in failures_filepaths: - doc_type_counts, _, total_elements, raw_text_elements_size = ( - self._get_document_type_counts( - [filename_to_result_map.get(os.path.basename(filepath), [])] - ) + ( + doc_type_counts, + _, + total_elements, + raw_text_elements_size, + ) = self._get_document_type_counts( + [filename_to_result_map.get(os.path.basename(filepath), [])] ) document_info = create_document_metadata( @@ -2265,6 +2269,13 @@ async def __run_nvingest_batched_ingestion( logger.info( f"Processing batches in parallel with concurrency: {state_manager.concurrent_batches}" ) + + if vdb_op is not None and SerializedVDBWrapper is not None: + vdb_op = SerializedVDBWrapper(vdb_op) + logger.info( + "VDB write serialization enabled — extraction runs in parallel, VDB writes are sequential" + ) + all_results = [] all_failures = [] tasks = [] @@ -2850,9 +2861,12 @@ def _log_result_info( Returns: dict[str, Any]: Document info with metrics """ - doc_type_counts, total_documents, total_elements, raw_text_elements_size = ( - self._get_document_type_counts(results) - ) + ( + doc_type_counts, + total_documents, + total_elements, + raw_text_elements_size, + ) = self._get_document_type_counts(results) document_info = { "doc_type_counts": doc_type_counts, diff --git a/src/nvidia_rag/utils/vdb/vdb_ingest_base.py b/src/nvidia_rag/utils/vdb/vdb_ingest_base.py index 190d69ad4..bbbb61911 100644 --- a/src/nvidia_rag/utils/vdb/vdb_ingest_base.py +++ b/src/nvidia_rag/utils/vdb/vdb_ingest_base.py @@ -14,7 +14,8 @@ # limitations under the License. """ -This module provides VDBRagIngest, a VDBRag subclass with nv_ingest support. +This module provides VDBRagIngest, a VDBRag subclass with nv_ingest support, +and SerializedVDBWrapper for serializing concurrent VDB write operations. VDBRagIngest combines VDBRag (pure abstract base) with VDB from nv_ingest_client, providing full ingestion capabilities. This class should be used by ingestor_server @@ -25,6 +26,7 @@ """ import logging +import threading from nvidia_rag.utils.vdb.vdb_base import VDBRag @@ -52,6 +54,40 @@ class VDBRagIngest(VDBRag, VDB): pass + class SerializedVDBWrapper: + """Wraps a VDB op to serialize write operations while keeping reads parallel. + + When multiple batches run extraction concurrently, their VDB writes can + overlap and cause indexing timeouts (e.g., GPU_CAGRA JIT compilation takes + longer than the client's patience window). This wrapper uses a threading + lock to ensure only one batch writes to the VDB at a time. + """ + + def __init__(self, vdb_op): + self._vdb_op = vdb_op + self._write_lock = threading.Lock() + + def run_async(self, records): + with self._write_lock: + return self._vdb_op.run_async(records) + + def run(self, records): + with self._write_lock: + return self._vdb_op.run(records) + + def write_to_index(self, records, **kwargs): + with self._write_lock: + return self._vdb_op.write_to_index(records, **kwargs) + + def create_index(self, **kwargs): + with self._write_lock: + return self._vdb_op.create_index(**kwargs) + + def __getattr__(self, name): + return getattr(self._vdb_op, name) + + VDB.register(SerializedVDBWrapper) + except ImportError: logger.warning( "Optional nv_ingest_client module not installed. " @@ -59,4 +95,4 @@ class VDBRagIngest(VDBRag, VDB): ) # Fallback: VDBRagIngest is just VDBRag without nv_ingest support VDBRagIngest = VDBRag - + SerializedVDBWrapper = None diff --git a/tests/unit/test_utils/test_configuration.py b/tests/unit/test_utils/test_configuration.py index e865b7326..8c8f6651d 100644 --- a/tests/unit/test_utils/test_configuration.py +++ b/tests/unit/test_utils/test_configuration.py @@ -23,8 +23,6 @@ import pytest import yaml -from pydantic import SecretStr, ValidationError - from nvidia_rag.utils.configuration import ( EmbeddingConfig, FilterExpressionGeneratorConfig, @@ -44,6 +42,7 @@ VectorStoreConfig, VLMConfig, ) +from pydantic import SecretStr, ValidationError class TestVectorStoreConfig: diff --git a/tests/unit/test_utils/test_vdb/test_vdb_ingest_base.py b/tests/unit/test_utils/test_vdb/test_vdb_ingest_base.py new file mode 100644 index 000000000..a13877d05 --- /dev/null +++ b/tests/unit/test_utils/test_vdb/test_vdb_ingest_base.py @@ -0,0 +1,129 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for SerializedVDBWrapper from vdb_ingest_base module.""" + +import threading +from concurrent.futures import ThreadPoolExecutor +from unittest.mock import MagicMock + +import pytest +from nvidia_rag.utils.vdb.vdb_ingest_base import SerializedVDBWrapper + + +@pytest.fixture +def mock_vdb_op(): + """Create a mock VDB operation object.""" + op = MagicMock() + op.run_async.return_value = "run_async_result" + op.run.return_value = "run_result" + op.write_to_index.return_value = "write_result" + op.create_index.return_value = "index_result" + op.some_read_method.return_value = "read_result" + return op + + +@pytest.fixture +def wrapper(mock_vdb_op): + """Create a SerializedVDBWrapper around a mock VDB op.""" + return SerializedVDBWrapper(mock_vdb_op) + + +@pytest.mark.skipif( + SerializedVDBWrapper is None, + reason="nv_ingest_client not installed", +) +class TestSerializedVDBWrapper: + """Test cases for SerializedVDBWrapper.""" + + def test_run_async_delegates_to_wrapped_op(self, wrapper, mock_vdb_op): + """Test that run_async delegates to the wrapped VDB op.""" + records = [{"data": "test"}] + result = wrapper.run_async(records) + + mock_vdb_op.run_async.assert_called_once_with(records) + assert result == "run_async_result" + + def test_run_delegates_to_wrapped_op(self, wrapper, mock_vdb_op): + """Test that run delegates to the wrapped VDB op.""" + records = [{"data": "test"}] + result = wrapper.run(records) + + mock_vdb_op.run.assert_called_once_with(records) + assert result == "run_result" + + def test_write_to_index_delegates_with_kwargs(self, wrapper, mock_vdb_op): + """Test that write_to_index passes kwargs to the wrapped VDB op.""" + records = [{"data": "test"}] + result = wrapper.write_to_index(records, collection_name="test") + + mock_vdb_op.write_to_index.assert_called_once_with( + records, collection_name="test" + ) + assert result == "write_result" + + def test_create_index_delegates_with_kwargs(self, wrapper, mock_vdb_op): + """Test that create_index passes kwargs to the wrapped VDB op.""" + result = wrapper.create_index(collection_name="test") + + mock_vdb_op.create_index.assert_called_once_with(collection_name="test") + assert result == "index_result" + + def test_getattr_delegates_non_overridden_methods(self, wrapper, mock_vdb_op): + """Test that non-write methods pass through to the wrapped VDB op.""" + result = wrapper.some_read_method() + mock_vdb_op.some_read_method.assert_called_once() + assert result == "read_result" + + def test_isinstance_check_with_vdb(self, wrapper): + """Test that wrapper passes isinstance check for VDB (was a real bug).""" + from nv_ingest_client.util.vdb.adt_vdb import VDB + + assert isinstance(wrapper, VDB) + + def test_write_methods_are_serialized(self, mock_vdb_op): + """Test that concurrent write calls are serialized by the lock.""" + execution_log = [] + lock_held = threading.Event() + + def locked_write(records): + batch = records[0] + execution_log.append(f"acquired_{batch}") + if batch == "batch_1": + lock_held.set() + threading.Event().wait(0.1) + execution_log.append(f"released_{batch}") + return "done" + + mock_vdb_op.run.side_effect = locked_write + wrapper = SerializedVDBWrapper(mock_vdb_op) + + with ThreadPoolExecutor(max_workers=2) as executor: + f1 = executor.submit(wrapper.run, ["batch_1"]) + lock_held.wait(timeout=2) + f2 = executor.submit(wrapper.run, ["batch_2"]) + f1.result(timeout=5) + f2.result(timeout=5) + + assert execution_log.index("released_batch_1") < execution_log.index( + "acquired_batch_2" + ) + + def test_wrapper_propagates_exceptions(self, wrapper, mock_vdb_op): + """Test that exceptions from the wrapped op propagate through the lock.""" + mock_vdb_op.run.side_effect = ValueError("indexing failed") + + with pytest.raises(ValueError, match="indexing failed"): + wrapper.run([{"data": "test"}]) From c91de07e86e9c5d222cb1618b9355453c8409200 Mon Sep 17 00:00:00 2001 From: nv-pranjald <150428320+nv-pranjald@users.noreply.github.com> Date: Mon, 2 Mar 2026 11:59:15 +0530 Subject: [PATCH 11/52] Update prompt and unify reasoning budged and enable thinking (#386) * Prompt tuining, low reasoning and reasoning budget * Filter out think token when enable filter is on * Use default prompt * Fix unit test * Add doc for nemotron thinking budget * Add question back in prompt.yaml --- deploy/compose/docker-compose-rag-server.yaml | 10 +- deploy/helm/nvidia-blueprint-rag/values.yaml | 10 +- docs/accuracy_perf.md | 2 +- docs/enable-nemotron-thinking.md | 155 +++++---- src/nvidia_rag/rag_server/main.py | 16 +- src/nvidia_rag/utils/configuration.py | 22 +- src/nvidia_rag/utils/llm.py | 320 ++++++++++-------- .../test_rag_main_advanced_features.py | 12 +- tests/unit/test_utils/test_configuration.py | 12 +- tests/unit/test_utils/test_llm.py | 108 +++--- 10 files changed, 381 insertions(+), 286 deletions(-) diff --git a/deploy/compose/docker-compose-rag-server.yaml b/deploy/compose/docker-compose-rag-server.yaml index 9f7e59a12..d6bafd3ed 100644 --- a/deploy/compose/docker-compose-rag-server.yaml +++ b/deploy/compose/docker-compose-rag-server.yaml @@ -74,11 +74,11 @@ services: LLM_MAX_TOKENS: ${LLM_MAX_TOKENS:-32768} LLM_TEMPERATURE: ${LLM_TEMPERATURE:-0} LLM_TOP_P: ${LLM_TOP_P:-1.0} - - # Enable/disable thinking/reasoning for nemotron-3-nano models (30b variant) - # Set to "true" to enable reasoning mode with reasoning_budget - # Set to "false" to disable reasoning and get direct answers - ENABLE_NEMOTRON_3_NANO_THINKING: ${ENABLE_NEMOTRON_3_NANO_THINKING:-true} + + # Reasoning configuration (supported by Nemotron 3 and other reasoning models) + LLM_ENABLE_THINKING: ${LLM_ENABLE_THINKING:-false} + LLM_REASONING_BUDGET: ${LLM_REASONING_BUDGET:-0} + LLM_LOW_EFFORT: ${LLM_LOW_EFFORT:-false} ##===Query Rewriter Model specific configurations=== APP_QUERYREWRITER_MODELNAME: ${APP_QUERYREWRITER_MODELNAME:-"nvidia/llama-3.3-nemotron-super-49b-v1.5"} diff --git a/deploy/helm/nvidia-blueprint-rag/values.yaml b/deploy/helm/nvidia-blueprint-rag/values.yaml index b385b5296..00c0ecd1d 100644 --- a/deploy/helm/nvidia-blueprint-rag/values.yaml +++ b/deploy/helm/nvidia-blueprint-rag/values.yaml @@ -164,11 +164,6 @@ envVars: LLM_TEMPERATURE: "0" LLM_TOP_P: "1.0" - # Enable/disable thinking/reasoning for nemotron-3-nano models (30b variant) - # Set to "true" to enable reasoning mode with reasoning_budget - # Set to "false" to disable reasoning and get direct answers - ENABLE_NEMOTRON_3_NANO_THINKING: "true" - ##===Query Rewriter Model specific configurations=== APP_QUERYREWRITER_MODELNAME: "nvidia/llama-3.3-nemotron-super-49b-v1.5" # URL on which query rewriter model is hosted. If "", Nvidia hosted API is used @@ -260,6 +255,11 @@ envVars: # Whether to filter content within tags in model responses FILTER_THINK_TOKENS: "true" + # Reasoning configuration (supported by Nemotron 3 and other reasoning models) + LLM_ENABLE_THINKING: "false" + LLM_REASONING_BUDGET: "0" + LLM_LOW_EFFORT: "false" + NEMO_GUARDRAILS_URL: "nemo-guardrails:7331" # enable iterative query decomposition diff --git a/docs/accuracy_perf.md b/docs/accuracy_perf.md index 8367747ae..fa4f6b7a7 100644 --- a/docs/accuracy_perf.md +++ b/docs/accuracy_perf.md @@ -37,7 +37,7 @@ Change the setting if you want different behavior. | `ENABLE_REFLECTION` | `false` | Set to `true` to enable self-reflection. For details, refer to [Self-Reflection Support](self-reflection.md). | - Can improve the response quality by refining intermediate retrieval and final LLM output.
| - Significantly higher latency due to multiple iterations of LLM model call.
- You might need to deploy a separate judge LLM model, increasing GPU requirement.
| | `ENABLE_RERANKER` | `true` | Set to `true` to use the reranking model. | - Improves accuracy by selecting better documents for response generation.
| - Increases latency due to additional processing.
- Additional hardware requirements for self-hosted on premises deployment.
| | `ENABLE_VLM_INFERENCE` | `false` | Set to `true` to use the Vision-Language Model (VLM) for response generation. For details, refer to [VLM for Generation](vlm.md). | - Enables analysis of retrieved images alongside text for richer, multimodal responses.
- Can process up to 4 images per citation.
- Useful for document Q&A, visual search, and multimodal chatbots.
| - Requires additional GPU resources for VLM model deployment.
- Increases latency due to image processing.
| -| Reasoning in `llama-3.3-nemotron-super-49b-v1.5` | `/no_think` | Use `/think` to enable reasoning. For details, refer to [Enable Reasoning](enable-nemotron-thinking.md). | - Improves response quality through enhanced reasoning capabilities.
- Yields more precise responses. The default model is verbose and works best with reasoning enabled.
| - Can increase response latency due to additional thinking process.
- Can increase token usage and computational overhead.
| +| `LLM_ENABLE_THINKING` | `false` | Set to `true` to enable reasoning for Nemotron 3 models. Use `LLM_REASONING_BUDGET` and `LLM_LOW_EFFORT` for fine-grained control. For Nemotron 1.5 models, use the `/think` system prompt instead. For details, refer to [Enable Reasoning](enable-nemotron-thinking.md). | - Improves response quality through enhanced reasoning capabilities.
- Yields more precise responses.
| - Can increase response latency due to additional thinking process.
- Can increase token usage and computational overhead.
| | `RERANKER_SCORE_THRESHOLD` | `0.0` | Filters out retrieved chunks if reranker relevance is lower than this threshold. We recommend that you set this value between `0.3` and `0.5` to balance quality and coverage. For details, refer to [Use the Python Package](python-client.md). | - Faster retrieval by processing fewer documents.
- Can improve accuracy by excluding low-relevance documents.
| - Requires `ENABLE_RERANKER` set to `true` for effective filtering.
- Might filter out too many chunks if the threshold is set high, causing no response from the RAG server.
| | `RERANKER TOP K` | 10 | Increase `reranker TOP K` to increase the probability of relevant context being part of the top-k contexts. | Increasing the value can improve accuracy. | Increasing the value can increase latency. | | `VDB TOP K` | 100 | Increase `VDB TOP K` to provide a larger candidate pool for reranking. | Increasing the value can improve accuracy. | Increasing the value can increase latency. | diff --git a/docs/enable-nemotron-thinking.md b/docs/enable-nemotron-thinking.md index 182a09ea1..d9f9b5383 100644 --- a/docs/enable-nemotron-thinking.md +++ b/docs/enable-nemotron-thinking.md @@ -19,9 +19,102 @@ This guide explains how to enable reasoning for different Nemotron models, each | Model | Control Method | Thinking Budget Parameters | |-------|----------------|----------------------------| +| Nemotron 3 (Nano 30B, and others) | Environment variables | `LLM_ENABLE_THINKING`, `LLM_REASONING_BUDGET`, `LLM_LOW_EFFORT` | | Nemotron 1.5 | System prompts | None | | Nemotron-3-Nano 9B | System prompts | min/max thinking tokens | -| Nemotron-3-Nano 30B | Environment variable | max thinking tokens only | + +## Enable Reasoning for Nemotron 3 Models + +Nemotron 3 models (such as `nvidia/nemotron-3-nano-30b-a3b`) use environment variables to control reasoning. + +Set the following environment variables on the RAG server container (via Docker Compose, Helm values, or shell export): + +**`LLM_ENABLE_THINKING`** +: Enable or disable the reasoning phase. When `true`, the model emits reasoning tokens before the final answer. Default: `false`. + +**`LLM_REASONING_BUDGET`** +: Maximum number of tokens allocated for reasoning. Only used when `LLM_ENABLE_THINKING` is `true`. Default: `0`. + +**`LLM_LOW_EFFORT`** +: Low-effort reasoning mode for faster, cheaper responses with shorter reasoning. Only used when `LLM_ENABLE_THINKING` is `true`. Default: `false`. + +**`FILTER_THINK_TOKENS`** +: Filter content between `` and `` tags in model responses. Keep `true` for production to return only the final answer. Set `false` to see the full reasoning process. Default: `true`. + +## Enable Reasoning for Nemotron 3 Models + +Nemotron 3 models (such as `nvidia/nemotron-3-super-120b-a12b` and `nvidia/nemotron-3-nano-30b-a3b`) use environment variables to control reasoning. + +### Basic Configuration + +```bash +export LLM_ENABLE_THINKING=true +``` + +### Configure Reasoning Budget (Optional) + +Limit the number of reasoning tokens to control latency and cost: + +```bash +export LLM_ENABLE_THINKING=true +export LLM_REASONING_BUDGET=8192 +``` + +### Low-Effort Mode (Optional) + +For faster responses where deep reasoning is unnecessary: + +```bash +export LLM_ENABLE_THINKING=true +export LLM_LOW_EFFORT=true +``` + +### Configure Model Parameters + +After you enable reasoning, configure the model parameters for optimal reasoning performance: + +```bash +export LLM_TEMPERATURE=0.6 +export LLM_TOP_P=0.95 +``` + +### Nemotron-3-Nano 30B + +For `nvidia/nemotron-3-nano-30b-a3b`, reasoning is controlled with the same `LLM_ENABLE_THINKING` variable. The reasoning budget can be set with either `LLM_REASONING_BUDGET` or `LLM_MAX_THINKING_TOKENS`: + +```bash +export LLM_ENABLE_THINKING=true +export LLM_REASONING_BUDGET=8192 +``` + +The 30B model also supports a maximum thinking token limit directly in API requests: + +```json +{ + "model": "nvidia/nemotron-3-nano-30b-a3b", + "messages": [ + { + "role": "user", + "content": "What is the capital of France?" + } + ], + "max_thinking_tokens": 8192 +} +``` + +**Thinking budget parameters:** + +**`max_thinking_tokens`** +: Maximum number of reasoning tokens allowed before generating the final answer. + +:::{important} +The key differences for the 30B model are the following: + +- Uses only `max_thinking_tokens` (not `min_thinking_tokens`) +- Reasoning is available in the model output's `reasoning_content` field (not wrapped in `` tags) +- The `reasoning_content` field is present in the model output but isn't exposed in the generate API response +- No filtering is needed because reasoning is already separated from the final answer +::: ## Enable Reasoning for Nemotron 1.5 @@ -81,7 +174,7 @@ export FILTER_THINK_TOKENS=false For most production use cases, keep `FILTER_THINK_TOKENS=true` (default) to provide cleaner responses to end users. ::: -## Enable Reasoning for Nemotron-3-Nano 9B +## Enable Reasoning for Nemotron Nano 9B The `nvidia/nvidia-nemotron-nano-9b-v2` model uses system prompts to control reasoning similar to Nemotron 1.5. It also adds support for thinking budget parameters to control the extent of reasoning. @@ -132,63 +225,6 @@ The key differences for the 9B model are the following: - No filtering is needed because reasoning is already separated from the final answer ::: -## Enable Reasoning for Nemotron-3-Nano 30B - -The `nvidia/nemotron-3-nano-30b-a3b` model uses a different approach for reasoning control. Instead of system prompts, you control reasoning through an environment variable. - -### Enable Reasoning Through an Environment Variable - -Set the environment variable to enable or disable reasoning: - -```bash -# Enable reasoning (default) -export ENABLE_NEMOTRON_3_NANO_THINKING=true - -# Disable reasoning -export ENABLE_NEMOTRON_3_NANO_THINKING=false -``` - -### Configure Thinking Budget (Optional) - -The 30B model supports a maximum thinking token limit to control the reasoning phase: - -```json -{ - "model": "nvidia/nemotron-3-nano-30b-a3b", - "messages": [ - { - "role": "user", - "content": "What is the capital of France?" - } - ], - "max_thinking_tokens": 8192 -} -``` - -**Thinking budget parameters:** - -**`max_thinking_tokens`** -: Maximum number of reasoning tokens allowed before generating the final answer. - -:::{important} -The key differences for the 30B model are the following: - -- Uses only `max_thinking_tokens` (not `min_thinking_tokens`) -- Reasoning is available in the model output's `reasoning_content` field (not wrapped in `` tags) -- The `reasoning_content` field is present in the model output but isn't exposed in the generate API response -- No filtering is needed because reasoning is already separated from the final answer -::: - -### Model Naming - -Use the correct model name based on your deployment: - -**Locally deployed NIMs** -: `nvidia/nemotron-3-nano` - -**NVIDIA-hosted models** -: `nvidia/nemotron-3-nano-30b-a3b` - ## Deploy with Reasoning Enabled After you configure reasoning settings in `prompt.yaml` or environment variables, redeploy your services: @@ -220,6 +256,7 @@ Adjust the thinking budget based on your use case: - **Lower values (1024-4096)**: Faster responses for simpler questions - **Higher values (8192-16384)**: More thorough reasoning for complex queries +- **Low-effort mode**: Use `LLM_LOW_EFFORT=true` for fast, low-cost reasoning when deep thought is not required ::: ## Related Topics diff --git a/src/nvidia_rag/rag_server/main.py b/src/nvidia_rag/rag_server/main.py index 044120926..3e9ddf810 100644 --- a/src/nvidia_rag/rag_server/main.py +++ b/src/nvidia_rag/rag_server/main.py @@ -262,7 +262,9 @@ def __init__( # Load prompts and other utilities self.prompts = get_prompts(prompts) self.vdb_top_k = int(self.config.retriever.vdb_top_k) - self.StreamingFilterThinkParser = get_streaming_filter_think_parser_async() + self.StreamingFilterThinkParser = get_streaming_filter_think_parser_async( + enable_thinking=self.config.llm.parameters.enable_thinking + ) if self._init_errors: logger.warning( @@ -1368,18 +1370,6 @@ def _handle_prompt_processing( conversation_history = [] user_message = [] - is_nemotron_v1 = str(model).endswith("llama-3.3-nemotron-super-49b-v1") - - # Nemotron controls thinking using system prompt, if nemotron v1 model is used update system prompt to enable/disable think - if is_nemotron_v1: - logger.info("Nemotron v1 model detected, updating system prompt") - if os.environ.get("ENABLE_NEMOTRON_THINKING", "false").lower() == "true": - logger.info("Setting system prompt as detailed thinking on") - system_prompt = "detailed thinking on" - else: - logger.info("Setting system prompt as detailed thinking off") - system_prompt = "detailed thinking off" - # Process chat history for message in chat_history: # Overwrite system message if provided in conversation history diff --git a/src/nvidia_rag/utils/configuration.py b/src/nvidia_rag/utils/configuration.py index 853fe20d7..3eb738ea0 100644 --- a/src/nvidia_rag/utils/configuration.py +++ b/src/nvidia_rag/utils/configuration.py @@ -412,15 +412,30 @@ class ModelParametersConfig(_ConfigBase): env="LLM_MIN_TOKENS", description="Minimum number of tokens to generate in response", ) + enable_thinking: bool = Field( + default=False, + env="LLM_ENABLE_THINKING", + description="Enable reasoning/thinking mode. Model emits reasoning tokens before the final answer.", + ) + reasoning_budget: int = Field( + default=0, + env="LLM_REASONING_BUDGET", + description="Token budget for reasoning (0 = no budget, model decides depth). Only used when enable_thinking is true.", + ) + low_effort: bool = Field( + default=False, + env="LLM_LOW_EFFORT", + description="Low-effort reasoning mode for faster, cheaper responses with shorter reasoning. Only used when enable_thinking is true.", + ) max_thinking_tokens: int = Field( default=0, env="LLM_MAX_THINKING_TOKENS", - description="Maximum thinking tokens to allocate for reasoning models (0 = disabled by default)", + description="Maximum thinking tokens for reasoning models. Used directly by nemotron-nano-9b-v2; for other models acts as an alternative to reasoning_budget (0 = disabled).", ) min_thinking_tokens: int = Field( default=0, env="LLM_MIN_THINKING_TOKENS", - description="Minimum thinking tokens to allocate for reasoning models (0 = disabled by default)", + description="Minimum thinking tokens for reasoning models. Only used by nemotron-nano-9b-v2 (0 = disabled).", ) ignore_eos: bool = Field( default=False, @@ -502,6 +517,9 @@ def get_model_parameters(self) -> dict: "min_tokens": self.parameters.min_tokens, "ignore_eos": self.parameters.ignore_eos, "max_tokens": self.parameters.max_tokens, + "enable_thinking": self.parameters.enable_thinking, + "reasoning_budget": self.parameters.reasoning_budget, + "low_effort": self.parameters.low_effort, "min_thinking_tokens": self.parameters.min_thinking_tokens, "max_thinking_tokens": self.parameters.max_thinking_tokens, "temperature": self.parameters.temperature, diff --git a/src/nvidia_rag/utils/llm.py b/src/nvidia_rag/utils/llm.py index 639c87d70..0148f3ce2 100644 --- a/src/nvidia_rag/utils/llm.py +++ b/src/nvidia_rag/utils/llm.py @@ -33,6 +33,7 @@ import yaml from langchain_core.language_models.llms import LLM from langchain_core.language_models.chat_models import SimpleChatModel +from langchain_core.messages import AIMessageChunk from langchain_nvidia_ai_endpoints import ChatNVIDIA from nvidia_rag.rag_server.response_generator import APIError, ErrorCodeMapping @@ -128,117 +129,112 @@ def _is_nvidia_endpoint(url: str | None) -> bool: return True -def _bind_thinking_tokens_if_configured( - llm: LLM | SimpleChatModel, **kwargs -) -> LLM | SimpleChatModel: - """ - If min_thinking_tokens or max_thinking_tokens are > 0 in kwargs, bind them to the LLM. - - Supports multiple reasoning/thinking model variants: - - 1. nvidia/nvidia-nemotron-nano-9b-v2: - - Uses min_thinking_tokens and max_thinking_tokens parameters - - Reasoning content is not available for this model - - 2. nemotron-3-nano variants (nemotron-3-nano-30b-a3b, nvidia/nemotron-3-nano): - - Uses reasoning_budget parameter (mapped from max_thinking_tokens) - - reasoning_budget is ONLY set when enable_thinking is true - - Outputs reasoning in a separate 'reasoning_content' field (not in content) - - Does NOT use tags - - Can be controlled via ENABLE_NEMOTRON_3_NANO_THINKING env var - - Raises: - ValueError: If min_thinking_tokens or max_thinking_tokens is passed but model - is not a supported Nemotron thinking model, or if any of these - parameters have invalid values (0 or negative). - """ - min_think = kwargs.get("min_thinking_tokens", None) - max_think = kwargs.get("max_thinking_tokens", None) - model = kwargs.get("model", None) +def _is_nemotron_3(model: str | None) -> bool: + """Detect Nemotron 3 model variants by checking for 'nemotron-3' in the model name.""" + if not model: + return False + return "nemotron-3" in model.lower() - # Validate model compatibility for thinking tokens - has_thinking_tokens = (min_think is not None and min_think > 0) or ( - max_think is not None and max_think > 0 - ) - if not has_thinking_tokens: - return llm +def _is_nemotron_3_nano(model: str | None) -> bool: + """Detect Nemotron 3 Nano models (30b-a3b and locally hosted variants).""" + if not model: + return False + m = model.lower() + return "nemotron-3-nano" in m - # Check if model is a supported reasoning model (various name formats) - # Note: For locally hosted models, use "nvidia/nemotron-3-nano" - # For NVIDIA-hosted models, use "nvidia/nemotron-3-nano-30b-a3b" - is_nano_9b_v2 = model and "nvidia/nvidia-nemotron-nano-9b-v2" in model - is_nemotron_3_nano = model and ( - "nemotron-3-nano" in model.lower() or - "nvidia/nemotron-3-nano" in model or - "nemotron-3-nano-30b-a3b" in model - ) - - if has_thinking_tokens and not (is_nano_9b_v2 or is_nemotron_3_nano): - raise ValueError( - "min_thinking_tokens and max_thinking_tokens are only supported for models " - "'nvidia/nvidia-nemotron-nano-9b-v2' and nemotron-3-nano variants " - "(e.g., 'nemotron-3-nano-30b-a3b', 'nvidia/nemotron-3-nano'), " - f"but got model '{model}'" - ) - bind_args = {} - if is_nano_9b_v2: - # nvidia/nvidia-nemotron-nano-9b-v2: Uses thinking token parameters directly - if min_think is not None and min_think > 0: - bind_args["min_thinking_tokens"] = min_think - else: - raise ValueError( - f"min_thinking_tokens must be a positive integer, but got {min_think}" - ) - if max_think is not None and max_think > 0: - bind_args["max_thinking_tokens"] = max_think - else: - raise ValueError( - f"max_thinking_tokens must be a positive integer, but got {max_think}" - ) - logger.info( - "nvidia-nemotron-nano-9b-v2: Setting min_thinking_tokens=%d, max_thinking_tokens=%d", - min_think, max_think +def _is_nemotron_nano_9b_v2(model: str | None) -> bool: + """Detect legacy Nemotron Nano 9B v2.""" + if not model: + return False + return "nvidia/nvidia-nemotron-nano-9b-v2" in model + + +def _resolve_enable_thinking(config: NvidiaRAGConfig | None = None, **kwargs) -> bool: + """Resolve enable_thinking from config, kwargs, or deprecated env var fallback.""" + if config is not None: + enable = config.llm.parameters.enable_thinking + if enable: + return True + enable = kwargs.get("enable_thinking", False) + if enable: + return True + deprecated = os.getenv("ENABLE_NEMOTRON_3_NANO_THINKING") + if deprecated is not None: + logger.warning( + "ENABLE_NEMOTRON_3_NANO_THINKING is deprecated, use LLM_ENABLE_THINKING instead" ) - elif is_nemotron_3_nano: - enable_thinking = os.getenv("ENABLE_NEMOTRON_3_NANO_THINKING", "true").lower() == "true" - if not enable_thinking: - raise ValueError( - "ENABLE_NEMOTRON_3_NANO_THINKING must be set to 'true' to use reasoning budget" - ) + return deprecated.lower() == "true" + return False - # For nemotron-3-nano variants, min_thinking_tokens is not supported - if min_think is not None and min_think > 0: - logger.warning( - "min_thinking_tokens is not supported for nemotron-3-nano variants, " - "only max_thinking_tokens (mapped to reasoning_budget or nvext) is supported" - ) - if max_think is not None and max_think > 0: - # Check if llm_endpoint is provided (locally hosted model) - llm_endpoint = kwargs.get("llm_endpoint", None) +def _bind_reasoning_config( + llm: LLM | SimpleChatModel, config: NvidiaRAGConfig | None = None, **kwargs +) -> LLM | SimpleChatModel: + """ + Bind reasoning parameters to the LLM based on model type and configuration. + + Reads enable_thinking, reasoning_budget, and low_effort from the config + object (LLM_ENABLE_THINKING, LLM_REASONING_BUDGET, LLM_LOW_EFFORT env vars). + kwargs can still override these for backward compatibility. + + Supports: + - Nemotron 3 variants: enable_thinking, reasoning_budget, low_effort via chat_template_kwargs + - Nemotron 3 Nano: enable_thinking + reasoning_budget (or nvext for local NIM) + - Nemotron Nano 9B v2: legacy min_thinking_tokens / max_thinking_tokens + - Other models: no reasoning features bound + """ + model = kwargs.get("model", "") + enable_thinking = _resolve_enable_thinking(config=config, **kwargs) + params = config.llm.parameters if config is not None else None + reasoning_budget = kwargs.get("reasoning_budget") or (params.reasoning_budget if params else 0) + low_effort = kwargs.get("low_effort") or (params.low_effort if params else False) + min_think = kwargs.get("min_thinking_tokens") or (params.min_thinking_tokens if params else 0) or 0 + max_think = kwargs.get("max_thinking_tokens") or (params.max_thinking_tokens if params else 0) or 0 + + # Check specific variants first, then fall through to the general nemotron-3 check + + if _is_nemotron_3_nano(model): + llm = llm.bind(chat_template_kwargs={"enable_thinking": enable_thinking}) + if enable_thinking and (reasoning_budget > 0 or max_think > 0): + budget = reasoning_budget if reasoning_budget > 0 else max_think + llm_endpoint = kwargs.get("llm_endpoint", "") if llm_endpoint: - # For locally hosted models, use nvext syntax - bind_args["nvext"] = {"max_thinking_tokens": max_think} - logger.info( - "nemotron-3-nano (locally hosted): Setting max_thinking_tokens=%d via nvext", - max_think - ) + llm = llm.bind(nvext={"max_thinking_tokens": budget}) + logger.info("nemotron-3-nano (local): enable_thinking=%s, nvext.max_thinking_tokens=%d", enable_thinking, budget) else: - # For API catalog models, use reasoning_budget - bind_args["reasoning_budget"] = max_think - logger.info( - "nemotron-3-nano (API catalog): Setting reasoning_budget=%d", - max_think - ) + llm = llm.bind(reasoning_budget=budget) + logger.info("nemotron-3-nano (API): enable_thinking=%s, reasoning_budget=%d", enable_thinking, budget) else: + logger.info("nemotron-3-nano: enable_thinking=%s", enable_thinking) + return llm + + if _is_nemotron_nano_9b_v2(model): + if min_think > 0 and max_think > 0: + llm = llm.bind(min_thinking_tokens=min_think, max_thinking_tokens=max_think) + logger.info("nemotron-nano-9b-v2: min_thinking_tokens=%d, max_thinking_tokens=%d", min_think, max_think) + elif min_think > 0 or max_think > 0: raise ValueError( - f"max_thinking_tokens must be a positive integer, but got {max_think}" + "nemotron-nano-9b-v2 requires both min_thinking_tokens and max_thinking_tokens " + f"to be positive, got min={min_think}, max={max_think}" ) + return llm + + if _is_nemotron_3(model): + template_kwargs: dict = {"enable_thinking": enable_thinking} + if enable_thinking and low_effort: + template_kwargs["low_effort"] = True + budget = reasoning_budget if reasoning_budget > 0 else max_think + if enable_thinking and budget > 0: + template_kwargs["reasoning_budget"] = budget + llm = llm.bind(chat_template_kwargs=template_kwargs) + logger.info( + "nemotron-3: enable_thinking=%s, reasoning_budget=%d, low_effort=%s", + enable_thinking, budget, low_effort, + ) + return llm - if bind_args: - return llm.bind(**bind_args) return llm @@ -289,16 +285,18 @@ def get_llm(config: NvidiaRAGConfig | None = None, **kwargs) -> LLM | SimpleChat default_headers = {**NVIDIA_API_DEFAULT_HEADERS} if api_key: default_headers["X-Model-Authorization"] = api_key - return ChatOpenAI( - model_name=kwargs.get("model"), - openai_api_base=f"{guardrails_url}/v1/guardrail", - openai_api_key="dummy-value", - default_headers=default_headers, - temperature=kwargs.get("temperature", None), - top_p=kwargs.get("top_p", None), - max_tokens=kwargs.get("max_tokens", None), - stop=kwargs.get("stop", []), - ) + openai_kwargs = { + "model_name": kwargs.get("model"), + "openai_api_base": f"{guardrails_url}/v1/guardrail", + "openai_api_key": "dummy-value", + "default_headers": default_headers, + "temperature": kwargs.get("temperature", None), + "top_p": kwargs.get("top_p", None), + "max_tokens": kwargs.get("max_tokens", None), + } + if kwargs.get("stop"): + openai_kwargs["stop"] = kwargs["stop"] + return ChatOpenAI(**openai_kwargs) except (requests.RequestException, requests.ConnectionError) as e: error_msg = f"Guardrails NIM unavailable at {guardrails_url}. Please verify the service is running and accessible." logger.exception( @@ -318,13 +316,15 @@ def get_llm(config: NvidiaRAGConfig | None = None, **kwargs) -> LLM | SimpleChat # Build kwargs dict, only including parameters that are set # For non-NVIDIA endpoints, exclude NVIDIA-specific parameters + # Do not pass stop=[] - some Nemotron 3 APIs reject empty stop arrays chat_nvidia_kwargs = { "base_url": url, "model": kwargs.get("model"), "api_key": api_key, - "stop": kwargs.get("stop", []), "default_headers": NVIDIA_API_DEFAULT_HEADERS, } + if kwargs.get("stop"): + chat_nvidia_kwargs["stop"] = kwargs["stop"] if kwargs.get("temperature") is not None: chat_nvidia_kwargs["temperature"] = kwargs["temperature"] if kwargs.get("top_p") is not None: @@ -342,15 +342,8 @@ def get_llm(config: NvidiaRAGConfig | None = None, **kwargs) -> LLM | SimpleChat chat_nvidia_kwargs["model_kwargs"] = model_kwargs llm = ChatNVIDIA(**chat_nvidia_kwargs) - # Only bind thinking tokens for NVIDIA endpoints if is_nvidia: - llm = _bind_thinking_tokens_if_configured(llm, **kwargs) - # For nemotron-3-nano models, set enable_thinking from env var - model = kwargs.get("model") - if model and ("nemotron-3-nano" in model.lower() or "nvidia/nemotron-3-nano" in model or "nemotron-3-nano-30b-a3b" in model): - enable_thinking = os.getenv("ENABLE_NEMOTRON_3_NANO_THINKING", "true").lower() == "true" - llm = llm.bind(chat_template_kwargs={"enable_thinking": enable_thinking}) - logger.info("nemotron-3-nano: Setting enable_thinking=%s (from ENABLE_NEMOTRON_3_NANO_THINKING)", enable_thinking) + llm = _bind_reasoning_config(llm, config=config, **kwargs) return llm logger.debug("Using llm model %s from api catalog", kwargs.get("model")) @@ -363,23 +356,20 @@ def get_llm(config: NvidiaRAGConfig | None = None, **kwargs) -> LLM | SimpleChat if kwargs.get("ignore_eos") is not None: model_kwargs["ignore_eos"] = kwargs["ignore_eos"] - llm = ChatNVIDIA( - model=kwargs.get("model"), - api_key=api_key, - temperature=kwargs.get("temperature", None), - top_p=kwargs.get("top_p", None), - max_completion_tokens=kwargs.get("max_tokens", None), - stop=kwargs.get("stop", []), - default_headers=NVIDIA_API_DEFAULT_HEADERS, + # Do not pass stop=[] - some Nemotron 3 APIs reject empty stop arrays + chat_nvidia_kwargs = { + "model": kwargs.get("model"), + "api_key": api_key, + "temperature": kwargs.get("temperature", None), + "top_p": kwargs.get("top_p", None), + "max_completion_tokens": kwargs.get("max_tokens", None), + "default_headers": NVIDIA_API_DEFAULT_HEADERS, **({"model_kwargs": model_kwargs} if model_kwargs else {}), - ) - llm = _bind_thinking_tokens_if_configured(llm, **kwargs) - # For nemotron-3-nano models, set enable_thinking from env var - model = kwargs.get("model") - if model and ("nemotron-3-nano" in model.lower() or "nvidia/nemotron-3-nano" in model or "nemotron-3-nano-30b-a3b" in model): - enable_thinking = os.getenv("ENABLE_NEMOTRON_3_NANO_THINKING", "true").lower() == "true" - llm = llm.bind(chat_template_kwargs={"enable_thinking": enable_thinking}) - logger.info("nemotron-3-nano: Setting enable_thinking=%s (from ENABLE_NEMOTRON_3_NANO_THINKING)", enable_thinking) + } + if kwargs.get("stop"): + chat_nvidia_kwargs["stop"] = kwargs["stop"] + llm = ChatNVIDIA(**chat_nvidia_kwargs) + llm = _bind_reasoning_config(llm, config=config, **kwargs) return llm raise RuntimeError( @@ -477,7 +467,8 @@ def streaming_filter_think(chunks: Iterable[str]) -> Iterable[str]: chunk_count = 0 for chunk in chunks: - content = chunk.content + reasoning, content = extract_reasoning_and_content(chunk) + content = content or reasoning chunk_count += 1 # Let's first check for full tags - this is the most reliable approach @@ -611,14 +602,20 @@ def get_streaming_filter_think_parser(): return RunnablePassthrough() -async def streaming_filter_think_async(chunks): +async def streaming_filter_think_async(chunks, enable_thinking: bool = False): """ Async version of streaming_filter_think. This async generator filters content between think tags in streaming LLM responses. It handles both complete tags in a single chunk and tags split across multiple tokens. + When enable_thinking is True and the model uses a separate reasoning_content field + (e.g. Nemotron 3), reasoning tokens are dropped and only content is forwarded. + The tag filter still runs to handle models that embed reasoning in content. + Args: chunks: Async iterable of chunks from a streaming LLM response + enable_thinking: When True, drop reasoning_content (genuine chain-of-thought). + When False, fall back to reasoning_content if content is empty (model quirk). Yields: str: Filtered content with think blocks removed @@ -644,7 +641,8 @@ async def streaming_filter_think_async(chunks): chunk_count = 0 async for chunk in chunks: - content = chunk.content + reasoning, content = extract_reasoning_and_content(chunk) + content = content if enable_thinking else (content or reasoning) chunk_count += 1 # Let's first check for full tags - this is the most reliable approach @@ -753,27 +751,61 @@ async def streaming_filter_think_async(chunks): ) -def get_streaming_filter_think_parser_async(): +async def _content_fallback_async(chunks, enable_thinking: bool = False): + """ + Pass through LLM chunks WITHOUT filtering thinking tokens. + Used when FILTER_THINK_TOKENS=false - the user wants to see everything. + + - When enable_thinking=true: forwards both reasoning_content and content so + the user can see the chain-of-thought followed by the answer. + - When enable_thinking=false: falls back to reasoning_content if content is + empty (NIM quirk where the answer lands in reasoning_content). + + Args: + chunks: Async iterable of LLM response chunks + enable_thinking: Whether the model is producing genuine reasoning tokens. + """ + async for chunk in chunks: + reasoning, content = extract_reasoning_and_content(chunk) + + if enable_thinking: + if reasoning: + yield AIMessageChunk(content=reasoning) + if content: + yield AIMessageChunk(content=content) + else: + text = content or reasoning + if text: + yield AIMessageChunk(content=text) + + +def get_streaming_filter_think_parser_async(enable_thinking: bool = False): """ Creates and returns an async RunnableGenerator for filtering think tokens. If FILTER_THINK_TOKENS environment variable is set to "true" (case-insensitive), returns a parser that filters out content between and tags. - Otherwise, returns a pass-through parser that doesn't modify the content. + Otherwise, returns a parser that normalizes content (content or reasoning_content) + so models like Nemotron 3 that put reply in reasoning_content still yield text. + + Args: + enable_thinking: When True, reasoning_content is genuine chain-of-thought and + will be dropped. When False, reasoning_content is used as a fallback if + content is empty (workaround for model quirk). Returns: - RunnableGenerator: An async parser for filtering (or not filtering) think tokens + RunnableGenerator: An async parser for filtering or content normalization """ + from functools import partial from langchain_core.runnables import RunnableGenerator, RunnablePassthrough # Check environment variable filter_enabled = os.getenv("FILTER_THINK_TOKENS", "true").lower() == "true" if filter_enabled: - logger.info("Think token filtering is enabled (async)") - return RunnableGenerator(streaming_filter_think_async) + logger.info("Think token filtering is enabled (async), enable_thinking=%s", enable_thinking) + return RunnableGenerator(partial(streaming_filter_think_async, enable_thinking=enable_thinking)) else: - logger.info("Think token filtering is disabled (async)") - # If filtering is disabled, use a passthrough that passes content as-is - return RunnablePassthrough() + logger.info("Think token filtering is disabled (async), enable_thinking=%s", enable_thinking) + return RunnableGenerator(partial(_content_fallback_async, enable_thinking=enable_thinking)) \ No newline at end of file diff --git a/tests/unit/test_rag_server/test_rag_main_advanced_features.py b/tests/unit/test_rag_server/test_rag_main_advanced_features.py index 4331586a7..30f4ace20 100644 --- a/tests/unit/test_rag_server/test_rag_main_advanced_features.py +++ b/tests/unit/test_rag_server/test_rag_main_advanced_features.py @@ -1192,26 +1192,20 @@ def test_handle_prompt_processing_basic(self): assert user_message == [("user", "Test human prompt")] def test_handle_prompt_processing_with_nemotron_v1_model(self): - """Test prompt processing with Nemotron v1 model.""" + """Test prompt processing with Nemotron v1 model uses the chat_template system prompt.""" rag = NvidiaRAG() chat_history = [] - # Mock instance attribute - mock_prompts = Mock() with patch.dict(os.environ, {"ENABLE_NEMOTRON_THINKING": "true"}): - mock_prompts.get.return_value = { - "system": "Test system prompt", - "human": "Test human prompt", - } - result = rag._handle_prompt_processing( chat_history, "llama-3.3-nemotron-super-49b-v1", "chat_template" ) assert len(result) == 3 system_message, conversation_history, user_message = result - assert system_message == [("system", "detailed thinking on")] + expected_system = rag.prompts.get("chat_template", {}).get("system", "") + assert system_message == [("system", expected_system)] def test_handle_prompt_processing_with_system_message_in_history(self): """Test prompt processing with system message in chat history.""" diff --git a/tests/unit/test_utils/test_configuration.py b/tests/unit/test_utils/test_configuration.py index 8c8f6651d..04c81ba9d 100644 --- a/tests/unit/test_utils/test_configuration.py +++ b/tests/unit/test_utils/test_configuration.py @@ -134,14 +134,16 @@ def test_get_model_parameters_default(self): config = LLMConfig() params = config.get_model_parameters() - # Default model contains "llama-3.3-nemotron-super-49b" so it triggers nemotron logic expected = { "min_tokens": 0, "ignore_eos": False, "max_tokens": 32768, + "enable_thinking": False, + "reasoning_budget": 0, + "low_effort": False, "min_thinking_tokens": 0, "max_thinking_tokens": 0, - "temperature": 0, + "temperature": 0.0, "top_p": 1.0, } assert params == expected @@ -151,14 +153,16 @@ def test_get_model_parameters_generic(self): config = LLMConfig(model_name="meta/llama-3.1-8b-instruct") params = config.get_model_parameters() - # Generic model should use the base parameter values expected = { "min_tokens": 0, "ignore_eos": False, "max_tokens": 32768, + "enable_thinking": False, + "reasoning_budget": 0, + "low_effort": False, "min_thinking_tokens": 0, "max_thinking_tokens": 0, - "temperature": 0, + "temperature": 0.0, "top_p": 1.0, } assert params == expected diff --git a/tests/unit/test_utils/test_llm.py b/tests/unit/test_utils/test_llm.py index f691edb9d..377f0b68d 100644 --- a/tests/unit/test_utils/test_llm.py +++ b/tests/unit/test_utils/test_llm.py @@ -240,7 +240,6 @@ def test_get_llm_nvidia_endpoints_with_url(self, mock_chatnvidia, mock_sanitize) base_url="http://test-url:8000", model="test-model", api_key="test-api-key", - stop=[], default_headers={"source": "rag-blueprint"}, temperature=0.7, top_p=0.9, @@ -272,7 +271,6 @@ def test_get_llm_nvidia_endpoints_api_catalog(self, mock_chatnvidia, mock_saniti temperature=None, top_p=None, max_completion_tokens=None, - stop=[], default_headers={"source": "rag-blueprint"}, ) @@ -325,7 +323,6 @@ def test_get_llm_with_guardrails_success( temperature=0.7, top_p=None, max_tokens=None, - stop=[], ) @patch("requests.get") @@ -419,7 +416,6 @@ def test_get_llm_none_parameters(self, mock_sanitize): temperature=None, top_p=None, max_completion_tokens=None, - stop=[], default_headers={"source": "rag-blueprint"}, model_kwargs={"ignore_eos": False}, ) @@ -432,6 +428,7 @@ def create_mock_chunk(self, content): """Helper to create mock chunk with content attribute.""" chunk = Mock() chunk.content = content + chunk.additional_kwargs = {} return chunk def test_streaming_filter_think_no_tags(self): @@ -683,7 +680,6 @@ def test_llm_creation_with_all_parameters(self, mock_chatnvidia): base_url="http://test:8000", model="meta/llama-3.1-8b-instruct", api_key="test-api-key", - stop=[], default_headers={"source": "rag-blueprint"}, temperature=0.7, top_p=0.9, @@ -829,64 +825,80 @@ def create_mock_chunk(self, content): """Helper to create mock chunk with content attribute.""" chunk = Mock() chunk.content = content + chunk.additional_kwargs = {} return chunk -class TestThinkingBudgetNemotron3Nano30B: - """Tests for thinking budget behavior with nvidia/nemotron-3-nano-30b-a3b.""" +class TestBindReasoningConfigNemotron3Nano: + """Tests for _bind_reasoning_config with nemotron-3-nano models.""" - @patch.dict(os.environ, {"ENABLE_NEMOTRON_3_NANO_THINKING": "true"}) - def test_bind_thinking_tokens_for_nemotron_30b_maps_reasoning_budget(self): - """max_thinking_tokens for nemotron-3-nano-30b-a3b maps to reasoning_budget.""" - from nvidia_rag.utils.llm import _bind_thinking_tokens_if_configured + @patch.dict(os.environ, {"LLM_ENABLE_THINKING": "true"}) + def test_bind_reasoning_config_nemotron_3_nano_with_budget(self): + """enable_thinking + reasoning_budget for nemotron-3-nano binds chat_template_kwargs and reasoning_budget.""" + from nvidia_rag.utils.llm import _bind_reasoning_config mock_llm = Mock() - bound_llm = _bind_thinking_tokens_if_configured( + mock_llm.bind.return_value = mock_llm + config = Mock() + config.llm.parameters.enable_thinking = True + config.llm.parameters.reasoning_budget = 8192 + config.llm.parameters.low_effort = False + config.llm.parameters.min_thinking_tokens = 0 + config.llm.parameters.max_thinking_tokens = 0 + + bound_llm = _bind_reasoning_config( mock_llm, + config=config, model="nvidia/nemotron-3-nano-30b-a3b", - max_thinking_tokens=8192, ) - mock_llm.bind.assert_called_once_with( - reasoning_budget=8192, + calls = mock_llm.bind.call_args_list + assert any( + call.kwargs.get("chat_template_kwargs", {}).get("enable_thinking") is True + for call in calls ) - assert bound_llm is mock_llm.bind.return_value - def test_min_thinking_tokens_alone_raises_for_nemotron_30b(self): - """min_thinking_tokens alone raises ValueError for nemotron-3-nano-30b-a3b (max_thinking_tokens required).""" - from nvidia_rag.utils.llm import _bind_thinking_tokens_if_configured + def test_bind_reasoning_config_unsupported_model_returns_original(self): + """Unsupported model returns original LLM without binding.""" + from nvidia_rag.utils.llm import _bind_reasoning_config mock_llm = Mock() - with pytest.raises(ValueError, match="max_thinking_tokens must be a positive integer"): - _bind_thinking_tokens_if_configured( - mock_llm, - model="nvidia/nemotron-3-nano-30b-a3b", - min_thinking_tokens=1, - ) - - def test_thinking_tokens_unsupported_model_raises(self): - """Using thinking tokens with unsupported model raises ValueError.""" - from nvidia_rag.utils.llm import _bind_thinking_tokens_if_configured + config = Mock() + config.llm.parameters.enable_thinking = False + config.llm.parameters.reasoning_budget = 0 + config.llm.parameters.low_effort = False + config.llm.parameters.min_thinking_tokens = 0 + config.llm.parameters.max_thinking_tokens = 0 + + bound_llm = _bind_reasoning_config( + mock_llm, + config=config, + model="meta/llama-3.1-8b-instruct", + ) - mock_llm = Mock() - with pytest.raises(ValueError): - _bind_thinking_tokens_if_configured( - mock_llm, - model="meta/llama-3.1-8b-instruct", - max_thinking_tokens=10, - ) + mock_llm.bind.assert_not_called() + assert bound_llm is mock_llm -class TestThinkingBudgetNemotronNano9B: - """Tests for thinking budget behavior with nvidia/nvidia-nemotron-nano-9b-v2.""" +class TestBindReasoningConfigNemotronNano9B: + """Tests for _bind_reasoning_config with nvidia/nvidia-nemotron-nano-9b-v2.""" - def test_bind_thinking_tokens_for_nano_9b_binds_min_and_max(self): + def test_bind_reasoning_config_nano_9b_binds_min_and_max(self): """Both min_thinking_tokens and max_thinking_tokens bind for nano-9b.""" - from nvidia_rag.utils.llm import _bind_thinking_tokens_if_configured + from nvidia_rag.utils.llm import _bind_reasoning_config mock_llm = Mock() - bound_llm = _bind_thinking_tokens_if_configured( + mock_llm.bind.return_value = mock_llm + config = Mock() + config.llm.parameters.enable_thinking = False + config.llm.parameters.reasoning_budget = 0 + config.llm.parameters.low_effort = False + config.llm.parameters.min_thinking_tokens = 1 + config.llm.parameters.max_thinking_tokens = 8192 + + bound_llm = _bind_reasoning_config( mock_llm, + config=config, model="nvidia/nvidia-nemotron-nano-9b-v2", min_thinking_tokens=1, max_thinking_tokens=8192, @@ -898,13 +910,21 @@ def test_bind_thinking_tokens_for_nano_9b_binds_min_and_max(self): ) assert bound_llm is mock_llm.bind.return_value - def test_no_thinking_tokens_for_nano_9b_returns_original_llm(self): + def test_bind_reasoning_config_nano_9b_no_tokens_returns_original(self): """If no thinking tokens are provided, nano-9b returns original LLM.""" - from nvidia_rag.utils.llm import _bind_thinking_tokens_if_configured + from nvidia_rag.utils.llm import _bind_reasoning_config mock_llm = Mock() - bound_llm = _bind_thinking_tokens_if_configured( + config = Mock() + config.llm.parameters.enable_thinking = False + config.llm.parameters.reasoning_budget = 0 + config.llm.parameters.low_effort = False + config.llm.parameters.min_thinking_tokens = 0 + config.llm.parameters.max_thinking_tokens = 0 + + bound_llm = _bind_reasoning_config( mock_llm, + config=config, model="nvidia/nvidia-nemotron-nano-9b-v2", ) From 1f72a5735a37c357bfd72fd66730c0dacdd3c89f Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Mon, 2 Mar 2026 13:19:44 +0530 Subject: [PATCH 12/52] Concatenate multimodal content for VLM Embed (#362) (#392) Signed-off-by: Swapnil Masurekar --- src/nvidia_rag/rag_server/main.py | 53 ++++++---- .../test_rag_server/test_query_rewriting.py | 99 ++++++++++++++++++- .../test_rag_main_core_components.py | 5 +- .../test_rag_main_integration.py | 4 +- 4 files changed, 139 insertions(+), 22 deletions(-) diff --git a/src/nvidia_rag/rag_server/main.py b/src/nvidia_rag/rag_server/main.py index 3e9ddf810..55aaabede 100644 --- a/src/nvidia_rag/rag_server/main.py +++ b/src/nvidia_rag/rag_server/main.py @@ -1873,27 +1873,46 @@ def _build_retriever_query_from_content(self, content: Any) -> tuple[str, bool]: tuple[str, bool]: Query string that may include base64 image data for VLM embeddings bool: True if image URL is provided, False otherwise """ + is_image_query = False if isinstance(content, str): - return content, False + return content, is_image_query elif isinstance(content, list): - # Build multimodal query with both text and base64 images - query_parts = [] - for item in content: - if isinstance(item, dict): - if item.get("type") == "text": - text_content = item.get("text", "").strip() - if text_content: - query_parts.append(text_content) - elif item.get("type") == "image_url": - image_url = item.get("image_url", {}).get("url", "") - if image_url: - # If image URL is provided, return it as is - return image_url, True - # If no image URL is provided, return the text content - return "\n\n".join(query_parts), False + # Build multimodal query with both text and base64 images. + + # Process text types first, then image_url types. + text_items = [ + item for item in content + if isinstance(item, dict) and item.get("type") == "text" + ] + image_items = [ + item for item in content + if isinstance(item, dict) and item.get("type") == "image_url" + ] + + # Extract text and image parts in separate lists + text_parts = [] + image_parts = [] + for item in text_items: + text_content = item.get("text", "").strip() + if text_content: + text_parts.append(text_content) + for item in image_items: + image_url = item.get("image_url", {}).get("url", "") + if image_url: + image_parts.append(image_url) + is_image_query = True + break # only one image is supported + + text_query = "\n\n".join(text_parts) + if image_parts: + image_str = " ".join(image_parts) + final_query = (text_query + " " + image_str) if text_query else image_str + else: + final_query = text_query + return final_query, is_image_query else: # Fallback for any other content type - return (str(content) if content is not None else ""), False + return (str(content) if content is not None else ""), is_image_query async def _rag_chain( self, diff --git a/tests/unit/test_rag_server/test_query_rewriting.py b/tests/unit/test_rag_server/test_query_rewriting.py index 129d82124..78e010474 100644 --- a/tests/unit/test_rag_server/test_query_rewriting.py +++ b/tests/unit/test_rag_server/test_query_rewriting.py @@ -17,6 +17,8 @@ Test suite for query rewriting functionality in the RAG server. """ +from unittest.mock import AsyncMock, patch + import pytest @@ -64,6 +66,7 @@ class DummyVDB: """A minimal VDB stub used via monkeypatch on __prepare_vdb_op.""" last_query = None + last_retrieval_method = None def check_collection_exists(self, collection_name: str) -> bool: return True @@ -77,6 +80,15 @@ def get_metadata_schema(self, collection_name: str): def retrieval_langchain(self, query, collection_name, vectorstore=None, top_k=None, filter_expr="", otel_ctx=None): """Sync method - called in ThreadPoolExecutor or directly.""" DummyVDB.last_query = query + DummyVDB.last_retrieval_method = "langchain" + return [] + + def retrieval_image_langchain( + self, query, collection_name, vectorstore=None, top_k=None, reranker_top_k=None + ): + """Called when query contains images (multimodal).""" + DummyVDB.last_query = query + DummyVDB.last_retrieval_method = "image" return [] @@ -253,6 +265,43 @@ async def test_search_combines_history_when_multiturn_enabled(monkeypatch): assert fake_vdb.last_query == "What is RAG?. How does it work?" +@pytest.mark.asyncio +async def test_search_skips_query_rewriter_for_image_query(monkeypatch): + """When query is multimodal with image, query rewriting is skipped and retrieval_image_langchain is used.""" + from nvidia_rag.rag_server.main import NvidiaRAG + + monkeypatch.setenv("CONVERSATION_HISTORY", "5") + monkeypatch.setenv("ENABLE_REFLECTION", "false") + + fake_vdb = DummyVDB() + rag = NvidiaRAG() + monkeypatch.setattr(NvidiaRAG, "_prepare_vdb_op", lambda self, **kw: fake_vdb) + + multimodal_query = [ + {"type": "text", "text": "What is in this image?"}, + {"type": "image_url", "image_url": {"url": "data:image/png;base64,x"}}, + ] + messages = [ + {"role": "user", "content": "Previous question"}, + {"role": "assistant", "content": "Previous answer"}, + ] + + await rag.search( + query=multimodal_query, + messages=messages, + collection_names=["test"], + enable_query_rewriting=True, + enable_reranker=False, + filter_expr="", + ) + + # Assert: query rewriting skipped - last_query is text + image URL (no "REWRITTEN(...)") + assert fake_vdb.last_query == "What is in this image? data:image/png;base64,x" + assert "REWRITTEN" not in str(fake_vdb.last_query) + # Assert: retrieval_image_langchain was used (not retrieval_langchain) + assert fake_vdb.last_retrieval_method == "image" + + @pytest.mark.asyncio async def test_generate_uses_query_rewriter_when_enabled(monkeypatch): """Test that query rewriting is used in generate when enabled with conversation history.""" @@ -319,6 +368,55 @@ async def test_generate_uses_only_current_query_when_history_disabled(monkeypatc assert fake_vdb.last_query == "How does it work?" +@pytest.mark.asyncio +async def test_generate_skips_query_rewriter_for_image_query(monkeypatch): + """When messages contain multimodal content with image, query rewriting is skipped.""" + from nvidia_rag.rag_server.main import NvidiaRAG + + monkeypatch.setenv("CONVERSATION_HISTORY", "5") + monkeypatch.setenv("ENABLE_REFLECTION", "false") + monkeypatch.setenv("MULTITURN_RETRIEVER_SIMPLE", "False") + + fake_vdb = DummyVDB() + rag = NvidiaRAG() + monkeypatch.setattr(NvidiaRAG, "_prepare_vdb_op", lambda self, **kw: fake_vdb) + + messages = [ + {"role": "user", "content": "What is RAG?"}, + {"role": "assistant", "content": "A retrieval-augmented framework."}, + { + "role": "user", + "content": [ + {"type": "text", "text": "What is in this image?"}, + {"type": "image_url", "image_url": {"url": "data:image/png;base64,x"}}, + ], + }, + ] + + async def _stream(*a, **k): + yield "ok" + + with patch("nvidia_rag.rag_server.main.VLM") as mock_vlm_class: + mock_vlm_instance = mock_vlm_class.return_value + mock_vlm_instance.stream_with_messages = _stream + + stream = await rag.generate( + messages=messages, + use_knowledge_base=True, + collection_names=["test"], + enable_query_rewriting=True, + enable_reranker=False, + enable_vlm_inference=True, + filter_expr="", + ) + + # Assert: query rewriting skipped - last_query is text + image URL (no "REWRITTEN(...)") + assert fake_vdb.last_query == "What is in this image? data:image/png;base64,x" + assert "REWRITTEN" not in str(fake_vdb.last_query) + # Assert: retrieval_image_langchain was used + assert fake_vdb.last_retrieval_method == "image" + + @pytest.mark.asyncio async def test_generate_combines_history_when_multiturn_enabled(monkeypatch): """Test that when multiturn_retrieval_simple is True, history is concatenated.""" @@ -353,4 +451,3 @@ async def test_generate_combines_history_when_multiturn_enabled(monkeypatch): # last previous user query is combined with current retriever_query # Expected concatenation: "What is RAG?. How does it work?" assert fake_vdb.last_query == "What is RAG?. How does it work?" - diff --git a/tests/unit/test_rag_server/test_rag_main_core_components.py b/tests/unit/test_rag_server/test_rag_main_core_components.py index cc14c7c08..d4567399c 100644 --- a/tests/unit/test_rag_server/test_rag_main_core_components.py +++ b/tests/unit/test_rag_server/test_rag_main_core_components.py @@ -416,8 +416,8 @@ def test_build_retriever_query_from_multimodal_list(self): ] result = rag._build_retriever_query_from_content(content) - # When image_url is present, the method returns the image URL - assert result == ("http://example.com/image.jpg", True) + # Text parts joined with \n\n first, then image URL with space separator + assert result == ("Hello\n\nworld http://example.com/image.jpg", True) def test_build_retriever_query_from_list_without_text(self): """Test building retriever query from list without text items.""" @@ -428,6 +428,7 @@ def test_build_retriever_query_from_list_without_text(self): ] result = rag._build_retriever_query_from_content(content) + # Image-only: no text, so final query is just the image URL (no leading space) assert result == ("http://example.com/image.jpg", True) def test_build_retriever_query_from_other_type(self): diff --git a/tests/unit/test_rag_server/test_rag_main_integration.py b/tests/unit/test_rag_server/test_rag_main_integration.py index 3e3cd8be6..9de59b630 100644 --- a/tests/unit/test_rag_server/test_rag_main_integration.py +++ b/tests/unit/test_rag_server/test_rag_main_integration.py @@ -445,8 +445,8 @@ def test_build_retriever_query_from_content_multimodal(self): ] result = rag._build_retriever_query_from_content(content) - # When image_url is present, the method returns the image URL - assert result == ("http://example.com/image.jpg", True) + # Text parts joined with \n\n first, then image URL with space separator + assert result == ("Hello\n\nworld http://example.com/image.jpg", True) def test_print_conversation_history(self): """Test __print_conversation_history method.""" From a1d1144823c514cca36943d5303e8b42a6baab26 Mon Sep 17 00:00:00 2001 From: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:39:58 +0530 Subject: [PATCH 13/52] security: Fix frontend CVEs (#396) --- frontend/Dockerfile | 2 +- frontend/package.json | 7 + frontend/pnpm-lock.yaml | 276 ++++++++++++++++++++++++---------------- 3 files changed, 175 insertions(+), 110 deletions(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 1c0a6961d..c6f3e3b01 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -94,7 +94,7 @@ RUN if [ "$DOWNLOAD_LEGAL_COMPLIANCE" = "true" ] && [ -d /legal ]; then \ # Production stage - NVIDIA distroless (pre-approved) # Updated to latest version to address CVE-2025-9230 (libssl3) -FROM nvcr.io/nvidia/distroless/node:24-v3.1.3 +FROM nvcr.io/nvidia/distroless/node:24-v4.0.2 # Copy built application and config for production preview WORKDIR /app/frontend diff --git a/frontend/package.json b/frontend/package.json index fe77b7146..bdb3500f5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -51,5 +51,12 @@ }, "resolutions": { "@kui/foundations": "./src/assets/kui-foundations-react-external-0.504.1.tgz" + }, + "pnpm": { + "overrides": { + "rollup": ">=4.59.0", + "minimatch@3.1.2": "3.1.4", + "minimatch@9.0.5": "9.0.7" + } } } \ No newline at end of file diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index ea8207293..cc1a633fc 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -6,6 +6,9 @@ settings: overrides: '@kui/foundations': ./src/assets/kui-foundations-react-external-0.504.1.tgz + rollup: '>=4.59.0' + minimatch@3.1.2: 3.1.4 + minimatch@9.0.5: 9.0.7 importers: @@ -1233,113 +1236,141 @@ packages: '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - '@rollup/rollup-android-arm-eabi@4.53.3': - resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.53.3': - resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.53.3': - resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.53.3': - resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.53.3': - resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.53.3': - resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.53.3': - resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.53.3': - resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.53.3': - resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.53.3': - resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.53.3': - resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.53.3': - resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.53.3': - resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.53.3': - resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.53.3': - resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.53.3': - resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.53.3': - resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.53.3': - resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.53.3': - resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.53.3': - resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.53.3': - resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.53.3': - resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} cpu: [x64] os: [win32] @@ -1381,24 +1412,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.17': resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.17': resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.17': resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.17': resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} @@ -1677,6 +1712,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.9.6: resolution: {integrity: sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==} hasBin: true @@ -1684,8 +1723,9 @@ packages: brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} @@ -2135,24 +2175,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -2214,11 +2258,11 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.4: + resolution: {integrity: sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.7: + resolution: {integrity: sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==} engines: {node: '>=16 || 14 >=14.17'} minipass@7.1.2: @@ -2400,8 +2444,8 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - rollup@4.53.3: - resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3031,7 +3075,7 @@ snapshots: dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3 - minimatch: 3.1.2 + minimatch: 3.1.4 transitivePeerDependencies: - supports-color @@ -3052,7 +3096,7 @@ snapshots: ignore: 5.3.2 import-fresh: 3.3.1 js-yaml: 4.1.1 - minimatch: 3.1.2 + minimatch: 3.1.4 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color @@ -3910,70 +3954,79 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.27': {} - '@rollup/rollup-android-arm-eabi@4.53.3': + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': optional: true - '@rollup/rollup-android-arm64@4.53.3': + '@rollup/rollup-darwin-x64@4.59.0': optional: true - '@rollup/rollup-darwin-arm64@4.53.3': + '@rollup/rollup-freebsd-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-x64@4.53.3': + '@rollup/rollup-freebsd-x64@4.59.0': optional: true - '@rollup/rollup-freebsd-arm64@4.53.3': + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': optional: true - '@rollup/rollup-freebsd-x64@4.53.3': + '@rollup/rollup-linux-arm-musleabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + '@rollup/rollup-linux-arm64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.53.3': + '@rollup/rollup-linux-arm64-musl@4.59.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.53.3': + '@rollup/rollup-linux-loong64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.53.3': + '@rollup/rollup-linux-loong64-musl@4.59.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.53.3': + '@rollup/rollup-linux-ppc64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.53.3': + '@rollup/rollup-linux-ppc64-musl@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.53.3': + '@rollup/rollup-linux-riscv64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.53.3': + '@rollup/rollup-linux-riscv64-musl@4.59.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.53.3': + '@rollup/rollup-linux-s390x-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.53.3': + '@rollup/rollup-linux-x64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-musl@4.53.3': + '@rollup/rollup-linux-x64-musl@4.59.0': optional: true - '@rollup/rollup-openharmony-arm64@4.53.3': + '@rollup/rollup-openbsd-x64@4.59.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.53.3': + '@rollup/rollup-openharmony-arm64@4.59.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.53.3': + '@rollup/rollup-win32-arm64-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.53.3': + '@rollup/rollup-win32-ia32-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.53.3': + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true '@tailwindcss/node@4.1.17': @@ -4198,7 +4251,7 @@ snapshots: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/visitor-keys': 8.49.0 debug: 4.4.3 - minimatch: 9.0.5 + minimatch: 9.0.7 semver: 7.7.3 tinyglobby: 0.2.15 ts-api-utils: 2.1.0(typescript@5.8.3) @@ -4355,6 +4408,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + baseline-browser-mapping@2.9.6: {} brace-expansion@1.1.12: @@ -4362,9 +4417,9 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.2: + brace-expansion@5.0.4: dependencies: - balanced-match: 1.0.2 + balanced-match: 4.0.4 browserslist@4.28.1: dependencies: @@ -4557,7 +4612,7 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.4 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -4639,7 +4694,7 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 + minimatch: 9.0.7 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 @@ -4871,13 +4926,13 @@ snapshots: min-indent@1.0.1: {} - minimatch@3.1.2: + minimatch@3.1.4: dependencies: brace-expansion: 1.1.12 - minimatch@9.0.5: + minimatch@9.0.7: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 5.0.4 minipass@7.1.2: {} @@ -5082,32 +5137,35 @@ snapshots: resolve-from@4.0.0: {} - rollup@4.53.3: + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.53.3 - '@rollup/rollup-android-arm64': 4.53.3 - '@rollup/rollup-darwin-arm64': 4.53.3 - '@rollup/rollup-darwin-x64': 4.53.3 - '@rollup/rollup-freebsd-arm64': 4.53.3 - '@rollup/rollup-freebsd-x64': 4.53.3 - '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 - '@rollup/rollup-linux-arm-musleabihf': 4.53.3 - '@rollup/rollup-linux-arm64-gnu': 4.53.3 - '@rollup/rollup-linux-arm64-musl': 4.53.3 - '@rollup/rollup-linux-loong64-gnu': 4.53.3 - '@rollup/rollup-linux-ppc64-gnu': 4.53.3 - '@rollup/rollup-linux-riscv64-gnu': 4.53.3 - '@rollup/rollup-linux-riscv64-musl': 4.53.3 - '@rollup/rollup-linux-s390x-gnu': 4.53.3 - '@rollup/rollup-linux-x64-gnu': 4.53.3 - '@rollup/rollup-linux-x64-musl': 4.53.3 - '@rollup/rollup-openharmony-arm64': 4.53.3 - '@rollup/rollup-win32-arm64-msvc': 4.53.3 - '@rollup/rollup-win32-ia32-msvc': 4.53.3 - '@rollup/rollup-win32-x64-gnu': 4.53.3 - '@rollup/rollup-win32-x64-msvc': 4.53.3 + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 rrweb-cssom@0.8.0: {} @@ -5192,7 +5250,7 @@ snapshots: dependencies: '@istanbuljs/schema': 0.1.3 glob: 10.5.0 - minimatch: 9.0.5 + minimatch: 9.0.7 tinybench@2.9.0: {} @@ -5308,7 +5366,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.53.3 + rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.10.3 From de589b49c305959dd532b1da0c1f83510d4049a0 Mon Sep 17 00:00:00 2001 From: Nikhil Kulkarni Date: Tue, 3 Mar 2026 01:01:22 +0530 Subject: [PATCH 14/52] Add config to enable nemotron parse only extraction in nv-ingest (#395) * Add config to enable nemotron parse only extraction in nv-ingest * Refactor nemotron parse only documentation * Remove nemotron parse only references from the previous section --- .../docker-compose-ingestor-server.yaml | 3 +- deploy/helm/nvidia-blueprint-rag/Chart.lock | 6 +- deploy/helm/nvidia-blueprint-rag/values.yaml | 1 + docs/nemotron-parse-extraction.md | 135 +++++++++++++++++- src/nvidia_rag/ingestor_server/nvingest.py | 2 + src/nvidia_rag/utils/configuration.py | 14 ++ 6 files changed, 154 insertions(+), 7 deletions(-) diff --git a/deploy/compose/docker-compose-ingestor-server.yaml b/deploy/compose/docker-compose-ingestor-server.yaml index be15c17dc..fa82c06c8 100644 --- a/deploy/compose/docker-compose-ingestor-server.yaml +++ b/deploy/compose/docker-compose-ingestor-server.yaml @@ -95,7 +95,8 @@ services: APP_NVINGEST_EXTRACTPAGEASIMAGE: ${APP_NVINGEST_EXTRACTPAGEASIMAGE:-False} APP_NVINGEST_STRUCTURED_ELEMENTS_MODALITY: ${APP_NVINGEST_STRUCTURED_ELEMENTS_MODALITY:-""} # Select from "image", "text_image" APP_NVINGEST_IMAGE_ELEMENTS_MODALITY: ${APP_NVINGEST_IMAGE_ELEMENTS_MODALITY:-""} # Select from "image" - APP_NVINGEST_PDFEXTRACTMETHOD: ${APP_NVINGEST_PDFEXTRACTMETHOD:-None} # Select from pdfium, nemoretron_parse, None + APP_NVINGEST_PDFEXTRACTMETHOD: ${APP_NVINGEST_PDFEXTRACTMETHOD:-None} # Select from pdfium, nemotron_parse, None + APP_NVINGEST_EXTRACTTABLESMETHOD: ${APP_NVINGEST_EXTRACTTABLESMETHOD:-yolox} # yolox, nemotron_parse, or None # Extract text by "page" only recommended for documents with pages like .pdf, .docx, etc. APP_NVINGEST_TEXTDEPTH: ${APP_NVINGEST_TEXTDEPTH:-page} # extract by "page" or "document" diff --git a/deploy/helm/nvidia-blueprint-rag/Chart.lock b/deploy/helm/nvidia-blueprint-rag/Chart.lock index 7b479e4aa..723660bfd 100644 --- a/deploy/helm/nvidia-blueprint-rag/Chart.lock +++ b/deploy/helm/nvidia-blueprint-rag/Chart.lock @@ -1,7 +1,7 @@ dependencies: - name: nv-ingest repository: https://helm.ngc.nvidia.com/nvidia/nemo-microservices - version: 26.1.1 + version: 26.1.2 - name: eck-elasticsearch repository: https://helm.elastic.co version: 0.18.0 @@ -14,5 +14,5 @@ dependencies: - name: kube-prometheus-stack repository: https://prometheus-community.github.io/helm-charts version: 76.3.0 -digest: sha256:7f85073bdf19922173b3372d9b5a877d6c2f783b431ce7a2f783308f67806c66 -generated: "2026-02-04T07:29:44.453434343Z" +digest: sha256:a65037bbcb6fa587af3d15b949a32b059cf26d1102a2166d0e77daed29a0f520 +generated: "2026-03-02T16:48:31.702049307+05:30" diff --git a/deploy/helm/nvidia-blueprint-rag/values.yaml b/deploy/helm/nvidia-blueprint-rag/values.yaml index 00c0ecd1d..af3c2166e 100644 --- a/deploy/helm/nvidia-blueprint-rag/values.yaml +++ b/deploy/helm/nvidia-blueprint-rag/values.yaml @@ -359,6 +359,7 @@ ingestor-server: # === NV-Ingest extraction configurations === APP_NVINGEST_PDFEXTRACTMETHOD: "None" # Method used for text extraction from "None", "pdfium", "nemotron_parse" + APP_NVINGEST_EXTRACTTABLESMETHOD: "yolox" # Method for table extraction: "yolox", "nemotron_parse", or None APP_NVINGEST_EXTRACTTEXT: "True" # Enable text extraction APP_NVINGEST_EXTRACTINFOGRAPHICS: "False" # Enable infographic extraction APP_NVINGEST_EXTRACTTABLES: "True" # Enable table extraction diff --git a/docs/nemotron-parse-extraction.md b/docs/nemotron-parse-extraction.md index a23dca7f4..c15cacf3f 100644 --- a/docs/nemotron-parse-extraction.md +++ b/docs/nemotron-parse-extraction.md @@ -62,7 +62,7 @@ When using NVIDIA hosted endpoints, you may encounter rate limiting with larger ## Using Helm -To enable PDF extraction with Nemotron Parse using Helm, you need to enable the Nemotron Parse service and configure the ingestor-server to use it. +To enable PDF extraction with Nemotron Parse using Helm, enable the Nemotron Parse service and configure the ingestor-server to use it. ### Prerequisites - Ensure you have sufficient GPU resources. Nemotron Parse requires a dedicated GPU. @@ -71,7 +71,7 @@ To enable PDF extraction with Nemotron Parse using Helm, you need to enable the To deploy with Nemotron Parse enabled: -Modify [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml) to enable Nemotron Parse: +Modify [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml) to enable Nemotron Parse and configure the ingestor-server: ```yaml # Enable Nemotron Parse NIM @@ -93,9 +93,136 @@ For detailed HELM deployment instructions, see [Helm Deployment Guide](deploy-he :::{note} **Key Configuration Changes:** - `nv-ingest.nimOperator.nemotron_parse.enabled=true` - Enables Nemotron Parse NIM -- `ingestor-server.envVars.APP_NVINGEST_PDFEXTRACTMETHOD="nemotron_parse"` - Configures ingestor to use Nemotron Parse +- `ingestor-server.envVars.APP_NVINGEST_PDFEXTRACTMETHOD="nemotron_parse"` - Configures ingestor to use Nemotron Parse for PDF extraction ::: +## Experimental: Nemotron-parse-only extraction + +:::{note} +The steps in this section describe a nemotron-parse-only pipeline. For production use, the default pipeline (Nemotron Parse with page-elements and table-structure NIMs) is recommended for better accuracy. +::: + +The **default** Nemotron Parse pipeline uses the **page-elements** and **table-structure** NIMs together with the Nemotron Parse NIM in the extraction pipeline. This combination provides better accuracy for PDF and table extraction. +To **experiment** with a nemotron-parse-only extraction pipeline (using only the Nemotron Parse NIM, without OCR, page-elements, graphic-elements, or table-structure NIMs), use the following steps. + +### Key configuration + +- **PDF extraction method** — Set `APP_NVINGEST_PDFEXTRACTMETHOD` to `nemotron_parse` so the ingestor uses Nemotron Parse for PDF text extraction. +- **Table extraction method** — Set `APP_NVINGEST_EXTRACTTABLESMETHOD` to `nemotron_parse` so the ingestor uses Nemotron Parse for table extraction instead of the default YOLOX-based table NIMs. This is required for a nemotron-parse-only pipeline. +- **nv-ingest health check** — Set `COMPONENTS_TO_READY_CHECK` to an empty string (`""`) in the **nv-ingest** service environment. By default, nv-ingest readiness waits for other ingest NIMs. With only Nemotron Parse running, the readiness probe would otherwise never pass. Emptying this value allows nv-ingest to become ready when only Nemotron Parse is available. + +### Using Docker Compose (nemotron-parse-only) + +#### On-prem models + +1. **Prerequisites**: Follow the [deployment guide](deploy-docker-self-hosted.md) up to and including the step labelled "Start all required NIMs." + +2. Start only the Nemotron Parse service (and any other non-ingest services your setup needs): + ```bash + USERID=$(id -u) docker compose --profile rag --profile nemotron-parse -f deploy/compose/nims.yaml up -d + ``` + You can skip the OCR, page-elements, graphic-elements, or table-structure NIMs if you want a nemotron-parse-only pipeline. + +3. Configure the ingestor-server and nv-ingest for nemotron-parse-only. Set these environment variables: + + **Ingestor-server** (ingestor-server environment): + ```bash + export APP_NVINGEST_PDFEXTRACTMETHOD=nemotron_parse + export APP_NVINGEST_EXTRACTTABLESMETHOD=nemotron_parse + ``` + + **nv-ingest** (nv-ingest service environment, e.g. in the compose file where nv-ingest runs): + ```bash + export COMPONENTS_TO_READY_CHECK="" + ``` + This ensures the nv-ingest readiness probe passes when other ingest NIMs are not running. + +4. Deploy the ingestion-server and rag-server containers following the remaining steps in the deployment guide. + +5. Ingest PDFs using the [ingestion API usage notebook](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/ingestion_api_usage.ipynb). + +#### NVIDIA hosted API endpoints + +1. **Prerequisites**: Follow the [deployment guide](deploy-docker-nvidia-hosted.md) up to and including the step labelled "Start the vector db containers from the repo root." + +2. Export variables for the Nemotron Parse API: + ```bash + export NEMOTRON_PARSE_HTTP_ENDPOINT=https://integrate.api.nvidia.com/v1/chat/completions + export NEMOTRON_PARSE_MODEL_NAME=nvidia/nemotron-parse + export NEMOTRON_PARSE_INFER_PROTOCOL=http + ``` + +3. Configure the ingestor-server and nv-ingest for nemotron-parse-only: + + **Ingestor-server**: + ```bash + export APP_NVINGEST_PDFEXTRACTMETHOD=nemotron_parse + export APP_NVINGEST_EXTRACTTABLESMETHOD=nemotron_parse + ``` + + **nv-ingest** (so readiness passes without other NIMs): + ```bash + export COMPONENTS_TO_READY_CHECK="" + ``` + +4. Deploy the ingestion-server and rag-server containers following the remaining steps in the deployment guide. + +5. Ingest PDFs using the [ingestion API usage notebook](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/ingestion_api_usage.ipynb). + +:::{note} +When using NVIDIA hosted endpoints, you may encounter rate limiting with larger file ingestions (>10 files). +::: + +### Using Helm (nemotron-parse-only) + +To run only Nemotron Parse for PDF and table extraction with Helm: + +1. **Prerequisites**: Ensure you have sufficient GPU resources. Nemotron Parse requires a dedicated GPU. + +2. Edit [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml): + + - **Enable Nemotron Parse** and **disable the other ingest NIMs** under `nv-ingest.nimOperator`: + + ```yaml + nv-ingest: + nimOperator: + nemotron_parse: + enabled: true + nemoretriever_ocr_v1: + enabled: false + graphic_elements: + enabled: false + page_elements: + enabled: false + table_structure: + enabled: false + envVars: + COMPONENTS_TO_READY_CHECK: "" + ``` + + - **Configure the ingestor-server** to use Nemotron Parse for both PDF and table extraction: + + ```yaml + ingestor-server: + envVars: + APP_NVINGEST_PDFEXTRACTMETHOD: "nemotron_parse" + APP_NVINGEST_EXTRACTTABLESMETHOD: "nemotron_parse" + ``` + +3. Apply the changes as described in [Change a Deployment](deploy-helm.md#change-a-deployment). + +4. For full Helm deployment steps, see the [Helm Deployment Guide](deploy-helm.md). + +**Summary of nemotron-parse-only Helm settings:** + +| Setting | Purpose | +|---------|---------| +| `nv-ingest.nimOperator.nemotron_parse.enabled: true` | Enable the Nemotron Parse NIM. | +| `nv-ingest.nimOperator..enabled: false` | Disable OCR, page-elements, graphic-elements, and table-structure NIMs. | +| `nv-ingest.envVars.COMPONENTS_TO_READY_CHECK: ""` | nv-ingest health check: readiness passes without other NIMs. | +| `ingestor-server.envVars.APP_NVINGEST_PDFEXTRACTMETHOD: "nemotron_parse"` | Use Nemotron Parse for PDF extraction. | +| `ingestor-server.envVars.APP_NVINGEST_EXTRACTTABLESMETHOD: "nemotron_parse"` | Use Nemotron Parse for table extraction. | + ## Limitations and Requirements When using Nemotron Parse for PDF extraction, consider the following: @@ -115,6 +242,8 @@ The `APP_NVINGEST_PDFEXTRACTMETHOD` environment variable supports the following - `pdfium`: Uses the default PDFium-based extraction - `None`: Uses the default extraction method +**Table extraction method:** The `APP_NVINGEST_EXTRACTTABLESMETHOD` environment variable controls how tables are extracted. Set it to `nemotron_parse` to use Nemotron Parse for table extraction (recommended for a nemotron-parse-only pipeline). The default is `yolox`, which uses the YOLOX-based table NIMs. + :::{note} The Nemotron Parse service requires GPU resources and must run on a dedicated GPU. Make sure you have sufficient GPU resources available before enabling this feature. ::: diff --git a/src/nvidia_rag/ingestor_server/nvingest.py b/src/nvidia_rag/ingestor_server/nvingest.py index 55e218c53..0f02aa1bb 100644 --- a/src/nvidia_rag/ingestor_server/nvingest.py +++ b/src/nvidia_rag/ingestor_server/nvingest.py @@ -141,6 +141,8 @@ def get_nv_ingest_ingestor( "extract_audio_params": {"segment_audio": config.nv_ingest.segment_audio}, "extract_page_as_image": config.nv_ingest.extract_page_as_image, } + if config.nv_ingest.extract_tables_method is not None: + extract_kwargs["extract_tables_method"] = config.nv_ingest.extract_tables_method if remove_extract_method or config.nv_ingest.pdf_extract_method is None: extract_kwargs.pop("extract_method", None) diff --git a/src/nvidia_rag/utils/configuration.py b/src/nvidia_rag/utils/configuration.py index 3eb738ea0..e1c9c1526 100644 --- a/src/nvidia_rag/utils/configuration.py +++ b/src/nvidia_rag/utils/configuration.py @@ -302,6 +302,20 @@ def normalize_pdf_extract_method(cls, v: Any) -> Any: env="APP_NVINGEST_TEXTDEPTH", description="Granularity level for text extraction (page, document)", ) + extract_tables_method: str | None = Field( + default=None, + env="APP_NVINGEST_EXTRACTTABLESMETHOD", + description="Method for table/chart extraction in PDFs (e.g. yolox, nemotron_parse). If None, client default is used.", + ) + + @field_validator("extract_tables_method", mode="before") + @classmethod + def normalize_extract_tables_method(cls, v: Any) -> Any: + """Normalize string 'None'/'none' to Python None.""" + if isinstance(v, str) and v.lower() in ("none", "null", ""): + return None + return v + tokenizer: str = Field( default="intfloat/e5-large-unsupervised", env="APP_NVINGEST_TOKENIZER", From 5cee48f035839bdfac24cf91d3266171895ddebf Mon Sep 17 00:00:00 2001 From: kumar-punit Date: Tue, 3 Mar 2026 15:15:19 +0530 Subject: [PATCH 15/52] Added patch command in rtx6000pro mig block also in documentation (#398) --- docs/mig-deployment.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/mig-deployment.md b/docs/mig-deployment.md index 149c25366..af5230e2c 100644 --- a/docs/mig-deployment.md +++ b/docs/mig-deployment.md @@ -158,6 +158,9 @@ Use [`mig-config-rtx6000.yaml`](../deploy/helm/mig-slicing/mig-config-rtx6000.ya ```bash kubectl apply -n nvidia-gpu-operator -f mig-slicing/mig-config-rtx6000.yaml +kubectl patch clusterpolicies.nvidia.com/cluster-policy \ + --type='json' \ + -p='[{"op":"replace", "path":"/spec/migManager/config/name", "value":"custom-mig-config"}]' kubectl label nodes nvidia.com/mig.config=custom-rtx6000-4x1g24-2x1g24-1x2g48-1x4g96 --overwrite ``` ::: From 60d3ca69daa85f0f0d765428335763d49ecd09be Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Wed, 4 Mar 2026 11:34:27 +0530 Subject: [PATCH 16/52] Upgraded to GA nemotron-ranking-ms and nemotron-embedding-ms containers (#402) Signed-off-by: Swapnil Masurekar --- deploy/compose/nims.yaml | 4 ++-- deploy/helm/nvidia-blueprint-rag/values.yaml | 8 ++++---- deploy/workbench/compose.yaml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/deploy/compose/nims.yaml b/deploy/compose/nims.yaml index 261a705bc..eb29bfc06 100644 --- a/deploy/compose/nims.yaml +++ b/deploy/compose/nims.yaml @@ -33,7 +33,7 @@ services: nemotron-embedding-ms: container_name: nemotron-embedding-ms - image: nvcr.io/nvstaging/nim/llama-nemotron-embed-1b-v2:1.12.0-rc.20260216115852-6fba925dcb63c190 + image: nvcr.io/nim/nvidia/llama-nemotron-embed-1b-v2:1.13.0 volumes: - ${MODEL_DIRECTORY:-./}:/opt/nim/.cache ports: @@ -93,7 +93,7 @@ services: nemotron-ranking-ms: container_name: nemotron-ranking-ms - image: nvcr.io/nvstaging/nim/llama-nemotron-rerank-1b-v2:1.10.0-rc.20260219005237-7e2370934c6f9b9c + image: nvcr.io/nim/nvidia/llama-nemotron-rerank-1b-v2:1.10.0 volumes: - ${MODEL_DIRECTORY:-./}:/opt/nim/.cache ports: diff --git a/deploy/helm/nvidia-blueprint-rag/values.yaml b/deploy/helm/nvidia-blueprint-rag/values.yaml index af3c2166e..986caacc9 100644 --- a/deploy/helm/nvidia-blueprint-rag/values.yaml +++ b/deploy/helm/nvidia-blueprint-rag/values.yaml @@ -719,8 +719,8 @@ nimOperator: service: name: "nemotron-embedding-ms" image: - repository: nvcr.io/nvstaging/nim/llama-nemotron-embed-1b-v2 - tag: "1.12.0-rc.20260216115852-6fba925dcb63c190" + repository: nvcr.io/nim/nvidia/llama-nemotron-embed-1b-v2 + tag: "1.13.0" pullPolicy: IfNotPresent resources: limits: @@ -798,8 +798,8 @@ nimOperator: service: name: "nemotron-ranking-ms" image: - repository: nvcr.io/nvstaging/nim/llama-nemotron-rerank-1b-v2 - tag: "1.10.0-rc.20260219005237-7e2370934c6f9b9c" + repository: nvcr.io/nim/nvidia/llama-nemotron-rerank-1b-v2 + tag: "1.10.0" pullPolicy: IfNotPresent resources: limits: diff --git a/deploy/workbench/compose.yaml b/deploy/workbench/compose.yaml index aa9dd5713..c6867b9d6 100644 --- a/deploy/workbench/compose.yaml +++ b/deploy/workbench/compose.yaml @@ -30,7 +30,7 @@ services: nemotron-embedding-ms: container_name: nemotron-embedding-ms - image: nvcr.io/nvstaging/nim/llama-nemotron-embed-1b-v2:1.12.0-rc.20260216115852-6fba925dcb63c190 + image: nvcr.io/nim/nvidia/llama-nemotron-embed-1b-v2:1.13.0 volumes: - ${MODEL_DIRECTORY:-/tmp}:/opt/nim/.cache ports: @@ -60,7 +60,7 @@ services: nemotron-ranking-ms: container_name: nemotron-ranking-ms - image: nvcr.io/nvstaging/nim/llama-nemotron-rerank-1b-v2:1.10.0-rc.20260219005237-7e2370934c6f9b9c + image: nvcr.io/nim/nvidia/llama-nemotron-rerank-1b-v2:1.10.0 volumes: - ${MODEL_DIRECTORY:-/tmp}:/opt/nim/.cache ports: From 720aed393cf4dedd13bb04577c2bb0b5b05403c8 Mon Sep 17 00:00:00 2001 From: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:59:15 +0530 Subject: [PATCH 17/52] Update packages to resolve source code CVEs (#400) * Update langchain-nvidia-ai-endpointsto >=1.1.0 * security: Update langgraph to version 1.10.0 --- examples/rag_react_agent/pyproject.toml | 2 +- examples/rag_react_agent/uv.lock | 372 ++++++++++++++++-------- pyproject.toml | 2 +- uv.lock | 20 +- 4 files changed, 258 insertions(+), 138 deletions(-) diff --git a/examples/rag_react_agent/pyproject.toml b/examples/rag_react_agent/pyproject.toml index fcebbcb3a..c4967a58a 100644 --- a/examples/rag_react_agent/pyproject.toml +++ b/examples/rag_react_agent/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ # Keep package version constraints as open as possible to avoid conflicts with other packages. Always define a minimum # version when adding a new package. If unsure, default to using `~=` instead of `==`. Does not apply to nvidia-nat packages. # Keep sorted!!! - "langgraph>=1.0.7", # Required for react_agent workflow + "langgraph>=1.0.8", # Required for react_agent workflow "langchain_classic", "nvidia-nat>=1.5.0a0,<2.0", # Allow pre-release versions "nvidia-nat-langchain>=1.5.0a0,<2.0", # Allow pre-release versions diff --git a/examples/rag_react_agent/uv.lock b/examples/rag_react_agent/uv.lock index 244c06a23..eee7b812b 100644 --- a/examples/rag_react_agent/uv.lock +++ b/examples/rag_react_agent/uv.lock @@ -324,6 +324,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, ] +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + [[package]] name = "boto3" version = "1.40.61" @@ -597,21 +606,20 @@ name = "datasets" version = "4.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "dill" }, - { name = "filelock" }, - { name = "fsspec", extra = ["http"] }, - { name = "httpx" }, - { name = "huggingface-hub" }, - { name = "multiprocess" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "packaging" }, - { name = "pandas" }, - { name = "pyarrow" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "tqdm" }, - { name = "xxhash" }, + { name = "dill", marker = "python_full_version >= '3.12'" }, + { name = "filelock", marker = "python_full_version >= '3.12'" }, + { name = "fsspec", extra = ["http"], marker = "python_full_version >= '3.12'" }, + { name = "httpx", marker = "python_full_version >= '3.12'" }, + { name = "huggingface-hub", marker = "python_full_version >= '3.12'" }, + { name = "multiprocess", marker = "python_full_version >= '3.12'" }, + { name = "numpy", marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.12'" }, + { name = "pandas", marker = "python_full_version >= '3.12'" }, + { name = "pyarrow", marker = "python_full_version >= '3.12'" }, + { name = "pyyaml", marker = "python_full_version >= '3.12'" }, + { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "tqdm", marker = "python_full_version >= '3.12'" }, + { name = "xxhash", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/bf/bb927bde63d649296c83e883171ae77074717c1b80fe2868b328bd0dbcbb/datasets-4.5.0.tar.gz", hash = "sha256:00c698ce1c2452e646cc5fad47fef39d3fe78dd650a8a6eb205bb45eb63cd500", size = 588384, upload-time = "2026-01-14T18:27:54.297Z" } wheels = [ @@ -736,6 +744,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, ] +[[package]] +name = "flask" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, +] + [[package]] name = "flatbuffers" version = "25.12.19" @@ -828,7 +853,7 @@ wheels = [ [package.optional-dependencies] http = [ - { name = "aiohttp" }, + { name = "aiohttp", marker = "python_full_version >= '3.12'" }, ] [[package]] @@ -1078,6 +1103,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -1235,18 +1269,17 @@ wheels = [ [[package]] name = "langchain-aws" -version = "1.0.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, { name = "langchain-core" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/52/7e57fb7fc34c386625f66f0ab31da9cf2788b03ef15ae78ccd4c627b30cf/langchain_aws-1.0.0.tar.gz", hash = "sha256:597342bda0e7384e13590e9ab69c872ddcfbbf07d81ac6bb0f8a67970252212e", size = 214146, upload-time = "2025-10-17T19:06:49.001Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/1d/bb306951b1c394b7a27effb8eb6c9ee65dd77fcc4be7c20f76e3299a9e1e/langchain_aws-1.1.0.tar.gz", hash = "sha256:1e2f8570328eae4907c3cf7e900dc68d8034ddc865d9dc96823c9f9d8cccb901", size = 393899, upload-time = "2025-11-24T14:35:24.216Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/5d/5b3c07780a8eb4b916ffe504893896f87f318924c86dcbeb89562baa2d20/langchain_aws-1.0.0-py3-none-any.whl", hash = "sha256:68f6965b5030d0779b02e731ce1c910a5f4518bfe0e2ae82999a5342bc46dbd5", size = 150400, upload-time = "2025-10-17T19:06:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/26/33/91b8d2a7570657b371382b45054142c54165a51706990a5c1b4cc40c0e9a/langchain_aws-1.1.0-py3-none-any.whl", hash = "sha256:8ec074615b42839e035354063717374c32c63f5028ef5221ba073fd5f3ef5e37", size = 152432, upload-time = "2025-11-24T14:35:23.004Z" }, ] [[package]] @@ -1278,8 +1311,7 @@ dependencies = [ { name = "langchain-classic" }, { name = "langchain-core" }, { name = "langsmith" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "pydantic-settings" }, { name = "pyyaml" }, { name = "requests" }, @@ -1293,7 +1325,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.2.7" +version = "1.2.17" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1305,9 +1337,23 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/0e/664d8d81b3493e09cbab72448d2f9d693d1fa5aa2bcc488602203a9b6da0/langchain_core-1.2.7.tar.gz", hash = "sha256:e1460639f96c352b4a41c375f25aeb8d16ffc1769499fb1c20503aad59305ced", size = 837039, upload-time = "2026-01-09T17:44:25.505Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/93/36226f593df52b871fc24d494c274f3a6b2ac76763a2806e7d35611634a1/langchain_core-1.2.17.tar.gz", hash = "sha256:54aa267f3311e347fb2e50951fe08e53761cebfb999ab80e6748d70525bbe872", size = 836130, upload-time = "2026-03-02T22:47:55.846Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/6f/34a9fba14d191a67f7e2ee3dbce3e9b86d2fa7310e2c7f2c713583481bd2/langchain_core-1.2.7-py3-none-any.whl", hash = "sha256:452f4fef7a3d883357b22600788d37e3d8854ef29da345b7ac7099f33c31828b", size = 490232, upload-time = "2026-01-09T17:44:24.236Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/073f33ab383a62908eca7ea699586dfea280e77182176e33199c80ddf22a/langchain_core-1.2.17-py3-none-any.whl", hash = "sha256:bf6bd6ce503874e9c2da1669a69383e967c3de1ea808921d19a9a6bff1a9fbbe", size = 502727, upload-time = "2026-03-02T22:47:54.537Z" }, +] + +[[package]] +name = "langchain-huggingface" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "langchain-core" }, + { name = "tokenizers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/5b/4910551367de5c6ec246616fcc0ddb0bc6f9e5d353d4a22dcb5ab1f87e60/langchain_huggingface-1.2.1.tar.gz", hash = "sha256:33d52a30a56775380c6b4321b78136a410eb079132a80fe7120ddd4b954b4efa", size = 253106, upload-time = "2026-03-02T18:44:39.163Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/90/a1440bfa467a6dd9025ad80f3c239554de28aec49dacfb369fda92871556/langchain_huggingface-1.2.1-py3-none-any.whl", hash = "sha256:0930c216a457d2c8dc7b39a756c39c567f1d88593bfee2c3441f3ae718435f0f", size = 30924, upload-time = "2026-03-02T18:44:37.745Z" }, ] [[package]] @@ -1338,16 +1384,16 @@ wheels = [ [[package]] name = "langchain-nvidia-ai-endpoints" -version = "1.0.3" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "filetype" }, { name = "langchain-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/9e/30814da280f7a79b168f83180f6a0396c166f86a566e56bb9877bf562611/langchain_nvidia_ai_endpoints-1.0.3.tar.gz", hash = "sha256:11c48fd24e4a9d4c86c65bcef943400f4e709497c93254c7dc97c43f68c2be89", size = 46526, upload-time = "2026-01-28T22:04:33.93Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/d1/f72ec11097694e24d93268ab031c7ec56ab4bec1c43ef7814c659f3e2493/langchain_nvidia_ai_endpoints-1.1.0.tar.gz", hash = "sha256:048a3e6d7231365fdb9fff7bcff18ce6a516b25500681f51dcb69c39e82512a0", size = 47433, upload-time = "2026-02-25T21:48:16.87Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/04/c83f61106a245b74de11c1e075c1cc1e70462ece1dd9fc0584ad992a776d/langchain_nvidia_ai_endpoints-1.0.3-py3-none-any.whl", hash = "sha256:e5f170ad0a335637298bb90fb3df119793821e316355f61ab82f0106913eebbf", size = 50130, upload-time = "2026-01-28T22:04:33.065Z" }, + { url = "https://files.pythonhosted.org/packages/0b/22/5f07957028f7fa8c3d695934af8e7309bfd5ab43f2a7a756d3c3d6ce44f3/langchain_nvidia_ai_endpoints-1.1.0-py3-none-any.whl", hash = "sha256:eb04251b2b21facf9d6f2e6e7fa593b89e4f5023ebe3af1e02813512d1cd9687", size = 51514, upload-time = "2026-02-25T21:48:15.695Z" }, ] [[package]] @@ -1393,7 +1439,7 @@ wheels = [ [[package]] name = "langgraph" -version = "1.0.7" +version = "1.0.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -1403,9 +1449,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/5b/f72655717c04e33d3b62f21b166dc063d192b53980e9e3be0e2a117f1c9f/langgraph-1.0.7.tar.gz", hash = "sha256:0cfdfee51e6e8cfe503ecc7367c73933437c505b03fa10a85c710975c8182d9a", size = 497098, upload-time = "2026-01-22T16:57:47.303Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/92/14df6fefba28c10caf1cb05aa5b8c7bf005838fe32a86d903b6c7cc4018d/langgraph-1.0.10.tar.gz", hash = "sha256:73bd10ee14a8020f31ef07e9cd4c1a70c35cc07b9c2b9cd637509a10d9d51e29", size = 511644, upload-time = "2026-02-27T21:04:38.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/0e/fe80144e3e4048e5d19ccdb91ac547c1a7dc3da8dbd1443e210048194c14/langgraph-1.0.7-py3-none-any.whl", hash = "sha256:9d68e8f8dd8f3de2fec45f9a06de05766d9b075b78fb03171779893b7a52c4d2", size = 157353, upload-time = "2026-01-22T16:57:45.997Z" }, + { url = "https://files.pythonhosted.org/packages/5d/60/260e0c04620a37ba8916b712766c341cc5fc685dabc6948c899494bbc2ae/langgraph-1.0.10-py3-none-any.whl", hash = "sha256:7c298bef4f6ea292fcf9824d6088fe41a6727e2904ad6066f240c4095af12247", size = 160920, upload-time = "2026-02-27T21:04:35.932Z" }, ] [[package]] @@ -1423,15 +1469,15 @@ wheels = [ [[package]] name = "langgraph-prebuilt" -version = "1.0.7" +version = "1.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/59/711aecd1a50999456850dc328f3cad72b4372d8218838d8d5326f80cb76f/langgraph_prebuilt-1.0.7.tar.gz", hash = "sha256:38e097e06de810de4d0e028ffc0e432bb56d1fb417620fb1dfdc76c5e03e4bf9", size = 163692, upload-time = "2026-01-22T16:45:22.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442, upload-time = "2026-02-19T18:14:39.083Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/49/5e37abb3f38a17a3487634abc2a5da87c208cc1d14577eb8d7184b25c886/langgraph_prebuilt-1.0.7-py3-none-any.whl", hash = "sha256:e14923516504405bb5edc3977085bc9622c35476b50c1808544490e13871fe7c", size = 35324, upload-time = "2026-01-22T16:45:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648, upload-time = "2026-02-19T18:14:37.611Z" }, ] [[package]] @@ -1592,20 +1638,20 @@ name = "mcp" version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, - { name = "httpx" }, - { name = "httpx-sse" }, - { name = "jsonschema" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "python-multipart" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "sse-starlette" }, - { name = "starlette" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, - { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, + { name = "anyio", marker = "python_full_version >= '3.12'" }, + { name = "httpx", marker = "python_full_version >= '3.12'" }, + { name = "httpx-sse", marker = "python_full_version >= '3.12'" }, + { name = "jsonschema", marker = "python_full_version >= '3.12'" }, + { name = "pydantic", marker = "python_full_version >= '3.12'" }, + { name = "pydantic-settings", marker = "python_full_version >= '3.12'" }, + { name = "pyjwt", extra = ["crypto"], marker = "python_full_version >= '3.12'" }, + { name = "python-multipart", marker = "python_full_version >= '3.12'" }, + { name = "pywin32", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "sse-starlette", marker = "python_full_version >= '3.12'" }, + { name = "starlette", marker = "python_full_version >= '3.12'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.12'" }, + { name = "typing-inspection", marker = "python_full_version >= '3.12'" }, + { name = "uvicorn", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } wheels = [ @@ -1746,7 +1792,7 @@ name = "multiprocess" version = "0.70.18" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "dill" }, + { name = "dill", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/72/fd/2ae3826f5be24c6ed87266bc4e59c46ea5b059a103f3d7e7eb76a52aeecb/multiprocess-0.70.18.tar.gz", hash = "sha256:f9597128e6b3e67b23956da07cf3d2e5cba79e2f4e0fba8d7903636663ec6d0d", size = 1798503, upload-time = "2025-04-17T03:11:27.742Z" } wheels = [ @@ -1770,6 +1816,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "narwhals" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/59/81d0f4cad21484083466f278e6b392addd9f4205b48d45b5c8771670ebf8/narwhals-2.17.0.tar.gz", hash = "sha256:ebd5bc95bcfa2f8e89a8ac09e2765a63055162837208e67b42d6eeb6651d5e67", size = 620306, upload-time = "2026-02-23T09:44:34.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl", hash = "sha256:2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd", size = 444897, upload-time = "2026-02-23T09:44:32.006Z" }, +] + [[package]] name = "nest-asyncio" version = "1.6.0" @@ -1797,41 +1852,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, ] -[[package]] -name = "numpy" -version = "1.26.4" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.12'", -] -sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, - { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, - { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, - { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, - { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, - { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, - { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, - { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, - { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, - { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, - { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, - { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, - { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, -] - [[package]] name = "numpy" version = "2.4.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version == '3.12.*'", -] sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a5/34/2b1bc18424f3ad9af577f6ce23600319968a70575bd7db31ce66731bbef9/numpy-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0cce2a669e3c8ba02ee563c7835f92c153cf02edff1ae05e1823f1dde21b16a5", size = 16944563, upload-time = "2026-01-10T06:42:14.615Z" }, @@ -1890,62 +1914,124 @@ wheels = [ name = "nvidia-nat" version = "1.5.0a20260112" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", +] +dependencies = [ + { name = "aioboto3", marker = "python_full_version >= '3.12'" }, + { name = "authlib", marker = "python_full_version >= '3.12'" }, + { name = "click", marker = "python_full_version >= '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.12'" }, + { name = "datasets", marker = "python_full_version >= '3.12'" }, + { name = "expandvars", marker = "python_full_version >= '3.12'" }, + { name = "fastapi", marker = "python_full_version >= '3.12'" }, + { name = "httpx", marker = "python_full_version >= '3.12'" }, + { name = "jinja2", marker = "python_full_version >= '3.12'" }, + { name = "jsonpath-ng", marker = "python_full_version >= '3.12'" }, + { name = "mcp", marker = "python_full_version >= '3.12'" }, + { name = "nest-asyncio2", marker = "python_full_version >= '3.12'" }, + { name = "networkx", marker = "python_full_version >= '3.12'" }, + { name = "numpy", marker = "python_full_version >= '3.12'" }, + { name = "openinference-semantic-conventions", marker = "python_full_version >= '3.12'" }, + { name = "openpyxl", marker = "python_full_version >= '3.12'" }, + { name = "optuna", marker = "python_full_version >= '3.12'" }, + { name = "pip", marker = "python_full_version >= '3.12'" }, + { name = "pkce", marker = "python_full_version >= '3.12'" }, + { name = "pkginfo", marker = "python_full_version >= '3.12'" }, + { name = "platformdirs", marker = "python_full_version >= '3.12'" }, + { name = "pydantic", marker = "python_full_version >= '3.12'" }, + { name = "pymilvus", marker = "python_full_version >= '3.12'" }, + { name = "python-dotenv", marker = "python_full_version >= '3.12'" }, + { name = "pyyaml", marker = "python_full_version >= '3.12'" }, + { name = "ragas", marker = "python_full_version >= '3.12'" }, + { name = "rich", marker = "python_full_version >= '3.12'" }, + { name = "tabulate", marker = "python_full_version >= '3.12'" }, + { name = "uvicorn", extra = ["standard"], marker = "python_full_version >= '3.12'" }, + { name = "wikipedia", marker = "python_full_version >= '3.12'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/e0/c7426ed15d1eb528eb0c9135efb66da033b0a56b63f42d4099b2fe05fd24/nvidia_nat-1.5.0a20260112-py3-none-any.whl", hash = "sha256:3d05c948efe0e3ab58e3d7a58ab90510d1a1128eb678810e1ef62efc5dfc9681", size = 950027, upload-time = "2026-01-12T10:46:15.705Z" }, +] + +[[package]] +name = "nvidia-nat" +version = "1.5.0a20260223" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] +dependencies = [ + { name = "nvidia-nat-core", marker = "python_full_version < '3.12'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/7e/6e984de1473e8264d5cf6598d14f1c01f6dabf22f2fedda5f8e97140ae05/nvidia_nat-1.5.0a20260223-py3-none-any.whl", hash = "sha256:137461b310af90ed12e0496bac90ddb62297b00287707c80df48208437e2502a", size = 52704, upload-time = "2026-02-23T10:04:57.955Z" }, +] + +[[package]] +name = "nvidia-nat-core" +version = "1.5.0a20260223" +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aioboto3" }, { name = "authlib" }, { name = "click" }, { name = "colorama" }, - { name = "datasets" }, { name = "expandvars" }, { name = "fastapi" }, + { name = "flask" }, { name = "httpx" }, { name = "jinja2" }, { name = "jsonpath-ng" }, - { name = "mcp" }, { name = "nest-asyncio2" }, { name = "networkx" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "openinference-semantic-conventions" }, - { name = "openpyxl" }, { name = "optuna" }, + { name = "pandas" }, { name = "pip" }, { name = "pkce" }, { name = "pkginfo" }, { name = "platformdirs" }, + { name = "plotly" }, { name = "pydantic" }, + { name = "pyjwt" }, { name = "pymilvus" }, { name = "python-dotenv" }, + { name = "python-multipart" }, { name = "pyyaml" }, - { name = "ragas" }, { name = "rich" }, { name = "tabulate" }, + { name = "urllib3" }, { name = "uvicorn", extra = ["standard"] }, { name = "wikipedia" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/e0/c7426ed15d1eb528eb0c9135efb66da033b0a56b63f42d4099b2fe05fd24/nvidia_nat-1.5.0a20260112-py3-none-any.whl", hash = "sha256:3d05c948efe0e3ab58e3d7a58ab90510d1a1128eb678810e1ef62efc5dfc9681", size = 950027, upload-time = "2026-01-12T10:46:15.705Z" }, + { url = "https://files.pythonhosted.org/packages/b1/23/b043caadf08a72e4eb2c95bb65fed5083e7bf40af48ea92305fabf3b2820/nvidia_nat_core-1.5.0a20260223-py3-none-any.whl", hash = "sha256:5262cae48d66efbd53f98134e7820759121a1b4398b339e1d14d307ed2195a21", size = 762259, upload-time = "2026-02-23T10:01:44.692Z" }, ] [[package]] name = "nvidia-nat-langchain" -version = "1.5.0a20260112" +version = "1.5.0a20260223" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain" }, { name = "langchain-aws" }, { name = "langchain-classic" }, + { name = "langchain-community" }, { name = "langchain-core" }, + { name = "langchain-huggingface" }, { name = "langchain-litellm" }, { name = "langchain-milvus" }, { name = "langchain-nvidia-ai-endpoints" }, { name = "langchain-openai" }, { name = "langchain-tavily" }, { name = "langgraph" }, - { name = "nvidia-nat" }, + { name = "nvidia-nat-core" }, + { name = "openevals" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/84/2a/7a2cd2e7444ef03bdebb6c9637e63a9eee33da84e7c23baceb18f83f2250/nvidia_nat_langchain-1.5.0a20260112-py3-none-any.whl", hash = "sha256:cba64b0192d589f325cbbc2de60da8eb514efc27b49157b3eafca204ab989a55", size = 60925, upload-time = "2026-01-12T10:43:35.414Z" }, + { url = "https://files.pythonhosted.org/packages/98/65/e565dc570ecfdf4c4ca34d0d873794d33fdf11a8889a9b2b1a78ad15b589/nvidia_nat_langchain-1.5.0a20260223-py3-none-any.whl", hash = "sha256:87c70294c1f38fcd09252a79dd5e9038aee73326678dd9d7519b8064b914d7e4", size = 160480, upload-time = "2026-02-23T10:06:08.62Z" }, ] [[package]] @@ -2016,16 +2102,16 @@ requires-dist = [ { name = "langchain-elasticsearch", marker = "extra == 'all'", specifier = ">=0.3" }, { name = "langchain-elasticsearch", marker = "extra == 'elasticsearch'", specifier = ">=0.3" }, { name = "langchain-milvus", specifier = ">=0.3.0" }, - { name = "langchain-nvidia-ai-endpoints", specifier = ">=1.0.3" }, + { name = "langchain-nvidia-ai-endpoints", specifier = ">=1.1.0" }, { name = "langchain-openai", marker = "extra == 'all'", specifier = ">=0.2" }, { name = "langchain-openai", marker = "extra == 'ingest'", specifier = ">=0.2" }, { name = "langchain-openai", marker = "extra == 'rag'", specifier = ">=0.2" }, { name = "lark", specifier = ">=1.2.2" }, { name = "minio", specifier = ">=7.2,<8.0" }, - { name = "nv-ingest-api", marker = "extra == 'all'", specifier = "==26.1.1" }, - { name = "nv-ingest-api", marker = "extra == 'ingest'", specifier = "==26.1.1" }, - { name = "nv-ingest-client", marker = "extra == 'all'", specifier = "==26.1.1" }, - { name = "nv-ingest-client", marker = "extra == 'ingest'", specifier = "==26.1.1" }, + { name = "nv-ingest-api", marker = "extra == 'all'", specifier = "==26.1.2" }, + { name = "nv-ingest-api", marker = "extra == 'ingest'", specifier = "==26.1.2" }, + { name = "nv-ingest-client", marker = "extra == 'all'", specifier = "==26.1.2" }, + { name = "nv-ingest-client", marker = "extra == 'ingest'", specifier = "==26.1.2" }, { name = "opentelemetry-api", marker = "extra == 'all'", specifier = ">=1.29,<2.0" }, { name = "opentelemetry-api", marker = "extra == 'ingest'", specifier = ">=1.29,<2.0" }, { name = "opentelemetry-api", marker = "extra == 'rag'", specifier = ">=1.29,<2.0" }, @@ -2094,8 +2180,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coloredlogs" }, { name = "flatbuffers" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "packaging" }, { name = "protobuf" }, { name = "sympy" }, @@ -2129,6 +2214,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/df/c306f7375d42bafb379934c2df4c2fa3964656c8c782bac75ee10c102818/openai-2.15.0-py3-none-any.whl", hash = "sha256:6ae23b932cd7230f7244e52954daa6602716d6b9bf235401a107af731baea6c3", size = 1067879, upload-time = "2026-01-09T22:10:06.446Z" }, ] +[[package]] +name = "openevals" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain" }, + { name = "langchain-openai" }, + { name = "langsmith" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/37/31e23ef661fa4c3c6a3c979afd884b30205512b4dde680b36d5909550500/openevals-0.1.3.tar.gz", hash = "sha256:9b00df1a7738464676aa887d4d950b77d3ef7024f6e8a54be3a83c82f485ea65", size = 100828, upload-time = "2025-12-18T04:09:03.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/68/162b0d273ffef5b0ad557ebccb790725bf94d78969702324dd5726828cf0/openevals-0.1.3-py3-none-any.whl", hash = "sha256:aed448df0cfdded732e24cda026eda065435a71ffb8c406a3ce73e590156d9f9", size = 67802, upload-time = "2025-12-18T04:09:01.59Z" }, +] + [[package]] name = "openinference-semantic-conventions" version = "0.1.25" @@ -2143,7 +2243,7 @@ name = "openpyxl" version = "3.1.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "et-xmlfile" }, + { name = "et-xmlfile", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } wheels = [ @@ -2392,8 +2492,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, { name = "colorlog" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "packaging" }, { name = "pyyaml" }, { name = "sqlalchemy" }, @@ -2506,8 +2605,7 @@ name = "pandas" version = "2.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "python-dateutil" }, { name = "pytz" }, { name = "tzdata" }, @@ -2668,6 +2766,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] +[[package]] +name = "plotly" +version = "6.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/fb/41efe84970cfddefd4ccf025e2cbfafe780004555f583e93dba3dac2cdef/plotly-6.6.0.tar.gz", hash = "sha256:b897f15f3b02028d69f755f236be890ba950d0a42d7dfc619b44e2d8cea8748c", size = 7027956, upload-time = "2026-03-02T21:10:25.321Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/d2/c6e44dba74f17c6216ce1b56044a9b93a929f1c2d5bdaff892512b260f5e/plotly-6.6.0-py3-none-any.whl", hash = "sha256:8d6daf0f87412e0c0bfe72e809d615217ab57cc715899a1e5145135a7800d1d0", size = 9910315, upload-time = "2026-03-02T21:10:18.131Z" }, +] + [[package]] name = "ply" version = "3.11" @@ -2963,7 +3074,7 @@ wheels = [ [package.optional-dependencies] crypto = [ - { name = "cryptography" }, + { name = "cryptography", marker = "python_full_version >= '3.12'" }, ] [[package]] @@ -2994,8 +3105,7 @@ name = "pymilvus-model" version = "0.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "onnxruntime" }, { name = "protobuf" }, { name = "scipy" }, @@ -3142,7 +3252,8 @@ source = { editable = "." } dependencies = [ { name = "langchain-classic" }, { name = "langgraph" }, - { name = "nvidia-nat" }, + { name = "nvidia-nat", version = "1.5.0a20260112", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "nvidia-nat", version = "1.5.0a20260223", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "nvidia-nat-langchain" }, { name = "nvidia-rag", extra = ["rag"] }, { name = "transformers" }, @@ -3151,7 +3262,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "langchain-classic" }, - { name = "langgraph", specifier = ">=1.0.7" }, + { name = "langgraph", specifier = ">=1.0.8" }, { name = "nvidia-nat", specifier = ">=1.5.0a0,<2.0" }, { name = "nvidia-nat-langchain", specifier = ">=1.5.0a0,<2.0" }, { name = "nvidia-rag", extras = ["rag"], editable = "../../" }, @@ -3163,19 +3274,18 @@ name = "ragas" version = "0.2.15" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "appdirs" }, - { name = "datasets" }, - { name = "diskcache" }, - { name = "langchain" }, - { name = "langchain-community" }, - { name = "langchain-core" }, - { name = "langchain-openai" }, - { name = "nest-asyncio" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "tiktoken" }, + { name = "appdirs", marker = "python_full_version >= '3.12'" }, + { name = "datasets", marker = "python_full_version >= '3.12'" }, + { name = "diskcache", marker = "python_full_version >= '3.12'" }, + { name = "langchain", marker = "python_full_version >= '3.12'" }, + { name = "langchain-community", marker = "python_full_version >= '3.12'" }, + { name = "langchain-core", marker = "python_full_version >= '3.12'" }, + { name = "langchain-openai", marker = "python_full_version >= '3.12'" }, + { name = "nest-asyncio", marker = "python_full_version >= '3.12'" }, + { name = "numpy", marker = "python_full_version >= '3.12'" }, + { name = "openai", marker = "python_full_version >= '3.12'" }, + { name = "pydantic", marker = "python_full_version >= '3.12'" }, + { name = "tiktoken", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6c/0f/04fddfa94744b1c3d8901aed8832a6b4193cc8e4886881f1bb88ff055350/ragas-0.2.15.tar.gz", hash = "sha256:2d0cd77b315a9c9c02ceb0a19ca8a48e82e1d02416587a2944ea51e6e327cd7b", size = 40867766, upload-time = "2025-04-24T16:39:28.734Z" } wheels = [ @@ -3438,8 +3548,7 @@ name = "scipy" version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } wheels = [ @@ -3569,7 +3678,7 @@ name = "sse-starlette" version = "3.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, + { name = "anyio", marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/3c/fa6517610dc641262b77cc7bf994ecd17465812c1b0585fe33e11be758ab/sse_starlette-3.0.3.tar.gz", hash = "sha256:88cfb08747e16200ea990c8ca876b03910a23b547ab3bd764c0d8eb81019b971", size = 21943, upload-time = "2025-10-30T18:44:20.117Z" } wheels = [ @@ -3703,8 +3812,7 @@ version = "5.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, - { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "numpy", version = "2.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "numpy" }, { name = "packaging" }, { name = "pyyaml" }, { name = "regex" }, @@ -3976,6 +4084,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, ] +[[package]] +name = "werkzeug" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, +] + [[package]] name = "wikipedia" version = "1.4.0" diff --git a/pyproject.toml b/pyproject.toml index 6baa185b2..4b1faf01f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "langchain>=1.2.7", "langchain-community>=0.4", "langchain-milvus>=0.3.0", - "langchain-nvidia-ai-endpoints>=1.0.3", + "langchain-nvidia-ai-endpoints>=1.1.0", "minio>=7.2,<8.0", "pdfplumber>=0.11.9", "pydantic>=2.11,<3.0", diff --git a/uv.lock b/uv.lock index 9e77bddac..3c72857d4 100644 --- a/uv.lock +++ b/uv.lock @@ -1309,16 +1309,16 @@ wheels = [ [[package]] name = "langchain-nvidia-ai-endpoints" -version = "1.0.3" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "filetype" }, { name = "langchain-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/9e/30814da280f7a79b168f83180f6a0396c166f86a566e56bb9877bf562611/langchain_nvidia_ai_endpoints-1.0.3.tar.gz", hash = "sha256:11c48fd24e4a9d4c86c65bcef943400f4e709497c93254c7dc97c43f68c2be89", size = 46526, upload-time = "2026-01-28T22:04:33.93Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/d1/f72ec11097694e24d93268ab031c7ec56ab4bec1c43ef7814c659f3e2493/langchain_nvidia_ai_endpoints-1.1.0.tar.gz", hash = "sha256:048a3e6d7231365fdb9fff7bcff18ce6a516b25500681f51dcb69c39e82512a0", size = 47433, upload-time = "2026-02-25T21:48:16.87Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/04/c83f61106a245b74de11c1e075c1cc1e70462ece1dd9fc0584ad992a776d/langchain_nvidia_ai_endpoints-1.0.3-py3-none-any.whl", hash = "sha256:e5f170ad0a335637298bb90fb3df119793821e316355f61ab82f0106913eebbf", size = 50130, upload-time = "2026-01-28T22:04:33.065Z" }, + { url = "https://files.pythonhosted.org/packages/0b/22/5f07957028f7fa8c3d695934af8e7309bfd5ab43f2a7a756d3c3d6ce44f3/langchain_nvidia_ai_endpoints-1.1.0-py3-none-any.whl", hash = "sha256:eb04251b2b21facf9d6f2e6e7fa593b89e4f5023ebe3af1e02813512d1cd9687", size = 51514, upload-time = "2026-02-25T21:48:15.695Z" }, ] [[package]] @@ -1349,7 +1349,7 @@ wheels = [ [[package]] name = "langgraph" -version = "1.0.7" +version = "1.0.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -1359,9 +1359,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/5b/f72655717c04e33d3b62f21b166dc063d192b53980e9e3be0e2a117f1c9f/langgraph-1.0.7.tar.gz", hash = "sha256:0cfdfee51e6e8cfe503ecc7367c73933437c505b03fa10a85c710975c8182d9a", size = 497098, upload-time = "2026-01-22T16:57:47.303Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/92/14df6fefba28c10caf1cb05aa5b8c7bf005838fe32a86d903b6c7cc4018d/langgraph-1.0.10.tar.gz", hash = "sha256:73bd10ee14a8020f31ef07e9cd4c1a70c35cc07b9c2b9cd637509a10d9d51e29", size = 511644, upload-time = "2026-02-27T21:04:38.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/0e/fe80144e3e4048e5d19ccdb91ac547c1a7dc3da8dbd1443e210048194c14/langgraph-1.0.7-py3-none-any.whl", hash = "sha256:9d68e8f8dd8f3de2fec45f9a06de05766d9b075b78fb03171779893b7a52c4d2", size = 157353, upload-time = "2026-01-22T16:57:45.997Z" }, + { url = "https://files.pythonhosted.org/packages/5d/60/260e0c04620a37ba8916b712766c341cc5fc685dabc6948c899494bbc2ae/langgraph-1.0.10-py3-none-any.whl", hash = "sha256:7c298bef4f6ea292fcf9824d6088fe41a6727e2904ad6066f240c4095af12247", size = 160920, upload-time = "2026-02-27T21:04:35.932Z" }, ] [[package]] @@ -1379,15 +1379,15 @@ wheels = [ [[package]] name = "langgraph-prebuilt" -version = "1.0.7" +version = "1.0.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/59/711aecd1a50999456850dc328f3cad72b4372d8218838d8d5326f80cb76f/langgraph_prebuilt-1.0.7.tar.gz", hash = "sha256:38e097e06de810de4d0e028ffc0e432bb56d1fb417620fb1dfdc76c5e03e4bf9", size = 163692, upload-time = "2026-01-22T16:45:22.801Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442, upload-time = "2026-02-19T18:14:39.083Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/49/5e37abb3f38a17a3487634abc2a5da87c208cc1d14577eb8d7184b25c886/langgraph_prebuilt-1.0.7-py3-none-any.whl", hash = "sha256:e14923516504405bb5edc3977085bc9622c35476b50c1808544490e13871fe7c", size = 35324, upload-time = "2026-01-22T16:45:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648, upload-time = "2026-02-19T18:14:37.611Z" }, ] [[package]] @@ -1918,7 +1918,7 @@ requires-dist = [ { name = "langchain-elasticsearch", marker = "extra == 'all'", specifier = ">=0.3" }, { name = "langchain-elasticsearch", marker = "extra == 'elasticsearch'", specifier = ">=0.3" }, { name = "langchain-milvus", specifier = ">=0.3.0" }, - { name = "langchain-nvidia-ai-endpoints", specifier = ">=1.0.3" }, + { name = "langchain-nvidia-ai-endpoints", specifier = ">=1.1.0" }, { name = "langchain-openai", marker = "extra == 'all'", specifier = ">=0.2" }, { name = "langchain-openai", marker = "extra == 'ingest'", specifier = ">=0.2" }, { name = "langchain-openai", marker = "extra == 'rag'", specifier = ">=0.2" }, From a85a69ee15aa60efe2ae4f19504655846774c2e1 Mon Sep 17 00:00:00 2001 From: Minh Nguyen Date: Wed, 4 Mar 2026 15:41:48 +0700 Subject: [PATCH 18/52] Update NIM wait times and patch VSS embed/rerank models (#397) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update NIM wait times and patch VSS embed/rerank models Adjust expected NIM model loading wait from 2-5 min to ~10 min for RTX PRO 6000 hardware. Add explicit patching of VSS config.yaml to align embedding and reranker model names with RAG stack defaults. Signed-off-by: Minh Nguyen Made-with: Cursor * Update VSS prompts to match default format and use seconds-based queries - Align consumer VSS prompts with VSS config.yaml defaults (sports-adapted): caption, caption_summarization, summary_aggregation with proper dedup/merge logic - Extract RAG embed/rerank model names dynamically from compose file - Add parse_compose_default helper to avoid hardcoded model names - Change time-range query from MM:SS to seconds format for VSS compatibility Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen Made-with: Cursor * Clean up notebook: rename variable, remove config overrides - Rename _rag_compose to _rag_compose_path for clarity - Remove hardcoded max_tokens and batch_size patches from VSS config - Simplify time-range query cell comments Signed-off-by: Minh Nguyen Made-with: Cursor * Rename helper function and revert prompts to MM:SS format - Rename parse_compose_default to extract_rag_default_compose_var - Revert VSS prompts to MM:SS timestamp conversion style - Revert time-range query to MM:SS format Signed-off-by: Minh Nguyen Made-with: Cursor * Add GPU assignment table and update NIM container names Update notebook to reflect renamed NIM containers (nemoretriever-* → nemotron-*) and add default GPU assignment table for RTX PRO 6000 / H100 hardware. Signed-off-by: Minh Nguyen Made-with: Cursor --------- Signed-off-by: Minh Nguyen --- notebooks/rag_event_ingest.ipynb | 59 ++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/notebooks/rag_event_ingest.ipynb b/notebooks/rag_event_ingest.ipynb index 372a6cfca..6609a6e1d 100644 --- a/notebooks/rag_event_ingest.ipynb +++ b/notebooks/rag_event_ingest.ipynb @@ -30,6 +30,16 @@ "### Hardware\n", "- **GPU**: 4x RTX PRO 6000 Blackwell or 4x H100\n", "\n", + "#### Default GPU Assignment\n", + "\n", + "| GPU | Service |\n", + "|-----|---------|\n", + "| 0 | RAG NIMs (Embedding, Reranker) |\n", + "| 1 | RAG LLM NIM (Llama-3.3-Nemotron-Super-49B) |\n", + "| 2 | VSS LLM NIM (Llama-3.1-8B-Instruct) |\n", + "| 3 | VSS VLM / via-server (Cosmos-Reason2-8B) |\n", + "\n", + "\n", "### Software (pre-installed required)\n", "- Ubuntu 22.04 or later\n", "- Docker 24.0+ with Docker Compose v2\n", @@ -369,6 +379,18 @@ " print(f\"[ERROR] {e}\")\n", " return None\n", "\n", + "def extract_rag_default_compose_var(file_path, var_name):\n", + " \"\"\"Extract default value from ${VAR:-default} pattern in a compose file.\"\"\"\n", + " pattern = re.compile(r'\\$\\{' + var_name + r':-\"?([^\"}\\s]+)\"?\\}')\n", + " with open(file_path) as f:\n", + " for line in f:\n", + " if line.lstrip().startswith('#'):\n", + " continue\n", + " m = pattern.search(line)\n", + " if m:\n", + " return m.group(1)\n", + " return None\n", + "\n", "print(f\"[OK] Helpers loaded | Host IP: {get_host_ip()}\")\n" ] }, @@ -429,14 +451,14 @@ "ip = get_host_ip()\n", "print(f\"\\nRAG deployed: http://{ip}:8081 (server) | http://{ip}:8082 (ingestor) | http://{ip}:8090 (UI)\")\n", "print(f\"COLLECTION_NAME: {MINIO_COLLECTION}\")\n", - "print(\"Wait 2-5 minutes for NIMs to load models, then run the status check cell.\")\n" + "print(\"Wait ~10 minutes for NIMs to load models, then run the status check cell.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Verify RAG services are healthy. Wait 2-5 minutes for NIMs to load models.\n", + "Verify RAG services are healthy. Wait ~10 minutes for NIMs to load models.\n", "\n", "The deployment status should be:\n", "```\n", @@ -448,8 +470,8 @@ "milvus-etcd Up 2 minutes (healthy)\n", "milvus-minio Up 2 minutes (healthy)\n", "nim-llm-ms Up 2 minutes (healthy)\n", - "nemoretriever-embedding-ms Up 2 minutes (healthy)\n", - "nemoretriever-ranking-ms Up 2 minutes (healthy)\n", + "nemotron-embedding-ms Up 2 minutes (healthy)\n", + "nemotron-ranking-ms Up 2 minutes (healthy)\n", "```\n", "\n" ] @@ -461,7 +483,7 @@ "outputs": [], "source": [ "# Check service status and print access URLs\n", - "print(\"Wait 2-5 minutes for services to become healthy.\")\n", + "print(\"Wait ~10 minutes for services to become healthy.\")\n", "print(\"Run this cell again after waiting.\\n\")\n", "\n", "ip = get_host_ip()\n", @@ -476,7 +498,7 @@ " except requests.Timeout:\n", " s = \"[TIMEOUT]\"\n", " print(f\" {s} {name}: http://{ip}:{port}\")\n", - "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(rag|milvus|ingestor|nim|nemoretriever|rerankqa|NAMES)'\")\n" + "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(rag|milvus|ingestor|nim|nemotron|NAMES)'\")\n" ] }, { @@ -500,8 +522,13 @@ "VSS_GPU_DEVICE = 2 # GPU for VSS LLM NIM\n", "VSS_VLM_GPU_DEVICE = 3 # GPU for VLM (via-server with Cosmos-Reason2)\n", "\n", + "_rag_compose_path = os.path.join(RAG_REPO_DIR, \"deploy/compose/docker-compose-rag-server.yaml\")\n", + "RAG_EMBED_MODEL = extract_rag_default_compose_var(_rag_compose_path, \"APP_EMBEDDINGS_MODELNAME\")\n", + "RAG_RERANK_MODEL = extract_rag_default_compose_var(_rag_compose_path, \"APP_RANKING_MODELNAME\")\n", + "print(f\"RAG models from compose: embed={RAG_EMBED_MODEL}, rerank={RAG_RERANK_MODEL}\")\n", + "\n", "# Only deploy LLM for VSS; embedding and reranker are shared from RAG stack\n", - "# RAG NIMs on nvidia-rag network: nemoretriever-embedding-ms:8000, nemoretriever-ranking-ms:8000\n", + "# RAG NIMs on nvidia-rag network: nemotron-embedding-ms:8000, nemotron-ranking-ms:8000\n", "NIM_IMAGES = {\n", " \"vss-llm\": (\"nvcr.io/nim/meta/llama-3.1-8b-instruct:1.12.0\", VSS_LLM_PORT),\n", "}\n", @@ -572,6 +599,9 @@ " cfg = re.sub(r\"http://[^:]+:8007/v1\", f\"http://host.docker.internal:{VSS_LLM_PORT}/v1\", cfg)\n", " cfg = re.sub(r\"http://[^:]+:8006/v1\", \"http://host.docker.internal:9080/v1\", cfg)\n", " cfg = re.sub(r\"http://[^:]+:8005/v1\", \"http://host.docker.internal:1976/v1\", cfg)\n", + " cfg = re.sub(r\"model:\\s*nvidia/[\\w.-]+-embed[\\w.-]+\", f\"model: {RAG_EMBED_MODEL}\", cfg)\n", + " cfg = re.sub(r\"model:\\s*nvidia/[\\w.-]+-re?rank[\\w.-]+\", f\"model: {RAG_RERANK_MODEL}\", cfg)\n", + "\n", " open(config_file, \"w\").write(cfg)\n", "\n", "cmd = f\"cd {vss_deploy_dir} && set -a && source .env && set +a && docker compose up -d\"\n", @@ -579,18 +609,27 @@ "\n", "ip = get_host_ip()\n", "print(f\"\\nVSS deployed: http://{ip}:{VSS_UI_PORT} (UI) | http://{ip}:{VSS_API_PORT} (API)\")\n", - "print(\"Embedding/Reranker: shared from RAG stack (nemoretriever-embedding-ms, nemoretriever-ranking-ms)\")\n", - "print(\"Wait 2-5 minutes for NIMs to load models, then run the status check cell.\")\n" + "print(\"Embedding/Reranker: shared from RAG stack (nemotron-embedding-ms, nemotron-ranking-ms)\")\n", + "print(\"Wait ~10 minutes for NIMs to load models, then run the status check cell.\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Verify VSS services are healthy. Wait 2-5 minutes for NIMs to load models.\n", + "Verify VSS services are healthy. Wait ~10 minutes for NIMs to load models.\n", "\n", "> **Note**: Embedding and reranker NIMs are shared from the RAG stack (`nemoretriever-embedding-ms`, `nemoretriever-ranking-ms`) — no separate VSS containers needed.\n", "\n", + "The health check output should be:\n", + "```\n", + " [OK] VSS UI: http://:9110\n", + " [OK] VSS API: http://:8110\n", + " [OK] VSS LLM NIM: http://:8107\n", + " [OK] Embedding (shared from RAG): http://:9080\n", + " [OK] Reranker (shared from RAG): http://:1976\n", + "```\n", + "\n", "The deployment status should be:\n", "```\n", "NAMES STATUS\n", From 8fb149b1d72d4dbdea876856604972cc7a5bac17 Mon Sep 17 00:00:00 2001 From: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:45:44 +0530 Subject: [PATCH 19/52] docs: Add release note for v2.5.0 release (#401) Co-authored-by: Kurt Heiss --- docs/release-notes.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index c0a359206..2a3550fad 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,38 @@ This documentation contains the release notes for [NVIDIA RAG Blueprint](readme. +## Release 2.5.0 (2026-03-12) + +This release introduces support for the Nemotron-super-3 model, updates NIMs to the latest versions, upgrades NV-Ingest, and adds continuous ingestion along with RTX 6000 MIG support. + +### Highlights + +This release includes the following key updates: + +- **Nemotron-super-3 model support.** You can now integrate the Nemotron-super-3 model by following the steps outlined in [Change the Inference or Embedding Model](change-model.md). +- **NIMs updated to latest versions.** + The following model updates are included: + - `nvidia/llama-3.2-nv-embedqa-1b-v2` → `nvidia/llama-nemotron-embed-1b-v2` + - `nvidia/llama-3.2-nv-rerankqa-1b-v2` → `nvidia/llama-nemotron-rerank-1b-v2` + - `nemoretriever-page-elements-v3` → `nemotron-page-elements-v3` + - `nemoretriever-graphic-elements-v1` → `nemotron-graphic-elements-v1` + - `nemoretriever-table-structure-v1` → `nemotron-table-structure-v1` + - `nvidia/llama-3.2-nemoretriever-1b-vlm-embed-v1` → `nvidia/llama-nemotron-embed-vl-1b-v2` + - `nemoretriever-ocr-v1` to `nemotron-ocr-v1` +- Updated NVIngest to [version 26.1.2](https://github.com/NVIDIA/NeMo-Retriever/releases/tag/26.1.2). +- Added an example demonstrating the continuous ingestion pipeline. For more information, see [rag_event_ingest.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_event_ingest.ipynb). +- **Added MIG support for RTX 6000.** For details, refer to [MIG Deployment](mig-deployment.md) and use `values-mig-rtx6000.yaml` and `mig-config-rtx6000.yaml`. +- Added documentation for the experimental Nemotron-parse-only ingestion pipeline. This configuration allows you to perform extraction using only Nemotron Parse through NV-Ingest, without relying on OCR, page-elements, graphic-elements, or table-structure NIMs. For more information, refer to [nemotron-parse-extraction.md](nemotron-parse-extraction.md#experimental-nemotron-parse-only-extraction). +- Several bug fixes, including frontend CVE resolutions, improved multimodal content concatenation for VLM embeddings, enhanced VDB serialization for high-concurrency parallel ingestion, and updates to observability and NeMo Guardrails configurations. + +### Fixed Known Issues + +The following known issues have been resolved in this release: + +- Addressed frontend CVEs. + +- Resolved VDB indexing issues during high-concurrency batch parallel ingestion by implementing VDB serialization. + ## Release 2.4.0 (2026-02-20) This release adds new features to the RAG pipeline for supporting agent workflows and enhances generations with VLMs augmenting multimodal input. From e7ad2fdbe4c95909cac8709218fd156e134fe379 Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Thu, 5 Mar 2026 10:17:48 +0530 Subject: [PATCH 20/52] Upgraded to GA containers for page-elements, graphic-elements, table-structure (#403) Signed-off-by: Swapnil Masurekar --- deploy/compose/nims.yaml | 6 +++--- deploy/helm/nvidia-blueprint-rag/values.yaml | 12 ++++++------ deploy/workbench/compose.yaml | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/deploy/compose/nims.yaml b/deploy/compose/nims.yaml index eb29bfc06..497ed6646 100644 --- a/deploy/compose/nims.yaml +++ b/deploy/compose/nims.yaml @@ -119,7 +119,7 @@ services: profiles: ["", "rag", "vlm-generation"] page-elements: - image: ${YOLOX_IMAGE:-nvcr.io/nvstaging/nim/nemotron-page-elements-v3}:${YOLOX_TAG:-1.8.0-rc.20260218170155-6b1b2aa9dfefc9c3} + image: ${YOLOX_IMAGE:-nvcr.io/nim/nvidia/nemotron-page-elements-v3}:${YOLOX_TAG:-1.8.0} shm_size: 16gb ports: - "8000:8000" @@ -157,7 +157,7 @@ services: profiles: ["", "ingest", "vlm-ingest"] graphic-elements: - image: ${YOLOX_GRAPHIC_ELEMENTS_IMAGE:-nvcr.io/nvstaging/nim/nemotron-graphic-elements-v1}:${YOLOX_GRAPHIC_ELEMENTS_TAG:-1.8.0-rc.20260218170144-35d5a3f270662864} + image: ${YOLOX_GRAPHIC_ELEMENTS_IMAGE:-nvcr.io/nim/nvidia/nemotron-graphic-elements-v1}:${YOLOX_GRAPHIC_ELEMENTS_TAG:-1.8.0} shm_size: 16gb ports: - "8003:8000" @@ -183,7 +183,7 @@ services: profiles: ["", "ingest", "vlm-ingest"] table-structure: - image: ${YOLOX_TABLE_STRUCTURE_IMAGE:-nvcr.io/nvstaging/nim/nemotron-table-structure-v1}:${YOLOX_TABLE_STRUCTURE_TAG:-1.8.0-rc.20260218170204-cca5cb850146dbbc} + image: ${YOLOX_TABLE_STRUCTURE_IMAGE:-nvcr.io/nim/nvidia/nemotron-table-structure-v1}:${YOLOX_TABLE_STRUCTURE_TAG:-1.8.0} shm_size: 16gb ports: - "8006:8000" diff --git a/deploy/helm/nvidia-blueprint-rag/values.yaml b/deploy/helm/nvidia-blueprint-rag/values.yaml index 986caacc9..a3b501983 100644 --- a/deploy/helm/nvidia-blueprint-rag/values.yaml +++ b/deploy/helm/nvidia-blueprint-rag/values.yaml @@ -1050,8 +1050,8 @@ nv-ingest: tolerations: [] replicaCount: 1 image: - repository: nvcr.io/nvstaging/nim/nemotron-graphic-elements-v1 - tag: "1.8.0-rc.20260218170144-35d5a3f270662864" + repository: nvcr.io/nim/nvidia/nemotron-graphic-elements-v1 + tag: "1.8.0" env: - name: NIM_HTTP_API_PORT value: "8000" @@ -1083,8 +1083,8 @@ nv-ingest: tolerations: [] replicaCount: 1 image: - repository: nvcr.io/nvstaging/nim/nemotron-page-elements-v3 - tag: "1.8.0-rc.20260218170155-6b1b2aa9dfefc9c3" + repository: nvcr.io/nim/nvidia/nemotron-page-elements-v3 + tag: "1.8.0" env: - name: NIM_HTTP_API_PORT value: "8000" @@ -1134,8 +1134,8 @@ nv-ingest: tolerations: [] replicaCount: 1 image: - repository: nvcr.io/nvstaging/nim/nemotron-table-structure-v1 - tag: "1.8.0-rc.20260218170204-cca5cb850146dbbc" + repository: nvcr.io/nim/nvidia/nemotron-table-structure-v1 + tag: "1.8.0" env: - name: NIM_HTTP_API_PORT value: "8000" diff --git a/deploy/workbench/compose.yaml b/deploy/workbench/compose.yaml index c6867b9d6..7bdaadf47 100644 --- a/deploy/workbench/compose.yaml +++ b/deploy/workbench/compose.yaml @@ -86,7 +86,7 @@ services: profiles: ["local"] page-elements: - image: ${YOLOX_IMAGE:-nvcr.io/nvstaging/nim/nemotron-page-elements-v3}:${YOLOX_TAG:-1.8.0-rc.20260218170155-6b1b2aa9dfefc9c3} + image: ${YOLOX_IMAGE:-nvcr.io/nim/nvidia/nemotron-page-elements-v3}:${YOLOX_TAG:-1.8.0} ports: - "8000:8000" - "8001:8001" @@ -122,7 +122,7 @@ services: profiles: ["local"] graphic-elements: - image: ${YOLOX_GRAPHIC_ELEMENTS_IMAGE:-nvcr.io/nvstaging/nim/nemotron-graphic-elements-v1}:${YOLOX_GRAPHIC_ELEMENTS_TAG:-1.8.0-rc.20260218170144-35d5a3f270662864} + image: ${YOLOX_GRAPHIC_ELEMENTS_IMAGE:-nvcr.io/nim/nvidia/nemotron-graphic-elements-v1}:${YOLOX_GRAPHIC_ELEMENTS_TAG:-1.8.0} ports: - "8003:8000" - "8004:8001" @@ -147,7 +147,7 @@ services: profiles: ["local"] table-structure: - image: ${YOLOX_TABLE_STRUCTURE_IMAGE:-nvcr.io/nvstaging/nim/nemotron-table-structure-v1}:${YOLOX_TABLE_STRUCTURE_TAG:-1.8.0-rc.20260218170204-cca5cb850146dbbc} + image: ${YOLOX_TABLE_STRUCTURE_IMAGE:-nvcr.io/nim/nvidia/nemotron-table-structure-v1}:${YOLOX_TABLE_STRUCTURE_TAG:-1.8.0} ports: - "8006:8000" - "8007:8001" From 16f5ad136ee09152447dca778a94a7ccf4a212a3 Mon Sep 17 00:00:00 2001 From: Nikhil Kulkarni Date: Sat, 7 Mar 2026 01:39:41 +0530 Subject: [PATCH 21/52] Add SHM size 16gb to reranking and VLM NIM in compose (#405) --- deploy/compose/nims.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/compose/nims.yaml b/deploy/compose/nims.yaml index 497ed6646..2bca3dce2 100644 --- a/deploy/compose/nims.yaml +++ b/deploy/compose/nims.yaml @@ -108,6 +108,7 @@ services: interval: 10s timeout: 20s retries: 100 + shm_size: 16GB deploy: resources: reservations: @@ -323,6 +324,7 @@ services: interval: 10s timeout: 20s retries: 100 + shm_size: 16GB deploy: resources: reservations: From c347f4ff5b913abcc10184b4e360102d6d5dfb80 Mon Sep 17 00:00:00 2001 From: kumar-punit Date: Sat, 7 Mar 2026 01:40:23 +0530 Subject: [PATCH 22/52] Fixed the query to be derived from messages if query is not explicitly given in argument and adding messages list in logs (#404) --- src/nvidia_rag/rag_server/server.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/nvidia_rag/rag_server/server.py b/src/nvidia_rag/rag_server/server.py index dbd5698ed..2ec1db341 100644 --- a/src/nvidia_rag/rag_server/server.py +++ b/src/nvidia_rag/rag_server/server.py @@ -741,6 +741,26 @@ def validate_messages_structure(cls, values): raise ValueError("The last message must have role='user'") return values + @model_validator(mode="before") + @classmethod + def derive_query_from_messages(cls, data): + """When query is not explicitly provided but messages are, derive query from the last user message.""" + if isinstance(data, dict) and "query" not in data: + messages = data.get("messages", []) + for msg in reversed(messages): + if ( + isinstance(msg, dict) + and msg.get("role") == "user" + and msg.get("content") + ): + data["query"] = msg["content"] + break + else: + raise ValueError( + "Either 'query' must be provided or 'messages' must contain at least one user message with content." + ) + return data + # Define the summary response model class SummaryResponse(BaseModel): @@ -1596,6 +1616,11 @@ def sanitize_query_for_logging(query): request_data = { "query": sanitize_query_for_logging(data.query), + "messages": [ + {"role": msg.role, "content": msg.content} for msg in data.messages + ] + if data.messages + else [], "reranker_top_k": data.reranker_top_k, "vdb_top_k": data.vdb_top_k, "collection_names": data.collection_names, From 1a11733169fb17e80537f7ded879472a1ada4d8c Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Mon, 9 Mar 2026 23:24:16 +0530 Subject: [PATCH 23/52] Added url for nvidia/llama-nemotron-rerank-1b-v2 model for cloud endpoint (#412) Signed-off-by: Swapnil Masurekar --- deploy/compose/.env | 2 +- docs/python-client.md | 4 ++-- docs/retrieval-only-deployment.md | 2 +- docs/text_only_ingest.md | 2 +- notebooks/building_rag_vdb_operator.ipynb | 2 +- notebooks/image_input.ipynb | 2 +- notebooks/rag_library_lite_usage.ipynb | 2 +- notebooks/rag_library_usage.ipynb | 4 ++-- notebooks/summarization.ipynb | 2 +- tests/unit/test_rag_server/test_rag_server_health.py | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/deploy/compose/.env b/deploy/compose/.env index cc80cfaf0..e9c4cd68d 100644 --- a/deploy/compose/.env +++ b/deploy/compose/.env @@ -45,7 +45,7 @@ export YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=grpc # export APP_FILTEREXPRESSIONGENERATOR_MODELNAME=nvidia/llama-3.3-nemotron-super-49b-v1.5 # export APP_FILTEREXPRESSIONGENERATOR_SERVERURL="" # export SUMMARY_LLM="nvidia/llama-3.3-nemotron-super-49b-v1.5" -# export APP_RANKING_SERVERURL="" +# export APP_RANKING_SERVERURL="https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" # export SUMMARY_LLM_SERVERURL="" # export OCR_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr # export OCR_INFER_PROTOCOL=http diff --git a/docs/python-client.md b/docs/python-client.md index e052b5e91..e3d8b3119 100644 --- a/docs/python-client.md +++ b/docs/python-client.md @@ -362,7 +362,7 @@ from nvidia_rag.utils.configuration import NvidiaRAGConfig # }, # "ranking": { # "model_name": "nvidia/llama-nemotron-rerank-1b-v2", -# "server_url": "", +# "server_url": "https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking", # }, # }) @@ -371,7 +371,7 @@ config_rag = NvidiaRAGConfig.from_yaml("config.yaml") # Update config for cloud deployment if using Option 2 if DEPLOYMENT_MODE == "cloud": config_rag.embeddings.server_url = "https://integrate.api.nvidia.com/v1" - config_rag.ranking.server_url = "" # Empty uses NVIDIA API catalog + config_rag.ranking.server_url = "https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" config_rag.llm.server_url = "" # Empty uses NVIDIA API catalog # Initialize NvidiaRAG with config diff --git a/docs/retrieval-only-deployment.md b/docs/retrieval-only-deployment.md index 7f7f94475..09d70c311 100644 --- a/docs/retrieval-only-deployment.md +++ b/docs/retrieval-only-deployment.md @@ -116,7 +116,7 @@ For an even lighter deployment, use [NVIDIA-hosted NIMs](deploy-docker-nvidia-ho ```bash # Configure to use NVIDIA-hosted endpoints export APP_EMBEDDINGS_SERVERURL="" -export APP_RANKING_SERVERURL="" +export APP_RANKING_SERVERURL="https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" ``` :::{note} diff --git a/docs/text_only_ingest.md b/docs/text_only_ingest.md index 10cb6e9b1..cf61f69ec 100644 --- a/docs/text_only_ingest.md +++ b/docs/text_only_ingest.md @@ -69,7 +69,7 @@ In case you are [interacting with cloud hosted models](deploy-docker-nvidia-host ```bash export APP_EMBEDDINGS_SERVERURL="" export APP_LLM_SERVERURL="" - export APP_RANKING_SERVERURL="" + export APP_RANKING_SERVERURL="https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" export YOLOX_HTTP_ENDPOINT="https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3" export YOLOX_INFER_PROTOCOL="http" ``` diff --git a/notebooks/building_rag_vdb_operator.ipynb b/notebooks/building_rag_vdb_operator.ipynb index 1852f1d9c..0cab35d6e 100644 --- a/notebooks/building_rag_vdb_operator.ipynb +++ b/notebooks/building_rag_vdb_operator.ipynb @@ -2092,7 +2092,7 @@ " config_ingestor.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", " config_ingestor.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", " config_rag.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", - " config_rag.ranking.server_url = \"\" # Empty uses NVIDIA API catalog\n", + " config_rag.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", " config_rag.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", "\n", "# Create embedding model using the configuration\n", diff --git a/notebooks/image_input.ipynb b/notebooks/image_input.ipynb index 4f778a2fa..9332f4144 100644 --- a/notebooks/image_input.ipynb +++ b/notebooks/image_input.ipynb @@ -360,7 +360,7 @@ "outputs": [], "source": [ "# Start the RAG server (accessible at localhost:8081)\n", - "os.environ[\"APP_RANKING_SERVERURL\"] = \"\"\n", + "os.environ[\"APP_RANKING_SERVERURL\"] = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", "! docker compose -f ../deploy/compose/docker-compose-rag-server.yaml up -d --build" ] }, diff --git a/notebooks/rag_library_lite_usage.ipynb b/notebooks/rag_library_lite_usage.ipynb index 915eb00ca..ded451558 100644 --- a/notebooks/rag_library_lite_usage.ipynb +++ b/notebooks/rag_library_lite_usage.ipynb @@ -482,7 +482,7 @@ "\n", "# Set config for cloud API endpoints\n", "config_rag.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", - "config_rag.ranking.server_url = \"\" # Empty uses NVIDIA API catalog\n", + "config_rag.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", "config_rag.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", "\n", "# Initialize NvidiaRAG with config\n", diff --git a/notebooks/rag_library_usage.ipynb b/notebooks/rag_library_usage.ipynb index 894e82620..659f2f304 100644 --- a/notebooks/rag_library_usage.ipynb +++ b/notebooks/rag_library_usage.ipynb @@ -630,7 +630,7 @@ "# },\n", "# \"ranking\": {\n", "# \"model_name\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", - "# \"server_url\": \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-3_2-nv-rerankqa-1b-v2/reranking/v1\",\n", + "# \"server_url\": \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\",\n", "# },\n", "# }\n", "# )\n", @@ -640,7 +640,7 @@ "# Update config for cloud deployment if using Option 2\n", "if DEPLOYMENT_MODE == \"cloud\":\n", " config_rag.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", - " config_rag.ranking.server_url = \"\" # Empty uses NVIDIA API catalog\n", + " config_rag.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", " config_rag.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", "\n", "# Initialize NvidiaRAG with config\n", diff --git a/notebooks/summarization.ipynb b/notebooks/summarization.ipynb index d0c7ef285..63d39c5ac 100644 --- a/notebooks/summarization.ipynb +++ b/notebooks/summarization.ipynb @@ -545,7 +545,7 @@ "if DEPLOYMENT_MODE == \"cloud\":\n", " config.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", " config.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", - " config.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-3_2-nv-rerankqa-1b-v2/reranking/v1\"\n", + " config.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", " config.summarizer.server_url = \"\" # Empty uses NVIDIA API catalog\n", "else:\n", " config.embeddings.server_url = \"nemotron-embedding-ms:8000/v1\"\n", diff --git a/tests/unit/test_rag_server/test_rag_server_health.py b/tests/unit/test_rag_server/test_rag_server_health.py index 966a85e74..fb8783dff 100644 --- a/tests/unit/test_rag_server/test_rag_server_health.py +++ b/tests/unit/test_rag_server/test_rag_server_health.py @@ -415,7 +415,7 @@ async def test_check_all_services_health_nvidia_api_catalog(self, mock_config): mock_config.query_rewriter.server_url = "https://ai.api.nvidia.com/v1" mock_config.embeddings.server_url = "https://api.nvcf.nvidia.com/v2" mock_config.ranking.server_url = ( - "" # Empty URL should be treated as API catalog + "https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" # Empty URL should be treated as API catalog ) mock_vdb_op = MagicMock() From 65e965b527b6cb1f88a7cc6f42e88a583c56e502 Mon Sep 17 00:00:00 2001 From: nv-pranjald <150428320+nv-pranjald@users.noreply.github.com> Date: Mon, 9 Mar 2026 23:24:46 +0530 Subject: [PATCH 24/52] fix start proble failureThreshold to 750 (#411) --- .../nvidia-blueprint-rag/templates/llm-nim.yaml | 4 ++++ deploy/helm/nvidia-blueprint-rag/values.yaml | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/deploy/helm/nvidia-blueprint-rag/templates/llm-nim.yaml b/deploy/helm/nvidia-blueprint-rag/templates/llm-nim.yaml index 60f043973..f103a1a72 100644 --- a/deploy/helm/nvidia-blueprint-rag/templates/llm-nim.yaml +++ b/deploy/helm/nvidia-blueprint-rag/templates/llm-nim.yaml @@ -61,4 +61,8 @@ spec: {{- end }} expose: {{ toYaml $nimLlm.expose | nindent 4 }} + {{- with $nimLlm.startupProbe }} + startupProbe: +{{ toYaml . | nindent 4 }} + {{- end }} {{- end }} \ No newline at end of file diff --git a/deploy/helm/nvidia-blueprint-rag/values.yaml b/deploy/helm/nvidia-blueprint-rag/values.yaml index a3b501983..3a39c1224 100644 --- a/deploy/helm/nvidia-blueprint-rag/values.yaml +++ b/deploy/helm/nvidia-blueprint-rag/values.yaml @@ -703,6 +703,8 @@ nimOperator: value: "1" - name: NIM_SERVED_MODEL_NAME value: "nvidia/llama-3.3-nemotron-super-49b-v1.5" + - name: NIM_MAX_MODEL_LEN + value: "131072" # - name: CUDA_VISIBLE_DEVICES # value: "0" expose: @@ -711,6 +713,17 @@ nimOperator: type: ClusterIP port: 8000 grpcPort: 8001 + startupProbe: + enabled: true + probe: + httpGet: + path: /v1/health/ready + port: 8000 + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 750 + timeoutSeconds: 5 + # subsection: nvidia-nim-llama-32-nv-embedqa-1b-v2 # NIM Text Embedding nvidia-nim-llama-32-nv-embedqa-1b-v2: From 48671f76984706f098ac6d1c8b3f72c58b876c60 Mon Sep 17 00:00:00 2001 From: anngu-2xx3 Date: Tue, 10 Mar 2026 13:13:20 +0700 Subject: [PATCH 25/52] Update: Remove Vss on AIDP notebook (#409) * Update: Remove Vss Signed-off-by: Minh Nguyen * Remove video processing (VSS) from kafka consumer Video handler, video analyzer service, and all VSS-related configuration have been removed to simplify the event ingestion pipeline to document-only processing. Signed-off-by: Minh Nguyen Made-with: Cursor Signed-off-by: Minh Nguyen * fix: add USERID Signed-off-by: Minh Nguyen * fix: update document Signed-off-by: Minh Nguyen --------- Signed-off-by: Minh Nguyen Co-authored-by: Minh Nguyen --- examples/README.md | 8 +- .../deploy/docker-compose.yaml | 13 - .../kafka_consumer/config/__init__.py | 55 +- .../kafka_consumer/config/constants.py | 114 - .../kafka_consumer/config/settings.py | 64 - .../kafka_consumer/consumer.py | 7 - .../kafka_consumer/handlers/__init__.py | 3 +- .../kafka_consumer/handlers/video.py | 118 - .../rag_event_ingest/kafka_consumer/main.py | 10 +- .../rag_event_ingest/kafka_consumer/router.py | 15 +- .../kafka_consumer/services/__init__.py | 3 +- .../services/document_indexer.py | 71 - .../kafka_consumer/services/video_analyzer.py | 326 --- notebooks/rag_event_ingest.ipynb | 2050 +++++++---------- 14 files changed, 803 insertions(+), 2054 deletions(-) delete mode 100644 examples/rag_event_ingest/kafka_consumer/handlers/video.py delete mode 100644 examples/rag_event_ingest/kafka_consumer/services/video_analyzer.py diff --git a/examples/README.md b/examples/README.md index 2e8d54c1c..ba86d6765 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,7 +8,7 @@ This directory contains example integrations and extensions for NVIDIA RAG. |---------|-------------|---------------| | [rag_react_agent](./rag_react_agent/) | Integration with [NeMo Agent Toolkit (NAT)](https://github.com/NVIDIA/NeMo-Agent-Toolkit) providing RAG query and search capabilities for agent workflows | [README](./rag_react_agent/README.md) | | [nvidia_rag_mcp](./nvidia_rag_mcp/) | MCP (Model Context Protocol) server and client for exposing NVIDIA RAG capabilities to MCP-compatible applications | [Documentation](../docs/mcp.md) | -| [rag_event_ingest](./rag_event_ingest/) | Automated document & video ingestion from object storage (MinIO) via Kafka, with VSS video analysis | [Notebook](../notebooks/rag_event_ingest.ipynb) | +| [rag_event_ingest](./rag_event_ingest/) | Automated document ingestion from object storage (MinIO) via Kafka | [Notebook](../notebooks/rag_event_ingest.ipynb) | ## rag_react_agent @@ -31,11 +31,11 @@ See the [MCP documentation](../docs/mcp.md) for detailed setup and usage instruc ## rag_event_ingest -This example deploys an event-driven ingestion pipeline that monitors MinIO object storage for new file uploads via Kafka events. Documents are indexed through the RAG Ingestor, while videos are analyzed by NVIDIA VSS (Video Search & Summarization). All content becomes queryable through the RAG Agent. +This example deploys an event-driven ingestion pipeline that monitors MinIO object storage for new file uploads via Kafka events. Documents are automatically indexed through the RAG Ingestor and become queryable through the RAG Agent. Components: -- **kafka_consumer/** - Event-driven consumer that routes files to RAG or VSS based on file type +- **kafka_consumer/** - Event-driven consumer that routes files to RAG based on file type - **deploy/** - Docker Compose for Kafka, MinIO, and the consumer -- **data/** - Sample documents and videos for testing +- **data/** - Sample documents for testing See the [notebook](../notebooks/rag_event_ingest.ipynb) for step-by-step deployment and testing. diff --git a/examples/rag_event_ingest/deploy/docker-compose.yaml b/examples/rag_event_ingest/deploy/docker-compose.yaml index 774e5c6cd..05e5bbc0f 100644 --- a/examples/rag_event_ingest/deploy/docker-compose.yaml +++ b/examples/rag_event_ingest/deploy/docker-compose.yaml @@ -137,27 +137,14 @@ services: - MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin} - MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin} - MINIO_SECURE=false - - VSS_SERVER_URL=${VSS_SERVER_URL:-http://host.docker.internal:8110} - - VSS_TIMEOUT=${VSS_TIMEOUT:-1800} - - VSS_CHUNK_DURATION=${VSS_CHUNK_DURATION:-30} - - VSS_CHUNK_OVERLAP=${VSS_CHUNK_OVERLAP:-10} - - VSS_NUM_FRAMES_PER_CHUNK=${VSS_NUM_FRAMES_PER_CHUNK:-16} - - VSS_MAX_TOKENS=${VSS_MAX_TOKENS:-8192} # RAG Ingestor - INGESTOR_SERVER_URL=${INGESTOR_SERVER_URL:-http://ingestor-server:8082} - COLLECTION_NAME=${COLLECTION_NAME:-aidp_bucket} - # Video Analysis Prompts (customizable via environment variables) - - "VSS_PROMPT=${VSS_PROMPT:-Analyze this sports video. TIMESTAMP FORMAT: Convert seconds to MM:SS (40s=00:40, 80s=01:20, 150s=02:30, 200s=03:20). Describe: key plays, scoring, turnovers, big gains, defensive stops, celebrations.}" - - "VSS_SYSTEM_PROMPT=${VSS_SYSTEM_PROMPT:-You are a sports broadcaster. CRITICAL: Convert all timestamps from seconds to MM:SS format. Examples: 40 seconds = 00:40, 90 seconds = 01:30, 200 seconds = 03:20, 350 seconds = 05:50. Focus on action, field position, execution, and game momentum.}" - - "VSS_CAPTION_SUMMARIZATION_PROMPT=${VSS_CAPTION_SUMMARIZATION_PROMPT:-Format: [MM:SS] Play description. CONVERT seconds to MM:SS (40s=00:40, 80s=01:20, 150s=02:30). Describe: what happened, how it was executed, the result. Be vivid like a TV broadcast.}" - - "VSS_SUMMARY_AGGREGATION_PROMPT=${VSS_SUMMARY_AGGREGATION_PROMPT:-Create game summary with MM:SS timestamps. CONVERT all times: 40s=00:40, 80s=01:20, 200s=03:20, 350s=05:50. Highlight scoring plays, turnovers, momentum shifts, spectacular plays. Write like a highlight reel narrator - vivid and exciting.}" # Logging - LOG_LEVEL=${LOG_LEVEL:-INFO} restart: unless-stopped networks: - nvidia-rag - extra_hosts: - - "host.docker.internal:host-gateway" # ============================================================================= # VOLUMES diff --git a/examples/rag_event_ingest/kafka_consumer/config/__init__.py b/examples/rag_event_ingest/kafka_consumer/config/__init__.py index 3e38cf59c..6bd140441 100644 --- a/examples/rag_event_ingest/kafka_consumer/config/__init__.py +++ b/examples/rag_event_ingest/kafka_consumer/config/__init__.py @@ -3,9 +3,9 @@ Usage: import config.settings as cfg - print(cfg.VSS_SERVER_URL) + print(cfg.INGESTOR_SERVER_URL) - from config.constants import VIDEO_EXTENSIONS, DEST_VSS + from config.constants import DOCUMENT_EXTENSIONS, DEST_RAG """ # Settings (env vars) @@ -21,11 +21,7 @@ KAFKA_HEARTBEAT_INTERVAL_MS, # Services INGESTOR_SERVER_URL, - VSS_SERVER_URL, INGESTOR_TIMEOUT, - VSS_TIMEOUT, - VSS_UPLOAD_TIMEOUT, - VIDEO_INDEX_TIMEOUT, # MinIO MINIO_ENDPOINT, MINIO_ACCESS_KEY, @@ -34,10 +30,8 @@ MINIO_DEFAULT_COLLECTION, MINIO_SOURCES, # Features - ENABLE_MULTIMODAL_RAG, ENABLE_IMAGE_PROCESSING, ENABLE_AUDIO_PROCESSING, - VSS_EMBEDDING_ENABLED, # Collection EMBEDDING_DIMENSION, CHUNK_SIZE, @@ -47,31 +41,16 @@ LOG_FORMAT, # History HISTORY_FILE, - # VSS Prompts (configurable via env) - VSS_DEFAULT_PROMPT, - VSS_SYSTEM_PROMPT, - VSS_CAPTION_SUMMARIZATION_PROMPT, - VSS_SUMMARY_AGGREGATION_PROMPT, - # VSS Settings - VSS_CHUNK_DURATION, - VSS_CHUNK_OVERLAP, - VSS_NUM_FRAMES_PER_CHUNK, - VSS_MAX_TOKENS, - VSS_MODEL, - VSS_STREAM_ENABLED, # API Endpoints (configurable via env) API_INGESTOR_DOCUMENTS, API_INGESTOR_COLLECTIONS, API_INGESTOR_COLLECTION, API_INGESTOR_STATUS, - API_VSS_FILES, - API_VSS_SUMMARIZE, ) # Constants from .constants import ( # File extensions - VIDEO_EXTENSIONS, DOCUMENT_EXTENSIONS, IMAGE_EXTENSIONS, AUDIO_EXTENSIONS, @@ -80,7 +59,6 @@ CONTENT_TYPE_MAP, DEFAULT_CONTENT_TYPE, # Routing - DEST_VSS, DEST_RAG, DEST_SKIP, DEST_UNKNOWN, @@ -143,30 +121,7 @@ FIELD_GENERATE_SUMMARY, FIELD_EMBEDDING_DIMENSION, FIELD_TASK_ID, - # API request fields (VSS) - FIELD_ID, - FIELD_FILE, - FIELD_MODEL, - FIELD_PROMPT, - FIELD_SYSTEM_PROMPT, - FIELD_MAX_TOKENS, - FIELD_CHUNK_DURATION, - FIELD_CHUNK_OVERLAP_DURATION, - FIELD_NUM_FRAMES_PER_CHUNK, - FIELD_CAPTION_SUMMARIZATION_PROMPT, - FIELD_SUMMARY_AGGREGATION_PROMPT, - FIELD_PURPOSE, - FIELD_MEDIA_TYPE, - VALUE_VISION, - VALUE_VIDEO, - FIELD_STREAM, - RESP_DELTA, - RESP_DATA_PREFIX, # API response fields - RESP_CONTENT, - RESP_RESPONSE, - RESP_TEXT, - RESP_CHOICES, RESP_MESSAGE, RESP_ERROR, RESP_COLLECTIONS, @@ -178,12 +133,8 @@ # Timeouts TIMEOUT_DEFAULT, TIMEOUT_UPLOAD, - TIMEOUT_VIDEO_UPLOAD, TIMEOUT_TASK_CHECK, TIMEOUT_MAX_TASK_WAIT, - VIDEO_ANALYSIS_POLL_INTERVAL, - VIDEO_ANALYSIS_MAX_WAIT, - VIDEO_DESCRIPTION_MIN_LENGTH, # Kafka defaults KAFKA_DEFAULT_TOPIC, KAFKA_DEFAULT_CONSUMER_GROUP, @@ -196,6 +147,4 @@ COLLECTION_EMBEDDING_DIMENSION, COLLECTION_CHUNK_SIZE, COLLECTION_CHUNK_OVERLAP, - COLLECTION_VIDEO_CHUNK_SIZE, - COLLECTION_VIDEO_CHUNK_OVERLAP, ) diff --git a/examples/rag_event_ingest/kafka_consumer/config/constants.py b/examples/rag_event_ingest/kafka_consumer/config/constants.py index a90eee342..050cf70c4 100644 --- a/examples/rag_event_ingest/kafka_consumer/config/constants.py +++ b/examples/rag_event_ingest/kafka_consumer/config/constants.py @@ -6,11 +6,6 @@ # ==================== File Extensions ==================== -VIDEO_EXTENSIONS = frozenset({ - '.mp4', '.mkv', '.avi', '.mov', '.wmv', - '.flv', '.webm', '.m4v', '.mpeg', '.mpg' -}) - DOCUMENT_EXTENSIONS = frozenset({ '.pdf', '.docx', '.doc', '.txt', '.md', '.rst', '.html', '.htm', '.pptx', '.ppt', '.xlsx', '.xls', @@ -61,17 +56,6 @@ '.bmp': 'image/bmp', '.tiff': 'image/tiff', '.svg': 'image/svg+xml', - # Videos - '.mp4': 'video/mp4', - '.mkv': 'video/x-matroska', - '.avi': 'video/x-msvideo', - '.mov': 'video/quicktime', - '.wmv': 'video/x-ms-wmv', - '.flv': 'video/x-flv', - '.webm': 'video/webm', - '.m4v': 'video/x-m4v', - '.mpeg': 'video/mpeg', - '.mpg': 'video/mpeg', # Audio '.mp3': 'audio/mpeg', '.wav': 'audio/wav', @@ -88,7 +72,6 @@ # ==================== Routing ==================== # Destinations -DEST_VSS = 'vss' DEST_RAG = 'rag' DEST_SKIP = 'skip' DEST_UNKNOWN = 'unknown' @@ -100,7 +83,6 @@ KEY_REASON = 'reason' # File types -FILE_TYPE_VIDEO = 'video' FILE_TYPE_DOCUMENT = 'document' FILE_TYPE_IMAGE = 'image' FILE_TYPE_AUDIO = 'audio' @@ -108,7 +90,6 @@ FILE_TYPE_UNKNOWN = 'unknown' # Config keys -CFG_VIDEO_EXTENSIONS = 'video_extensions' CFG_DOCUMENT_EXTENSIONS = 'document_extensions' CFG_IMAGE_EXTENSIONS = 'image_extensions' CFG_AUDIO_EXTENSIONS = 'audio_extensions' @@ -199,30 +180,6 @@ FIELD_EMBEDDING_DIMENSION = 'embedding_dimension' FIELD_TASK_ID = 'task_id' -# VSS request fields -FIELD_ID = 'id' -FIELD_FILE = 'file' -FIELD_MODEL = 'model' -FIELD_PROMPT = 'prompt' -FIELD_SYSTEM_PROMPT = 'system_prompt' -FIELD_MAX_TOKENS = 'max_tokens' -FIELD_CHUNK_DURATION = 'chunk_duration' -FIELD_CHUNK_OVERLAP_DURATION = 'chunk_overlap_duration' -FIELD_NUM_FRAMES_PER_CHUNK = 'num_frames_per_chunk' -FIELD_CAPTION_SUMMARIZATION_PROMPT = 'caption_summarization_prompt' -FIELD_SUMMARY_AGGREGATION_PROMPT = 'summary_aggregation_prompt' -FIELD_PURPOSE = 'purpose' -FIELD_MEDIA_TYPE = 'media_type' - -# VSS request values -VALUE_VISION = 'vision' -VALUE_VIDEO = 'video' - -# VSS streaming fields -FIELD_STREAM = 'stream' -RESP_DELTA = 'delta' -RESP_DATA_PREFIX = 'data: ' - # ==================== API Response Fields ==================== @@ -247,16 +204,9 @@ TIMEOUT_DEFAULT = 30 TIMEOUT_UPLOAD = 600 -TIMEOUT_VIDEO_UPLOAD = 600 -TIMEOUT_VIDEO_INDEX = 300 TIMEOUT_TASK_CHECK = 30 TIMEOUT_MAX_TASK_WAIT = 300 -# Video analysis polling -VIDEO_ANALYSIS_POLL_INTERVAL = 10 # seconds between polls -VIDEO_ANALYSIS_MAX_WAIT = 180 # max seconds to wait for analysis -VIDEO_DESCRIPTION_MIN_LENGTH = 200 # minimum chars for valid description - # ==================== Kafka Defaults ==================== @@ -274,8 +224,6 @@ COLLECTION_EMBEDDING_DIMENSION = 2048 COLLECTION_CHUNK_SIZE = 512 COLLECTION_CHUNK_OVERLAP = 150 -COLLECTION_VIDEO_CHUNK_SIZE = 1024 -COLLECTION_VIDEO_CHUNK_OVERLAP = 100 # ==================== Environment Variable Keys ==================== @@ -292,19 +240,13 @@ # Service URLs ENV_INGESTOR_SERVER_URL = 'INGESTOR_SERVER_URL' -ENV_VSS_SERVER_URL = 'VSS_SERVER_URL' ENV_INGESTOR_TIMEOUT = 'INGESTOR_TIMEOUT' -ENV_VSS_TIMEOUT = 'VSS_TIMEOUT' -ENV_VSS_UPLOAD_TIMEOUT = 'VSS_UPLOAD_TIMEOUT' -ENV_VIDEO_INDEX_TIMEOUT = 'VIDEO_INDEX_TIMEOUT' # API Endpoints ENV_API_INGESTOR_DOCUMENTS = 'API_INGESTOR_DOCUMENTS' ENV_API_INGESTOR_COLLECTIONS = 'API_INGESTOR_COLLECTIONS' ENV_API_INGESTOR_COLLECTION = 'API_INGESTOR_COLLECTION' ENV_API_INGESTOR_STATUS = 'API_INGESTOR_STATUS' -ENV_API_VSS_FILES = 'API_VSS_FILES' -ENV_API_VSS_SUMMARIZE = 'API_VSS_SUMMARIZE' # MinIO ENV_MINIO_ENDPOINT = 'MINIO_ENDPOINT' @@ -315,10 +257,8 @@ ENV_MINIO_SOURCES = 'MINIO_SOURCES' # Feature Flags -ENV_ENABLE_MULTIMODAL_RAG = 'ENABLE_MULTIMODAL_RAG' ENV_ENABLE_IMAGE_PROCESSING = 'ENABLE_IMAGE_PROCESSING' ENV_ENABLE_AUDIO_PROCESSING = 'ENABLE_AUDIO_PROCESSING' -ENV_VSS_EMBEDDING = 'VSS_EMBEDDING' # Collection Settings ENV_EMBEDDING_DIMENSION = 'EMBEDDING_DIMENSION' @@ -332,27 +272,12 @@ # History ENV_HISTORY_FILE = 'HISTORY_FILE' -# VSS Settings -ENV_VSS_PROMPT = 'VSS_PROMPT' -ENV_VSS_SYSTEM_PROMPT = 'VSS_SYSTEM_PROMPT' -ENV_VSS_CAPTION_SUMMARIZATION_PROMPT = 'VSS_CAPTION_SUMMARIZATION_PROMPT' -ENV_VSS_SUMMARY_AGGREGATION_PROMPT = 'VSS_SUMMARY_AGGREGATION_PROMPT' -ENV_VSS_CHUNK_DURATION = 'VSS_CHUNK_DURATION' -ENV_VSS_CHUNK_OVERLAP = 'VSS_CHUNK_OVERLAP' -ENV_VSS_NUM_FRAMES_PER_CHUNK = 'VSS_NUM_FRAMES_PER_CHUNK' -ENV_VSS_MAX_TOKENS = 'VSS_MAX_TOKENS' -ENV_VSS_MODEL = 'VSS_MODEL' -ENV_VSS_STREAM_ENABLED = 'VSS_STREAM_ENABLED' - - # ==================== API Endpoint Defaults ==================== DEFAULT_API_INGESTOR_DOCUMENTS = '/v1/documents' DEFAULT_API_INGESTOR_COLLECTIONS = '/v1/collections' DEFAULT_API_INGESTOR_COLLECTION = '/v1/collection' DEFAULT_API_INGESTOR_STATUS = '/v1/status' -DEFAULT_API_VSS_FILES = '/files' -DEFAULT_API_VSS_SUMMARIZE = '/summarize' # ==================== Logging Defaults ==================== @@ -369,42 +294,3 @@ # ==================== MinIO Defaults ==================== DEFAULT_COLLECTION_NAME = 'multimodal_data' - - -# ==================== VSS Defaults ==================== - -DEFAULT_VSS_MODEL = 'Cosmos-Reason2-8B' -DEFAULT_VSS_CHUNK_DURATION = 60 # seconds per chunk -DEFAULT_VSS_CHUNK_OVERLAP = 5 # overlap between chunks -DEFAULT_VSS_NUM_FRAMES_PER_CHUNK = 8 # frames per chunk for VLM -DEFAULT_VSS_MAX_TOKENS = 1024 - -# Default prompts for general videos -DEFAULT_VSS_PROMPT = ( - "Analyze this video. TIMESTAMP FORMAT: Convert seconds to MM:SS " - "(40s=00:40, 80s=01:20, 150s=02:30, 200s=03:20). " - "Describe: key events, important moments, actions, transitions, and notable scenes. " - "Capture the flow and progression of the content." -) - -DEFAULT_VSS_SYSTEM_PROMPT = ( - "You are a video analyst providing detailed descriptions. " - "CRITICAL: Convert all timestamps from seconds to MM:SS format. " - "Examples: 40 seconds = 00:40, 90 seconds = 01:30, 200 seconds = 03:20. " - "Focus on what you clearly see: actions, scenes, transitions, and key moments. " - "Be descriptive and engaging." -) - -DEFAULT_VSS_CAPTION_SUMMARIZATION_PROMPT = ( - "Format: [MM:SS] Description. CONVERT seconds to MM:SS " - "(40s=00:40, 80s=01:20, 150s=02:30). " - "Describe: what happened, how it occurred, the result. " - "Be specific about the action and context." -) - -DEFAULT_VSS_SUMMARY_AGGREGATION_PROMPT = ( - "Create a comprehensive video summary with MM:SS timestamps. " - "CONVERT all times: 40s=00:40, 80s=01:20, 200s=03:20, 350s=05:50. " - "Highlight: key events, transitions, important moments, and notable scenes. " - "Write in an engaging narrative style." -) diff --git a/examples/rag_event_ingest/kafka_consumer/config/settings.py b/examples/rag_event_ingest/kafka_consumer/config/settings.py index 69794c2a0..bbfe4824c 100644 --- a/examples/rag_event_ingest/kafka_consumer/config/settings.py +++ b/examples/rag_event_ingest/kafka_consumer/config/settings.py @@ -13,8 +13,6 @@ KAFKA_DEFAULT_SESSION_TIMEOUT_MS, KAFKA_DEFAULT_HEARTBEAT_INTERVAL_MS, TIMEOUT_UPLOAD, - TIMEOUT_VIDEO_UPLOAD, - TIMEOUT_VIDEO_INDEX, COLLECTION_EMBEDDING_DIMENSION, COLLECTION_CHUNK_SIZE, COLLECTION_CHUNK_OVERLAP, @@ -23,8 +21,6 @@ DEFAULT_API_INGESTOR_COLLECTIONS, DEFAULT_API_INGESTOR_COLLECTION, DEFAULT_API_INGESTOR_STATUS, - DEFAULT_API_VSS_FILES, - DEFAULT_API_VSS_SUMMARIZE, # Logging defaults DEFAULT_LOG_LEVEL, DEFAULT_LOG_FORMAT, @@ -32,16 +28,6 @@ DEFAULT_HISTORY_FILE, # MinIO defaults DEFAULT_COLLECTION_NAME, - # VSS defaults - DEFAULT_VSS_MODEL, - DEFAULT_VSS_CHUNK_DURATION, - DEFAULT_VSS_CHUNK_OVERLAP, - DEFAULT_VSS_NUM_FRAMES_PER_CHUNK, - DEFAULT_VSS_MAX_TOKENS, - DEFAULT_VSS_PROMPT, - DEFAULT_VSS_SYSTEM_PROMPT, - DEFAULT_VSS_CAPTION_SUMMARIZATION_PROMPT, - DEFAULT_VSS_SUMMARY_AGGREGATION_PROMPT, # Environment variable keys ENV_KAFKA_BOOTSTRAP_SERVERS, ENV_KAFKA_TOPIC, @@ -52,43 +38,25 @@ ENV_KAFKA_SESSION_TIMEOUT_MS, ENV_KAFKA_HEARTBEAT_INTERVAL_MS, ENV_INGESTOR_SERVER_URL, - ENV_VSS_SERVER_URL, ENV_INGESTOR_TIMEOUT, - ENV_VSS_TIMEOUT, - ENV_VSS_UPLOAD_TIMEOUT, - ENV_VIDEO_INDEX_TIMEOUT, ENV_API_INGESTOR_DOCUMENTS, ENV_API_INGESTOR_COLLECTIONS, ENV_API_INGESTOR_COLLECTION, ENV_API_INGESTOR_STATUS, - ENV_API_VSS_FILES, - ENV_API_VSS_SUMMARIZE, ENV_MINIO_ENDPOINT, ENV_MINIO_ACCESS_KEY, ENV_MINIO_SECRET_KEY, ENV_MINIO_SECURE, ENV_COLLECTION_NAME, ENV_MINIO_SOURCES, - ENV_ENABLE_MULTIMODAL_RAG, ENV_ENABLE_IMAGE_PROCESSING, ENV_ENABLE_AUDIO_PROCESSING, - ENV_VSS_EMBEDDING, ENV_EMBEDDING_DIMENSION, ENV_CHUNK_SIZE, ENV_CHUNK_OVERLAP, ENV_LOG_LEVEL, ENV_LOG_FORMAT, ENV_HISTORY_FILE, - ENV_VSS_PROMPT, - ENV_VSS_SYSTEM_PROMPT, - ENV_VSS_CAPTION_SUMMARIZATION_PROMPT, - ENV_VSS_SUMMARY_AGGREGATION_PROMPT, - ENV_VSS_CHUNK_DURATION, - ENV_VSS_CHUNK_OVERLAP, - ENV_VSS_NUM_FRAMES_PER_CHUNK, - ENV_VSS_MAX_TOKENS, - ENV_VSS_MODEL, - ENV_VSS_STREAM_ENABLED, ) @@ -122,11 +90,7 @@ def _get_int(key: str, default: int) -> int: # ==================== Service URLs ==================== INGESTOR_SERVER_URL = os.getenv(ENV_INGESTOR_SERVER_URL) # Required -VSS_SERVER_URL = os.getenv(ENV_VSS_SERVER_URL) # Required INGESTOR_TIMEOUT = _get_int(ENV_INGESTOR_TIMEOUT, TIMEOUT_UPLOAD) -VSS_TIMEOUT = _get_int(ENV_VSS_TIMEOUT, TIMEOUT_VIDEO_UPLOAD) -VSS_UPLOAD_TIMEOUT = _get_int(ENV_VSS_UPLOAD_TIMEOUT, TIMEOUT_VIDEO_UPLOAD) -VIDEO_INDEX_TIMEOUT = _get_int(ENV_VIDEO_INDEX_TIMEOUT, TIMEOUT_VIDEO_INDEX) # API Endpoints - Ingestor Server API_INGESTOR_DOCUMENTS = os.getenv(ENV_API_INGESTOR_DOCUMENTS, DEFAULT_API_INGESTOR_DOCUMENTS) @@ -134,10 +98,6 @@ def _get_int(key: str, default: int) -> int: API_INGESTOR_COLLECTION = os.getenv(ENV_API_INGESTOR_COLLECTION, DEFAULT_API_INGESTOR_COLLECTION) API_INGESTOR_STATUS = os.getenv(ENV_API_INGESTOR_STATUS, DEFAULT_API_INGESTOR_STATUS) -# API Endpoints - VSS -API_VSS_FILES = os.getenv(ENV_API_VSS_FILES, DEFAULT_API_VSS_FILES) -API_VSS_SUMMARIZE = os.getenv(ENV_API_VSS_SUMMARIZE, DEFAULT_API_VSS_SUMMARIZE) - # ==================== MinIO Settings ==================== @@ -152,10 +112,8 @@ def _get_int(key: str, default: int) -> int: # ==================== Feature Flags ==================== -ENABLE_MULTIMODAL_RAG = _get_bool(ENV_ENABLE_MULTIMODAL_RAG, True) ENABLE_IMAGE_PROCESSING = _get_bool(ENV_ENABLE_IMAGE_PROCESSING, False) ENABLE_AUDIO_PROCESSING = _get_bool(ENV_ENABLE_AUDIO_PROCESSING, False) -VSS_EMBEDDING_ENABLED = _get_bool(ENV_VSS_EMBEDDING, False) # ==================== Collection Settings ==================== @@ -175,25 +133,3 @@ def _get_int(key: str, default: int) -> int: HISTORY_FILE = os.getenv(ENV_HISTORY_FILE, DEFAULT_HISTORY_FILE) - -# ==================== VSS Settings ==================== - -# Default prompts for general videos (can be overridden via environment variables) -# Use environment variables for specific video types (sports, talks, tutorials, etc.) -VSS_DEFAULT_PROMPT = os.getenv(ENV_VSS_PROMPT, DEFAULT_VSS_PROMPT) -VSS_SYSTEM_PROMPT = os.getenv(ENV_VSS_SYSTEM_PROMPT, DEFAULT_VSS_SYSTEM_PROMPT) -VSS_CAPTION_SUMMARIZATION_PROMPT = os.getenv( - ENV_VSS_CAPTION_SUMMARIZATION_PROMPT, DEFAULT_VSS_CAPTION_SUMMARIZATION_PROMPT -) -VSS_SUMMARY_AGGREGATION_PROMPT = os.getenv( - ENV_VSS_SUMMARY_AGGREGATION_PROMPT, DEFAULT_VSS_SUMMARY_AGGREGATION_PROMPT -) - -# VSS chunking settings for long videos -VSS_CHUNK_DURATION = _get_int(ENV_VSS_CHUNK_DURATION, DEFAULT_VSS_CHUNK_DURATION) -VSS_CHUNK_OVERLAP = _get_int(ENV_VSS_CHUNK_OVERLAP, DEFAULT_VSS_CHUNK_OVERLAP) -VSS_NUM_FRAMES_PER_CHUNK = _get_int(ENV_VSS_NUM_FRAMES_PER_CHUNK, DEFAULT_VSS_NUM_FRAMES_PER_CHUNK) -VSS_MAX_TOKENS = _get_int(ENV_VSS_MAX_TOKENS, DEFAULT_VSS_MAX_TOKENS) -VSS_MODEL = os.getenv(ENV_VSS_MODEL, DEFAULT_VSS_MODEL) -VSS_STREAM_ENABLED = _get_bool(ENV_VSS_STREAM_ENABLED, True) - diff --git a/examples/rag_event_ingest/kafka_consumer/consumer.py b/examples/rag_event_ingest/kafka_consumer/consumer.py index b78d3c322..87a5c0538 100644 --- a/examples/rag_event_ingest/kafka_consumer/consumer.py +++ b/examples/rag_event_ingest/kafka_consumer/consumer.py @@ -104,13 +104,6 @@ def _handle_delete(self, event: S3Event) -> HandlerResult: indexer = doc_handler.indexer success = indexer.delete(event.key, event.collection) - if self.router.is_video(event.key): - desc_filename = f"{event.key}_description.json" - desc_ok = indexer.delete(desc_filename, event.collection) - if desc_ok: - logger.info(f"✓ Deleted video description {desc_filename} from Milvus") - success = success or desc_ok - if success: logger.info(f"✓ Deleted {event.key} from Milvus") return HandlerResult(success=True, status='DELETED') diff --git a/examples/rag_event_ingest/kafka_consumer/handlers/__init__.py b/examples/rag_event_ingest/kafka_consumer/handlers/__init__.py index 112493e8e..e6f3efcff 100644 --- a/examples/rag_event_ingest/kafka_consumer/handlers/__init__.py +++ b/examples/rag_event_ingest/kafka_consumer/handlers/__init__.py @@ -1,6 +1,5 @@ # Handlers package from .base import BaseHandler from .document import DocumentHandler -from .video import VideoHandler -__all__ = ['BaseHandler', 'DocumentHandler', 'VideoHandler'] +__all__ = ['BaseHandler', 'DocumentHandler'] diff --git a/examples/rag_event_ingest/kafka_consumer/handlers/video.py b/examples/rag_event_ingest/kafka_consumer/handlers/video.py deleted file mode 100644 index 9e5d73a34..000000000 --- a/examples/rag_event_ingest/kafka_consumer/handlers/video.py +++ /dev/null @@ -1,118 +0,0 @@ -# handlers/video.py -"""Handler for video files.""" - -import logging - -import requests - -from .base import BaseHandler -from models.events import S3Event, HandlerResult -from services.storage import ObjectStorage -from services.video_analyzer import VideoAnalyzer -from services.document_indexer import DocumentIndexer - -logger = logging.getLogger(__name__) - - -class VideoHandler(BaseHandler): - """Handler for video files - analyzes and indexes in vector store.""" - - def __init__( - self, - storage: ObjectStorage, - analyzer: VideoAnalyzer, - indexer: DocumentIndexer, - enable_multimodal_rag: bool = True - ): - """Initialize video handler. - - Args: - storage: Object storage for file downloads - analyzer: Video analyzer for VLM analysis - indexer: Document indexer for storing descriptions - enable_multimodal_rag: Whether to index video descriptions - """ - self.storage = storage - self.analyzer = analyzer - self.indexer = indexer - self.enable_multimodal_rag = enable_multimodal_rag - - @property - def name(self) -> str: - return "VideoHandler" - - def handle(self, event: S3Event) -> HandlerResult: - """Process video file. - - 1. Download from MinIO - 2. Upload to VSS - 3. (Optional) Get description and index in Milvus - - Args: - event: S3 event with video info - - Returns: - HandlerResult - """ - self.log_start(event) - - try: - # Step 1: Download from storage - logger.info(f"📥 Downloading video from storage...") - video_data = self.storage.download(event.bucket, event.key) - - # Step 2: Upload to VSS - logger.info(f"📤 Uploading to analyzer...") - success, video_name = self.analyzer.upload_video(video_data, event.key) - - if not success: - result = HandlerResult.failed_result("Video upload failed") - self.log_failure(event, result) - return result - - logger.info(f"✓ Video uploaded: {video_name}") - - # Step 3: Multi-modal RAG indexing (optional) - if self.enable_multimodal_rag and video_name: - self._index_video_description(event, video_name) - - result = HandlerResult.success_result() - self.log_success(event, result) - return result - - except requests.RequestException as e: - logger.error(f"Network error processing video: {e}") - return HandlerResult.failed_result(str(e)) - except (IOError, OSError) as e: - logger.error(f"Storage error processing video: {e}") - return HandlerResult.failed_result(str(e)) - - def _index_video_description(self, event: S3Event, video_name: str): - """Get video description from VSS and index in Milvus. - - Args: - event: S3 event - video_name: Video name (stem of filename) - """ - logger.info(f"🔄 Starting Multi-Modal RAG indexing...") - - # Get description using VSS /generate with detailed prompt - logger.info(f"📹 Generating description for video: {video_name}...") - description = self.analyzer.get_video_description(video_name) - - if not description: - logger.warning("Failed to get video description from VSS") - return - - # Index description in Milvus - success = self.indexer.index_video_description( - collection=event.collection, - video_id=video_name, - video_name=event.key, - description=description - ) - - if success: - logger.info(f"✓ Indexed video description for {event.key}") - else: - logger.warning(f"⚠ Failed to index video description") diff --git a/examples/rag_event_ingest/kafka_consumer/main.py b/examples/rag_event_ingest/kafka_consumer/main.py index 02e86c137..df4384d4f 100644 --- a/examples/rag_event_ingest/kafka_consumer/main.py +++ b/examples/rag_event_ingest/kafka_consumer/main.py @@ -5,9 +5,9 @@ import logging import config.settings as cfg -from config.constants import DEST_RAG, DEST_VSS -from services import ObjectStorage, DocumentIndexer, VideoAnalyzer -from handlers import DocumentHandler, VideoHandler +from config.constants import DEST_RAG +from services import ObjectStorage, DocumentIndexer +from handlers import DocumentHandler from consumer import KafkaEventConsumer logging.basicConfig( @@ -27,13 +27,11 @@ def main(): logger.info("Initializing services...") storage = ObjectStorage() indexer = DocumentIndexer(cfg.INGESTOR_SERVER_URL) - analyzer = VideoAnalyzer(cfg.VSS_SERVER_URL) - + # Initialize handlers logger.info("Initializing handlers...") handlers = { DEST_RAG: DocumentHandler(storage, indexer), - DEST_VSS: VideoHandler(storage, analyzer, indexer, enable_multimodal_rag=cfg.ENABLE_MULTIMODAL_RAG), } # Initialize consumer diff --git a/examples/rag_event_ingest/kafka_consumer/router.py b/examples/rag_event_ingest/kafka_consumer/router.py index df9432357..41f5b8f23 100644 --- a/examples/rag_event_ingest/kafka_consumer/router.py +++ b/examples/rag_event_ingest/kafka_consumer/router.py @@ -6,25 +6,21 @@ from typing import Dict, Any, List, Set, Union from config.constants import ( - VIDEO_EXTENSIONS, DOCUMENT_EXTENSIONS, IMAGE_EXTENSIONS, AUDIO_EXTENSIONS, SKIP_EXTENSIONS, - DEST_VSS, DEST_RAG, DEST_SKIP, KEY_DESTINATION, KEY_FILE_TYPE, KEY_EXTENSION, KEY_REASON, - FILE_TYPE_VIDEO, FILE_TYPE_DOCUMENT, FILE_TYPE_IMAGE, FILE_TYPE_AUDIO, FILE_TYPE_SKIP, FILE_TYPE_UNKNOWN, - CFG_VIDEO_EXTENSIONS, CFG_DOCUMENT_EXTENSIONS, CFG_IMAGE_EXTENSIONS, CFG_AUDIO_EXTENSIONS, @@ -45,7 +41,6 @@ def __init__(self, config: Union[Dict[str, Any], Any] = None): config = {} elif hasattr(config, '__dataclass_fields__'): config = { - CFG_VIDEO_EXTENSIONS: config.video_extensions, CFG_DOCUMENT_EXTENSIONS: config.document_extensions, CFG_IMAGE_EXTENSIONS: config.image_extensions, CFG_AUDIO_EXTENSIONS: config.audio_extensions, @@ -55,7 +50,6 @@ def __init__(self, config: Union[Dict[str, Any], Any] = None): } self.config = config - self.video_extensions = self._to_set(config.get(CFG_VIDEO_EXTENSIONS, VIDEO_EXTENSIONS)) self.document_extensions = self._to_set(config.get(CFG_DOCUMENT_EXTENSIONS, DOCUMENT_EXTENSIONS)) self.image_extensions = self._to_set(config.get(CFG_IMAGE_EXTENSIONS, IMAGE_EXTENSIONS)) self.audio_extensions = self._to_set(config.get(CFG_AUDIO_EXTENSIONS, AUDIO_EXTENSIONS)) @@ -63,8 +57,7 @@ def __init__(self, config: Union[Dict[str, Any], Any] = None): self.enable_image_processing = config.get(CFG_ENABLE_IMAGE_PROCESSING, False) self.enable_audio_processing = config.get(CFG_ENABLE_AUDIO_PROCESSING, False) - logger.info(f"FileRouter initialized - Video: {len(self.video_extensions)} types, " - f"Documents: {len(self.document_extensions)} types") + logger.info(f"FileRouter initialized - Documents: {len(self.document_extensions)} types") @staticmethod def _to_set(value: Union[List, Set, None]) -> Set[str]: @@ -79,9 +72,6 @@ def route(self, filename: str) -> dict: if ext in self.skip_extensions: return {KEY_DESTINATION: DEST_SKIP, KEY_FILE_TYPE: FILE_TYPE_SKIP, KEY_EXTENSION: ext, KEY_REASON: 'File extension in skip list'} - if ext in self.video_extensions: - return {KEY_DESTINATION: DEST_VSS, KEY_FILE_TYPE: FILE_TYPE_VIDEO, KEY_EXTENSION: ext} - if ext in self.document_extensions: return {KEY_DESTINATION: DEST_RAG, KEY_FILE_TYPE: FILE_TYPE_DOCUMENT, KEY_EXTENSION: ext} @@ -97,8 +87,5 @@ def route(self, filename: str) -> dict: return {KEY_DESTINATION: DEST_RAG, KEY_FILE_TYPE: FILE_TYPE_UNKNOWN, KEY_EXTENSION: ext, KEY_REASON: 'Unknown extension, attempting RAG ingestion'} - def is_video(self, filename: str) -> bool: - return Path(filename).suffix.lower() in self.video_extensions - def is_document(self, filename: str) -> bool: return Path(filename).suffix.lower() in self.document_extensions diff --git a/examples/rag_event_ingest/kafka_consumer/services/__init__.py b/examples/rag_event_ingest/kafka_consumer/services/__init__.py index 24e2e98dd..db2c0e347 100644 --- a/examples/rag_event_ingest/kafka_consumer/services/__init__.py +++ b/examples/rag_event_ingest/kafka_consumer/services/__init__.py @@ -3,6 +3,5 @@ from .storage import ObjectStorage from .document_indexer import DocumentIndexer -from .video_analyzer import VideoAnalyzer -__all__ = ['ObjectStorage', 'DocumentIndexer', 'VideoAnalyzer'] +__all__ = ['ObjectStorage', 'DocumentIndexer'] diff --git a/examples/rag_event_ingest/kafka_consumer/services/document_indexer.py b/examples/rag_event_ingest/kafka_consumer/services/document_indexer.py index 55923accb..ac60d41a2 100644 --- a/examples/rag_event_ingest/kafka_consumer/services/document_indexer.py +++ b/examples/rag_event_ingest/kafka_consumer/services/document_indexer.py @@ -19,12 +19,9 @@ STATUS_FAILED, TIMEOUT_DEFAULT, TIMEOUT_MAX_TASK_WAIT, - VIDEO_INDEX_TIMEOUT, COLLECTION_EMBEDDING_DIMENSION, COLLECTION_CHUNK_SIZE, COLLECTION_CHUNK_OVERLAP, - COLLECTION_VIDEO_CHUNK_SIZE, - COLLECTION_VIDEO_CHUNK_OVERLAP, CONTENT_TYPE_MAP, DEFAULT_CONTENT_TYPE, FIELD_COLLECTION_NAME, @@ -224,74 +221,6 @@ def delete(self, filename: str, collection: str) -> bool: logger.error(f"Delete failed: {response.status_code}") return False - def index_video_description( - self, - collection: str, - video_id: str, - video_name: str, - description: str - ) -> bool: - """Index video description in Milvus.""" - try: - # Ensure collection exists first - if not self.ensure_collection_exists(collection): - logger.error(f"Failed to ensure collection '{collection}' exists") - return False - - doc_filename = f"{video_name}_description.json" - - # Delete existing document first to allow re-indexing - self.delete(doc_filename, collection) - - content = f"""Video Name: {video_name} -Video ID: {video_id} -Source: VSS Analysis - -Description: -{description}""" - - doc_data = { - 'content': content, - 'metadata': { - 'content_type': 'video', - 'video_id': video_id, - 'video_name': video_name, - 'source': 'vss_analysis' - } - } - - files = { - 'documents': (doc_filename, json.dumps(doc_data).encode(), 'application/json') - } - - data_config = { - FIELD_COLLECTION_NAME: collection, - FIELD_BLOCKING: True, - FIELD_SPLIT_OPTIONS: { - FIELD_CHUNK_SIZE: COLLECTION_VIDEO_CHUNK_SIZE, - FIELD_CHUNK_OVERLAP: COLLECTION_VIDEO_CHUNK_OVERLAP - }, - FIELD_GENERATE_SUMMARY: False - } - - response = requests.post( - f'{self.base_url}{API_INGESTOR_DOCUMENTS}', - files=files, - data={'data': json.dumps(data_config)}, - timeout=VIDEO_INDEX_TIMEOUT - ) - - if response.status_code in [200, 201, 202]: - logger.info(f"✓ Video description indexed to '{collection}'") - return True - else: - logger.error(f"Failed to index video description: {response.status_code} - {response.text}") - return False - - except requests.RequestException as e: - logger.error(f"Error indexing video description: {e}") - return False - def _get_content_type(self, filename: str) -> str: """Get content type from filename.""" ext = Path(filename).suffix.lower() diff --git a/examples/rag_event_ingest/kafka_consumer/services/video_analyzer.py b/examples/rag_event_ingest/kafka_consumer/services/video_analyzer.py deleted file mode 100644 index af7082550..000000000 --- a/examples/rag_event_ingest/kafka_consumer/services/video_analyzer.py +++ /dev/null @@ -1,326 +0,0 @@ -# services/video_analyzer.py -"""Video analysis service using VSS 2.4 API.""" - -import json -import re -import logging -from pathlib import Path -from typing import Optional, Tuple -import requests - -from config import ( - API_VSS_FILES, - API_VSS_SUMMARIZE, - CONTENT_TYPE_MAP, - DEFAULT_CONTENT_TYPE, - VSS_DEFAULT_PROMPT, - VSS_SYSTEM_PROMPT, - VSS_CAPTION_SUMMARIZATION_PROMPT, - VSS_SUMMARY_AGGREGATION_PROMPT, - VSS_CHUNK_DURATION, - VSS_CHUNK_OVERLAP, - VSS_NUM_FRAMES_PER_CHUNK, - VSS_MAX_TOKENS, - VSS_MODEL, - VSS_UPLOAD_TIMEOUT, - VSS_STREAM_ENABLED, - # API fields - FIELD_ID, - FIELD_FILE, - FIELD_MODEL, - FIELD_PROMPT, - FIELD_SYSTEM_PROMPT, - FIELD_MAX_TOKENS, - FIELD_CHUNK_DURATION, - FIELD_CHUNK_OVERLAP_DURATION, - FIELD_NUM_FRAMES_PER_CHUNK, - FIELD_CAPTION_SUMMARIZATION_PROMPT, - FIELD_SUMMARY_AGGREGATION_PROMPT, - FIELD_PURPOSE, - FIELD_MEDIA_TYPE, - FIELD_STREAM, - VALUE_VISION, - VALUE_VIDEO, - RESP_CONTENT, - RESP_RESPONSE, - RESP_TEXT, - RESP_CHOICES, - RESP_MESSAGE, - RESP_DELTA, - RESP_DATA_PREFIX, -) - -logger = logging.getLogger(__name__) - - -class VideoAnalyzer: - """Analyzes videos using VSS 2.4 /files and /summarize APIs.""" - - def __init__(self, base_url: str, timeout: int = 1800): - """Initialize video analyzer. - - Args: - base_url: VSS server URL - timeout: Timeout for summarization (default 30 min for long videos) - """ - self.base_url = base_url.rstrip('/') - self.timeout = timeout - - logger.info(f"VideoAnalyzer initialized (VSS 2.4): {self.base_url}") - - def upload_video(self, video_data: bytes, filename: str) -> Tuple[bool, Optional[str]]: - """Upload video to VSS using /files API. - - Args: - video_data: Video file bytes - filename: Original filename - - Returns: - Tuple of (success, file_id) - """ - file_name_sanitized = self._sanitize_filename(filename) - content_type = self._get_content_type(file_name_sanitized) - - logger.info(f"Uploading video to VSS: {file_name_sanitized} ({len(video_data)} bytes)") - - try: - response = requests.post( - f'{self.base_url}{API_VSS_FILES}', - files={FIELD_FILE: (file_name_sanitized, video_data, content_type)}, - data={FIELD_PURPOSE: VALUE_VISION, FIELD_MEDIA_TYPE: VALUE_VIDEO}, - timeout=VSS_UPLOAD_TIMEOUT - ) - except requests.RequestException as e: - logger.error(f"Error uploading video to VSS: {e}") - return False, None - - if response.status_code not in [200, 201]: - logger.error(f"Upload failed: {response.status_code}") - logger.error(f"Response: {response.text}") - return False, None - - result = response.json() - file_id = result.get(FIELD_ID) - - if not file_id: - logger.error(f"No file ID in response: {result}") - return False, None - - logger.info(f"✓ Video uploaded: {file_name_sanitized} (id: {file_id})") - return True, file_id - - def get_video_description(self, file_id: str) -> Optional[str]: - """Get video description using /summarize API. - - Uses streaming mode for real-time progress updates if enabled. - - Args: - file_id: File ID from upload - - Returns: - Video summary text, or None if failed - """ - if VSS_STREAM_ENABLED: - return self._get_description_streaming(file_id) - return self._get_description_blocking(file_id) - - def _build_summarize_payload(self, file_id: str, stream: bool = False) -> dict: - """Build the payload for summarize request.""" - payload = { - FIELD_ID: file_id, - FIELD_MODEL: VSS_MODEL, - FIELD_PROMPT: VSS_DEFAULT_PROMPT, - FIELD_SYSTEM_PROMPT: VSS_SYSTEM_PROMPT, - FIELD_MAX_TOKENS: VSS_MAX_TOKENS, - FIELD_STREAM: stream, - } - - # Add chunking for long videos - if VSS_CHUNK_DURATION > 0: - payload.update({ - FIELD_CHUNK_DURATION: VSS_CHUNK_DURATION, - FIELD_CHUNK_OVERLAP_DURATION: VSS_CHUNK_OVERLAP, - FIELD_NUM_FRAMES_PER_CHUNK: VSS_NUM_FRAMES_PER_CHUNK, - FIELD_CAPTION_SUMMARIZATION_PROMPT: VSS_CAPTION_SUMMARIZATION_PROMPT, - FIELD_SUMMARY_AGGREGATION_PROMPT: VSS_SUMMARY_AGGREGATION_PROMPT, - }) - - return payload - - def _get_description_blocking(self, file_id: str) -> Optional[str]: - """Get video description using blocking mode.""" - logger.info(f"Requesting summarization (blocking) for file: {file_id}") - - payload = self._build_summarize_payload(file_id, stream=False) - - if VSS_CHUNK_DURATION > 0: - logger.info(f"Using chunked processing: {VSS_CHUNK_DURATION}s chunks, " - f"{VSS_NUM_FRAMES_PER_CHUNK} frames/chunk") - - try: - response = requests.post( - f'{self.base_url}{API_VSS_SUMMARIZE}', - json=payload, - headers={'Content-Type': 'application/json'}, - timeout=self.timeout - ) - except requests.RequestException as e: - logger.error(f"Error requesting video summary: {e}") - return None - - if response.status_code != 200: - logger.error(f"Summarize failed: {response.status_code}") - logger.error(f"Response: {response.text}") - return None - - result = response.json() - content = self._extract_content(result) - - if not content: - logger.warning("No content in summarize response") - return None - - logger.info(f"✓ Got video summary ({len(content)} chars)") - return content - - def _get_description_streaming(self, file_id: str) -> Optional[str]: - """Get video description using streaming mode (Server-Sent Events).""" - logger.info(f"Requesting summarization (streaming) for file: {file_id}") - - payload = self._build_summarize_payload(file_id, stream=True) - - if VSS_CHUNK_DURATION > 0: - logger.info(f"Using chunked processing: {VSS_CHUNK_DURATION}s chunks, " - f"{VSS_NUM_FRAMES_PER_CHUNK} frames/chunk") - - try: - response = requests.post( - f'{self.base_url}{API_VSS_SUMMARIZE}', - json=payload, - headers={'Content-Type': 'application/json'}, - timeout=self.timeout, - stream=True # Enable streaming response - ) - except requests.RequestException as e: - logger.error(f"Error requesting video summary (streaming): {e}") - return None - - if response.status_code != 200: - logger.error(f"Summarize failed: {response.status_code}") - logger.error(f"Response: {response.text}") - return None - - # Collect streaming chunks - content = self._collect_stream_content(response) - - if not content: - logger.warning("No content in streaming response") - return None - - logger.info(f"✓ Got video summary via streaming ({len(content)} chars)") - return content - - def _collect_stream_content(self, response) -> Optional[str]: - """Collect content from streaming response (Server-Sent Events).""" - chunks = [] - chunk_count = 0 - - for line in response.iter_lines(decode_unicode=True): - if not line: - continue - - # SSE format: "data: {...json...}" - if line.startswith(RESP_DATA_PREFIX): - json_str = line[len(RESP_DATA_PREFIX):] - - # Handle [DONE] marker - if json_str.strip() == '[DONE]': - logger.info("Stream completed") - break - - try: - data = json.loads(json_str) - content = self._extract_stream_chunk(data) - if content: - chunks.append(content) - chunk_count += 1 - if chunk_count % 10 == 0: - logger.info(f"Received {chunk_count} chunks...") - elif chunk_count == 0: - # Debug: log first non-content chunk to understand format - logger.debug(f"SSE data (no content): {json_str[:200]}") - except json.JSONDecodeError as e: - logger.debug(f"SSE JSON parse error: {e}, line: {json_str[:100]}") - continue - else: - # Non-data line - could be event type or comment - if chunk_count == 0: - logger.debug(f"SSE non-data line: {line[:100]}") - - if not chunks: - logger.warning("No streaming chunks collected, checking full response") - return None - - logger.info(f"Collected {chunk_count} streaming chunks") - return ''.join(chunks) - - def _extract_stream_chunk(self, data: dict) -> Optional[str]: - """Extract content from a single streaming chunk.""" - choices = data.get(RESP_CHOICES, []) - if choices: - delta = choices[0].get(RESP_DELTA, {}) - content = delta.get(RESP_CONTENT) - if content: - return content - - message = choices[0].get(RESP_MESSAGE, {}) - content = message.get(RESP_CONTENT) - if content: - return content - - if RESP_CONTENT in data: - return data[RESP_CONTENT] - - if RESP_RESPONSE in data: - return data[RESP_RESPONSE] - - if RESP_TEXT in data: - return data[RESP_TEXT] - - return None - - def _extract_content(self, result: dict) -> Optional[str]: - """Extract text content from VSS response. - - returns OpenAI-compatible format: - { - "choices": [{"message": {"content": "..."}}] - } - """ - # Try OpenAI format first - choices = result.get(RESP_CHOICES, []) - if choices: - message = choices[0].get(RESP_MESSAGE, {}) - content = message.get(RESP_CONTENT) - if content: - return content.strip() - - if RESP_CONTENT in result: - return result[RESP_CONTENT].strip() - - if RESP_RESPONSE in result: - return result[RESP_RESPONSE].strip() - - return None - - def _sanitize_filename(self, filename: str) -> str: - """Sanitize filename for VSS compatibility.""" - name = Path(filename).name - file_name_sanitized = re.sub(r'[^A-Za-z0-9_.\-]', '_', name) - file_name_sanitized = re.sub(r'_+', '_', file_name_sanitized) - return file_name_sanitized - - def _get_content_type(self, filename: str) -> str: - """Get content type from filename.""" - ext = Path(filename).suffix.lower() - return CONTENT_TYPE_MAP.get(ext, DEFAULT_CONTENT_TYPE) diff --git a/notebooks/rag_event_ingest.ipynb b/notebooks/rag_event_ingest.ipynb index 6609a6e1d..b2bde4727 100644 --- a/notebooks/rag_event_ingest.ipynb +++ b/notebooks/rag_event_ingest.ipynb @@ -1,1263 +1,793 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Video/Document Continuous Ingestion from Object Storage\n", - "\n", - "## Purpose\n", - "\n", - "This notebook demonstrates an **automated document and video ingestion pipeline** that:\n", - "\n", - "1. Monitors emulated object storage for new uploads via Kafka events\n", - "2. Routes files to appropriate AI services based on file type, currently supports document and video ingestion\n", - "5. Enables RAG Agent for semantic search and contextual Q&A over all ingested content\n", - "\n", - "## What Gets Deployed\n", - "\n", - "1. **NVIDIA RAG** - Document indexing, vector search, and AI-powered Q&A (NIMs, Milvus, Ingestor)\n", - "2. **NVIDIA VSS** - Video understanding and summarization (VLM, LLM NIMs, VSS Engine)\n", - "3. **Continuous Ingestion** - Event-driven ingestion pipeline (Kafka, MinIO, Consumer)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "### Hardware\n", - "- **GPU**: 4x RTX PRO 6000 Blackwell or 4x H100\n", - "\n", - "#### Default GPU Assignment\n", - "\n", - "| GPU | Service |\n", - "|-----|---------|\n", - "| 0 | RAG NIMs (Embedding, Reranker) |\n", - "| 1 | RAG LLM NIM (Llama-3.3-Nemotron-Super-49B) |\n", - "| 2 | VSS LLM NIM (Llama-3.1-8B-Instruct) |\n", - "| 3 | VSS VLM / via-server (Cosmos-Reason2-8B) |\n", - "\n", - "\n", - "### Software (pre-installed required)\n", - "- Ubuntu 22.04 or later\n", - "- Docker 24.0+ with Docker Compose v2\n", - "- NVIDIA Driver 570+\n", - "- NVIDIA Container Toolkit\n", - "\n", - "### API Keys\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
KeyPurposeHow to Get
NGC_API_KEYDocker login, NIM deploymentsNGC Portal → Generate API Key
HF_TOKENDownload VSS modelsHuggingFace Tokens → Create token with Read access
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Table of Contents\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
SectionDescription
SetupClone repo, install deps, set API keys, load helpers
Deploy RAGNIMs, Vector DB, Ingestor, RAG Server
Deploy VSSClone VSS, deploy NIMs and VLM
Deploy Continuous IngestionKafka, MinIO, Consumer
TestingUpload documents & videos, query RAG
Clean UpStop services, clean data
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References\n", - "\n", - "- **RAG Blueprint**: [NVIDIA RAG Documentation](https://github.com/NVIDIA-AI-Blueprints/rag/blob/develop/docs/deploy-docker-self-hosted.md)\n", - "- **VSS**: [Video Search & Summarization Documentation](https://docs.nvidia.com/vss/latest/index.html)\n", - "- **NIM**: [NVIDIA NIM Documentation](https://docs.nvidia.com/nim/index.html)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Setup\n", - "\n", - "Clone the repository, configure API keys, and load helper functions.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Clone Repository\n", - "\n", - "Clone the RAG Blueprint repo to `~/rag`. This includes the consumer source code, deploy configs, and sample test data.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import subprocess, sys, os, shutil\n", - "\n", - "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", - "RAG_REPO_URL = \"https://github.com/NVIDIA-AI-Blueprints/rag.git\"\n", - "\n", - "# Ensure git-lfs is installed before any LFS operations\n", - "if not shutil.which(\"git-lfs\"):\n", - " print(\"[INSTALLING] git-lfs...\")\n", - " subprocess.run(\"sudo apt-get update && sudo apt-get install -y git-lfs && git lfs install\", shell=True, check=True)\n", - "else:\n", - " print(\"[OK] git-lfs found\")\n", - "\n", - "# Clone from correct branch (skip if already exists)\n", - "if not os.path.exists(RAG_REPO_DIR):\n", - " subprocess.run(f\"git clone {RAG_REPO_URL} {RAG_REPO_DIR}\", shell=True, check=True)\n", - "else:\n", - " print(f\"[OK] RAG repo already exists: {RAG_REPO_DIR}\")\n", - "subprocess.run(\"git lfs pull\", shell=True, cwd=RAG_REPO_DIR, check=True)\n", - "\n", - "# Verify\n", - "for path in [\"deploy/compose\", \"examples/rag_event_ingest/kafka_consumer\", \"examples/rag_event_ingest/data\"]:\n", - " status = \"[OK]\" if os.path.exists(os.path.join(RAG_REPO_DIR, path)) else \"[MISSING]\"\n", - " print(f\" {status} {path}\")\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Install Dependencies\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "! python3 -m ensurepip --upgrade" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Ensure pip is available (some minimal Python installs lack it)\n", - "subprocess.run([sys.executable, \"-m\", \"ensurepip\", \"--upgrade\"], capture_output=True)\n", - "\n", - "def check_install_system_pkg(cmd: str, install_cmd: str):\n", - " if shutil.which(cmd):\n", - " print(f\" [OK] {cmd} found\")\n", - " return True\n", - " print(f\" [INSTALLING] {cmd}...\")\n", - " result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True)\n", - " if result.returncode == 0:\n", - " print(f\" [OK] {cmd} installed\")\n", - " return True\n", - " print(f\" [ERROR] Failed to install {cmd}. Please install manually: {install_cmd}\")\n", - " return False\n", - "\n", - "check_install_system_pkg(\"git\", \"sudo apt-get update && sudo apt-get install -y git\")\n", - "\n", - "# Install Python packages\n", - "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\", \"aiohttp\", \"requests\", \"python-dotenv\", \"pyyaml\"])\n", - "print(\"[OK] Ready\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. Set API Keys\n", - "\n", - "Configure NGC and HuggingFace API keys for NIM deployments and model downloads.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import getpass\n", - "\n", - "def set_api_key(env_var: str, prompt: str, required: bool = True):\n", - " if os.environ.get(env_var):\n", - " print(f\" [OK] {env_var} already set ({os.environ[env_var][:10]}...)\")\n", - " return True\n", - " key = getpass.getpass(prompt)\n", - " if key:\n", - " os.environ[env_var] = key\n", - " print(f\" [OK] {env_var} set\")\n", - " return True\n", - " if required:\n", - " print(f\" [ERROR] {env_var} is required\")\n", - " return False\n", - " print(f\" [SKIP] {env_var} (optional)\")\n", - " return True\n", - "\n", - "set_api_key(\"NGC_API_KEY\", \"Enter NGC_API_KEY (starts with 'nvapi-'): \", required=True)\n", - "set_api_key(\"HF_TOKEN\", \"Enter HF_TOKEN (optional, press Enter to skip): \", required=False)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. Helper Functions\n", - "\n", - "Shared utilities for deployment, file upload, status checks, and RAG queries.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Install dependencies\n", - "import sys\n", - "!{sys.executable} -m pip install -q minio aiohttp requests python-dotenv" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os, sys, json, re, subprocess, time, socket, asyncio\n", - "import aiohttp, requests\n", - "from typing import List, Optional, Dict\n", - "\n", - "try:\n", - " from minio import Minio\n", - " from minio.error import S3Error\n", - "except ImportError:\n", - " subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\"])\n", - " from minio import Minio\n", - " from minio.error import S3Error\n", - "\n", - "# =============================================================================\n", - "# CONFIGURATION\n", - "# =============================================================================\n", - "\n", - "# Paths relative to RAG repo root\n", - "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", - "EXAMPLE_DIR = os.path.join(RAG_REPO_DIR, \"examples/rag_event_ingest\")\n", - "AIDP_COMPOSE_FILE = os.path.join(EXAMPLE_DIR, \"deploy/docker-compose.yaml\")\n", - "DATA_DIR = os.path.join(EXAMPLE_DIR, \"data\")\n", - "RAG_SERVER_URL = \"http://localhost:8081\"\n", - "INGESTOR_URL = \"http://localhost:8082\"\n", - "\n", - "VSS_DIR = os.path.expanduser(\"~/video-search-and-summarization\")\n", - "VSS_UI_PORT = 9110\n", - "VSS_API_PORT = 8110\n", - "VSS_LLM_PORT = 8107\n", - "VSS_EMBED_PORT = 8106\n", - "VSS_RERANK_PORT = 8105\n", - "LOCAL_NIM_CACHE = os.path.expanduser(\"~/.cache/nim\")\n", - "\n", - "MINIO_ENDPOINT = \"localhost:9201\"\n", - "MINIO_ACCESS_KEY = \"minioadmin\"\n", - "MINIO_SECRET_KEY = \"minioadmin\"\n", - "MINIO_BUCKET = \"aidp-bucket\"\n", - "MINIO_COLLECTION = \"aidp_bucket\"\n", - "MINIO_CONSOLE_PORT = 9211\n", - "\n", - "# =============================================================================\n", - "# SHARED UTILITIES\n", - "# =============================================================================\n", - "\n", - "def run_command(cmd: str, capture: bool = False) -> Optional[str]:\n", - " \"\"\"Execute a shell command and print it.\"\"\"\n", - " print(f\"$ {cmd}\")\n", - " result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)\n", - " return result.stdout if capture else None\n", - "\n", - "def get_host_ip() -> str:\n", - " \"\"\"Get host IP address for external access URLs.\"\"\"\n", - " try:\n", - " s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n", - " s.connect((\"8.8.8.8\", 80))\n", - " ip = s.getsockname()[0]\n", - " s.close()\n", - " return ip\n", - " except OSError:\n", - " return \"localhost\"\n", - "\n", - "def get_minio_client() -> Minio:\n", - " \"\"\"Create MinIO client for AIDP bucket operations.\"\"\"\n", - " return Minio(MINIO_ENDPOINT, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False)\n", - "\n", - "def upload_file(local_path: str, object_name: Optional[str] = None) -> bool:\n", - " \"\"\"Upload a local file to MinIO AIDP bucket.\"\"\"\n", - " if not os.path.exists(local_path):\n", - " print(f\"[ERROR] File not found: {local_path}\")\n", - " return False\n", - " obj = object_name or os.path.basename(local_path)\n", - " try:\n", - " client = get_minio_client()\n", - " if not client.bucket_exists(MINIO_BUCKET):\n", - " client.make_bucket(MINIO_BUCKET)\n", - " client.fput_object(MINIO_BUCKET, obj, local_path)\n", - " print(f\"[OK] Uploaded: {obj}\")\n", - " return True\n", - " except S3Error as e:\n", - " print(f\"[ERROR] {e}\")\n", - " return False\n", - "\n", - "def verify_file_in_storage(object_name: str, bucket: str = MINIO_BUCKET) -> bool:\n", - " \"\"\"Check if a file exists in MinIO bucket and print verification status.\"\"\"\n", - " try:\n", - " client = get_minio_client()\n", - " stat = client.stat_object(bucket, object_name)\n", - " print(f\"[OK] File verified in storage:\")\n", - " print(f\" Bucket: {bucket}\")\n", - " print(f\" Object: {object_name}\")\n", - " print(f\" Size: {stat.size:,} bytes\")\n", - " print(f\" Modified: {stat.last_modified}\")\n", - " return True\n", - " except S3Error as e:\n", - " print(f\"[ERROR] File not found in storage: {object_name}\")\n", - " print(f\" Error: {e}\")\n", - " return False\n", - "\n", - "def get_consumer_logs(lines: int = 30) -> None:\n", - " \"\"\"Show recent Kafka consumer logs.\"\"\"\n", - " run_command(f\"docker logs kafka-consumer --tail {lines}\")\n", - "\n", - "async def query_rag(question: str, collection: str = None) -> Optional[str]:\n", - " \"\"\"Query RAG system and print the answer.\"\"\"\n", - " coll = collection or MINIO_COLLECTION\n", - " print(f\"Q: {question}\\nCollection: {coll}\\n\" + \"-\" * 40)\n", - "\n", - " payload = {\n", - " \"messages\": [{\"role\": \"user\", \"content\": question}],\n", - " \"use_knowledge_base\": True,\n", - " \"collection_names\": [coll],\n", - " }\n", - " try:\n", - " async with aiohttp.ClientSession() as session:\n", - " async with session.post(\n", - " f\"{RAG_SERVER_URL}/generate\", json=payload,\n", - " timeout=aiohttp.ClientTimeout(total=120),\n", - " ) as resp:\n", - " text = await resp.text()\n", - " # Parse SSE response: extract content from each \"data: {...}\" line\n", - " chunks = []\n", - " for line in text.split(\"\\n\"):\n", - " if not line.startswith(\"data: \") or line[6:] == \"[DONE]\":\n", - " continue\n", - " try:\n", - " msg = json.loads(line[6:]).get(\"choices\", [{}])[0].get(\"message\", {})\n", - " if msg.get(\"content\"):\n", - " chunks.append(msg[\"content\"])\n", - " except json.JSONDecodeError:\n", - " pass\n", - " answer = \"\".join(chunks)\n", - " print(f\"Answer: {answer}\")\n", - " return answer\n", - " except aiohttp.ClientError as e:\n", - " print(f\"[ERROR] {e}\")\n", - " return None\n", - "\n", - "def extract_rag_default_compose_var(file_path, var_name):\n", - " \"\"\"Extract default value from ${VAR:-default} pattern in a compose file.\"\"\"\n", - " pattern = re.compile(r'\\$\\{' + var_name + r':-\"?([^\"}\\s]+)\"?\\}')\n", - " with open(file_path) as f:\n", - " for line in f:\n", - " if line.lstrip().startswith('#'):\n", - " continue\n", - " m = pattern.search(line)\n", - " if m:\n", - " return m.group(1)\n", - " return None\n", - "\n", - "print(f\"[OK] Helpers loaded | Host IP: {get_host_ip()}\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Deploy NVIDIA RAG\n", - "\n", - "Deploy the NVIDIA RAG: NIMs (LLM, Embedding, Reranker), Milvus vector database, Ingestor server, and RAG server.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ngc_key = os.environ.get(\"NGC_API_KEY\")\n", - "if not ngc_key:\n", - " raise RuntimeError(\"NGC_API_KEY not set! Run the API keys cell first.\")\n", - "\n", - "os.chdir(RAG_REPO_DIR)\n", - "\n", - "# Set env vars needed by docker compose\n", - "os.environ[\"NGC_API_KEY\"] = ngc_key\n", - "os.environ[\"USERID\"] = f\"{os.getuid()}:{os.getgid()}\"\n", - "os.environ[\"COLLECTION_NAME\"] = MINIO_COLLECTION\n", - "\n", - "# Load RAG .env defaults (MODEL_DIRECTORY, etc.)\n", - "from dotenv import load_dotenv\n", - "env_file = os.path.join(RAG_REPO_DIR, \"deploy/compose/.env\")\n", - "if os.path.exists(env_file):\n", - " load_dotenv(env_file, override=False)\n", - "\n", - "# Login to nvcr.io\n", - "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", - " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "\n", - "# Deploy components\n", - "for label, compose_file in [\n", - " (\"NIMs\", \"deploy/compose/nims.yaml\"),\n", - " (\"Vector DB\", \"deploy/compose/vectordb.yaml\"),\n", - "]:\n", - " print(f\"Deploying {label}...\")\n", - " run_command(f\"docker compose -f {compose_file} up -d\")\n", - "\n", - "print(\"Waiting 30s for Milvus...\")\n", - "time.sleep(30)\n", - "\n", - "for label, compose_file in [\n", - " (\"Ingestor\", \"deploy/compose/docker-compose-ingestor-server.yaml\"),\n", - " (\"RAG Server\", \"deploy/compose/docker-compose-rag-server.yaml\"),\n", - "]:\n", - " print(f\"Deploying {label}...\")\n", - " run_command(f\"docker compose -f {compose_file} up -d\")\n", - "\n", - "ip = get_host_ip()\n", - "print(f\"\\nRAG deployed: http://{ip}:8081 (server) | http://{ip}:8082 (ingestor) | http://{ip}:8090 (UI)\")\n", - "print(f\"COLLECTION_NAME: {MINIO_COLLECTION}\")\n", - "print(\"Wait ~10 minutes for NIMs to load models, then run the status check cell.\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Verify RAG services are healthy. Wait ~10 minutes for NIMs to load models.\n", - "\n", - "The deployment status should be:\n", - "```\n", - "NAMES STATUS\n", - "rag-frontend Up About a minute\n", - "rag-server Up About a minute\n", - "ingestor-server Up About a minute\n", - "milvus-standalone Up 2 minutes (healthy)\n", - "milvus-etcd Up 2 minutes (healthy)\n", - "milvus-minio Up 2 minutes (healthy)\n", - "nim-llm-ms Up 2 minutes (healthy)\n", - "nemotron-embedding-ms Up 2 minutes (healthy)\n", - "nemotron-ranking-ms Up 2 minutes (healthy)\n", - "```\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check service status and print access URLs\n", - "print(\"Wait ~10 minutes for services to become healthy.\")\n", - "print(\"Run this cell again after waiting.\\n\")\n", - "\n", - "ip = get_host_ip()\n", - "for name, port, path in [\n", - " (\"RAG Server\", 8081, \"/health\"), (\"Ingestor\", 8082, \"/health\"),\n", - " (\"Frontend\", 8090, \"/\"), (\"Milvus\", 19530, \"/v1/vector/collections\"),\n", - "]:\n", - " try:\n", - " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", - " except requests.ConnectionError:\n", - " s = \"[DOWN]\"\n", - " except requests.Timeout:\n", - " s = \"[TIMEOUT]\"\n", - " print(f\" {s} {name}: http://{ip}:{port}\")\n", - "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(rag|milvus|ingestor|nim|nemotron|NAMES)'\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Deploy NVIDIA VSS\n", - "\n", - "Deploy the NVIDIA VSS: NIMs (LLM, Embedding, Reranker) and VLM for video analysis.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# VSS deployment configuration\n", - "VSS_REPO_URL = \"https://github.com/NVIDIA-AI-Blueprints/video-search-and-summarization.git\"\n", - "VSS_TAG = \"v2.4.1\" # VSS Blueprint version (must match VIA_IMAGE tag)\n", - "VSS_GPU_DEVICE = 2 # GPU for VSS LLM NIM\n", - "VSS_VLM_GPU_DEVICE = 3 # GPU for VLM (via-server with Cosmos-Reason2)\n", - "\n", - "_rag_compose_path = os.path.join(RAG_REPO_DIR, \"deploy/compose/docker-compose-rag-server.yaml\")\n", - "RAG_EMBED_MODEL = extract_rag_default_compose_var(_rag_compose_path, \"APP_EMBEDDINGS_MODELNAME\")\n", - "RAG_RERANK_MODEL = extract_rag_default_compose_var(_rag_compose_path, \"APP_RANKING_MODELNAME\")\n", - "print(f\"RAG models from compose: embed={RAG_EMBED_MODEL}, rerank={RAG_RERANK_MODEL}\")\n", - "\n", - "# Only deploy LLM for VSS; embedding and reranker are shared from RAG stack\n", - "# RAG NIMs on nvidia-rag network: nemotron-embedding-ms:8000, nemotron-ranking-ms:8000\n", - "NIM_IMAGES = {\n", - " \"vss-llm\": (\"nvcr.io/nim/meta/llama-3.1-8b-instruct:1.12.0\", VSS_LLM_PORT),\n", - "}\n", - "\n", - "ngc_key = os.environ.get(\"NGC_API_KEY\", \"\")\n", - "hf_token = os.environ.get(\"HF_TOKEN\", \"\")\n", - "if not ngc_key:\n", - " raise RuntimeError(\"NGC_API_KEY not set!\")\n", - "\n", - "# Docker login\n", - "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", - " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "\n", - "# Clone VSS repo with correct release tag\n", - "if not os.path.exists(VSS_DIR):\n", - " print(f\"Cloning {VSS_REPO_URL} (tag: {VSS_TAG})...\")\n", - " subprocess.run(f\"git clone --branch {VSS_TAG} --depth 1 {VSS_REPO_URL} {VSS_DIR}\", shell=True)\n", - "else:\n", - " print(f\"[OK] VSS repo exists: {VSS_DIR}\")\n", - "\n", - "# Deploy VSS LLM NIM container\n", - "os.makedirs(LOCAL_NIM_CACHE, exist_ok=True)\n", - "for name, (image, port) in NIM_IMAGES.items():\n", - " subprocess.run(f\"docker rm -f {name} 2>/dev/null\", shell=True, capture_output=True)\n", - " cmd = f\"\"\"docker run -d --name {name} \\\n", - " -u $(id -u) --gpus '\"device={VSS_GPU_DEVICE}\"' --shm-size=16GB \\\n", - " --network nvidia-rag -e NGC_API_KEY={ngc_key} \\\n", - " -v \"{LOCAL_NIM_CACHE}:/opt/nim/.cache\" \\\n", - " -p {port}:8000 -e NIM_LOW_MEMORY_MODE=1 -e NIM_RELAX_MEM_CONSTRAINTS=1 \\\n", - " {image}\"\"\"\n", - " result = subprocess.run(cmd, shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - " status = \"[OK]\" if result.returncode == 0 else \"[ERROR]\"\n", - " print(f\" {status} {name} -> port {port}\")\n", - "\n", - "# Deploy VSS application (VLM on separate GPU)\n", - "vss_deploy_dir = f\"{VSS_DIR}/deploy/docker/local_deployment_single_gpu\"\n", - "env_content = f\"\"\"NGC_API_KEY={ngc_key}\n", - "HF_TOKEN={hf_token}\n", - "VIA_IMAGE=nvcr.io/nvidia/blueprint/vss-engine:2.4.1\n", - "FRONTEND_PORT={VSS_UI_PORT}\n", - "BACKEND_PORT={VSS_API_PORT}\n", - "MILVUS_DB_HTTP_PORT=19091\n", - "MILVUS_DB_GRPC_PORT=29530\n", - "MINIO_PORT=9002\n", - "MINIO_WEBUI_PORT=9003\n", - "GRAPH_DB_USERNAME=neo4j\n", - "GRAPH_DB_PASSWORD=password\n", - "ARANGO_DB_USERNAME=arangodb\n", - "ARANGO_DB_PASSWORD=password\n", - "CA_RAG_CONFIG=./config.yaml\n", - "GUARDRAILS_CONFIG=./guardrails\n", - "NVIDIA_VISIBLE_DEVICES={VSS_VLM_GPU_DEVICE}\n", - "VLM_MODEL_TO_USE=cosmos-reason2\n", - "MODEL_PATH=git:https://huggingface.co/nvidia/Cosmos-Reason2-8B\n", - "VLLM_GPU_MEMORY_UTILIZATION=0.4\n", - "VLM_MAX_MODEL_LEN=20480\n", - "DISABLE_GUARDRAILS=true\n", - "DISABLE_CV_PIPELINE=true\n", - "ENABLE_AUDIO=false\n", - "\"\"\"\n", - "with open(f\"{vss_deploy_dir}/.env\", \"w\") as f:\n", - " f.write(env_content)\n", - "\n", - "# Patch config.yaml: LLM points to vss-llm, embedding/reranker reuse RAG NIMs\n", - "config_file = f\"{vss_deploy_dir}/config.yaml\"\n", - "if os.path.exists(config_file):\n", - " cfg = open(config_file).read()\n", - " cfg = re.sub(r\"http://[^:]+:8007/v1\", f\"http://host.docker.internal:{VSS_LLM_PORT}/v1\", cfg)\n", - " cfg = re.sub(r\"http://[^:]+:8006/v1\", \"http://host.docker.internal:9080/v1\", cfg)\n", - " cfg = re.sub(r\"http://[^:]+:8005/v1\", \"http://host.docker.internal:1976/v1\", cfg)\n", - " cfg = re.sub(r\"model:\\s*nvidia/[\\w.-]+-embed[\\w.-]+\", f\"model: {RAG_EMBED_MODEL}\", cfg)\n", - " cfg = re.sub(r\"model:\\s*nvidia/[\\w.-]+-re?rank[\\w.-]+\", f\"model: {RAG_RERANK_MODEL}\", cfg)\n", - "\n", - " open(config_file, \"w\").write(cfg)\n", - "\n", - "cmd = f\"cd {vss_deploy_dir} && set -a && source .env && set +a && docker compose up -d\"\n", - "subprocess.run(cmd, shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "\n", - "ip = get_host_ip()\n", - "print(f\"\\nVSS deployed: http://{ip}:{VSS_UI_PORT} (UI) | http://{ip}:{VSS_API_PORT} (API)\")\n", - "print(\"Embedding/Reranker: shared from RAG stack (nemotron-embedding-ms, nemotron-ranking-ms)\")\n", - "print(\"Wait ~10 minutes for NIMs to load models, then run the status check cell.\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Verify VSS services are healthy. Wait ~10 minutes for NIMs to load models.\n", - "\n", - "> **Note**: Embedding and reranker NIMs are shared from the RAG stack (`nemoretriever-embedding-ms`, `nemoretriever-ranking-ms`) — no separate VSS containers needed.\n", - "\n", - "The health check output should be:\n", - "```\n", - " [OK] VSS UI: http://:9110\n", - " [OK] VSS API: http://:8110\n", - " [OK] VSS LLM NIM: http://:8107\n", - " [OK] Embedding (shared from RAG): http://:9080\n", - " [OK] Reranker (shared from RAG): http://:1976\n", - "```\n", - "\n", - "The deployment status should be:\n", - "```\n", - "NAMES STATUS\n", - "local_deployment_single_gpu-via-server-1 Up About a minute\n", - "local_deployment_single_gpu-elasticsearch-1 Up About a minute\n", - "local_deployment_single_gpu-graph-db-1 Up About a minute\n", - "local_deployment_single_gpu-minio-1 Up About a minute\n", - "local_deployment_single_gpu-arango-db-1 Up About a minute\n", - "local_deployment_single_gpu-milvus-standalone-1 Up About a minute (healthy)\n", - "vss-llm Up About a minute\n", - "```\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check service status and print access URLs\n", - "ip = get_host_ip()\n", - "for name, port, path in [\n", - " (\"VSS UI\", VSS_UI_PORT, \"/\"), (\"VSS API\", VSS_API_PORT, \"/\"),\n", - " (\"VSS LLM NIM\", VSS_LLM_PORT, \"/v1/health/ready\"),\n", - " (\"Embedding (shared from RAG)\", 9080, \"/v1/health/ready\"),\n", - " (\"Reranker (shared from RAG)\", 1976, \"/v1/health/ready\"),\n", - "]:\n", - " try:\n", - " requests.get(f\"http://localhost:{port}{path}\", timeout=10)\n", - " s = \"[OK]\"\n", - " except requests.ConnectionError:\n", - " s = \"[DOWN]\"\n", - " except requests.Timeout:\n", - " s = \"[TIMEOUT]\"\n", - " print(f\" {s} {name}: http://{ip}:{port}\")\n", - "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(vss|via|local_deployment|NAMES)'\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Deploy Continuous Ingestion from emulated object storage\n", - "\n", - "Deploy the Continuous Ingestion: Kafka message broker, MinIO object storage, and Kafka consumer for automated ingestion.\n", - "\n", - "## 1. Configure Video Analysis Prompts\n", - "\n", - "Customize the prompts used by the Kafka consumer when processing videos through VSS.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Consumer prompts for video analysis - customize these for your video content\n", - "# These are passed to the Kafka consumer which sends them to VSS for video summarization\n", - "\n", - "CONSUMER_VSS_PROMPT = \"\"\"Analyze this sports video. TIMESTAMP FORMAT: Convert seconds to MM:SS (40s=00:40, 80s=01:20, 150s=02:30, 200s=03:20). \\\n", - "Describe: key plays, scoring, turnovers, big gains, defensive stops, celebrations.\"\"\"\n", - "\n", - "CONSUMER_VSS_SYSTEM_PROMPT = \"\"\"You are a sports broadcaster. CRITICAL: Convert all timestamps from seconds to MM:SS format. \\\n", - "Examples: 40 seconds = 00:40, 90 seconds = 01:30, 200 seconds = 03:20, 350 seconds = 05:50. Focus on action, field position, execution, and game momentum.\"\"\"\n", - "\n", - "CONSUMER_VSS_CAPTION_SUMMARIZATION_PROMPT = \"\"\"Format: [MM:SS] Play description. CONVERT seconds to MM:SS (40s=00:40, 80s=01:20, 150s=02:30). \\\n", - "Describe: what happened, how it was executed, the result. Be vivid like a TV broadcast.\"\"\"\n", - "\n", - "CONSUMER_VSS_SUMMARY_AGGREGATION_PROMPT = \"\"\"Create game summary with MM:SS timestamps. CONVERT all times: 40s=00:40, 80s=01:20, 200s=03:20, 350s=05:50. \\\n", - "Highlight scoring plays, turnovers, momentum shifts, spectacular plays. Write like a highlight reel narrator - vivid and exciting.\"\"\"\n", - "\n", - "print(\"Consumer VSS prompts configured:\")\n", - "print(f\" VSS_PROMPT: {len(CONSUMER_VSS_PROMPT)} chars\")\n", - "print(f\" VSS_SYSTEM_PROMPT: {len(CONSUMER_VSS_SYSTEM_PROMPT)} chars\")\n", - "print(f\" VSS_CAPTION_SUMMARIZATION_PROMPT: {len(CONSUMER_VSS_CAPTION_SUMMARIZATION_PROMPT)} chars\")\n", - "print(f\" VSS_SUMMARY_AGGREGATION_PROMPT: {len(CONSUMER_VSS_SUMMARY_AGGREGATION_PROMPT)} chars\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Deploy Services\n", - "\n", - "Deploy Kafka, MinIO, and the Kafka consumer with custom prompts." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Verify prerequisites\n", - "net_check = subprocess.run(\"docker network inspect nvidia-rag\", shell=True, capture_output=True)\n", - "if net_check.returncode != 0:\n", - " raise RuntimeError(\"nvidia-rag network not found. Deploy RAG first.\")\n", - "\n", - "ngc_key = os.environ.get(\"NGC_API_KEY\", \"\")\n", - "if not ngc_key:\n", - " raise RuntimeError(\"NGC_API_KEY not set!\")\n", - "\n", - "host_ip = get_host_ip()\n", - "\n", - "# Set environment variables for docker compose (including custom prompts)\n", - "os.environ[\"HOST_IP\"] = host_ip\n", - "os.environ[\"VSS_SERVER_URL\"] = f\"http://{host_ip}:{VSS_API_PORT}\"\n", - "os.environ[\"VSS_PROMPT\"] = CONSUMER_VSS_PROMPT\n", - "os.environ[\"VSS_SYSTEM_PROMPT\"] = CONSUMER_VSS_SYSTEM_PROMPT\n", - "os.environ[\"VSS_CAPTION_SUMMARIZATION_PROMPT\"] = CONSUMER_VSS_CAPTION_SUMMARIZATION_PROMPT\n", - "os.environ[\"VSS_SUMMARY_AGGREGATION_PROMPT\"] = CONSUMER_VSS_SUMMARY_AGGREGATION_PROMPT\n", - "\n", - "# Login + pull + build\n", - "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", - " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "\n", - "compose = f\"docker compose -f {AIDP_COMPOSE_FILE}\"\n", - "subprocess.run(f\"{compose} pull --ignore-pull-failures\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "subprocess.run(f\"{compose} up -d --build\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "\n", - "print(f\"Continuous Ingestion deployed:\")\n", - "print(f\" Kafka UI: http://{host_ip}:8080\")\n", - "print(f\" MinIO Console: http://{host_ip}:{MINIO_CONSOLE_PORT}\")\n", - "print(f\" Credentials: minioadmin / minioadmin\")\n", - "print(f\" Custom prompts: ✓ passed to consumer\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Verify continuous ingestion services are running.\n", - "\n", - "The deployment status should be:\n", - "```\n", - "NAMES STATUS\n", - "kafka-consumer Up About a minute\n", - "aidp-kafka-ui Up About a minute\n", - "aidp-minio-mc Up About a minute\n", - "aidp-minio Up About a minute (healthy)\n", - "kafka Up About a minute (healthy)\n", - "```\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check service status and print access URLs\n", - "ip = get_host_ip()\n", - "for name, port, path in [\n", - " (\"Kafka UI\", 8080, \"/\"),\n", - " (\"MinIO Console\", MINIO_CONSOLE_PORT, \"/\"),\n", - "]:\n", - " try:\n", - " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", - " except requests.ConnectionError:\n", - " s = \"[DOWN]\"\n", - " except requests.Timeout:\n", - " s = \"[TIMEOUT]\"\n", - " print(f\" {s} {name}: http://{ip}:{port}\")\n", - "\n", - "# Check kafka-consumer container status\n", - "result = subprocess.run(\"docker inspect -f '{{.State.Status}}' kafka-consumer 2>/dev/null\",\n", - " shell=True, capture_output=True, text=True)\n", - "status = result.stdout.strip()\n", - "s = \"[OK]\" if status == \"running\" else \"[DOWN]\"\n", - "print(f\" {s} Kafka Consumer: {status or 'not found'}\")\n", - "\n", - "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(kafka|minio|NAMES)'\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Testing\n", - "\n", - "Test the deployment by uploading documents and videos, then querying via RAG.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Document Upload\n", - "\n", - "Upload a PDF document to MinIO, which triggers automatic ingestion via Kafka consumer.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.1 Upload to Storage\n", - "\n", - "Upload the document to MinIO object storage.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Sample documents are included in the repo under examples/rag_event_ingest/data/\n", - "pdf_path = os.path.join(DATA_DIR, \"documents\", \"Seahawks-Patriots in Super Bowl LX_ What We Learned from Seattle's 29-13 win.pdf\")\n", - "upload_file(pdf_path, \"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.2 Verify Document Ingestion" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Check consumer logs to verify document processing status.\n", - "\n", - "The logs should show the document being picked up and successfully ingested:\n", - "```\n", - "services.document_indexer - INFO - Task ...: PENDING (0s)\n", - "services.document_indexer - INFO - Task ...: PENDING (5s)\n", - "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", - "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", - "```\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Verify file landed in object storage\n", - "verify_file_in_storage(\"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.3 Verify Document Ingestion\n", - "\n", - "Check consumer logs to verify document processing status.\n", - "\n", - "The logs should show the document being picked up and successfully ingested:\n", - "```\n", - "services.document_indexer - INFO - Task ...: PENDING (0s)\n", - "services.document_indexer - INFO - Task ...: PENDING (5s)\n", - "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", - "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check consumer logs for ingestion status\n", - "print(\"Waiting for document processing...\")\n", - "get_consumer_logs(50)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.4 Query Document via RAG\n", - "\n", - "You can query the ingested document either **programmatically** below or via the **RAG Frontend UI**.\n", - "\n", - "> **💡 RAG Frontend**: Open `http://:8090` in your browser for an interactive Q&A interface.\n", - "> Make sure to select the collection **`aidp_bucket`** in the UI.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Query the document\n", - "await query_rag(\"What was the final score and who won Super Bowl LX?\", MINIO_COLLECTION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Ask another question about the document.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Query about key takeaways\n", - "await query_rag(\"What were the key lessons learned from Seattle's victory in Super Bowl LX?\", MINIO_COLLECTION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Video Upload\n", - "\n", - "Upload a video to MinIO, which triggers automatic ingestion via Kafka consumer → VSS for video analysis → RAG for indexing.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.1 Upload to Storage\n", - "\n", - "Upload the video to MinIO object storage.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Sample videos are included in the repo under examples/rag_event_ingest/data/\n", - "video_path = os.path.join(DATA_DIR, \"videos\", \"Seattle Seahawks vs New England Patriots - Super Bowl LX Game Highlights.mp4\")\n", - "upload_file(video_path)\n", - "\n", - "print(\"\\nVideo processing takes longer than documents. Check consumer logs for progress.\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.2 Verify Video Ingestion\n", - "\n", - "Check consumer logs to verify video processing status.\n", - "\n", - "The logs should show the video being picked up and processed by VSS:\n", - "```\n", - "handlers.video - INFO - [VideoHandler] Processing video: Seattle Seahawks vs New England Patriots - Super Bowl LX Game Highlights.mp4\n", - "services.video_analyzer - INFO - Submitting video to VSS...\n", - "services.video_analyzer - INFO - VSS processing complete\n", - "handlers.base - INFO - [VideoHandler] ✓ Seattle Seahawks...mp4 → SUCCESS\n", - "consumer - INFO - ✓ SUMMARY: Seattle Seahawks...mp4 | Collection: aidp_bucket | Duration: ~120s | Status: SUCCESS\n", - "```\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Verify video landed in object storage\n", - "video_filename = os.path.basename(video_path)\n", - "verify_file_in_storage(video_filename)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.3 Verify Video Ingestion\n", - "\n", - "Check consumer logs to verify video processing status.\n", - "\n", - "The logs should show the video being picked up and processed by VSS:\n", - "```\n", - "handlers.video - INFO - [VideoHandler] Processing video: Seattle Seahawks vs New England Patriots - Super Bowl LX Game Highlights.mp4\n", - "services.video_analyzer - INFO - Submitting video to VSS...\n", - "services.video_analyzer - INFO - VSS processing complete\n", - "handlers.base - INFO - [VideoHandler] ✓ Seattle Seahawks...mp4 → SUCCESS\n", - "consumer - INFO - ✓ SUMMARY: Seattle Seahawks...mp4 | Collection: aidp_bucket | Duration: ~120s | Status: SUCCESS\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check consumer logs for ingestion status\n", - "print(\"Waiting for video processing...\")\n", - "get_consumer_logs(50)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.4 Query Video via RAG\n", - "\n", - "Query the video content.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Query about the video content\n", - "await query_rag(\"Summarize the video content\", MINIO_COLLECTION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Query about a specific time range in the video.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Query about specific time range\n", - "await query_rag(\"What happened between 15:00 and 20:00?\", MINIO_COLLECTION)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Additional query: analyze key defensive plays and turnovers.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Defensive Analysis\n", - "await query_rag(\"Describe the key defensive plays and turnovers that impacted the game outcome.\", MINIO_COLLECTION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Additional query: identify critical momentum-changing plays in the second half.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Momentum Shifts\n", - "await query_rag(\"What were the critical momentum-changing plays in the second half of the game?\", MINIO_COLLECTION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.5 Delete Video and Verify Removal from RAG\n", - "\n", - "Delete the video from MinIO and confirm the ingested content is removed from the RAG collection." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Delete video from MinIO bucket\n", - "video_filename = os.path.basename(video_path)\n", - "try:\n", - " client = get_minio_client()\n", - " client.remove_object(MINIO_BUCKET, video_filename)\n", - " print(f\"[OK] Deleted '{video_filename}' from MinIO bucket '{MINIO_BUCKET}'\")\n", - "except S3Error as e:\n", - " print(f\"[ERROR] Failed to delete: {e}\")\n", - "\n", - "# Wait for Kafka consumer to process the delete event\n", - "print(\"Waiting 15s for consumer to process delete event...\")\n", - "time.sleep(15)\n", - "get_consumer_logs(20)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Verify the video description document was removed from the ingestor\n", - "video_basename = os.path.basename(video_path)\n", - "desc_filename = f\"{video_basename}_description.json\"\n", - "\n", - "resp = requests.get(f\"{INGESTOR_URL}/documents\", params={\"collection_name\": MINIO_COLLECTION}, timeout=30)\n", - "if resp.status_code == 200:\n", - " docs = resp.json().get(\"documents\", [])\n", - " video_docs = [d for d in docs if desc_filename in str(d)]\n", - " if not video_docs:\n", - " print(f\"[OK] '{desc_filename}' removed from collection '{MINIO_COLLECTION}'\")\n", - " else:\n", - " print(f\"[WARN] '{desc_filename}' still found in collection\")\n", - "else:\n", - " print(f\"[INFO] Could not list documents (status {resp.status_code}), checking via query instead\")\n", - "\n", - "print(\"\\nQuerying RAG (answers may come from the PDF document, not the deleted video):\")\n", - "await query_rag(\"Summarize the video content\", MINIO_COLLECTION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Clean Up\n", - "\n", - "Stop all services and clean up ingested data.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Stop RAG Deployment\n", - "\n", - "Stop all RAG services (NIMs, Milvus, Ingestor, RAG server).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "os.chdir(RAG_REPO_DIR)\n", - "for f in [\n", - " \"deploy/compose/docker-compose-rag-server.yaml\",\n", - " \"deploy/compose/docker-compose-ingestor-server.yaml\",\n", - " \"deploy/compose/vectordb.yaml\",\n", - " \"deploy/compose/nims.yaml\",\n", - "]:\n", - " run_command(f\"docker compose -f {f} down\")\n", - "print(\"[OK] RAG stopped\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Stop VSS Deployment\n", - "\n", - "Stop all VSS services (NIMs, VLM, via-server).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "vss_deploy_dir = f\"{VSS_DIR}/deploy/docker/local_deployment_single_gpu\"\n", - "if os.path.exists(vss_deploy_dir):\n", - " subprocess.run(f\"cd {vss_deploy_dir} && set -a && source .env 2>/dev/null && set +a && docker compose down\",\n", - " shell=True, executable=\"/bin/bash\", capture_output=True)\n", - "subprocess.run(\"docker rm -f vss-llm 2>/dev/null\", shell=True, capture_output=True)\n", - "print(\"[OK] VSS stopped\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. Stop Continuous ingestion Deployment\n", - "\n", - "Stop Continuous ingestion services (Kafka, MinIO, Consumer).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "run_command(f\"docker compose -f {AIDP_COMPOSE_FILE} down\")\n", - "print(\"[OK] Continuous ingestion stopped\")\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Document Continuous Ingestion from Object Storage\n", + "\n", + "## Purpose\n", + "\n", + "This notebook demonstrates an **automated document ingestion pipeline** that:\n", + "\n", + "1. Monitors emulated object storage for new uploads via Kafka events\n", + "2. Routes documents to appropriate AI services for indexing\n", + "5. Enables RAG Agent for semantic search and contextual Q&A over all ingested content\n", + "\n", + "## What Gets Deployed\n", + "\n", + "1. **NVIDIA RAG** - Document indexing, vector search, and AI-powered Q&A (NIMs, Milvus, Ingestor)\n", + "2. **Continuous Ingestion** - Event-driven ingestion pipeline (Kafka, MinIO, Consumer)\n" + ] }, - "nbformat": 4, - "nbformat_minor": 4 + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "### Hardware\n", + "- **GPU**: 2x RTX PRO 6000 Blackwell or 2x H100\n", + "\n", + "#### Default GPU Assignment\n", + "\n", + "| GPU | Service |\n", + "|-----|---------|\n", + "| 0 | RAG NIMs (Embedding, Reranker) |\n", + "| 1 | RAG LLM NIM (Llama-3.3-Nemotron-Super-49B) |\n", + "\n", + "\n", + "### Software (pre-installed required)\n", + "- Ubuntu 22.04 or later\n", + "- Docker 24.0+ with Docker Compose v2\n", + "- NVIDIA Driver 570+\n", + "- NVIDIA Container Toolkit\n", + "\n", + "### API Keys\n", + "\n", + "\n", + "\n", + "\n", + "
KeyPurposeHow to Get
NGC_API_KEYDocker login, NIM deploymentsNGC Portal → Generate API Key
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of Contents\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
SectionDescription
SetupClone repo, install deps, set API keys, load helpers
Deploy RAGNIMs, Vector DB, Ingestor, RAG Server
Deploy Continuous IngestionKafka, MinIO, Consumer
TestingUpload documents, query RAG
Clean UpStop services, clean data
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "- **RAG Blueprint**: [NVIDIA RAG Documentation](https://github.com/NVIDIA-AI-Blueprints/rag/blob/develop/docs/deploy-docker-self-hosted.md)\n", + "- **NIM**: [NVIDIA NIM Documentation](https://docs.nvidia.com/nim/index.html)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup\n", + "\n", + "Clone the repository, configure API keys, and load helper functions.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Clone Repository\n", + "\n", + "Clone the RAG Blueprint repo to `~/rag`. This includes the consumer source code, deploy configs, and sample test data.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess, sys, os, shutil\n", + "\n", + "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", + "RAG_REPO_URL = \"https://github.com/NVIDIA-AI-Blueprints/rag.git\"\n", + "\n", + "# Ensure git-lfs is installed before any LFS operations\n", + "if not shutil.which(\"git-lfs\"):\n", + " print(\"[INSTALLING] git-lfs...\")\n", + " subprocess.run(\"sudo apt-get update && sudo apt-get install -y git-lfs && git lfs install\", shell=True, check=True)\n", + "else:\n", + " print(\"[OK] git-lfs found\")\n", + "\n", + "# Clone from correct branch (skip if already exists)\n", + "if not os.path.exists(RAG_REPO_DIR):\n", + " subprocess.run(f\"git clone {RAG_REPO_URL} {RAG_REPO_DIR}\", shell=True, check=True)\n", + "else:\n", + " print(f\"[OK] RAG repo already exists: {RAG_REPO_DIR}\")\n", + "subprocess.run(\"git lfs pull\", shell=True, cwd=RAG_REPO_DIR, check=True)\n", + "\n", + "# Verify\n", + "for path in [\"deploy/compose\", \"examples/rag_event_ingest/kafka_consumer\", \"examples/rag_event_ingest/data\"]:\n", + " status = \"[OK]\" if os.path.exists(os.path.join(RAG_REPO_DIR, path)) else \"[MISSING]\"\n", + " print(f\" {status} {path}\")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Install Dependencies\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! python3 -m ensurepip --upgrade" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Ensure pip is available (some minimal Python installs lack it)\n", + "subprocess.run([sys.executable, \"-m\", \"ensurepip\", \"--upgrade\"], capture_output=True)\n", + "\n", + "def check_install_system_pkg(cmd: str, install_cmd: str):\n", + " if shutil.which(cmd):\n", + " print(f\" [OK] {cmd} found\")\n", + " return True\n", + " print(f\" [INSTALLING] {cmd}...\")\n", + " result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True)\n", + " if result.returncode == 0:\n", + " print(f\" [OK] {cmd} installed\")\n", + " return True\n", + " print(f\" [ERROR] Failed to install {cmd}. Please install manually: {install_cmd}\")\n", + " return False\n", + "\n", + "check_install_system_pkg(\"git\", \"sudo apt-get update && sudo apt-get install -y git\")\n", + "\n", + "# Install Python packages\n", + "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\", \"aiohttp\", \"requests\", \"python-dotenv\", \"pyyaml\"])\n", + "print(\"[OK] Ready\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Set API Keys\n", + "\n", + "Configure NGC API key for NIM deployments.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "\n", + "def set_api_key(env_var: str, prompt: str, required: bool = True):\n", + " if os.environ.get(env_var):\n", + " print(f\" [OK] {env_var} already set ({os.environ[env_var][:10]}...)\")\n", + " return True\n", + " key = getpass.getpass(prompt)\n", + " if key:\n", + " os.environ[env_var] = key\n", + " print(f\" [OK] {env_var} set\")\n", + " return True\n", + " if required:\n", + " print(f\" [ERROR] {env_var} is required\")\n", + " return False\n", + " print(f\" [SKIP] {env_var} (optional)\")\n", + " return True\n", + "\n", + "set_api_key(\"NGC_API_KEY\", \"Enter NGC_API_KEY (starts with 'nvapi-'): \", required=True)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Helper Functions\n", + "\n", + "Shared utilities for deployment, file upload, status checks, and RAG queries.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install dependencies\n", + "import sys\n", + "!{sys.executable} -m pip install -q minio aiohttp requests python-dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os, sys, json, re, subprocess, time, socket, asyncio\n", + "import aiohttp, requests\n", + "from typing import List, Optional, Dict\n", + "\n", + "try:\n", + " from minio import Minio\n", + " from minio.error import S3Error\n", + "except ImportError:\n", + " subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\"])\n", + " from minio import Minio\n", + " from minio.error import S3Error\n", + "\n", + "# =============================================================================\n", + "# CONFIGURATION\n", + "# =============================================================================\n", + "\n", + "# Paths relative to RAG repo root\n", + "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", + "EXAMPLE_DIR = os.path.join(RAG_REPO_DIR, \"examples/rag_event_ingest\")\n", + "AIDP_COMPOSE_FILE = os.path.join(EXAMPLE_DIR, \"deploy/docker-compose.yaml\")\n", + "DATA_DIR = os.path.join(EXAMPLE_DIR, \"data\")\n", + "RAG_SERVER_URL = \"http://localhost:8081\"\n", + "INGESTOR_URL = \"http://localhost:8082\"\n", + "\n", + "LOCAL_NIM_CACHE = os.path.expanduser(\"~/.cache/nim\")\n", + "\n", + "MINIO_ENDPOINT = \"localhost:9201\"\n", + "MINIO_ACCESS_KEY = \"minioadmin\"\n", + "MINIO_SECRET_KEY = \"minioadmin\"\n", + "MINIO_BUCKET = \"aidp-bucket\"\n", + "MINIO_COLLECTION = \"aidp_bucket\"\n", + "MINIO_CONSOLE_PORT = 9211\n", + "\n", + "# =============================================================================\n", + "# SHARED UTILITIES\n", + "# =============================================================================\n", + "\n", + "def run_command(cmd: str, capture: bool = False) -> Optional[str]:\n", + " \"\"\"Execute a shell command and print it.\"\"\"\n", + " print(f\"$ {cmd}\")\n", + " result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)\n", + " return result.stdout if capture else None\n", + "\n", + "def get_host_ip() -> str:\n", + " \"\"\"Get host IP address for external access URLs.\"\"\"\n", + " try:\n", + " s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n", + " s.connect((\"8.8.8.8\", 80))\n", + " ip = s.getsockname()[0]\n", + " s.close()\n", + " return ip\n", + " except OSError:\n", + " return \"localhost\"\n", + "\n", + "def get_minio_client() -> Minio:\n", + " \"\"\"Create MinIO client for AIDP bucket operations.\"\"\"\n", + " return Minio(MINIO_ENDPOINT, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False)\n", + "\n", + "def upload_file(local_path: str, object_name: Optional[str] = None) -> bool:\n", + " \"\"\"Upload a local file to MinIO AIDP bucket.\"\"\"\n", + " if not os.path.exists(local_path):\n", + " print(f\"[ERROR] File not found: {local_path}\")\n", + " return False\n", + " obj = object_name or os.path.basename(local_path)\n", + " try:\n", + " client = get_minio_client()\n", + " if not client.bucket_exists(MINIO_BUCKET):\n", + " client.make_bucket(MINIO_BUCKET)\n", + " client.fput_object(MINIO_BUCKET, obj, local_path)\n", + " print(f\"[OK] Uploaded: {obj}\")\n", + " return True\n", + " except S3Error as e:\n", + " print(f\"[ERROR] {e}\")\n", + " return False\n", + "\n", + "def verify_file_in_storage(object_name: str, bucket: str = MINIO_BUCKET) -> bool:\n", + " \"\"\"Check if a file exists in MinIO bucket and print verification status.\"\"\"\n", + " try:\n", + " client = get_minio_client()\n", + " stat = client.stat_object(bucket, object_name)\n", + " print(f\"[OK] File verified in storage:\")\n", + " print(f\" Bucket: {bucket}\")\n", + " print(f\" Object: {object_name}\")\n", + " print(f\" Size: {stat.size:,} bytes\")\n", + " print(f\" Modified: {stat.last_modified}\")\n", + " return True\n", + " except S3Error as e:\n", + " print(f\"[ERROR] File not found in storage: {object_name}\")\n", + " print(f\" Error: {e}\")\n", + " return False\n", + "\n", + "def get_consumer_logs(lines: int = 30) -> None:\n", + " \"\"\"Show recent Kafka consumer logs.\"\"\"\n", + " run_command(f\"docker logs kafka-consumer --tail {lines}\")\n", + "\n", + "async def query_rag(question: str, collection: str = None) -> Optional[str]:\n", + " \"\"\"Query RAG system and print the answer.\"\"\"\n", + " coll = collection or MINIO_COLLECTION\n", + " print(f\"Q: {question}\\nCollection: {coll}\\n\" + \"-\" * 40)\n", + "\n", + " payload = {\n", + " \"messages\": [{\"role\": \"user\", \"content\": question}],\n", + " \"use_knowledge_base\": True,\n", + " \"collection_names\": [coll],\n", + " }\n", + " try:\n", + " async with aiohttp.ClientSession() as session:\n", + " async with session.post(\n", + " f\"{RAG_SERVER_URL}/generate\", json=payload,\n", + " timeout=aiohttp.ClientTimeout(total=120),\n", + " ) as resp:\n", + " text = await resp.text()\n", + " # Parse SSE response: extract content from each \"data: {...}\" line\n", + " chunks = []\n", + " for line in text.split(\"\\n\"):\n", + " if not line.startswith(\"data: \") or line[6:] == \"[DONE]\":\n", + " continue\n", + " try:\n", + " msg = json.loads(line[6:]).get(\"choices\", [{}])[0].get(\"message\", {})\n", + " if msg.get(\"content\"):\n", + " chunks.append(msg[\"content\"])\n", + " except json.JSONDecodeError:\n", + " pass\n", + " answer = \"\".join(chunks)\n", + " print(f\"Answer: {answer}\")\n", + " return answer\n", + " except aiohttp.ClientError as e:\n", + " print(f\"[ERROR] {e}\")\n", + " return None\n", + "\n", + "print(f\"[OK] Helpers loaded | Host IP: {get_host_ip()}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy NVIDIA RAG\n", + "\n", + "Deploy the NVIDIA RAG: NIMs (LLM, Embedding, Reranker), Milvus vector database, Ingestor server, and RAG server.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ngc_key = os.environ.get(\"NGC_API_KEY\")\n", + "if not ngc_key:\n", + " raise RuntimeError(\"NGC_API_KEY not set! Run the API keys cell first.\")\n", + "\n", + "os.chdir(RAG_REPO_DIR)\n", + "\n", + "# Set env vars needed by docker compose\n", + "os.environ[\"NGC_API_KEY\"] = ngc_key\n", + "os.environ[\"USERID\"] = f\"{os.getuid()}:{os.getgid()}\"\n", + "os.environ[\"COLLECTION_NAME\"] = MINIO_COLLECTION\n", + "\n", + "# Load RAG .env defaults (MODEL_DIRECTORY, etc.)\n", + "from dotenv import load_dotenv\n", + "env_file = os.path.join(RAG_REPO_DIR, \"deploy/compose/.env\")\n", + "if os.path.exists(env_file):\n", + " load_dotenv(env_file, override=False)\n", + "\n", + "# Login to nvcr.io\n", + "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", + " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "# Deploy components\n", + "for label, compose_file in [\n", + " (\"NIMs\", \"deploy/compose/nims.yaml\"),\n", + " (\"Vector DB\", \"deploy/compose/vectordb.yaml\"),\n", + "]:\n", + " print(f\"Deploying {label}...\")\n", + " run_command(f\"USERID=$(id -u) docker compose -f {compose_file} up -d\")\n", + "\n", + "print(\"Waiting 30s for Milvus...\")\n", + "time.sleep(30)\n", + "\n", + "for label, compose_file in [\n", + " (\"Ingestor\", \"deploy/compose/docker-compose-ingestor-server.yaml\"),\n", + " (\"RAG Server\", \"deploy/compose/docker-compose-rag-server.yaml\"),\n", + "]:\n", + " print(f\"Deploying {label}...\")\n", + " run_command(f\"docker compose -f {compose_file} up -d\")\n", + "\n", + "ip = get_host_ip()\n", + "print(f\"\\nRAG deployed: http://{ip}:8081 (server) | http://{ip}:8082 (ingestor) | http://{ip}:8090 (UI)\")\n", + "print(f\"COLLECTION_NAME: {MINIO_COLLECTION}\")\n", + "print(\"Wait ~10 minutes for NIMs to load models, then run the status check cell.\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify RAG services are healthy. Wait ~10 minutes for NIMs to load models.\n", + "\n", + "The deployment status should be:\n", + "```\n", + "NAMES STATUS\n", + "rag-frontend Up About a minute\n", + "rag-server Up About a minute\n", + "ingestor-server Up About a minute\n", + "milvus-standalone Up 2 minutes (healthy)\n", + "milvus-etcd Up 2 minutes (healthy)\n", + "milvus-minio Up 2 minutes (healthy)\n", + "nim-llm-ms Up 2 minutes (healthy)\n", + "nemotron-embedding-ms Up 2 minutes (healthy)\n", + "nemotron-ranking-ms Up 2 minutes (healthy)\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check service status and print access URLs\n", + "print(\"Wait ~10 minutes for services to become healthy.\")\n", + "print(\"Run this cell again after waiting.\\n\")\n", + "\n", + "ip = get_host_ip()\n", + "for name, port, path in [\n", + " (\"RAG Server\", 8081, \"/health\"), (\"Ingestor\", 8082, \"/health\"),\n", + " (\"Frontend\", 8090, \"/\"), (\"Milvus\", 19530, \"/v1/vector/collections\"),\n", + "]:\n", + " try:\n", + " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", + " except requests.ConnectionError:\n", + " s = \"[DOWN]\"\n", + " except requests.Timeout:\n", + " s = \"[TIMEOUT]\"\n", + " print(f\" {s} {name}: http://{ip}:{port}\")\n", + "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(rag|milvus|ingestor|nim|nemotron|NAMES)'\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy Continuous Ingestion from emulated object storage\n", + "\n", + "Deploy the Continuous Ingestion: Kafka message broker, MinIO object storage, and Kafka consumer for automated ingestion.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Deploy Services\n", + "\n", + "Deploy Kafka, MinIO, and the Kafka consumer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify prerequisites\n", + "net_check = subprocess.run(\"docker network inspect nvidia-rag\", shell=True, capture_output=True)\n", + "if net_check.returncode != 0:\n", + " raise RuntimeError(\"nvidia-rag network not found. Deploy RAG first.\")\n", + "\n", + "ngc_key = os.environ.get(\"NGC_API_KEY\", \"\")\n", + "if not ngc_key:\n", + " raise RuntimeError(\"NGC_API_KEY not set!\")\n", + "\n", + "host_ip = get_host_ip()\n", + "\n", + "# Set environment variables for docker compose\n", + "os.environ[\"HOST_IP\"] = host_ip\n", + "\n", + "# Login + pull + build\n", + "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", + " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "compose = f\"docker compose -f {AIDP_COMPOSE_FILE}\"\n", + "subprocess.run(f\"{compose} pull --ignore-pull-failures\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "subprocess.run(f\"{compose} up -d --build\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "print(f\"Continuous Ingestion deployed:\")\n", + "print(f\" Kafka UI: http://{host_ip}:8080\")\n", + "print(f\" MinIO Console: http://{host_ip}:{MINIO_CONSOLE_PORT}\")\n", + "print(f\" Credentials: minioadmin / minioadmin\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify continuous ingestion services are running.\n", + "\n", + "The deployment status should be:\n", + "```\n", + "NAMES STATUS\n", + "kafka-consumer Up About a minute\n", + "aidp-kafka-ui Up About a minute\n", + "aidp-minio-mc Up About a minute\n", + "aidp-minio Up About a minute (healthy)\n", + "kafka Up About a minute (healthy)\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check service status and print access URLs\n", + "ip = get_host_ip()\n", + "for name, port, path in [\n", + " (\"Kafka UI\", 8080, \"/\"),\n", + " (\"MinIO Console\", MINIO_CONSOLE_PORT, \"/\"),\n", + "]:\n", + " try:\n", + " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", + " except requests.ConnectionError:\n", + " s = \"[DOWN]\"\n", + " except requests.Timeout:\n", + " s = \"[TIMEOUT]\"\n", + " print(f\" {s} {name}: http://{ip}:{port}\")\n", + "\n", + "# Check kafka-consumer container status\n", + "result = subprocess.run(\"docker inspect -f '{{.State.Status}}' kafka-consumer 2>/dev/null\",\n", + " shell=True, capture_output=True, text=True)\n", + "status = result.stdout.strip()\n", + "s = \"[OK]\" if status == \"running\" else \"[DOWN]\"\n", + "print(f\" {s} Kafka Consumer: {status or 'not found'}\")\n", + "\n", + "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(kafka|minio|NAMES)'\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Testing\n", + "\n", + "Test the deployment by uploading documents, then querying via RAG.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Document Upload\n", + "\n", + "Upload a PDF document to MinIO, which triggers automatic ingestion via Kafka consumer.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1 Upload to Storage\n", + "\n", + "Upload the document to MinIO object storage.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Sample documents are included in the repo under examples/rag_event_ingest/data/\n", + "pdf_path = os.path.join(DATA_DIR, \"documents\", \"Seahawks-Patriots in Super Bowl LX_ What We Learned from Seattle's 29-13 win.pdf\")\n", + "upload_file(pdf_path, \"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Verify Document Ingestion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check consumer logs to verify document processing status.\n", + "\n", + "The logs should show the document being picked up and successfully ingested:\n", + "```\n", + "services.document_indexer - INFO - Task ...: PENDING (0s)\n", + "services.document_indexer - INFO - Task ...: PENDING (5s)\n", + "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", + "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify file landed in object storage\n", + "verify_file_in_storage(\"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.3 Verify Document Ingestion\n", + "\n", + "Check consumer logs to verify document processing status.\n", + "\n", + "The logs should show the document being picked up and successfully ingested:\n", + "```\n", + "services.document_indexer - INFO - Task ...: PENDING (0s)\n", + "services.document_indexer - INFO - Task ...: PENDING (5s)\n", + "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", + "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check consumer logs for ingestion status\n", + "print(\"Waiting for document processing...\")\n", + "get_consumer_logs(50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.4 Query Document via RAG\n", + "\n", + "You can query the ingested document either **programmatically** below or via the **RAG Frontend UI**.\n", + "\n", + "> **💡 RAG Frontend**: Open `http://:8090` in your browser for an interactive Q&A interface.\n", + "> Make sure to select the collection **`aidp_bucket`** in the UI.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Query the document\n", + "await query_rag(\"What was the final score and who won Super Bowl LX?\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ask another question about the document.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Query about key takeaways\n", + "await query_rag(\"What were the key lessons learned from Seattle's victory in Super Bowl LX?\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Clean Up\n", + "\n", + "Stop all services and clean up ingested data.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Stop RAG Deployment\n", + "\n", + "Stop all RAG services (NIMs, Milvus, Ingestor, RAG server).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "os.chdir(RAG_REPO_DIR)\n", + "for f in [\n", + " \"deploy/compose/docker-compose-rag-server.yaml\",\n", + " \"deploy/compose/docker-compose-ingestor-server.yaml\",\n", + " \"deploy/compose/vectordb.yaml\",\n", + " \"deploy/compose/nims.yaml\",\n", + "]:\n", + " run_command(f\"docker compose -f {f} down\")\n", + "print(\"[OK] RAG stopped\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Stop Continuous ingestion Deployment\n", + "\n", + "Stop Continuous ingestion services (Kafka, MinIO, Consumer).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "run_command(f\"docker compose -f {AIDP_COMPOSE_FILE} down\")\n", + "print(\"[OK] Continuous ingestion stopped\")\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 } From e629fd1e448c66f879aaf05bfaf81d250033779e Mon Sep 17 00:00:00 2001 From: Kurt Heiss Date: Tue, 10 Mar 2026 10:58:47 -0700 Subject: [PATCH 26/52] Kheiss/chunking topic (#417) * confirming presence of switcher text in conf.py file * Added chunking information --- docs/accuracy_perf.md | 2 +- docs/api-ingestor.md | 2 +- docs/api-rag.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/accuracy_perf.md b/docs/accuracy_perf.md index fa4f6b7a7..c24f0c9a5 100644 --- a/docs/accuracy_perf.md +++ b/docs/accuracy_perf.md @@ -14,7 +14,7 @@ Change the setting if you want different behavior. | Name | Default | Description | Advantages | Disadvantages | |----------------------|------------|---------------------|----------------------|--------------------------| | `APP_NVINGEST_CHUNKOVERLAP` | `150` | Increase overlap to ensure smooth transitions between chunks. | - Larger overlap provides smoother transitions between chunks.
| - Might increase processing overhead.
| -| `APP_NVINGEST_CHUNKSIZE` | `512` | Increase chunk size for more context. | - Larger chunks retain more context, improving coherence.
| - Larger chunks increase embedding size, slowing retrieval.
- Longer chunks might increase latency due to larger prompt size.
| +| `APP_NVINGEST_CHUNKSIZE` | `512` | Increase chunk size for more context. | - Larger chunks retain more context, improving coherence.
- Larger chunks increase compute time for embedding creation.
- Larger chunks can lead to longer retrieved context, increasing generation latency.
- Very large chunks may dilute semantic focus, reducing embedding precision.
| | `APP_NVINGEST_ENABLEPDFSPLITTER` | `true` | Set to `true` to perform chunk-based splitting of pdfs after the default page-level extraction occurs. Recommended for PDFs that are mostly text content. | - Provides more granular content segmentation.
| - Can increase the number of chunks and slow down the ingestion process.
| | `APP_NVINGEST_EXTRACTCHARTS` | `true` | Set to `true` to extract charts. | - Improves accuracy for documents that contain charts.
| - Increases ingestion time.
| | `APP_NVINGEST_EXTRACTIMAGES` | `false` | Set to `true` to enable image captioning during ingestion. For details, refer to [Image Captioning Support](image_captioning.md). | - Enhances multimodal retrieval accuracy for documents having images.
| - Increased processing time during ingestion.
- Requires additional GPU resources for VLM model deployment.
| diff --git a/docs/api-ingestor.md b/docs/api-ingestor.md index 443a521a6..548841574 100644 --- a/docs/api-ingestor.md +++ b/docs/api-ingestor.md @@ -8,7 +8,7 @@ This documentation contains the OpenAPI reference for the ingestor server. :::{tip} -To view this documentation on docs.nvidia.com, go to https://docs.nvidia.com/rag/latest/api-ingestor.html. +To view this documentation on docs.nvidia.com, browse to [https://docs.nvidia.com/rag/latest/api-ingestor](https://docs.nvidia.com/rag/latest/api-ingestor.html). ::: diff --git a/docs/api-rag.md b/docs/api-rag.md index 366d44b0f..518696304 100644 --- a/docs/api-rag.md +++ b/docs/api-rag.md @@ -8,7 +8,7 @@ This documentation contains the OpenAPI reference for the RAG server. :::{tip} -To view this documentation on docs.nvidia.com, go to https://docs.nvidia.com/rag/latest/api-rag.html. +To view this documentation on docs.nvidia.com, browse to [https://docs.nvidia.com/rag/latest/api-rag](https://docs.nvidia.com/rag/latest/api-rag.html). ::: From 206049644ee8cb761a10b72da30b5a287b4c3b50 Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Wed, 11 Mar 2026 00:47:18 +0530 Subject: [PATCH 27/52] CI - Updated Nemotron endpoints for embedding, page-elements, graphic-elements, table-structure (#410) Signed-off-by: Swapnil Masurekar Co-authored-by: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> --- deploy/compose/nvdev.env | 8 ++++---- tests/integration/notebook_test_config.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/compose/nvdev.env b/deploy/compose/nvdev.env index b92e4500b..da3725dc7 100644 --- a/deploy/compose/nvdev.env +++ b/deploy/compose/nvdev.env @@ -20,7 +20,7 @@ export APP_LLM_MODELNAME=nvidia/llama-3.3-nemotron-super-49b-v1.5 # export APP_LLM_MODELNAME=nvidia/nemotron-3-nano-30b-a3b # Note: For locally deployed nemotron-3-nano, use: nvidia/nemotron-3-nano export APP_FILTEREXPRESSIONGENERATOR_MODELNAME=nvidia/llama-3.3-nemotron-super-49b-v1.5 -export APP_EMBEDDINGS_MODELNAME=nvdev/nvidia/llama-3.2-nv-embedqa-1b-v2 +export APP_EMBEDDINGS_MODELNAME=nvidia/llama-nemotron-embed-1b-v2 # For VLM Embedding Model (Nemoretriever-1b-vlm-embed-v1) # export APP_EMBEDDINGS_MODELNAME=nvdev/nvidia/llama-nemotron-embed-vl-1b-v2 export APP_RANKING_MODELNAME=nvidia/llama-3.2-nv-rerankqa-1b-v2 @@ -33,11 +33,11 @@ export APP_RANKING_SERVERURL="" export OCR_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr export OCR_INFER_PROTOCOL=http export OCR_MODEL_NAME=scene_text_ensemble -export YOLOX_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-page-elements-v3 +export YOLOX_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3 export YOLOX_INFER_PROTOCOL=http -export YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvdev/nvidia/nemoretriever-graphic-elements-v1 +export YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1 export YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL=http -export YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvdev/nvidia/nemoretriever-table-structure-v1 +export YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1 export YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=http export SUMMARY_LLM="nvidia/llama-3.3-nemotron-super-49b-v1.5" export SUMMARY_LLM_SERVERURL="" diff --git a/tests/integration/notebook_test_config.yaml b/tests/integration/notebook_test_config.yaml index 83bc001d5..e0bcc3464 100644 --- a/tests/integration/notebook_test_config.yaml +++ b/tests/integration/notebook_test_config.yaml @@ -55,7 +55,7 @@ filter_expression_generator: # Embedding Configuration embeddings: - model_name: "nvidia/llama-3.2-nv-embedqa-1b-v2" # Model for generating text embeddings + model_name: "nvidia/llama-nemotron-embed-1b-v2" # Model for generating text embeddings dimensions: 2048 # Dimensionality of the embedding vectors server_url: "http://localhost:9080/v1" # URL endpoint for embedding service (on-prem NIM default) # api_key: "" # Optional: API key for embeddings (overrides NVIDIA_API_KEY environment variable) From 51368c3c1562dffe3e794210c57f15ee3c1cabcb Mon Sep 17 00:00:00 2001 From: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> Date: Wed, 11 Mar 2026 01:59:02 +0530 Subject: [PATCH 28/52] Add notebook showcasing langchain connector for Nvidia RAG Retrieval (#419) * notebook: Add notebook showcasing langchain retriever connector * Update langchain connector version to 1.2.0 * Fix broken link in notebook doc --- docs/notebooks.md | 2 + examples/rag_react_agent/uv.lock | 9 +- notebooks/langchain_nvidia_retriever.ipynb | 277 +++++++++++++++++++++ pyproject.toml | 2 +- uv.lock | 9 +- 5 files changed, 290 insertions(+), 9 deletions(-) create mode 100644 notebooks/langchain_nvidia_retriever.ipynb diff --git a/docs/notebooks.md b/docs/notebooks.md index 7a7f50eee..6a56686a4 100644 --- a/docs/notebooks.md +++ b/docs/notebooks.md @@ -103,6 +103,8 @@ Use the following notebooks to learn comprehensive Python client usage, metadata - [rag_library_lite_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_library_lite_usage.ipynb) – Demonstrates containerless deployment of the NVIDIA RAG Python package in lite mode. Uses Milvus Lite (embedded vector database) and NV-Ingest subprocess mode for a simplified setup without Docker containers. Leverages NVIDIA cloud APIs for embeddings, ranking, and LLM inference. **Note**: This mode does not support image/table/chart citations or document summarization. +- [langchain_nvidia_retriever.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/langchain_nvidia_retriever.ipynb) – Showcases **LangChain integration** with the NVIDIA RAG Blueprint. Run [ingestion_api_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/ingestion_api_usage.ipynb) first to ingest documents, then use `NVIDIARetriever` for retrieval (sync/async), custom parameters, error handling, and optional RAG chaining with `ChatNVIDIA`. + ## Advanced Notebooks diff --git a/examples/rag_react_agent/uv.lock b/examples/rag_react_agent/uv.lock index eee7b812b..16af3b48d 100644 --- a/examples/rag_react_agent/uv.lock +++ b/examples/rag_react_agent/uv.lock @@ -1384,16 +1384,17 @@ wheels = [ [[package]] name = "langchain-nvidia-ai-endpoints" -version = "1.1.0" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "filetype" }, { name = "langchain-core" }, + { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/d1/f72ec11097694e24d93268ab031c7ec56ab4bec1c43ef7814c659f3e2493/langchain_nvidia_ai_endpoints-1.1.0.tar.gz", hash = "sha256:048a3e6d7231365fdb9fff7bcff18ce6a516b25500681f51dcb69c39e82512a0", size = 47433, upload-time = "2026-02-25T21:48:16.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/4b/e417af1b2b7f861f37e26bf4fa4b05cda4052002e3f84a966f0735baf94f/langchain_nvidia_ai_endpoints-1.2.0.tar.gz", hash = "sha256:4bd63b812707ea348a86539001aa9a89b3cba3ee56ade7379247a955e4bfd3eb", size = 53851, upload-time = "2026-03-10T17:55:08.127Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/22/5f07957028f7fa8c3d695934af8e7309bfd5ab43f2a7a756d3c3d6ce44f3/langchain_nvidia_ai_endpoints-1.1.0-py3-none-any.whl", hash = "sha256:eb04251b2b21facf9d6f2e6e7fa593b89e4f5023ebe3af1e02813512d1cd9687", size = 51514, upload-time = "2026-02-25T21:48:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/66/e4/186f1a99e4d30bd91c8438d024dc73a71c8f7e0657c7acb6e79658aa19cf/langchain_nvidia_ai_endpoints-1.2.0-py3-none-any.whl", hash = "sha256:c8e075d5b3d31216374af0cfa9e690ab28ada3ebbde34dd6d36fe16a26d883cc", size = 58269, upload-time = "2026-03-10T17:55:06.339Z" }, ] [[package]] @@ -2102,7 +2103,7 @@ requires-dist = [ { name = "langchain-elasticsearch", marker = "extra == 'all'", specifier = ">=0.3" }, { name = "langchain-elasticsearch", marker = "extra == 'elasticsearch'", specifier = ">=0.3" }, { name = "langchain-milvus", specifier = ">=0.3.0" }, - { name = "langchain-nvidia-ai-endpoints", specifier = ">=1.1.0" }, + { name = "langchain-nvidia-ai-endpoints", specifier = ">=1.2.0" }, { name = "langchain-openai", marker = "extra == 'all'", specifier = ">=0.2" }, { name = "langchain-openai", marker = "extra == 'ingest'", specifier = ">=0.2" }, { name = "langchain-openai", marker = "extra == 'rag'", specifier = ">=0.2" }, diff --git a/notebooks/langchain_nvidia_retriever.ipynb b/notebooks/langchain_nvidia_retriever.ipynb new file mode 100644 index 000000000..e3b57bdea --- /dev/null +++ b/notebooks/langchain_nvidia_retriever.ipynb @@ -0,0 +1,277 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NVIDIARetriever Connector \u2013 LangChain Integration\n", + "\n", + "**Motivation:** This notebook showcases the **LangChain integration** with the NVIDIA RAG Blueprint. The `NVIDIARetriever` from `langchain-nvidia-ai-endpoints` connects to the NVIDIA RAG `/v1/search` endpoint and returns standard LangChain `Document` objects, enabling seamless use in chains, agents, and RAG pipelines without custom HTTP code.\n", + "\n", + "---\n", + "\n", + "## Prerequisite: Run Ingestion First\n", + "\n", + "**You must ingest documents before using this notebook.** Use the [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb) notebook:\n", + "\n", + "1. Open [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb).\n", + "2. Execute the following cells **in order** (top to bottom):\n", + " - **1. Install Dependencies** \u2013 `pip install aiohttp`\n", + " - **2. Setup Base Configuration** \u2013 ingestor URL (port 8082)\n", + " - **3. Health Check** \u2013 verify ingestor is running\n", + " - **4. Create collection** \u2013 creates `multimodal_data` collection\n", + " - **4. Upload Document** \u2013 FILEPATHS cell, then `upload_documents` cell\n", + " - **5. Get Task Status** \u2013 poll until state is `FINISHED`\n", + "3. When ingestion is complete, return here and run the cells below.\n", + "\n", + "Ensure the **RAG server** (port 8081) is running. See [Get Started](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/deploy-docker-self-hosted.md)." + ], + "id": "303aa520" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Setup" + ], + "id": "c7a2a7dd" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "!pip install langchain-nvidia-ai-endpoints langchain-core\n", + "\n", + "import os\n", + "\n", + "# RAG server URL (use collection from ingestion_api_usage.ipynb)\n", + "RAG_IPADDRESS = (\n", + " \"rag-server\" if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\" else \"localhost\"\n", + ")\n", + "RAG_BASE_URL = f\"http://{RAG_IPADDRESS}:8081\"\n", + "\n", + "# Collection from ingestion_api_usage.ipynb (default: multimodal_data)\n", + "COLLECTION_NAME = \"multimodal_data\"" + ], + "execution_count": null, + "outputs": [], + "id": "e6fe7153" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Retrieval with NVIDIARetriever\n", + "\n", + "The `NVIDIARetriever` from `langchain-nvidia-ai-endpoints` connects to the NVIDIA RAG Blueprint `/v1/search` endpoint and returns LangChain `Document` objects. Use `COLLECTION_NAME` to match the collection you created in [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb)." + ], + "id": "640eee93" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Basic Sync Retrieval" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from langchain_nvidia_ai_endpoints import NVIDIARetriever\n", + "\n", + "retriever = NVIDIARetriever(\n", + " base_url=RAG_BASE_URL,\n", + " collection_names=[COLLECTION_NAME],\n", + " k=5,\n", + ")\n", + "\n", + "query = \"What are the main topics or products discussed?\"\n", + "docs = retriever.invoke(query)\n", + "\n", + "print(f\"Query: {query}\")\n", + "print(f\"Retrieved {len(docs)} documents:\\n\")\n", + "for i, doc in enumerate(docs, 1):\n", + " content_preview = (doc.page_content or \"\")[:300] + \"...\" if len(doc.page_content or \"\") > 300 else (doc.page_content or \"\")\n", + " score = doc.metadata.get(\"score\", \"N/A\")\n", + " source = doc.metadata.get(\"document_name\", \"N/A\")\n", + " print(f\"--- Document {i} ---\")\n", + " print(f\"Score: {score} | Source: {source}\")\n", + " print(f\"Content: {content_preview}\")\n", + " print()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Custom Retrieval Parameters" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "retriever_custom = NVIDIARetriever(\n", + " base_url=RAG_BASE_URL,\n", + " collection_names=[COLLECTION_NAME],\n", + " k=3,\n", + " vdb_top_k=50,\n", + " enable_reranker=True,\n", + " enable_query_rewriting=False,\n", + ")\n", + "\n", + "docs = retriever_custom.invoke(\"Summarize key information\")\n", + "print(f\"Retrieved {len(docs)} documents\")\n", + "for i, doc in enumerate(docs, 1):\n", + " print(f\" {i}. {doc.metadata.get('document_name', 'N/A')} (score: {doc.metadata.get('score', 'N/A')})\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3 Async Retrieval" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "docs = await retriever.ainvoke(\"What features or benefits are described?\")\n", + "print(f\"Async retrieval: {len(docs)} documents\")\n", + "for i, doc in enumerate(docs[:3], 1):\n", + " print(f\" {i}. {doc.metadata.get('document_name', 'N/A')}\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.4 Error Handling" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from langchain_nvidia_ai_endpoints.retrievers import (\n", + " NVIDIARAGConnectionError,\n", + " NVIDIARAGServerError,\n", + " NVIDIARAGValidationError,\n", + ")\n", + "\n", + "try:\n", + " bad_retriever = NVIDIARetriever(\n", + " base_url=\"http://invalid-host:8081\",\n", + " collection_names=[COLLECTION_NAME],\n", + " )\n", + " bad_retriever.invoke(\"test\")\n", + "except NVIDIARAGConnectionError as e:\n", + " print(f\"Connection error (expected): {e}\")\n", + "except NVIDIARAGValidationError as e:\n", + " print(f\"Validation error: {e}\")\n", + "except NVIDIARAGServerError as e:\n", + " print(f\"Server error ({e.status_code}): {e}\")\n", + "\n", + "print(\"\\nError handling works as expected.\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## RAG Chain (Optional)\n", + "\n", + "Chain `NVIDIARetriever` with `ChatNVIDIA` for end-to-end question answering. Requires `NVIDIA_API_KEY`." + ], + "id": "3b5824c6" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "if os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", + " from langchain_core.output_parsers import StrOutputParser\n", + " from langchain_core.prompts import ChatPromptTemplate\n", + " from langchain_core.runnables import RunnablePassthrough\n", + "\n", + " from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIARetriever\n", + "\n", + " retriever = NVIDIARetriever(\n", + " base_url=RAG_BASE_URL,\n", + " collection_names=[COLLECTION_NAME],\n", + " k=4,\n", + " )\n", + "\n", + " def format_docs(docs):\n", + " return \"\\n\\n\".join(d.page_content for d in docs)\n", + "\n", + " prompt = ChatPromptTemplate.from_messages([\n", + " (\"system\", \"Answer based only on the context below.\\n\\n{context}\"),\n", + " (\"human\", \"{question}\"),\n", + " ])\n", + "\n", + " llm = ChatNVIDIA(model=\"meta/llama-3.1-8b-instruct\")\n", + " chain = (\n", + " {\"context\": retriever | format_docs, \"question\": RunnablePassthrough()}\n", + " | prompt\n", + " | llm\n", + " | StrOutputParser()\n", + " )\n", + "\n", + " answer = chain.invoke(\"What are the main topics or products?\")\n", + " print(answer)\n", + "else:\n", + " print(\"NVIDIA_API_KEY not set. Set it to run the RAG chain with ChatNVIDIA.\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## Cleanup (Optional)\n", + "\n", + "To remove the collection and documents, use the delete cells in [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb) (sections 7 and 9)." + ], + "id": "4c95fdc6" + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# Use ingestion_api_usage.ipynb sections 7 (Delete Documents) and 9 (Delete Collections)\n", + "# to remove the multimodal_data collection when finished." + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4b1faf01f..acdf4f53c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "langchain>=1.2.7", "langchain-community>=0.4", "langchain-milvus>=0.3.0", - "langchain-nvidia-ai-endpoints>=1.1.0", + "langchain-nvidia-ai-endpoints>=1.2.0", "minio>=7.2,<8.0", "pdfplumber>=0.11.9", "pydantic>=2.11,<3.0", diff --git a/uv.lock b/uv.lock index 3c72857d4..090443721 100644 --- a/uv.lock +++ b/uv.lock @@ -1309,16 +1309,17 @@ wheels = [ [[package]] name = "langchain-nvidia-ai-endpoints" -version = "1.1.0" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "filetype" }, { name = "langchain-core" }, + { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/d1/f72ec11097694e24d93268ab031c7ec56ab4bec1c43ef7814c659f3e2493/langchain_nvidia_ai_endpoints-1.1.0.tar.gz", hash = "sha256:048a3e6d7231365fdb9fff7bcff18ce6a516b25500681f51dcb69c39e82512a0", size = 47433, upload-time = "2026-02-25T21:48:16.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/4b/e417af1b2b7f861f37e26bf4fa4b05cda4052002e3f84a966f0735baf94f/langchain_nvidia_ai_endpoints-1.2.0.tar.gz", hash = "sha256:4bd63b812707ea348a86539001aa9a89b3cba3ee56ade7379247a955e4bfd3eb", size = 53851, upload-time = "2026-03-10T17:55:08.127Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/22/5f07957028f7fa8c3d695934af8e7309bfd5ab43f2a7a756d3c3d6ce44f3/langchain_nvidia_ai_endpoints-1.1.0-py3-none-any.whl", hash = "sha256:eb04251b2b21facf9d6f2e6e7fa593b89e4f5023ebe3af1e02813512d1cd9687", size = 51514, upload-time = "2026-02-25T21:48:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/66/e4/186f1a99e4d30bd91c8438d024dc73a71c8f7e0657c7acb6e79658aa19cf/langchain_nvidia_ai_endpoints-1.2.0-py3-none-any.whl", hash = "sha256:c8e075d5b3d31216374af0cfa9e690ab28ada3ebbde34dd6d36fe16a26d883cc", size = 58269, upload-time = "2026-03-10T17:55:06.339Z" }, ] [[package]] @@ -1918,7 +1919,7 @@ requires-dist = [ { name = "langchain-elasticsearch", marker = "extra == 'all'", specifier = ">=0.3" }, { name = "langchain-elasticsearch", marker = "extra == 'elasticsearch'", specifier = ">=0.3" }, { name = "langchain-milvus", specifier = ">=0.3.0" }, - { name = "langchain-nvidia-ai-endpoints", specifier = ">=1.1.0" }, + { name = "langchain-nvidia-ai-endpoints", specifier = ">=1.2.0" }, { name = "langchain-openai", marker = "extra == 'all'", specifier = ">=0.2" }, { name = "langchain-openai", marker = "extra == 'ingest'", specifier = ">=0.2" }, { name = "langchain-openai", marker = "extra == 'rag'", specifier = ">=0.2" }, From da47ad0dbc4d8733a505a1e71f313e81c7992a3e Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Wed, 11 Mar 2026 11:13:35 +0530 Subject: [PATCH 29/52] =?UTF-8?q?Revert=20"Added=20url=20for=20nvidia/llam?= =?UTF-8?q?a-nemotron-rerank-1b-v2=20model=20for=20cloud=20endp=E2=80=A6"?= =?UTF-8?q?=20and=20add=20ranker=20endpoint=20in=20nvdev=20(#421)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 1a11733169fb17e80537f7ded879472a1ada4d8c. Signed-off-by: Swapnil Masurekar --- deploy/compose/.env | 2 +- deploy/compose/nvdev.env | 4 ++-- docs/python-client.md | 4 ++-- docs/retrieval-only-deployment.md | 2 +- docs/text_only_ingest.md | 2 +- notebooks/building_rag_vdb_operator.ipynb | 2 +- notebooks/image_input.ipynb | 2 +- notebooks/rag_library_lite_usage.ipynb | 2 +- notebooks/rag_library_usage.ipynb | 4 ++-- notebooks/summarization.ipynb | 2 +- tests/unit/test_rag_server/test_rag_server_health.py | 2 +- 11 files changed, 14 insertions(+), 14 deletions(-) diff --git a/deploy/compose/.env b/deploy/compose/.env index e9c4cd68d..cc80cfaf0 100644 --- a/deploy/compose/.env +++ b/deploy/compose/.env @@ -45,7 +45,7 @@ export YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=grpc # export APP_FILTEREXPRESSIONGENERATOR_MODELNAME=nvidia/llama-3.3-nemotron-super-49b-v1.5 # export APP_FILTEREXPRESSIONGENERATOR_SERVERURL="" # export SUMMARY_LLM="nvidia/llama-3.3-nemotron-super-49b-v1.5" -# export APP_RANKING_SERVERURL="https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" +# export APP_RANKING_SERVERURL="" # export SUMMARY_LLM_SERVERURL="" # export OCR_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr # export OCR_INFER_PROTOCOL=http diff --git a/deploy/compose/nvdev.env b/deploy/compose/nvdev.env index da3725dc7..d5a919153 100644 --- a/deploy/compose/nvdev.env +++ b/deploy/compose/nvdev.env @@ -23,13 +23,13 @@ export APP_FILTEREXPRESSIONGENERATOR_MODELNAME=nvidia/llama-3.3-nemotron-super-4 export APP_EMBEDDINGS_MODELNAME=nvidia/llama-nemotron-embed-1b-v2 # For VLM Embedding Model (Nemoretriever-1b-vlm-embed-v1) # export APP_EMBEDDINGS_MODELNAME=nvdev/nvidia/llama-nemotron-embed-vl-1b-v2 -export APP_RANKING_MODELNAME=nvidia/llama-3.2-nv-rerankqa-1b-v2 +export APP_RANKING_MODELNAME=nvidia/llama-nemotron-rerank-1b-v2 export ENABLE_RERANKER=True export APP_EMBEDDINGS_SERVERURL=https://integrate.api.nvidia.com/v1 export APP_LLM_SERVERURL="" export APP_FILTEREXPRESSIONGENERATOR_SERVERURL="" export APP_RANKING_SERVERURL="" -# export APP_RANKING_SERVERURL=https://ai.api.nvidia.com/v1/nvdev/retrieval/nvidia/llama-3_2-nv-rerankqa-1b-v2/reranking/v1 +# export APP_RANKING_SERVERURL=https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking export OCR_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr export OCR_INFER_PROTOCOL=http export OCR_MODEL_NAME=scene_text_ensemble diff --git a/docs/python-client.md b/docs/python-client.md index e3d8b3119..e052b5e91 100644 --- a/docs/python-client.md +++ b/docs/python-client.md @@ -362,7 +362,7 @@ from nvidia_rag.utils.configuration import NvidiaRAGConfig # }, # "ranking": { # "model_name": "nvidia/llama-nemotron-rerank-1b-v2", -# "server_url": "https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking", +# "server_url": "", # }, # }) @@ -371,7 +371,7 @@ config_rag = NvidiaRAGConfig.from_yaml("config.yaml") # Update config for cloud deployment if using Option 2 if DEPLOYMENT_MODE == "cloud": config_rag.embeddings.server_url = "https://integrate.api.nvidia.com/v1" - config_rag.ranking.server_url = "https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" + config_rag.ranking.server_url = "" # Empty uses NVIDIA API catalog config_rag.llm.server_url = "" # Empty uses NVIDIA API catalog # Initialize NvidiaRAG with config diff --git a/docs/retrieval-only-deployment.md b/docs/retrieval-only-deployment.md index 09d70c311..7f7f94475 100644 --- a/docs/retrieval-only-deployment.md +++ b/docs/retrieval-only-deployment.md @@ -116,7 +116,7 @@ For an even lighter deployment, use [NVIDIA-hosted NIMs](deploy-docker-nvidia-ho ```bash # Configure to use NVIDIA-hosted endpoints export APP_EMBEDDINGS_SERVERURL="" -export APP_RANKING_SERVERURL="https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" +export APP_RANKING_SERVERURL="" ``` :::{note} diff --git a/docs/text_only_ingest.md b/docs/text_only_ingest.md index cf61f69ec..10cb6e9b1 100644 --- a/docs/text_only_ingest.md +++ b/docs/text_only_ingest.md @@ -69,7 +69,7 @@ In case you are [interacting with cloud hosted models](deploy-docker-nvidia-host ```bash export APP_EMBEDDINGS_SERVERURL="" export APP_LLM_SERVERURL="" - export APP_RANKING_SERVERURL="https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" + export APP_RANKING_SERVERURL="" export YOLOX_HTTP_ENDPOINT="https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3" export YOLOX_INFER_PROTOCOL="http" ``` diff --git a/notebooks/building_rag_vdb_operator.ipynb b/notebooks/building_rag_vdb_operator.ipynb index 0cab35d6e..1852f1d9c 100644 --- a/notebooks/building_rag_vdb_operator.ipynb +++ b/notebooks/building_rag_vdb_operator.ipynb @@ -2092,7 +2092,7 @@ " config_ingestor.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", " config_ingestor.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", " config_rag.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", - " config_rag.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", + " config_rag.ranking.server_url = \"\" # Empty uses NVIDIA API catalog\n", " config_rag.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", "\n", "# Create embedding model using the configuration\n", diff --git a/notebooks/image_input.ipynb b/notebooks/image_input.ipynb index 9332f4144..4f778a2fa 100644 --- a/notebooks/image_input.ipynb +++ b/notebooks/image_input.ipynb @@ -360,7 +360,7 @@ "outputs": [], "source": [ "# Start the RAG server (accessible at localhost:8081)\n", - "os.environ[\"APP_RANKING_SERVERURL\"] = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", + "os.environ[\"APP_RANKING_SERVERURL\"] = \"\"\n", "! docker compose -f ../deploy/compose/docker-compose-rag-server.yaml up -d --build" ] }, diff --git a/notebooks/rag_library_lite_usage.ipynb b/notebooks/rag_library_lite_usage.ipynb index ded451558..915eb00ca 100644 --- a/notebooks/rag_library_lite_usage.ipynb +++ b/notebooks/rag_library_lite_usage.ipynb @@ -482,7 +482,7 @@ "\n", "# Set config for cloud API endpoints\n", "config_rag.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", - "config_rag.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", + "config_rag.ranking.server_url = \"\" # Empty uses NVIDIA API catalog\n", "config_rag.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", "\n", "# Initialize NvidiaRAG with config\n", diff --git a/notebooks/rag_library_usage.ipynb b/notebooks/rag_library_usage.ipynb index 659f2f304..894e82620 100644 --- a/notebooks/rag_library_usage.ipynb +++ b/notebooks/rag_library_usage.ipynb @@ -630,7 +630,7 @@ "# },\n", "# \"ranking\": {\n", "# \"model_name\": \"nvidia/llama-nemotron-rerank-1b-v2\",\n", - "# \"server_url\": \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\",\n", + "# \"server_url\": \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-3_2-nv-rerankqa-1b-v2/reranking/v1\",\n", "# },\n", "# }\n", "# )\n", @@ -640,7 +640,7 @@ "# Update config for cloud deployment if using Option 2\n", "if DEPLOYMENT_MODE == \"cloud\":\n", " config_rag.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", - " config_rag.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", + " config_rag.ranking.server_url = \"\" # Empty uses NVIDIA API catalog\n", " config_rag.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", "\n", "# Initialize NvidiaRAG with config\n", diff --git a/notebooks/summarization.ipynb b/notebooks/summarization.ipynb index 63d39c5ac..d0c7ef285 100644 --- a/notebooks/summarization.ipynb +++ b/notebooks/summarization.ipynb @@ -545,7 +545,7 @@ "if DEPLOYMENT_MODE == \"cloud\":\n", " config.embeddings.server_url = \"https://integrate.api.nvidia.com/v1\"\n", " config.llm.server_url = \"\" # Empty uses NVIDIA API catalog\n", - " config.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking\"\n", + " config.ranking.server_url = \"https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-3_2-nv-rerankqa-1b-v2/reranking/v1\"\n", " config.summarizer.server_url = \"\" # Empty uses NVIDIA API catalog\n", "else:\n", " config.embeddings.server_url = \"nemotron-embedding-ms:8000/v1\"\n", diff --git a/tests/unit/test_rag_server/test_rag_server_health.py b/tests/unit/test_rag_server/test_rag_server_health.py index fb8783dff..966a85e74 100644 --- a/tests/unit/test_rag_server/test_rag_server_health.py +++ b/tests/unit/test_rag_server/test_rag_server_health.py @@ -415,7 +415,7 @@ async def test_check_all_services_health_nvidia_api_catalog(self, mock_config): mock_config.query_rewriter.server_url = "https://ai.api.nvidia.com/v1" mock_config.embeddings.server_url = "https://api.nvcf.nvidia.com/v2" mock_config.ranking.server_url = ( - "https://ai.api.nvidia.com/v1/retrieval/nvidia/llama-nemotron-rerank-1b-v2/reranking" # Empty URL should be treated as API catalog + "" # Empty URL should be treated as API catalog ) mock_vdb_op = MagicMock() From cfb1dd9b23cb37e59c1c80fcfe9100b0dfa95597 Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Wed, 11 Mar 2026 12:52:14 +0530 Subject: [PATCH 30/52] Nemoretriever OCR version 1.2.0 -> 1.2.1 in Helm (#422) --- deploy/helm/nvidia-blueprint-rag/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/helm/nvidia-blueprint-rag/values.yaml b/deploy/helm/nvidia-blueprint-rag/values.yaml index 3a39c1224..1afe008d1 100644 --- a/deploy/helm/nvidia-blueprint-rag/values.yaml +++ b/deploy/helm/nvidia-blueprint-rag/values.yaml @@ -1029,7 +1029,7 @@ nv-ingest: replicaCount: 1 image: repository: nvcr.io/nim/nvidia/nemoretriever-ocr-v1 - tag: "1.2.0" + tag: "1.2.1" imagePullSecrets: - name: ngc-secret env: From 7a0c8662b14cb23809c87140cfc49c89854d92d9 Mon Sep 17 00:00:00 2001 From: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> Date: Wed, 11 Mar 2026 13:28:02 +0530 Subject: [PATCH 31/52] Fix name for rag langchain connector (#423) --- docs/notebooks.md | 2 +- notebooks/langchain_nvidia_retriever.ipynb | 588 +++++++++++---------- 2 files changed, 314 insertions(+), 276 deletions(-) diff --git a/docs/notebooks.md b/docs/notebooks.md index 6a56686a4..6146e4abf 100644 --- a/docs/notebooks.md +++ b/docs/notebooks.md @@ -103,7 +103,7 @@ Use the following notebooks to learn comprehensive Python client usage, metadata - [rag_library_lite_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_library_lite_usage.ipynb) – Demonstrates containerless deployment of the NVIDIA RAG Python package in lite mode. Uses Milvus Lite (embedded vector database) and NV-Ingest subprocess mode for a simplified setup without Docker containers. Leverages NVIDIA cloud APIs for embeddings, ranking, and LLM inference. **Note**: This mode does not support image/table/chart citations or document summarization. -- [langchain_nvidia_retriever.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/langchain_nvidia_retriever.ipynb) – Showcases **LangChain integration** with the NVIDIA RAG Blueprint. Run [ingestion_api_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/ingestion_api_usage.ipynb) first to ingest documents, then use `NVIDIARetriever` for retrieval (sync/async), custom parameters, error handling, and optional RAG chaining with `ChatNVIDIA`. +- [langchain_nvidia_retriever.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/langchain_nvidia_retriever.ipynb) – Showcases **LangChain integration** with the NVIDIA RAG Blueprint. Run [ingestion_api_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/ingestion_api_usage.ipynb) first to ingest documents, then use `NVIDIARAGRetriever` for retrieval (sync/async), custom parameters, error handling, and optional RAG chaining with `ChatNVIDIA`. diff --git a/notebooks/langchain_nvidia_retriever.ipynb b/notebooks/langchain_nvidia_retriever.ipynb index e3b57bdea..02b05b6db 100644 --- a/notebooks/langchain_nvidia_retriever.ipynb +++ b/notebooks/langchain_nvidia_retriever.ipynb @@ -1,277 +1,315 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# NVIDIARetriever Connector \u2013 LangChain Integration\n", - "\n", - "**Motivation:** This notebook showcases the **LangChain integration** with the NVIDIA RAG Blueprint. The `NVIDIARetriever` from `langchain-nvidia-ai-endpoints` connects to the NVIDIA RAG `/v1/search` endpoint and returns standard LangChain `Document` objects, enabling seamless use in chains, agents, and RAG pipelines without custom HTTP code.\n", - "\n", - "---\n", - "\n", - "## Prerequisite: Run Ingestion First\n", - "\n", - "**You must ingest documents before using this notebook.** Use the [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb) notebook:\n", - "\n", - "1. Open [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb).\n", - "2. Execute the following cells **in order** (top to bottom):\n", - " - **1. Install Dependencies** \u2013 `pip install aiohttp`\n", - " - **2. Setup Base Configuration** \u2013 ingestor URL (port 8082)\n", - " - **3. Health Check** \u2013 verify ingestor is running\n", - " - **4. Create collection** \u2013 creates `multimodal_data` collection\n", - " - **4. Upload Document** \u2013 FILEPATHS cell, then `upload_documents` cell\n", - " - **5. Get Task Status** \u2013 poll until state is `FINISHED`\n", - "3. When ingestion is complete, return here and run the cells below.\n", - "\n", - "Ensure the **RAG server** (port 8081) is running. See [Get Started](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/deploy-docker-self-hosted.md)." - ], - "id": "303aa520" + "cells": [ + { + "cell_type": "markdown", + "id": "303aa520", + "metadata": {}, + "source": [ + "# NVIDIARAGRetriever Connector – LangChain Integration\n", + "\n", + "**Motivation:** This notebook showcases the **LangChain integration** with the NVIDIA RAG Blueprint. The `NVIDIARAGRetriever` from `langchain-nvidia-ai-endpoints` connects to the NVIDIA RAG `/v1/search` endpoint and returns standard LangChain `Document` objects, enabling seamless use in chains, agents, and RAG pipelines without custom HTTP code.\n", + "\n", + "---\n", + "\n", + "## Prerequisite: Run Ingestion First\n", + "\n", + "**You must ingest documents before using this notebook.** Use the [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb) notebook:\n", + "\n", + "1. Open [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb).\n", + "2. Execute the following cells **in order** (top to bottom):\n", + " - **1. Install Dependencies** – `pip install aiohttp`\n", + " - **2. Setup Base Configuration** – ingestor URL (port 8082)\n", + " - **3. Health Check** – verify ingestor is running\n", + " - **4. Create collection** – creates `multimodal_data` collection\n", + " - **4. Upload Document** – FILEPATHS cell, then `upload_documents` cell\n", + " - **5. Get Task Status** – poll until state is `FINISHED`\n", + "3. When ingestion is complete, return here and run the cells below.\n", + "\n", + "Ensure the **RAG server** (port 8081) is running. See [Get Started](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/deploy-docker-self-hosted.md)." + ] + }, + { + "cell_type": "markdown", + "id": "c7a2a7dd", + "metadata": {}, + "source": [ + "---\n", + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6fe7153", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install langchain-nvidia-ai-endpoints langchain-core\n", + "\n", + "import os\n", + "\n", + "# RAG server URL (use collection from ingestion_api_usage.ipynb)\n", + "RAG_IPADDRESS = (\n", + " \"rag-server\" if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\" else \"localhost\"\n", + ")\n", + "RAG_BASE_URL = f\"http://{RAG_IPADDRESS}:8081\"\n", + "\n", + "# Collection from ingestion_api_usage.ipynb (default: multimodal_data)\n", + "COLLECTION_NAME = \"multimodal_data\"" + ] + }, + { + "cell_type": "markdown", + "id": "640eee93", + "metadata": {}, + "source": [ + "---\n", + "## Retrieval with NVIDIARAGRetriever\n", + "\n", + "The `NVIDIARAGRetriever` from `langchain-nvidia-ai-endpoints` connects to the NVIDIA RAG Blueprint `/v1/search` endpoint and returns LangChain `Document` objects. Use `COLLECTION_NAME` to match the collection you created in [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Basic Sync Retrieval" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b09138a", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_nvidia_ai_endpoints import NVIDIARAGRetriever\n", + "\n", + "retriever = NVIDIARAGRetriever(\n", + " base_url=RAG_BASE_URL,\n", + " collection_names=[COLLECTION_NAME],\n", + " k=5,\n", + ")\n", + "\n", + "query = \"What are the main topics or products discussed?\"\n", + "docs = retriever.invoke(query)\n", + "\n", + "print(f\"Query: {query}\")\n", + "print(f\"Retrieved {len(docs)} documents:\\n\")\n", + "for i, doc in enumerate(docs, 1):\n", + " content_preview = (doc.page_content or \"\")[:300] + \"...\" if len(doc.page_content or \"\") > 300 else (doc.page_content or \"\")\n", + " score = doc.metadata.get(\"score\", \"N/A\")\n", + " source = doc.metadata.get(\"document_name\", \"N/A\")\n", + " print(f\"--- Document {i} ---\")\n", + " print(f\"Score: {score} | Source: {source}\")\n", + " print(f\"Content: {content_preview}\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2 Custom Retrieval Parameters" + ] + }, + { + "cell_type": "markdown", + "id": "35d7f118", + "metadata": {}, + "source": [ + "For details on retrieval parameters, filter expressions, and metadata:\n", + "- [Custom metadata & filter expressions](../docs/custom-metadata.md) – `filter_expr` syntax (Milvus), metadata schema\n", + "- [Multi-turn & query rewriting](../docs/multiturn.md) – `enable_query_rewriting` for decontextualizing follow-up questions\n", + "- [Retriever API usage](./retriever_api_usage.ipynb) – Search endpoint payload parameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e5aa122", + "metadata": {}, + "outputs": [], + "source": [ + "retriever_custom = NVIDIARAGRetriever(\n", + " base_url=RAG_BASE_URL,\n", + " collection_names=[COLLECTION_NAME],\n", + " # Result counts\n", + " k=3, # Number of document chunks to return (0-25, maps to reranker_top_k)\n", + " vdb_top_k=50, # Top results from vector DB before reranking (0-400)\n", + " # Feature toggles\n", + " enable_reranker=True, # Rerank results for relevance\n", + " enable_query_rewriting=False, # Rewrite query for better retrieval\n", + " enable_filter_generator=False, # Auto-generate filters from query\n", + " enable_citations=True, # Include image/table/chart citations in metadata\n", + " # Filtering\n", + " confidence_threshold=0.0, # Min confidence (0.0-1.0, requires enable_reranker=True)\n", + " filter_expr=None, # Milvus filter expression, e.g. content_metadata['file_name'] == \"doc.pdf\"'\n", + " # Advanced\n", + " vdb_endpoint=\"http://milvus:19530\", # Vector DB endpoint (override if needed)\n", + " messages=[], # Conversation history for context-aware retrieval\n", + " timeout=60.0, # HTTP request timeout in seconds\n", + ")\n", + "\n", + "docs = retriever_custom.invoke(\"Summarize key information\")\n", + "print(f\"Retrieved {len(docs)} documents\")\n", + "for i, doc in enumerate(docs, 1):\n", + " print(f\" {i}. {doc.metadata.get('document_name', 'N/A')} (score: {doc.metadata.get('score', 'N/A')})\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3 Async Retrieval" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "docs = await retriever.ainvoke(\"What features or benefits are described?\")\n", + "print(f\"Async retrieval: {len(docs)} documents\")\n", + "for i, doc in enumerate(docs[:3], 1):\n", + " print(f\" {i}. {doc.metadata.get('document_name', 'N/A')}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.4 Error Handling" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfcc9f22", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_nvidia_ai_endpoints.retrievers import (\n", + " NVIDIARAGConnectionError,\n", + " NVIDIARAGServerError,\n", + " NVIDIARAGValidationError,\n", + ")\n", + "\n", + "try:\n", + " bad_retriever = NVIDIARAGRetriever(\n", + " base_url=\"http://invalid-host:8081\",\n", + " collection_names=[COLLECTION_NAME],\n", + " )\n", + " bad_retriever.invoke(\"test\")\n", + "except NVIDIARAGConnectionError as e:\n", + " print(f\"Connection error (expected): {e}\")\n", + "except NVIDIARAGValidationError as e:\n", + " print(f\"Validation error: {e}\")\n", + "except NVIDIARAGServerError as e:\n", + " print(f\"Server error ({e.status_code}): {e}\")\n", + "\n", + "print(\"\\nError handling works as expected.\")" + ] + }, + { + "cell_type": "markdown", + "id": "3b5824c6", + "metadata": {}, + "source": [ + "---\n", + "## RAG Chain (Optional)\n", + "\n", + "Chain `NVIDIARAGRetriever` with `ChatNVIDIA` for end-to-end question answering. Requires `NVIDIA_API_KEY` to call the NVIDIA API Catalog.\n", + "\n", + "**Get an API key:** See [Get an API Key](../docs/api-key.md) for instructions." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d82a5d54", + "metadata": {}, + "outputs": [], + "source": [ + "# Set NVIDIA_API_KEY if not already set (see ../docs/api-key.md to get a key)\n", + "if not os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", + " import getpass\n", + " key = getpass.getpass(\"Enter your NVIDIA API key (nvapi-...): \")\n", + " if key.startswith(\"nvapi-\"):\n", + " os.environ[\"NVIDIA_API_KEY\"] = key\n", + " else:\n", + " print(\"NVIDIA_API_KEY not set. Set it to run the RAG chain. See [Get an API Key](../docs/api-key.md)\")\n", + "\n", + "if os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", + " from langchain_core.output_parsers import StrOutputParser\n", + " from langchain_core.prompts import ChatPromptTemplate\n", + " from langchain_core.runnables import RunnablePassthrough\n", + "\n", + " from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIARAGRetriever\n", + "\n", + " retriever = NVIDIARAGRetriever(\n", + " base_url=RAG_BASE_URL,\n", + " collection_names=[COLLECTION_NAME],\n", + " k=4,\n", + " )\n", + "\n", + " def format_docs(docs):\n", + " return \"\\n\\n\".join(d.page_content for d in docs)\n", + "\n", + " prompt = ChatPromptTemplate.from_messages([\n", + " (\"system\", \"Answer based only on the context below.\\n\\n{context}\"),\n", + " (\"human\", \"{question}\"),\n", + " ])\n", + "\n", + " # Model aligned with rag-server default (nvidia/llama-3.3-nemotron-super-49b-v1.5)\n", + " llm = ChatNVIDIA(model=\"nvidia/llama-3.3-nemotron-super-49b-v1.5\")\n", + " chain = (\n", + " {\"context\": retriever | format_docs, \"question\": RunnablePassthrough()}\n", + " | prompt\n", + " | llm\n", + " | StrOutputParser()\n", + " )\n", + "\n", + " answer = chain.invoke(\"What are the main topics or products?\")\n", + " print(answer)\n", + "else:\n", + " print(\"NVIDIA_API_KEY not set. Set it (e.g. os.environ['NVIDIA_API_KEY'] = 'nvapi-...') or run this cell again to be prompted. See [Get an API Key](../docs/api-key.md)\")" + ] + }, + { + "cell_type": "markdown", + "id": "4c95fdc6", + "metadata": {}, + "source": [ + "---\n", + "## Cleanup (Optional)\n", + "\n", + "To remove the collection and documents, use the delete cells in [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb) (sections 7 and 9)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Use ingestion_api_usage.ipynb sections 7 (Delete Documents) and 9 (Delete Collections)\n", + "# to remove the multimodal_data collection when finished." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11.12" + } }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "## Setup" - ], - "id": "c7a2a7dd" - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "!pip install langchain-nvidia-ai-endpoints langchain-core\n", - "\n", - "import os\n", - "\n", - "# RAG server URL (use collection from ingestion_api_usage.ipynb)\n", - "RAG_IPADDRESS = (\n", - " \"rag-server\" if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\" else \"localhost\"\n", - ")\n", - "RAG_BASE_URL = f\"http://{RAG_IPADDRESS}:8081\"\n", - "\n", - "# Collection from ingestion_api_usage.ipynb (default: multimodal_data)\n", - "COLLECTION_NAME = \"multimodal_data\"" - ], - "execution_count": null, - "outputs": [], - "id": "e6fe7153" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "## Retrieval with NVIDIARetriever\n", - "\n", - "The `NVIDIARetriever` from `langchain-nvidia-ai-endpoints` connects to the NVIDIA RAG Blueprint `/v1/search` endpoint and returns LangChain `Document` objects. Use `COLLECTION_NAME` to match the collection you created in [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb)." - ], - "id": "640eee93" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.1 Basic Sync Retrieval" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "from langchain_nvidia_ai_endpoints import NVIDIARetriever\n", - "\n", - "retriever = NVIDIARetriever(\n", - " base_url=RAG_BASE_URL,\n", - " collection_names=[COLLECTION_NAME],\n", - " k=5,\n", - ")\n", - "\n", - "query = \"What are the main topics or products discussed?\"\n", - "docs = retriever.invoke(query)\n", - "\n", - "print(f\"Query: {query}\")\n", - "print(f\"Retrieved {len(docs)} documents:\\n\")\n", - "for i, doc in enumerate(docs, 1):\n", - " content_preview = (doc.page_content or \"\")[:300] + \"...\" if len(doc.page_content or \"\") > 300 else (doc.page_content or \"\")\n", - " score = doc.metadata.get(\"score\", \"N/A\")\n", - " source = doc.metadata.get(\"document_name\", \"N/A\")\n", - " print(f\"--- Document {i} ---\")\n", - " print(f\"Score: {score} | Source: {source}\")\n", - " print(f\"Content: {content_preview}\")\n", - " print()" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.2 Custom Retrieval Parameters" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "retriever_custom = NVIDIARetriever(\n", - " base_url=RAG_BASE_URL,\n", - " collection_names=[COLLECTION_NAME],\n", - " k=3,\n", - " vdb_top_k=50,\n", - " enable_reranker=True,\n", - " enable_query_rewriting=False,\n", - ")\n", - "\n", - "docs = retriever_custom.invoke(\"Summarize key information\")\n", - "print(f\"Retrieved {len(docs)} documents\")\n", - "for i, doc in enumerate(docs, 1):\n", - " print(f\" {i}. {doc.metadata.get('document_name', 'N/A')} (score: {doc.metadata.get('score', 'N/A')})\")" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.3 Async Retrieval" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "docs = await retriever.ainvoke(\"What features or benefits are described?\")\n", - "print(f\"Async retrieval: {len(docs)} documents\")\n", - "for i, doc in enumerate(docs[:3], 1):\n", - " print(f\" {i}. {doc.metadata.get('document_name', 'N/A')}\")" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 2.4 Error Handling" - ] - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "from langchain_nvidia_ai_endpoints.retrievers import (\n", - " NVIDIARAGConnectionError,\n", - " NVIDIARAGServerError,\n", - " NVIDIARAGValidationError,\n", - ")\n", - "\n", - "try:\n", - " bad_retriever = NVIDIARetriever(\n", - " base_url=\"http://invalid-host:8081\",\n", - " collection_names=[COLLECTION_NAME],\n", - " )\n", - " bad_retriever.invoke(\"test\")\n", - "except NVIDIARAGConnectionError as e:\n", - " print(f\"Connection error (expected): {e}\")\n", - "except NVIDIARAGValidationError as e:\n", - " print(f\"Validation error: {e}\")\n", - "except NVIDIARAGServerError as e:\n", - " print(f\"Server error ({e.status_code}): {e}\")\n", - "\n", - "print(\"\\nError handling works as expected.\")" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "## RAG Chain (Optional)\n", - "\n", - "Chain `NVIDIARetriever` with `ChatNVIDIA` for end-to-end question answering. Requires `NVIDIA_API_KEY`." - ], - "id": "3b5824c6" - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "if os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", - " from langchain_core.output_parsers import StrOutputParser\n", - " from langchain_core.prompts import ChatPromptTemplate\n", - " from langchain_core.runnables import RunnablePassthrough\n", - "\n", - " from langchain_nvidia_ai_endpoints import ChatNVIDIA, NVIDIARetriever\n", - "\n", - " retriever = NVIDIARetriever(\n", - " base_url=RAG_BASE_URL,\n", - " collection_names=[COLLECTION_NAME],\n", - " k=4,\n", - " )\n", - "\n", - " def format_docs(docs):\n", - " return \"\\n\\n\".join(d.page_content for d in docs)\n", - "\n", - " prompt = ChatPromptTemplate.from_messages([\n", - " (\"system\", \"Answer based only on the context below.\\n\\n{context}\"),\n", - " (\"human\", \"{question}\"),\n", - " ])\n", - "\n", - " llm = ChatNVIDIA(model=\"meta/llama-3.1-8b-instruct\")\n", - " chain = (\n", - " {\"context\": retriever | format_docs, \"question\": RunnablePassthrough()}\n", - " | prompt\n", - " | llm\n", - " | StrOutputParser()\n", - " )\n", - "\n", - " answer = chain.invoke(\"What are the main topics or products?\")\n", - " print(answer)\n", - "else:\n", - " print(\"NVIDIA_API_KEY not set. Set it to run the RAG chain with ChatNVIDIA.\")" - ], - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "## Cleanup (Optional)\n", - "\n", - "To remove the collection and documents, use the delete cells in [ingestion_api_usage.ipynb](./ingestion_api_usage.ipynb) (sections 7 and 9)." - ], - "id": "4c95fdc6" - }, - { - "cell_type": "code", - "metadata": {}, - "source": [ - "# Use ingestion_api_usage.ipynb sections 7 (Delete Documents) and 9 (Delete Collections)\n", - "# to remove the multimodal_data collection when finished." - ], - "execution_count": null, - "outputs": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.11.0" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file + "nbformat": 4, + "nbformat_minor": 5 +} From 4e2ff47680225927b5279808f220d08431f0fe3a Mon Sep 17 00:00:00 2001 From: niyatisingal Date: Wed, 11 Mar 2026 14:50:13 +0530 Subject: [PATCH 32/52] Print reasoning tokens if DEBUG logging is enabled (#424) * Print reasoning tokens if DEBUG logging is enabled (#378) * Print reasoning tokens if VERBOSE logging is enabled Signed-off-by: Niyati Singal * fix(observability): use hardcoded GenAI attribute keys instead of deprecated SpanAttributes (#377) Signed-off-by: Swapnil Masurekar --------- Signed-off-by: Niyati Singal Signed-off-by: Swapnil Masurekar Co-authored-by: Swapnil Masurekar * Print reasoning tokens if DEBUG logging is enabled Signed-off-by: Niyati Singal --------- Signed-off-by: Niyati Singal Signed-off-by: Swapnil Masurekar Co-authored-by: Swapnil Masurekar --- src/nvidia_rag/utils/llm.py | 52 +++++++++++++++++++++++++++++++ tests/unit/test_utils/test_llm.py | 4 +-- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/nvidia_rag/utils/llm.py b/src/nvidia_rag/utils/llm.py index 0148f3ce2..e1f226655 100644 --- a/src/nvidia_rag/utils/llm.py +++ b/src/nvidia_rag/utils/llm.py @@ -440,6 +440,9 @@ def streaming_filter_think(chunks: Iterable[str]) -> Iterable[str]: This generator filters content between think tags in streaming LLM responses. It handles both complete tags in a single chunk and tags split across multiple tokens. + When DEBUG logging is enabled (i.e. LOGLEVEL=DEBUG), reasoning tokens are + logged from block content or reasoning_content field. + Args: chunks (Iterable[str]): Chunks from a streaming LLM response @@ -464,6 +467,8 @@ def streaming_filter_think(chunks: Iterable[str]) -> Iterable[str]: match_position = 0 buffer = "" output_buffer = "" + think_accumulator = "" + reasoning_content_accumulator = "" chunk_count = 0 for chunk in chunks: @@ -471,6 +476,10 @@ def streaming_filter_think(chunks: Iterable[str]) -> Iterable[str]: content = content or reasoning chunk_count += 1 + # Accumulate reasoning tokens when DEBUG logging is enabled (e.g. reasoning_content from nemotron-3-nano) + if reasoning and logger.isEnabledFor(logging.DEBUG): + reasoning_content_accumulator += reasoning + # Let's first check for full tags - this is the most reliable approach buffer += content @@ -487,6 +496,10 @@ def streaming_filter_think(chunks: Iterable[str]) -> Iterable[str]: while state == IN_THINK and FULL_END_TAG in buffer: end_idx = buffer.find(FULL_END_TAG) + if logger.isEnabledFor(logging.DEBUG): + think_content = buffer[:end_idx] + if think_content: + think_accumulator += think_content + "\n" # Discard everything up to and including end tag buffer = buffer[end_idx + len(FULL_END_TAG) :] content = buffer @@ -534,10 +547,14 @@ def streaming_filter_think(chunks: Iterable[str]) -> Iterable[str]: elif state == IN_THINK: if content_stripped == END_TAG_PARTS[0].strip(): + # Accumulate think content before the end tag start + think_accumulator += buffer[: -len(content)] if content else buffer state = MATCHING_END match_position = 1 buffer = content # Keep this token in buffer else: + if logger.isEnabledFor(logging.DEBUG): + think_accumulator += buffer buffer = "" # Discard content inside think block elif state == MATCHING_END: @@ -546,11 +563,15 @@ def streaming_filter_think(chunks: Iterable[str]) -> Iterable[str]: match_position += 1 if match_position >= len(END_TAG_PARTS): # Complete end tag matched + if think_accumulator and logger.isEnabledFor(logging.DEBUG): + think_accumulator += "\n" state = NORMAL match_position = 0 buffer = "" # Clear buffer else: # False match, revert to IN_THINK + if logger.isEnabledFor(logging.DEBUG): + think_accumulator += buffer state = IN_THINK buffer = "" # Discard content @@ -572,6 +593,11 @@ def streaming_filter_think(chunks: Iterable[str]) -> Iterable[str]: if output_buffer: yield output_buffer + if think_accumulator and logger.isEnabledFor(logging.DEBUG): + logger.debug("Reasoning tokens (think): %s", think_accumulator.rstrip()) + if reasoning_content_accumulator and logger.isEnabledFor(logging.DEBUG): + logger.debug("Reasoning tokens: %s", reasoning_content_accumulator) + logger.info( "Finished streaming_filter_think processing after %d chunks", chunk_count ) @@ -608,6 +634,9 @@ async def streaming_filter_think_async(chunks, enable_thinking: bool = False): This async generator filters content between think tags in streaming LLM responses. It handles both complete tags in a single chunk and tags split across multiple tokens. + When DEBUG logging is enabled (i.e. LOGLEVEL=DEBUG), reasoning tokens are + logged from block content or reasoning_content field. + When enable_thinking is True and the model uses a separate reasoning_content field (e.g. Nemotron 3), reasoning tokens are dropped and only content is forwarded. The tag filter still runs to handle models that embed reasoning in content. @@ -638,6 +667,8 @@ async def streaming_filter_think_async(chunks, enable_thinking: bool = False): match_position = 0 buffer = "" output_buffer = "" + think_accumulator = "" + reasoning_content_accumulator = "" chunk_count = 0 async for chunk in chunks: @@ -645,6 +676,10 @@ async def streaming_filter_think_async(chunks, enable_thinking: bool = False): content = content if enable_thinking else (content or reasoning) chunk_count += 1 + # Accumulate reasoning when DEBUG logging is enabled (e.g. reasoning_content from nemotron-3-nano) + if reasoning and logger.isEnabledFor(logging.DEBUG): + reasoning_content_accumulator += reasoning + # Let's first check for full tags - this is the most reliable approach buffer += content @@ -661,6 +696,10 @@ async def streaming_filter_think_async(chunks, enable_thinking: bool = False): while state == IN_THINK and FULL_END_TAG in buffer: end_idx = buffer.find(FULL_END_TAG) + if logger.isEnabledFor(logging.DEBUG): + think_content = buffer[:end_idx] + if think_content: + think_accumulator += think_content + "\n" # Discard everything up to and including end tag buffer = buffer[end_idx + len(FULL_END_TAG) :] content = buffer @@ -708,10 +747,14 @@ async def streaming_filter_think_async(chunks, enable_thinking: bool = False): elif state == IN_THINK: if content_stripped == END_TAG_PARTS[0].strip(): + # Accumulate think content before the end tag start + think_accumulator += buffer[: -len(content)] if content else buffer state = MATCHING_END match_position = 1 buffer = content # Keep this token in buffer else: + if logger.isEnabledFor(logging.DEBUG): + think_accumulator += buffer buffer = "" # Discard content inside think block elif state == MATCHING_END: @@ -720,11 +763,15 @@ async def streaming_filter_think_async(chunks, enable_thinking: bool = False): match_position += 1 if match_position >= len(END_TAG_PARTS): # Complete end tag matched + if think_accumulator and logger.isEnabledFor(logging.DEBUG): + think_accumulator += "\n" state = NORMAL match_position = 0 buffer = "" # Clear buffer else: # False match, revert to IN_THINK + if logger.isEnabledFor(logging.DEBUG): + think_accumulator += buffer state = IN_THINK buffer = "" # Discard content @@ -746,6 +793,11 @@ async def streaming_filter_think_async(chunks, enable_thinking: bool = False): if output_buffer: yield output_buffer + if think_accumulator and logger.isEnabledFor(logging.DEBUG): + logger.debug("Reasoning tokens: %s", think_accumulator.rstrip()) + if reasoning_content_accumulator and logger.isEnabledFor(logging.DEBUG): + logger.debug("Reasoning tokens: %s", reasoning_content_accumulator) + logger.info( "Finished streaming_filter_think_async processing after %d chunks", chunk_count ) diff --git a/tests/unit/test_utils/test_llm.py b/tests/unit/test_utils/test_llm.py index 377f0b68d..c59cabf43 100644 --- a/tests/unit/test_utils/test_llm.py +++ b/tests/unit/test_utils/test_llm.py @@ -425,7 +425,7 @@ class TestStreamingFilterThink: """Test cases for streaming_filter_think function.""" def create_mock_chunk(self, content): - """Helper to create mock chunk with content attribute.""" + """Helper to create mock chunk with content and additional_kwargs (so 'in' works).""" chunk = Mock() chunk.content = content chunk.additional_kwargs = {} @@ -822,7 +822,7 @@ def test_streaming_filter_complete_workflow(self): assert result == expected def create_mock_chunk(self, content): - """Helper to create mock chunk with content attribute.""" + """Helper to create mock chunk with content and additional_kwargs (so 'in' works).""" chunk = Mock() chunk.content = content chunk.additional_kwargs = {} From aa965864af0c3ef391b7d00449d55d4a5ca7c10d Mon Sep 17 00:00:00 2001 From: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:39:02 +0530 Subject: [PATCH 33/52] CI: Update helm packaging and selective publish support (#425) --- .github/workflows/publish-artifacts.yml | 114 ++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/.github/workflows/publish-artifacts.yml b/.github/workflows/publish-artifacts.yml index 2be3979e1..659d4ed04 100644 --- a/.github/workflows/publish-artifacts.yml +++ b/.github/workflows/publish-artifacts.yml @@ -7,6 +7,16 @@ on: - cron: '30 18 * * *' workflow_dispatch: inputs: + JOBS_TO_RUN: + description: 'Jobs to run (manual trigger only)' + required: true + default: 'all' + type: choice + options: + - all + - wheel-only + - containers-only + - helm-chart-only CONTAINER_TAG: description: 'Custom tag for containers (optional)' required: false @@ -15,6 +25,26 @@ on: description: 'Artifactory version (optional, defaults to auto-generated from get_version.sh)' required: false default: '' + HELM_CHART_VERSION: + description: 'Helm chart version for NGC (optional, defaults to auto-generated from get_version.sh)' + required: false + default: '' + # Container-level selection (applies when JOBS_TO_RUN is 'all' or 'containers-only') + PUBLISH_RAG_SERVER: + description: 'Publish rag-server container' + required: false + default: true + type: boolean + PUBLISH_INGESTOR_SERVER: + description: 'Publish ingestor-server container' + required: false + default: true + type: boolean + PUBLISH_RAG_FRONTEND: + description: 'Publish rag-frontend container' + required: false + default: true + type: boolean env: RELEASE_TYPE: dev @@ -26,6 +56,7 @@ jobs: publish-wheel: name: Build and Publish Python Wheel runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' || github.event.inputs.JOBS_TO_RUN == 'all' || github.event.inputs.JOBS_TO_RUN == 'wheel-only' container: image: python:3.10 steps: @@ -106,6 +137,7 @@ jobs: publish-rag-server: name: Build and Publish RAG Server Container runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' || ((github.event.inputs.JOBS_TO_RUN == 'all' || github.event.inputs.JOBS_TO_RUN == 'containers-only') && github.event.inputs.PUBLISH_RAG_SERVER != 'false') steps: - name: Checkout code uses: actions/checkout@v4 @@ -164,6 +196,7 @@ jobs: publish-ingestor-server: name: Build and Publish Ingestor Server Container runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' || ((github.event.inputs.JOBS_TO_RUN == 'all' || github.event.inputs.JOBS_TO_RUN == 'containers-only') && github.event.inputs.PUBLISH_INGESTOR_SERVER != 'false') steps: - name: Checkout code uses: actions/checkout@v4 @@ -222,6 +255,7 @@ jobs: publish-rag-frontend: name: Build and Publish RAG Frontend Container runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' || ((github.event.inputs.JOBS_TO_RUN == 'all' || github.event.inputs.JOBS_TO_RUN == 'containers-only') && github.event.inputs.PUBLISH_RAG_FRONTEND != 'false') steps: - name: Checkout code uses: actions/checkout@v4 @@ -274,3 +308,83 @@ jobs: docker images | grep "rag-frontend" | awk '{print $3}' | xargs -r docker rmi -f || echo "No rag-frontend images to delete" docker system prune -f || true + # ============================================================================ + # PUBLISH HELM CHART TO NGC + # ============================================================================ + publish-helm-chart: + name: Build and Publish Helm Chart to NGC + runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' || github.event.inputs.JOBS_TO_RUN == 'all' || github.event.inputs.JOBS_TO_RUN == 'helm-chart-only' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Helm + uses: azure/setup-helm@v4 + with: + version: 'v3.17.0' + + - name: Install NGC CLI + env: + NGC_API_KEY: ${{ secrets.CI_NVSTAGING_BLUEPRINT_KEY }} + run: | + echo "Installing NGC CLI..." + wget --content-disposition https://api.ngc.nvidia.com/v2/resources/nvidia/ngc-apps/ngc_cli/versions/4.9.10/files/ngccli_linux.zip -O ngccli_linux.zip + unzip -o ngccli_linux.zip + chmod u+x ngc-cli/ngc + echo "$(pwd)/ngc-cli" >> $GITHUB_PATH + echo "NGC CLI installed successfully" + + - name: Determine Helm chart version + id: helm_version + run: | + if [ -n "${{ github.event.inputs.HELM_CHART_VERSION }}" ]; then + echo "Using custom Helm chart version: ${{ github.event.inputs.HELM_CHART_VERSION }}" + VERSION="${{ github.event.inputs.HELM_CHART_VERSION }}" + else + echo "Using auto-generated version from get_version.sh" + chmod +x ./ci/get_version.sh + VERSION=$(./ci/get_version.sh) + echo "Generated version: $VERSION" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "HELM_CHART_VERSION=$VERSION" >> $GITHUB_ENV + + - name: Add Helm repositories + env: + NGC_API_KEY: ${{ secrets.CI_NVSTAGING_BLUEPRINT_KEY }} + run: | + cd deploy/helm + helm repo add nvidia-nim https://helm.ngc.nvidia.com/nim/nvidia/ --username='$oauthtoken' --password="$NGC_API_KEY" + helm repo add nim https://helm.ngc.nvidia.com/nim/ --username='$oauthtoken' --password="$NGC_API_KEY" + helm repo add nemo-microservices https://helm.ngc.nvidia.com/nvidia/nemo-microservices --username='$oauthtoken' --password="$NGC_API_KEY" + helm repo add baidu-nim https://helm.ngc.nvidia.com/nim/baidu --username='$oauthtoken' --password="$NGC_API_KEY" + helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo add elastic https://helm.elastic.co + helm repo add otel https://open-telemetry.github.io/opentelemetry-helm-charts + helm repo add zipkin https://zipkin.io/zipkin-helm + helm repo add prometheus https://prometheus-community.github.io/helm-charts + helm repo update + + - name: Package Helm chart + env: + NGC_API_KEY: ${{ secrets.CI_NVSTAGING_BLUEPRINT_KEY }} + run: | + cd deploy/helm + helm dependency update nvidia-blueprint-rag + helm package nvidia-blueprint-rag/ --version "${{ env.HELM_CHART_VERSION }}" + CHART_TGZ=$(ls nvidia-blueprint-rag-*.tgz) + echo "Created: $CHART_TGZ" + + - name: Push Helm chart to NGC + env: + NGC_API_KEY: ${{ secrets.CI_NVSTAGING_BLUEPRINT_KEY }} + run: | + cd deploy/helm + CHART_TGZ="nvidia-blueprint-rag-${{ env.HELM_CHART_VERSION }}.tgz" + TARGET="nvstaging/blueprint/nvidia-blueprint-rag:${{ env.HELM_CHART_VERSION }}" + # Remove existing version to overwrite (ignore error if version does not exist) + ngc registry chart remove "$TARGET" --org nvstaging -y 2>/dev/null || true + ngc registry chart push "$TARGET" --source "$CHART_TGZ" --org nvstaging + echo "Helm chart published to NGC: $TARGET" + From b68c69fd21e38bcc2190a9838382c5c60be1d096 Mon Sep 17 00:00:00 2001 From: kumar-punit Date: Wed, 11 Mar 2026 21:43:03 +0530 Subject: [PATCH 34/52] Add rag-blueprint agent skill with CLAUDE.md and project config (#407) * Add rag-blueprint agent skill with CLAUDE.md and project config * Update query-and-conversation.md * Update notebooks.md Updated with NVIDIA style guide recommendations --------- Co-authored-by: Kurt Heiss --- .gitignore | 5 + AGENTS.md | 86 +++++++++++ CLAUDE.md | 84 ++++++++++ README.md | 23 +++ .../.agents/skills/rag-blueprint/SKILL.md | 136 ++++++++++++++++ .../references/configure/api-reference.md | 29 ++++ .../references/configure/data-catalog.md | 36 +++++ .../references/configure/evaluation.md | 26 ++++ .../references/configure/guardrails.md | 30 ++++ .../references/configure/ingestion.md | 53 +++++++ .../rag-blueprint/references/configure/mcp.md | 26 ++++ .../references/configure/migration.md | 35 +++++ .../configure/models-and-infrastructure.md | 68 ++++++++ .../references/configure/multimodal-query.md | 35 +++++ .../references/configure/notebooks.md | 50 ++++++ .../references/configure/observability.md | 29 ++++ .../configure/query-and-conversation.md | 82 ++++++++++ .../configure/reasoning-and-generation.md | 57 +++++++ .../configure/search-and-retrieval.md | 67 ++++++++ .../references/configure/summarization.md | 40 +++++ .../references/configure/user-interface.md | 27 ++++ .../rag-blueprint/references/configure/vlm.md | 56 +++++++ .../skills/rag-blueprint/references/deploy.md | 119 ++++++++++++++ .../references/deploy/docker-nvidia-hosted.md | 38 +++++ .../deploy/docker-retrieval-only.md | 37 +++++ .../references/deploy/docker-self-hosted.md | 49 ++++++ .../rag-blueprint/references/deploy/docker.md | 88 +++++++++++ .../references/deploy/helm-mig.md | 38 +++++ .../references/deploy/helm-standard.md | 51 ++++++ .../rag-blueprint/references/deploy/helm.md | 103 ++++++++++++ .../references/deploy/library-full.md | 43 ++++++ .../references/deploy/library-lite.md | 37 +++++ .../references/deploy/library.md | 54 +++++++ .../rag-blueprint/references/shutdown.md | 128 +++++++++++++++ .../rag-blueprint/references/troubleshoot.md | 146 ++++++++++++++++++ skill-source/README.md | 111 +++++++++++++ 36 files changed, 2122 insertions(+) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 skill-source/.agents/skills/rag-blueprint/SKILL.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/api-reference.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/data-catalog.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/evaluation.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/guardrails.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/ingestion.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/mcp.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/migration.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/models-and-infrastructure.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/multimodal-query.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/notebooks.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/observability.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/query-and-conversation.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/reasoning-and-generation.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/search-and-retrieval.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/summarization.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/user-interface.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/configure/vlm.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/docker-nvidia-hosted.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/docker-retrieval-only.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/docker-self-hosted.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/docker.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/helm-mig.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/helm-standard.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/helm.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/library-full.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/library-lite.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/deploy/library.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/shutdown.md create mode 100644 skill-source/.agents/skills/rag-blueprint/references/troubleshoot.md create mode 100644 skill-source/README.md diff --git a/.gitignore b/.gitignore index 9dded62bf..3611412e3 100644 --- a/.gitignore +++ b/.gitignore @@ -80,4 +80,9 @@ coverage/ cover/ *.log tests/data/ +# Agent skills (installed via npx skills add) +/.agents/ +/.claude/ +skills-lock.json + # Workbench Project Layout \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..183677906 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,86 @@ +# NVIDIA RAG Blueprint + +Reference implementation for a Retrieval Augmented Generation pipeline. Python 3.11+ backend (FastAPI + LangChain), React/TypeScript frontend, deployable via Docker Compose or Helm. + +## Project structure + +``` +src/nvidia_rag/ +├── rag_server/ # RAG query/response server (FastAPI) +├── ingestor_server/ # Document ingestion server (FastAPI) +└── utils/ # Shared utilities +frontend/ # React + TypeScript UI (pnpm) +deploy/ +├── compose/ # Docker Compose files and env configs +└── helm/ # Helm charts (standard + MIG-slicing) +docs/ # User-facing documentation (Sphinx, RST/MD) +tests/ +├── unit/ # No network calls allowed +└── integration/ # Network calls permitted +notebooks/ # Jupyter notebooks for evaluation and examples +``` + +## Development commands + +### Backend (Python) + +```bash +uv sync # Install all deps +uv run pytest tests/unit/ # Unit tests +uv run pytest tests/integration/ # Integration tests +ruff check --fix src/ # Lint + autofix +ruff format src/ # Format +pre-commit run --all-files # Run all pre-commit hooks +``` + +### Frontend (TypeScript) + +```bash +cd frontend +pnpm install +pnpm run dev # Dev server +pnpm run lint # ESLint +pnpm exec tsc --noEmit # Type check +pnpm run test:run # Tests +``` + +## Code conventions + +- **Python**: Ruff for linting and formatting (line-length 88, double quotes, space indent). Config in `pyproject.toml`. +- **Type hints**: Required on all function signatures. +- **Imports**: Sorted by isort via Ruff. No in-function imports. +- **Tests**: Mirror source tree (`src/nvidia_rag/rag_server/server.py` → `tests/unit/rag_server/test_server.py`). +- **Frontend**: ESLint + TypeScript strict mode. Function components with hooks. +- **Env files**: `deploy/compose/nvdev.env` (NVIDIA-hosted NIMs) and `deploy/compose/.env` (self-hosted). These are the source of truth for Docker deployments — shell-only exports are lost on restart. + +## Deployment modes + +1. **Docker Compose** — `deploy/compose/` with env-file configs. Multiple profiles: standard, retrieval-only, NVIDIA-hosted. +2. **Helm** — `deploy/helm/nvidia-blueprint-rag/` chart with `values.yaml`. Supports MIG GPU slicing via `deploy/helm/mig-slicing/`. +3. **Library** — Import `nvidia_rag` as a Python package for custom pipelines. + +## Key files + +- `pyproject.toml` — All Python deps, ruff config, project metadata +- `deploy/compose/nvdev.env` — Default env file for NVIDIA API Catalog deployments +- `src/nvidia_rag/rag_server/prompt.yaml` — System prompt templates +- `docs/support-matrix.md` — GPU requirements per deployment mode +- `docs/service-port-gpu-reference.md` — Port mappings and GPU assignments + +## PR and commit guidelines + +- Target the `develop` branch, never `main`. +- All commits must be signed off (DCO). +- Run `pre-commit run --all-files` before submitting. +- See `CONTRIBUTING.md` for full workflow. + +## Operations — `rag-blueprint` skill + +For any operational task — deploying, configuring, troubleshooting, or shutting down the RAG Blueprint — read and follow the skill at `.agents/skills/rag-blueprint/SKILL.md`. + +The skill handles: + +- **Deploy** — Docker Compose (standard, retrieval-only, NVIDIA-hosted), Helm, MIG-slicing, library mode +- **Configure** — VLM, guardrails, query rewriting, ingestion, search & retrieval, models, observability, summarization, multimodal, MCP, evaluation, notebooks, UI, and more +- **Troubleshoot** — Debug unhealthy services, container errors, GPU issues, connectivity failures +- **Shutdown** — Stop, tear down, and clean up services diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..e16f0c9d6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,84 @@ +# NVIDIA RAG Blueprint + +Reference implementation for a Retrieval Augmented Generation pipeline. Python 3.11+ backend (FastAPI + LangChain), React/TypeScript frontend, deployable via Docker Compose or Helm. + +## Project structure + +``` +src/nvidia_rag/ +├── rag_server/ # RAG query/response server (FastAPI) +├── ingestor_server/ # Document ingestion server (FastAPI) +└── utils/ # Shared utilities +frontend/ # React + TypeScript UI (pnpm) +deploy/ +├── compose/ # Docker Compose files and env configs +└── helm/ # Helm charts (standard + MIG-slicing) +docs/ # User-facing documentation (Sphinx, RST/MD) +tests/ +├── unit/ # No network calls allowed +└── integration/ # Network calls permitted +notebooks/ # Jupyter notebooks for evaluation and examples +``` + +## Development commands + +### Backend (Python) + +```bash +uv sync # Install all deps +uv run pytest tests/unit/ # Unit tests +uv run pytest tests/integration/ # Integration tests +ruff check --fix src/ # Lint + autofix +ruff format src/ # Format +pre-commit run --all-files # Run all pre-commit hooks +``` + +### Frontend (TypeScript) + +```bash +cd frontend +pnpm install +pnpm run dev # Dev server +pnpm run lint # ESLint +pnpm exec tsc --noEmit # Type check +pnpm run test:run # Tests +``` + +## Code conventions + +- **Python**: Ruff for linting and formatting (line-length 88, double quotes, space indent). Config in `pyproject.toml`. +- **Type hints**: Required on all function signatures. +- **Imports**: Sorted by isort via Ruff. No in-function imports. +- **Tests**: Mirror source tree (`src/nvidia_rag/rag_server/server.py` → `tests/unit/rag_server/test_server.py`). +- **Frontend**: ESLint + TypeScript strict mode. Function components with hooks. +- **Env files**: `deploy/compose/nvdev.env` (NVIDIA-hosted NIMs) and `deploy/compose/.env` (self-hosted). These are the source of truth for Docker deployments — shell-only exports are lost on restart. + +## Deployment modes + +1. **Docker Compose** — `deploy/compose/` with env-file configs. Multiple profiles: standard, retrieval-only, NVIDIA-hosted. +2. **Helm** — `deploy/helm/nvidia-blueprint-rag/` chart with `values.yaml`. Supports MIG GPU slicing via `deploy/helm/mig-slicing/`. +3. **Library** — Import `nvidia_rag` as a Python package for custom pipelines. + +## Key files + +- `pyproject.toml` — All Python deps, ruff config, project metadata +- `deploy/compose/nvdev.env` — Default env file for NVIDIA API Catalog deployments +- `src/nvidia_rag/rag_server/prompt.yaml` — System prompt templates +- `docs/support-matrix.md` — GPU requirements per deployment mode +- `docs/service-port-gpu-reference.md` — Port mappings and GPU assignments + +## PR and commit guidelines + +- Target the `develop` branch, never `main`. +- All commits must be signed off (DCO). +- Run `pre-commit run --all-files` before submitting. +- See `CONTRIBUTING.md` for full workflow. + +## Operations — `/rag-blueprint` skill + +For any operational task, use the `rag-blueprint` skill (`.agents/skills/rag-blueprint/`). + +- **Deploy** — Docker Compose (standard, retrieval-only, NVIDIA-hosted), Helm, MIG-slicing, library mode +- **Configure** — VLM, guardrails, query rewriting, ingestion, search & retrieval, models, observability, summarization, multimodal, MCP, evaluation, notebooks, UI, and more +- **Troubleshoot** — Debug unhealthy services, container errors, GPU issues, connectivity failures +- **Shutdown** — Stop, tear down, and clean up services diff --git a/README.md b/README.md index 72c5ca5a3..c400dd410 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,29 @@ The following is a step-by-step explanation of the workflow from the end-user pe +## AI Agent Skill + +An agent skill is included that enables AI coding assistants (Claude Code, Cursor, etc.) to deploy, configure, troubleshoot, and manage the RAG Blueprint autonomously. + +### Install + +```bash +npx skills add . +``` + +This installs the `rag-blueprint` skill from `skill-source/`. After installation, the agent handles requests like: + +- *"Deploy RAG on Docker with NVIDIA-hosted models"* +- *"Enable VLM image captioning and restart the ingestor"* +- *"Ingestion failed for 3 files, can you check why?"* +- *"Switch from Docker to library mode"* +- *"Shut down all RAG services"* + +> **Note:** If the agent doesn't pick up the skill automatically (e.g., for short or ambiguous queries), invoke it explicitly with `/rag-blueprint `. + +For skill architecture details, see [`skill-source/README.md`](skill-source/README.md). + + ## Get Started With NVIDIA RAG Blueprint The recommended way to get started is to deploy the NVIDIA RAG Blueprint diff --git a/skill-source/.agents/skills/rag-blueprint/SKILL.md b/skill-source/.agents/skills/rag-blueprint/SKILL.md new file mode 100644 index 000000000..8f48d2858 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/SKILL.md @@ -0,0 +1,136 @@ +--- +name: rag-blueprint +description: "NVIDIA RAG Blueprint — deploy, configure, troubleshoot, and manage. Handles any RAG action: deploy, install, start, enable, disable, toggle, change, configure, troubleshoot, debug, fix, shutdown, stop, or tear down any RAG feature or service (VLM, guardrails, query rewriting, models, search, ingestion, observability, summarization, and more)." +argument-hint: deploy RAG | enable feature | disable feature | configure | troubleshoot | shutdown +allowed-tools: Bash(echo *), Bash(nvidia-smi *), Bash(curl *), Bash(docker ps *), Bash(docker exec *), Bash(docker info *), Bash(docker --version *), Bash(docker compose version *), Bash(docker logs *), Bash(docker system *), Bash(kubectl get *), Bash(kubectl describe *), Bash(kubectl version *), Bash(kubectl logs *), Bash(helm version *), Bash(helm list *), Bash(git rev-parse *), Bash(git describe *), Bash(git status *), Bash(python3 --version *), Bash(pip3 show *), Bash(df *), Bash(du *), Bash(cat /proc/*), Bash(cat /etc/os-release *), Bash(ss *), Bash(netstat *), Bash(ls *), Bash(grep *), Bash(lsof *), Bash(ps aux *), Read, Grep, Glob +license: Apache-2.0 +metadata: + author: nvidia-rag-team + version: "1.0" +--- + +# NVIDIA RAG Blueprint + +## Autonomy Principles + +- Auto-detect everything: GPU, VRAM, drivers, Docker, CUDA, disk, OS, ports, existing services, NGC key, repo state. +- If it can be checked with a command, check it — don't ask the user. +- Ask only when user action is required: providing an API key, confirming data deletion, or choosing between equally valid options. +- Once analysis is done, route to the correct workflow and execute. + +## Intent Detection + +Determine what the user wants and route immediately: + +| User Intent | Action | +|-------------|--------| +| Deploy, install, set up, start RAG | Read and follow `references/deploy.md` | +| Configure, enable, change, toggle a feature | Use the **Configure** section below | +| Troubleshoot, debug, fix, error, unhealthy | Read and follow `references/troubleshoot.md` | +| Stop, shutdown, tear down, clean up | Read and follow `references/shutdown.md` | + +If the intent is ambiguous, infer from context (e.g., "RAG isn't working" → troubleshoot; "get RAG running" → deploy). Only ask if genuinely unclear. + +--- + +## Configure + +Requires a running RAG deployment. If services are not running, deploy first via `references/deploy.md`. + +Match the user's request to a reference file, then read and follow it: + +| Feature Keywords | Reference | +|-----------------|-----------| +| VLM, VLM embeddings, image captioning | `references/configure/vlm.md` | +| NeMo Guardrails | `references/configure/guardrails.md` | +| Query rewriting, decomposition, multi-turn | `references/configure/query-and-conversation.md` | +| Ingestion (text-only, audio, Nemotron Parse, OCR, batch CLI, NV-Ingest, volume mount, performance) | `references/configure/ingestion.md` | +| Search, retrieval, hybrid search, multi-collection, metadata, filters, reranker, topK, accuracy/performance | `references/configure/search-and-retrieval.md` | +| LLM/embedding/ranking model changes, vector DB, Milvus/Elasticsearch auth, service keys, model profiles, ports/GPU | `references/configure/models-and-infrastructure.md` | +| Reasoning, self-reflection, prompts, generation params (tokens, temperature, citations), per-request LLM params | `references/configure/reasoning-and-generation.md` | +| Summarization | `references/configure/summarization.md` | +| Observability (tracing, Zipkin, Grafana, Prometheus) | `references/configure/observability.md` | +| Multimodal query (image + text) | `references/configure/multimodal-query.md` | +| Data catalog (collection/document metadata) | `references/configure/data-catalog.md` | +| User interface (UI settings) | `references/configure/user-interface.md` | +| API reference (endpoints, schemas) | `references/configure/api-reference.md` | +| Evaluation (RAGAS metrics) | `references/configure/evaluation.md` | +| MCP server & client, agent toolkit | `references/configure/mcp.md` | +| Migration (version upgrades) | `references/configure/migration.md` | +| Notebooks (setup and catalog) | `references/configure/notebooks.md` | + +### Configure Flow + +1. Match the user's request to a reference file from the table above. + +2. Detect what's running: + ```bash + echo "=== NIM ===" && docker ps --format '{{.Names}}' 2>/dev/null | grep -iE '(nim-llm|nemoretriever-embedding|nemoretriever-ranking|nemo-vlm|nemotron-vlm)' || echo "NO_LOCAL_NIMS"; echo "=== RAG ===" && docker ps --format '{{.Names}}' 2>/dev/null | grep -iE '(rag-server|ingestor-server|milvus)' || echo "NO_DOCKER_RAG"; echo "=== K8S ===" && kubectl get pods -n rag 2>/dev/null | head -5 || echo "NO_K8S"; echo "=== LIBRARY ===" && ps aux 2>/dev/null | grep -E '(nvidia_rag|uvicorn.*rag)' | grep -v grep || echo "NO_LIBRARY" + ``` + +3. Use this table to determine platform, deployment type, and where config lives: + + | Local NIMs running? | RAG services running? | Deployment Type | Config Location | + |---------------------|-----------------------|-----------------|-----------------| + | Yes (Docker) | Any | Self-hosted | `deploy/compose/.env` | + | No | Yes (Docker) | NVIDIA-hosted | `deploy/compose/nvdev.env` | + | Yes (K8s pods) | Any | Self-hosted | `values.yaml` (NIM sections) | + | No | Yes (K8s pods) | NVIDIA-hosted | `values.yaml` (envVars) | + | — | Library processes | Library mode | `notebooks/config.yaml` | + | No | No | Not running | Deploy first via `references/deploy.md` | + + Tell the user what you detected and ask to confirm. Example: "I see local NIM containers running (nim-llm-ms, nemoretriever-embedding-ms) — this is a self-hosted deployment. Config file is `deploy/compose/.env`. Correct?" + +4. Check current feature state before changing anything — read the config location from step 3, then cross-check the live service: + - Docker: `docker exec rag-server env 2>/dev/null | grep -E ""` + - Helm: `kubectl get pod -n rag -l app=rag-server -o jsonpath='{.items[0].spec.containers[0].env}' 2>/dev/null` + + If the config file and live service disagree, tell the user the service has stale config and will need a restart. + +5. If the feature needs extra GPUs, check availability against hardware restrictions (see below): + ```bash + nvidia-smi --query-gpu=index,name,memory.total,memory.used --format=csv,noheader 2>/dev/null || echo "NO_GPU" + ``` + +6. Read the reference file and apply changes: + - **Docker**: edit the env file (uncomment to enable, re-comment to disable — the env file is the source of truth). Then restart the affected service: + ``` + source && docker compose -f deploy/compose/ up -d + ``` + | Service | Compose File | + |---------|-------------| + | rag-server | `docker-compose-rag-server.yaml` | + | ingestor-server | `docker-compose-ingestor-server.yaml` | + | milvus, etcd, minio | `vectordb.yaml` | + | NIM containers (LLM, embedding, ranking, VLM, OCR) | `nims.yaml` | + | guardrails | `docker-compose-nemo-guardrails.yaml` | + | observability (Grafana, Prometheus, Zipkin) | `observability.yaml` | + - **Helm**: edit `values.yaml`, then upgrade: `helm upgrade rag -n rag -f values.yaml` + - **Library**: edit `notebooks/config.yaml`, then restart the Python process + +7. Verify: + - Docker: `docker ps --format "table {{.Names}}\t{{.Status}}" | head -20; curl -s http://localhost:8081/v1/health?check_dependencies=true 2>/dev/null | head -1` + - Helm: `kubectl get pods -n rag; kubectl rollout status deployment/rag-server -n rag --timeout=120s` + - Library: `curl -s http://localhost:8081/v1/health 2>/dev/null | head -1` + +8. If restart fails, read `references/troubleshoot.md`. If multiple features requested, repeat from step 1 for each. + +### When User Says "Configure" Without Specifics + +Run steps 2–3 above, then read the identified config file to list what's currently enabled: +```bash +grep -E "^(export )?(ENABLE_|APP_)" 2>/dev/null | sort +``` +Summarize what's running and enabled, then ask which feature to change. + +--- + +## Hardware Restrictions + +Read `docs/support-matrix.md` for current GPU requirements per deployment mode. +Read `docs/service-port-gpu-reference.md` for port mappings and GPU assignments. + +| GPU | Feature Restrictions | +|-----|---------------------| +| B200 | No VLM, No Guardrails, No Nemotron Parse. May need multi-GPU LLM (`LLM_MS_GPU_ID`). | +| RTX PRO 6000 | No Nemotron Parse. No Audio on Helm. | diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/api-reference.md b/skill-source/.agents/skills/rag-blueprint/references/configure/api-reference.md new file mode 100644 index 000000000..056d814ba --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/api-reference.md @@ -0,0 +1,29 @@ +# API Reference + +## When to Use +- User needs to call RAG or Ingestor APIs directly +- User asks about endpoints, request/response formats, or task status tracking + +## Process +1. Read `docs/api-rag.md` for RAG server endpoints (port 8081) +2. Read `docs/api-ingestor.md` for Ingestor server endpoints (port 8082) +3. Consult OpenAPI schemas for exact request/response shapes + +## Agent-Specific Notes +- RAG Server runs on port 8081: `/v1/generate`, `/v1/search`, `/v1/health`, `/v1/configuration`, `/v1/metrics`, `/v1/summary` +- Ingestor Server runs on port 8082: `/v1/documents`, `/v1/collection`, `/v1/collections`, `/v1/status` +- `POST /v1/documents` returns a `task_id` — poll `GET /v1/status?task_id=` for progress +- Task states: `PENDING` → `FINISHED` or `FAILED` (also `UNKNOWN` if not found) +- NV-Ingest extraction states: `not_started` → `submitted` → `processing` → `completed` or `failed` +- Max file size: 400 MB per document +- Full health check: `GET /v1/health?check_dependencies=true` + +## Notebooks +- `notebooks/ingestion_api_usage.ipynb` — ingestion API usage examples +- `notebooks/retriever_api_usage.ipynb` — RAG retriever API: search and query examples + +## Source Documentation +- `docs/api-rag.md` -- RAG server API details +- `docs/api-ingestor.md` -- Ingestor server API details +- `docs/api_reference/openapi_schema_rag_server.json` -- RAG server OpenAPI schema +- `docs/api_reference/openapi_schema_ingestor_server.json` -- Ingestor server OpenAPI schema diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/data-catalog.md b/skill-source/.agents/skills/rag-blueprint/references/configure/data-catalog.md new file mode 100644 index 000000000..7b2d6c106 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/data-catalog.md @@ -0,0 +1,36 @@ +# Data Catalog + +## When to Use +- User wants to manage collection or document metadata for governance +- User asks about tagging, ownership, or lifecycle status of collections +- User wants to list or update collection metadata + +## Restrictions +- None — available automatically after deployment, no additional configuration needed +- Works with both Milvus and Elasticsearch (full feature parity) + +## Process +1. Read `docs/data-catalog.md` for full API reference, field definitions, and examples +2. All endpoints are on the ingestor server (port `8082`) +3. Use PATCH endpoints for updates (merge updates — only provided fields change) + +## Decision Table + +| Goal | Source Doc | Key Action | +|------|-----------|------------| +| Add governance metadata | `docs/data-catalog.md` | POST `/v1/collection` with description, tags, owner | +| Update lifecycle status | `docs/data-catalog.md` | PATCH with `status: "Archived"` | +| Track content types | `docs/data-catalog.md` | Read auto-populated `has_tables`, `has_images` metrics | +| Filter during retrieval | See custom metadata docs | Use `metadata_schema` + `filter_expr` (not data catalog) | + +## Agent-Specific Notes +- Auto-populated metrics (`number_of_files`, `last_indexed`, `has_tables`, etc.) are system-set — not user-editable +- `date_created` and `last_updated` timestamps are automatic +- PATCH is a merge update — omitted fields keep current values +- Different from custom metadata: catalog = governance/discovery, custom metadata = retrieval filtering + +## Notebooks +- `notebooks/ingestion_api_usage.ipynb` — ingestion and collection management examples + +## Source Documentation +- `docs/data-catalog.md` — full API reference, catalog fields, auto-populated metrics, Python client examples diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/evaluation.md b/skill-source/.agents/skills/rag-blueprint/references/configure/evaluation.md new file mode 100644 index 000000000..90f9c6206 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/evaluation.md @@ -0,0 +1,26 @@ +# Evaluation + +## When to Use +- User wants to measure RAG pipeline quality +- User asks about accuracy, relevancy, groundedness, or recall metrics + +## Process +1. Read `docs/evaluate.md` for full evaluation methodology and setup +2. Choose the appropriate notebook based on metrics needed +3. Run evaluation against the deployed RAG pipeline + +## Agent-Specific Notes +- Uses RAGAS framework for all metrics +- Answer Accuracy, Context Relevancy, and Groundedness are covered in one notebook +- Recall is measured separately at top-k cutoffs (1, 3, 5, 10) + +## Notebooks +| Notebook | Metrics | +|----------|---------| +| `notebooks/evaluation_01_ragas.ipynb` | Answer Accuracy, Context Relevancy, Groundedness | +| `notebooks/evaluation_02_recall.ipynb` | Recall at top-k cutoffs | + +## Source Documentation +- `docs/evaluate.md` -- full evaluation guide and metric definitions +- [RAGAS documentation](https://docs.ragas.io/en/stable/) +- [NVIDIA RAGAS metrics](https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/nvidia_metrics/) diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/guardrails.md b/skill-source/.agents/skills/rag-blueprint/references/configure/guardrails.md new file mode 100644 index 000000000..309a18611 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/guardrails.md @@ -0,0 +1,30 @@ +# NeMo Guardrails + +## When to Use +- User wants content safety, topic control, or jailbreak prevention +- User asks to enable/disable guardrails + +## Restrictions +- Not available on B200 GPUs +- Requires 2 extra GPUs with 48GB+ each (H100, A100 SXM 80GB, or RTX PRO 6000) +- Not supported in library mode or Helm deployments +- Jailbreak detection model not yet available out-of-the-box + +## Process + +1. Detect the deployment mode (guardrails are Docker-only — not supported on Helm or library mode). Edit the active env file for Docker +2. Read `docs/nemo-guardrails.md` for full setup and configuration +3. Choose deployment mode: self-hosted (local NIMs) or cloud-hosted (NVIDIA API) +4. For self-hosted: assign GPU IDs — read `docs/service-port-gpu-reference.md` for default GPU assignments and adjust for your system +5. Verify all three services healthy: `nemo-guardrails-microservice`, content-safety NIM, topic-control NIM +6. Enable in UI: Settings > Output Preferences > Guardrails toggle + +## Agent-Specific Notes +- Cloud mode (`nemoguard_cloud` config) skips local NIM containers — only the microservice is needed +- Per-request toggle via `enable_guardrails` in `/generate` body requires server-level `ENABLE_GUARDRAILS=true` first +- Override guardrails URL with `NEMO_GUARDRAILS_URL` if running on a different host +- Content-safety and topic-control models are trained on single-turn data — multi-turn conversations may get inconsistent safety classifications +- Current guardrails only produce simple refusal responses ("I'm sorry. I can't respond to that.") + +## Source Documentation +- `docs/nemo-guardrails.md` -- full setup, configuration, and customization of guardrail rules diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/ingestion.md b/skill-source/.agents/skills/rag-blueprint/references/configure/ingestion.md new file mode 100644 index 000000000..ec5e6251f --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/ingestion.md @@ -0,0 +1,53 @@ +# Ingestion: Text-Only, Audio, Nemotron Parse, OCR & Batch + +## When to Use +User wants to configure ingestion mode (text-only, audio, Nemotron Parse), switch OCR engines, save extraction results to disk, use standalone NV-Ingest, tune ingestion performance, or run batch ingestion. + +## Restrictions +- Nemotron Parse: not available on B200 or RTX PRO 6000 GPUs (requires H100 or A100 SXM 80GB) +- Audio on Helm: not supported on RTX PRO 6000 +- Nemotron Parse GPU conflict: read `docs/service-port-gpu-reference.md` for default GPU assignments. Nemotron Parse defaults to the same GPU as LLM — reassign on limited-GPU systems + +## Process + +1. Detect the deployment mode (Docker self-hosted / NVIDIA-hosted / Helm / Library). Docker: edit the active env file. Helm: edit `values.yaml`. Library: edit `notebooks/config.yaml` +2. Read the relevant source doc for detailed configuration +3. Apply the required env vars to the active config, restart ingestor (and NIM services if enabling new profiles) +4. Verify: upload a test document and check ingestion status + +## Decision Table + +| Goal | Source Doc | Key Action | +|------|-----------|------------| +| Text-only ingestion | `docs/text_only_ingest.md` | Set extract vars to False, set `COMPONENTS_TO_READY_CHECK=""` | +| Audio ingestion | `docs/audio_ingestion.md` | Start audio NIM (`--profile audio`), set `AUDIO_MS_GPU_ID` | +| Nemotron Parse | `docs/nemotron-parse-extraction.md` | `APP_NVINGEST_PDFEXTRACTMETHOD=nemotron_parse`, start NIM | +| OCR config/switch | `docs/nemoretriever-ocr.md` | Switch between NeMo Retriever OCR and Paddle OCR | +| Save to disk | `docs/mount-ingestor-volume.md` | `APP_NVINGEST_SAVETODISK=True`, mount volume | +| Standalone NV-Ingest | `docs/nv-ingest-standalone.md` | Direct Python client, no full ingestor server | +| Batch ingestion | See `scripts/batch_ingestion.py` | `python scripts/batch_ingestion.py --folder ... --collection-name ...` | +| Tune performance | `docs/accuracy_perf.md` | Adjust chunk size, overlap, batch settings | +| Summarization at ingest | `references/configure/summarization.md` | `generate_summary: true` in upload payload | + +## Agent-Specific Notes + +- Text-only mode: set `COMPONENTS_TO_READY_CHECK=""` in the active env file so NV-Ingest does not wait for disabled extraction services. If the compose file hardcodes `COMPONENTS_TO_READY_CHECK=ALL`, update it to `${COMPONENTS_TO_READY_CHECK:-ALL}` so the env var takes effect +- Use `--profile rag` with nims.yaml to skip OCR/detection NIMs in text-only mode +- Audio formats supported: `.mp3`, `.wav`, `.mp4`, `.avi`, `.mov`, `.mkv` +- Riva ASR requires ~8GB VRAM +- NeMo Retriever OCR is 2x+ faster than Paddle OCR but needs 8GB vs 3GB VRAM +- Batch CLI: `pip install -r scripts/requirements.txt` first; idempotent (skips already-ingested files) +- MIG deployments: reduce batch sizes for large bulk ingestion jobs + +## Notebooks +- `notebooks/ingestion_api_usage.ipynb` — Ingestor API: collections, uploads, document management + +## Source Documentation +- `docs/text_only_ingest.md` — Text-only ingestion (skip OCR/detection) +- `docs/audio_ingestion.md` — Audio/video ingestion via ASR +- `docs/nemotron-parse-extraction.md` — Nemotron Parse PDF extraction +- `docs/nemoretriever-ocr.md` — OCR configuration and switching +- `docs/mount-ingestor-volume.md` — Volume mount for extraction results +- `docs/nv-ingest-standalone.md` — Standalone NV-Ingest without ingestor server +- `docs/accuracy_perf.md` — Ingestion tuning settings (chunk size, overlap, batch params) +- `docs/service-port-gpu-reference.md` — OCR port mappings and GPU assignments diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/mcp.md b/skill-source/.agents/skills/rag-blueprint/references/configure/mcp.md new file mode 100644 index 000000000..0fc9516e8 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/mcp.md @@ -0,0 +1,26 @@ +# MCP Server & Client + +## When to Use +- User wants to expose RAG APIs as MCP tools for agentic workflows +- User asks about MCP transport modes, NeMo Agent Toolkit integration, or ReAct agents + +## Process +1. Read `docs/mcp.md` for full MCP server/client setup and configuration +2. Choose transport mode: `sse`, `streamable_http`, or `stdio` +3. Run MCP server from `examples/nvidia_rag_mcp/mcp_server.py` +4. For agentic RAG, see ReAct agent example in `examples/rag_react_agent/` + +## Agent-Specific Notes +- MCP wraps both RAG tools (`generate`, `search`, `get_summary`) and Ingestor tools (`create_collection`, `upload_documents`, etc.) via FastMCP +- `stdio` transport does not require a running server — client spawns it directly +- ReAct agent requires: Python 3.11+, `NVIDIA_API_KEY`, and data already ingested into Milvus +- Configure Milvus endpoint in `examples/rag_react_agent/src/rag_react_agent/configs/config.yml` or via `APP_VECTORSTORE_URL` + +## Notebooks +| Notebook | Description | +|----------|-------------| +| `notebooks/mcp_server_usage.ipynb` | End-to-end MCP workflow: collection creation, upload, RAG queries | +| `notebooks/nat_mcp_integration.ipynb` | NeMo Agent Toolkit integration with RAG MCP server | + +## Source Documentation +- `docs/mcp.md` -- full MCP server/client documentation and transport configuration diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/migration.md b/skill-source/.agents/skills/rag-blueprint/references/configure/migration.md new file mode 100644 index 000000000..c56e020ee --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/migration.md @@ -0,0 +1,35 @@ +# Migration Guide + +## When to Use +- User is upgrading between RAG Blueprint versions +- User encounters breaking API changes or deprecated endpoints after an update + +## Process +1. Read `docs/migration_guide.md` for full version-by-version migration details +2. Identify the user's current and target versions +3. Apply changes sequentially for each version gap + +## Agent-Specific Notes + +### v2.2.0 → v2.3.0 +- New `confidence_threshold` field in `/generate` and `/search` (0.0–1.0, default 0.0) +- New `summary_options` parameter with `page_filter`, `shallow_summary`, `summarization_strategy` +- `SUMMARY_LLM_MAX_CHUNK_LENGTH` and `SUMMARY_CHUNK_OVERLAP` changed from character-based to token-based — divide old values by ~4 + +### v2.1.0 → v2.2.0 +- Added `generate_summary` to `/documents`, new `GET /summary` endpoint +- `POST /collection` (singular) replaces `POST /collections` for single collection creation +- `collection_names: List[str]` replaces `collection_name: str` in `/generate` and `/search` + +### v2.0.0 → v2.1.0 +- `POST /documents` gained `blocking: bool` (default `True`); use `false` + `GET /status` for async + +### v1.0.0 → v2.0.0 (Breaking) +- Single server split into RAG Server (port 8081) and Ingestion Server (port 8082) +- Collections must be explicitly created before uploading documents +- Default changed from cloud-hosted to on-prem models + +## Source Documentation +- `docs/migration_guide.md` — Full migration guide with examples and env var changes +- `docs/release-notes.md` — Release notes and version history +- `docs/query-to-answer-pipeline.md` — Query-to-answer pipeline architecture overview diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/models-and-infrastructure.md b/skill-source/.agents/skills/rag-blueprint/references/configure/models-and-infrastructure.md new file mode 100644 index 000000000..68add08bc --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/models-and-infrastructure.md @@ -0,0 +1,68 @@ +# Models, Vector DB & Service API Keys + +## When to Use +User wants to change LLM, embedding, or ranking models; switch vector DB (Milvus/Elasticsearch); configure Milvus auth, GPU mode, or custom endpoints; set service-specific API keys; or build a custom VDB operator. + +## Process + +Detect the deployment mode before making changes. Docker: edit the active env file. Helm: edit `values.yaml` under `nimOperator` and `envVars` sections. Library: edit `notebooks/config.yaml`. + +### Change Models (LLM, Embedding, Ranking) +1. Read `docs/change-model.md` for full model change instructions +2. Read `docs/model-profiles.md` for NIM profile selection and GPU-specific profiles +3. Key env vars: `APP_LLM_MODELNAME`, `APP_EMBEDDINGS_MODELNAME`, `APP_RANKING_MODELNAME` +4. Embedding model change requires re-ingesting all documents — update `APP_EMBEDDINGS_DIMENSIONS` to match +5. Restart affected services (RAG server + ingestor for embedding changes) +6. Verify via health endpoint + +### Switch Vector DB (Milvus to Elasticsearch) +1. Read `docs/change-vectordb.md` for full setup (Docker and Helm) +2. Key env vars: `APP_VECTORSTORE_URL`, `APP_VECTORSTORE_NAME` +3. Data is not migrated — re-ingest all documents after switching +4. Elasticsearch requires port 9200; check for conflicts + +### Milvus Configuration +1. Read `docs/milvus-configuration.md` for indexing, GPU, auth, and tuning +2. Read `docs/milvus-schema.md` for collection schema requirements +3. CPU mode: set `APP_VECTORSTORE_ENABLEGPUSEARCH=False`, `APP_VECTORSTORE_ENABLEGPUINDEX=False`, change Milvus image to non-GPU +4. Auth: download milvus.yaml, enable `authorizationEnabled`, set password before first deployment + +### API Keys +1. Read `docs/api-key.md` for NGC API key setup and per-service keys +2. Fallback order: service-specific key > `NVIDIA_API_KEY` > `NGC_API_KEY` +3. Per-service keys: `APP_LLM_APIKEY`, `APP_EMBEDDINGS_APIKEY`, `APP_RANKING_APIKEY`, `APP_VLM_APIKEY`, etc. + +## Decision Table + +| Goal | Source Doc | Key Action | +|------|-----------|------------| +| Change LLM | `docs/change-model.md` | Set `APP_LLM_MODELNAME`, restart RAG server | +| Change embedding | `docs/change-model.md` | Set `APP_EMBEDDINGS_MODELNAME` + `APP_EMBEDDINGS_DIMENSIONS`, re-ingest | +| Change reranker | `docs/change-model.md` | Set `APP_RANKING_MODELNAME`, restart RAG server | +| Switch to Elasticsearch | `docs/change-vectordb.md` | Create data dir, start ES profile, set env vars, re-ingest | +| Milvus auth | `docs/milvus-configuration.md` | Download config, enable auth, mount volume | +| Milvus CPU mode | `docs/milvus-configuration.md` | Change image, disable GPU env vars | +| Custom VDB | `docs/change-vectordb.md` | Implement `VDBRag`, register in `__init__.py` | +| NIM profiles | `docs/model-profiles.md` | List profiles, set `NIM_MODEL_PROFILE` | +| Service API keys | `docs/api-key.md` | Set per-service `*_APIKEY` vars | +| Collection schema | `docs/milvus-schema.md` | Required fields: pk, vector, text, source, content_metadata | + +## Agent-Specific Notes + +- Nemotron-3-Nano naming: `nvidia/nemotron-3-nano-30b-a3b` (NVIDIA-hosted) vs `nvidia/nemotron-3-nano` (self-hosted NIM) — same model, different names +- Helm model changes go in `values.yaml` under `nimOperator` and `envVars` sections +- Custom VDB operator requires implementing `VDBRag` base class — see `docs/change-vectordb.md` "Custom Vector Database Operator" section +- VDB auth tokens can be passed per-request via `Authorization: Bearer ` header +- Milvus password persists in etcd volume — to change after deployment, must delete volumes (destroys data) + +## Notebooks +- `notebooks/building_rag_vdb_operator.ipynb` — Custom VDB operator implementation (OpenSearch example) + +## Source Documentation +- `docs/change-model.md` — Model changes (LLM, embedding, ranking, NIM images) +- `docs/change-vectordb.md` — Vector DB switching, Elasticsearch setup, custom VDB operator +- `docs/milvus-configuration.md` — Milvus indexing, GPU config, auth, tuning +- `docs/milvus-schema.md` — Collection schema fields and requirements +- `docs/model-profiles.md` — NIM profile definitions and selection +- `docs/api-key.md` — NGC API key setup, per-service keys, fallback order +- `docs/service-port-gpu-reference.md` — Port mappings and GPU assignments for all services diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/multimodal-query.md b/skill-source/.agents/skills/rag-blueprint/references/configure/multimodal-query.md new file mode 100644 index 000000000..783b6c209 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/multimodal-query.md @@ -0,0 +1,35 @@ +# Multimodal Query (Image + Text) + +## When to Use +- User wants to query knowledge base with images and text together +- User asks about VLM (Vision Language Model) deployment for RAG +- User wants image-based document understanding or visual Q&A + +## Restrictions +- Not available with Elasticsearch — Milvus only +- Reranker must be disabled (`ENABLE_RERANKER=false`) +- Summarization not supported (VLM replaces LLM) +- On-prem: requires NVIDIA H100 or A100 SXM 80GB GPU +- Single-page retrieval only — image queries return content from one page per document + +## Process +1. Detect the deployment mode (Docker / Helm / Library). Docker: edit the active env file. Helm: edit `values.yaml`. Library: edit `notebooks/config.yaml` +2. Read `docs/multimodal-query.md` for full env var configuration and commands +3. Choose variant: self-hosted (Docker), NVIDIA-hosted (cloud), or Helm +4. Deploy VLM + VLM Embedding NIMs per source doc instructions +5. Set VLM env vars in the active config and switch embedding model to VLM embedding +6. Restart ingestor + RAG server (Docker: add `--build` flag) and verify + +## Agent-Specific Notes +- Must select a collection before querying — queries without collection return no results +- First VLM deployment: model downloads take 10–20 min (~10GB+) +- `VLM_MS_GPU_ID` — read `docs/service-port-gpu-reference.md` for the default GPU assignment and override if needed +- Cloud rate limits apply for ingestion of >10 files +- For Helm with MIG: ensure dedicated MIG slice is assigned to VLM +- Image extraction must be enabled: `APP_NVINGEST_EXTRACTIMAGES=True`, `APP_NVINGEST_IMAGE_ELEMENTS_MODALITY=image` + +## Notebooks +- `notebooks/image_input.ipynb` — end-to-end multimodal query examples, image upload, VLM querying + +## Source Documentation +- `docs/multimodal-query.md` — full Docker/cloud/Helm configuration, env vars, API usage, limitations diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/notebooks.md b/skill-source/.agents/skills/rag-blueprint/references/configure/notebooks.md new file mode 100644 index 000000000..544de03bb --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/notebooks.md @@ -0,0 +1,50 @@ +# Notebooks + +## When to Use +- Hands-on examples of NVIDIA RAG Blueprint features are needed +- There are questions about Jupyter notebooks, tutorials, or code samples + +## Process +1. Read `docs/notebooks.md` for full notebook descriptions and prerequisites. +2. Set up the environment: virtualenv, `jupyterlab`, and `git lfs pull` for test data. +3. Open JupyterLab at `http://:8889`. + +## Agent-Specific Notes +- Git LFS is required because several notebooks rely on large data files (`git lfs install && git lfs pull`). +- In Docker mode, deploy NVIDIA RAG Blueprint first, then run notebooks against the running services. +- In library mode, use `rag_library_usage.ipynb` (full) or `rag_library_lite_usage.ipynb` (containerless). +- The custom VDB operator notebook requires Docker for OpenSearch services. + +## Notebook Catalog + +### Beginner +| Notebook | Topic | +|-----------------------------|-------------------------------------| +| `ingestion_api_usage.ipynb` | Document ingestion through the API | +| `retriever_api_usage.ipynb` | Search and retrieval API | +| `image_input.ipynb` | Image upload and multimodal queries | + +### Intermediate +| Notebook | Topic | +|--------------------------------|----------------------------------------| +| `summarization.ipynb` | Document summarization strategies | +| `evaluation_01_ragas.ipynb` | RAGAS accuracy, relevancy, groundedness| +| `evaluation_02_recall.ipynb` | Recall at top-k cutoffs | +| `nb_metadata.ipynb` | Custom metadata and filtered retrieval | +| `rag_library_usage.ipynb` | Full library mode end-to-end | +| `rag_library_lite_usage.ipynb` | Lite, containerless library mode | + +### Advanced +| Notebook | Topic | +|-----------------------------------|-------------------------------------| +| `building_rag_vdb_operator.ipynb` | Custom OpenSearch VDB operator | +| `mcp_server_usage.ipynb` | MCP server with transport modes | +| `nat_mcp_integration.ipynb` | NeMo Agent Toolkit plus MCP | + +### Deployment +| Notebook | Topic | +|--------------------|-----------------------| +| `launchable.ipynb` | Brev cloud deployment | + +## Source Documentation +- `docs/notebooks.md` — full notebook descriptions, setup, and prerequisites. diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/observability.md b/skill-source/.agents/skills/rag-blueprint/references/configure/observability.md new file mode 100644 index 000000000..5b291d339 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/observability.md @@ -0,0 +1,29 @@ +# Observability + +## When to Use +- User wants tracing, metrics, or monitoring for the RAG pipeline +- User asks about latency debugging, Zipkin, Grafana, or Prometheus + +## Process +1. Detect the deployment mode. Docker: edit the active env file. Helm: edit `values.yaml`. Library: edit `notebooks/config.yaml` +2. Read `docs/observability.md` for full setup (Docker and Helm) +3. Set `OPENTELEMETRY_CONFIG_FILE` and `APP_TRACING_ENABLED=True` in the active config +4. Start observability stack and restart RAG server +5. Import Grafana dashboard from `deploy/config/rag-metrics-dashboard.json` + +## Agent-Specific Notes +- Library mode: set `OPENTELEMETRY_CONFIG_FILE` in the environment for tracing; the Docker-based Prometheus/Grafana stack is independent +- Helm: Prometheus Operator CRDs must be installed before deploying with observability enabled +- Default Grafana credentials: `admin` / `admin` +- Zipkin spans cover: `query-rewriter`, `retriever`, `context-reranker`, `llm-stream` +- Span I/O visible via `traceloop.entity.input` / `traceloop.entity.output` fields + +### Quick Latency Triage +| Symptom | Check | +|---------|-------| +| Slow first token | `rag_ttft_ms` — compare retriever and reranker spans | +| Slow full response | `llm_generation_time_ms` / `llm-stream` span | +| Retrieval heavy | Compare `retrieval_time_ms` vs `context_reranker_time_ms` | + +## Source Documentation +- `docs/observability.md` -- full Docker/Helm setup, env vars, metrics reference, and dashboard import diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/query-and-conversation.md b/skill-source/.agents/skills/rag-blueprint/references/configure/query-and-conversation.md new file mode 100644 index 000000000..2f092cd97 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/query-and-conversation.md @@ -0,0 +1,82 @@ +```markdown +# Query Rewriting, Query Decomposition, and Multi-Turn + +Use these features when you want the system to understand follow-up questions, rewrite queries for better retrieval, or break complex questions into smaller parts. + +## When to use + +Use these settings when: + +- You want to enable multi-turn conversations or support follow-up questions. +- You want query rewriting to improve retrieval accuracy. +- You need complex multi-hop query decomposition. +- You are configuring or debugging conversation history behavior.[file:1] + +## Restrictions + +- Query rewriting and multi-turn both require `CONVERSATION_HISTORY > 0`. If it is set to 0, query rewriting has no effect.[file:1] +- Query decomposition works only when `use_knowledge_base=true` and with a single collection.[file:1] +- On Helm, query rewriting is supported only with an on-prem LLM, not with cloud-hosted models.[file:1] + +## Dependencies + +`CONVERSATION_HISTORY` is shared by query rewriting and multi-turn, so changing it affects both behaviors. + +| Setting | Depends on | Side effect when changed | +|-------------------------|----------------------------|-----------------------------------------------------------| +| `ENABLE_QUERYREWRITER` | `CONVERSATION_HISTORY > 0` | Enabling requires conversation history; disabling has no side effects | +| `CONVERSATION_HISTORY` | — | Setting to `0` also effectively disables query rewriting |[file:1] + +## Process + +First detect the deployment mode. +- Docker: edit the active environment file. +- Helm: edit `values.yaml`. +- Library: edit `notebooks/config.yaml`.[file:1] + +### Query rewriting + +1. Review `docs/multiturn.md` for full configuration details.[file:1] +2. To enable, set `ENABLE_QUERYREWRITER=True`. If `CONVERSATION_HISTORY` is `0`, set it to `5` or another positive value.[file:1] +3. To disable, unset or comment out `ENABLE_QUERYREWRITER`.[file:1] +4. Restart the RAG server.[file:1] + +### Multi-turn + +1. Review `docs/multiturn.md` for configuration, retrieval strategies, and API usage.[file:1] +2. To enable, set `CONVERSATION_HISTORY > 0` and choose the retrieval strategy you want to use.[file:1] +3. To disable, set `CONVERSATION_HISTORY=0`.[file:1] +4. Restart the RAG server.[file:1] + +### Query decomposition + +1. Review `docs/query_decomposition.md` for the decomposition algorithm, limitations, and examples.[file:1] +2. Set `ENABLE_QUERY_DECOMPOSITION=true` and `MAX_RECURSION_DEPTH=3` (or a different depth that fits your use case).[file:1] +3. Restart the RAG server.[file:1] + +## Decision table + +| Goal | Source doc | Key settings | +|-------------------------------|----------------------------|-----------------------------------------------------------| +| Multi-turn with best accuracy | `docs/multiturn.md` | `CONVERSATION_HISTORY=5`, `ENABLE_QUERYREWRITER=True` | +| Multi-turn with low latency | `docs/multiturn.md` | `CONVERSATION_HISTORY=5`, `MULTITURN_RETRIEVER_SIMPLE=True` | +| Complex multi-hop queries | `docs/query_decomposition.md` | `ENABLE_QUERY_DECOMPOSITION=true`, `MAX_RECURSION_DEPTH=3` | +| Disable multi-turn (default) | — | `CONVERSATION_HISTORY=0` |[file:1] + +## Agent-specific notes + +- `MULTITURN_RETRIEVER_SIMPLE` only applies when query rewriting is disabled. If both are configured, query rewriting takes precedence.[file:1] +- You can toggle query rewriting per request by setting `enable_query_rewriting: true` in `POST /generate`, but `CONVERSATION_HISTORY` must still be greater than 0.[file:1] +- By default, multi-turn is disabled with `CONVERSATION_HISTORY=0`.[file:1] +- Query decomposition adds latency and is most useful for multi-hop queries that involve multiple entities or steps.[file:1] +- In library mode, configure these settings in `notebooks/config.yaml` instead of using environment variables.[file:1] + +## Notebooks + +- `notebooks/retriever_api_usage.ipynb`: RAG retriever API usage with search and end-to-end query examples.[file:1] + +## Source documentation + +- `docs/query_decomposition.md`: Decomposition algorithm details, when to use it, and recursion depth guidance.[file:1] +- `docs/multiturn.md`: Conversation history behavior, retrieval strategies, API usage, and Helm configuration.[file:1] +``` diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/reasoning-and-generation.md b/skill-source/.agents/skills/rag-blueprint/references/configure/reasoning-and-generation.md new file mode 100644 index 000000000..d5bb24114 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/reasoning-and-generation.md @@ -0,0 +1,57 @@ +# Reasoning, Self-Reflection & Prompt Customization + +## When to Use +User wants to enable reasoning/thinking mode, configure self-reflection, customize prompts, adjust generation parameters (max tokens, temperature, citations), or understand thinking budget options. + +## Process +1. Detect the deployment mode (Docker / Helm / Library). Docker: edit the active env file. Helm: edit `values.yaml`. Library: edit `notebooks/config.yaml` +2. Read the relevant source doc for the specific feature +3. Apply env vars to the active config or edit prompt files, restart RAG server +4. Prompt changes require `--build` flag (Docker); env var changes only need restart +5. Verify: test with a query and check for reasoning output or changed behavior + +## Decision Table + +| Goal | Source Doc | Key Action | +|------|-----------|------------| +| Enable reasoning (Nemotron 1.5) | `docs/enable-nemotron-thinking.md` | Edit `prompt.yaml`: `/no_think` → `/think`, set temperature | +| Enable reasoning (Nano 30B) | `docs/enable-nemotron-thinking.md` | `ENABLE_NEMOTRON_3_NANO_THINKING=true` | +| Self-reflection | `docs/self-reflection.md` | `ENABLE_REFLECTION=true`, set thresholds | +| Prompt customization | `docs/prompt-customization.md` | `PROMPT_CONFIG_FILE=/path/to/custom.yaml` or edit prompt.yaml | +| Generation parameters | `docs/llm-params.md` | `LLM_MAX_TOKENS`, `LLM_TEMPERATURE`, `ENABLE_CITATIONS` | +| Per-request overrides | `docs/llm-params.md` | `temperature`, `top_p`, `max_tokens`, `stop` in API payload | + +## Agent-Specific Notes + +- Prompt changes need `--build` flag on restart; env var changes do not +- Self-reflection: streaming not supported during groundedness checks +- Self-reflection uses same LLM by default; override with `REFLECTION_LLM`, `REFLECTION_LLM_SERVERURL`, `REFLECTION_LLM_APIKEY` +- Helm: only on-premises reflection is supported +- GPU requirements for reflection: see `docs/self-reflection.md` for optimal GPU configurations +- Debug reflection: set `LOGLEVEL=INFO` to observe iteration counts +- `FILTER_THINK_TOKENS=false` to see full reasoning output (filtered by default) +- 18 prompt templates available in `prompt.yaml` — custom file only overrides specified keys + +### Reasoning Model Comparison + +| Model | Control | Thinking Budget | Output Format | +|-------|---------|-----------------|---------------| +| Nemotron 1.5 | System prompt (`/think`) | None | `` tags (filtered by default) | +| Nemotron-3-Nano 9B | System prompt (`/think`) | `min_thinking_tokens` + `max_thinking_tokens` | `reasoning_content` field | +| Nemotron-3-Nano 30B | `ENABLE_NEMOTRON_3_NANO_THINKING` env var | `max_thinking_tokens` only | `reasoning_content` field | + +### Thinking Budget Recommendations + +| Range | Use Case | +|-------|----------| +| 1024–4096 | Faster responses for simpler questions | +| 8192–16384 | More thorough reasoning for complex queries | + +## Notebooks +- `notebooks/retriever_api_usage.ipynb` — end-to-end query examples showing generation behavior + +## Source Documentation +- `docs/enable-nemotron-thinking.md` — Reasoning mode for all Nemotron models +- `docs/self-reflection.md` — Self-reflection configuration and thresholds +- `docs/prompt-customization.md` — Prompt template catalog and customization +- `docs/llm-params.md` — Generation parameters (temperature, max tokens, etc.) diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/search-and-retrieval.md b/skill-source/.agents/skills/rag-blueprint/references/configure/search-and-retrieval.md new file mode 100644 index 000000000..b311c958d --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/search-and-retrieval.md @@ -0,0 +1,67 @@ +# Search & Retrieval: Hybrid Search, Multi-Collection, Metadata & Profiles + +## When to Use +User wants to enable hybrid search, query multiple collections, add custom metadata/filters, tune retrieval performance, configure reranker, enable natural language filter generation, or switch accuracy/performance profiles. + +## Process + +1. Detect the deployment mode (Docker / Helm / Library). Docker: edit the active env file. Helm: edit `values.yaml`. Library: edit `notebooks/config.yaml` +2. Read the relevant source doc for detailed configuration +3. Apply the required env vars to the active config and restart affected services +4. Verify via search/generate API call + +## Decision Table + +| Goal | Source Doc | Key Env Vars | +|------|-----------|-------------| +| Hybrid search | `docs/hybrid_search.md` | `APP_VECTORSTORE_SEARCHTYPE=hybrid` | +| Multi-collection | `docs/multi-collection-retrieval.md` | `enable_reranker: True` in API payload | +| Custom metadata | `docs/custom-metadata.md` | Metadata in upload payload, `vdb_filter_expression` in query | +| Accuracy profile | `docs/accuracy_perf.md` | Copy values from `deploy/compose/accuracy_profile.env` into the active env file | +| Performance profile | `docs/accuracy_perf.md` | Copy values from `deploy/compose/perf_profile.env` into the active env file | +| Filter generation | `docs/custom-metadata.md` | `ENABLE_FILTER_GENERATOR=True` | + +## Agent-Specific Notes + +- Hybrid search requires re-ingesting — existing collections created with `dense` must be re-created +- Multi-collection: limited to 5 collections per query; reranker is mandatory +- Multi-collection not supported when `ENABLE_QUERY_DECOMPOSITION=true` +- Elasticsearch RRF not supported in open-source version — must use `weighted` ranker +- Ingestor must be restarted alongside RAG server when enabling hybrid search +- `RERANKER_CONFIDENCE_THRESHOLD` is a legacy alias for `RERANKER_SCORE_THRESHOLD` +- Recommended `RERANKER_SCORE_THRESHOLD` range: 0.3–0.5 (too high filters out too many chunks) + +### Advanced Tuning (not fully documented elsewhere) + +| Variable | Default | Description | +|----------|---------|-------------| +| `APP_VECTORSTORE_INDEXTYPE` | `GPU_CAGRA` | Vector index type | +| `APP_VECTORSTORE_EF` | `100` | Search accuracy/speed trade-off (must be >= `VECTOR_DB_TOPK`) | +| `VECTOR_DB_TOPK` | `100` | Candidates from vector DB (input to reranker) | +| `APP_RETRIEVER_TOPK` | `10` | Chunks sent to LLM prompt (after reranking) | +| `ENABLE_RERANKER` | `True` | Toggle reranking model | +| `RERANKER_SCORE_THRESHOLD` | `0.0` | Minimum reranker score (0.0–1.0) | +| `COLLECTION_NAME` | `multimodal_data` | Default collection name | + +### Partial Filtering +- Strict (default): fails if any collection doesn't support the filter +- Flexible (`allow_partial_filtering: true` in config.yaml): succeeds if at least one collection supports it + +### VDB Filter Support + +| Feature | Milvus | Elasticsearch | +|---------|--------|---------------| +| NL filter generation | LLM-powered | Not supported (manual DSL) | +| Filter syntax | String expressions | List of dicts (ES Query DSL) | +| UI support | Full filtering interface | API only | + +## Notebooks +- `notebooks/retriever_api_usage.ipynb` — RAG retriever API: search and end-to-end queries +- `notebooks/nb_metadata.ipynb` — Metadata ingestion, filtering, and extraction from queries + +## Source Documentation +- `docs/hybrid_search.md` — Hybrid dense + sparse search configuration +- `docs/multi-collection-retrieval.md` — Multi-collection querying +- `docs/custom-metadata.md` — Custom metadata schema, filtering expressions, filter generation +- `docs/accuracy_perf.md` — Best practices for tuning ingestion/retrieval/generation settings +- `docs/python-client.md` — Python library API for search and filtering diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/summarization.md b/skill-source/.agents/skills/rag-blueprint/references/configure/summarization.md new file mode 100644 index 000000000..299c41e89 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/summarization.md @@ -0,0 +1,40 @@ +# Document Summarization + +## When to Use +- User wants to generate summaries during document ingestion +- User asks about summarization strategies or options +- User wants to check summary status or progress + +## Restrictions +- Not supported in lite mode (containerless/library-only deployment) +- Requires Redis for status tracking and rate limiting +- Collection must exist before uploading with `generate_summary: true` + +## Process +1. Detect the deployment mode. Docker: edit the active env file. Helm: configure under `ingestor-server.envVars` in `values.yaml`. Library: use the upload API parameters directly (no env vars needed) +2. Read `docs/summarization.md` for full configuration, env vars, and prompt customization +3. Set `generate_summary: true` in the upload payload (per-request, no global toggle) +4. Optionally configure `summary_options`: strategy, shallow mode, page filter +5. Retrieve summary via `GET /v1/summary?collection_name=...&file_name=...` + +## Decision Table + +| Goal | Strategy | Notes | +|------|----------|-------| +| Fastest overview | `"single"` + `shallow_summary=true` + `page_filter` | Quick text-only extraction | +| Best quality | `null` (iterative, default) + `shallow_summary=false` | Sequential refinement | +| Balanced | `"hierarchical"` + `shallow_summary=true` | Parallel tree-based | + +## Agent-Specific Notes +- `CONVERSATION_HISTORY` prerequisite does not apply — that's for query rewriting only +- `SUMMARY_LLM_SERVERURL=""` (empty) routes to NVIDIA cloud; `"nim-llm:8000"` for self-hosted +- `SUMMARY_LLM_MAX_CHUNK_LENGTH` should be below the model's context window to leave room for prompt + output +- Redis semaphore auto-resets on ingestor startup (prevents stale values from crashes) +- If Redis is unavailable, summaries still generate but no real-time status tracking +- Status entries have 24-hour TTL in Redis + +## Notebooks +- `notebooks/summarization.ipynb` — complete examples for all strategies, status polling, library mode usage + +## Source Documentation +- `docs/summarization.md` — env var reference, prompt customization, rate limiting, chunking details diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/user-interface.md b/skill-source/.agents/skills/rag-blueprint/references/configure/user-interface.md new file mode 100644 index 000000000..7fbad8165 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/user-interface.md @@ -0,0 +1,27 @@ +# User Interface + +## When to Use +- User asks about the RAG UI, uploading documents, settings, or metadata filtering +- User wants to configure features via the web interface + +## Restrictions +- Sample/experimentation UI — not intended for production +- 100-file limit per upload batch; use multiple batches or API for bulk uploads +- 10 MB max per image attachment + +## Process +1. Read `docs/user-interface.md` for full UI documentation +2. Access at `http://localhost:8090` (or `http://:8090` for remote) +3. Configure RAG settings and feature toggles via Settings panel +4. Use Filter Bar above chat input for metadata-filtered queries + +## Agent-Specific Notes +- VLM Inference must be enabled in Settings > Feature Toggles before image attachments work +- ECONNRESET errors on multi-file uploads — recommend API for bulk operations +- Document summaries generate asynchronously; UI shows "Generating summary..." until complete +- Document count in UI may lag slightly after ingestion +- Metadata filtering supports AND/OR logic between filters (toggle via logic button) +- Custom metadata schema is set during collection creation via the Metadata Schema Editor + +## Source Documentation +- `docs/user-interface.md` -- full UI documentation including settings, file types, metadata, and health monitoring diff --git a/skill-source/.agents/skills/rag-blueprint/references/configure/vlm.md b/skill-source/.agents/skills/rag-blueprint/references/configure/vlm.md new file mode 100644 index 000000000..d573cd0c2 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/configure/vlm.md @@ -0,0 +1,56 @@ +# VLM, VLM Embeddings & Image Captioning + +## When to Use +User wants image understanding, visual content analysis, VLM inference, multimodal embeddings, or image captioning during ingestion. + +## Restrictions +- Not available on B200 GPUs — use H100, A100 SXM 80GB, or RTX PRO 6000 +- Requires extra GPU (GPU 1+ for 2-GPU systems, GPU 2+ for 3+ GPUs with fallback) +- VLM embeddings: experimental, PDF-only, no summarization, no citations with page-as-image +- Image captioning on Helm: on-prem only (modify `values.yaml` to enable) + +## Process +1. Detect the deployment mode (Docker / Helm / Library). Docker: edit the active env file. Helm: edit `values.yaml`. Library: edit `notebooks/config.yaml` +2. Read the relevant source doc for detailed steps: + - VLM generation: `docs/vlm.md` + - VLM embeddings: `docs/vlm-embed.md` + - Image captioning: `docs/image_captioning.md` +3. Start VLM NIM (self-hosted) or configure cloud endpoint (NVIDIA-hosted) +4. Set the required variables in the active config: + - Enabling: `ENABLE_VLM_INFERENCE=true` and `APP_NVINGEST_EXTRACTIMAGES=True` + - Disabling: re-comment those variables in the env file +5. Restart affected services and verify with a health check + image-containing document query + +## Decision Table + +| Goal | Source Doc | Docker Profile | Notes | +|------|-----------|---------------|-------| +| VLM replaces LLM | `docs/vlm.md` | `--profile vlm-generation` | LLM not started; set `VLM_TO_LLM_FALLBACK=false` | +| VLM + LLM fallback | `docs/vlm.md` | `--profile vlm-only` | Needs 3+ GPUs; both VLM and LLM running | +| VLM embeddings | `docs/vlm-embed.md` | `--profile vlm-embed` | Experimental; requires re-ingestion | +| Image captioning | `docs/image_captioning.md` | `--profile vlm-only` | Requires VLM NIM; Helm: on-prem only | +| Multimodal query | `docs/multimodal-query.md` | (depends on VLM mode) | Image + text querying | + +## Agent-Specific Notes + +- `--profile vlm-generation` skips the LLM entirely — use `--profile vlm-only` for fallback mode +- `VLM_TO_LLM_FALLBACK` defaults to `true`, but `vlm-generation` profile does not start LLM +- Helm VLM: disable `nim-llm` and enable `nim-vlm` (VLM uses LLM's GPU allocation) +- Helm fallback: keep both `nim-vlm` and `nim-llm` enabled, set `VLM_TO_LLM_FALLBACK: "true"` +- VLM context window is limited — keep queries self-contained +- Image captioning known issue: files without graphs/charts/tables/plots fail to ingest when captioning is enabled + +### Key Env Vars (always needed) +- `ENABLE_VLM_INFERENCE=true` — master toggle +- `APP_NVINGEST_EXTRACTIMAGES=True` — extract images during ingestion +- `VLM_MS_GPU_ID=` — self-hosted GPU assignment + +## Notebooks +- `notebooks/image_input.ipynb` — Multimodal queries with VLM (text + image) + +## Source Documentation +- `docs/vlm.md` — VLM generation (self-hosted, NVIDIA-hosted, Helm, Library) +- `docs/vlm-embed.md` — VLM embeddings (experimental) +- `docs/image_captioning.md` — Image captioning during ingestion +- `docs/multimodal-query.md` — Image + text querying +- `docs/service-port-gpu-reference.md` — default GPU assignments for VLM and other NIMs diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy.md b/skill-source/.agents/skills/rag-blueprint/references/deploy.md new file mode 100644 index 000000000..96d712892 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy.md @@ -0,0 +1,119 @@ +# RAG Blueprint Deployment + +## Phase 1: Environment Analysis + +Run this single command to collect all environment information at once: + +```bash +echo "=== GPU ===" && nvidia-smi --query-gpu=index,name,memory.total --format=csv,noheader 2>/dev/null || echo "NO_GPU"; echo "=== VRAM ===" && nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | awk '{s+=$1} END {print s "MB total"}' || echo "0MB total"; echo "=== DRIVER ===" && cat /proc/driver/nvidia/version 2>/dev/null | head -1 || echo "NO_DRIVER"; echo "=== CUDA ===" && nvcc --version 2>/dev/null | grep "release" || echo "NO_CUDA_TOOLKIT"; echo "=== DOCKER ===" && docker --version 2>/dev/null || echo "NO_DOCKER"; echo "=== COMPOSE ===" && docker compose version 2>/dev/null || echo "NO_COMPOSE"; echo "=== NVIDIA_TOOLKIT ===" && docker info 2>/dev/null | grep -i "runtimes.*nvidia" || echo "NO_NVIDIA_TOOLKIT"; echo "=== PYTHON ===" && python3 --version 2>/dev/null || echo "NO_PYTHON"; echo "=== DISK ===" && df -h --output=avail / | tail -1; echo "=== OS ===" && cat /etc/os-release 2>/dev/null | grep -E "^(NAME|VERSION)="; echo "=== NGC_KEY ===" && if [ -n "$NGC_API_KEY" ]; then echo "NGC_KEY_SET"; elif [ -n "$NVIDIA_API_KEY" ]; then echo "NVIDIA_KEY_SET"; elif grep -qr "NGC_API_KEY=" deploy/compose/.env deploy/compose/nvdev.env 2>/dev/null | grep -qv "nvapi-your-key"; then echo "DOTENV_SET"; else echo "NOT_SET"; fi; echo "=== RUNNING ===" && docker ps --format "{{.Names}}" 2>/dev/null | grep -E "(rag-server|ingestor-server|nim-llm|milvus)" | head -10 || echo "NO_RUNNING_SERVICES"; echo "=== PORTS ===" && (ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null) | grep -E ":(8081|8082|8090|19530) " || echo "PORTS_FREE"; echo "=== REPO ===" && git rev-parse --show-toplevel 2>/dev/null && git describe --tags 2>/dev/null || echo "NO_GIT_REPO"; echo "=== CACHE ===" && du -sh ~/.cache/model-cache/ 2>/dev/null || echo "NO_CACHE" +``` + +Present a summary table: + +| Check | Result | +|-------|--------| +| GPU(s) | (list with VRAM, or NO_GPU) | +| Total VRAM | (sum in MB/GB) | +| NVIDIA Driver | (version or NO_DRIVER) | +| CUDA Toolkit | (version or NO_CUDA_TOOLKIT) | +| Docker | (version or NO_DOCKER) | +| Docker Compose | (version or NO_COMPOSE) | +| NVIDIA Container Toolkit | (detected or NO_NVIDIA_TOOLKIT) | +| Python | (version or NO_PYTHON) | +| Free disk | (value) | +| OS | (name + version) | +| NGC_API_KEY | ENV_SET / DOTENV_SET / NOT_SET | +| Existing services | (list or none) | +| Port availability | (free or list conflicts) | +| Repo | (tag/branch or NO_GIT_REPO) | +| Model cache | (size or empty) | + +### Existing Services Warning + +If RAG services are already running, tell the user briefly: "Existing RAG services detected (list). Proceeding will restart them." Continue unless the user objects. + +If the user wants to **switch deployment modes** (e.g., NVIDIA-hosted → self-hosted, or Docker → library), shut down the existing deployment first via `references/shutdown.md`, then proceed with the new mode. + +If ports are occupied by non-RAG processes, tell the user which ports conflict and suggest stopping the conflicting process. This is a blocker. + +## Phase 2: NGC_API_KEY Handling + +Check in this order: + +1. If `NGC_API_KEY` is set in the shell environment → proceed. +2. If `NVIDIA_API_KEY` is set (common in library mode) → proceed silently. +3. If `NGC_API_KEY` is in `deploy/compose/.env` or `deploy/compose/nvdev.env` (and not the placeholder `nvapi-your-key`) → load it and proceed. +4. If none found → tell the user: "NGC_API_KEY is required. Get one from https://org.ngc.nvidia.com/setup/api-keys and run: `export NGC_API_KEY=\"nvapi-...\"` — then tell me when done." +5. After user confirms → re-check silently. If still not set, write placeholder to `.env` and tell the user to edit it. + +## Phase 3: Blocker Checks + +Automatically check and report all blockers at once (don't stop at the first one): + +Read `docs/support-matrix.md` for current minimum versions and disk requirements, then check: + +- **Docker Compose below minimum**: "Upgrade Docker Compose. See https://docs.docker.com/compose/install/linux/" +- **NVIDIA Driver below minimum** (if self-hosted): "Upgrade NVIDIA driver. See `docs/support-matrix.md` for required version." +- **NVIDIA Container Toolkit missing** (and self-hosted needed): "Install NVIDIA Container Toolkit. See https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html" +- **Insufficient disk**: "Check `docs/support-matrix.md` for disk requirements per deployment mode." +- **No Docker and no Python 3.11+**: "Install Docker or Python 3.11+ to proceed." + +List all blockers together so the user can fix them in one pass — don't make them fix one, re-run, fix another. + +## Phase 4: Route to Deployment Mode + +### User explicitly requests a mode +- "library mode" / "lite mode" / "no docker" / "python mode" → read and follow `deploy/library.md` +- "docker" / "self-hosted" / "local" → read and follow `deploy/docker.md` with mode **self-hosted** +- "cloud" / "nvidia-hosted" / "hosted" → read and follow `deploy/docker.md` with mode **nvidia-hosted** +- "retrieval only" / "search only" / "no LLM" → read and follow `deploy/docker.md` with mode **retrieval-only** +- "kubernetes" / "k8s" / "helm" → read and follow `deploy/helm.md` +- "workbench" / "ai workbench" → tell user to follow `deploy/workbench/README.md` (AI Workbench uses its own UI-driven workflow) + +### Docker is available (Docker + Compose detected) + +**Self-hosted eligible** — read `docs/support-matrix.md` ("Hardware Requirements (Docker)" section) for current GPU requirements. All of the following must also be true: +- GPU count and type matches the Docker self-hosted requirements from the support matrix +- ≥200 GB free disk (per `docs/support-matrix.md` "Disk Space Requirements") +- NVIDIA Container Toolkit detected +- NVIDIA driver meets minimum version from `docs/support-matrix.md` ("Driver Versions") + +If self-hosted eligible → read and follow `deploy/docker.md` with mode **self-hosted** + +**Otherwise with Docker** → read and follow `deploy/docker.md` with mode **nvidia-hosted** + +Tell the user WHY if they have some GPU but not enough: +- "You have [X GPU] with [Y GB] VRAM. Self-hosted requires [requirements from docs/support-matrix.md]. Deploying with NVIDIA-hosted cloud NIMs instead — faster startup, no model download." + +### Docker is available but Compose is not + +Tell the user: "Docker is installed but Docker Compose is below the minimum version (see `docs/support-matrix.md`). Install it: https://docs.docker.com/compose/install/linux/ — or use library mode instead." + +If user chooses library mode → read and follow `deploy/library.md` + +### Docker is not available + +- Python 3.11+ available → read and follow `deploy/library.md` with mode **lite** +- No Python → tell user to install Python 3.11+ or Docker + +## After Deployment + +Once deployment completes, verify health: + +```bash +echo "=== RAG Server ===" && curl -s http://localhost:8081/v1/health?check_dependencies=true 2>/dev/null || echo "RAG_SERVER_NOT_READY"; echo "=== Ingestor ===" && curl -s http://localhost:8082/v1/health?check_dependencies=true 2>/dev/null || echo "INGESTOR_NOT_READY" +``` + +If healthy, tell the user: +- "RAG Blueprint is running and healthy." +- "Ask me to configure features like VLM, query rewriting, guardrails, etc." +- "Ask me to shutdown when you're done." + +If unhealthy, read `references/troubleshoot.md` and diagnose. Match error output against known issues, fix, and retry. Escalate to the user only if the fix requires their action (API key, data deletion). + +## Notebooks +- `notebooks/launchable.ipynb` — Cloud deployment via Brev (alternative to local deployment) + +## Source Documentation +- `docs/support-matrix.md` — GPU requirements, driver versions, disk space, supported platforms +- `docs/service-port-gpu-reference.md` — port mappings and GPU assignments for all services diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/docker-nvidia-hosted.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/docker-nvidia-hosted.md new file mode 100644 index 000000000..f4c6ede07 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/docker-nvidia-hosted.md @@ -0,0 +1,38 @@ +# Docker Deployment (NVIDIA-Hosted NIMs) + +## When to Use +- User wants fast deployment without local model downloads +- User has no GPU or limited GPU +- User asks about cloud-hosted or NVIDIA API deployment +- User wants to avoid 15–30 min NIM startup time + +## Restrictions +- Requires internet access (calls NVIDIA cloud APIs) +- NVIDIA-hosted endpoints have rate limits — large ingestions (>10 files) may hit 429 errors +- NGC_API_KEY required for cloud API access +- Docker and Compose minimum versions per `docs/support-matrix.md` + +## Process +1. Read `docs/deploy-docker-nvidia-hosted.md` for full commands and env configuration +2. Use `deploy/compose/nvdev.env` — pre-configured for cloud endpoints. Source it before compose commands: `source deploy/compose/nvdev.env` +3. Start vector DB → ingestor → RAG server + frontend (no NIM startup needed) +4. Verify: `docker ps` shows containers; UI at `http://localhost:8090` + +## Decision Table + +| Goal | Key Action | +|------|------------| +| Standard cloud deployment | Use `nvdev.env` (pre-configured for cloud) | +| Zero-GPU (no Milvus GPU) | Also switch Milvus image to CPU-only | +| Large file ingestion | Reduce batch/concurrency settings to avoid 429s | +| Maximum throughput | Use self-hosted deployment instead | + +## Agent-Specific Notes +- First run: 5–10 min (image pulls only); subsequent: 1–2 min +- No `nims.yaml` startup — all model inference is cloud-hosted +- All subsequent configure/restart operations should source the same env file used for the initial deploy (`deploy/compose/nvdev.env`) +- For zero-GPU: switch Milvus to CPU-only by changing the GPU image tag to the equivalent non-GPU tag and setting `APP_VECTORSTORE_ENABLEGPUSEARCH=False`. See `docs/deploy-docker-nvidia-hosted.md` for the current image tags +- Rate limit mitigation for large ingestions: reduce `NV_INGEST_FILES_PER_BATCH`, `NV_INGEST_CONCURRENT_BATCHES`, `MAX_INGEST_PROCESS_WORKERS`, `NV_INGEST_MAX_UTIL` to minimum values + +## Source Documentation +- `docs/deploy-docker-nvidia-hosted.md` — full step-by-step commands, env var blocks, CPU Milvus setup diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/docker-retrieval-only.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/docker-retrieval-only.md new file mode 100644 index 000000000..fb4935cf8 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/docker-retrieval-only.md @@ -0,0 +1,37 @@ +# Retrieval-Only Deployment + +## When to Use +- User wants search/retrieval without LLM generation +- User asks to deploy only embedding + reranking services +- User wants `/search` endpoint with an external LLM +- User wants a lightweight, low-GPU deployment + +## Restrictions +- `/generate` endpoint returns an error — no LLM is deployed +- Self-hosted: 1 GPU, ~24 GB memory +- NVIDIA-hosted: 0 GPUs (cloud embedding + reranking) + +## Process +1. Read `docs/retrieval-only-deployment.md` for full commands, env vars, and API examples +2. Choose variant: self-hosted (local NIMs), NVIDIA-hosted (cloud), or Helm +3. For self-hosted: start only embedding + ranking NIMs, skip LLM +4. For NVIDIA-hosted: set embedding/ranking server URLs to empty, skip NIM startup entirely +5. For Helm: set `nimOperator.nim-llm.enabled=false` +6. Start vector DB → ingestor → RAG server +7. Verify health: `GET http://localhost:8081/v1/health?check_dependencies=true` + +## Decision Table + +| Goal | Variant | Key Difference | +|------|---------|----------------| +| Minimal GPU usage with local models | Self-hosted | 1 GPU, ~24 GB | +| Zero GPU, cloud APIs | NVIDIA-hosted | Set server URLs to empty, skip NIM startup | +| Kubernetes | Helm | Disable `nim-llm` in values.yaml | + +## Agent-Specific Notes +- Permission errors on model cache → try `USERID=0` or `chmod -R 755 ~/.cache/model-cache` +- Empty search results → verify documents ingested: `GET http://localhost:8082/v1/documents?collection_name=` +- Users can send `/search` results to their own external LLM for generation + +## Source Documentation +- `docs/retrieval-only-deployment.md` — full deployment commands, API examples, search payload options diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/docker-self-hosted.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/docker-self-hosted.md new file mode 100644 index 000000000..dc18ef649 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/docker-self-hosted.md @@ -0,0 +1,49 @@ +# Docker Deployment (Self-Hosted NIMs) + +## When to Use +- User wants full on-premises deployment with local NIM containers +- User has supported GPUs and wants models running locally +- User asks to deploy RAG Blueprint with Docker + +## Restrictions + +Read `docs/support-matrix.md` for current GPU requirements. Feature restrictions per GPU type: + +| GPU | Cannot Use | +|-----|------------| +| B200 | VLM, Guardrails, Nemotron Parse | +| RTX PRO 6000 | Nemotron Parse | + +- Read `docs/support-matrix.md` for current minimum NVIDIA Driver, CUDA, Docker, and Compose versions +- NVIDIA Container Toolkit required (`docker info` shows nvidia runtime) +- Disk space per `docs/support-matrix.md` ("Disk Space Requirements") +- If any prerequisite is missing, tell the user what to install before proceeding + +## Process +1. Read `docs/deploy-docker-self-hosted.md` for full commands and env configuration +2. Read `docs/support-matrix.md` for GPU compatibility and supported model combinations +3. Verify container toolkit, prepare model cache directory, source `.env` +4. Apply GPU-specific config per source docs +5. Start NIMs → wait for healthy → start remaining services +6. Verify: `docker ps` shows all containers healthy; UI at `http://localhost:8090` + +## Decision Table + +| Goal | Profile Flag | Notes | +|------|-------------|-------| +| Full deployment (default) | (none) | LLM + embedding + ranking + OCR + detection | +| Text-only RAG (lighter) | `--profile rag` | Skip OCR/detection NIMs | +| Ingestion workload only | `--profile ingest` | Embedding + OCR + detection | +| VLM replaces LLM | `--profile vlm-generation` | Not on B200 | +| Advanced PDF extraction | `--profile nemotron-parse` | Not on B200 or RTX PRO 6000 | + +## Agent-Specific Notes +- First run: 15–30 min (model downloads ~100–150 GB, no progress bar); subsequent: 2–5 min +- Monitor download progress: `du -sh ~/.cache/model-cache/` +- Permission error on model cache → try `USERID=0` instead of `USERID=$(id -u)` +- Cloud NIM section in `deploy/compose/.env` must be commented out for self-hosted +- Rebuild after code changes: add `--build` flag to compose up commands + +## Source Documentation +- `docs/deploy-docker-self-hosted.md` — full step-by-step commands, env vars, GPU assignments +- `docs/support-matrix.md` — GPU compatibility, supported models, hardware requirements diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/docker.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/docker.md new file mode 100644 index 000000000..eb31fb1a6 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/docker.md @@ -0,0 +1,88 @@ +# RAG Docker Deployment + +## Determine Mode + +If routed here from the deploy workflow, the mode (self-hosted, nvidia-hosted, or retrieval-only) was already decided. Use it. + +If invoked directly without a mode, auto-detect: + +```bash +echo "=== COMPOSE ===" && docker compose version 2>/dev/null || echo "NO_COMPOSE"; echo "=== GPU ===" && nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>/dev/null || echo "NO_GPU"; echo "=== DISK ===" && df -h --output=avail / | tail -1; echo "=== RUNNING ===" && docker ps --format "{{.Names}}" 2>/dev/null | grep -E "(rag-server|ingestor-server|nim-llm|milvus)" | head -10 || echo "NONE_RUNNING" +``` + +If NO_COMPOSE: stop and tell the user to install Docker Compose (see `docs/support-matrix.md` for minimum version). + +Read `docs/support-matrix.md` ("Hardware Requirements (Docker)" section) for current GPU requirements, then: +- GPU count/type meets self-hosted requirements from the support matrix, and 200+ GB free disk → **self-hosted** +- Any GPU or no GPU with ≥50 GB free disk → **nvidia-hosted** +- User explicitly says "retrieval only" / "no LLM" / "search only" → **retrieval-only** + +Auto-route based on hardware. Only ask if two modes are equally valid and the user's intent is ambiguous. + +## Verify NGC_API_KEY + +Auto-check all possible locations before asking: + +```bash +[ -n "$NGC_API_KEY" ] && echo "ENV_SET" || (grep -qr "NGC_API_KEY=" deploy/compose/.env deploy/compose/nvdev.env 2>/dev/null | grep -qv "nvapi-your-key" && echo "DOTENV_SET" || echo "NOT_SET") +``` + +- **ENV_SET**: proceed silently. +- **DOTENV_SET**: load the env file that contains the key and proceed. +- **NOT_SET**: ask the user to provide it. This is the only thing to ask for. + +## Docker Login + +Auto-check if already logged in: + +```bash +grep -q "nvcr.io" ~/.docker/config.json 2>/dev/null && echo "ALREADY_LOGGED_IN" || echo "NOT_LOGGED_IN" +``` + +If already logged in → proceed silently. + +If not logged in → tell the user to run this themselves (the key gets expanded in agent logs): + +> Please run in your terminal: `echo "${NGC_API_KEY}" | docker login nvcr.io -u '$oauthtoken' --password-stdin` + +Wait for confirmation only if login was needed. + +## Deploy + +Based on the mode, read and follow the appropriate reference: + +- **Self-hosted**: read and follow `docker-self-hosted.md` +- **NVIDIA-hosted**: read and follow `docker-nvidia-hosted.md` +- **Retrieval-only**: read and follow `docker-retrieval-only.md` + +## Post-Deploy Verification + +Run health checks: + +```bash +sleep 5; echo "=== RAG ===" && curl -s http://localhost:8081/v1/health?check_dependencies=true 2>/dev/null || echo "RAG_NOT_READY"; echo "=== INGESTOR ===" && curl -s http://localhost:8082/v1/health?check_dependencies=true 2>/dev/null || echo "INGESTOR_NOT_READY"; echo "=== CONTAINERS ===" && docker ps --format "table {{.Names}}\t{{.Status}}" 2>/dev/null | grep -E "(rag|milvus|nim|ingest)" | head -15 +``` + +If services are still initializing, automatically poll every 30 seconds: +- **NVIDIA-hosted**: poll until healthy or 5 minutes elapsed (no model downloads needed). +- **Self-hosted**: poll until healthy or 15 minutes elapsed (model downloads on first run). +- **Retrieval-only**: poll until healthy or 5 minutes elapsed. + +Show progress to the user during polling. + +## On Success + +Tell the user: +- "RAG Blueprint is running and healthy. Open http://localhost:8090 to use the UI." (skip for retrieval-only) +- "Ask me to configure features (VLM, query rewriting, guardrails, etc.)" +- "Ask me to shutdown when you're done." + +## On Error + +1. Read the error output from the failed command. +2. Read `references/troubleshoot.md` to match against common issues (port conflict, disk full, NGC auth, GPU OOM). +3. Apply the fix and retry. +4. If still failing, report the specific error to the user with the fix that was attempted. + +## Source Documentation +- `docs/support-matrix.md` — GPU requirements, hardware compatibility, disk space diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/helm-mig.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/helm-mig.md new file mode 100644 index 000000000..dd3dd8ce3 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/helm-mig.md @@ -0,0 +1,38 @@ +# MIG GPU Deployment + +## When to Use +- User wants fine-grained GPU allocation on Kubernetes using MIG slices +- User has H100 GPUs and wants to share them across RAG services +- User asks about Multi-Instance GPU deployment + +## Restrictions +- Requires H100 80GB HBM3 GPUs (MIG-compatible) +- MIG profiles in this guide are specific to H100 80GB — other GPUs need different profiles +- Requires cloned repository (MIG config files in `deploy/helm/`) +- All standard Helm prerequisites apply (GPU Operator, NIM Operator, StorageClass) +- Ingestion profile is scaled down with MIG — large bulk ingestion jobs may fail + +## Process +1. Read `docs/mig-deployment.md` for full configuration, commands, and MIG slice definitions +2. Enable MIG with mixed strategy on ClusterPolicy +3. Apply MIG ConfigMap and label the node +4. Verify node labels show `mig.config.state: "success"` before proceeding +5. Install Helm chart with `-f mig-slicing/values-mig.yaml` + +## Decision Table + +| Goal | Source Doc | Key Action | +|------|-----------|------------| +| Standard MIG on H100 | `docs/mig-deployment.md` | Apply MIG config, label node, install chart | +| RTX PRO 6000 with MIG | `docs/mig-deployment.md` | Also uncomment model section in values.yaml | +| Custom MIG profiles | NVIDIA MIG User Guide | Modify `mig-config.yaml` for different GPU types | + +## Agent-Specific Notes +- Must wait for `mig.config.state: "success"` on the node before Helm install — if not present, wait and re-check +- Default H100 MIG layout (see `docs/mig-deployment.md` for current GPU count and slice definitions): GPU 0 → small slices, GPU 1 → mixed slices, GPU 2 → full-GPU slice +- LLM gets the largest slice (`7g.80gb`); embedding/Milvus/ingest share small slices +- RTX PRO 6000 variant: uncomment model section in values.yaml, then use both `-f values.yaml -f mig-slicing/values-mig.yaml` +- Uninstall follows standard Helm procedure (see Helm deployment docs) + +## Source Documentation +- `docs/mig-deployment.md` — full MIG config, ClusterPolicy patches, node labeling, verification, Helm install commands diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/helm-standard.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/helm-standard.md new file mode 100644 index 000000000..df2630660 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/helm-standard.md @@ -0,0 +1,51 @@ +# Helm Deployment + +## When to Use +- User wants to deploy RAG Blueprint on Kubernetes +- User asks about Helm chart installation (from NGC or local repo) +- User mentions Kubernetes, k8s, or Helm in deployment context + +## Restrictions + +Read `docs/support-matrix.md` for current Kubernetes, Helm, and OS version requirements. + +- Requires GPU Operator + NIM Operator pre-installed +- Default StorageClass must be configured for PVC provisioning +- Disk space per `docs/support-matrix.md` +- NeMo Guardrails not available in Helm deployment +- Image captioning: on-prem only (requires `values.yaml` changes; see `docs/image_captioning.md`) + +## Process + +### Option A: Deploy from NGC (Remote Chart) +1. Read `docs/deploy-helm.md` for full commands and values +2. Ensure prerequisites: GPU Operator, NIM Operator, StorageClass, NGC_API_KEY +3. Install chart, monitor pods, port-forward frontend + +### Option B: Deploy from Repository (Local Chart) +1. Read `docs/deploy-helm-from-repo.md` for full commands and repo setup +2. Add required Helm repos, run `helm dependency update`, install from local path + +### RTX PRO 6000 Variant +1. Uncomment model section under `nimOperator.nim-llm.model` in `values.yaml` +2. See source docs for engine/precision/GPU settings + +## Decision Table + +| Goal | Option | Key Action | +|------|--------|------------| +| Quick deploy from published chart | NGC (Option A) | `helm upgrade --install` with NGC URL | +| Customized chart | Local repo (Option B) | Clone, modify values, `helm dependency update` | +| RTX PRO 6000 GPUs | Either option | Uncomment model section in values.yaml | +| Retrieval-only (no LLM) | Either option | `--set nimOperator.nim-llm.enabled=false` | + +## Agent-Specific Notes +- First deployment: 60–70 min (model cache download); subsequent: 10–15 min +- Pods in `ContainerCreating`/`Init` for extended time is normal during cache download +- PVCs are not removed by `helm uninstall` — delete manually: `kubectl delete nimcache --all -n rag && kubectl delete pvc --all -n rag` +- Port-forwarding may timeout for large file ingestion — not suitable for bulk uploads +- All configurable endpoints documented in `deploy/helm/nvidia-blueprint-rag/endpoints.md` + +## Source Documentation +- `docs/deploy-helm.md` — NGC remote chart deployment, prerequisites, monitoring +- `docs/deploy-helm-from-repo.md` — local chart deployment, repo setup, dependency management diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/helm.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/helm.md new file mode 100644 index 000000000..b381f4dcc --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/helm.md @@ -0,0 +1,103 @@ +# RAG Helm Deployment + +If routed here from the deploy workflow, proceed directly to Phase 1. + +## Phase 1: Prerequisites Check + +Run all checks at once: + +```bash +echo "=== KUBECTL ===" && kubectl version --client 2>/dev/null || echo "NO_KUBECTL"; echo "=== HELM ===" && helm version --short 2>/dev/null || echo "NO_HELM"; echo "=== STORAGECLASS ===" && kubectl get storageclass 2>/dev/null || echo "NO_STORAGECLASS"; echo "=== NODES ===" && kubectl get nodes -o wide 2>/dev/null || echo "NO_CLUSTER_ACCESS"; echo "=== GPU_OPERATOR ===" && kubectl get pods -n gpu-operator 2>/dev/null | grep -i running || echo "NO_GPU_OPERATOR"; echo "=== NIM_OPERATOR ===" && kubectl get pods -n nim-operator 2>/dev/null | grep -i running || echo "NO_NIM_OPERATOR"; echo "=== NAMESPACE ===" && kubectl get namespace rag 2>/dev/null && echo "NAMESPACE_EXISTS" || echo "NO_NAMESPACE"; echo "=== HELM_RELEASE ===" && helm list -n rag 2>/dev/null | grep rag || echo "NO_EXISTING_RELEASE"; echo "=== PODS ===" && kubectl get pods -n rag 2>/dev/null | head -10 || echo "NO_PODS"; echo "=== NGC_KEY ===" && [ -n "$NGC_API_KEY" ] && echo "NGC_API_KEY SET" || echo "NGC_API_KEY NOT_SET"; echo "=== GPU_RESOURCES ===" && kubectl get nodes -o json 2>/dev/null | grep -o '"nvidia.com/gpu": "[0-9]*"' || echo "NO_GPU_RESOURCES" +``` + +Read `docs/support-matrix.md` for current Kubernetes, Helm, and OS version requirements. + +| Requirement | Check | +|-------------|-------| +| Kubernetes | Per `docs/support-matrix.md` | +| Helm | Per `docs/support-matrix.md` | +| NVIDIA GPU Operator | Installed and running | +| NVIDIA NIM Operator | Installed and running | +| Default StorageClass | Configured (e.g. local-path-provisioner) | +| Disk space | ≥200 GB per node | +| NGC_API_KEY | Set in environment | + +Report all missing prerequisites together so the user can fix everything in one pass. + +If NGC_API_KEY is NOT_SET: this is the one thing we must ask the user for. + +If an existing Helm release is detected: warn "Existing RAG Helm release found. Proceeding will upgrade it." Continue unless user objects. + +## Phase 2: Route to Reference + +Auto-detect the GPU variant from cluster nodes (not the local machine): + +```bash +echo "=== GPU_LABELS ===" && kubectl get nodes -o json 2>/dev/null | grep -oE '"nvidia.com/gpu.product":\s*"[^"]*"' | sort -u || echo "NO_GPU_LABELS"; echo "=== MIG ===" && kubectl get nodes -o json 2>/dev/null | grep -oE '"nvidia.com/mig.strategy":\s*"[^"]*"' || echo "NO_MIG" +``` + +Determine variant from node GPU labels: + +Route based on detection: + +- **MIG enabled** → read and follow `helm-mig.md` +- **RTX PRO 6000** → read and follow `helm-standard.md` (use the RTX values.yaml variant described there) +- **Standard (everything else)** → read and follow `helm-standard.md` + +Ask the user only if the variant is genuinely ambiguous. Default to standard deployment. + +## Phase 3: Expected Timelines + +Set expectations with the user: + +| Scenario | Duration | +|----------|----------| +| First deployment | 60–70 min (NIM cache download ~40–50 min, NIMService init ~10–15 min, pod startup ~5–10 min) | +| Subsequent deployments | 10–15 min (model caches already populated) | + +Pods in `ContainerCreating` or `Init` state for extended periods is normal — models download in the background without progress indicators. + +## Phase 4: Verification + +After deployment completes, verify: + +```bash +echo "=== PODS ===" && kubectl get pods -n rag; echo "=== NIMCACHE ===" && kubectl get nimcache -n rag; echo "=== NIMSERVICE ===" && kubectl get nimservice -n rag +``` + +Wait for all pods to reach `Running` status. Poll every 60 seconds for up to 70 minutes (first deployment involves model downloads). Show progress. + +Once pods are running, port-forward and verify health: + +```bash +kubectl port-forward -n rag service/rag-server 8081:8081 --address 0.0.0.0 & kubectl port-forward -n rag service/rag-frontend 3000:3000 --address 0.0.0.0 & sleep 3 && curl -s http://localhost:8081/v1/health?check_dependencies=true 2>/dev/null || echo "RAG_NOT_READY" +``` + +## Phase 5: Uninstall + +If the user wants to tear down: + +```bash +helm uninstall rag -n rag +kubectl delete nimcache --all -n rag +kubectl delete pvc --all -n rag +``` + +## On Success + +Tell the user: +- "RAG Blueprint is running on Kubernetes. Access the UI at http://localhost:3000 (via port-forward)." +- "Ask me to configure features (VLM, query rewriting, guardrails, etc.)" +- "Ask me to shutdown when you're done." + +## On Error + +1. Check pod status and events: `kubectl describe pod -n rag` and `kubectl get events -n rag --sort-by='.lastTimestamp' | tail -20`. +2. Read pod logs: `kubectl logs -n rag --tail 50`. +3. Read `references/troubleshoot.md` to match against common issues (PVC pending, OOM, image pull failure, port conflict). +4. Apply the fix and retry. If the fix requires data deletion (PVCs, namespace), confirm with user first. + +## Source Documentation +- `docs/support-matrix.md` — Kubernetes/Helm version requirements, GPU compatibility +- `docs/deploy-helm.md` — standard Helm deployment from NGC +- `docs/deploy-helm-from-repo.md` — Helm deployment from local repo diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/library-full.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/library-full.md new file mode 100644 index 000000000..1a386cb19 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/library-full.md @@ -0,0 +1,43 @@ +# Library Mode (Full) + +## When to Use +- User wants programmatic Python access to RAG via `nvidia_rag` package +- User prefers code-level configuration over Docker-based servers +- User asks about library mode, Python client, or `NvidiaRAG`/`NvidiaRAGIngestor` + +## Restrictions +- Python 3.11+ (< 3.14) +- Docker still required for backend services (Milvus, NV-Ingest, Redis, optionally NIMs) +- Self-hosted NIMs require supported GPUs (see `docs/support-matrix.md`) + +## Process +1. Read `docs/python-client.md` for full API reference, configuration, and backend setup +2. Create virtual environment and install `nvidia-rag[all]` +3. Start backend services via Docker (Milvus, NV-Ingest + Redis, optionally NIMs) +4. Load config from `notebooks/config.yaml` using `NvidiaRAGConfig.from_yaml()` +5. Create `NvidiaRAGIngestor` and `NvidiaRAG` instances +6. Use `ingestor.create_collection()`, `ingestor.upload_documents()`, `rag.generate()`, `rag.search()` + +## Decision Table + +| Goal | Source Doc | Key Action | +|------|-----------|------------| +| Self-hosted (local GPUs) | `docs/python-client.md` | Start nims.yaml + set on-prem config | +| Cloud (NVIDIA-hosted) | `docs/python-client.md` | Skip nims.yaml, override server URLs in config | +| Custom prompts | `docs/python-client.md` | Pass `prompts=` to NvidiaRAG constructor | +| Summarization | `docs/python-client.md` | `generate_summary=True` in upload_documents | + +## Agent-Specific Notes +- Config file: `notebooks/config.yaml`; env file: `notebooks/.env_library` +- Docker login is interactive — tell user to run `docker login nvcr.io` themselves +- For cloud deployment: override `config.embeddings.server_url`, `config.llm.server_url`, etc. in code +- Config changes take effect immediately (no container restart needed, unlike Docker mode) +- Prompt customization via constructor: `NvidiaRAG(config=config, prompts="custom_prompts.yaml")` +- `upload_documents()` is async — returns `task_id` for status polling +- NV-Ingest cloud endpoints must be exported before starting NV-Ingest container + +## Notebooks +- `notebooks/rag_library_usage.ipynb` — complete walkthrough: setup, ingestion, querying, search, summaries + +## Source Documentation +- `docs/python-client.md` — full API reference, backend setup, configuration, cloud/self-hosted options diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/library-lite.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/library-lite.md new file mode 100644 index 000000000..bf7da9041 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/library-lite.md @@ -0,0 +1,37 @@ +# Library Mode (Lite / Containerless) + +## When to Use +- Quick prototyping with zero infrastructure (no Docker, no GPU) +- User wants the fastest path to try RAG +- CI/CD pipelines needing lightweight RAG testing + +## Restrictions +- No image/table/chart citations +- No document summarization +- Subject to NVIDIA API rate limits (cloud-hosted inference) +- Requires Python 3.11+ (< 3.14), internet access, and `NGC_API_KEY` + +## Process +1. Read `docs/python-client.md` for full library mode documentation +2. Create virtualenv and install: `pip install nvidia-rag[all]` +3. Ensure `NGC_API_KEY` is exported — maps to `NVIDIA_API_KEY` internally +4. Run the lite notebook: `jupyter lab notebooks/rag_library_lite_usage.ipynb` + +## Agent-Specific Notes +- `NVIDIA_API_KEY` (used by `nvidia_rag` package) must be set from `NGC_API_KEY`: `os.environ["NVIDIA_API_KEY"] = os.environ.get("NGC_API_KEY", "")` +- Lite config lives in `notebooks/config.yaml`; override `server_url` for embeddings to the NVIDIA API Catalog endpoint (see `docs/python-client.md` for current URL), and set LLM/ranking URLs to empty string for cloud defaults +- Milvus Lite runs embedded (no container), NV-Ingest runs as subprocess (no container) +- Also install `python-dotenv jupyterlab` for notebook support + +## When Not to Use +- Production workloads — use Docker or Kubernetes +- Large-scale ingestion — rate limits apply +- Need citations from images/tables/charts or document summarization + +## Notebooks +| Notebook | Description | +|----------|-------------| +| `notebooks/rag_library_lite_usage.ipynb` | End-to-end lite mode: collection creation, ingestion, querying, search | + +## Source Documentation +- `docs/python-client.md` -- full library mode documentation (lite and full) diff --git a/skill-source/.agents/skills/rag-blueprint/references/deploy/library.md b/skill-source/.agents/skills/rag-blueprint/references/deploy/library.md new file mode 100644 index 000000000..a5162be7a --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/deploy/library.md @@ -0,0 +1,54 @@ +# RAG Library Mode Setup + +## Determine Mode + +If routed here from the deploy workflow, the mode (full or lite) may already be decided. Use it. + +If invoked directly, auto-detect: + +```bash +echo "=== DOCKER ===" && docker --version 2>/dev/null || echo "NO_DOCKER"; echo "=== GPU ===" && nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || echo "NO_GPU"; echo "=== PYTHON ===" && python3 --version 2>/dev/null || echo "NO_PYTHON"; echo "=== PKG_MANAGER ===" && which uv 2>/dev/null && echo "UV_AVAILABLE" || (which pip3 2>/dev/null && echo "PIP_AVAILABLE" || echo "NO_PKG_MANAGER"); echo "=== VENV ===" && ls -d .venv/ venv/ nvidia-rag-env/ 2>/dev/null || echo "NO_EXISTING_VENV"; echo "=== INSTALLED ===" && pip3 show nvidia_rag 2>/dev/null | head -3 || echo "NOT_INSTALLED" +``` + +- Docker available → **full** (Python API + Docker backend services) +- No Docker or user explicitly says "lite" / "no docker" / "containerless" → **lite** + +Auto-route based on Docker availability. Only ask if both modes are equally valid. + +## Verify NGC_API_KEY + +Auto-check all locations: + +```bash +if [ -n "$NGC_API_KEY" ]; then echo "NGC_KEY_SET"; elif [ -n "$NVIDIA_API_KEY" ]; then echo "NVIDIA_KEY_SET"; else echo "NOT_SET"; fi +``` + +If NOT_SET: ask the user. Otherwise proceed silently. + +## Deploy + +Based on the mode: + +- **Full**: read and follow `library-full.md` +- **Lite**: read and follow `library-lite.md` + +## On Success + +Tell the user: +- Which mode was set up and how to start using it (notebook or Python script) +- "Ask me to configure features, change models, etc." +- "Ask me to shutdown backend services when done (if full mode)." + +## On Error + +1. Read the error output (pip install failure, import error, service connection error). +2. Read `references/troubleshoot.md` to match against common issues. +3. Common fixes to try: + - `pip install` failure → try `uv pip install` or check Python version ≥3.11. + - Import error → check if virtual environment is activated. + - Connection error to backend services → check Docker containers are running. +4. Retry the failed step after fixing. +5. If still failing, report the specific error to the user. + +## Source Documentation +- `docs/python-client.md` — Python library API, installation, full and lite mode setup diff --git a/skill-source/.agents/skills/rag-blueprint/references/shutdown.md b/skill-source/.agents/skills/rag-blueprint/references/shutdown.md new file mode 100644 index 000000000..7407b63d8 --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/shutdown.md @@ -0,0 +1,128 @@ +# RAG Shutdown + +Stopping containers and processes does not require confirmation. Deleting data (volumes, cache, images) does. + +## Step 1: Detect What Is Running + +Detect all deployment modes — Docker, K8s, and library: + +```bash +echo "=== DOCKER ===" && docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" 2>/dev/null || echo "NO_DOCKER"; echo "=== LIBRARY ===" && ps aux | grep -E "(nvidia_rag|uvicorn|jupyter)" | grep -v grep || echo "NO_LIBRARY_PROCESSES"; echo "=== K8S ===" && kubectl get pods -n rag 2>/dev/null | head -10 || echo "NO_K8S"; echo "=== HELM ===" && helm list -n rag 2>/dev/null | grep rag || echo "NO_HELM_RELEASE" +``` + +Based on what's detected, execute the appropriate shutdown path below. If multiple modes are active (e.g., Docker + library), stop all of them. + +## Step 2: Stop Services (Reverse Startup Order) + +Stop in this order — reverse of deployment. Only stop what is actually running (detected in Step 1). + +### 2a: Optional Services + +Stop these first if they are running: + +```bash +docker compose -f deploy/compose/docker-compose-nemo-guardrails.yaml down 2>/dev/null; docker compose -f deploy/compose/observability.yaml down 2>/dev/null +``` + +### 2b: Application Services + +```bash +docker compose -f deploy/compose/docker-compose-rag-server.yaml down; docker compose -f deploy/compose/docker-compose-ingestor-server.yaml down +``` + +### 2c: Vector DB + +```bash +docker compose -f deploy/compose/vectordb.yaml down +``` + +If using Elasticsearch instead of Milvus: +```bash +docker compose -f deploy/compose/vectordb.yaml --profile elasticsearch down +``` + +### 2d: NIMs (Self-Hosted Only) + +Only present if self-hosted deployment was used: + +```bash +docker compose -f deploy/compose/nims.yaml down +``` + +This stops ALL NIM containers (LLM, embedding, ranking, OCR, detection, and any profile-specific NIMs like VLM, audio, nemotron-parse). + +### 2e: Library Mode Processes + +If library mode is active (detected Python processes): + +```bash +pkill -f "nvidia_rag" 2>/dev/null; pkill -f "uvicorn.*rag" 2>/dev/null; docker compose -f deploy/compose/docker-compose-ingestor-server.yaml down 2>/dev/null; docker compose -f deploy/compose/vectordb.yaml down 2>/dev/null +``` + +### 2f: Kubernetes (Helm) Deployment + +If K8s deployment was detected, use the release name and namespace from `helm list` output in step 1: + +```bash +helm uninstall -n 2>/dev/null +``` + +To also clean up persistent data (only if user requests full cleanup): +```bash +kubectl delete nimcache --all -n 2>/dev/null; kubectl delete pvc --all -n 2>/dev/null +``` + +## Step 3: Verify Everything Stopped + +```bash +echo "=== REMAINING ===" && docker ps --format "table {{.Names}}\t{{.Status}}" 2>/dev/null; echo "=== K8S ===" && kubectl get pods -n rag 2>/dev/null | head -10 || echo "NOT_K8S"; helm list -n rag 2>/dev/null || true +``` + +If any RAG-related containers remain, force remove: +```bash +docker ps -a --format "{{.Names}}" | grep -E "(rag|milvus|nim|ingest|redis|nemo|grafana|prometheus|embedding|ranking|vlm|ocr|page-elements|graphic-elements|table-structure)" | xargs -r docker rm -f +``` + +If pods remain after `helm uninstall`, force delete: +```bash +kubectl delete pods --all -n rag --force --grace-period=0 2>/dev/null +``` + +## Step 4: Optional Cleanup + +Ask the user if they want to clean up data/volumes: + +- **Remove Docker volumes** (deletes ingested data, Milvus indices): + ```bash + docker volume prune -f + ``` + +- **Remove model cache** (frees 100-200 GB for self-hosted): + ```bash + rm -rf ~/.cache/model-cache/ + ``` + +- **Remove Docker images** (frees disk space): + ```bash + docker images | grep -E "nvcr.io/nvidia|milvusdb" | awk '{print $3}' | xargs -r docker rmi + ``` + +Only perform cleanup if the user explicitly requests it. + +## Quick One-Liner (All Docker Services) + +If the user wants a fast full teardown: + +```bash +cd "$(git rev-parse --show-toplevel)" && \ +docker compose -f deploy/compose/docker-compose-nemo-guardrails.yaml down 2>/dev/null; \ +docker compose -f deploy/compose/observability.yaml down 2>/dev/null; \ +docker compose -f deploy/compose/docker-compose-rag-server.yaml down 2>/dev/null; \ +docker compose -f deploy/compose/docker-compose-ingestor-server.yaml down 2>/dev/null; \ +docker compose -f deploy/compose/vectordb.yaml down 2>/dev/null; \ +docker compose -f deploy/compose/nims.yaml down 2>/dev/null; \ +echo "All RAG services stopped." +``` + +## Source Documentation +- `docs/troubleshooting.md` — if services won't stop or containers hang diff --git a/skill-source/.agents/skills/rag-blueprint/references/troubleshoot.md b/skill-source/.agents/skills/rag-blueprint/references/troubleshoot.md new file mode 100644 index 000000000..bf0308f8b --- /dev/null +++ b/skill-source/.agents/skills/rag-blueprint/references/troubleshoot.md @@ -0,0 +1,146 @@ +# RAG Troubleshooting + +## Auto-Triage: Run First + +Start with this diagnostic sweep: + +```bash +echo "=== CONTAINERS ===" && docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null | grep -E "(rag|milvus|nim|ingest|redis|etcd|minio)" | head -20; echo "=== HEALTH ===" && curl -s http://localhost:8081/v1/health?check_dependencies=true 2>/dev/null || echo "RAG_UNREACHABLE"; curl -s http://localhost:8082/v1/health?check_dependencies=true 2>/dev/null || echo "INGESTOR_UNREACHABLE"; echo "=== LOGS ===" && for svc in rag-server ingestor-server nim-llm-ms nemoretriever-embedding-ms nemoretriever-ranking-ms; do echo "--- $svc ---"; docker logs --tail 20 "$svc" 2>/dev/null | grep -iE "(error|fail|exception|timeout|oom)" || echo "OK"; done; echo "=== GPU ===" && nvidia-smi 2>/dev/null | head -20 || echo "NO_GPU"; echo "=== DISK ===" && df -h / | tail -1; echo "=== DOCKER_DISK ===" && docker system df 2>/dev/null; echo "=== K8S ===" && kubectl get pods -n rag 2>/dev/null | head -20 || echo "NOT_K8S" +``` + +Analyze all output, then diagnose and fix. If Auto-Triage doesn't reveal the cause, dig deeper into the specific failing service's logs (`docker logs --tail 100` or `kubectl logs -n rag --tail 100`). + +Confirm with the user before deleting data (volumes, collections, model cache), changing deployment mode, or modifying API keys. + +## Source Documentation for Detailed Diagnosis + +Read these docs to find specific issue descriptions, causes, and fixes: + +- `docs/troubleshooting.md` — primary reference: all common issues with detailed symptoms/fixes +- `docs/debugging.md` — Pipeline debugging: monitoring deployment, verifying endpoints, tracing requests +- `docs/service-port-gpu-reference.md` — Complete port/GPU mapping table for all services + +## Expected Deployment Times + +If user reports "deployment is taking too long," compare against these baselines: + +| Mode | First Run | Subsequent | +|------|-----------|------------| +| Docker (self-hosted) | 15--30 min (model downloads) | 2--5 min | +| Docker (NVIDIA-hosted) | 5--10 min (no model downloads) | 1--2 min | +| K8s/Helm | 60--70 min (NIM cache 40--50 min + init 10--15 min + pod startup 5--10 min) | 10--15 min | + +If deployment exceeds these times, check NIM container logs: `docker logs nim-llm-ms --tail 50` and model cache disk usage: `watch -n 10 'du -sh ~/.cache/model-cache/'`. + +## Service Health Endpoints + +Read `docs/service-port-gpu-reference.md` for the complete port/GPU mapping. Quick check: + +| Service | URL | Expected | +|---------|-----|----------| +| RAG Server | `http://localhost:8081/v1/health?check_dependencies=true` | `{"status":"healthy"}` | +| Ingestor | `http://localhost:8082/v1/health?check_dependencies=true` | `{"status":"healthy"}` | +| NV-Ingest | `http://localhost:7670/v1/health/ready` | 200 OK | +| Embedding NIM | `http://localhost:9080/v1/health/ready` | 200 OK | +| LLM NIM | `http://localhost:8999/v1/health/ready` | 200 OK | +| Ranking NIM | `http://localhost:1976/v1/health/ready` | 200 OK | +| Milvus | `http://localhost:9091/healthz` | 200 OK | + +## Kubernetes Monitoring Commands + +```bash +kubectl get nimcache -n rag +kubectl get pods -n rag +kubectl logs -f -n rag +kubectl get pvc -n rag +kubectl get events -n rag --sort-by='.lastTimestamp' +``` + +Pods in `ContainerCreating` or `Init` state during model download is expected. Use `kubectl get nimcache -n rag -w` to watch download progress. + +## Enable Debug Logging + +```bash +export LOGLEVEL=DEBUG +docker compose -f deploy/compose/docker-compose-ingestor-server.yaml up -d --no-deps ingestor-server +docker compose -f deploy/compose/docker-compose-rag-server.yaml up -d --no-deps rag-server +``` + +--- + +## Symptom-to-Fix Quick Index + +Match the symptom from Auto-Triage output, then read `docs/troubleshooting.md` for the detailed fix. For pipeline debugging steps, read `docs/debugging.md`. + +| Symptom | Category | Quick Fix | +|---------|----------|-----------| +| NIM container stuck at `(health: starting)` >30min | NIM Startup | Check GPU memory, NGC auth, disk space. First-run model downloads are slow — wait and monitor cache size. | +| Milvus unhealthy / search returns nothing | Milvus | Restart vectordb compose. Check etcd/MinIO. Port 19530 conflict. Corrupt data → `down -v` (destroys data). | +| Document upload fails / ingestor health check fails | NV-Ingest | Check Redis, OCR NIMs. Rate limit (429) → reduce batch vars. Large PDFs → reduce batch size. | +| Chat returns errors / /generate fails | RAG Server | Check LLM NIM health, embedding NIM, cloud API key. Verify `APP_LLM_MODELNAME` matches deployed NIM. | +| DNS resolution failed for `:` | Networking | Service container not running. Check `docker ps`, restart missing service. | +| Port already in use | Networking | `lsof -i :` to find conflicting process. See port table above. | +| GPU out of memory / `torch.OutOfMemoryError` | GPU | Kill other GPU processes, use `--profile rag` for fewer NIMs, or set correct `NIM_MODEL_PROFILE`. | +| `nvidia-container-cli: unknown device` | GPU | GPU ID exceeds available GPUs. Run `nvidia-smi -L`, adjust `*_GPU_ID` vars to valid IDs. | +| Disk full / insufficient space | Disk | `docker system prune -f`, remove unused images, check model cache size. | +| `no configuration file provided: not found` | Docker Compose | Run from the repo root directory. | +| `too many open files` | Docker Compose | Set `LimitNOFILE=65536` in containerd override, restart containerd. | +| PVC stuck in Pending | Helm | Create missing StorageClass or update PVC. | +| `ProvisioningFailed` access mode mismatch | Helm | Patch NIMCache to `ReadWriteOnce`. | +| Ingestor OOMKilled | Helm | Increase memory limits in values.yaml. Set `SUMMARY_MAX_PARALLELIZATION=1`. | +| Elasticsearch timeout during ingestion | Elasticsearch | Increase `ES_REQUEST_TIMEOUT` (default 600s). | +| Hallucination / out-of-context responses | Quality | Add missing-info handling to prompt in `prompt.yaml`. | +| Embedding dimensions mismatch | Models | Set `APP_EMBEDDINGS_DIMENSIONS` to match model output. Re-ingest. | +| Hybrid/dense search type mismatch | Search | Align `APP_VECTORSTORE_SEARCHTYPE` on ingestor and rag-server. Re-ingest. | +| Confidence threshold filtering all results | Search | Lower `RERANKER_SCORE_THRESHOLD` (range 0.0–1.0, default 0.0). | +| OCR not starting / connection errors | OCR | Check GPU memory, NGC auth. Verify `OCR_GRPC_ENDPOINT`/`OCR_HTTP_ENDPOINT` match running service. | +| NVIDIA API credits exhausted | Cloud | Contact NVIDIA representative for additional credits. | +| Image-only PDFs not ingesting | Ingestion | Enable `APP_NVINGEST_EXTRACTINFOGRAPHICS`. Consider image captioning. | + +--- + +## Troubleshooting Checklists + +### Ingestion Checklist +- [ ] All required containers running (ingestor-server, nv-ingest-ms-runtime, milvus, redis) +- [ ] Vector database accessible (`curl http://localhost:9091/healthz`) +- [ ] Embedding service healthy (`curl http://localhost:9080/v1/health/ready`) +- [ ] File format supported and size <= 400 MB +- [ ] Sufficient disk space (`df -h /`) +- [ ] GPU resources available (`nvidia-smi`) + +### Retrieval Checklist +- [ ] RAG server running and healthy +- [ ] LLM service accessible (`curl http://localhost:8999/v1/health/ready`) +- [ ] Vector database contains data (collection exists with documents) +- [ ] Collection name is correct +- [ ] Query format is valid + +### Quality Checklist +- [ ] Reranker is enabled and healthy +- [ ] Top-K values are appropriate +- [ ] Collection has sufficient relevant data +- [ ] Query rewriting configured correctly +- [ ] Prompt template appropriate for use case + +--- + +## Full Reset + +Destroys all data (volumes, images, caches). Confirm with the user before running. + +If nothing else works and the user confirms: + +```bash +cd "$(git rev-parse --show-toplevel)" +docker compose -f deploy/compose/docker-compose-nemo-guardrails.yaml down 2>/dev/null +docker compose -f deploy/compose/observability.yaml down 2>/dev/null +docker compose -f deploy/compose/docker-compose-rag-server.yaml down 2>/dev/null +docker compose -f deploy/compose/docker-compose-ingestor-server.yaml down 2>/dev/null +docker compose -f deploy/compose/vectordb.yaml down -v 2>/dev/null +docker compose -f deploy/compose/nims.yaml down 2>/dev/null + +docker system prune -af --volumes +``` + +Then deploy fresh using the deploy workflow. diff --git a/skill-source/README.md b/skill-source/README.md new file mode 100644 index 000000000..f5a787677 --- /dev/null +++ b/skill-source/README.md @@ -0,0 +1,111 @@ +# RAG Blueprint Agent Skill + +A single agent skill that enables AI coding assistants (Claude Code, Cursor, Codex, etc.) to deploy, configure, troubleshoot, and manage the NVIDIA RAG Blueprint autonomously. + +## Installation + +```bash +npx skills add . +``` + +Select **rag-blueprint** — it includes all capabilities (deploy, configure, shutdown, troubleshoot) in one skill. + +## Architecture: Skills = Process, Docs = Truth + +``` +SKILL.md = ROUTER (intent detection, autonomy rules, configure routing table) +Reference files = WHAT/HOW (deployment workflows, feature playbooks, diagnostics) +docs/*.md = SOURCE OF TRUTH (never copied into skills) +notebooks/*.ipynb = RUNNABLE EXAMPLES (referenced from relevant skills) +``` + +The SKILL.md detects user intent and routes to the correct reference file. Reference files are concise playbooks that point to `docs/*.md` for detailed configuration — this prevents staleness from duplicated content. + +## Skill Structure + +``` +skill-source/.agents/skills/rag-blueprint/ + SKILL.md ← Single entry point (intent router) + references/ + deploy.md ← Deployment: env analysis, NGC key, routing + deploy/ + docker.md ← Docker Compose deployment workflow + docker-self-hosted.md ← Self-hosted NIMs (local GPU inference) + docker-nvidia-hosted.md ← Cloud NIMs (NVIDIA API endpoints) + docker-retrieval-only.md ← Search/retrieve only (no LLM) + helm.md ← Kubernetes / Helm deployment workflow + helm-standard.md ← Standard Helm chart deployment + helm-mig.md ← Multi-Instance GPU deployment + library.md ← Python library mode workflow + library-full.md ← Python API + Docker backend + library-lite.md ← Containerless (Milvus Lite + cloud APIs) + configure/ + vlm.md ← VLM, VLM embeddings, image captioning + guardrails.md ← NeMo Guardrails + query-and-conversation.md ← Query rewriting, decomposition, multi-turn + ingestion.md ← Text-only, audio, Nemotron Parse, OCR, batch CLI + search-and-retrieval.md ← Hybrid search, multi-collection, metadata, filters + models-and-infrastructure.md ← Model changes, vector DB, auth, API keys, profiles + reasoning-and-generation.md ← Reasoning, self-reflection, prompts, generation params + summarization.md ← Document summarization during ingestion + observability.md ← Tracing, Zipkin, Grafana, Prometheus + multimodal-query.md ← Image + text querying with VLM embeddings + data-catalog.md ← Collection/document metadata management + user-interface.md ← RAG UI settings and usage + api-reference.md ← REST API endpoints and schemas + evaluation.md ← RAGAS quality metrics + mcp.md ← MCP server & client tools + migration.md ← Version upgrade guide + notebooks.md ← Notebook environment and catalog + shutdown.md ← Stop and tear down services + troubleshoot.md ← Diagnose and fix common issues +``` + +## How It Works + +1. User says "deploy RAG" → SKILL.md routes to `references/deploy.md` → env analysis → routes to `deploy/docker.md`, `deploy/helm.md`, or `deploy/library.md` +2. User says "enable VLM" → SKILL.md routes to `references/configure/vlm.md` → reads `docs/vlm.md` for detailed steps +3. User says "RAG is broken" → SKILL.md routes to `references/troubleshoot.md` → auto-triage diagnostic sweep +4. User says "stop RAG" → SKILL.md routes to `references/shutdown.md` → detects and stops all services + +## Supported Deployment Modes + +Read `docs/support-matrix.md` for current hardware requirements per mode. + +| Mode | Docker Required | Description | +|------|-----------------|-------------| +| Docker (self-hosted) | Yes | Full on-prem with local NIM inference | +| Docker (NVIDIA-hosted) | Yes | Cloud APIs for model inference | +| Docker (retrieval-only) | Yes | No LLM, search/retrieve only | +| Helm / Kubernetes | No (K8s) | Production K8s with NIM Operator | +| Library (full) | Yes (backend) | Python API with Docker backend services | +| Library (lite) | No | Milvus Lite + cloud APIs, zero infrastructure | + +## NGC_API_KEY Handling + +Skills never expose the API key value to the LLM. The approach: + +1. Check if `NGC_API_KEY` is set: `[ -n "$NGC_API_KEY" ] && echo "SET" || echo "NOT_SET"` +2. If not set, ask the user to run `export NGC_API_KEY="nvapi-your-key"` in the terminal +3. For `docker login`, the user runs it themselves (the command expands the key) +4. As a fallback, offer to write a placeholder to `deploy/compose/.env` for the user to replace + +## Notebook Integration + +All 13 notebooks are referenced from relevant reference files: + +| Notebook | Referenced In | +|----------|--------------| +| `ingestion_api_usage.ipynb` | `references/configure/ingestion.md` | +| `retriever_api_usage.ipynb` | `references/configure/search-and-retrieval.md` | +| `image_input.ipynb` | `references/configure/vlm.md`, `references/configure/multimodal-query.md` | +| `summarization.ipynb` | `references/configure/summarization.md` | +| `evaluation_01_ragas.ipynb` | `references/configure/evaluation.md` | +| `evaluation_02_recall.ipynb` | `references/configure/evaluation.md` | +| `nb_metadata.ipynb` | `references/configure/search-and-retrieval.md` | +| `rag_library_usage.ipynb` | `references/deploy/library-full.md` | +| `rag_library_lite_usage.ipynb` | `references/deploy/library-lite.md` | +| `building_rag_vdb_operator.ipynb` | `references/configure/models-and-infrastructure.md` | +| `mcp_server_usage.ipynb` | `references/configure/mcp.md` | +| `nat_mcp_integration.ipynb` | `references/configure/mcp.md` | +| `launchable.ipynb` | `SKILL.md` | From 0d40efa4e4e64c5572459c4e2534a68d9aeb3fad Mon Sep 17 00:00:00 2001 From: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:55:39 +0530 Subject: [PATCH 35/52] doc: Fix broken links in notebooks and docs (#426) --- docs/nemoretriever-ocr.md | 4 +- docs/support-matrix.md | 3 +- notebooks/evaluation_01_ragas.ipynb | 1103 +++++++-------- notebooks/image_input.ipynb | 1980 +++++++++++++-------------- notebooks/launchable.ipynb | 6 +- notebooks/rag_event_ingest.ipynb | 1582 ++++++++++----------- 6 files changed, 2342 insertions(+), 2336 deletions(-) diff --git a/docs/nemoretriever-ocr.md b/docs/nemoretriever-ocr.md index d8ef4f3c5..1893da0aa 100644 --- a/docs/nemoretriever-ocr.md +++ b/docs/nemoretriever-ocr.md @@ -49,7 +49,7 @@ By default, the NVIDIA RAG Blueprint is configured to use NeMo Retriever OCR wit ### Hardware Requirements -For detailed hardware requirements and GPU support, refer to the [NeMo Retriever OCR Support Matrix](https://docs.nvidia.com/nim/ingestion/image-ocr/1.2.0/support-matrix.html). +For detailed hardware requirements and GPU support, refer to the [NeMo Retriever OCR Support Matrix](https://docs.nvidia.com/nim/ingestion/image-ocr/1.2.1/support-matrix.html). ### Docker Configuration @@ -83,8 +83,6 @@ Consider using Paddle OCR if you: ### Hardware Requirements -For detailed hardware requirements, refer to the [Paddle OCR Support Matrix](https://docs.nvidia.com/nim/ingestion/table-extraction/latest/support-matrix.html#supported-hardware). - ### Docker Configuration The Paddle OCR service configuration: diff --git a/docs/support-matrix.md b/docs/support-matrix.md index 6344822e1..07843a774 100644 --- a/docs/support-matrix.md +++ b/docs/support-matrix.md @@ -78,8 +78,7 @@ The following are requirements and recommendations for the individual components - **LLM NIM (llama-3.3-nemotron-super-49b-v1.5)** – Refer to the [Support Matrix](https://docs.nvidia.com/nim/large-language-models/latest/supported-models.html#llama-3-3-nemotron-super-49b-v1-5). - **Embedding NIM (Llama-3.2-NV-EmbedQA-1B-v2 )** – Refer to the [Support Matrix](https://docs.nvidia.com/nim/nemo-retriever/text-embedding/latest/support-matrix.html#llama-3-2-nv-embedqa-1b-v2). - **Reranking NIM (llama-3_2-nv-rerankqa-1b-v2 )**: Refer to the [Support Matrix](https://docs.nvidia.com/nim/nemo-retriever/text-reranking/latest/support-matrix.html#llama-3-2-nv-rerankqa-1b-v2). -- **NeMo Retriever OCR (Default)**: Refer to the [Support Matrix](https://docs.nvidia.com/nim/ingestion/image-ocr/1.2.0/support-matrix.html). -- **NVIDIA NIM for Image OCR (baidu/paddleocr - Legacy)**: Refer to the [Support Matrix](https://docs.nvidia.com/nim/ingestion/table-extraction/latest/support-matrix.html#supported-hardware). +- **NeMo Retriever OCR (Default)**: Refer to the [Support Matrix](https://docs.nvidia.com/nim/ingestion/image-ocr/1.2.1/support-matrix.html). - **NVIDIA NIMs for Object Detection**: - NeMo Retriever Page Elements v3 [Support Matrix](https://docs.nvidia.com/nim/ingestion/object-detection/latest/support-matrix.html#nemo-retriever-page-elements-v3) - NeMo Retriever Graphic Elements v1 [Support Matrix](https://docs.nvidia.com/nim/ingestion/object-detection/latest/support-matrix.html#nemo-retriever-graphic-elements-v1) diff --git a/notebooks/evaluation_01_ragas.ipynb b/notebooks/evaluation_01_ragas.ipynb index 4b50aceb0..d3f12933d 100644 --- a/notebooks/evaluation_01_ragas.ipynb +++ b/notebooks/evaluation_01_ragas.ipynb @@ -1,550 +1,559 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "f1de2541", - "metadata": {}, - "source": [ - "# Evaluate Your RAG Pipeline with Ragas: Answer Accuracy, Context Relevancy, and Groundedness\n", - "\n", - "In this notebook, we will evaluate our RAG system using three key metrics with the [Ragas](https://docs.ragas.io/en/stable/) library. \n", - "\n", - "Ragas provides a set of metrics that you can use to evaluate the performance of your LLM application. These metrics are designed to help you objectively measure the performance of your application. \n", - "\n", - "## Evaluation Metrics\n", - "\n", - "In this notebook, we will use the following three metrics, introduced to Ragas by NVIDIA:\n", - "- **Answer Accuracy** – Measures the agreement between a model’s response and a reference ground truth for a given question.\n", - "- **Context Relevancy** – Evaluates whether the retrieved contexts (chunks or passages) are pertinent to the user input. \n", - "- **Response Groundedness** – Measures how well a response is supported or \"grounded\" by the retrieved contexts. It assesses whether each claim in the response can be found, either wholly or partially, in the provided contexts.\n", - "\n", - "## Prerequisites\n", - "\n", - "This notebook assumes you are familiar with the RAG system and you have both `rag-server` and `ingestor-server` up and running. If you have not done that, you can refer to [Get Started](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/deploy-docker-self-hosted.md) to start the RAG server.\n", - "\n", - "## 1. Download Evaluation Documents\n", - "\n", - "First, let's download the FinanceBench dataset to evaluate our RAG system. This dataset includes PDF files with information and reports about publicly traded companies, as well as ground truth question and answer pairs.\n", - "\n", - "We'll clone the repository into our data directory in a subdirectory called `financebench`. The PDFs can be found in the `pdfs` subdirectory.\n" - ] + "cells": [ + { + "cell_type": "markdown", + "id": "f1de2541", + "metadata": {}, + "source": [ + "# Evaluate Your RAG Pipeline with Ragas: Answer Accuracy, Context Relevancy, and Groundedness\n", + "\n", + "In this notebook, we will evaluate our RAG system using three key metrics with the [Ragas](https://docs.ragas.io/en/stable/) library. \n", + "\n", + "Ragas provides a set of metrics that you can use to evaluate the performance of your LLM application. These metrics are designed to help you objectively measure the performance of your application. \n", + "\n", + "## Evaluation Metrics\n", + "\n", + "In this notebook, we will use the following three metrics, introduced to Ragas by NVIDIA:\n", + "- **Answer Accuracy** – Measures the agreement between a model’s response and a reference ground truth for a given question.\n", + "- **Context Relevancy** – Evaluates whether the retrieved contexts (chunks or passages) are pertinent to the user input. \n", + "- **Response Groundedness** – Measures how well a response is supported or \"grounded\" by the retrieved contexts. It assesses whether each claim in the response can be found, either wholly or partially, in the provided contexts.\n", + "\n", + "## Prerequisites\n", + "\n", + "This notebook assumes you are familiar with the RAG system and you have both `rag-server` and `ingestor-server` up and running. If you have not done that, you can refer to [Get Started](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/deploy-docker-self-hosted.md) to start the RAG server.\n", + "\n", + "## 1. Download Evaluation Documents\n", + "\n", + "First, let's download the FinanceBench dataset to evaluate our RAG system. This dataset includes PDF files with information and reports about publicly traded companies, as well as ground truth question and answer pairs.\n", + "\n", + "We'll clone the repository into our data directory in a subdirectory called `financebench`. The PDFs can be found in the `pdfs` subdirectory.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d87b89d8", + "metadata": {}, + "outputs": [], + "source": [ + "! git clone https://github.com/patronus-ai/financebench.git ../data/financebench" + ] + }, + { + "cell_type": "markdown", + "id": "702a5f07", + "metadata": {}, + "source": [ + "## 2. Ingest Evaluation Documents\n", + "\n", + "For evaluation, we will use the FinanceBench dataset. In the data directory, we have the PDF files for the FinanceBench dataset, as well as the `financebench_open_source.jsonl` file, which includes ground truth question and answer pairs. \n", + "\n", + "Let's start by creating a collection called `financebench` and upload the relevant documents.\n", + "\n", + "This process is similar to the `ingestion_api_usage` notebook. First, we'll install the required packages and set up our API connections." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0b88ef79", + "metadata": {}, + "outputs": [], + "source": [ + "# Installing required Python packages\n", + "! pip install aiohttp langchain-nvidia-ai-endpoints ragas httpx" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11bcb3fe", + "metadata": {}, + "outputs": [], + "source": [ + "import aiohttp\n", + "import os\n", + "import json\n", + "import glob\n", + "import httpx" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fa7a4226", + "metadata": {}, + "outputs": [], + "source": [ + "IPADDRESS = \"ingestor-server\" if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\" else \"localhost\" # Replace this with the correct IP address\n", + "INGESTOR_SERVER_PORT = \"8082\"\n", + "INGESTOR_BASE_URL = f\"http://{IPADDRESS}:{INGESTOR_SERVER_PORT}\" # Replace with your server URL\n", + "\n", + "async def print_response(response):\n", + " \"\"\"Helper to print API response.\"\"\"\n", + " try:\n", + " response_json = await response.json()\n", + " print(json.dumps(response_json, indent=2))\n", + " except aiohttp.ClientResponseError:\n", + " print(await response.text())\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47cc6774", + "metadata": {}, + "outputs": [], + "source": [ + "async def create_collection(\n", + " collection_name: str = None,\n", + " metadata_schema: list = []\n", + "):\n", + " \"\"\"Create a new collection in the vector database.\"\"\"\n", + " data = {\n", + " \"collection_name\": collection_name,\n", + " \"metadata_schema\": metadata_schema\n", + " }\n", + "\n", + " HEADERS = {\"Content-Type\": \"application/json\"}\n", + "\n", + " async with aiohttp.ClientSession() as session:\n", + " try:\n", + " async with session.post(f\"{INGESTOR_BASE_URL}/v1/collection\", json=data, headers=HEADERS) as response:\n", + " await print_response(response)\n", + " except aiohttp.ClientError as e:\n", + " return 500, {\"error\": str(e)}\n", + "\n", + "# Create the financebench collection\n", + "await create_collection(\n", + " collection_name=\"financebench\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92418e23", + "metadata": {}, + "outputs": [], + "source": [ + "# Get all PDF files from the financebench directory\n", + "FILEPATHS = glob.glob(os.path.join(\"../data/financebench/pdfs\", \"*.pdf\"))\n", + "\n", + "async def upload_documents(collection_name: str = \"\"):\n", + " \"\"\"Upload documents to the specified collection.\"\"\"\n", + " data = {\n", + " \"collection_name\": collection_name,\n", + " \"blocking\": False, # If True, upload is blocking; else async. Status API not needed when blocking\n", + " \"split_options\": {\n", + " \"chunk_size\": 512,\n", + " \"chunk_overlap\": 150\n", + " },\n", + " \"generate_summary\": False # Set to True to optionally generate summaries for all documents after ingestion\n", + " }\n", + "\n", + " form_data = aiohttp.FormData()\n", + " \n", + " # Add all PDF files to the form data\n", + " for file_path in FILEPATHS:\n", + " form_data.add_field(\"documents\", open(file_path, \"rb\"), filename=os.path.basename(file_path), content_type=\"application/pdf\")\n", + "\n", + " form_data.add_field(\"data\", json.dumps(data), content_type=\"application/json\")\n", + "\n", + " async with aiohttp.ClientSession() as session:\n", + " try:\n", + " async with session.post(f\"{INGESTOR_BASE_URL}/v1/documents\", data=form_data) as response: # Replace with session.patch for reingesting\n", + " await print_response(response)\n", + " # Return the response JSON for task_id extraction\n", + " response_json = await response.json()\n", + " return response_json\n", + " except aiohttp.ClientError as e:\n", + " print(f\"Error uploading documents: {e}\")\n", + " return None\n", + "\n", + "# Store the response and extract task_id\n", + "upload_response = await upload_documents(collection_name=\"financebench\")\n", + "task_id = upload_response.get(\"task_id\") if upload_response else None\n", + "print(f\"Extracted task_id: {task_id}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "798b7771", + "metadata": {}, + "source": [ + "**⚠️ Note**: During the document ingestion process, two files (`INTEL_2023_8K_dated-2023-08-16.pdf` and `INTEL_2023_8K_dated-2023-02-10.pdf`) may fail to process due to formatting issues. This is expected and can be safely ignored, as it will not affect the evaluation methodology or results. The remaining documents in the dataset are sufficient for comprehensive evaluation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82b3e199", + "metadata": {}, + "outputs": [], + "source": [ + "# This might take a few minutes to complete depending on the number of documents uploaded\n", + "async def get_task_status(\n", + " task_id: str\n", + "):\n", + "\n", + " params = {\n", + " \"task_id\": task_id,\n", + " }\n", + "\n", + " HEADERS = {\"Content-Type\": \"application/json\"}\n", + "\n", + " async with aiohttp.ClientSession() as session:\n", + " try:\n", + " async with session.get(f\"{INGESTOR_BASE_URL}/v1/status\", params=params, headers=HEADERS) as response:\n", + " await print_response(response)\n", + " except aiohttp.ClientError as e:\n", + " return 500, {\"error\": str(e)}\n", + "\n", + "# Use the extracted task_id from the upload_documents response\n", + "if task_id:\n", + " await get_task_status(task_id=task_id)\n", + "else:\n", + " print(\"No task_id available. Please run the upload_documents cell first.\")" + ] + }, + { + "cell_type": "markdown", + "id": "8bb5edff", + "metadata": {}, + "source": [ + "## 3. Create Dataset for Ragas Evaluation\n", + "\n", + "In `data/financebench/data`, there is a file called `financebench_open_source.jsonl`. This file contains questions about the PDFs, as well as corresponding ground truth answers.\n", + "\n", + "For each ground-truth question and answer pair, we will:\n", + "1. Generate an answer from our RAG system\n", + "2. Retrieve the relevant document contexts\n", + "3. Create a dataset suitable for Ragas evaluation\n", + "\n", + "The answer and context retrieval from the RAG system is similar to the `retriever_api_usage` notebook.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b96c09f1", + "metadata": {}, + "outputs": [], + "source": [ + "IPADDRESS = \"rag-server\" if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\" else \"localhost\" #Replace this with the correct IP address\n", + "RAG_SERVER_PORT = \"8081\"\n", + "RAG_BASE_URL = f\"http://{IPADDRESS}:{RAG_SERVER_PORT}\" # Replace with your server URL\n", + "\n", + "generate_url = f\"{RAG_BASE_URL}/v1/generate\"\n", + "\n", + "async def generate_answer(payload):\n", + " \"\"\"Generate an answer using the RAG server.\"\"\"\n", + " rag_response = \"\"\n", + " citations = []\n", + " is_first_token = True\n", + "\n", + " async with httpx.AsyncClient(timeout=300.0) as client:\n", + " try:\n", + " async with client.stream(\"POST\", url=generate_url, json=payload) as response:\n", + " # Raise an exception for bad status codes like 4xx or 5xx\n", + " response.raise_for_status()\n", + "\n", + " # iterate over the response lines\n", + " async for line in response.aiter_lines():\n", + " if line.startswith(\"data: \"):\n", + " json_str = line[6:].strip()\n", + " if not json_str:\n", + " continue\n", + "\n", + " try:\n", + " data = json.loads(json_str)\n", + "\n", + " # --- Extract the response from the RAG server ---\n", + " message = data.get(\"choices\", [{}])[0].get(\"message\", {}).get(\"content\", \"\")\n", + " if message:\n", + " rag_response += message\n", + "\n", + " # --- Extract the citations from the RAG server ---\n", + " if is_first_token and data.get(\"citations\"):\n", + " for result in data.get(\"citations\", {}).get(\"results\", []):\n", + " description = result.get(\"metadata\", {}).get(\"description\")\n", + " if description:\n", + " citations.append(description)\n", + " is_first_token = False\n", + "\n", + " finish_reason = data.get(\"choices\", [{}])[0].get(\"finish_reason\")\n", + " if finish_reason == \"stop\":\n", + " return rag_response, citations\n", + "\n", + " except json.JSONDecodeError:\n", + " print(f\"Skipping malformed JSON line: {json_str}\")\n", + " continue\n", + " \n", + " except httpx.HTTPStatusError as e:\n", + " print(f\"HTTP error occurred: {e.response.status_code} - {e.response.text}\")\n", + " except httpx.RequestError as e:\n", + " print(f\"An error occurred while requesting {e.request.url!r}: {e}\")\n", + " except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + "\n", + " return rag_response, citations\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "805c5744", + "metadata": {}, + "outputs": [], + "source": [ + "# Load the question and ground-truth answer pairs from the FinanceBench dataset\n", + "with open('../data/financebench/data/financebench_open_source.jsonl', 'r') as file:\n", + " gt_qa_pairs = [json.loads(line) for line in file]\n", + "\n", + "print(f\"Loaded {len(gt_qa_pairs)} question-answer pairs from FinanceBench dataset\")\n", + "\n", + "dataset = []\n", + "\n", + "# For the purposes of keeping this demo brief, we will only evaluate on 50 questions. \n", + "# You can increase this to the full dataset for more comprehensive results.\n", + "n = 50 \n", + "print(f\"Evaluating on {n} questions...\")\n", + "\n", + "for idx, qa_pair in enumerate(gt_qa_pairs[:n]):\n", + " question = qa_pair['question']\n", + " \n", + " print(f\"Processing question {idx + 1}/{n}: {question[:100]}...\")\n", + "\n", + " generate_payload = {\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": question\n", + " }\n", + " ],\n", + " \"use_knowledge_base\": True,\n", + " \"reranker_top_k\": 2,\n", + " \"vdb_top_k\": 10,\n", + " \"vdb_endpoint\": \"http://milvus:19530\",\n", + " \"collection_names\": [\"financebench\"],\n", + " \"enable_reranker\": True,\n", + " \"enable_citations\": True,\n", + " \"stop\": [],\n", + " \"filter_expr\": ''\n", + " }\n", + " \n", + " rag_answer, citations = await generate_answer(generate_payload)\n", + "\n", + " dataset.append({\n", + " \"user_input\": question,\n", + " \"retrieved_contexts\": citations,\n", + " \"response\": rag_answer,\n", + " \"reference\": qa_pair['answer'],\n", + " })\n", + "\n", + "print(f\"Created dataset with {len(dataset)} entries for evaluation\")" + ] + }, + { + "cell_type": "markdown", + "id": "43e68742", + "metadata": {}, + "source": [ + "\n", + "## 4. Evaluate with Ragas\n", + "\n", + "In this example, we will use the NVIDIA hosted endpoint for our judge model. To use this endpoint, please provide your NVIDIA API Key below. \n", + "\n", + "### Rate Limiting Considerations\n", + "\n", + "When using the public endpoint for the Judge LLM, you will likely encounter rate limit errors. We can try to reduce the number of errors by adjusting the configuration, which we do below. \n", + "\n", + "Alternatively, you can use self-hosted NIM Microservices endpoints to avoid these errors altogether. If you're using a self-hosted NIM, you do not need to provide your API Key.\n", + "\n", + "### Getting Your NVIDIA API Key\n", + "\n", + "To generate an API Key:\n", + "1. Go to [build.nvidia.com](https://build.nvidia.com/)\n", + "2. Click the green \"Get API Key\" button in the top right corner\n", + "3. Paste your key below to save it as an environment variable\n", + "\n", + "### Self-Hosted Option\n", + "\n", + "To deploy the Judge LLM as a NIM on your own infrastructure, follow the instructions [here](https://build.nvidia.com/openai/gpt-oss-120b/deploy).\n" + ] + }, + { + "cell_type": "markdown", + "id": "32df51d0", + "metadata": {}, + "source": [ + "Note: Mixtral 8x22b is the preferred model if you have required compute available. You can deploy it following steps [here](https://build.nvidia.com/mistralai/mixtral-8x22b-instruct/deploy)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31df3819", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from getpass import getpass\n", + "# del os.environ['NVIDIA_API_KEY'] ## delete key and reset if needed\n", + "if os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", + " print(\"Valid NVIDIA_API_KEY already in environment. Delete to reset\")\n", + "else:\n", + " candidate_api_key = getpass(\"NVAPI Key (starts with nvapi-): \")\n", + " assert candidate_api_key.startswith(\"nvapi-\"), (\n", + " f\"{candidate_api_key[:5]}... is not a valid key\"\n", + " )\n", + " os.environ[\"NVIDIA_API_KEY\"] = candidate_api_key" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78fb75fe", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# Note: Models on build.nvidia.com are rate limited.\n", + "# To avoid rate-limit issues, either deploy the judge model locally (self-hosted NIM)\n", + "# or use any OpenAI-compatible LLM as the judge for evaluation.\n", + "from langchain_nvidia_ai_endpoints.chat_models import ChatNVIDIA\n", + "\n", + "# Initialize the judge LLM for evaluation\n", + "# You can use any other model by creating a ChatNVIDIA object with a different model id\n", + "llm = ChatNVIDIA(model=\"openai/gpt-oss-120b\") # For using NVIDIA hosted endpoint\n", + "# llm = ChatNVIDIA(model=\"openai/gpt-oss-120b\", base_url=\"http://0.0.0.0:8000/v1\") # If using self-hosted NIM" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "928a3c8a", + "metadata": {}, + "outputs": [], + "source": [ + "# Create the evaluation dataset from our collected data\n", + "from ragas import EvaluationDataset\n", + "\n", + "evaluation_dataset = EvaluationDataset.from_list(dataset)\n", + "print(f\"Created evaluation dataset with {len(evaluation_dataset)} samples\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3ec24f4", + "metadata": {}, + "outputs": [], + "source": [ + "# Import the required metrics and evaluation components\n", + "from ragas.metrics import AnswerAccuracy, ContextRelevance, ResponseGroundedness\n", + "from ragas import evaluate\n", + "from ragas.llms import LangchainLLMWrapper\n", + "\n", + "# Wrap the LLM for use with Ragas\n", + "evaluator_llm = LangchainLLMWrapper(llm)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f2f4245", + "metadata": {}, + "outputs": [], + "source": [ + "from ragas.run_config import RunConfig\n", + "\n", + "custom_config = RunConfig(max_workers=1, max_wait=120)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a3571af", + "metadata": {}, + "outputs": [], + "source": [ + "# Run the evaluation with our three metrics\n", + "print(\"Starting Ragas evaluation...\")\n", + "print(\"This may take several minutes depending on the dataset size.\")\n", + "\n", + "results = evaluate(\n", + " dataset=evaluation_dataset,\n", + " metrics=[AnswerAccuracy(), ContextRelevance(), ResponseGroundedness()],\n", + " llm=evaluator_llm, \n", + " run_config=custom_config\n", + ")\n", + "\n", + "print(\"Evaluation completed!\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "bac9dde6", + "metadata": {}, + "source": [ + "## 5. Analyze Results\n", + "\n", + "Finally, let's examine our evaluation results. We'll look at both the overall metrics and individual sample performance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c90647f", + "metadata": {}, + "outputs": [], + "source": [ + "results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2da683a1", + "metadata": {}, + "outputs": [], + "source": [ + "# Convert results to pandas DataFrame for detailed analysis of individual queries\n", + "results_df = results.to_pandas()\n", + "\n", + "import pandas as pd\n", + "\n", + "# 1. Set the option to display ALL columns, preventing the '...'\n", + "pd.set_option('display.max_columns', None)\n", + "\n", + "# 2. To prevent long text in cells from being cut off, you can set the column width\n", + "pd.set_option('display.max_colwidth', 80)\n", + "\n", + "results_df.head()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "evaluate", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } }, - { - "cell_type": "code", - "execution_count": null, - "id": "d87b89d8", - "metadata": {}, - "outputs": [], - "source": [ - "! git clone https://github.com/patronus-ai/financebench.git ../data/financebench" - ] - }, - { - "cell_type": "markdown", - "id": "702a5f07", - "metadata": {}, - "source": [ - "## 2. Ingest Evaluation Documents\n", - "\n", - "For evaluation, we will use the FinanceBench dataset. In the data directory, we have the PDF files for the FinanceBench dataset, as well as the `financebench_open_source.jsonl` file, which includes ground truth question and answer pairs. \n", - "\n", - "Let's start by creating a collection called `financebench` and upload the relevant documents.\n", - "\n", - "This process is similar to the `ingestion_api_usage` notebook. First, we'll install the required packages and set up our API connections." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0b88ef79", - "metadata": {}, - "outputs": [], - "source": [ - "# Installing required Python packages\n", - "! pip install aiohttp langchain-nvidia-ai-endpoints ragas httpx" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "11bcb3fe", - "metadata": {}, - "outputs": [], - "source": [ - "import aiohttp\n", - "import os\n", - "import json\n", - "import glob\n", - "import httpx" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fa7a4226", - "metadata": {}, - "outputs": [], - "source": [ - "IPADDRESS = \"ingestor-server\" if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\" else \"localhost\" # Replace this with the correct IP address\n", - "INGESTOR_SERVER_PORT = \"8082\"\n", - "INGESTOR_BASE_URL = f\"http://{IPADDRESS}:{INGESTOR_SERVER_PORT}\" # Replace with your server URL\n", - "\n", - "async def print_response(response):\n", - " \"\"\"Helper to print API response.\"\"\"\n", - " try:\n", - " response_json = await response.json()\n", - " print(json.dumps(response_json, indent=2))\n", - " except aiohttp.ClientResponseError:\n", - " print(await response.text())\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "47cc6774", - "metadata": {}, - "outputs": [], - "source": [ - "async def create_collection(\n", - " collection_name: str = None,\n", - " metadata_schema: list = []\n", - "):\n", - " \"\"\"Create a new collection in the vector database.\"\"\"\n", - " data = {\n", - " \"collection_name\": collection_name,\n", - " \"metadata_schema\": metadata_schema\n", - " }\n", - "\n", - " HEADERS = {\"Content-Type\": \"application/json\"}\n", - "\n", - " async with aiohttp.ClientSession() as session:\n", - " try:\n", - " async with session.post(f\"{INGESTOR_BASE_URL}/v1/collection\", json=data, headers=HEADERS) as response:\n", - " await print_response(response)\n", - " except aiohttp.ClientError as e:\n", - " return 500, {\"error\": str(e)}\n", - "\n", - "# Create the financebench collection\n", - "await create_collection(\n", - " collection_name=\"financebench\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "92418e23", - "metadata": {}, - "outputs": [], - "source": [ - "# Get all PDF files from the financebench directory\n", - "FILEPATHS = glob.glob(os.path.join(\"../data/financebench/pdfs\", \"*.pdf\"))\n", - "\n", - "async def upload_documents(collection_name: str = \"\"):\n", - " \"\"\"Upload documents to the specified collection.\"\"\"\n", - " data = {\n", - " \"collection_name\": collection_name,\n", - " \"blocking\": False, # If True, upload is blocking; else async. Status API not needed when blocking\n", - " \"split_options\": {\n", - " \"chunk_size\": 512,\n", - " \"chunk_overlap\": 150\n", - " },\n", - " \"generate_summary\": False # Set to True to optionally generate summaries for all documents after ingestion\n", - " }\n", - "\n", - " form_data = aiohttp.FormData()\n", - " \n", - " # Add all PDF files to the form data\n", - " for file_path in FILEPATHS:\n", - " form_data.add_field(\"documents\", open(file_path, \"rb\"), filename=os.path.basename(file_path), content_type=\"application/pdf\")\n", - "\n", - " form_data.add_field(\"data\", json.dumps(data), content_type=\"application/json\")\n", - "\n", - " async with aiohttp.ClientSession() as session:\n", - " try:\n", - " async with session.post(f\"{INGESTOR_BASE_URL}/v1/documents\", data=form_data) as response: # Replace with session.patch for reingesting\n", - " await print_response(response)\n", - " # Return the response JSON for task_id extraction\n", - " response_json = await response.json()\n", - " return response_json\n", - " except aiohttp.ClientError as e:\n", - " print(f\"Error uploading documents: {e}\")\n", - " return None\n", - "\n", - "# Store the response and extract task_id\n", - "upload_response = await upload_documents(collection_name=\"financebench\")\n", - "task_id = upload_response.get(\"task_id\") if upload_response else None\n", - "print(f\"Extracted task_id: {task_id}\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "798b7771", - "metadata": {}, - "source": [ - "**⚠️ Note**: During the document ingestion process, two files (`INTEL_2023_8K_dated-2023-08-16.pdf` and `INTEL_2023_8K_dated-2023-02-10.pdf`) may fail to process due to formatting issues. This is expected and can be safely ignored, as it will not affect the evaluation methodology or results. The remaining documents in the dataset are sufficient for comprehensive evaluation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "82b3e199", - "metadata": {}, - "outputs": [], - "source": [ - "# This might take a few minutes to complete depending on the number of documents uploaded\n", - "async def get_task_status(\n", - " task_id: str\n", - "):\n", - "\n", - " params = {\n", - " \"task_id\": task_id,\n", - " }\n", - "\n", - " HEADERS = {\"Content-Type\": \"application/json\"}\n", - "\n", - " async with aiohttp.ClientSession() as session:\n", - " try:\n", - " async with session.get(f\"{INGESTOR_BASE_URL}/v1/status\", params=params, headers=HEADERS) as response:\n", - " await print_response(response)\n", - " except aiohttp.ClientError as e:\n", - " return 500, {\"error\": str(e)}\n", - "\n", - "# Use the extracted task_id from the upload_documents response\n", - "if task_id:\n", - " await get_task_status(task_id=task_id)\n", - "else:\n", - " print(\"No task_id available. Please run the upload_documents cell first.\")" - ] - }, - { - "cell_type": "markdown", - "id": "8bb5edff", - "metadata": {}, - "source": [ - "## 3. Create Dataset for Ragas Evaluation\n", - "\n", - "In `data/financebench/data`, there is a file called `financebench_open_source.jsonl`. This file contains questions about the PDFs, as well as corresponding ground truth answers.\n", - "\n", - "For each ground-truth question and answer pair, we will:\n", - "1. Generate an answer from our RAG system\n", - "2. Retrieve the relevant document contexts\n", - "3. Create a dataset suitable for Ragas evaluation\n", - "\n", - "The answer and context retrieval from the RAG system is similar to the `retriever_api_usage` notebook.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b96c09f1", - "metadata": {}, - "outputs": [], - "source": [ - "IPADDRESS = \"rag-server\" if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\" else \"localhost\" #Replace this with the correct IP address\n", - "RAG_SERVER_PORT = \"8081\"\n", - "RAG_BASE_URL = f\"http://{IPADDRESS}:{RAG_SERVER_PORT}\" # Replace with your server URL\n", - "\n", - "generate_url = f\"{RAG_BASE_URL}/v1/generate\"\n", - "\n", - "async def generate_answer(payload):\n", - " \"\"\"Generate an answer using the RAG server.\"\"\"\n", - " rag_response = \"\"\n", - " citations = []\n", - " is_first_token = True\n", - "\n", - " async with httpx.AsyncClient(timeout=300.0) as client:\n", - " try:\n", - " async with client.stream(\"POST\", url=generate_url, json=payload) as response:\n", - " # Raise an exception for bad status codes like 4xx or 5xx\n", - " response.raise_for_status()\n", - "\n", - " # iterate over the response lines\n", - " async for line in response.aiter_lines():\n", - " if line.startswith(\"data: \"):\n", - " json_str = line[6:].strip()\n", - " if not json_str:\n", - " continue\n", - "\n", - " try:\n", - " data = json.loads(json_str)\n", - "\n", - " # --- Extract the response from the RAG server ---\n", - " message = data.get(\"choices\", [{}])[0].get(\"message\", {}).get(\"content\", \"\")\n", - " if message:\n", - " rag_response += message\n", - "\n", - " # --- Extract the citations from the RAG server ---\n", - " if is_first_token and data.get(\"citations\"):\n", - " for result in data.get(\"citations\", {}).get(\"results\", []):\n", - " description = result.get(\"metadata\", {}).get(\"description\")\n", - " if description:\n", - " citations.append(description)\n", - " is_first_token = False\n", - "\n", - " finish_reason = data.get(\"choices\", [{}])[0].get(\"finish_reason\")\n", - " if finish_reason == \"stop\":\n", - " return rag_response, citations\n", - "\n", - " except json.JSONDecodeError:\n", - " print(f\"Skipping malformed JSON line: {json_str}\")\n", - " continue\n", - " \n", - " except httpx.HTTPStatusError as e:\n", - " print(f\"HTTP error occurred: {e.response.status_code} - {e.response.text}\")\n", - " except httpx.RequestError as e:\n", - " print(f\"An error occurred while requesting {e.request.url!r}: {e}\")\n", - " except Exception as e:\n", - " print(f\"An error occurred: {e}\")\n", - "\n", - " return rag_response, citations\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "805c5744", - "metadata": {}, - "outputs": [], - "source": [ - "# Load the question and ground-truth answer pairs from the FinanceBench dataset\n", - "with open('../data/financebench/data/financebench_open_source.jsonl', 'r') as file:\n", - " gt_qa_pairs = [json.loads(line) for line in file]\n", - "\n", - "print(f\"Loaded {len(gt_qa_pairs)} question-answer pairs from FinanceBench dataset\")\n", - "\n", - "dataset = []\n", - "\n", - "# For the purposes of keeping this demo brief, we will only evaluate on 50 questions. \n", - "# You can increase this to the full dataset for more comprehensive results.\n", - "n = 50 \n", - "print(f\"Evaluating on {n} questions...\")\n", - "\n", - "for idx, qa_pair in enumerate(gt_qa_pairs[:n]):\n", - " question = qa_pair['question']\n", - " \n", - " print(f\"Processing question {idx + 1}/{n}: {question[:100]}...\")\n", - "\n", - " generate_payload = {\n", - " \"messages\": [\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": question\n", - " }\n", - " ],\n", - " \"use_knowledge_base\": True,\n", - " \"reranker_top_k\": 2,\n", - " \"vdb_top_k\": 10,\n", - " \"vdb_endpoint\": \"http://milvus:19530\",\n", - " \"collection_names\": [\"financebench\"],\n", - " \"enable_reranker\": True,\n", - " \"enable_citations\": True,\n", - " \"stop\": [],\n", - " \"filter_expr\": ''\n", - " }\n", - " \n", - " rag_answer, citations = await generate_answer(generate_payload)\n", - "\n", - " dataset.append({\n", - " \"user_input\": question,\n", - " \"retrieved_contexts\": citations,\n", - " \"response\": rag_answer,\n", - " \"reference\": qa_pair['answer'],\n", - " })\n", - "\n", - "print(f\"Created dataset with {len(dataset)} entries for evaluation\")" - ] - }, - { - "cell_type": "markdown", - "id": "43e68742", - "metadata": {}, - "source": [ - "\n", - "## 4. Evaluate with Ragas\n", - "\n", - "In this example, we will use the NVIDIA hosted endpoint for our judge model. To use this endpoint, please provide your NVIDIA API Key below. \n", - "\n", - "### Rate Limiting Considerations\n", - "\n", - "When using the public endpoint for the Judge LLM, you will likely encounter rate limit errors. We can try to reduce the number of errors by adjusting the configuration, which we do below. \n", - "\n", - "Alternatively, you can use self-hosted NIM Microservices endpoints to avoid these errors altogether. If you're using a self-hosted NIM, you do not need to provide your API Key.\n", - "\n", - "### Getting Your NVIDIA API Key\n", - "\n", - "To generate an API Key:\n", - "1. Go to [build.nvidia.com](https://build.nvidia.com/)\n", - "2. Click the green \"Get API Key\" button in the top right corner\n", - "3. Paste your key below to save it as an environment variable\n", - "\n", - "### Self-Hosted Option\n", - "\n", - "To deploy the Judge LLM as a NIM on your own infrastructure, follow the instructions [here](https://build.nvidia.com/mistralai/mixtral-8x22b-instruct/deploy).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "31df3819", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from getpass import getpass\n", - "# del os.environ['NVIDIA_API_KEY'] ## delete key and reset if needed\n", - "if os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n", - " print(\"Valid NVIDIA_API_KEY already in environment. Delete to reset\")\n", - "else:\n", - " candidate_api_key = getpass(\"NVAPI Key (starts with nvapi-): \")\n", - " assert candidate_api_key.startswith(\"nvapi-\"), (\n", - " f\"{candidate_api_key[:5]}... is not a valid key\"\n", - " )\n", - " os.environ[\"NVIDIA_API_KEY\"] = candidate_api_key" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "78fb75fe", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "# Note: Models on build.nvidia.com are rate limited.\n", - "# To avoid rate-limit issues, either deploy the judge model locally (self-hosted NIM)\n", - "# or use any OpenAI-compatible LLM as the judge for evaluation.\n", - "from langchain_nvidia_ai_endpoints.chat_models import ChatNVIDIA\n", - "\n", - "# Initialize the judge LLM for evaluation\n", - "# You can use any other model by creating Chat Model object\n", - "llm = ChatNVIDIA(model=\"openai/gpt-oss-120b\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "928a3c8a", - "metadata": {}, - "outputs": [], - "source": [ - "# Create the evaluation dataset from our collected data\n", - "from ragas import EvaluationDataset\n", - "\n", - "evaluation_dataset = EvaluationDataset.from_list(dataset)\n", - "print(f\"Created evaluation dataset with {len(evaluation_dataset)} samples\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b3ec24f4", - "metadata": {}, - "outputs": [], - "source": [ - "# Import the required metrics and evaluation components\n", - "from ragas.metrics import AnswerAccuracy, ContextRelevance, ResponseGroundedness\n", - "from ragas import evaluate\n", - "from ragas.llms import LangchainLLMWrapper\n", - "\n", - "# Wrap the LLM for use with Ragas\n", - "evaluator_llm = LangchainLLMWrapper(llm)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9f2f4245", - "metadata": {}, - "outputs": [], - "source": [ - "from ragas.run_config import RunConfig\n", - "\n", - "custom_config = RunConfig(max_workers=1, max_wait=120)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3a3571af", - "metadata": {}, - "outputs": [], - "source": [ - "# Run the evaluation with our three metrics\n", - "print(\"Starting Ragas evaluation...\")\n", - "print(\"This may take several minutes depending on the dataset size.\")\n", - "\n", - "results = evaluate(\n", - " dataset=evaluation_dataset,\n", - " metrics=[AnswerAccuracy(), ContextRelevance(), ResponseGroundedness()],\n", - " llm=evaluator_llm, \n", - " run_config=custom_config\n", - ")\n", - "\n", - "print(\"Evaluation completed!\")\n" - ] - }, - { - "cell_type": "markdown", - "id": "bac9dde6", - "metadata": {}, - "source": [ - "## 5. Analyze Results\n", - "\n", - "Finally, let's examine our evaluation results. We'll look at both the overall metrics and individual sample performance." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4c90647f", - "metadata": {}, - "outputs": [], - "source": [ - "results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2da683a1", - "metadata": {}, - "outputs": [], - "source": [ - "# Convert results to pandas DataFrame for detailed analysis of individual queries\n", - "results_df = results.to_pandas()\n", - "\n", - "import pandas as pd\n", - "\n", - "# 1. Set the option to display ALL columns, preventing the '...'\n", - "pd.set_option('display.max_columns', None)\n", - "\n", - "# 2. To prevent long text in cells from being cut off, you can set the column width\n", - "pd.set_option('display.max_colwidth', 80)\n", - "\n", - "results_df.head()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "evaluate", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/notebooks/image_input.ipynb b/notebooks/image_input.ipynb index 4f778a2fa..5698b7985 100644 --- a/notebooks/image_input.ipynb +++ b/notebooks/image_input.ipynb @@ -1,992 +1,992 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "e20e694c", - "metadata": {}, - "source": [ - "# Retriever API Usage with Multimodal Query Support\n", - "\n", - "This notebook demonstrates how to use the NVIDIA RAG retriever APIs with **multimodal queries** (text + images). You'll learn how to:\n", - "\n", - "- 🔍 Search for relevant documents using queries that contain images\n", - "- 🤖 Generate AI responses using the end-to-end RAG API with vision-language models (VLMs)\n", - "- 📊 Work with multimodal embeddings and vector databases\n", - "\n", - "**Use Case**: Query documents with images (e.g., \"What is the price of this item?\" + product image)" - ] - }, - { - "cell_type": "markdown", - "id": "0152f1eb", - "metadata": {}, - "source": [ - "## 📦 Setting up the Dependencies\n", - "\n", - "This section will guide you through:\n", - "1. Configuring your NGC API key for accessing NVIDIA services\n", - "2. Deploying the Milvus vector database\n", - "3. Setting up NVIDIA NIMs (NVIDIA Inference Microservices) for embeddings and VLM\n", - "4. Starting the NVIDIA Ingest runtime for document processing\n", - "5. Launching the RAG server\n", - "\n", - "**Note**: This setup uses Docker Compose to orchestrate all services." - ] - }, - { - "cell_type": "markdown", - "id": "d77a630e", - "metadata": {}, - "source": [ - "### 0. Create a Virtual Environment (Recommended)\n", - "\n", - "Before running this notebook, create a virtual environment using `uv` to isolate dependencies:\n", - "\n", - "```bash\n", - "# Create a virtual environment\n", - "uv venv .venv\n", - "\n", - "# Activate the virtual environment\n", - "source .venv/bin/activate # Linux/macOS\n", - "# .venv\\Scripts\\activate # Windows\n", - "\n", - "# Install Jupyter Lab and ipykernel (if not already installed)\n", - "uv pip install jupyterlab ipykernel\n", - "\n", - "# Register the venv as a Jupyter kernel\n", - "python -m ipykernel install --user --name=.venv --display-name=\"Python (.venv)\"\n", - "```\n", - "\n", - "After setup, select the venv as the kernel for this notebook:\n", - "1. In Jupyter/VS Code/Cursor, click on the kernel selector (top right)\n", - "2. Choose **\".venv\"** or **\"Python (.venv)\"** as the kernel\n", - "\n", - "This ensures all packages installed via `uv pip install` in the notebook cells are installed into the isolated environment.\n" - ] - }, - { - "cell_type": "markdown", - "id": "c39e628e", - "metadata": {}, - "source": [ - "### 1. Setup the Default Configurations\n", - "\n", - "Import necessary libraries for environment management." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c03780a7", - "metadata": {}, - "outputs": [], - "source": [ - "# Install python-dotenv for environment variable management\n", - "! uv pip install python-dotenv\n", - "\n", - "import os\n", - "from getpass import getpass" - ] - }, - { - "cell_type": "markdown", - "id": "8a19cef7", - "metadata": {}, - "source": [ - "Provide your NGC_API_KEY after executing the cell below. You can obtain a key by following steps [here](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/quickstart.md##obtain-an-api-key)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c1f7ffa3", - "metadata": {}, - "outputs": [], - "source": [ - "# Check if NGC_API_KEY is already set, otherwise prompt for it\n", - "# Uncomment the line below to reset your API key\n", - "# del os.environ['NGC_API_KEY']\n", - "\n", - "if os.environ.get(\"NGC_API_KEY\", \"\").startswith(\"nvapi-\"):\n", - " print(\"Valid NGC_API_KEY already in environment. Delete to reset\")\n", - "else:\n", - " candidate_api_key = getpass(\"NVAPI Key (starts with nvapi-): \")\n", - " assert candidate_api_key.startswith(\"nvapi-\"), (\n", - " f\"{candidate_api_key[:5]}... is not a valid key\"\n", - " )\n", - " os.environ[\"NGC_API_KEY\"] = candidate_api_key" - ] - }, - { - "cell_type": "markdown", - "id": "20ec8b61", - "metadata": {}, - "source": [ - "Login to nvcr.io which is needed for pulling the containers of dependencies" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "03972882", - "metadata": {}, - "outputs": [], - "source": [ - "# Login to NVIDIA Container Registry (nvcr.io) to pull required containers\n", - "!echo \"${NGC_API_KEY}\" | docker login nvcr.io -u '$oauthtoken' --password-stdin" - ] - }, - { - "cell_type": "markdown", - "id": "84642fbb", - "metadata": {}, - "source": [ - "### 2. Setup the Milvus Vector Database\n", - "\n", - "Milvus is a high-performance vector database used to store and search multimodal embeddings.\n", - "\n", - "**Configuration Notes**:\n", - "- By default, Milvus uses GPU indexing for faster performance\n", - "- Ensure you have provided the correct GPU ID below\n", - "- If you don't have a GPU available, you can switch to CPU-only Milvus by following the instructions in [milvus-configuration.md](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/milvus-configuration.md)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8125f717", - "metadata": {}, - "outputs": [], - "source": [ - "# Specify which GPU to use for Milvus (change if using a different GPU)\n", - "os.environ[\"VECTORSTORE_GPU_DEVICE_ID\"] = \"0\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3e2d3457", - "metadata": {}, - "outputs": [], - "source": [ - "# Start Milvus vector database service\n", - "# This will run in the background (-d flag)\n", - "!docker compose -f ../deploy/compose/vectordb.yaml up -d" - ] - }, - { - "cell_type": "markdown", - "id": "afe17557", - "metadata": {}, - "source": [ - "### 3. Setup NVIDIA Inference Microservices (NIMs)\n", - "\n", - "NIMs provide optimized inference for AI models. For multimodal RAG, we need:\n", - "- **VLM (Vision-Language Model)**: `nvidia/nemotron-nano-12b-v2-vl` for understanding images and generating responses\n", - "- **Embedding Model**: `llama-3.2-nemoretriever-1b-vlm-embed-v1` for creating multimodal embeddings" - ] - }, - { - "cell_type": "markdown", - "id": "89a135eb", - "metadata": {}, - "source": [ - "#### Deploy On-Premise Models\n", - "\n", - "This section deploys NIMs locally using Docker. Models will be cached to avoid re-downloading." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1b3d2e5c", - "metadata": {}, - "outputs": [], - "source": [ - "# Create the model cache directory\n", - "!mkdir -p ~/.cache/model-cache" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "390df52d", - "metadata": {}, - "outputs": [], - "source": [ - "# Set the MODEL_DIRECTORY environment variable to specify where models are cached\n", - "import os\n", - "\n", - "os.environ[\"MODEL_DIRECTORY\"] = os.path.expanduser(\"~/.cache/model-cache\")\n", - "print(\"MODEL_DIRECTORY set to:\", os.environ[\"MODEL_DIRECTORY\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "62a9946a", - "metadata": {}, - "outputs": [], - "source": [ - "# Deploy NIMs with VLM and embedding profiles\n", - "# ⚠️ WARNING: This may take 10-20 minutes as models download (~10GB+)\n", - "# If the kernel times out, just rerun this cell - it will resume where it left off\n", - "# Select a free GPU for VLM Microservice\n", - "os.environ[\"VLM_MS_GPU_ID\"] = \"1\"\n", - "! USERID=$(id -u) docker compose --profile vlm-ingest --profile vlm-only -f ../deploy/compose/nims.yaml up -d" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e91f511a", - "metadata": {}, - "outputs": [], - "source": [ - "# Monitor the status of running containers\n", - "# Run this cell repeatedly to check if all services are healthy\n", - "# Look for STATUS showing \"healthy\" or \"Up\" for all containers\n", - "!docker ps" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cfb34a6a", - "metadata": {}, - "outputs": [], - "source": [ - "# Configure the model names and service URLs for the RAG pipeline\n", - "# These settings tell the RAG server which models and endpoints to use\n", - "\n", - "# VLM (Vision-Language Model) configuration\n", - "os.environ[\"APP_VLM_MODELNAME\"] = \"nvidia/nemotron-nano-12b-v2-vl\"\n", - "os.environ[\"APP_VLM_SERVERURL\"] = \"http://vlm-ms:8000/v1\"\n", - "\n", - "# Multimodal embedding model configuration\n", - "os.environ[\"APP_EMBEDDINGS_MODELNAME\"] = \"nvidia/llama-nemotron-embed-vl-1b-v2\"\n", - "os.environ[\"APP_EMBEDDINGS_SERVERURL\"] = \"nemotron-vlm-embedding-ms:8000/v1\"\n", - "os.environ[\"ENABLE_VLM_INFERENCE\"] = \"true\"\n", - "os.environ[\"VLM_TO_LLM_FALLBACK\"] = \"false\"\n", - "os.environ[\"ENABLE_RERANKER\"] = \"false\"" - ] - }, - { - "cell_type": "markdown", - "id": "e62c7037", - "metadata": {}, - "source": [ - "#### Cloud based deployment\n", - "Using NVIDIA hosted cloud model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "82084d4d", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "# OCR and document processing endpoints - cloud hosted\n", - "os.environ[\"OCR_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr\"\n", - "os.environ[\"OCR_INFER_PROTOCOL\"] = \"http\"\n", - "os.environ[\"OCR_MODEL_NAME\"] = \"scene_text_ensemble\"\n", - "os.environ[\"YOLOX_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3\"\n", - "os.environ[\"YOLOX_INFER_PROTOCOL\"] = \"http\"\n", - "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1\"\n", - "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL\"] = \"http\"\n", - "os.environ[\"YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1\"\n", - "os.environ[\"YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL\"] = \"http\"\n", - "os.environ[\"APP_NVINGEST_CAPTIONENDPOINTURL\"] = \"https://integrate.api.nvidia.com/v1/chat/completions\"\n", - "\n", - "# VLM Model configuration - cloud hosted\n", - "os.environ[\"APP_VLM_MODELNAME\"] = \"nvidia/nemotron-nano-12b-v2-vl\"\n", - "os.environ[\"APP_VLM_SERVERURL\"] = \"https://integrate.api.nvidia.com/v1\"\n", - "os.environ[\"APP_LLM_SERVERURL\"] = \"\"\n", - "\n", - "# Multimodal embedding model configuration - cloud hosted\n", - "os.environ[\"APP_EMBEDDINGS_MODELNAME\"] = \"nvidia/llama-nemotron-embed-vl-1b-v2\"\n", - "os.environ[\"APP_EMBEDDINGS_SERVERURL\"] = \"https://integrate.api.nvidia.com/v1\"\n", - "os.environ[\"ENABLE_VLM_INFERENCE\"] = \"true\"\n", - "os.environ[\"VLM_TO_LLM_FALLBACK\"] = \"false\"\n", - "os.environ[\"ENABLE_RERANKER\"] = \"false\"" - ] - }, - { - "cell_type": "markdown", - "id": "7cbcfa50", - "metadata": {}, - "source": [ - "### 4. Setup NVIDIA Ingest Runtime\n", - "\n", - "NVIDIA Ingest processes documents to extract text, images, and other elements. We'll configure it to:\n", - "- Extract images from documents\n", - "- Handle multimodal content" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a5e0d73f", - "metadata": {}, - "outputs": [], - "source": [ - "# Configure NVIDIA Ingest to extract and process images from documents\n", - "os.environ[\"APP_NVINGEST_STRUCTURED_ELEMENTS_MODALITY\"] = \"\" # No special handling for structured elements\n", - "os.environ[\"APP_NVINGEST_IMAGE_ELEMENTS_MODALITY\"] = \"image\" # Process image elements as images\n", - "os.environ[\"APP_NVINGEST_EXTRACTIMAGES\"] = \"True\" # Extract images from documents\n", - "\n", - "# Start the ingestor server with Redis\n", - "! docker compose -f ../deploy/compose/docker-compose-ingestor-server.yaml up -d --build" - ] - }, - { - "cell_type": "markdown", - "id": "da1bd9a3", - "metadata": {}, - "source": [ - "### 5. Setup the NVIDIA RAG Server\n", - "\n", - "The RAG server provides the main API endpoints for search and generation. It orchestrates all the components (embeddings, vector DB, VLM) to deliver intelligent responses." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "38ba7752", - "metadata": {}, - "outputs": [], - "source": [ - "# Start the RAG server (accessible at localhost:8081)\n", - "os.environ[\"APP_RANKING_SERVERURL\"] = \"\"\n", - "! docker compose -f ../deploy/compose/docker-compose-rag-server.yaml up -d --build" - ] - }, - { - "cell_type": "markdown", - "id": "ce492ce3", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## 📚 Document Ingestion Workflow\n", - "\n", - "Now that all services are running, let's ingest documents into a collection.\n", - "\n", - "### 6. Create a Collection\n", - "\n", - "A collection is a logical grouping of documents in the vector database. Think of it as a database table optimized for similarity search." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a8611aa1", - "metadata": {}, - "outputs": [], - "source": [ - "# Install aiohttp for async HTTP requests\n", - "! uv pip install aiohttp\n", - "\n", - "# Configure the ingestor server URL\n", - "# Use \"ingestor-server\" when running in AI Workbench, otherwise \"localhost\"\n", - "IPADDRESS = (\n", - " \"ingestor-server\"\n", - " if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\"\n", - " else \"localhost\"\n", - ")\n", - "INGESTOR_SERVER_PORT = \"8082\"\n", - "BASE_URL = f\"http://{IPADDRESS}:{INGESTOR_SERVER_PORT}\"\n", - "\n", - "async def print_response(response):\n", - " \"\"\"Helper function to pretty-print API responses.\"\"\"\n", - " try:\n", - " response_json = await response.json()\n", - " print(json.dumps(response_json, indent=2))\n", - " except aiohttp.ClientResponseError:\n", - " print(await response.text())\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "688bc70f", - "metadata": {}, - "outputs": [], - "source": [ - "# Define a unique name for your collection\n", - "# Change this if you want to create a different collection\n", - "collection_name = \"multimodal_query\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "24378f6f", - "metadata": {}, - "outputs": [], - "source": [ - "import aiohttp\n", - "import json\n", - "\n", - "\n", - "async def create_collection(\n", - " collection_name: str | None = None,\n", - " metadata_schema: list = [],\n", - "):\n", - " \"\"\"\n", - " Create a new collection in the vector database.\n", - " \n", - " Args:\n", - " collection_name: Unique identifier for the collection\n", - " metadata_schema: Optional schema for metadata fields\n", - " \"\"\"\n", - " data = {\n", - " \"collection_name\": collection_name,\n", - " \"metadata_schema\": metadata_schema,\n", - " }\n", - "\n", - " HEADERS = {\"Content-Type\": \"application/json\"}\n", - "\n", - " async with aiohttp.ClientSession() as session:\n", - " try:\n", - " async with session.post(\n", - " f\"{BASE_URL}/v1/collection\", json=data, headers=HEADERS\n", - " ) as response:\n", - " await print_response(response)\n", - " except aiohttp.ClientError as e:\n", - " return 500, {\"error\": str(e)}\n", - "\n", - "\n", - "# Create the collection\n", - "# The embedding dimension is 2048 for the multimodal embedding model we're using\n", - "await create_collection(\n", - " collection_name=collection_name,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a29f4633", - "metadata": {}, - "outputs": [], - "source": [ - "# Specify the documents to upload\n", - "# This PDF contains product images with pricing information\n", - "FILEPATHS = [\n", - " \"../data/multimodal/product_catalog.pdf\",\n", - "]\n", - "\n", - "async def upload_documents(collection_name: str = \"\"):\n", - " \"\"\"\n", - " Upload and process documents into the collection.\n", - " \n", - " This will:\n", - " 1. Extract text and images from the PDFs\n", - " 2. Chunk the content for optimal retrieval\n", - " 3. Generate multimodal embeddings\n", - " 4. Store everything in the vector database\n", - " \"\"\"\n", - " data = {\n", - " \"collection_name\": collection_name,\n", - " \"blocking\": False, # Async upload - use status API to check progress\n", - " \"split_options\": {\n", - " \"chunk_size\": 512, # Characters per chunk\n", - " \"chunk_overlap\": 150 # Overlap between chunks for context\n", - " },\n", - " \"generate_summary\": False # Set to True to generate document summaries\n", - " }\n", - "\n", - " form_data = aiohttp.FormData()\n", - " \n", - " # Add all PDF files to the form data\n", - " for file_path in FILEPATHS:\n", - " form_data.add_field(\"documents\", open(file_path, \"rb\"), \n", - " filename=os.path.basename(file_path), \n", - " content_type=\"application/pdf\")\n", - "\n", - " form_data.add_field(\"data\", json.dumps(data), content_type=\"application/json\")\n", - "\n", - " async with aiohttp.ClientSession() as session:\n", - " try:\n", - " # Use POST for new uploads, PATCH for re-ingesting existing documents\n", - " async with session.post(f\"{BASE_URL}/v1/documents\", data=form_data) as response:\n", - " await print_response(response)\n", - " response_json = await response.json()\n", - " return response_json\n", - " except aiohttp.ClientError as e:\n", - " print(f\"Error uploading documents: {e}\")\n", - " return None\n", - "\n", - "# Upload the documents and get the task ID for tracking progress\n", - "upload_response = await upload_documents(collection_name=collection_name)\n", - "task_id = upload_response.get(\"task_id\") if upload_response else None\n", - "print(f\"\\nTask ID for tracking: {task_id}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2234e059", - "metadata": {}, - "outputs": [], - "source": [ - "async def get_task_status(task_id: str):\n", - " \"\"\"\n", - " Check the status of an asynchronous ingestion task.\n", - " \n", - " Possible statuses:\n", - " - \"pending\": Task is queued\n", - " - \"processing\": Currently processing documents\n", - " - \"completed\": Successfully finished\n", - " - \"failed\": Error occurred\n", - " \"\"\"\n", - " params = {\n", - " \"task_id\": task_id,\n", - " }\n", - "\n", - " HEADERS = {\"Content-Type\": \"application/json\"}\n", - "\n", - " async with aiohttp.ClientSession() as session:\n", - " try:\n", - " async with session.get(\n", - " f\"{BASE_URL}/v1/status\", params=params, headers=HEADERS\n", - " ) as response:\n", - " await print_response(response)\n", - " except aiohttp.ClientError as e:\n", - " return 500, {\"error\": str(e)}\n", - "\n", - "\n", - "# Check the ingestion status\n", - "# Run this cell multiple times until status shows \"completed\"\n", - "await get_task_status(\n", - " task_id=[task_id]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "e2c1f1a8", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## 🔍 Querying with Multimodal Inputs\n", - "\n", - "Now that documents are ingested, let's query them using both text and images!\n", - "\n", - "### 7. Using the Search and Generate APIs\n", - "\n", - "We'll demonstrate two approaches:\n", - "1. **Search API**: Find relevant documents without generating a response\n", - "2. **Generate API**: Get an AI-generated answer with citations" - ] - }, - { - "cell_type": "markdown", - "id": "3990ca33", - "metadata": {}, - "source": [ - "#### Prepare a Multimodal Query\n", - "\n", - "To query with an image, we need to:\n", - "1. Convert the image to base64 encoding\n", - "2. Format it according to the OpenAI vision API format\n", - "3. Combine it with a text prompt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "02dde830", - "metadata": {}, - "outputs": [], - "source": [ - "! uv pip install requests httpx\n", - "import base64\n", - "import requests\n", - "from IPython.display import Image, Markdown, display\n", - "\n", - "def get_base64_image(image_source: str) -> str:\n", - " \"\"\"\n", - " Convert an image to base64 encoding.\n", - " \n", - " Args:\n", - " image_source: Local file path or URL to the image\n", - " \n", - " Returns:\n", - " Base64 encoded string of the image\n", - " \"\"\"\n", - " if image_source.startswith(('http://', 'https://')):\n", - " # Download image from URL\n", - " response = requests.get(image_source)\n", - " return base64.b64encode(response.content).decode()\n", - " else:\n", - " # Read local file\n", - " with open(image_source, \"rb\") as image_file:\n", - " return base64.b64encode(image_file.read()).decode()\n", - "\n", - "# Convert the query image to base64\n", - "# Try different images to test different queries:\n", - "image_b64 = get_base64_image(\"../data/multimodal/Creme_clutch_purse1-small.jpg\")\n", - "\n", - "# Display the query image for reference\n", - "query_image_path = \"../data/multimodal/Creme_clutch_purse1-small.jpg\"\n", - "print(\"📷 Query Image:\")\n", - "display(Image(filename=query_image_path, width=300))\n", - "\n", - "# Format as a data URL\n", - "image_input = f\"data:image/png;base64,{image_b64}\"\n", - "\n", - "# Create the multimodal query with text + image\n", - "# This follows the OpenAI vision API format\n", - "query_1 = \"What material is this made of?\"\n", - "image_query = [\n", - " {\"type\": \"text\", \"text\": query_1},\n", - " {\n", - " \"type\": \"image_url\",\n", - " \"image_url\": {\n", - " \"url\": image_input,\n", - " \"detail\": \"auto\" # Let the model decide the appropriate detail level\n", - " }\n", - " }\n", - "]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c311f9d0", - "metadata": {}, - "outputs": [], - "source": [ - "import httpx\n", - "import json\n", - "from IPython.display import Image, Markdown, display\n", - "\n", - "RAG_BASE_URL = \"http://localhost:8081\"\n", - "\n", - "async def search_documents(payload):\n", - " \"\"\"\n", - " Search for relevant documents using a multimodal query.\n", - " \n", - " This performs similarity search in the vector database and optionally\n", - " reranks results for better relevance.\n", - " \"\"\"\n", - " search_url = f\"{RAG_BASE_URL}/v1/search\"\n", - " \n", - " async with httpx.AsyncClient(timeout=300.0) as client:\n", - " try:\n", - " response = await client.post(url=search_url, json=payload)\n", - " response.raise_for_status()\n", - " \n", - " search_results = response.json()\n", - " print(\"Search Results:\")\n", - " \n", - " # Display search results with nice formatting\n", - " if \"results\" in search_results:\n", - " for idx, result in enumerate(search_results[\"results\"]):\n", - " doc_type = result.get(\"document_type\", \"text\")\n", - " content = result.get(\"content\", \"\")\n", - " doc_name = result.get(\"document_name\", f\"Result {idx + 1}\")\n", - " score = result.get(\"score\", \"N/A\")\n", - " \n", - " display(Markdown(f\"**Result {idx + 1}: {doc_name} (Score: {score})**\"))\n", - " try:\n", - " if doc_type == \"image\":\n", - " # Display image results\n", - " image_bytes = base64.b64decode(content)\n", - " display(Image(data=image_bytes))\n", - " else:\n", - " # Display text results\n", - " display(Markdown(f\"```\\n{content}\\n```\"))\n", - " except Exception as e:\n", - " print(f\"Error displaying content: {e}\")\n", - " display(Markdown(f\"```\\n{content}\\n```\"))\n", - " \n", - " return search_results\n", - " \n", - " except httpx.HTTPStatusError as e:\n", - " print(f\"HTTP error occurred: {e.response.status_code} - {e.response.text}\")\n", - " except httpx.RequestError as e:\n", - " print(f\"An error occurred while requesting {e.request.url!r}: {e}\")\n", - " except Exception as e:\n", - " print(f\"An error occurred: {e}\")\n", - "\n", - "# Configure the search parameters\n", - "search_payload = {\n", - " \"query\": image_query, # Our multimodal query (text + image)\n", - " \"messages\": [], # No conversation history\n", - " \"use_knowledge_base\": True, # Search the vector database\n", - " \"collection_names\": [collection_name], # Which collection to search\n", - " \"vdb_top_k\": 5, # Retrieve top 5 results from vector DB\n", - " \"vdb_endpoint\": \"http://milvus:19530\", # Milvus connection string\n", - " \"enable_reranker\": False, # Set to True for better relevance (slower)\n", - " \"reranker_top_k\": 3, # If reranker enabled, return top 3\n", - " \"filter_expr\": \"\", # Optional metadata filter\n", - "}\n", - "\n", - "# Execute the search\n", - "print(\"🔍 Searching for documents matching the query...\\n\")\n", - "search_result = await search_documents(search_payload)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bc5b1545", - "metadata": {}, - "outputs": [], - "source": [ - "import base64\n", - "import json\n", - "from IPython.display import Image, Markdown, display\n", - "\n", - "\n", - "async def print_streaming_response_and_citations(response_generator):\n", - " \"\"\"\n", - " Helper function to display streaming responses with citations.\n", - " \n", - " This function:\n", - " 1. Streams the AI-generated response token by token\n", - " 2. Extracts citations from the first chunk\n", - " 3. Displays citations (text or images) after the response completes\n", - " \"\"\"\n", - " first_chunk_data = None\n", - " \n", - " async for chunk in response_generator:\n", - " # Parse Server-Sent Events (SSE) format\n", - " if chunk.startswith(\"data: \"):\n", - " chunk = chunk[len(\"data: \") :].strip()\n", - " if not chunk:\n", - " continue\n", - " \n", - " try:\n", - " data = json.loads(chunk)\n", - " except Exception as e:\n", - " print(f\"JSON decode error: {e}\")\n", - " continue\n", - " \n", - " choices = data.get(\"choices\", [])\n", - " if not choices:\n", - " continue\n", - " \n", - " # Save the first chunk with citations\n", - " if first_chunk_data is None and data.get(\"citations\"):\n", - " first_chunk_data = data\n", - " \n", - " # Print streaming text\n", - " delta = choices[0].get(\"delta\", {})\n", - " text = delta.get(\"content\")\n", - " if not text:\n", - " message = choices[0].get(\"message\", {})\n", - " text = message.get(\"content\", \"\")\n", - " print(text, end=\"\", flush=True)\n", - " \n", - " print() # Newline after streaming\n", - "\n", - " # Display citations after streaming is done\n", - " if first_chunk_data and first_chunk_data.get(\"citations\"):\n", - " print(\"\\n📚 Citations:\")\n", - " citations = first_chunk_data[\"citations\"]\n", - " for idx, citation in enumerate(citations.get(\"results\", [])):\n", - " doc_type = citation.get(\"document_type\", \"text\")\n", - " content = citation.get(\"content\", \"\")\n", - " doc_name = citation.get(\"document_name\", f\"Citation {idx + 1}\")\n", - " display(Markdown(f\"**Citation {idx + 1}: {doc_name}**\"))\n", - " try:\n", - " # Try to display as image\n", - " image_bytes = base64.b64decode(content)\n", - " display(Image(data=image_bytes))\n", - " except Exception:\n", - " # Fall back to text display\n", - " display(Markdown(f\"```\\n{content}\\n```\"))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "31071359", - "metadata": {}, - "outputs": [], - "source": [ - "import httpx\n", - "\n", - "# Configure RAG server URL\n", - "IPADDRESS = \"rag-server\" if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\" else \"localhost\"\n", - "RAG_SERVER_PORT = \"8081\"\n", - "RAG_BASE_URL = f\"http://{IPADDRESS}:{RAG_SERVER_PORT}\"\n", - "generate_url = f\"{RAG_BASE_URL}/v1/generate\"\n", - "\n", - "async def generate_answer(payload):\n", - " \"\"\"\n", - " Generate an AI answer using the RAG pipeline.\n", - " \n", - " This function:\n", - " 1. Sends the query to the RAG server\n", - " 2. Retrieves relevant context from the vector database\n", - " 3. Streams the AI-generated response\n", - " 4. Displays citations (sources) used to generate the answer\n", - " \"\"\"\n", - " rag_response = \"\"\n", - " citations = []\n", - " is_first_token = True\n", - "\n", - " async with httpx.AsyncClient(timeout=300.0) as client:\n", - " try:\n", - " async with client.stream(\"POST\", url=generate_url, json=payload) as response:\n", - " # Raise an exception for bad status codes like 4xx or 5xx\n", - " response.raise_for_status()\n", - "\n", - " # Iterate over the streaming response\n", - " async for line in response.aiter_lines():\n", - " if line.startswith(\"data: \"):\n", - " json_str = line[6:].strip()\n", - " if not json_str:\n", - " continue\n", - "\n", - " try:\n", - " data = json.loads(json_str)\n", - "\n", - " # Extract and display the streaming response\n", - " message = data.get(\"choices\", [{}])[0].get(\"message\", {}).get(\"content\", \"\")\n", - " if message:\n", - " rag_response += message\n", - "\n", - " # Extract and display citations from the first chunk\n", - " if is_first_token and data.get(\"citations\"):\n", - " print(\"\\n📚 Citations:\")\n", - " citations = data[\"citations\"]\n", - " for idx, citation in enumerate(citations.get(\"results\", [])):\n", - " doc_type = citation.get(\"document_type\", \"text\")\n", - " content = citation.get(\"content\", \"\")\n", - " doc_name = citation.get(\"document_name\", f\"Citation {idx + 1}\")\n", - " display(Markdown(f\"**Citation {idx + 1}: {doc_name}**\"))\n", - " try:\n", - " # Display image citations\n", - " image_bytes = base64.b64decode(content)\n", - " display(Image(data=image_bytes))\n", - " except Exception:\n", - " # Display text citations\n", - " display(Markdown(f\"```\\n{content}\\n```\"))\n", - " is_first_token = False\n", - "\n", - " # Check if streaming is complete\n", - " finish_reason = data.get(\"choices\", [{}])[0].get(\"finish_reason\")\n", - " if finish_reason == \"stop\":\n", - " return rag_response\n", - "\n", - " except json.JSONDecodeError:\n", - " print(f\"Skipping malformed JSON line: {json_str}\")\n", - " continue\n", - " \n", - " except httpx.HTTPStatusError as e:\n", - " print(f\"HTTP error occurred: {e.response.status_code} - {e.response.text}\")\n", - " except httpx.RequestError as e:\n", - " print(f\"An error occurred while requesting {e.request.url!r}: {e}\")\n", - " except Exception as e:\n", - " print(f\"An error occurred: {e}\")\n", - "\n", - " print(\"\\n✅ Response complete!\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c3049696", - "metadata": {}, - "outputs": [], - "source": [ - "# Format the query as a chat message\n", - "messages = [\n", - " {\n", - " \"role\": \"user\",\n", - " \"content\": image_query # Our multimodal query (text + image)\n", - " }\n", - "]\n", - "\n", - "# Configure the generate API parameters\n", - "payload = {\n", - " \"messages\": messages, # Chat conversation\n", - " \"use_knowledge_base\": True, # Enable RAG - use vector DB for context\n", - " \"temperature\": 0.2, # Lower = more deterministic, higher = more creative\n", - " \"top_p\": 0.7, # Nucleus sampling parameter\n", - " \"max_tokens\": 1024, # Maximum response length\n", - " \"reranker_top_k\": 2, # Keep top 2 results after reranking\n", - " \"vdb_top_k\": 10, # Retrieve top 10 from vector DB initially\n", - " \"vdb_endpoint\": \"http://milvus:19530\", # Milvus connection\n", - " \"collection_names\": [collection_name], # Which collection to search\n", - " \"enable_query_rewriting\": True, # Improve query before searching\n", - " \"enable_citations\": True, # Include source citations in response\n", - " \"stop\": [], # Optional stop sequences\n", - " \"filter_expr\": \"\", # Optional metadata filter \n", - "}\n", - "\n", - "# Generate the answer with RAG\n", - "print(\"🤖 Generating answer with RAG...\\n\")\n", - "await generate_answer(payload)" - ] - }, - { - "cell_type": "markdown", - "id": "dcddc78d", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## 🎉 Summary\n", - "\n", - "Congratulations! You've successfully:\n", - "\n", - "✅ **Set up the infrastructure**: Deployed Milvus vector DB, NVIDIA NIMs, and RAG services \n", - "✅ **Ingested multimodal documents**: Uploaded PDFs with images and extracted their content \n", - "✅ **Created multimodal queries**: Combined text and images in your search queries \n", - "✅ **Retrieved relevant context**: Used semantic search to find matching documents \n", - "✅ **Generated AI responses**: Got intelligent answers with source citations \n", - "\n", - "### Next Steps\n", - "\n", - "- **Try different queries**: Change the query text or use different query images\n", - "- **Upload more documents**: Add more PDFs to enrich your knowledge base\n", - "- **Experiment with parameters**: Adjust `temperature`, `top_k`, reranker settings\n", - "- **Build applications**: Integrate these APIs into your own applications\n", - "\n", - "### Cleanup\n", - "\n", - "To stop all services and free up resources:\n", - "\n", - "```bash\n", - "cd ../deploy/compose\n", - "docker compose -f docker-compose-rag-server.yaml down\n", - "docker compose -f docker-compose-ingestor-server.yaml down\n", - "docker compose -f nims.yaml down\n", - "docker compose -f vectordb.yaml down\n", - "```\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "cells": [ + { + "cell_type": "markdown", + "id": "e20e694c", + "metadata": {}, + "source": [ + "# Retriever API Usage with Multimodal Query Support\n", + "\n", + "This notebook demonstrates how to use the NVIDIA RAG retriever APIs with **multimodal queries** (text + images). You'll learn how to:\n", + "\n", + "- 🔍 Search for relevant documents using queries that contain images\n", + "- 🤖 Generate AI responses using the end-to-end RAG API with vision-language models (VLMs)\n", + "- 📊 Work with multimodal embeddings and vector databases\n", + "\n", + "**Use Case**: Query documents with images (e.g., \"What is the price of this item?\" + product image)" + ] + }, + { + "cell_type": "markdown", + "id": "0152f1eb", + "metadata": {}, + "source": [ + "## 📦 Setting up the Dependencies\n", + "\n", + "This section will guide you through:\n", + "1. Configuring your NGC API key for accessing NVIDIA services\n", + "2. Deploying the Milvus vector database\n", + "3. Setting up NVIDIA NIMs (NVIDIA Inference Microservices) for embeddings and VLM\n", + "4. Starting the NVIDIA Ingest runtime for document processing\n", + "5. Launching the RAG server\n", + "\n", + "**Note**: This setup uses Docker Compose to orchestrate all services." + ] + }, + { + "cell_type": "markdown", + "id": "d77a630e", + "metadata": {}, + "source": [ + "### 0. Create a Virtual Environment (Recommended)\n", + "\n", + "Before running this notebook, create a virtual environment using `uv` to isolate dependencies:\n", + "\n", + "```bash\n", + "# Create a virtual environment\n", + "uv venv .venv\n", + "\n", + "# Activate the virtual environment\n", + "source .venv/bin/activate # Linux/macOS\n", + "# .venv\\Scripts\\activate # Windows\n", + "\n", + "# Install Jupyter Lab and ipykernel (if not already installed)\n", + "uv pip install jupyterlab ipykernel\n", + "\n", + "# Register the venv as a Jupyter kernel\n", + "python -m ipykernel install --user --name=.venv --display-name=\"Python (.venv)\"\n", + "```\n", + "\n", + "After setup, select the venv as the kernel for this notebook:\n", + "1. In Jupyter/VS Code/Cursor, click on the kernel selector (top right)\n", + "2. Choose **\".venv\"** or **\"Python (.venv)\"** as the kernel\n", + "\n", + "This ensures all packages installed via `uv pip install` in the notebook cells are installed into the isolated environment.\n" + ] + }, + { + "cell_type": "markdown", + "id": "c39e628e", + "metadata": {}, + "source": [ + "### 1. Setup the Default Configurations\n", + "\n", + "Import necessary libraries for environment management." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c03780a7", + "metadata": {}, + "outputs": [], + "source": [ + "# Install python-dotenv for environment variable management\n", + "! uv pip install python-dotenv\n", + "\n", + "import os\n", + "from getpass import getpass" + ] + }, + { + "cell_type": "markdown", + "id": "8a19cef7", + "metadata": {}, + "source": [ + "Provide your NGC_API_KEY after executing the cell below. You can obtain a key by following steps [here](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/api-key.md)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1f7ffa3", + "metadata": {}, + "outputs": [], + "source": [ + "# Check if NGC_API_KEY is already set, otherwise prompt for it\n", + "# Uncomment the line below to reset your API key\n", + "# del os.environ['NGC_API_KEY']\n", + "\n", + "if os.environ.get(\"NGC_API_KEY\", \"\").startswith(\"nvapi-\"):\n", + " print(\"Valid NGC_API_KEY already in environment. Delete to reset\")\n", + "else:\n", + " candidate_api_key = getpass(\"NVAPI Key (starts with nvapi-): \")\n", + " assert candidate_api_key.startswith(\"nvapi-\"), (\n", + " f\"{candidate_api_key[:5]}... is not a valid key\"\n", + " )\n", + " os.environ[\"NGC_API_KEY\"] = candidate_api_key" + ] + }, + { + "cell_type": "markdown", + "id": "20ec8b61", + "metadata": {}, + "source": [ + "Login to nvcr.io which is needed for pulling the containers of dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03972882", + "metadata": {}, + "outputs": [], + "source": [ + "# Login to NVIDIA Container Registry (nvcr.io) to pull required containers\n", + "!echo \"${NGC_API_KEY}\" | docker login nvcr.io -u '$oauthtoken' --password-stdin" + ] + }, + { + "cell_type": "markdown", + "id": "84642fbb", + "metadata": {}, + "source": [ + "### 2. Setup the Milvus Vector Database\n", + "\n", + "Milvus is a high-performance vector database used to store and search multimodal embeddings.\n", + "\n", + "**Configuration Notes**:\n", + "- By default, Milvus uses GPU indexing for faster performance\n", + "- Ensure you have provided the correct GPU ID below\n", + "- If you don't have a GPU available, you can switch to CPU-only Milvus by following the instructions in [milvus-configuration.md](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/milvus-configuration.md)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8125f717", + "metadata": {}, + "outputs": [], + "source": [ + "# Specify which GPU to use for Milvus (change if using a different GPU)\n", + "os.environ[\"VECTORSTORE_GPU_DEVICE_ID\"] = \"0\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e2d3457", + "metadata": {}, + "outputs": [], + "source": [ + "# Start Milvus vector database service\n", + "# This will run in the background (-d flag)\n", + "!docker compose -f ../deploy/compose/vectordb.yaml up -d" + ] + }, + { + "cell_type": "markdown", + "id": "afe17557", + "metadata": {}, + "source": [ + "### 3. Setup NVIDIA Inference Microservices (NIMs)\n", + "\n", + "NIMs provide optimized inference for AI models. For multimodal RAG, we need:\n", + "- **VLM (Vision-Language Model)**: `nvidia/nemotron-nano-12b-v2-vl` for understanding images and generating responses\n", + "- **Embedding Model**: `llama-3.2-nemoretriever-1b-vlm-embed-v1` for creating multimodal embeddings" + ] + }, + { + "cell_type": "markdown", + "id": "89a135eb", + "metadata": {}, + "source": [ + "#### Deploy On-Premise Models\n", + "\n", + "This section deploys NIMs locally using Docker. Models will be cached to avoid re-downloading." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b3d2e5c", + "metadata": {}, + "outputs": [], + "source": [ + "# Create the model cache directory\n", + "!mkdir -p ~/.cache/model-cache" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "390df52d", + "metadata": {}, + "outputs": [], + "source": [ + "# Set the MODEL_DIRECTORY environment variable to specify where models are cached\n", + "import os\n", + "\n", + "os.environ[\"MODEL_DIRECTORY\"] = os.path.expanduser(\"~/.cache/model-cache\")\n", + "print(\"MODEL_DIRECTORY set to:\", os.environ[\"MODEL_DIRECTORY\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "62a9946a", + "metadata": {}, + "outputs": [], + "source": [ + "# Deploy NIMs with VLM and embedding profiles\n", + "# ⚠️ WARNING: This may take 10-20 minutes as models download (~10GB+)\n", + "# If the kernel times out, just rerun this cell - it will resume where it left off\n", + "# Select a free GPU for VLM Microservice\n", + "os.environ[\"VLM_MS_GPU_ID\"] = \"1\"\n", + "! USERID=$(id -u) docker compose --profile vlm-ingest --profile vlm-only -f ../deploy/compose/nims.yaml up -d" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e91f511a", + "metadata": {}, + "outputs": [], + "source": [ + "# Monitor the status of running containers\n", + "# Run this cell repeatedly to check if all services are healthy\n", + "# Look for STATUS showing \"healthy\" or \"Up\" for all containers\n", + "!docker ps" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cfb34a6a", + "metadata": {}, + "outputs": [], + "source": [ + "# Configure the model names and service URLs for the RAG pipeline\n", + "# These settings tell the RAG server which models and endpoints to use\n", + "\n", + "# VLM (Vision-Language Model) configuration\n", + "os.environ[\"APP_VLM_MODELNAME\"] = \"nvidia/nemotron-nano-12b-v2-vl\"\n", + "os.environ[\"APP_VLM_SERVERURL\"] = \"http://vlm-ms:8000/v1\"\n", + "\n", + "# Multimodal embedding model configuration\n", + "os.environ[\"APP_EMBEDDINGS_MODELNAME\"] = \"nvidia/llama-nemotron-embed-vl-1b-v2\"\n", + "os.environ[\"APP_EMBEDDINGS_SERVERURL\"] = \"nemotron-vlm-embedding-ms:8000/v1\"\n", + "os.environ[\"ENABLE_VLM_INFERENCE\"] = \"true\"\n", + "os.environ[\"VLM_TO_LLM_FALLBACK\"] = \"false\"\n", + "os.environ[\"ENABLE_RERANKER\"] = \"false\"" + ] + }, + { + "cell_type": "markdown", + "id": "e62c7037", + "metadata": {}, + "source": [ + "#### Cloud based deployment\n", + "Using NVIDIA hosted cloud model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82084d4d", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# OCR and document processing endpoints - cloud hosted\n", + "os.environ[\"OCR_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr\"\n", + "os.environ[\"OCR_INFER_PROTOCOL\"] = \"http\"\n", + "os.environ[\"OCR_MODEL_NAME\"] = \"scene_text_ensemble\"\n", + "os.environ[\"YOLOX_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3\"\n", + "os.environ[\"YOLOX_INFER_PROTOCOL\"] = \"http\"\n", + "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1\"\n", + "os.environ[\"YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL\"] = \"http\"\n", + "os.environ[\"YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT\"] = \"https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1\"\n", + "os.environ[\"YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL\"] = \"http\"\n", + "os.environ[\"APP_NVINGEST_CAPTIONENDPOINTURL\"] = \"https://integrate.api.nvidia.com/v1/chat/completions\"\n", + "\n", + "# VLM Model configuration - cloud hosted\n", + "os.environ[\"APP_VLM_MODELNAME\"] = \"nvidia/nemotron-nano-12b-v2-vl\"\n", + "os.environ[\"APP_VLM_SERVERURL\"] = \"https://integrate.api.nvidia.com/v1\"\n", + "os.environ[\"APP_LLM_SERVERURL\"] = \"\"\n", + "\n", + "# Multimodal embedding model configuration - cloud hosted\n", + "os.environ[\"APP_EMBEDDINGS_MODELNAME\"] = \"nvidia/llama-nemotron-embed-vl-1b-v2\"\n", + "os.environ[\"APP_EMBEDDINGS_SERVERURL\"] = \"https://integrate.api.nvidia.com/v1\"\n", + "os.environ[\"ENABLE_VLM_INFERENCE\"] = \"true\"\n", + "os.environ[\"VLM_TO_LLM_FALLBACK\"] = \"false\"\n", + "os.environ[\"ENABLE_RERANKER\"] = \"false\"" + ] + }, + { + "cell_type": "markdown", + "id": "7cbcfa50", + "metadata": {}, + "source": [ + "### 4. Setup NVIDIA Ingest Runtime\n", + "\n", + "NVIDIA Ingest processes documents to extract text, images, and other elements. We'll configure it to:\n", + "- Extract images from documents\n", + "- Handle multimodal content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5e0d73f", + "metadata": {}, + "outputs": [], + "source": [ + "# Configure NVIDIA Ingest to extract and process images from documents\n", + "os.environ[\"APP_NVINGEST_STRUCTURED_ELEMENTS_MODALITY\"] = \"\" # No special handling for structured elements\n", + "os.environ[\"APP_NVINGEST_IMAGE_ELEMENTS_MODALITY\"] = \"image\" # Process image elements as images\n", + "os.environ[\"APP_NVINGEST_EXTRACTIMAGES\"] = \"True\" # Extract images from documents\n", + "\n", + "# Start the ingestor server with Redis\n", + "! docker compose -f ../deploy/compose/docker-compose-ingestor-server.yaml up -d --build" + ] + }, + { + "cell_type": "markdown", + "id": "da1bd9a3", + "metadata": {}, + "source": [ + "### 5. Setup the NVIDIA RAG Server\n", + "\n", + "The RAG server provides the main API endpoints for search and generation. It orchestrates all the components (embeddings, vector DB, VLM) to deliver intelligent responses." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38ba7752", + "metadata": {}, + "outputs": [], + "source": [ + "# Start the RAG server (accessible at localhost:8081)\n", + "os.environ[\"APP_RANKING_SERVERURL\"] = \"\"\n", + "! docker compose -f ../deploy/compose/docker-compose-rag-server.yaml up -d --build" + ] + }, + { + "cell_type": "markdown", + "id": "ce492ce3", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 📚 Document Ingestion Workflow\n", + "\n", + "Now that all services are running, let's ingest documents into a collection.\n", + "\n", + "### 6. Create a Collection\n", + "\n", + "A collection is a logical grouping of documents in the vector database. Think of it as a database table optimized for similarity search." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a8611aa1", + "metadata": {}, + "outputs": [], + "source": [ + "# Install aiohttp for async HTTP requests\n", + "! uv pip install aiohttp\n", + "\n", + "# Configure the ingestor server URL\n", + "# Use \"ingestor-server\" when running in AI Workbench, otherwise \"localhost\"\n", + "IPADDRESS = (\n", + " \"ingestor-server\"\n", + " if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\"\n", + " else \"localhost\"\n", + ")\n", + "INGESTOR_SERVER_PORT = \"8082\"\n", + "BASE_URL = f\"http://{IPADDRESS}:{INGESTOR_SERVER_PORT}\"\n", + "\n", + "async def print_response(response):\n", + " \"\"\"Helper function to pretty-print API responses.\"\"\"\n", + " try:\n", + " response_json = await response.json()\n", + " print(json.dumps(response_json, indent=2))\n", + " except aiohttp.ClientResponseError:\n", + " print(await response.text())\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "688bc70f", + "metadata": {}, + "outputs": [], + "source": [ + "# Define a unique name for your collection\n", + "# Change this if you want to create a different collection\n", + "collection_name = \"multimodal_query\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24378f6f", + "metadata": {}, + "outputs": [], + "source": [ + "import aiohttp\n", + "import json\n", + "\n", + "\n", + "async def create_collection(\n", + " collection_name: str | None = None,\n", + " metadata_schema: list = [],\n", + "):\n", + " \"\"\"\n", + " Create a new collection in the vector database.\n", + " \n", + " Args:\n", + " collection_name: Unique identifier for the collection\n", + " metadata_schema: Optional schema for metadata fields\n", + " \"\"\"\n", + " data = {\n", + " \"collection_name\": collection_name,\n", + " \"metadata_schema\": metadata_schema,\n", + " }\n", + "\n", + " HEADERS = {\"Content-Type\": \"application/json\"}\n", + "\n", + " async with aiohttp.ClientSession() as session:\n", + " try:\n", + " async with session.post(\n", + " f\"{BASE_URL}/v1/collection\", json=data, headers=HEADERS\n", + " ) as response:\n", + " await print_response(response)\n", + " except aiohttp.ClientError as e:\n", + " return 500, {\"error\": str(e)}\n", + "\n", + "\n", + "# Create the collection\n", + "# The embedding dimension is 2048 for the multimodal embedding model we're using\n", + "await create_collection(\n", + " collection_name=collection_name,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a29f4633", + "metadata": {}, + "outputs": [], + "source": [ + "# Specify the documents to upload\n", + "# This PDF contains product images with pricing information\n", + "FILEPATHS = [\n", + " \"../data/multimodal/product_catalog.pdf\",\n", + "]\n", + "\n", + "async def upload_documents(collection_name: str = \"\"):\n", + " \"\"\"\n", + " Upload and process documents into the collection.\n", + " \n", + " This will:\n", + " 1. Extract text and images from the PDFs\n", + " 2. Chunk the content for optimal retrieval\n", + " 3. Generate multimodal embeddings\n", + " 4. Store everything in the vector database\n", + " \"\"\"\n", + " data = {\n", + " \"collection_name\": collection_name,\n", + " \"blocking\": False, # Async upload - use status API to check progress\n", + " \"split_options\": {\n", + " \"chunk_size\": 512, # Characters per chunk\n", + " \"chunk_overlap\": 150 # Overlap between chunks for context\n", + " },\n", + " \"generate_summary\": False # Set to True to generate document summaries\n", + " }\n", + "\n", + " form_data = aiohttp.FormData()\n", + " \n", + " # Add all PDF files to the form data\n", + " for file_path in FILEPATHS:\n", + " form_data.add_field(\"documents\", open(file_path, \"rb\"), \n", + " filename=os.path.basename(file_path), \n", + " content_type=\"application/pdf\")\n", + "\n", + " form_data.add_field(\"data\", json.dumps(data), content_type=\"application/json\")\n", + "\n", + " async with aiohttp.ClientSession() as session:\n", + " try:\n", + " # Use POST for new uploads, PATCH for re-ingesting existing documents\n", + " async with session.post(f\"{BASE_URL}/v1/documents\", data=form_data) as response:\n", + " await print_response(response)\n", + " response_json = await response.json()\n", + " return response_json\n", + " except aiohttp.ClientError as e:\n", + " print(f\"Error uploading documents: {e}\")\n", + " return None\n", + "\n", + "# Upload the documents and get the task ID for tracking progress\n", + "upload_response = await upload_documents(collection_name=collection_name)\n", + "task_id = upload_response.get(\"task_id\") if upload_response else None\n", + "print(f\"\\nTask ID for tracking: {task_id}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2234e059", + "metadata": {}, + "outputs": [], + "source": [ + "async def get_task_status(task_id: str):\n", + " \"\"\"\n", + " Check the status of an asynchronous ingestion task.\n", + " \n", + " Possible statuses:\n", + " - \"pending\": Task is queued\n", + " - \"processing\": Currently processing documents\n", + " - \"completed\": Successfully finished\n", + " - \"failed\": Error occurred\n", + " \"\"\"\n", + " params = {\n", + " \"task_id\": task_id,\n", + " }\n", + "\n", + " HEADERS = {\"Content-Type\": \"application/json\"}\n", + "\n", + " async with aiohttp.ClientSession() as session:\n", + " try:\n", + " async with session.get(\n", + " f\"{BASE_URL}/v1/status\", params=params, headers=HEADERS\n", + " ) as response:\n", + " await print_response(response)\n", + " except aiohttp.ClientError as e:\n", + " return 500, {\"error\": str(e)}\n", + "\n", + "\n", + "# Check the ingestion status\n", + "# Run this cell multiple times until status shows \"completed\"\n", + "await get_task_status(\n", + " task_id=[task_id]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e2c1f1a8", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 🔍 Querying with Multimodal Inputs\n", + "\n", + "Now that documents are ingested, let's query them using both text and images!\n", + "\n", + "### 7. Using the Search and Generate APIs\n", + "\n", + "We'll demonstrate two approaches:\n", + "1. **Search API**: Find relevant documents without generating a response\n", + "2. **Generate API**: Get an AI-generated answer with citations" + ] + }, + { + "cell_type": "markdown", + "id": "3990ca33", + "metadata": {}, + "source": [ + "#### Prepare a Multimodal Query\n", + "\n", + "To query with an image, we need to:\n", + "1. Convert the image to base64 encoding\n", + "2. Format it according to the OpenAI vision API format\n", + "3. Combine it with a text prompt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "02dde830", + "metadata": {}, + "outputs": [], + "source": [ + "! uv pip install requests httpx\n", + "import base64\n", + "import requests\n", + "from IPython.display import Image, Markdown, display\n", + "\n", + "def get_base64_image(image_source: str) -> str:\n", + " \"\"\"\n", + " Convert an image to base64 encoding.\n", + " \n", + " Args:\n", + " image_source: Local file path or URL to the image\n", + " \n", + " Returns:\n", + " Base64 encoded string of the image\n", + " \"\"\"\n", + " if image_source.startswith(('http://', 'https://')):\n", + " # Download image from URL\n", + " response = requests.get(image_source)\n", + " return base64.b64encode(response.content).decode()\n", + " else:\n", + " # Read local file\n", + " with open(image_source, \"rb\") as image_file:\n", + " return base64.b64encode(image_file.read()).decode()\n", + "\n", + "# Convert the query image to base64\n", + "# Try different images to test different queries:\n", + "image_b64 = get_base64_image(\"../data/multimodal/Creme_clutch_purse1-small.jpg\")\n", + "\n", + "# Display the query image for reference\n", + "query_image_path = \"../data/multimodal/Creme_clutch_purse1-small.jpg\"\n", + "print(\"📷 Query Image:\")\n", + "display(Image(filename=query_image_path, width=300))\n", + "\n", + "# Format as a data URL\n", + "image_input = f\"data:image/png;base64,{image_b64}\"\n", + "\n", + "# Create the multimodal query with text + image\n", + "# This follows the OpenAI vision API format\n", + "query_1 = \"What material is this made of?\"\n", + "image_query = [\n", + " {\"type\": \"text\", \"text\": query_1},\n", + " {\n", + " \"type\": \"image_url\",\n", + " \"image_url\": {\n", + " \"url\": image_input,\n", + " \"detail\": \"auto\" # Let the model decide the appropriate detail level\n", + " }\n", + " }\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c311f9d0", + "metadata": {}, + "outputs": [], + "source": [ + "import httpx\n", + "import json\n", + "from IPython.display import Image, Markdown, display\n", + "\n", + "RAG_BASE_URL = \"http://localhost:8081\"\n", + "\n", + "async def search_documents(payload):\n", + " \"\"\"\n", + " Search for relevant documents using a multimodal query.\n", + " \n", + " This performs similarity search in the vector database and optionally\n", + " reranks results for better relevance.\n", + " \"\"\"\n", + " search_url = f\"{RAG_BASE_URL}/v1/search\"\n", + " \n", + " async with httpx.AsyncClient(timeout=300.0) as client:\n", + " try:\n", + " response = await client.post(url=search_url, json=payload)\n", + " response.raise_for_status()\n", + " \n", + " search_results = response.json()\n", + " print(\"Search Results:\")\n", + " \n", + " # Display search results with nice formatting\n", + " if \"results\" in search_results:\n", + " for idx, result in enumerate(search_results[\"results\"]):\n", + " doc_type = result.get(\"document_type\", \"text\")\n", + " content = result.get(\"content\", \"\")\n", + " doc_name = result.get(\"document_name\", f\"Result {idx + 1}\")\n", + " score = result.get(\"score\", \"N/A\")\n", + " \n", + " display(Markdown(f\"**Result {idx + 1}: {doc_name} (Score: {score})**\"))\n", + " try:\n", + " if doc_type == \"image\":\n", + " # Display image results\n", + " image_bytes = base64.b64decode(content)\n", + " display(Image(data=image_bytes))\n", + " else:\n", + " # Display text results\n", + " display(Markdown(f\"```\\n{content}\\n```\"))\n", + " except Exception as e:\n", + " print(f\"Error displaying content: {e}\")\n", + " display(Markdown(f\"```\\n{content}\\n```\"))\n", + " \n", + " return search_results\n", + " \n", + " except httpx.HTTPStatusError as e:\n", + " print(f\"HTTP error occurred: {e.response.status_code} - {e.response.text}\")\n", + " except httpx.RequestError as e:\n", + " print(f\"An error occurred while requesting {e.request.url!r}: {e}\")\n", + " except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + "\n", + "# Configure the search parameters\n", + "search_payload = {\n", + " \"query\": image_query, # Our multimodal query (text + image)\n", + " \"messages\": [], # No conversation history\n", + " \"use_knowledge_base\": True, # Search the vector database\n", + " \"collection_names\": [collection_name], # Which collection to search\n", + " \"vdb_top_k\": 5, # Retrieve top 5 results from vector DB\n", + " \"vdb_endpoint\": \"http://milvus:19530\", # Milvus connection string\n", + " \"enable_reranker\": False, # Set to True for better relevance (slower)\n", + " \"reranker_top_k\": 3, # If reranker enabled, return top 3\n", + " \"filter_expr\": \"\", # Optional metadata filter\n", + "}\n", + "\n", + "# Execute the search\n", + "print(\"🔍 Searching for documents matching the query...\\n\")\n", + "search_result = await search_documents(search_payload)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc5b1545", + "metadata": {}, + "outputs": [], + "source": [ + "import base64\n", + "import json\n", + "from IPython.display import Image, Markdown, display\n", + "\n", + "\n", + "async def print_streaming_response_and_citations(response_generator):\n", + " \"\"\"\n", + " Helper function to display streaming responses with citations.\n", + " \n", + " This function:\n", + " 1. Streams the AI-generated response token by token\n", + " 2. Extracts citations from the first chunk\n", + " 3. Displays citations (text or images) after the response completes\n", + " \"\"\"\n", + " first_chunk_data = None\n", + " \n", + " async for chunk in response_generator:\n", + " # Parse Server-Sent Events (SSE) format\n", + " if chunk.startswith(\"data: \"):\n", + " chunk = chunk[len(\"data: \") :].strip()\n", + " if not chunk:\n", + " continue\n", + " \n", + " try:\n", + " data = json.loads(chunk)\n", + " except Exception as e:\n", + " print(f\"JSON decode error: {e}\")\n", + " continue\n", + " \n", + " choices = data.get(\"choices\", [])\n", + " if not choices:\n", + " continue\n", + " \n", + " # Save the first chunk with citations\n", + " if first_chunk_data is None and data.get(\"citations\"):\n", + " first_chunk_data = data\n", + " \n", + " # Print streaming text\n", + " delta = choices[0].get(\"delta\", {})\n", + " text = delta.get(\"content\")\n", + " if not text:\n", + " message = choices[0].get(\"message\", {})\n", + " text = message.get(\"content\", \"\")\n", + " print(text, end=\"\", flush=True)\n", + " \n", + " print() # Newline after streaming\n", + "\n", + " # Display citations after streaming is done\n", + " if first_chunk_data and first_chunk_data.get(\"citations\"):\n", + " print(\"\\n📚 Citations:\")\n", + " citations = first_chunk_data[\"citations\"]\n", + " for idx, citation in enumerate(citations.get(\"results\", [])):\n", + " doc_type = citation.get(\"document_type\", \"text\")\n", + " content = citation.get(\"content\", \"\")\n", + " doc_name = citation.get(\"document_name\", f\"Citation {idx + 1}\")\n", + " display(Markdown(f\"**Citation {idx + 1}: {doc_name}**\"))\n", + " try:\n", + " # Try to display as image\n", + " image_bytes = base64.b64decode(content)\n", + " display(Image(data=image_bytes))\n", + " except Exception:\n", + " # Fall back to text display\n", + " display(Markdown(f\"```\\n{content}\\n```\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31071359", + "metadata": {}, + "outputs": [], + "source": [ + "import httpx\n", + "\n", + "# Configure RAG server URL\n", + "IPADDRESS = \"rag-server\" if os.environ.get(\"AI_WORKBENCH\", \"false\") == \"true\" else \"localhost\"\n", + "RAG_SERVER_PORT = \"8081\"\n", + "RAG_BASE_URL = f\"http://{IPADDRESS}:{RAG_SERVER_PORT}\"\n", + "generate_url = f\"{RAG_BASE_URL}/v1/generate\"\n", + "\n", + "async def generate_answer(payload):\n", + " \"\"\"\n", + " Generate an AI answer using the RAG pipeline.\n", + " \n", + " This function:\n", + " 1. Sends the query to the RAG server\n", + " 2. Retrieves relevant context from the vector database\n", + " 3. Streams the AI-generated response\n", + " 4. Displays citations (sources) used to generate the answer\n", + " \"\"\"\n", + " rag_response = \"\"\n", + " citations = []\n", + " is_first_token = True\n", + "\n", + " async with httpx.AsyncClient(timeout=300.0) as client:\n", + " try:\n", + " async with client.stream(\"POST\", url=generate_url, json=payload) as response:\n", + " # Raise an exception for bad status codes like 4xx or 5xx\n", + " response.raise_for_status()\n", + "\n", + " # Iterate over the streaming response\n", + " async for line in response.aiter_lines():\n", + " if line.startswith(\"data: \"):\n", + " json_str = line[6:].strip()\n", + " if not json_str:\n", + " continue\n", + "\n", + " try:\n", + " data = json.loads(json_str)\n", + "\n", + " # Extract and display the streaming response\n", + " message = data.get(\"choices\", [{}])[0].get(\"message\", {}).get(\"content\", \"\")\n", + " if message:\n", + " rag_response += message\n", + "\n", + " # Extract and display citations from the first chunk\n", + " if is_first_token and data.get(\"citations\"):\n", + " print(\"\\n📚 Citations:\")\n", + " citations = data[\"citations\"]\n", + " for idx, citation in enumerate(citations.get(\"results\", [])):\n", + " doc_type = citation.get(\"document_type\", \"text\")\n", + " content = citation.get(\"content\", \"\")\n", + " doc_name = citation.get(\"document_name\", f\"Citation {idx + 1}\")\n", + " display(Markdown(f\"**Citation {idx + 1}: {doc_name}**\"))\n", + " try:\n", + " # Display image citations\n", + " image_bytes = base64.b64decode(content)\n", + " display(Image(data=image_bytes))\n", + " except Exception:\n", + " # Display text citations\n", + " display(Markdown(f\"```\\n{content}\\n```\"))\n", + " is_first_token = False\n", + "\n", + " # Check if streaming is complete\n", + " finish_reason = data.get(\"choices\", [{}])[0].get(\"finish_reason\")\n", + " if finish_reason == \"stop\":\n", + " return rag_response\n", + "\n", + " except json.JSONDecodeError:\n", + " print(f\"Skipping malformed JSON line: {json_str}\")\n", + " continue\n", + " \n", + " except httpx.HTTPStatusError as e:\n", + " print(f\"HTTP error occurred: {e.response.status_code} - {e.response.text}\")\n", + " except httpx.RequestError as e:\n", + " print(f\"An error occurred while requesting {e.request.url!r}: {e}\")\n", + " except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + "\n", + " print(\"\\n✅ Response complete!\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3049696", + "metadata": {}, + "outputs": [], + "source": [ + "# Format the query as a chat message\n", + "messages = [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": image_query # Our multimodal query (text + image)\n", + " }\n", + "]\n", + "\n", + "# Configure the generate API parameters\n", + "payload = {\n", + " \"messages\": messages, # Chat conversation\n", + " \"use_knowledge_base\": True, # Enable RAG - use vector DB for context\n", + " \"temperature\": 0.2, # Lower = more deterministic, higher = more creative\n", + " \"top_p\": 0.7, # Nucleus sampling parameter\n", + " \"max_tokens\": 1024, # Maximum response length\n", + " \"reranker_top_k\": 2, # Keep top 2 results after reranking\n", + " \"vdb_top_k\": 10, # Retrieve top 10 from vector DB initially\n", + " \"vdb_endpoint\": \"http://milvus:19530\", # Milvus connection\n", + " \"collection_names\": [collection_name], # Which collection to search\n", + " \"enable_query_rewriting\": True, # Improve query before searching\n", + " \"enable_citations\": True, # Include source citations in response\n", + " \"stop\": [], # Optional stop sequences\n", + " \"filter_expr\": \"\", # Optional metadata filter \n", + "}\n", + "\n", + "# Generate the answer with RAG\n", + "print(\"🤖 Generating answer with RAG...\\n\")\n", + "await generate_answer(payload)" + ] + }, + { + "cell_type": "markdown", + "id": "dcddc78d", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## 🎉 Summary\n", + "\n", + "Congratulations! You've successfully:\n", + "\n", + "✅ **Set up the infrastructure**: Deployed Milvus vector DB, NVIDIA NIMs, and RAG services \n", + "✅ **Ingested multimodal documents**: Uploaded PDFs with images and extracted their content \n", + "✅ **Created multimodal queries**: Combined text and images in your search queries \n", + "✅ **Retrieved relevant context**: Used semantic search to find matching documents \n", + "✅ **Generated AI responses**: Got intelligent answers with source citations \n", + "\n", + "### Next Steps\n", + "\n", + "- **Try different queries**: Change the query text or use different query images\n", + "- **Upload more documents**: Add more PDFs to enrich your knowledge base\n", + "- **Experiment with parameters**: Adjust `temperature`, `top_k`, reranker settings\n", + "- **Build applications**: Integrate these APIs into your own applications\n", + "\n", + "### Cleanup\n", + "\n", + "To stop all services and free up resources:\n", + "\n", + "```bash\n", + "cd ../deploy/compose\n", + "docker compose -f docker-compose-rag-server.yaml down\n", + "docker compose -f docker-compose-ingestor-server.yaml down\n", + "docker compose -f nims.yaml down\n", + "docker compose -f vectordb.yaml down\n", + "```\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/notebooks/launchable.ipynb b/notebooks/launchable.ipynb index 1bc8e793b..22e293f90 100644 --- a/notebooks/launchable.ipynb +++ b/notebooks/launchable.ipynb @@ -792,7 +792,7 @@ " print(f\" • {warn}\")\n", " print(\"\\n\" + \"=\" * 70)\n", " print(\"Please fix the above errors before continuing.\")\n", - " print(\"See: https://docs.nvidia.com/ai-blueprints/rag/latest/support-matrix.html\")\n", + " print(\"See: https://docs.nvidia.com/rag/latest/support-matrix.html\")\n", " print(\"=\" * 70)\n", "elif warnings:\n", " print(\"\\n✅ REQUIREMENTS MET (with warnings)\")\n", @@ -2030,9 +2030,9 @@ "\n", "## 📚 Additional Resources\n", "\n", - "- **Documentation**: https://docs.nvidia.com/ai-blueprints/rag/latest/\n", + "- **Documentation**: https://docs.nvidia.com/rag/latest/\n", "- **GitHub**: https://github.com/NVIDIA-AI-Blueprints/rag\n", - "- **Support Matrix**: https://docs.nvidia.com/ai-blueprints/rag/latest/support-matrix.html\n", + "- **Support Matrix**: https://docs.nvidia.com/rag/latest/support-matrix.html\n", "\n", "---\n", "\n", diff --git a/notebooks/rag_event_ingest.ipynb b/notebooks/rag_event_ingest.ipynb index b2bde4727..a38f976af 100644 --- a/notebooks/rag_event_ingest.ipynb +++ b/notebooks/rag_event_ingest.ipynb @@ -1,793 +1,793 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Document Continuous Ingestion from Object Storage\n", - "\n", - "## Purpose\n", - "\n", - "This notebook demonstrates an **automated document ingestion pipeline** that:\n", - "\n", - "1. Monitors emulated object storage for new uploads via Kafka events\n", - "2. Routes documents to appropriate AI services for indexing\n", - "5. Enables RAG Agent for semantic search and contextual Q&A over all ingested content\n", - "\n", - "## What Gets Deployed\n", - "\n", - "1. **NVIDIA RAG** - Document indexing, vector search, and AI-powered Q&A (NIMs, Milvus, Ingestor)\n", - "2. **Continuous Ingestion** - Event-driven ingestion pipeline (Kafka, MinIO, Consumer)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "### Hardware\n", - "- **GPU**: 2x RTX PRO 6000 Blackwell or 2x H100\n", - "\n", - "#### Default GPU Assignment\n", - "\n", - "| GPU | Service |\n", - "|-----|---------|\n", - "| 0 | RAG NIMs (Embedding, Reranker) |\n", - "| 1 | RAG LLM NIM (Llama-3.3-Nemotron-Super-49B) |\n", - "\n", - "\n", - "### Software (pre-installed required)\n", - "- Ubuntu 22.04 or later\n", - "- Docker 24.0+ with Docker Compose v2\n", - "- NVIDIA Driver 570+\n", - "- NVIDIA Container Toolkit\n", - "\n", - "### API Keys\n", - "\n", - "\n", - "\n", - "\n", - "
KeyPurposeHow to Get
NGC_API_KEYDocker login, NIM deploymentsNGC Portal → Generate API Key
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Table of Contents\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
SectionDescription
SetupClone repo, install deps, set API keys, load helpers
Deploy RAGNIMs, Vector DB, Ingestor, RAG Server
Deploy Continuous IngestionKafka, MinIO, Consumer
TestingUpload documents, query RAG
Clean UpStop services, clean data
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References\n", - "\n", - "- **RAG Blueprint**: [NVIDIA RAG Documentation](https://github.com/NVIDIA-AI-Blueprints/rag/blob/develop/docs/deploy-docker-self-hosted.md)\n", - "- **NIM**: [NVIDIA NIM Documentation](https://docs.nvidia.com/nim/index.html)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Setup\n", - "\n", - "Clone the repository, configure API keys, and load helper functions.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Clone Repository\n", - "\n", - "Clone the RAG Blueprint repo to `~/rag`. This includes the consumer source code, deploy configs, and sample test data.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import subprocess, sys, os, shutil\n", - "\n", - "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", - "RAG_REPO_URL = \"https://github.com/NVIDIA-AI-Blueprints/rag.git\"\n", - "\n", - "# Ensure git-lfs is installed before any LFS operations\n", - "if not shutil.which(\"git-lfs\"):\n", - " print(\"[INSTALLING] git-lfs...\")\n", - " subprocess.run(\"sudo apt-get update && sudo apt-get install -y git-lfs && git lfs install\", shell=True, check=True)\n", - "else:\n", - " print(\"[OK] git-lfs found\")\n", - "\n", - "# Clone from correct branch (skip if already exists)\n", - "if not os.path.exists(RAG_REPO_DIR):\n", - " subprocess.run(f\"git clone {RAG_REPO_URL} {RAG_REPO_DIR}\", shell=True, check=True)\n", - "else:\n", - " print(f\"[OK] RAG repo already exists: {RAG_REPO_DIR}\")\n", - "subprocess.run(\"git lfs pull\", shell=True, cwd=RAG_REPO_DIR, check=True)\n", - "\n", - "# Verify\n", - "for path in [\"deploy/compose\", \"examples/rag_event_ingest/kafka_consumer\", \"examples/rag_event_ingest/data\"]:\n", - " status = \"[OK]\" if os.path.exists(os.path.join(RAG_REPO_DIR, path)) else \"[MISSING]\"\n", - " print(f\" {status} {path}\")\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Install Dependencies\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "! python3 -m ensurepip --upgrade" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Ensure pip is available (some minimal Python installs lack it)\n", - "subprocess.run([sys.executable, \"-m\", \"ensurepip\", \"--upgrade\"], capture_output=True)\n", - "\n", - "def check_install_system_pkg(cmd: str, install_cmd: str):\n", - " if shutil.which(cmd):\n", - " print(f\" [OK] {cmd} found\")\n", - " return True\n", - " print(f\" [INSTALLING] {cmd}...\")\n", - " result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True)\n", - " if result.returncode == 0:\n", - " print(f\" [OK] {cmd} installed\")\n", - " return True\n", - " print(f\" [ERROR] Failed to install {cmd}. Please install manually: {install_cmd}\")\n", - " return False\n", - "\n", - "check_install_system_pkg(\"git\", \"sudo apt-get update && sudo apt-get install -y git\")\n", - "\n", - "# Install Python packages\n", - "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\", \"aiohttp\", \"requests\", \"python-dotenv\", \"pyyaml\"])\n", - "print(\"[OK] Ready\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. Set API Keys\n", - "\n", - "Configure NGC API key for NIM deployments.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import getpass\n", - "\n", - "def set_api_key(env_var: str, prompt: str, required: bool = True):\n", - " if os.environ.get(env_var):\n", - " print(f\" [OK] {env_var} already set ({os.environ[env_var][:10]}...)\")\n", - " return True\n", - " key = getpass.getpass(prompt)\n", - " if key:\n", - " os.environ[env_var] = key\n", - " print(f\" [OK] {env_var} set\")\n", - " return True\n", - " if required:\n", - " print(f\" [ERROR] {env_var} is required\")\n", - " return False\n", - " print(f\" [SKIP] {env_var} (optional)\")\n", - " return True\n", - "\n", - "set_api_key(\"NGC_API_KEY\", \"Enter NGC_API_KEY (starts with 'nvapi-'): \", required=True)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. Helper Functions\n", - "\n", - "Shared utilities for deployment, file upload, status checks, and RAG queries.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Install dependencies\n", - "import sys\n", - "!{sys.executable} -m pip install -q minio aiohttp requests python-dotenv" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os, sys, json, re, subprocess, time, socket, asyncio\n", - "import aiohttp, requests\n", - "from typing import List, Optional, Dict\n", - "\n", - "try:\n", - " from minio import Minio\n", - " from minio.error import S3Error\n", - "except ImportError:\n", - " subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\"])\n", - " from minio import Minio\n", - " from minio.error import S3Error\n", - "\n", - "# =============================================================================\n", - "# CONFIGURATION\n", - "# =============================================================================\n", - "\n", - "# Paths relative to RAG repo root\n", - "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", - "EXAMPLE_DIR = os.path.join(RAG_REPO_DIR, \"examples/rag_event_ingest\")\n", - "AIDP_COMPOSE_FILE = os.path.join(EXAMPLE_DIR, \"deploy/docker-compose.yaml\")\n", - "DATA_DIR = os.path.join(EXAMPLE_DIR, \"data\")\n", - "RAG_SERVER_URL = \"http://localhost:8081\"\n", - "INGESTOR_URL = \"http://localhost:8082\"\n", - "\n", - "LOCAL_NIM_CACHE = os.path.expanduser(\"~/.cache/nim\")\n", - "\n", - "MINIO_ENDPOINT = \"localhost:9201\"\n", - "MINIO_ACCESS_KEY = \"minioadmin\"\n", - "MINIO_SECRET_KEY = \"minioadmin\"\n", - "MINIO_BUCKET = \"aidp-bucket\"\n", - "MINIO_COLLECTION = \"aidp_bucket\"\n", - "MINIO_CONSOLE_PORT = 9211\n", - "\n", - "# =============================================================================\n", - "# SHARED UTILITIES\n", - "# =============================================================================\n", - "\n", - "def run_command(cmd: str, capture: bool = False) -> Optional[str]:\n", - " \"\"\"Execute a shell command and print it.\"\"\"\n", - " print(f\"$ {cmd}\")\n", - " result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)\n", - " return result.stdout if capture else None\n", - "\n", - "def get_host_ip() -> str:\n", - " \"\"\"Get host IP address for external access URLs.\"\"\"\n", - " try:\n", - " s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n", - " s.connect((\"8.8.8.8\", 80))\n", - " ip = s.getsockname()[0]\n", - " s.close()\n", - " return ip\n", - " except OSError:\n", - " return \"localhost\"\n", - "\n", - "def get_minio_client() -> Minio:\n", - " \"\"\"Create MinIO client for AIDP bucket operations.\"\"\"\n", - " return Minio(MINIO_ENDPOINT, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False)\n", - "\n", - "def upload_file(local_path: str, object_name: Optional[str] = None) -> bool:\n", - " \"\"\"Upload a local file to MinIO AIDP bucket.\"\"\"\n", - " if not os.path.exists(local_path):\n", - " print(f\"[ERROR] File not found: {local_path}\")\n", - " return False\n", - " obj = object_name or os.path.basename(local_path)\n", - " try:\n", - " client = get_minio_client()\n", - " if not client.bucket_exists(MINIO_BUCKET):\n", - " client.make_bucket(MINIO_BUCKET)\n", - " client.fput_object(MINIO_BUCKET, obj, local_path)\n", - " print(f\"[OK] Uploaded: {obj}\")\n", - " return True\n", - " except S3Error as e:\n", - " print(f\"[ERROR] {e}\")\n", - " return False\n", - "\n", - "def verify_file_in_storage(object_name: str, bucket: str = MINIO_BUCKET) -> bool:\n", - " \"\"\"Check if a file exists in MinIO bucket and print verification status.\"\"\"\n", - " try:\n", - " client = get_minio_client()\n", - " stat = client.stat_object(bucket, object_name)\n", - " print(f\"[OK] File verified in storage:\")\n", - " print(f\" Bucket: {bucket}\")\n", - " print(f\" Object: {object_name}\")\n", - " print(f\" Size: {stat.size:,} bytes\")\n", - " print(f\" Modified: {stat.last_modified}\")\n", - " return True\n", - " except S3Error as e:\n", - " print(f\"[ERROR] File not found in storage: {object_name}\")\n", - " print(f\" Error: {e}\")\n", - " return False\n", - "\n", - "def get_consumer_logs(lines: int = 30) -> None:\n", - " \"\"\"Show recent Kafka consumer logs.\"\"\"\n", - " run_command(f\"docker logs kafka-consumer --tail {lines}\")\n", - "\n", - "async def query_rag(question: str, collection: str = None) -> Optional[str]:\n", - " \"\"\"Query RAG system and print the answer.\"\"\"\n", - " coll = collection or MINIO_COLLECTION\n", - " print(f\"Q: {question}\\nCollection: {coll}\\n\" + \"-\" * 40)\n", - "\n", - " payload = {\n", - " \"messages\": [{\"role\": \"user\", \"content\": question}],\n", - " \"use_knowledge_base\": True,\n", - " \"collection_names\": [coll],\n", - " }\n", - " try:\n", - " async with aiohttp.ClientSession() as session:\n", - " async with session.post(\n", - " f\"{RAG_SERVER_URL}/generate\", json=payload,\n", - " timeout=aiohttp.ClientTimeout(total=120),\n", - " ) as resp:\n", - " text = await resp.text()\n", - " # Parse SSE response: extract content from each \"data: {...}\" line\n", - " chunks = []\n", - " for line in text.split(\"\\n\"):\n", - " if not line.startswith(\"data: \") or line[6:] == \"[DONE]\":\n", - " continue\n", - " try:\n", - " msg = json.loads(line[6:]).get(\"choices\", [{}])[0].get(\"message\", {})\n", - " if msg.get(\"content\"):\n", - " chunks.append(msg[\"content\"])\n", - " except json.JSONDecodeError:\n", - " pass\n", - " answer = \"\".join(chunks)\n", - " print(f\"Answer: {answer}\")\n", - " return answer\n", - " except aiohttp.ClientError as e:\n", - " print(f\"[ERROR] {e}\")\n", - " return None\n", - "\n", - "print(f\"[OK] Helpers loaded | Host IP: {get_host_ip()}\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Deploy NVIDIA RAG\n", - "\n", - "Deploy the NVIDIA RAG: NIMs (LLM, Embedding, Reranker), Milvus vector database, Ingestor server, and RAG server.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ngc_key = os.environ.get(\"NGC_API_KEY\")\n", - "if not ngc_key:\n", - " raise RuntimeError(\"NGC_API_KEY not set! Run the API keys cell first.\")\n", - "\n", - "os.chdir(RAG_REPO_DIR)\n", - "\n", - "# Set env vars needed by docker compose\n", - "os.environ[\"NGC_API_KEY\"] = ngc_key\n", - "os.environ[\"USERID\"] = f\"{os.getuid()}:{os.getgid()}\"\n", - "os.environ[\"COLLECTION_NAME\"] = MINIO_COLLECTION\n", - "\n", - "# Load RAG .env defaults (MODEL_DIRECTORY, etc.)\n", - "from dotenv import load_dotenv\n", - "env_file = os.path.join(RAG_REPO_DIR, \"deploy/compose/.env\")\n", - "if os.path.exists(env_file):\n", - " load_dotenv(env_file, override=False)\n", - "\n", - "# Login to nvcr.io\n", - "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", - " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "\n", - "# Deploy components\n", - "for label, compose_file in [\n", - " (\"NIMs\", \"deploy/compose/nims.yaml\"),\n", - " (\"Vector DB\", \"deploy/compose/vectordb.yaml\"),\n", - "]:\n", - " print(f\"Deploying {label}...\")\n", - " run_command(f\"USERID=$(id -u) docker compose -f {compose_file} up -d\")\n", - "\n", - "print(\"Waiting 30s for Milvus...\")\n", - "time.sleep(30)\n", - "\n", - "for label, compose_file in [\n", - " (\"Ingestor\", \"deploy/compose/docker-compose-ingestor-server.yaml\"),\n", - " (\"RAG Server\", \"deploy/compose/docker-compose-rag-server.yaml\"),\n", - "]:\n", - " print(f\"Deploying {label}...\")\n", - " run_command(f\"docker compose -f {compose_file} up -d\")\n", - "\n", - "ip = get_host_ip()\n", - "print(f\"\\nRAG deployed: http://{ip}:8081 (server) | http://{ip}:8082 (ingestor) | http://{ip}:8090 (UI)\")\n", - "print(f\"COLLECTION_NAME: {MINIO_COLLECTION}\")\n", - "print(\"Wait ~10 minutes for NIMs to load models, then run the status check cell.\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Verify RAG services are healthy. Wait ~10 minutes for NIMs to load models.\n", - "\n", - "The deployment status should be:\n", - "```\n", - "NAMES STATUS\n", - "rag-frontend Up About a minute\n", - "rag-server Up About a minute\n", - "ingestor-server Up About a minute\n", - "milvus-standalone Up 2 minutes (healthy)\n", - "milvus-etcd Up 2 minutes (healthy)\n", - "milvus-minio Up 2 minutes (healthy)\n", - "nim-llm-ms Up 2 minutes (healthy)\n", - "nemotron-embedding-ms Up 2 minutes (healthy)\n", - "nemotron-ranking-ms Up 2 minutes (healthy)\n", - "```\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check service status and print access URLs\n", - "print(\"Wait ~10 minutes for services to become healthy.\")\n", - "print(\"Run this cell again after waiting.\\n\")\n", - "\n", - "ip = get_host_ip()\n", - "for name, port, path in [\n", - " (\"RAG Server\", 8081, \"/health\"), (\"Ingestor\", 8082, \"/health\"),\n", - " (\"Frontend\", 8090, \"/\"), (\"Milvus\", 19530, \"/v1/vector/collections\"),\n", - "]:\n", - " try:\n", - " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", - " except requests.ConnectionError:\n", - " s = \"[DOWN]\"\n", - " except requests.Timeout:\n", - " s = \"[TIMEOUT]\"\n", - " print(f\" {s} {name}: http://{ip}:{port}\")\n", - "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(rag|milvus|ingestor|nim|nemotron|NAMES)'\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Deploy Continuous Ingestion from emulated object storage\n", - "\n", - "Deploy the Continuous Ingestion: Kafka message broker, MinIO object storage, and Kafka consumer for automated ingestion.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Deploy Services\n", - "\n", - "Deploy Kafka, MinIO, and the Kafka consumer." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Verify prerequisites\n", - "net_check = subprocess.run(\"docker network inspect nvidia-rag\", shell=True, capture_output=True)\n", - "if net_check.returncode != 0:\n", - " raise RuntimeError(\"nvidia-rag network not found. Deploy RAG first.\")\n", - "\n", - "ngc_key = os.environ.get(\"NGC_API_KEY\", \"\")\n", - "if not ngc_key:\n", - " raise RuntimeError(\"NGC_API_KEY not set!\")\n", - "\n", - "host_ip = get_host_ip()\n", - "\n", - "# Set environment variables for docker compose\n", - "os.environ[\"HOST_IP\"] = host_ip\n", - "\n", - "# Login + pull + build\n", - "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", - " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "\n", - "compose = f\"docker compose -f {AIDP_COMPOSE_FILE}\"\n", - "subprocess.run(f\"{compose} pull --ignore-pull-failures\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "subprocess.run(f\"{compose} up -d --build\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", - "\n", - "print(f\"Continuous Ingestion deployed:\")\n", - "print(f\" Kafka UI: http://{host_ip}:8080\")\n", - "print(f\" MinIO Console: http://{host_ip}:{MINIO_CONSOLE_PORT}\")\n", - "print(f\" Credentials: minioadmin / minioadmin\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Verify continuous ingestion services are running.\n", - "\n", - "The deployment status should be:\n", - "```\n", - "NAMES STATUS\n", - "kafka-consumer Up About a minute\n", - "aidp-kafka-ui Up About a minute\n", - "aidp-minio-mc Up About a minute\n", - "aidp-minio Up About a minute (healthy)\n", - "kafka Up About a minute (healthy)\n", - "```\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check service status and print access URLs\n", - "ip = get_host_ip()\n", - "for name, port, path in [\n", - " (\"Kafka UI\", 8080, \"/\"),\n", - " (\"MinIO Console\", MINIO_CONSOLE_PORT, \"/\"),\n", - "]:\n", - " try:\n", - " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", - " except requests.ConnectionError:\n", - " s = \"[DOWN]\"\n", - " except requests.Timeout:\n", - " s = \"[TIMEOUT]\"\n", - " print(f\" {s} {name}: http://{ip}:{port}\")\n", - "\n", - "# Check kafka-consumer container status\n", - "result = subprocess.run(\"docker inspect -f '{{.State.Status}}' kafka-consumer 2>/dev/null\",\n", - " shell=True, capture_output=True, text=True)\n", - "status = result.stdout.strip()\n", - "s = \"[OK]\" if status == \"running\" else \"[DOWN]\"\n", - "print(f\" {s} Kafka Consumer: {status or 'not found'}\")\n", - "\n", - "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(kafka|minio|NAMES)'\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Testing\n", - "\n", - "Test the deployment by uploading documents, then querying via RAG.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Document Upload\n", - "\n", - "Upload a PDF document to MinIO, which triggers automatic ingestion via Kafka consumer.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.1 Upload to Storage\n", - "\n", - "Upload the document to MinIO object storage.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Sample documents are included in the repo under examples/rag_event_ingest/data/\n", - "pdf_path = os.path.join(DATA_DIR, \"documents\", \"Seahawks-Patriots in Super Bowl LX_ What We Learned from Seattle's 29-13 win.pdf\")\n", - "upload_file(pdf_path, \"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.2 Verify Document Ingestion" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Check consumer logs to verify document processing status.\n", - "\n", - "The logs should show the document being picked up and successfully ingested:\n", - "```\n", - "services.document_indexer - INFO - Task ...: PENDING (0s)\n", - "services.document_indexer - INFO - Task ...: PENDING (5s)\n", - "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", - "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", - "```\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Verify file landed in object storage\n", - "verify_file_in_storage(\"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.3 Verify Document Ingestion\n", - "\n", - "Check consumer logs to verify document processing status.\n", - "\n", - "The logs should show the document being picked up and successfully ingested:\n", - "```\n", - "services.document_indexer - INFO - Task ...: PENDING (0s)\n", - "services.document_indexer - INFO - Task ...: PENDING (5s)\n", - "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", - "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Check consumer logs for ingestion status\n", - "print(\"Waiting for document processing...\")\n", - "get_consumer_logs(50)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 1.4 Query Document via RAG\n", - "\n", - "You can query the ingested document either **programmatically** below or via the **RAG Frontend UI**.\n", - "\n", - "> **💡 RAG Frontend**: Open `http://:8090` in your browser for an interactive Q&A interface.\n", - "> Make sure to select the collection **`aidp_bucket`** in the UI.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Query the document\n", - "await query_rag(\"What was the final score and who won Super Bowl LX?\", MINIO_COLLECTION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Ask another question about the document.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Query about key takeaways\n", - "await query_rag(\"What were the key lessons learned from Seattle's victory in Super Bowl LX?\", MINIO_COLLECTION)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Clean Up\n", - "\n", - "Stop all services and clean up ingested data.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 1. Stop RAG Deployment\n", - "\n", - "Stop all RAG services (NIMs, Milvus, Ingestor, RAG server).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "os.chdir(RAG_REPO_DIR)\n", - "for f in [\n", - " \"deploy/compose/docker-compose-rag-server.yaml\",\n", - " \"deploy/compose/docker-compose-ingestor-server.yaml\",\n", - " \"deploy/compose/vectordb.yaml\",\n", - " \"deploy/compose/nims.yaml\",\n", - "]:\n", - " run_command(f\"docker compose -f {f} down\")\n", - "print(\"[OK] RAG stopped\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Stop Continuous ingestion Deployment\n", - "\n", - "Stop Continuous ingestion services (Kafka, MinIO, Consumer).\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "run_command(f\"docker compose -f {AIDP_COMPOSE_FILE} down\")\n", - "print(\"[OK] Continuous ingestion stopped\")\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Document Continuous Ingestion from Object Storage\n", + "\n", + "## Purpose\n", + "\n", + "This notebook demonstrates an **automated document ingestion pipeline** that:\n", + "\n", + "1. Monitors emulated object storage for new uploads via Kafka events\n", + "2. Routes documents to appropriate AI services for indexing\n", + "5. Enables RAG Agent for semantic search and contextual Q&A over all ingested content\n", + "\n", + "## What Gets Deployed\n", + "\n", + "1. **NVIDIA RAG** - Document indexing, vector search, and AI-powered Q&A (NIMs, Milvus, Ingestor)\n", + "2. **Continuous Ingestion** - Event-driven ingestion pipeline (Kafka, MinIO, Consumer)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "### Hardware\n", + "- **GPU**: 2x RTX PRO 6000 Blackwell or 2x H100\n", + "\n", + "#### Default GPU Assignment\n", + "\n", + "| GPU | Service |\n", + "|-----|---------|\n", + "| 0 | RAG NIMs (Embedding, Reranker) |\n", + "| 1 | RAG LLM NIM (Llama-3.3-Nemotron-Super-49B) |\n", + "\n", + "\n", + "### Software (pre-installed required)\n", + "- Ubuntu 22.04 or later\n", + "- Docker 24.0+ with Docker Compose v2\n", + "- NVIDIA Driver 570+\n", + "- NVIDIA Container Toolkit\n", + "\n", + "### API Keys\n", + "\n", + "\n", + "\n", + "\n", + "
KeyPurposeHow to Get
NGC_API_KEYDocker login, NIM deploymentsNGC Portal → Generate API Key
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Table of Contents\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
SectionDescription
SetupClone repo, install deps, set API keys, load helpers
Deploy RAGNIMs, Vector DB, Ingestor, RAG Server
Deploy Continuous IngestionKafka, MinIO, Consumer
TestingUpload documents, query RAG
Clean UpStop services, clean data
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "- **RAG Blueprint**: [NVIDIA RAG Documentation](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/deploy-docker-self-hosted.md)\n", + "- **NIM**: [NVIDIA NIM Documentation](https://docs.nvidia.com/nim/index.html)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup\n", + "\n", + "Clone the repository, configure API keys, and load helper functions.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Clone Repository\n", + "\n", + "Clone the RAG Blueprint repo to `~/rag`. This includes the consumer source code, deploy configs, and sample test data.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess, sys, os, shutil\n", + "\n", + "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", + "RAG_REPO_URL = \"https://github.com/NVIDIA-AI-Blueprints/rag.git\"\n", + "\n", + "# Ensure git-lfs is installed before any LFS operations\n", + "if not shutil.which(\"git-lfs\"):\n", + " print(\"[INSTALLING] git-lfs...\")\n", + " subprocess.run(\"sudo apt-get update && sudo apt-get install -y git-lfs && git lfs install\", shell=True, check=True)\n", + "else:\n", + " print(\"[OK] git-lfs found\")\n", + "\n", + "# Clone from correct branch (skip if already exists)\n", + "if not os.path.exists(RAG_REPO_DIR):\n", + " subprocess.run(f\"git clone {RAG_REPO_URL} {RAG_REPO_DIR}\", shell=True, check=True)\n", + "else:\n", + " print(f\"[OK] RAG repo already exists: {RAG_REPO_DIR}\")\n", + "subprocess.run(\"git lfs pull\", shell=True, cwd=RAG_REPO_DIR, check=True)\n", + "\n", + "# Verify\n", + "for path in [\"deploy/compose\", \"examples/rag_event_ingest/kafka_consumer\", \"examples/rag_event_ingest/data\"]:\n", + " status = \"[OK]\" if os.path.exists(os.path.join(RAG_REPO_DIR, path)) else \"[MISSING]\"\n", + " print(f\" {status} {path}\")\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Install Dependencies\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "! python3 -m ensurepip --upgrade" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Ensure pip is available (some minimal Python installs lack it)\n", + "subprocess.run([sys.executable, \"-m\", \"ensurepip\", \"--upgrade\"], capture_output=True)\n", + "\n", + "def check_install_system_pkg(cmd: str, install_cmd: str):\n", + " if shutil.which(cmd):\n", + " print(f\" [OK] {cmd} found\")\n", + " return True\n", + " print(f\" [INSTALLING] {cmd}...\")\n", + " result = subprocess.run(install_cmd, shell=True, capture_output=True, text=True)\n", + " if result.returncode == 0:\n", + " print(f\" [OK] {cmd} installed\")\n", + " return True\n", + " print(f\" [ERROR] Failed to install {cmd}. Please install manually: {install_cmd}\")\n", + " return False\n", + "\n", + "check_install_system_pkg(\"git\", \"sudo apt-get update && sudo apt-get install -y git\")\n", + "\n", + "# Install Python packages\n", + "subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\", \"aiohttp\", \"requests\", \"python-dotenv\", \"pyyaml\"])\n", + "print(\"[OK] Ready\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Set API Keys\n", + "\n", + "Configure NGC API key for NIM deployments.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import getpass\n", + "\n", + "def set_api_key(env_var: str, prompt: str, required: bool = True):\n", + " if os.environ.get(env_var):\n", + " print(f\" [OK] {env_var} already set ({os.environ[env_var][:10]}...)\")\n", + " return True\n", + " key = getpass.getpass(prompt)\n", + " if key:\n", + " os.environ[env_var] = key\n", + " print(f\" [OK] {env_var} set\")\n", + " return True\n", + " if required:\n", + " print(f\" [ERROR] {env_var} is required\")\n", + " return False\n", + " print(f\" [SKIP] {env_var} (optional)\")\n", + " return True\n", + "\n", + "set_api_key(\"NGC_API_KEY\", \"Enter NGC_API_KEY (starts with 'nvapi-'): \", required=True)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Helper Functions\n", + "\n", + "Shared utilities for deployment, file upload, status checks, and RAG queries.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install dependencies\n", + "import sys\n", + "!{sys.executable} -m pip install -q minio aiohttp requests python-dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os, sys, json, re, subprocess, time, socket, asyncio\n", + "import aiohttp, requests\n", + "from typing import List, Optional, Dict\n", + "\n", + "try:\n", + " from minio import Minio\n", + " from minio.error import S3Error\n", + "except ImportError:\n", + " subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"minio\"])\n", + " from minio import Minio\n", + " from minio.error import S3Error\n", + "\n", + "# =============================================================================\n", + "# CONFIGURATION\n", + "# =============================================================================\n", + "\n", + "# Paths relative to RAG repo root\n", + "RAG_REPO_DIR = os.path.expanduser(\"~/rag\")\n", + "EXAMPLE_DIR = os.path.join(RAG_REPO_DIR, \"examples/rag_event_ingest\")\n", + "AIDP_COMPOSE_FILE = os.path.join(EXAMPLE_DIR, \"deploy/docker-compose.yaml\")\n", + "DATA_DIR = os.path.join(EXAMPLE_DIR, \"data\")\n", + "RAG_SERVER_URL = \"http://localhost:8081\"\n", + "INGESTOR_URL = \"http://localhost:8082\"\n", + "\n", + "LOCAL_NIM_CACHE = os.path.expanduser(\"~/.cache/nim\")\n", + "\n", + "MINIO_ENDPOINT = \"localhost:9201\"\n", + "MINIO_ACCESS_KEY = \"minioadmin\"\n", + "MINIO_SECRET_KEY = \"minioadmin\"\n", + "MINIO_BUCKET = \"aidp-bucket\"\n", + "MINIO_COLLECTION = \"aidp_bucket\"\n", + "MINIO_CONSOLE_PORT = 9211\n", + "\n", + "# =============================================================================\n", + "# SHARED UTILITIES\n", + "# =============================================================================\n", + "\n", + "def run_command(cmd: str, capture: bool = False) -> Optional[str]:\n", + " \"\"\"Execute a shell command and print it.\"\"\"\n", + " print(f\"$ {cmd}\")\n", + " result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)\n", + " return result.stdout if capture else None\n", + "\n", + "def get_host_ip() -> str:\n", + " \"\"\"Get host IP address for external access URLs.\"\"\"\n", + " try:\n", + " s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n", + " s.connect((\"8.8.8.8\", 80))\n", + " ip = s.getsockname()[0]\n", + " s.close()\n", + " return ip\n", + " except OSError:\n", + " return \"localhost\"\n", + "\n", + "def get_minio_client() -> Minio:\n", + " \"\"\"Create MinIO client for AIDP bucket operations.\"\"\"\n", + " return Minio(MINIO_ENDPOINT, access_key=MINIO_ACCESS_KEY, secret_key=MINIO_SECRET_KEY, secure=False)\n", + "\n", + "def upload_file(local_path: str, object_name: Optional[str] = None) -> bool:\n", + " \"\"\"Upload a local file to MinIO AIDP bucket.\"\"\"\n", + " if not os.path.exists(local_path):\n", + " print(f\"[ERROR] File not found: {local_path}\")\n", + " return False\n", + " obj = object_name or os.path.basename(local_path)\n", + " try:\n", + " client = get_minio_client()\n", + " if not client.bucket_exists(MINIO_BUCKET):\n", + " client.make_bucket(MINIO_BUCKET)\n", + " client.fput_object(MINIO_BUCKET, obj, local_path)\n", + " print(f\"[OK] Uploaded: {obj}\")\n", + " return True\n", + " except S3Error as e:\n", + " print(f\"[ERROR] {e}\")\n", + " return False\n", + "\n", + "def verify_file_in_storage(object_name: str, bucket: str = MINIO_BUCKET) -> bool:\n", + " \"\"\"Check if a file exists in MinIO bucket and print verification status.\"\"\"\n", + " try:\n", + " client = get_minio_client()\n", + " stat = client.stat_object(bucket, object_name)\n", + " print(f\"[OK] File verified in storage:\")\n", + " print(f\" Bucket: {bucket}\")\n", + " print(f\" Object: {object_name}\")\n", + " print(f\" Size: {stat.size:,} bytes\")\n", + " print(f\" Modified: {stat.last_modified}\")\n", + " return True\n", + " except S3Error as e:\n", + " print(f\"[ERROR] File not found in storage: {object_name}\")\n", + " print(f\" Error: {e}\")\n", + " return False\n", + "\n", + "def get_consumer_logs(lines: int = 30) -> None:\n", + " \"\"\"Show recent Kafka consumer logs.\"\"\"\n", + " run_command(f\"docker logs kafka-consumer --tail {lines}\")\n", + "\n", + "async def query_rag(question: str, collection: str = None) -> Optional[str]:\n", + " \"\"\"Query RAG system and print the answer.\"\"\"\n", + " coll = collection or MINIO_COLLECTION\n", + " print(f\"Q: {question}\\nCollection: {coll}\\n\" + \"-\" * 40)\n", + "\n", + " payload = {\n", + " \"messages\": [{\"role\": \"user\", \"content\": question}],\n", + " \"use_knowledge_base\": True,\n", + " \"collection_names\": [coll],\n", + " }\n", + " try:\n", + " async with aiohttp.ClientSession() as session:\n", + " async with session.post(\n", + " f\"{RAG_SERVER_URL}/generate\", json=payload,\n", + " timeout=aiohttp.ClientTimeout(total=120),\n", + " ) as resp:\n", + " text = await resp.text()\n", + " # Parse SSE response: extract content from each \"data: {...}\" line\n", + " chunks = []\n", + " for line in text.split(\"\\n\"):\n", + " if not line.startswith(\"data: \") or line[6:] == \"[DONE]\":\n", + " continue\n", + " try:\n", + " msg = json.loads(line[6:]).get(\"choices\", [{}])[0].get(\"message\", {})\n", + " if msg.get(\"content\"):\n", + " chunks.append(msg[\"content\"])\n", + " except json.JSONDecodeError:\n", + " pass\n", + " answer = \"\".join(chunks)\n", + " print(f\"Answer: {answer}\")\n", + " return answer\n", + " except aiohttp.ClientError as e:\n", + " print(f\"[ERROR] {e}\")\n", + " return None\n", + "\n", + "print(f\"[OK] Helpers loaded | Host IP: {get_host_ip()}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy NVIDIA RAG\n", + "\n", + "Deploy the NVIDIA RAG: NIMs (LLM, Embedding, Reranker), Milvus vector database, Ingestor server, and RAG server.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ngc_key = os.environ.get(\"NGC_API_KEY\")\n", + "if not ngc_key:\n", + " raise RuntimeError(\"NGC_API_KEY not set! Run the API keys cell first.\")\n", + "\n", + "os.chdir(RAG_REPO_DIR)\n", + "\n", + "# Set env vars needed by docker compose\n", + "os.environ[\"NGC_API_KEY\"] = ngc_key\n", + "os.environ[\"USERID\"] = f\"{os.getuid()}:{os.getgid()}\"\n", + "os.environ[\"COLLECTION_NAME\"] = MINIO_COLLECTION\n", + "\n", + "# Load RAG .env defaults (MODEL_DIRECTORY, etc.)\n", + "from dotenv import load_dotenv\n", + "env_file = os.path.join(RAG_REPO_DIR, \"deploy/compose/.env\")\n", + "if os.path.exists(env_file):\n", + " load_dotenv(env_file, override=False)\n", + "\n", + "# Login to nvcr.io\n", + "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", + " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "# Deploy components\n", + "for label, compose_file in [\n", + " (\"NIMs\", \"deploy/compose/nims.yaml\"),\n", + " (\"Vector DB\", \"deploy/compose/vectordb.yaml\"),\n", + "]:\n", + " print(f\"Deploying {label}...\")\n", + " run_command(f\"USERID=$(id -u) docker compose -f {compose_file} up -d\")\n", + "\n", + "print(\"Waiting 30s for Milvus...\")\n", + "time.sleep(30)\n", + "\n", + "for label, compose_file in [\n", + " (\"Ingestor\", \"deploy/compose/docker-compose-ingestor-server.yaml\"),\n", + " (\"RAG Server\", \"deploy/compose/docker-compose-rag-server.yaml\"),\n", + "]:\n", + " print(f\"Deploying {label}...\")\n", + " run_command(f\"docker compose -f {compose_file} up -d\")\n", + "\n", + "ip = get_host_ip()\n", + "print(f\"\\nRAG deployed: http://{ip}:8081 (server) | http://{ip}:8082 (ingestor) | http://{ip}:8090 (UI)\")\n", + "print(f\"COLLECTION_NAME: {MINIO_COLLECTION}\")\n", + "print(\"Wait ~10 minutes for NIMs to load models, then run the status check cell.\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify RAG services are healthy. Wait ~10 minutes for NIMs to load models.\n", + "\n", + "The deployment status should be:\n", + "```\n", + "NAMES STATUS\n", + "rag-frontend Up About a minute\n", + "rag-server Up About a minute\n", + "ingestor-server Up About a minute\n", + "milvus-standalone Up 2 minutes (healthy)\n", + "milvus-etcd Up 2 minutes (healthy)\n", + "milvus-minio Up 2 minutes (healthy)\n", + "nim-llm-ms Up 2 minutes (healthy)\n", + "nemotron-embedding-ms Up 2 minutes (healthy)\n", + "nemotron-ranking-ms Up 2 minutes (healthy)\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check service status and print access URLs\n", + "print(\"Wait ~10 minutes for services to become healthy.\")\n", + "print(\"Run this cell again after waiting.\\n\")\n", + "\n", + "ip = get_host_ip()\n", + "for name, port, path in [\n", + " (\"RAG Server\", 8081, \"/health\"), (\"Ingestor\", 8082, \"/health\"),\n", + " (\"Frontend\", 8090, \"/\"), (\"Milvus\", 19530, \"/v1/vector/collections\"),\n", + "]:\n", + " try:\n", + " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", + " except requests.ConnectionError:\n", + " s = \"[DOWN]\"\n", + " except requests.Timeout:\n", + " s = \"[TIMEOUT]\"\n", + " print(f\" {s} {name}: http://{ip}:{port}\")\n", + "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(rag|milvus|ingestor|nim|nemotron|NAMES)'\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deploy Continuous Ingestion from emulated object storage\n", + "\n", + "Deploy the Continuous Ingestion: Kafka message broker, MinIO object storage, and Kafka consumer for automated ingestion.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Deploy Services\n", + "\n", + "Deploy Kafka, MinIO, and the Kafka consumer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify prerequisites\n", + "net_check = subprocess.run(\"docker network inspect nvidia-rag\", shell=True, capture_output=True)\n", + "if net_check.returncode != 0:\n", + " raise RuntimeError(\"nvidia-rag network not found. Deploy RAG first.\")\n", + "\n", + "ngc_key = os.environ.get(\"NGC_API_KEY\", \"\")\n", + "if not ngc_key:\n", + " raise RuntimeError(\"NGC_API_KEY not set!\")\n", + "\n", + "host_ip = get_host_ip()\n", + "\n", + "# Set environment variables for docker compose\n", + "os.environ[\"HOST_IP\"] = host_ip\n", + "\n", + "# Login + pull + build\n", + "subprocess.run(f\"echo {ngc_key} | docker login nvcr.io -u '$oauthtoken' --password-stdin\",\n", + " shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "compose = f\"docker compose -f {AIDP_COMPOSE_FILE}\"\n", + "subprocess.run(f\"{compose} pull --ignore-pull-failures\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "subprocess.run(f\"{compose} up -d --build\", shell=True, capture_output=True, text=True, executable=\"/bin/bash\")\n", + "\n", + "print(f\"Continuous Ingestion deployed:\")\n", + "print(f\" Kafka UI: http://{host_ip}:8080\")\n", + "print(f\" MinIO Console: http://{host_ip}:{MINIO_CONSOLE_PORT}\")\n", + "print(f\" Credentials: minioadmin / minioadmin\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify continuous ingestion services are running.\n", + "\n", + "The deployment status should be:\n", + "```\n", + "NAMES STATUS\n", + "kafka-consumer Up About a minute\n", + "aidp-kafka-ui Up About a minute\n", + "aidp-minio-mc Up About a minute\n", + "aidp-minio Up About a minute (healthy)\n", + "kafka Up About a minute (healthy)\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check service status and print access URLs\n", + "ip = get_host_ip()\n", + "for name, port, path in [\n", + " (\"Kafka UI\", 8080, \"/\"),\n", + " (\"MinIO Console\", MINIO_CONSOLE_PORT, \"/\"),\n", + "]:\n", + " try:\n", + " s = \"[OK]\" if requests.get(f\"http://localhost:{port}{path}\", timeout=10).status_code == 200 else \"[WARN]\"\n", + " except requests.ConnectionError:\n", + " s = \"[DOWN]\"\n", + " except requests.Timeout:\n", + " s = \"[TIMEOUT]\"\n", + " print(f\" {s} {name}: http://{ip}:{port}\")\n", + "\n", + "# Check kafka-consumer container status\n", + "result = subprocess.run(\"docker inspect -f '{{.State.Status}}' kafka-consumer 2>/dev/null\",\n", + " shell=True, capture_output=True, text=True)\n", + "status = result.stdout.strip()\n", + "s = \"[OK]\" if status == \"running\" else \"[DOWN]\"\n", + "print(f\" {s} Kafka Consumer: {status or 'not found'}\")\n", + "\n", + "run_command(\"docker ps --format 'table {{.Names}}\\t{{.Status}}' | grep -E '(kafka|minio|NAMES)'\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Testing\n", + "\n", + "Test the deployment by uploading documents, then querying via RAG.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Document Upload\n", + "\n", + "Upload a PDF document to MinIO, which triggers automatic ingestion via Kafka consumer.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1 Upload to Storage\n", + "\n", + "Upload the document to MinIO object storage.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Sample documents are included in the repo under examples/rag_event_ingest/data/\n", + "pdf_path = os.path.join(DATA_DIR, \"documents\", \"Seahawks-Patriots in Super Bowl LX_ What We Learned from Seattle's 29-13 win.pdf\")\n", + "upload_file(pdf_path, \"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2 Verify Document Ingestion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Check consumer logs to verify document processing status.\n", + "\n", + "The logs should show the document being picked up and successfully ingested:\n", + "```\n", + "services.document_indexer - INFO - Task ...: PENDING (0s)\n", + "services.document_indexer - INFO - Task ...: PENDING (5s)\n", + "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", + "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", + "```\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Verify file landed in object storage\n", + "verify_file_in_storage(\"Seahawks-Patriots_SuperBowl_LX_Analysis.pdf\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.3 Verify Document Ingestion\n", + "\n", + "Check consumer logs to verify document processing status.\n", + "\n", + "The logs should show the document being picked up and successfully ingested:\n", + "```\n", + "services.document_indexer - INFO - Task ...: PENDING (0s)\n", + "services.document_indexer - INFO - Task ...: PENDING (5s)\n", + "handlers.base - INFO - [DocumentHandler] ✓ Seahawks-Patriots_SuperBowl_LX_Analysis.pdf → SUCCESS\n", + "consumer - INFO - ✓ SUMMARY: Seahawks-Patriots_SuperBowl_LX_Analysis.pdf | Collection: aidp_bucket | Duration: 12.76s | Status: SUCCESS\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check consumer logs for ingestion status\n", + "print(\"Waiting for document processing...\")\n", + "get_consumer_logs(50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.4 Query Document via RAG\n", + "\n", + "You can query the ingested document either **programmatically** below or via the **RAG Frontend UI**.\n", + "\n", + "> **💡 RAG Frontend**: Open `http://:8090` in your browser for an interactive Q&A interface.\n", + "> Make sure to select the collection **`aidp_bucket`** in the UI.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Query the document\n", + "await query_rag(\"What was the final score and who won Super Bowl LX?\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ask another question about the document.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Query about key takeaways\n", + "await query_rag(\"What were the key lessons learned from Seattle's victory in Super Bowl LX?\", MINIO_COLLECTION)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Clean Up\n", + "\n", + "Stop all services and clean up ingested data.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Stop RAG Deployment\n", + "\n", + "Stop all RAG services (NIMs, Milvus, Ingestor, RAG server).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "os.chdir(RAG_REPO_DIR)\n", + "for f in [\n", + " \"deploy/compose/docker-compose-rag-server.yaml\",\n", + " \"deploy/compose/docker-compose-ingestor-server.yaml\",\n", + " \"deploy/compose/vectordb.yaml\",\n", + " \"deploy/compose/nims.yaml\",\n", + "]:\n", + " run_command(f\"docker compose -f {f} down\")\n", + "print(\"[OK] RAG stopped\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Stop Continuous ingestion Deployment\n", + "\n", + "Stop Continuous ingestion services (Kafka, MinIO, Consumer).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "run_command(f\"docker compose -f {AIDP_COMPOSE_FILE} down\")\n", + "print(\"[OK] Continuous ingestion stopped\")\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 } From 6d96e2eea0920502133ca4ee6321af80fbc7a38f Mon Sep 17 00:00:00 2001 From: Kurt Heiss Date: Wed, 11 Mar 2026 23:32:09 -0700 Subject: [PATCH 36/52] Kheiss/prd additions (#427) * confirming presence of switcher text in conf.py file * Update documentation to reflect name change to NeMo Retriever Library * Update api-rag.md removed GitHub markers * Update change-model.md Removed GitHub markers * Update deploy-helm.md removed GitHub markers * Update deploy-helm.md Remove GitHub markers * Update deploy-helm.md Remove GitHub markers * Update mig-deployment.md removed GitHub markers * Update deploy-helm.md removed extra space --- docs/api-ingestor.md | 4 +-- docs/api-rag.md | 2 ++ docs/audio_ingestion.md | 2 +- docs/change-model.md | 14 +++++++- docs/change-vectordb.md | 2 +- docs/conf.py | 6 ++-- docs/custom-metadata.md | 14 ++++---- docs/debugging.md | 8 ++--- docs/deploy-docker-nvidia-hosted.md | 4 +-- docs/deploy-docker-self-hosted.md | 4 +-- docs/deploy-helm-from-repo.md | 2 +- docs/deploy-helm.md | 2 +- docs/index.md | 4 +-- docs/mount-ingestor-volume.md | 2 +- docs/nemoretriever-ocr.md | 56 ++++++++++++++--------------- docs/nemotron-parse-extraction.md | 2 +- docs/notebooks.md | 2 +- docs/nv-ingest-standalone.md | 14 ++++---- docs/python-client.md | 6 ++-- docs/readme.md | 2 +- docs/release-notes.md | 8 ++--- docs/service-port-gpu-reference.md | 4 +-- docs/text_only_ingest.md | 8 ++--- docs/troubleshooting.md | 2 +- docs/vlm-embed.md | 2 +- 25 files changed, 95 insertions(+), 81 deletions(-) diff --git a/docs/api-ingestor.md b/docs/api-ingestor.md index 548841574..adeeb4cf0 100644 --- a/docs/api-ingestor.md +++ b/docs/api-ingestor.md @@ -41,7 +41,7 @@ The status response includes progress metrics updated after each batch completes For more granular progress updates during batch processing, use the `nv_ingest_status` object described below, which tracks individual document extraction progress and updates more frequently than the batch-level metrics. ::: -### NV-Ingest Extraction Status +### Extraction status The `/status` endpoint response includes an `nv_ingest_status` object that provides real-time document extraction progress, updating more frequently than batch-level metrics. This is useful for monitoring individual document processing when polling the status endpoint: @@ -53,7 +53,7 @@ The `/status` endpoint response includes an `nv_ingest_status` object that provi | Status | Description | |--------|-------------| | `not_started` | Document queued, extraction not yet initiated | -| `submitted` | Document submitted to NV-Ingest for processing | +| `submitted` | Document submitted to NeMo Retriever Library for processing | | `processing` | Document extraction is in progress | | `completed` | Document extraction completed successfully | | `failed` | Document extraction failed | diff --git a/docs/api-rag.md b/docs/api-rag.md index 518696304..7a15d8890 100644 --- a/docs/api-rag.md +++ b/docs/api-rag.md @@ -10,6 +10,8 @@ This documentation contains the OpenAPI reference for the RAG server. :::{tip} To view this documentation on docs.nvidia.com, browse to [https://docs.nvidia.com/rag/latest/api-rag](https://docs.nvidia.com/rag/latest/api-rag.html). ::: +======= +To view this documentation on docs.nvidia.com, browse to [https://docs.nvidia.com/rag/latest/api-rag](https://docs.nvidia.com/rag/latest/api-rag.html). :::{swagger-plugin} ../docs/api_reference/openapi_schema_rag_server.json diff --git a/docs/audio_ingestion.md b/docs/audio_ingestion.md index 55fcbc132..399ea7a64 100644 --- a/docs/audio_ingestion.md +++ b/docs/audio_ingestion.md @@ -132,7 +132,7 @@ When using Helm deployment, the Audio NIM service requires an additional GPU. The `APP_NVINGEST_SEGMENTAUDIO` environment variable controls whether audio segmentation is enabled during the ingestion process. -When set to `True`, NV-Ingest will segment audio files based on commas and other punctuation marks, resulting in more granular audio chunks. This can improve downstream processing and retrieval accuracy for audio content. Note that splitting on captions will occur regardless of this setting; enabling `APP_NVINGEST_SEGMENTAUDIO` simply adds additional segmentation based on punctuation. +When set to `True`, NeMo Retriever Library will segment audio files based on commas and other punctuation marks, resulting in more granular audio chunks. This can improve downstream processing and retrieval accuracy for audio content. Note that splitting on captions will occur regardless of this setting; enabling `APP_NVINGEST_SEGMENTAUDIO` simply adds additional segmentation based on punctuation. To enable audio segmentation, add the following export command to your environment configuration: diff --git a/docs/change-model.md b/docs/change-model.md index 27aa80e28..96308764f 100644 --- a/docs/change-model.md +++ b/docs/change-model.md @@ -264,7 +264,19 @@ Use this procedure to change models when you are running self-hosted NVIDIA NIM **If only the vLLM profile is available** When only a vLLM profile is available for a model, such as on H100 and RTX GPUs, you must use the vLLM engine. First [run the list-model-profiles command](model-profiles.md#list-available-profiles) to confirm which profiles are available and then apply the following configurations. - + **For Nemotron Nano Models VLLM profile** + + When deploying `nvidia/nvidia-nemotron-nano-9b-v2` or `nvidia/nemotron-3-nano`, check if `tensorrt_llm` profile is available using below command for your required model. + + ```bash + # Change model name as needed + USERID=$(id -u) docker run --rm --gpus all \ + nvcr.io/nim/nvidia/nvidia-nemotron-nano-9b-v2:latest \ + list-model-profiles + ``` + + If only `vllm` profile is available, you must use the **vLLM engine** and add these specific configurations: + ```yaml nimOperator: nim-llm: diff --git a/docs/change-vectordb.md b/docs/change-vectordb.md index a4dc993b8..36f4d4f9f 100644 --- a/docs/change-vectordb.md +++ b/docs/change-vectordb.md @@ -1001,7 +1001,7 @@ Update your [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml) fil ### Disable Default Vector Database and Add Custom Helm Chart -1. **Disable Milvus in the NV-Ingest configuration:** +1. **Disable Milvus in the NeMo Retriever Library configuration:** ```yaml nv-ingest: enabled: true diff --git a/docs/conf.py b/docs/conf.py index 6f95103b4..27aeca848 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# Copyright (c) '2025-%Y, NVIDIA CORPORATION. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ import os import sys -project = " NVIDIA-RAG-blueprint" -copyright = "2025, NVIDIA Corporation" +project = " NVIDIA RAG blueprint" +copyright = "'2025-%Y, NVIDIA Corporation" author = "NVIDIA Corporation" release = "2.5.0" diff --git a/docs/custom-metadata.md b/docs/custom-metadata.md index c02b80eeb..ce96b63ba 100644 --- a/docs/custom-metadata.md +++ b/docs/custom-metadata.md @@ -232,13 +232,13 @@ The system automatically manages certain metadata fields that are added to all c | Field Name | Type | Description | Auto-Populated | User Override | |------------|------|-------------|----------------|---------------| -| **`filename`** | `string` | Name of the uploaded file (case-sensitive when filtering) | ✅ RAG system | ✅ Yes - define in schema | -| **`page_number`** | `integer` | Page number where content appears (1-indexed) | ✅ nv-ingest | ✅ Yes - define in schema | -| **`start_time`** | `integer` | Start timestamp in milliseconds for audio/video segments | ✅ nv-ingest | ✅ Yes - define in schema | -| **`end_time`** | `integer` | End timestamp in milliseconds for audio/video segments | ✅ nv-ingest | ✅ Yes - define in schema | +| **`filename`** | `string` | Name of the uploaded file | ✅ RAG system | ✅ Yes - define in schema | +| **`page_number`** | `integer` | Page number where content appears (1-indexed) | ✅ NeMo Retriever Library | ✅ Yes - define in schema | +| **`start_time`** | `integer` | Start timestamp in milliseconds for audio/video segments | ✅ NeMo Retriever Library | ✅ Yes - define in schema | +| **`end_time`** | `integer` | End timestamp in milliseconds for audio/video segments | ✅ NeMo Retriever Library | ✅ Yes - define in schema | :::{note} -The following field names are **reserved** by NV-Ingest and cannot be used in custom metadata schemas: `type`, `subtype`, and `location`. These fields are exclusively managed by NV-Ingest during document processing and attempting to use them will result in a validation error. +The following field names are **reserved** by NeMo Retriever Library and cannot be used in custom metadata schemas: `type`, `subtype`, and `location`. These fields are exclusively managed by NeMo Retriever Library during document processing and attempting to use them will result in a validation error. ::: #### System-Managed Field Behavior @@ -246,7 +246,7 @@ The following field names are **reserved** by NV-Ingest and cannot be used in cu - **Auto-Addition**: These fields are automatically added to your collection schema if you don't define them - **Auto-Population**: - `filename` is populated by the RAG system during ingestion - - `page_number`, `start_time`, `end_time` are extracted and populated by nv-ingest during document processing + - `page_number`, `start_time`, `end_time` are extracted and populated by NeMo Retriever Library during document processing - **User Override**: You can define any of these fields in your schema with custom properties (e.g., different description, constraints) - If you provide a definition, your definition takes priority - If you don't provide a definition, the system auto-adds them with default settings @@ -258,7 +258,7 @@ The following field names are **reserved** by NV-Ingest and cannot be used in cu :::{note} **Example**: If you upload a multi-page PDF without defining `page_number` in your schema, the system will: 1. Automatically add the `page_number` field to your collection schema -2. nv-ingest will extract the page number from each chunk during processing +2. NeMo Retriever Library extracts the page number from each chunk during processing 3. The page number will be available for filtering (e.g., `content_metadata["page_number"] == 5`) 4. The page number will appear in citations when generating responses ::: diff --git a/docs/debugging.md b/docs/debugging.md index a9c25debd..1fb5aea0d 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -141,7 +141,7 @@ docker ps | grep -E "(ingestor-server|nv-ingest|nemoretriever-embedding|milvus|r # Check ingestor server health with all dependencies curl -X GET "http://localhost:8082/v1/health?check_dependencies=true" | jq -# Verify NV-Ingest runtime is ready for processing +# Verify NeMo Retriever Library runtime is ready for processing curl -X GET "http://localhost:7670/v1/health/ready" # Check embedding service is responding @@ -219,7 +219,7 @@ Start by examining the logs of key ingestion services to identify the specific e # Check ingestor server logs for API errors docker logs ingestor-server --tail 100 -# Check NV-Ingest runtime logs for processing errors +# Check NeMo Retriever Library runtime logs for processing errors docker logs nv-ingest-ms-runtime --tail 100 # Check embedding service logs for model issues @@ -251,9 +251,9 @@ docker logs nemotron-embedding-ms --tail 100 nvidia-smi ``` -**NV-Ingest Processing Errors:** +**NeMo Retriever Library Processing Errors:** ```bash -# Check NV-Ingest logs for processing errors +# Check NeMo Retriever Library logs for processing errors docker logs nv-ingest-ms-runtime --tail 200 | grep -i error # Check Redis connectivity for task queue diff --git a/docs/deploy-docker-nvidia-hosted.md b/docs/deploy-docker-nvidia-hosted.md index 4487edff3..2aabd06ce 100644 --- a/docs/deploy-docker-nvidia-hosted.md +++ b/docs/deploy-docker-nvidia-hosted.md @@ -111,7 +111,7 @@ Use the following procedure to start all containers needed for this blueprint. ], "processing": [ { - "service": "NV-Ingest", + "service": "NeMo Retriever Library", "status": "healthy", ... } @@ -238,7 +238,7 @@ After the first time you deploy the RAG Blueprint successfully, you can consider - If you don't have a GPU available, you can switch to CPU-only Milvus by following the instructions in [milvus-configuration.md](./milvus-configuration.md). -- If you have a requirement to build the NVIDIA Ingest runtime container from source, you can do it by following instructions [here](https://github.com/NVIDIA/nv-ingest). +- If you have a requirement to build the NeMo Retriever Library runtime container from source, you can do it by following instructions [here](https://github.com/NVIDIA/NeMo-Retriever). diff --git a/docs/deploy-docker-self-hosted.md b/docs/deploy-docker-self-hosted.md index 6607aac73..4913be36a 100644 --- a/docs/deploy-docker-self-hosted.md +++ b/docs/deploy-docker-self-hosted.md @@ -174,7 +174,7 @@ Use the following procedure to start all containers needed for this blueprint. ], "processing": [ { - "service": "NV-Ingest", + "service": "NeMo Retriever Library", "status": "healthy", ... } @@ -333,7 +333,7 @@ After the first time you deploy the RAG Blueprint successfully, you can consider - For improved accuracy, consider enabling reasoning mode. For details, refer to [Enable thinking](./enable-nemotron-thinking.md). -- NeMo Retriever OCR is now the default OCR service. To use legacy Paddle OCR instead, refer to [OCR Configuration Guide](nemoretriever-ocr.md). +- NeMo Retriever Library OCR is now the default OCR service. To use legacy Paddle OCR instead, refer to [OCR Configuration Guide](nemoretriever-ocr.md). - For advanced users who need direct filesystem access to extraction results, refer to [Ingestor Server Volume Mounting](mount-ingestor-volume.md). diff --git a/docs/deploy-helm-from-repo.md b/docs/deploy-helm-from-repo.md index 04c39829a..e57c9ea26 100644 --- a/docs/deploy-helm-from-repo.md +++ b/docs/deploy-helm-from-repo.md @@ -14,7 +14,7 @@ The following are the core services that you install: - RAG server - Ingestor server -- NV-Ingest +- NeMo Retriever Library ## Prerequisites diff --git a/docs/deploy-helm.md b/docs/deploy-helm.md index 9e7a39217..c0b8743e8 100644 --- a/docs/deploy-helm.md +++ b/docs/deploy-helm.md @@ -14,7 +14,7 @@ The following are the core services that you install: - RAG server - Ingestor server -- NV-Ingest +- NeMo Retriever Library ## Prerequisites diff --git a/docs/index.md b/docs/index.md index 941b2f9d8..1e62202a6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -141,7 +141,7 @@ After you deploy the RAG blueprint, you can customize it for your use cases. ## Blog Posts -- [NVIDIA NeMo Retriever Delivers Accurate Multimodal PDF Data Extraction 15x Faster](https://developer.nvidia.com/blog/nvidia-nemo-retriever-delivers-accurate-multimodal-pdf-data-extraction-15x-faster/) +- [NVIDIA NeMo Retriever Library Delivers Accurate Multimodal PDF Data Extraction 15x Faster](https://developer.nvidia.com/blog/nvidia-nemo-retriever-delivers-accurate-multimodal-pdf-data-extraction-15x-faster/) - [Finding the Best Chunking Strategy for Accurate AI Responses](https://developer.nvidia.com/blog/finding-the-best-chunking-strategy-for-accurate-ai-responses/) @@ -217,7 +217,7 @@ After you deploy the RAG blueprint, you can customize it for your use cases. Multimodal Embedding Support (Early Access) OCR Configuration Guide Enhanced PDF Extraction - Standalone NV-Ingest + Standalone NeMo Retriever Library Text-Only Ingestion MCP Server Usage ``` diff --git a/docs/mount-ingestor-volume.md b/docs/mount-ingestor-volume.md index a9bbb43c4..ff776e34d 100644 --- a/docs/mount-ingestor-volume.md +++ b/docs/mount-ingestor-volume.md @@ -4,7 +4,7 @@ --> # Ingestor Server Volume Mounting for NVIDIA RAG Blueprint -You can mount a host directory to access NV-Ingest extraction results directly from the filesystem when you use the [NVIDIA RAG Blueprint](readme.md). Designed for advanced developers who need programmatic access to raw extraction results for custom processing pipelines or external vector database integration. +You can mount a host directory to access extraction results from NeMo Retriever Library directly from the filesystem when you use the [NVIDIA RAG Blueprint](readme.md). Designed for advanced developers who need programmatic access to raw extraction results for custom processing pipelines or external vector database integration. ## Configuration diff --git a/docs/nemoretriever-ocr.md b/docs/nemoretriever-ocr.md index 1893da0aa..a76a7c113 100644 --- a/docs/nemoretriever-ocr.md +++ b/docs/nemoretriever-ocr.md @@ -11,17 +11,17 @@ This guide explains the OCR (Optical Character Recognition) services available i The NVIDIA RAG Blueprint supports two OCR services: -1. **NeMo Retriever OCR** (Default) - High-performance OCR service offering 2x+ faster performance +1. **NeMo Retriever Library OCR** (Default) - High-performance OCR service offering 2x+ faster performance 2. **Paddle OCR** (Legacy) - General-purpose OCR service maintained for compatibility :::{tip} -**NeMo Retriever OCR is now the default OCR service** and is recommended for all new deployments due to its superior performance and efficiency. +**NeMo Retriever Library OCR is now the default OCR service** and is recommended for all new deployments due to its superior performance and efficiency. ::: -## NeMo Retriever OCR (Default) +## NeMo Retriever Library OCR (Default) -NeMo Retriever OCR is the default and recommended OCR service for the NVIDIA RAG Blueprint, providing: +NeMo Retriever Library OCR is the default and recommended OCR service for the NVIDIA RAG Blueprint, providing: - **2x+ faster performance** compared to Paddle OCR - Optimized text extraction from documents and images @@ -38,7 +38,7 @@ NeMo Retriever OCR is the default and recommended OCR service for the NVIDIA RAG ### Default Configuration -By default, the NVIDIA RAG Blueprint is configured to use NeMo Retriever OCR with the following settings: +By default, the NVIDIA RAG Blueprint is configured to use NeMo Retriever Library OCR with the following settings: | Variable | Default Value | Description | |----------|---------------|-------------| @@ -49,11 +49,11 @@ By default, the NVIDIA RAG Blueprint is configured to use NeMo Retriever OCR wit ### Hardware Requirements -For detailed hardware requirements and GPU support, refer to the [NeMo Retriever OCR Support Matrix](https://docs.nvidia.com/nim/ingestion/image-ocr/1.2.1/support-matrix.html). +For detailed hardware requirements and GPU support, refer to the [NeMo Retriever Library OCR Support Matrix](https://docs.nvidia.com/nim/ingestion/image-ocr/1.2.0/support-matrix.html). ### Docker Configuration -The NeMo Retriever OCR service is configured in the Docker Compose file with the following key settings: +The NeMo Retriever Library OCR service is configured in the Docker Compose file with the following key settings: - **Image**: `nvcr.io/nim/nvidia/nemoretriever-ocr-v1:1.2.0` - **GPU Memory**: 8192 MB (default) @@ -72,7 +72,7 @@ export OCR_OMP_NUM_THREADS=8 # Set OpenMP threads ## Paddle OCR (Legacy) -Paddle OCR is maintained as a legacy option for compatibility with existing workflows. While still functional, it is recommended to migrate to NeMo Retriever OCR for better performance. +Paddle OCR is maintained as a legacy option for compatibility with existing workflows. While still functional, it is recommended to migrate to NeMo Retriever Library OCR for better performance. ### When to Use Paddle OCR @@ -92,7 +92,7 @@ The Paddle OCR service configuration: - **Ports**: 8009 (HTTP), 8010 (gRPC), 8011 (Metrics) :::{note} -**Legacy Service**: Paddle OCR is maintained as a legacy option. For new deployments, we recommend using the default NeMo Retriever OCR service for better performance. +**Legacy Service**: Paddle OCR is maintained as a legacy option. For new deployments, we recommend using the default NeMo Retriever Library OCR service for better performance. ::: @@ -100,9 +100,9 @@ The Paddle OCR service configuration: ### Docker Compose Deployment -#### Using NeMo Retriever OCR (Default) +#### Using NeMo Retriever Library OCR (Default) -NeMo Retriever OCR is deployed by default when you follow the standard deployment guide. No additional configuration is required. +NeMo Retriever Library OCR is deployed by default when you follow the standard deployment guide. No additional configuration is required. 1. **Prerequisites**: Follow the [deployment guide](deploy-docker-self-hosted.md) for standard setup. @@ -112,7 +112,7 @@ NeMo Retriever OCR is deployed by default when you follow the standard deploymen ``` :::{tip} - NeMo Retriever OCR is included in the default profile and will start automatically. + NeMo Retriever Library OCR is included in the default profile and will start automatically. ::: 3. **Verify Service Status**: @@ -134,7 +134,7 @@ If you need to use Paddle OCR instead: export OCR_MODEL_NAME=paddle ``` -3. **Stop NeMo Retriever OCR if running**: +3. **Stop NeMo Retriever Library OCR if running**: ```bash USERID=$(id -u) docker compose -f deploy/compose/nims.yaml down nemoretriever-ocr ``` @@ -144,7 +144,7 @@ If you need to use Paddle OCR instead: USERID=$(id -u) docker compose -f deploy/compose/nims.yaml --profile paddle up -d ``` -5. **Restart Ingestor Server and NV-Ingest Runtime**: +5. **Restart Ingestor Server and NeMo Retriever Library Runtime**: ```bash docker compose -f deploy/compose/docker-compose-ingestor-server.yaml up -d ``` @@ -154,9 +154,9 @@ If you need to use Paddle OCR instead: ### NVIDIA-Hosted Deployment -#### Using NeMo Retriever OCR (Default) +#### Using NeMo Retriever Library OCR (Default) -Follow the standard [NVIDIA-hosted deployment guide](deploy-docker-nvidia-hosted.md) - NeMo Retriever OCR is the default configuration. +Follow the standard [NVIDIA-hosted deployment guide](deploy-docker-nvidia-hosted.md) - NeMo Retriever Library OCR is the default configuration. #### Using Paddle OCR with NVIDIA-Hosted Deployment @@ -176,13 +176,13 @@ Follow the standard [NVIDIA-hosted deployment guide](deploy-docker-nvidia-hosted ### Helm Deployment -#### Using NeMo Retriever OCR (Default) +#### Using NeMo Retriever Library OCR (Default) -NeMo Retriever OCR is deployed by default with Helm installations. Follow the standard [Helm Deployment Guide](deploy-helm.md) - no additional OCR configuration is required. +NeMo Retriever Library OCR is deployed by default with Helm installations. Follow the standard [Helm Deployment Guide](deploy-helm.md) - no additional OCR configuration is required. #### Using Paddle OCR with Helm -To use Paddle OCR instead of the default NeMo Retriever OCR: +To use Paddle OCR instead of the default NeMo Retriever Library OCR: Modify [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml) to override the OCR service image: @@ -214,7 +214,7 @@ For detailed Helm deployment instructions, see [Helm Deployment Guide](deploy-he ### Environment Variables -| Variable | Description | NeMo Retriever Default | Paddle Default | Required | +| Variable | Description | NeMo Retriever Library Default | Paddle Default | Required | |----------|-------------|------------------------|----------------|----------| | `OCR_GRPC_ENDPOINT` | gRPC endpoint for OCR service | `nemoretriever-ocr:8001` | `paddle:8001` | Yes (on-premises) | | `OCR_HTTP_ENDPOINT` | HTTP endpoint for OCR service | `http://nemoretriever-ocr:8000/v1/infer` | `http://paddle:8000/v1/infer` | Yes | @@ -238,16 +238,16 @@ Replace `workstation_ip` with the actual IP address of the machine running the O ## Switching Between OCR Services -### Migrating from Paddle OCR to NeMo Retriever OCR +### Migrating from Paddle OCR to NeMo Retriever Library OCR -To switch to the default NeMo Retriever OCR service: +To switch to the default NeMo Retriever Library OCR service: 1. **Stop Paddle OCR**: ```bash USERID=$(id -u) docker compose -f deploy/compose/nims.yaml down paddle ``` -2. **Configure NeMo Retriever OCR environment variables**: +2. **Configure NeMo Retriever Library OCR environment variables**: ```bash export OCR_GRPC_ENDPOINT=nemoretriever-ocr:8001 export OCR_HTTP_ENDPOINT=http://nemoretriever-ocr:8000/v1/infer @@ -255,7 +255,7 @@ To switch to the default NeMo Retriever OCR service: export OCR_MODEL_NAME=scene_text_ensemble ``` -3. **Start NeMo Retriever OCR**: +3. **Start NeMo Retriever Library OCR**: ```bash USERID=$(id -u) docker compose -f deploy/compose/nims.yaml up -d nemoretriever-ocr ``` @@ -265,14 +265,14 @@ To switch to the default NeMo Retriever OCR service: docker compose -f deploy/compose/docker-compose-ingestor-server.yaml up -d ``` -### Migrating from NeMo Retriever OCR to Paddle OCR +### Migrating from NeMo Retriever Library OCR to Paddle OCR Follow the steps in [Switching to Paddle OCR](#switching-to-paddle-ocr) above. ## Performance Comparison -| Feature | NeMo Retriever OCR | Paddle OCR | +| Feature | NeMo Retriever Library OCR | Paddle OCR | |---------|-------------------|------------| | **Performance** | 2x+ faster | Baseline | | **GPU Memory** | 8 GB (default) | 3 GB (default) | @@ -297,13 +297,13 @@ Follow the steps in [Switching to Paddle OCR](#switching-to-paddle-ocr) above. 3. **Performance Issues** - Consider increasing `OCR_CUDA_MEMORY_POOL_MB` - - Adjust `OCR_BATCH_SIZE` for NeMo Retriever OCR + - Adjust `OCR_BATCH_SIZE` for NeMo Retriever Library OCR - Verify GPU has sufficient memory ### Getting Logs ```bash -# NeMo Retriever OCR logs +# NeMo Retriever Library OCR logs docker logs nemoretriever-ocr # Paddle OCR logs diff --git a/docs/nemotron-parse-extraction.md b/docs/nemotron-parse-extraction.md index c15cacf3f..0e2fc0b11 100644 --- a/docs/nemotron-parse-extraction.md +++ b/docs/nemotron-parse-extraction.md @@ -232,7 +232,7 @@ When using Nemotron Parse for PDF extraction, consider the following: - The extraction quality may vary depending on the PDF structure and content. - Nemotron Parse is not supported on NVIDIA B200 GPUs or RTX Pro 6000 GPUs. -For detailed information about hardware requirements and supported GPUs for all NeMo Retriever extraction NIMs, refer to the [Nemotron Parse Support Matrix](https://docs.nvidia.com/nim/vision-language-models/latest/support-matrix.html#nemotron-parse). +For detailed information about hardware requirements and supported GPUs for extraction NIMs used by NeMo Retriever Library, refer to the [Nemotron Parse Support Matrix](https://docs.nvidia.com/nim/vision-language-models/latest/support-matrix.html#nemotron-parse). ## Available PDF Extraction Methods diff --git a/docs/notebooks.md b/docs/notebooks.md index 6146e4abf..beff3dc94 100644 --- a/docs/notebooks.md +++ b/docs/notebooks.md @@ -101,7 +101,7 @@ Use the following notebooks to learn comprehensive Python client usage, metadata - [rag_library_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_library_usage.ipynb) – Demonstrates native usage of the NVIDIA RAG Python client, including environment setup, document ingestion, collection management, and querying. This notebook provides end-to-end API usage examples for interacting directly with the RAG system from Python, covering both ingestion and retrieval workflows. -- [rag_library_lite_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_library_lite_usage.ipynb) – Demonstrates containerless deployment of the NVIDIA RAG Python package in lite mode. Uses Milvus Lite (embedded vector database) and NV-Ingest subprocess mode for a simplified setup without Docker containers. Leverages NVIDIA cloud APIs for embeddings, ranking, and LLM inference. **Note**: This mode does not support image/table/chart citations or document summarization. +- [rag_library_lite_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_library_lite_usage.ipynb) – Demonstrates containerless deployment of the NVIDIA RAG Python package in lite mode. Uses Milvus Lite (embedded vector database) and NeMo Retriever Library subprocess mode for a simplified setup without Docker containers. Leverages NVIDIA cloud APIs for embeddings, ranking, and LLM inference. **Note**: This mode does not support image/table/chart citations or document summarization. - [langchain_nvidia_retriever.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/langchain_nvidia_retriever.ipynb) – Showcases **LangChain integration** with the NVIDIA RAG Blueprint. Run [ingestion_api_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/ingestion_api_usage.ipynb) first to ingest documents, then use `NVIDIARAGRetriever` for retrieval (sync/async), custom parameters, error handling, and optional RAG chaining with `ChatNVIDIA`. diff --git a/docs/nv-ingest-standalone.md b/docs/nv-ingest-standalone.md index 691a23607..14319ad94 100644 --- a/docs/nv-ingest-standalone.md +++ b/docs/nv-ingest-standalone.md @@ -3,19 +3,19 @@ SPDX-License-Identifier: Apache-2.0 --> -# Deploy NV-Ingest Standalone for NVIDIA RAG Blueprint +# Deploy NeMo Retriever Library Standalone for NVIDIA RAG Blueprint -This guide explains how to deploy and use NV-Ingest as a standalone service for [NVIDIA RAG Blueprint](readme.md) without deploying the full ingestor server. This is useful when you want to ingest documents directly using Python scripts. +This guide explains how to deploy and use NeMo Retriever Library as a standalone service for [NVIDIA RAG Blueprint](readme.md) without deploying the full ingestor server. This is useful when you want to ingest documents directly using Python scripts. For more details and advanced usage, refer to: -- [NVIDIA/nv-ingest repository](https://github.com/NVIDIA/nv-ingest) -- [Official NV-Ingest Quickstart Guide](https://github.com/NVIDIA/nv-ingest/blob/main/docs/docs/extraction/quickstart-guide.md) +- [NVIDIA/NeMo-Retriever Library repository](https://github.com/NVIDIA/NeMo-Retriever) +- [Official NeMo Retriever Library Quickstart Guide](https://docs.nvidia.com/nemo/retriever/) ## Limitations -When using NV-Ingest in standalone mode, consider the following limitations: +When using NeMo Retriever Library in standalone mode, consider the following limitations: -1. **Citations Disabled**: The RAG server's citation feature will be disabled for documents ingested through standalone NV-Ingest. This is because the citation metadata requires additional processing that is handled by the full ingestor server. +1. **Citations Disabled**: The RAG server's citation feature will be disabled for documents ingested through standalone NeMo Retriever Library. This is because the citation metadata requires additional processing that is handled by the full ingestor server. 2. **No Web UI**: The standalone deployment does not include the web-based upload interface. All document ingestion must be done through Python scripts. @@ -92,7 +92,7 @@ COLLECTION_NAME = "multimodal_data_nvingest" MILVUS_URI = "http://localhost:19530" MINIO_ENDPOINT = "localhost:9010" -# Server Mode (Create NV-Ingest client) +# Server Mode (Create NeMo Retriever Library client) client = NvIngestClient( message_client_hostname="localhost", message_client_port=7670 diff --git a/docs/python-client.md b/docs/python-client.md index e052b5e91..5c9bc33ea 100644 --- a/docs/python-client.md +++ b/docs/python-client.md @@ -170,7 +170,7 @@ nim-llm-ms Up ... (healthy) `DEPLOYMENT_MODE = "cloud"` -2. Configure NV-Ingest to use NVIDIA hosted cloud APIs using the following hosted models. +2. Configure NeMo Retriever Library to use NVIDIA hosted cloud APIs using the following hosted models. - os.environ["OCR_HTTP_ENDPOINT"] = "https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr" @@ -193,9 +193,9 @@ os.environ["YOLOX_HTTP_ENDPOINT"] = ( os.environ["YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL"] = "http" -### Setup NVIDIA Ingest Runtime and Redis Service +### Setup NeMo Retriever Library Runtime and Redis Service -Use the following command to setup your NVIDIA Ingest Runtime and Redis Service. +Use the following command to setup your NeMo Retriever Library Runtime and Redis Service. `docker compose -f ../deploy/compose/docker-compose-ingestor-server.yaml up nv-ingest-ms-runtime redis -d` diff --git a/docs/readme.md b/docs/readme.md index ec8c5cf8e..0248a4a00 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -147,5 +147,5 @@ After you deploy the RAG blueprint, you can customize it for your use cases. ## Blog Posts -- [NVIDIA NeMo Retriever Delivers Accurate Multimodal PDF Data Extraction 15x Faster](https://developer.nvidia.com/blog/nvidia-nemo-retriever-delivers-accurate-multimodal-pdf-data-extraction-15x-faster/) +- [NVIDIA NeMo Retriever Library Delivers Accurate Multimodal PDF Data Extraction 15x Faster](https://developer.nvidia.com/blog/nvidia-nemo-retriever-delivers-accurate-multimodal-pdf-data-extraction-15x-faster/) - [Finding the Best Chunking Strategy for Accurate AI Responses](https://developer.nvidia.com/blog/finding-the-best-chunking-strategy-for-accurate-ai-responses/) diff --git a/docs/release-notes.md b/docs/release-notes.md index 2a3550fad..5a3f1ccc3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -48,9 +48,9 @@ This release adds new features to the RAG pipeline for supporting agent workflow This release contains the following key changes: -- Updated NIMs and code to support [NVIDIA Ingest 26.01 release](https://docs.nvidia.com/nemo/retriever/latest/extraction/releasenotes-nv-ingest/). +- Updated NIMs and code to support [NeMo Retriever Library 26.01 release](https://docs.nvidia.com/nemo/retriever/latest/extraction/releasenotes-nv-ingest/). - Added support for non-NIM models including OpenAI, models hosted on AWS and Azure, OSS models, and others. Supported through service-specific API keys. For details, refer to [Get an API Key](api-key.md). -- The RAG Blueprint now uses [nemoretriever-ocr-v1](https://build.nvidia.com/nvidia/nemoretriever-ocr-v1/modelcard) as the default OCR model. For details, refer to [NeMo Retriever OCR Configuration Guide](nemoretriever-ocr.md). +- The RAG Blueprint now uses [nemoretriever-ocr-v1](https://build.nvidia.com/nvidia/nemoretriever-ocr-v1/modelcard) as the default OCR model. For details, refer to [NeMo Retriever Library OCR Configuration Guide](nemoretriever-ocr.md). - Improved VLM based generation support. The Vision-Language Model (VLM) inference feature now uses the model [nemotron-nano-12b-v2-vl](https://build.nvidia.com/nvidia/nemotron-nano-12b-v2-vl/modelcard). For details, refer to [VLM for Generation](vlm.md). - User interface improvements including catalog display, image and text query, and others. For details, refer to [User Interface](user-interface.md). - Added ingestion metrics endpoint support with OpenTelemetry (OTEL) for monitoring document uploads, elements ingested, and pages processed. For details, refer to [Observability](observability.md). @@ -72,7 +72,7 @@ This release contains the following key changes: - Shallow summarization support - Easy model switches and dedicated configurations - Ease of prompt changes -- Reserved field names `type`, `subtype`, and `location` for NV-Ingest exclusive use in metadata schemas. +- Reserved field names `type`, `subtype`, and `location` for NeMo Retriever Library exclusive use in metadata schemas. - Added support for [rag_library_lite_usage.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_library_lite_usage.ipynb) which demonstrates containerless deployment of the NVIDIA RAG Python package in lite mode. - Added example showcasing [NeMo Agent Toolkit integration](https://github.com/NVIDIA/NeMo-Agent-Toolkit) with NVIDIA RAG. - Added [weighted hybrid search](hybrid_search.md#weighted-hybrid-search) support with configurable weights. @@ -109,7 +109,7 @@ The following are the known issues for the NVIDIA RAG Blueprint: - Optional features reflection and image captioning are not available in Helm-based deployment. - Currently, Helm-based deployment is not supported for [NeMo Guardrails](nemo-guardrails.md). - The Blueprint responses can have significant latency when using [NVIDIA API Catalog cloud hosted models](deploy-docker-nvidia-hosted.md). -- The accuracy of the pipeline is optimized for certain file types like `.pdf`, `.txt`, `.docx`. The accuracy may be poor for other file types supported by NV-Ingest, since image captioning is disabled by default. +- The accuracy of the pipeline is optimized for certain file types like `.pdf`, `.txt`, `.docx`. The accuracy may be poor for other file types supported by NeMo Retriever Library, since image captioning is disabled by default. - When updating model configurations in Kubernetes `values.yaml` (for example, changing from 70B to 8B models), the RAG UI automatically detects and displays the new model configuration from the backend. No container rebuilds are required - simply redeploy the Helm chart with updated values and refresh the UI to see the new model settings in the Settings panel. - The NeMo LLM microservice can take 5-6 minutes to start for every deployment. - B200 GPUs are not supported for the following advanced features. For these features, use H100 or A100 GPUs instead. diff --git a/docs/service-port-gpu-reference.md b/docs/service-port-gpu-reference.md index f95c9925a..ed24b39f2 100644 --- a/docs/service-port-gpu-reference.md +++ b/docs/service-port-gpu-reference.md @@ -13,7 +13,7 @@ The following table provides a comprehensive reference of all services, their po | RAG Server | `rag-server` | 8081 | 8081 | N/A (CPU) | Main RAG API endpoint | | Ingestor Server | `ingestor-server` | 8082 | 8082 | N/A (CPU) | Document ingestion API | | RAG Frontend | `rag-frontend` | 8090 | 3000 | N/A (CPU) | Web UI | -| NV-Ingest Runtime | `nv-ingest-ms-runtime` | 7670, 7671, 8265 | 7670, 7671, 8265 | N/A (CPU) | Main orchestrator (Ray dashboard: 8265) | +| NeMo Retriever Library Runtime | `nv-ingest-ms-runtime` | 7670, 7671, 8265 | 7670, 7671, 8265 | N/A (CPU) | Main orchestrator (Ray dashboard: 8265) | ## NIM Microservices @@ -29,7 +29,7 @@ The following table provides a comprehensive reference of all services, their po | Page Elements | `compose-page-elements-1` | 8000, 8001, 8002 | 8000, 8001, 8002 | 0 | `YOLOX_MS_GPU_ID` | Object detection for pages | | Graphic Elements | `compose-graphic-elements-1` | 8003, 8004, 8005 | 8000, 8001, 8002 | 0 | `YOLOX_GRAPHICS_MS_GPU_ID` | Graphics detection | | Table Structure | `compose-table-structure-1` | 8006, 8007, 8008 | 8000, 8001, 8002 | 0 | `YOLOX_TABLE_MS_GPU_ID` | Table structure detection | -| NeMo Retriever OCR | `compose-nemoretriever-ocr-1` | 8012, 8013, 8014 | 8000, 8001, 8002 | 0 | `OCR_MS_GPU_ID` | OCR service (default) | +| NeMo Retriever Library OCR | `compose-nemoretriever-ocr-1` | 8012, 8013, 8014 | 8000, 8001, 8002 | 0 | `OCR_MS_GPU_ID` | OCR service (default) | ## Vector Database and Infrastructure diff --git a/docs/text_only_ingest.md b/docs/text_only_ingest.md index 10cb6e9b1..6ba23fd19 100644 --- a/docs/text_only_ingest.md +++ b/docs/text_only_ingest.md @@ -19,7 +19,7 @@ You can enable text-only ingestion for the [NVIDIA RAG Blueprint](readme.md). Fo ``` :::{important} - When disabling nv-ingest dependent services, you must set `COMPONENTS_TO_READY_CHECK=""` to ensure the nv-ingest container reaches ready state. Without this setting, nv-ingest will wait indefinitely for the disabled components. + When disabling NeMo Retriever Library dependent services, you must set `COMPONENTS_TO_READY_CHECK=""` to ensure the NeMo Retriever Library container reaches ready state. Without this setting, the NeMo Retriever Library container will wait indefinitely for the disabled components. ::: Then deploy the ingestor-server: @@ -131,9 +131,9 @@ helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprin ``` :::{important} -**Disabling NV-Ingest Components for GPU Resource Management:** +**Disabling NeMo Retriever Library Components for GPU Resource Management:** -If you disable any nv-ingest dependent services (such as `table_structure`, `graphic_elements`, `nemoretriever_ocr_v1`, etc.) to free up GPU resources for customization, you must set the `COMPONENTS_TO_READY_CHECK` parameter to an empty string in the `nv-ingest.envVars` section of your [values.yaml](../deploy/helm/nvidia-blueprint-rag/values.yaml) file: +If you disable any NeMo Retriever Library dependent services (such as `table_structure`, `graphic_elements`, `nemoretriever_ocr_v1`, etc.) to free up GPU resources for customization, you must set the `COMPONENTS_TO_READY_CHECK` parameter to an empty string in the `nv-ingest.envVars` section of your [values.yaml](../deploy/helm/nvidia-blueprint-rag/values.yaml) file: ```yaml nv-ingest: @@ -141,6 +141,6 @@ nv-ingest: COMPONENTS_TO_READY_CHECK: "" ``` -This ensures the nv-ingest pod reaches ready state even when some dependent components are disabled. Without this setting, the nv-ingest pod will wait indefinitely for the disabled components to become ready. +This ensures the NeMo Retriever Library pod reaches ready state even when some dependent components are disabled. Without this setting, the NeMo Retriever Library pod will wait indefinitely for the disabled components to become ready. ::: diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 485ae84f1..782176ed2 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -340,7 +340,7 @@ If the above error related to dependency conflicts are seen while building conta We've integrated VDB and embedding creation directly into the pipeline with caching included for expediency. However, in a production environment, it's better to use a separately managed VDB service. -NVIDIA offers optimized models and tools like NVIDIA NeMo Retriever ([build.nvidia.com/explore/retrieval](https://build.nvidia.com/explore/retrieval)) +NVIDIA offers optimized models and tools like NVIDIA NeMo Retriever Library ([build.nvidia.com/explore/retrieval](https://build.nvidia.com/explore/retrieval)) and cuVS ([github.com/rapidsai/cuvs](https://github.com/rapidsai/cuvs)). diff --git a/docs/vlm-embed.md b/docs/vlm-embed.md index 5b9913232..6cd5b0304 100644 --- a/docs/vlm-embed.md +++ b/docs/vlm-embed.md @@ -214,7 +214,7 @@ ingestor-server: nv-ingest: envVars: - # NV-Ingest runtime embedding target + # NeMo Retriever Library runtime embedding target EMBEDDING_NIM_ENDPOINT: "http://nemotron-vlm-embedding-ms:8000/v1" EMBEDDING_NIM_MODEL_NAME: "nvidia/llama-nemotron-embed-vl-1b-v2" ``` From 59a43ca300288fe810c43d31bf032f865f2d98c3 Mon Sep 17 00:00:00 2001 From: Shubhadeep Das <149712532+shubhadeepd@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:43:43 +0530 Subject: [PATCH 37/52] Launchable Updates for release 2.5 (#428) (#429) Co-authored-by: rkharwar-nv --- notebooks/launchable.ipynb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/notebooks/launchable.ipynb b/notebooks/launchable.ipynb index 22e293f90..cccba6eba 100644 --- a/notebooks/launchable.ipynb +++ b/notebooks/launchable.ipynb @@ -127,6 +127,16 @@ "RAG_BASE_URL = f\"http://{IPADDRESS}:{RAG_SERVER_PORT}\"\n", "INGESTOR_BASE_URL = f\"http://{IPADDRESS}:{INGESTOR_SERVER_PORT}\"\n", "\n", + "# NIM services to deploy (excludes nim-llm and vlm-ms since we use NVIDIA-hosted endpoints)\n", + "NIM_SERVICES = (\n", + " \"nemotron-embedding-ms \"\n", + " \"nemotron-ranking-ms \"\n", + " \"page-elements \"\n", + " \"graphic-elements \"\n", + " \"table-structure \"\n", + " \"nemoretriever-ocr\"\n", + ")\n", + "\n", "\n", "# =============================================================================\n", "# DOCKER COMPOSE HELPERS\n", @@ -1295,10 +1305,10 @@ "902445432dde milvus-standalone Up 3 minutes\n", "340bc8210a0d milvus-minio Up 3 minutes (healthy)\n", "0be702b87ad6 milvus-etcd Up 3 minutes (healthy)\n", - "fe2751bfa734 nemotron-ranking-ms Up 10 minutes (healthy)\n", + "fe2751bfa734 nemotron-ranking-ms Up 4 seconds (healthy)\n", "7b5ddabf8be7 compose-graphic-elements-1 Up 10 minutes\n", "ecfaa5190302 compose-page-elements-1 Up 10 minutes\n", - "ea8c7fdf20d1 nemotron-embedding-ms Up 10 minutes (healthy)\n", + "ea8c7fdf20d1 nemotron-embedding-ms Up 4 seconds (healthy)\n", "6d62008a9b42 compose-nemoretriever-ocr-1 Up 10 minutes\n", "969b9f5c987c compose-table-structure-1 Up 10 minutes\n", "```\n", @@ -2056,7 +2066,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.12.13" } }, "nbformat": 4, From 1c544b2ecffabe7f607bd1b9256b4d9b242c883e Mon Sep 17 00:00:00 2001 From: Kurt Heiss Date: Thu, 12 Mar 2026 22:15:42 -0700 Subject: [PATCH 38/52] Kheiss/cont ingest (#431) * confirming presence of switcher text in conf.py file * Continuous ingestion topic * updated index for continuous ingestion * updated reademe for continuous ingestion * Update continuous-ingestion-object-storage.md added RAG Blueprint * Update continuous-ingestion-object-storage.md Converted first sentence into 2 sentences * Update index.md * Apply suggestion from @nkmcalli Co-authored-by: nkmcalli * Update continuous-ingestion-object-storage.md * Update continuous-ingestion-object-storage.md * Update continuous-ingestion-object-storage.md --------- Co-authored-by: nkmcalli --- docs/continuous-ingestion-object-storage.md | 121 ++++++++++++++++++++ docs/index.md | 2 + 2 files changed, 123 insertions(+) create mode 100644 docs/continuous-ingestion-object-storage.md diff --git a/docs/continuous-ingestion-object-storage.md b/docs/continuous-ingestion-object-storage.md new file mode 100644 index 000000000..a718224d9 --- /dev/null +++ b/docs/continuous-ingestion-object-storage.md @@ -0,0 +1,121 @@ + +# Continuous Ingestion from Object Storage RAG Blueprint + +Continuous ingestion from object storage connects the [RAG blueprint](readme.md) to continuous integration. This enables an event-driven pipeline that automatically indexes documents and, optionally, video files. Continuous integration means that when you add files to a storage bucket, the system detects new uploads, routes them for processing, and indexes their content—making all data immediately searchable and available for analysis through the [RAG Frontend](user-interface.md). + +## Overview + +You can create an event-driven continuous ingestion pipeline that works as follows: + +1. Upload files (documents or, optionally, videos) to object storage. + +2. The system detects new uploads via storage events and routes them for processing. + +3. Content is automatically indexed into the RAG vector store. + +4. You can then query the ingested content through the RAG UI or API. + +Continuous ingestion supports the following content types: + +- **Documents:** PDF, DOCX (and other formats supported by the [ingestor](api-ingestor.md)). +- **Video (optional/future):** MP4, MKV, AVI — typically processed by a Video Summary Service (VSS) and then ingested as documents. + +## Architecture + +The continuous ingestion architecture features the following high-level flow: + +1. Object storage: Files are written to storage using a protocol that emits events (for example, MinIO configured with Kafka notifications). + +2. Event trigger: Upload events are published to a Kafka topic. + +3. Consumer: A Kafka consumer subscribes to the topic, retrieves the events, downloads the corresponding files from object storage, and routes them for processing. + +4. Document path: Files are passed to a file-based processing pipeline (such as the NeMo Retriever Library or ingestor-server) and then indexed in the vector database. + +5. Video path (optional): For video files, the consumer submits a processing request to the Video Search Service (VSS), polls for results, uploads the processed outputs to RAG as documents, and continues through the standard file-based ingestion flow. + + +The continuous ingestion architecture follows the end-to-end sequence described above and can be summarized as: + +- Document ingestion flow: (1) → (2) → (3) → file-based processing → VectorDB → RAG Agent. + +- Video ingestion flow: (1) → (2) → (3) → VSS request → poll for results → upload to RAG as a document → file-based processing → VectorDB → RAG Agent. + +## Implementation Components + +The reference implementation includes the following components: + +- Object storage (MinIO): A bucket configured with Kafka notifications on put (and optionally delete) events. + +- Kafka: A broker and topic (for example, aidp-topic) used to publish storage event notifications. + +- Kafka consumer: A service that: + +-- Subscribes to the Kafka topic and consumes storage events. + +-- Downloads new objects from MinIO and routes them based on type (document or video). + +-- For documents: Sends files to the RAG ingestor for indexing. + +-- For videos (optional): Submits files to the Video Search Service (VSS), then ingests VSS outputs as documents. + +The deployment is defined in `examples/rag_event_ingest/deploy/docker-compose.yaml`, which runs MinIO, Kafka, and the Kafka consumer on the same Docker network as the RAG stack (`nvidia-rag`). + +### Prerequisites + +- [Deploy the NVIDIA RAG Blueprint](deploy-docker-self-hosted.md) (NIMs, Milvus, ingestor-server, RAG server) so the consumer can reach the ingestor and the rest of the stack. +- Ensure the `nvidia-rag` Docker network exists (created by the RAG deployment). +- For the notebook, clone the repo, set `NGC_API_KEY`, and have the required hardware (see notebook for GPU and software requirements). + +### Option 1: Use the Notebook + +The notebook provides a guided walkthrough of the following steps: + +- Environment setup +- NVIDIA RAG deployment +- (Optional) NVIDIA VSS deployment for video processing +- Continuous ingestion pipeline deployment (Kafka, MinIO, and consumer) +- Testing document and optional video uploads with RAG queries +- Cleanup + +To follow along, open and run: [rag_event_ingest.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_event_ingest.ipynb). + +### Option 2: Deploy the Example with Docker Compose + +From the repository root, after the RAG stack is up: + +```bash +docker compose -f examples/rag_event_ingest/deploy/docker-compose.yaml up -d +``` + +This command launches the following components: + +- Kafka (with an optional Kafka UI available on port 8080) +- MinIO (object storage and console using ports 9201 and 9211 in the example) +- Kafka consumer — connects to the ingestor at `INGESTOR_SERVER_URL` (default: `http://ingestor-server:8082`) and uses `COLLECTION_NAME` (default: `aidp_bucket`) + +After deployment, upload files to the MinIO bucket (for example, `aidp-bucket`). The system publishes upload events to Kafka, the consumer retrieves the corresponding files, and documents are sent to the ingestor for indexing. You can then query the same collection using the RAG UI or API. + +### Key Environment Variables + +The following environment variables configure the Kafka consumer. For details, refer to `examples/rag_event_ingest/deploy/docker-compose.yaml`. + +Consumer environment variables + +| Variable | Description | Default Value| +|----------|---------|--------| +| `KAFKA_BOOTSTRAP_SERVERS` | Address of the Kafka broker(s). | `kafka:9092` | +| `KAFKA_TOPIC` |Kafka topic used for object storage events. | `aidp-topic` | +| `MINIO_ENDPOINT` | MinIO endpoint in : format. | `minio-source-1:9000` | +| `INGESTOR_SERVER_URL` | Base URL for the RAG ingestor service. | `http://ingestor-server:8082` | +| `COLLECTION_NAME` | Target RAG collection for content indexing. | `aidp_bucket` | + +## Reference + +- [RAG Blueprint deployment (Docker self-hosted)](deploy-docker-self-hosted.md) +- [Ingestor API](api-ingestor.md) +- [Notebook: Document continuous ingestion from object storage](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_event_ingest.ipynb) +- [Example: `examples/rag_event_ingest/`](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/examples/rag_event_ingest/) — Kafka consumer and `deploy/docker-compose.yaml` diff --git a/docs/index.md b/docs/index.md index 1e62202a6..d3c1a3ec9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -81,6 +81,7 @@ After you deploy the RAG blueprint, you can customize it for your use cases. - Data Ingestion & Processing - [Audio Ingestion Support](audio_ingestion.md) + - [Continuous Ingestion from Object Storage](continuous-ingestion-object-storage.md) - [Custom Metadata Support](custom-metadata.md) - [File System Access to Extraction Results](mount-ingestor-volume.md) - [Multimodal Embedding Support (Early Access)](vlm-embed.md) @@ -211,6 +212,7 @@ After you deploy the RAG blueprint, you can customize it for your use cases. :hidden: Audio Ingestion Support + Continuous Ingestion from Object Storage Custom metadata Support Data Catalog for Collections and Documents File System Access to Results From 0f6c99059ab98921a3487a719424640af32961f0 Mon Sep 17 00:00:00 2001 From: nv-pranjald <150428320+nv-pranjald@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:07:00 +0530 Subject: [PATCH 39/52] Nemotron 3 super deployment guide and migration guide (#430) * Nemotron 3 super deployment guide and migration guide * Organize gpu requirement and heading for nemotron3 super * Add instruction for updating values.yaml and refractor doc * Add cloud endpoint url in env file * Instruction to export llm max token in docker flow * remove unrequired llm api key from doc * Seprate yaml for nemotron 3 deployment * Remove unnecessary information for local hosted * Simplify docker deployment logs * Remove cuda device from rtx 6000 pro * Add prompt customization instruction in nemotron3 helm section * Instruction for prompt customization * Remove heading for rtx 6000 pro --- deploy/compose/nemotron3-super-cloud.env | 49 ++ deploy/compose/nemotron3-super-prompt.yaml | 445 ++++++++++++++++++ deploy/compose/nemotron3-super.env | 34 ++ .../nemotron3-super-rtx6000-values.yaml | 25 + .../nemotron3-super-values.yaml | 39 ++ deploy/helm/nvidia-blueprint-rag/values.yaml | 25 +- docs/change-model.md | 5 + docs/deploy-helm.md | 2 + docs/enable-nemotron-thinking.md | 4 + docs/nemotron3-super-deployment.md | 181 +++++++ 10 files changed, 808 insertions(+), 1 deletion(-) create mode 100644 deploy/compose/nemotron3-super-cloud.env create mode 100644 deploy/compose/nemotron3-super-prompt.yaml create mode 100644 deploy/compose/nemotron3-super.env create mode 100644 deploy/helm/nvidia-blueprint-rag/nemotron3-super-rtx6000-values.yaml create mode 100644 deploy/helm/nvidia-blueprint-rag/nemotron3-super-values.yaml create mode 100644 docs/nemotron3-super-deployment.md diff --git a/deploy/compose/nemotron3-super-cloud.env b/deploy/compose/nemotron3-super-cloud.env new file mode 100644 index 000000000..468bd2fb7 --- /dev/null +++ b/deploy/compose/nemotron3-super-cloud.env @@ -0,0 +1,49 @@ +# ============================================================================== +# Nemotron 3 Super - NVIDIA-hosted (cloud) endpoints +# ============================================================================== +# Self-contained cloud + Nemotron 3 Super. Source after .env so cloud endpoints +# override on-prem defaults: source deploy/compose/.env && source deploy/compose/nemotron3-super-cloud.env +# No need to edit .env (uncomment/comment sections). +# ============================================================================== + +# === Authentication === +export NVIDIA_API_KEY=${NGC_API_KEY} + +# === Embeddings, Ranking, OCR, YOLOX (cloud) === +export APP_EMBEDDINGS_SERVERURL=https://integrate.api.nvidia.com/v1 +export APP_RANKING_SERVERURL=https://integrate.api.nvidia.com/v1 +export OCR_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemoretriever-ocr +export OCR_INFER_PROTOCOL=http +export OCR_MODEL_NAME=scene_text_ensemble +export YOLOX_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-page-elements-v3 +export YOLOX_INFER_PROTOCOL=http +export YOLOX_GRAPHIC_ELEMENTS_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-graphic-elements-v1 +export YOLOX_GRAPHIC_ELEMENTS_INFER_PROTOCOL=http +export YOLOX_TABLE_STRUCTURE_HTTP_ENDPOINT=https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-table-structure-v1 +export YOLOX_TABLE_STRUCTURE_INFER_PROTOCOL=http + +# === LLM === +export APP_LLM_MODELNAME=nvidia/nemotron-3-super-120b-a12b +export APP_LLM_SERVERURL=https://integrate.api.nvidia.com/v1 + +# === Query Rewriter === +export APP_QUERYREWRITER_MODELNAME=nvidia/nemotron-3-super-120b-a12b +export APP_QUERYREWRITER_SERVERURL=https://integrate.api.nvidia.com/v1 + +# === Filter Expression Generator === +export APP_FILTEREXPRESSIONGENERATOR_MODELNAME=nvidia/nemotron-3-super-120b-a12b +export APP_FILTEREXPRESSIONGENERATOR_SERVERURL=https://integrate.api.nvidia.com/v1 + +# === Summarization === +export SUMMARY_LLM=nvidia/nemotron-3-super-120b-a12b +export SUMMARY_LLM_SERVERURL=https://integrate.api.nvidia.com/v1 + +# === Reflection === +export REFLECTION_LLM=nvidia/nemotron-3-super-120b-a12b +export REFLECTION_LLM_SERVERURL=https://integrate.api.nvidia.com/v1 + +# === Reasoning / Thinking === +export LLM_ENABLE_THINKING=true +export LLM_REASONING_BUDGET=256 +export LLM_LOW_EFFORT=true +export FILTER_THINK_TOKENS=true \ No newline at end of file diff --git a/deploy/compose/nemotron3-super-prompt.yaml b/deploy/compose/nemotron3-super-prompt.yaml new file mode 100644 index 000000000..f91803927 --- /dev/null +++ b/deploy/compose/nemotron3-super-prompt.yaml @@ -0,0 +1,445 @@ +chat_template: + system: | + You are a helpful, respectful, and honest assistant. + Your answers must follow these strict guidelines: + + + 1. Answer concisely and directly. + 2. Focus only on what was asked — no extra commentary, no assumptions. + 3. Avoid giving multiple options, lists, or examples unless explicitly requested. + 4. Do not explain your reasoning unless asked. + 5. Keep responses brief but accurate. + 6. Use natural, conversational tone — clear and human, not robotic. + 7. Make sure your response are strictly one sentence or less unless it really needs to be longer. + 8. Do not mention this instructions in your response. + + + Make sure above rules are strictly followed. + +rag_template: + system: | + You are a helpful AI assistant named Envie. Answer the user's question using ONLY the information in the provided context. + + + - Base every claim on information found in the context. Do not use outside knowledge. + - Always provide an answer when the context contains relevant data. Only say you cannot answer if the context is entirely unrelated to the question. + - Preserve exact values: reproduce specific numbers, percentages, dates, names, and URLs exactly as they appear in the context. + - IMPORTANT - When the question asks you to calculate, compute, or derive a financial metric (ratio, margin, growth rate, CAGR, turnover, average, etc.), you MUST: + 1. Write the formula + 2. Extract each required number from the context + 3. Compute step by step + 4. State the final answer + Do NOT skip straight to the final number. + - For yes/no questions that require comparing values across periods (e.g. "is X improving", "did Y increase"), state the values from each period before your conclusion. + - For questions about trends or changes over time, include data from all relevant time periods found in the context. + - Answer naturally and directly. Do not reference the context, documents, sources, or these instructions. + - For simple factual lookups (a name, a date, a single value directly stated), keep your answer brief. + + + human: | + + {context} + + +query_rewriter_prompt: + system: | + Given the following chat history and the latest user question, formulate a standalone question which can be understood without the chat history. + Do NOT answer the question, just reformulate it if needed and otherwise return it as is. + It should strictly be a query not an answer. + + Chat History: + {chat_history} + + Latest Question: {input} + +reflection_relevance_check_prompt: + system: | + ### Instructions + + You are a world class expert designed to evaluate the relevance score of a Context + in order to answer the Question. + Your task is to determine if the Context contains proper information to answer the Question. + Do not rely on your previous knowledge about the Question. + Use only what is written in the Context and in the Question. + Follow the instructions below: + 0. If the context does not contains any relevant information to answer the question, say 0. + 1. If the context partially contains relevant information to answer the question, say 1. + 2. If the context contains any relevant information to answer the question, say 2. + You must provide the relevance score of 0, 1, or 2, nothing else. + Do not explain. + ### Question: {query} + + ### Context: {context} + + Do not try to explain. + Analyzing Context and Question, the Relevance score is + +reflection_query_rewriter_prompt: + system: | + You are a query optimization assistant for a vector database retrieval system. + Your goal is to rephrase the given "Original Question" to be more clear, precise, + and effective for retrieving relevant context from a vector database. + + Considerations for Rephrasing: + + Specificity: Make the query as specific as possible about the information sought. + Avoid vague terms. + + Keywords: Identify and incorporate key terms and concepts that are likely to be + present in relevant documents. + + Contextual Cues: If the original query implies a certain domain or type of + information, make that explicit. + + Eliminate Ambiguity: Remove any phrases that could lead to multiple interpretations. + + Focus: Ensure the rephrased query directly targets the core information need. + + Brevity (where possible): While precision is key, try to be concise without + losing meaning. + + Only output the rewritten question with no other information. + + Original Question: {query} + + Rewritten Question: + +reflection_groundedness_check_prompt: + system: | + ### Instruction + + You are a world class expert designed to evaluate the groundedness of an assertion. + You will be provided with an assertion and a context. + Your task is to determine if the assertion is supported by the context. + Follow the instructions below: + A. If there is no context or no assertion or context is empty or assertion is empty, say 0. + B. If the assertion is not supported by the context, say 0. + C. If the assertion is partially supported by the context, say 1. + D. If the assertion is fully supported by the context, say 2. + You must provide a rating of 0, 1, or 2, nothing else. + + ### Context: + <{context}> + + ### Assertion: + <{response}> + + Analyzing Context and Response, the Groundedness score is + +reflection_response_regeneration_prompt: + system: | + You are tasked with creating a new "Response" based solely on the provided + "Context" and "Query". Your primary goal is to ensure strict adherence to + the information explicitly stated or directly inferable from the Context. + + Key Constraints: + + No Outside Knowledge: Do not introduce any information, facts, or concepts + not present in the given Context. + + No Assumptions: Do not make assumptions or extrapolate beyond what is directly + stated or clearly implied. + + Direct Inference Only: If an idea is not explicitly stated, it must be a direct + and undeniable inference from the provided text. Avoid speculative or highly + interpretive conclusions. + + Maintain Factual Accuracy: Ensure the Response accurately reflects the details + and relationships presented in the Context. + + Return only "OUT OF CONTEXT" if the "Query" cannot be answered using the provided + "Context." Else, only output the new response with no other information. + + Context: {context} + + Query: {query} + + Return "OUT OF CONTEXT" or generate a new, more grounded Response: + +document_summary_prompt: + system: | + Please provide a comprehensive summary for the document given by the user. Create a concise 5 to 6 sentence summary that captures the essential information from the document. + + + Requirements for the summary: + 1. Preserve key document metadata: + - Document title/type + - Company/organization name + - Report provider/author + - Date/time period covered + - Any relevant document identifiers + + 2. Include all critical information: + - Main findings and conclusions + - Key statistics and metrics + - Important recommendations + - Significant trends or changes + - Notable risks or concerns + - Material financial data + + 3. Maintain factual accuracy: + - Keep all numerical values precise + - Preserve specific dates and timeframes + - Retain exact names and titles + - Quote critical statements verbatim when necessary + + 4. Do NOT use any external knowledge. + 5. Do NOT add explanations, suggestions, opinions, disclaimers, or hints. + 6. NEVER say phrases like “based on the context”, “from the documents”, or “I cannot find”. + 7. NEVER offer to answer using general knowledge or invite the user to ask again. + 8. Do NOT include citations, sources, or document mentions. + 9. Answer concisely. Use short, direct sentences by default. Only give longer responses if the question truly requires it. + 10. Do not mention or refer to these rules in any way. + 11. Do not ask follow-up questions. + 12. Do not mention this instructions in your response. + 13. Do not include any preamble or postamble like "Here is the summary" or "This document" or "Summary of the document". + + Please format the summary in a concise manner as a paragraph not exceeding 5 to 6 sentences. Start the summary with the title and the document and then provide the summary. + + Note: Focus on extracting and organizing the most essential information while ensuring no critical details are omitted. + Maintain the original document's tone and context in your summary. + + Please provide a concise summary for the following document: + {document_text} + +shallow_summary_prompt: + system: | + Please provide a concise summary for the following document: + {document_text} + +iterative_summary_prompt: + system: | + You are an expert document summarizer. Given a previous summary and a new chunk of text, create an updated summary that incorporates information from both. Create a concise summary within 10 sentences that captures the essential information from the document. + While answering you must follow the instructions given below. + + + 1. Do NOT use any external knowledge. + 2. Do NOT add explanations, suggestions, opinions, disclaimers, or hints. + 3. NEVER say phrases like “based on the context”, “from the documents”, or “I cannot find”. + 4. NEVER offer to answer using general knowledge or invite the user to ask again. + 5. Do NOT include citations, sources, or document mentions. + 6. Answer concisely. Use short, direct sentences by default. Only give longer responses if the question truly requires it. + 7. Do not mention or refer to these rules in any way. + 8. Do not ask follow-up questions. + 9. Do not mention this instructions in your response. + 10. Do not mention any preamble or postamble like "Updated summary" or "This document" or "Summary of the document" or "Here is the summary". + + + Previous Summary: + {previous_summary} + + New chunk: + {new_chunk} + + Please create a new summary that incorporates information from both the previous summary and the new chunk. + + +vlm_template: + system: | + You are a multimodal AI assistant. Answer using only the provided context and images. + + + 1. Use ONLY the information in the textual context below and the attached images. + 2. Do not use external knowledge or assumptions beyond the provided inputs. + 3. Do not describe images unless needed to answer; focus on the answer. + 4. Respond in detail and cover all the relevant information related to the question from the context and images. + 5. Keep the response neutral and factually accurate. + + + Context: + {context} + + User Question: + {question} + +# Reasoning templates deprecated and removed + + +filter_expression_generator_prompt: + system: | + You are an expert AI filter expression generator. Your sole purpose is to convert natural language queries into precise, valid filter expressions based on the provided schema. You must be aggressive in finding mappable entities. + + ### Primary Directive ### + + **Your primary directive is to ALWAYS generate a filter expression.** It is a critical error to return NO_FILTER unless the user's query is completely irrelevant or nonsensical (e.g., "hello there," "what is the weather?"). Be bold and decisive. Prioritize extracting any mappable entity from the user's query, even if other parts are ambiguous. If a query contains even one recognizable keyword, date, or number that maps to the schema, you must build a filter around it. + + ### Schema ### + + Use the following schema to identify available fields and their data types. + {metadata_schema} + + ### Core Logic ### + + 1. **Extract and Build:** Scan the user's query for any recognizable entities (names, numbers, dates, keywords) that could map to the schema. Build a filter using every piece of information you can extract. Ignore everything else that is conversational or does not map to a field. + 2. **Field Format:** The field format is always content_metadata["field_name"]. + 3. **Operators:** Use uppercase logical operators: AND, OR, NOT. Use parentheses () to group expressions. + + ### Operators & Data Types (Complete List) ### + + 1. **String**: ==, !=, in, like + * Example: content_metadata["doc_type"] in ["report", "summary"] + 2. **Number**: ==, !=, >, >=, <, <=, in, between + * Example: content_metadata["page_count"] > 10 + 3. **Datetime** (Format: YYYY-MM-DDTHH:MM:SS): ==, !=, >, >=, <, <= + * Example: content_metadata["created_at"] >= "2024-01-01T00:00:00" + 4. **Boolean**: ==, != + * Example: content_metadata["is_public"] == true + 5. **Array**: array_contains, array_contains_any, array_contains_all, array_length + * Single value: array_contains(content_metadata["category"], "AI") + * Multiple values (any): array_contains_any(content_metadata["regions"], ["EMEA", "APAC"]) + * Multiple values (all): array_contains_all(content_metadata["tags"], ["urgent", "review"]) + + ### Intelligent Mapping Examples ### + + * **Query:** "Project X" + * **Action:** Recognizes "Project X" as a single mappable entity and builds a filter. + * **Output:** content_metadata["project"] == "Project X" + * **Query:** "approved" + * **Action:** Recognizes "approved" as a status and builds a filter just for that. + * **Output:** content_metadata["status"] == "approved" + * **Query:** "Find the latest financial reports for Project X" + * **Action:** Ignore "latest" as it's subjective. Extract "financial reports" and "Project X". + * **Output:** (content_metadata["doc_type"] == "financial_report" AND content_metadata["project"] == "Project X") + * **Query:** "I think I need the document from Q2 last year about compliance" + * **Action:** Ignore "I think I need". Extract "Q2 last year" (2024) and "compliance". + * **Output:** (content_metadata["created_at"] >= "2024-04-01T00:00:00" AND content_metadata["created_at"] < "2024-07-01T00:00:00" AND array_contains(content_metadata["tags"], "compliance")) + + ### Your Task ### + + Convert the following user query into a filter expression. + {user_request} + + ### Response Format ### + + Your response **MUST** be only the raw filter expression string and nothing else. Do not use explanations, comments, or markdown. + + 1. **On Success:** The filter expression string. + * content_metadata["year"] == 2024 + + 2. **On Absolute Failure:** The exact text NO_FILTER. + * **Use this ONLY if the query is completely unrelated to the schema**, like "what is your name?" or "tell me a joke". + + 3. **On Logical Conflict:** The exact text UNSUPPORTED. + * **Use this ONLY for impossible logic**, like "year is 2022 and year is 2023". + +query_decomposition_multiquery_prompt: + system: | + You are an AI assistant designed to break down a user's complex question into a list of simpler, focused subqueries. + The purpose of this decomposition is to improve the accuracy of a retrieval-augmented generation (RAG) system. + + + 1. Analyze the user's main question to identify its key components. + 2. Decompose the question into 1-3 distinct, self-contained subqueries. + 3. If the original question is simple and already focused, return query directly. + 4. Each subquery should be a clear, direct question that, when answered, contributes to a comprehensive response to the original question. + 5. Avoid creating redundant or overly broad subqueries. Focus on the core information needed to answer the original prompt + + + Return only the subqueries as a numbered list, without any additional text. + Original question: {question} + +query_decompositions_query_rewriter_prompt: + system: | + You are an expert at rewriting queries to improve information retrieval for a conversational AI system. Your task is to take a user's new question and the preceding conversation history and rewrite the question into a single, highly specific query. This new query should be ideal for a search or retrieval system. + + + 1. Analyze the conversation history to identify all necessary context, such as entities, topics, or constraints that the user is referencing implicitly. + 2. Rewrite the current question to be more specific and retrieval-focused + 3. Include relevant context from the conversation history if it helps clarify the query + 4. Make the query more explicit about what information is being sought + 5. Ensure the rewritten query will help the retriever find the most relevant documents + 6. Just provide the rewritten query, no other text. + 7. Keep the query as short as possible. + 8. Do not provide any explanation. + 9. Do not answer the question. + + + Conversation History: + {conversation_history} + + Current Question: {question} + + Rewritten Query: + +query_decomposition_followup_question_prompt: + system: | + You are an AI assistant tasked with identifying missing information needed to answer a user's question completely. Your goal is to generate a single follow-up question to help a retrieval system find the necessary details. + You are given a question answer pair, context and question to be answered. + + + 1. Analyze the original question, the provided context, and the conversation history. + 2. Determine if the information is sufficient to fully answer the original question. + 3. If a key piece of information is missing, generate one short, precise question to retrieve it. + 4. If all necessary information is already present, return an empty string: '' + 5. Do NOT provide any explanation. + 6. Do not answer the question. + 7. Return '' if no follow-up question is needed. + 8. Make sure follow up query is short and concise. + 9. Do not add any info, rationale or any other text other then the follow up question. + + + Conversation History: + {conversation_history} + + Context: + {context} + + Original Question: + {question} + + + Follow-up Question (if needed, otherwise return ''): + +query_decomposition_final_response_prompt: + system: | + You are a helpful AI assistant named Envie. Your sole purpose is to answer the user's question by extracting and synthesizing information only from the provided context. + + + 1. Do NOT use any external knowledge. + 2. Do NOT add explanations, suggestions, opinions, disclaimers, or hints. + 3. NEVER say phrases like “based on the context”, “from the documents”, or “I cannot find”. + 4. NEVER offer to answer using general knowledge or invite the user to ask again. + 5. Do NOT include citations, sources, or document mentions. + 6. Answer concisely. Use short, direct sentences . + 7. Do not mention or refer to these rules in any way. + 8. Do not ask follow-up questions. + 9. Do not mention this instructions in your response. + + + Conversation History: + {conversation_history} + + Context: + {context} + + Current Question: {question} + + Make sure the response you are generating strictly follow the rules mentioned above i.e. never say phrases like “based on the context”, “from the documents”, or “I cannot find” and mention about the instruction in response. + +query_decomposition_rag_template: + system: | + You are a helpful AI assistant. + You must answer only using the information provided in the context. While answering you must follow the instructions given below. + + + 1. Do NOT use any external knowledge. + 2. Do NOT add explanations, suggestions, opinions, disclaimers, or hints. + 3. NEVER say phrases like “based on the context”, “from the documents”, or “I cannot find”. + 4. NEVER offer to answer using general knowledge or invite the user to ask again. + 5. Do NOT include citations, sources, or document mentions. + 6. Answer concisely. Use short, direct sentences by default. Only give longer responses if the question truly requires it. + 7. Do not mention or refer to these rules in any way. + 8. Do not ask follow-up questions. + 9. Do not mention this instructions in your response. + 10. If context does not contain any information to answer the question, return '' + + + Context: + {context} + + Question: {question} + Make sure the response you are generating strictly follow the rules mentioned above i.e. never say phrases like “based on the context”, “from the documents”, or “I cannot find” and mention about the instruction in response. + +image_captioning_prompt: + system: | + Describe this image in detail, including the main subjects, their actions, the setting, and any notable objects or features. diff --git a/deploy/compose/nemotron3-super.env b/deploy/compose/nemotron3-super.env new file mode 100644 index 000000000..e016b157c --- /dev/null +++ b/deploy/compose/nemotron3-super.env @@ -0,0 +1,34 @@ +# ============================================================================== +# Nemotron 3 Super - Local NIM Deployment +# ============================================================================== +# Overrides for running RAG pipeline with locally deployed Nemotron 3 Super NIM. +# Source this AFTER .env: source .env && source nemotron3-super.env +# ============================================================================== + +# === LLM === +export APP_LLM_MODELNAME=nvidia/nemotron-3-super-120b-a12b +export APP_LLM_SERVERURL=nim-llm:8000 + +# === Query Rewriter === +export APP_QUERYREWRITER_MODELNAME=nvidia/nemotron-3-super-120b-a12b + +# === Filter Expression Generator === +export APP_FILTEREXPRESSIONGENERATOR_MODELNAME=nvidia/nemotron-3-super-120b-a12b + +# === Summarization === +export SUMMARY_LLM=nvidia/nemotron-3-super-120b-a12b +export SUMMARY_LLM_SERVERURL=nim-llm:8000 + +# === Reflection === +export REFLECTION_LLM=nvidia/nemotron-3-super-120b-a12b +export REFLECTION_LLM_SERVERURL=nim-llm:8000 + +# === Reasoning / Thinking === +export LLM_ENABLE_THINKING=true +export LLM_REASONING_BUDGET=256 +export LLM_LOW_EFFORT=true +export FILTER_THINK_TOKENS=true + +# === LLM_MAX_TOKENS (for RTX 6000 Pro when using NIM_MAX_MODEL_LEN=32768) === +# Uncomment and set: 16256 +# export LLM_MAX_TOKENS=16256 diff --git a/deploy/helm/nvidia-blueprint-rag/nemotron3-super-rtx6000-values.yaml b/deploy/helm/nvidia-blueprint-rag/nemotron3-super-rtx6000-values.yaml new file mode 100644 index 000000000..d042a6c44 --- /dev/null +++ b/deploy/helm/nvidia-blueprint-rag/nemotron3-super-rtx6000-values.yaml @@ -0,0 +1,25 @@ +# Override values for Nemotron 3 Super on RTX 6000 Pro only. +# Use after nemotron3-super-values.yaml: +# -f deploy/helm/nvidia-blueprint-rag/values.yaml \ +# -f deploy/helm/nvidia-blueprint-rag/nemotron3-super-values.yaml \ +# -f deploy/helm/nvidia-blueprint-rag/nemotron3-super-rtx6000-values.yaml +# See docs/nemotron3-super-deployment.md. Requires host GRUB/reboot for RTX 6000 Pro. + +envVars: + LLM_MAX_TOKENS: "16256" # use "1024" for non-reasoning mode + +nimOperator: + nim-llm: + env: + - name: NIM_HTTP_API_PORT + value: "8000" + - name: NIM_TRITON_LOG_VERBOSE + value: "1" + - name: NIM_SERVED_MODEL_NAME + value: "nvidia/nemotron-3-super-120b-a12b" + - name: NIM_MAX_MODEL_LEN + value: "32768" + - name: NCCL_P2P_DISABLE + value: "1" + - name: NIM_KVCACHE_PERCENT + value: "0.9" \ No newline at end of file diff --git a/deploy/helm/nvidia-blueprint-rag/nemotron3-super-values.yaml b/deploy/helm/nvidia-blueprint-rag/nemotron3-super-values.yaml new file mode 100644 index 000000000..710fff1fe --- /dev/null +++ b/deploy/helm/nvidia-blueprint-rag/nemotron3-super-values.yaml @@ -0,0 +1,39 @@ +# Override values for Nemotron 3 Super LLM NIM (all hardware). +# Use with: -f deploy/helm/nvidia-blueprint-rag/values.yaml -f deploy/helm/nvidia-blueprint-rag/nemotron3-super-values.yaml +# For RTX 6000 Pro, add: -f deploy/helm/nvidia-blueprint-rag/nemotron3-super-rtx6000-values.yaml +# See docs/nemotron3-super-deployment.md. + +envVars: + APP_LLM_MODELNAME: "nvidia/nemotron-3-super-120b-a12b" + APP_QUERYREWRITER_MODELNAME: "nvidia/nemotron-3-super-120b-a12b" + APP_FILTEREXPRESSIONGENERATOR_MODELNAME: "nvidia/nemotron-3-super-120b-a12b" + REFLECTION_LLM: "nvidia/nemotron-3-super-120b-a12b" + +ingestor-server: + envVars: + SUMMARY_LLM: "nvidia/nemotron-3-super-120b-a12b" + +nimOperator: + nim-llm: + image: + repository: nvcr.io/nim/nvidia/nemotron-3-super-120b-a12b + pullPolicy: IfNotPresent + tag: "1.8.0" + resources: + limits: + nvidia.com/gpu: 2 + requests: + nvidia.com/gpu: 2 + model: + engine: vllm + precision: "fp8" + tensorParallelism: "2" + env: + - name: NIM_HTTP_API_PORT + value: "8000" + - name: NIM_TRITON_LOG_VERBOSE + value: "1" + - name: NIM_SERVED_MODEL_NAME + value: "nvidia/nemotron-3-super-120b-a12b" + - name: NIM_MAX_MODEL_LEN + value: "131072" diff --git a/deploy/helm/nvidia-blueprint-rag/values.yaml b/deploy/helm/nvidia-blueprint-rag/values.yaml index 1afe008d1..77d871aa3 100644 --- a/deploy/helm/nvidia-blueprint-rag/values.yaml +++ b/deploy/helm/nvidia-blueprint-rag/values.yaml @@ -160,7 +160,8 @@ envVars: # URL on which LLM model is hosted. If "", Nvidia hosted API is used APP_LLM_SERVERURL: "nim-llm:8000" # LLM model parameters - LLM_MAX_TOKENS: "32768" + # For Nemotron 3 Super on RTX 6000 Pro: uncomment and set to 16256 (reasoning) or 1024 (non-reasoning); comment LLM_MAX_TOKENS above + LLM_MAX_TOKENS: "32768" # "16256" LLM_TEMPERATURE: "0" LLM_TOP_P: "1.0" @@ -658,11 +659,22 @@ nimOperator: repository: nvcr.io/nim/nvidia/llama-3.3-nemotron-super-49b-v1.5 pullPolicy: IfNotPresent tag: "1.14.0" +# -- For Nemotron 3 Super: uncomment the block below and comment the image block above +# image: +# repository: nvcr.io/nim/nvidia/nemotron-3-super-120b-a12b +# pullPolicy: IfNotPresent +# tag: "1.8.0" resources: limits: nvidia.com/gpu: 1 requests: nvidia.com/gpu: 1 +# -- For Nemotron 3 Super (all hardware): uncomment the block below and comment the resources block above +# resources: +# limits: +# nvidia.com/gpu: 2 +# requests: +# nvidia.com/gpu: 2 nodeSelector: {} tolerations: [] model: @@ -673,6 +685,10 @@ nimOperator: # tensorParallelism: "1" # gpus: # - product: "rtx6000_blackwell_sv" +# -- For Nemotron 3 Super (all hardware): comment "engine: tensorrt_llm" above and uncomment the three lines below +# engine: vllm +# precision: "fp8" +# tensorParallelism: "2" storage: pvc: create: true @@ -705,6 +721,13 @@ nimOperator: value: "nvidia/llama-3.3-nemotron-super-49b-v1.5" - name: NIM_MAX_MODEL_LEN value: "131072" +# -- For Nemotron 3 Super on RTX 6000 Pro: comment the NIM_MAX_MODEL_LEN entry above and uncomment the block below +# - name: NIM_MAX_MODEL_LEN +# value: "32768" +# - name: NCCL_P2P_DISABLE +# value: "1" +# - name: NIM_KVCACHE_PERCENT +# value: "0.9" # - name: CUDA_VISIBLE_DEVICES # value: "0" expose: diff --git a/docs/change-model.md b/docs/change-model.md index 96308764f..871d8f5f3 100644 --- a/docs/change-model.md +++ b/docs/change-model.md @@ -46,6 +46,10 @@ The `nemotron-3-nano-30b` model has different naming conventions depending on th Both names refer to the same underlying model. Use the appropriate name based on your deployment type. +##### Nemotron 3 Super + +Nemotron 3 Super is a larger model with different GPU and environment requirements: local NIM deployment requires at least 2 GPUs (FP8 TP2), and you may need a dedicated prompt config and reasoning settings. For full deployment steps (Docker and Helm), see the [Nemotron 3 Super deployment guide](nemotron3-super-deployment.md). + ### Change the Embedding Model @@ -304,4 +308,5 @@ Use this procedure to change models when you are running self-hosted NVIDIA NIM - [Deploy with Docker (Self-Hosted Models)](deploy-docker-self-hosted.md) - [Deploy with Docker (NVIDIA-Hosted Models)](deploy-docker-nvidia-hosted.md) - [Deploy with Helm](deploy-helm.md) +- [Nemotron 3 Super deployment (Docker and Helm)](nemotron3-super-deployment.md) - [Service-Specific API Keys](api-key.md#service-specific-api-keys) diff --git a/docs/deploy-helm.md b/docs/deploy-helm.md index c0b8743e8..b8982b3de 100644 --- a/docs/deploy-helm.md +++ b/docs/deploy-helm.md @@ -125,6 +125,8 @@ To deploy End-to-End RAG Server and Ingestor Server, use the following procedure Refer to [NIM Model Profile Configuration](model-profiles.md) for using non-default NIM LLM profile. ::: + For **Nemotron 3 Super** on Helm, see the [Nemotron 3 Super deployment guide](nemotron3-super-deployment.md#helm-deployment-nemotron-3-super). + ## Verify a Deployment diff --git a/docs/enable-nemotron-thinking.md b/docs/enable-nemotron-thinking.md index d9f9b5383..dc95b4285 100644 --- a/docs/enable-nemotron-thinking.md +++ b/docs/enable-nemotron-thinking.md @@ -41,6 +41,10 @@ Set the following environment variables on the RAG server container (via Docker **`FILTER_THINK_TOKENS`** : Filter content between `` and `` tags in model responses. Keep `true` for production to return only the final answer. Set `false` to see the full reasoning process. Default: `true`. +:::{important} +**Disabling reasoning:** To disable reasoning, set **`LLM_ENABLE_THINKING=false`**. Setting `LLM_REASONING_BUDGET=0` alone does not disable reasoning: when the budget is `0`, the RAG pipeline does not pass it to the LLM, and the model uses its default reasoning behavior. Always set `LLM_ENABLE_THINKING=false` to turn reasoning off. +::: + ## Enable Reasoning for Nemotron 3 Models Nemotron 3 models (such as `nvidia/nemotron-3-super-120b-a12b` and `nvidia/nemotron-3-nano-30b-a3b`) use environment variables to control reasoning. diff --git a/docs/nemotron3-super-deployment.md b/docs/nemotron3-super-deployment.md new file mode 100644 index 000000000..bf627d42b --- /dev/null +++ b/docs/nemotron3-super-deployment.md @@ -0,0 +1,181 @@ +# Using Nemotron-3-Super-120B-A12B LLM NIM + +[Nemotron-3-Super-120B-A12B](https://build.nvidia.com/nvidia/nemotron-3-super-120b-a12b/modelcard) is a large language model (LLM) trained by NVIDIA, designed to deliver strong agentic, reasoning, and conversational capabilities. It is optimized for collaborative agents and high-volume workloads such as IT ticket automation. This LLM can considerably improve the accuracy of the RAG pipeline, especially with reasoning enabled. ([Model card](https://build.nvidia.com/nvidia/nemotron-3-super-120b-a12b/modelcard)) + +We recommend to use the model with low-effort reasoning mode with a reasoning budget of 256 to have a balance between accuracy and performance. You can switch to non-reasoning mode for maximum performance or use reasoning mode for best accuracy. + +## Hardware requirements + +For Docker and Kubernetes deployment, see the following: + +- **Docker (local NIM):** [Hardware Requirements (Docker)](support-matrix.md#hardware-requirements-docker) +- **Kubernetes (Helm):** [Hardware Requirements (Kubernetes)](support-matrix.md#hardware-requirements-kubernetes) + +For [self-hosted local NIM](deploy-docker-self-hosted.md) deployment with `nemotron-3-super-120b-a12b`, you need one of the following: + +- 3 x H100 +- 3 x B200 +- 3 x RTX PRO 6000 + +### Hardware Requirements (Kubernetes) + +To deploy with [Helm](deploy-helm.md) using `nemotron-3-super-120b-a12b`, you need one of the following: + +- 9 x H100-80GB +- 9 x B200 +- 9 x RTX PRO 6000 + +--- + +## Start services using NVIDIA-hosted models + +No local GPU needed for the LLM. The file `deploy/compose/nemotron3-super-cloud.env` sets all NVIDIA-hosted (cloud) endpoints and the `nemotron-3-super-120b-a12b` model. + +1. [Set your API key](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/docs/api-key.md) and prompt config, then source the env files: + +```bash +export NGC_API_KEY= +source deploy/compose/.env +source deploy/compose/nemotron3-super-cloud.env +export PROMPT_CONFIG_FILE=$(pwd)/deploy/compose/nemotron3-super-prompt.yaml +``` + +2. Follow [Start services using NVIDIA-hosted models](deploy-docker-nvidia-hosted.md#start-services-using-nvidia-hosted-models) to start the vectorstore, rag-server, and ingestor-server. + +--- + +## Start services using self-hosted on-premises models + +1. Update `nims.yaml` + + Edit `deploy/compose/nims.yaml` and change the `nim-llm` service image and GPU allocation: + + ```yaml + nim-llm: + image: nvcr.io/nim/nvidia/nemotron-3-super-120b-a12b:1.8.0 + ... + user: "0" + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['1','2'] # 2 GPUs for FP8 TP2 + capabilities: [gpu] + ``` + + To confirm that a TP2 profile is available for your hardware, run: + + ```bash + docker run -ti --rm --gpus all nvcr.io/nim/nvidia/nemotron-3-super-120b-a12b:1.8.0 list-model-profiles + ``` + + Check the [model page](https://build.nvidia.com/nvidia/nemotron-3-super-120b-a12b/modelcard) for more details. + + > Note: For RTX 6000 Pro GPUs, additional NIM environment variables are required — see [RTX 6000 Pro](#rtx-6000-pro) below. + +2. Set nemotron-3-super specific environment variables. + + Ensure the section **`Endpoints for using cloud NIMs`** in `deploy/compose/.env` is **commented** (so on-prem endpoints are used). + + ```bash + source deploy/compose/.env + source deploy/compose/nemotron3-super.env + export PROMPT_CONFIG_FILE=$(pwd)/deploy/compose/nemotron3-super-prompt.yaml + ``` + + Follow [Start services using self-hosted on-premises models](deploy-docker-self-hosted.md#start-services-using-self-hosted-on-premises-models) to start the vectorstore, rag-server, NIMs, and ingestor-server. + +**RTX 6000 Pro** + +> Note: To deploy TP2 profiles on RTX PRO 6000 Blackwell Server Edition, run the following commands. You don't need to go through these steps if you are using TP4 or TP8 profile. + +1. Edit `/etc/default/grub` and set: + + ```text + GRUB_CMDLINE_LINUX_DEFAULT="quiet splash iommu=pt" + ``` + +2. Run: + + ```bash + sudo update-grub2 + sudo reboot + ``` + +3. In `nims.yaml`, add under the `nim-llm` `environment:` block: + + ```yaml + environment: + NGC_API_KEY: ${NGC_API_KEY} + NCCL_P2P_DISABLE: "1" + NIM_MAX_MODEL_LEN: "32768" + NIM_KVCACHE_PERCENT: "0.9" + ``` + +4. Export before starting the rag-server: + + ```bash + export LLM_MAX_TOKENS=16256 # for reasoning; use 1024 for non-reasoning + ``` + +--- + +## Helm deployment (`nemotron-3-super-120b-a12b`) + +From the repository root, run: + +```bash +helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.5.0.tgz \ + --username '$oauthtoken' \ + --password "${NGC_API_KEY}" \ + --set imagePullSecret.password=$NGC_API_KEY \ + --set ngcApiSecret.password=$NGC_API_KEY \ + -f deploy/helm/nvidia-blueprint-rag/values.yaml \ + -f deploy/helm/nvidia-blueprint-rag/nemotron3-super-values.yaml +``` + +The prompt file `deploy/compose/nemotron3-super-prompt.yaml` is tuned for `nemotron-3-super-120b-a12b`. To customize it, see [Prompt customization in Helm chart](prompt-customization.md#prompt-customization-in-helm-chart). + +**RTX 6000 Pro** + +> Note: To deploy TP2 profiles on RTX PRO 6000 Blackwell Server Edition, run the following commands. You don't need to go through these steps if you are using TP4 or TP8 profile. + +1. Edit `/etc/default/grub` and set: + + ```text + GRUB_CMDLINE_LINUX_DEFAULT="quiet splash iommu=pt" + ``` + +2. Run: + + ```bash + sudo update-grub2 + sudo reboot + ``` + +3. From the repository root, run: + + ```bash + helm upgrade --install rag -n rag https://helm.ngc.nvidia.com/nvstaging/blueprint/charts/nvidia-blueprint-rag-v2.5.0.tgz \ + --username '$oauthtoken' \ + --password "${NGC_API_KEY}" \ + --set imagePullSecret.password=$NGC_API_KEY \ + --set ngcApiSecret.password=$NGC_API_KEY \ + -f deploy/helm/nvidia-blueprint-rag/values.yaml \ + -f deploy/helm/nvidia-blueprint-rag/nemotron3-super-values.yaml \ + -f deploy/helm/nvidia-blueprint-rag/nemotron3-super-rtx6000-values.yaml + ``` + +--- + +## Reasoning and non-reasoning mode + +To disable reasoning mode set following + +```bash +export LLM_ENABLE_THINKING=false +export LLM_REASONING_BUDGET=0 +``` + +For other options (e.g. full reasoning budget), see [Enable reasoning for Nemotron 3 models](enable-nemotron-thinking.md). From 1f40729c5c523705932a6cb2a5ff7b4a16d624c3 Mon Sep 17 00:00:00 2001 From: nv-pranjald <150428320+nv-pranjald@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:08:41 +0530 Subject: [PATCH 40/52] docs: move NIM_MAX_MODEL_LEN and LLM_MAX_TOKENS to general self-hosted deployment steps (#432) --- docs/nemotron3-super-deployment.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/nemotron3-super-deployment.md b/docs/nemotron3-super-deployment.md index bf627d42b..aa3f7e336 100644 --- a/docs/nemotron3-super-deployment.md +++ b/docs/nemotron3-super-deployment.md @@ -55,6 +55,10 @@ export PROMPT_CONFIG_FILE=$(pwd)/deploy/compose/nemotron3-super-prompt.yaml image: nvcr.io/nim/nvidia/nemotron-3-super-120b-a12b:1.8.0 ... user: "0" + environment: + NGC_API_KEY: ${NGC_API_KEY} + NIM_MAX_MODEL_LEN: "32768" # required for TP2 profile + NIM_KVCACHE_PERCENT: "0.9" deploy: resources: reservations: @@ -64,6 +68,8 @@ export PROMPT_CONFIG_FILE=$(pwd)/deploy/compose/nemotron3-super-prompt.yaml capabilities: [gpu] ``` + > Note: To deploy TP2 profiles you need to limit NIM_MAX_MODEL_LEN to 32768 + To confirm that a TP2 profile is available for your hardware, run: ```bash @@ -82,6 +88,7 @@ export PROMPT_CONFIG_FILE=$(pwd)/deploy/compose/nemotron3-super-prompt.yaml source deploy/compose/.env source deploy/compose/nemotron3-super.env export PROMPT_CONFIG_FILE=$(pwd)/deploy/compose/nemotron3-super-prompt.yaml + export LLM_MAX_TOKENS=16256 ``` Follow [Start services using self-hosted on-premises models](deploy-docker-self-hosted.md#start-services-using-self-hosted-on-premises-models) to start the vectorstore, rag-server, NIMs, and ingestor-server. @@ -107,16 +114,8 @@ export PROMPT_CONFIG_FILE=$(pwd)/deploy/compose/nemotron3-super-prompt.yaml ```yaml environment: - NGC_API_KEY: ${NGC_API_KEY} + # In addition to variable already set in step 1 NCCL_P2P_DISABLE: "1" - NIM_MAX_MODEL_LEN: "32768" - NIM_KVCACHE_PERCENT: "0.9" - ``` - -4. Export before starting the rag-server: - - ```bash - export LLM_MAX_TOKENS=16256 # for reasoning; use 1024 for non-reasoning ``` --- From c1ad53236e3b1b70ec94d99c3f75e5cf223e3c75 Mon Sep 17 00:00:00 2001 From: anngu-2xx3 Date: Fri, 13 Mar 2026 21:01:12 +0700 Subject: [PATCH 41/52] Update: Remove vss and update Minio access console (#433) --- docs/continuous-ingestion-object-storage.md | 42 ++++++++++++--------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/docs/continuous-ingestion-object-storage.md b/docs/continuous-ingestion-object-storage.md index a718224d9..4baa9daf7 100644 --- a/docs/continuous-ingestion-object-storage.md +++ b/docs/continuous-ingestion-object-storage.md @@ -4,13 +4,24 @@ --> # Continuous Ingestion from Object Storage RAG Blueprint -Continuous ingestion from object storage connects the [RAG blueprint](readme.md) to continuous integration. This enables an event-driven pipeline that automatically indexes documents and, optionally, video files. Continuous integration means that when you add files to a storage bucket, the system detects new uploads, routes them for processing, and indexes their content—making all data immediately searchable and available for analysis through the [RAG Frontend](user-interface.md). +Continuous ingestion from object storage connects the [RAG blueprint](readme.md) to continuous integration. This enables an event-driven pipeline that automatically indexes documents. Continuous integration means that when you add documents to a storage bucket, the system detects new uploads, routes them for processing, and indexes their content—making all data immediately searchable and available for analysis through the [RAG Frontend](user-interface.md). + +## Hardware Requirements + +| Requirement | Details | +|-------------|---------| +| **GPU** | 2x RTX PRO 6000 Blackwell or 2x H100 | +| **OS** | Ubuntu 22.04 or later | +| **Docker** | Docker 24.0+ with Docker Compose v2 | +| **NVIDIA Driver** | 570+ | +| **NVIDIA Container Toolkit** | Required | + ## Overview You can create an event-driven continuous ingestion pipeline that works as follows: -1. Upload files (documents or, optionally, videos) to object storage. +1. Upload documents to object storage. 2. The system detects new uploads via storage events and routes them for processing. @@ -18,10 +29,7 @@ You can create an event-driven continuous ingestion pipeline that works as follo 4. You can then query the ingested content through the RAG UI or API. -Continuous ingestion supports the following content types: - -- **Documents:** PDF, DOCX (and other formats supported by the [ingestor](api-ingestor.md)). -- **Video (optional/future):** MP4, MKV, AVI — typically processed by a Video Summary Service (VSS) and then ingested as documents. +Continuous ingestion supports documents such as PDF, DOCX, and other formats supported by the [ingestor](api-ingestor.md). ## Architecture @@ -35,15 +43,10 @@ The continuous ingestion architecture features the following high-level flow: 4. Document path: Files are passed to a file-based processing pipeline (such as the NeMo Retriever Library or ingestor-server) and then indexed in the vector database. -5. Video path (optional): For video files, the consumer submits a processing request to the Video Search Service (VSS), polls for results, uploads the processed outputs to RAG as documents, and continues through the standard file-based ingestion flow. - - The continuous ingestion architecture follows the end-to-end sequence described above and can be summarized as: - Document ingestion flow: (1) → (2) → (3) → file-based processing → VectorDB → RAG Agent. -- Video ingestion flow: (1) → (2) → (3) → VSS request → poll for results → upload to RAG as a document → file-based processing → VectorDB → RAG Agent. - ## Implementation Components The reference implementation includes the following components: @@ -56,11 +59,9 @@ The reference implementation includes the following components: -- Subscribes to the Kafka topic and consumes storage events. --- Downloads new objects from MinIO and routes them based on type (document or video). +-- Downloads new objects from MinIO. --- For documents: Sends files to the RAG ingestor for indexing. - --- For videos (optional): Submits files to the Video Search Service (VSS), then ingests VSS outputs as documents. +-- Sends files to the RAG ingestor for indexing. The deployment is defined in `examples/rag_event_ingest/deploy/docker-compose.yaml`, which runs MinIO, Kafka, and the Kafka consumer on the same Docker network as the RAG stack (`nvidia-rag`). @@ -76,9 +77,8 @@ The notebook provides a guided walkthrough of the following steps: - Environment setup - NVIDIA RAG deployment -- (Optional) NVIDIA VSS deployment for video processing - Continuous ingestion pipeline deployment (Kafka, MinIO, and consumer) -- Testing document and optional video uploads with RAG queries +- Testing document uploads with RAG queries - Cleanup To follow along, open and run: [rag_event_ingest.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/rag_event_ingest.ipynb). @@ -97,7 +97,13 @@ This command launches the following components: - MinIO (object storage and console using ports 9201 and 9211 in the example) - Kafka consumer — connects to the ingestor at `INGESTOR_SERVER_URL` (default: `http://ingestor-server:8082`) and uses `COLLECTION_NAME` (default: `aidp_bucket`) -After deployment, upload files to the MinIO bucket (for example, `aidp-bucket`). The system publishes upload events to Kafka, the consumer retrieves the corresponding files, and documents are sent to the ingestor for indexing. You can then query the same collection using the RAG UI or API. +After deployment, upload documents and query ingested content as follows: + +1. Open the MinIO Console UI at `http://:9211/login`. +2. Log in with the default credentials (`minioadmin` / `minioadmin`). +3. Navigate to the `aidp-bucket` bucket and upload your documents (PDF, DOCX, etc.). +4. The system automatically publishes upload events to Kafka, the consumer retrieves the files, and documents are sent to the ingestor for indexing into the `aidp_bucket` collection. +5. Query the ingested content through the RAG Frontend UI at `http://:8090` (select the `aidp_bucket` collection) or via the RAG API at `http://:8081/generate`. ### Key Environment Variables From cdc9217474f5052f963bceb3d723741d1435b4af Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Tue, 17 Mar 2026 12:32:03 +0530 Subject: [PATCH 42/52] VLM embed doc fix (#435) Signed-off-by: smasurekar --- docs/vlm-embed.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/vlm-embed.md b/docs/vlm-embed.md index 6cd5b0304..0bec55afe 100644 --- a/docs/vlm-embed.md +++ b/docs/vlm-embed.md @@ -153,8 +153,8 @@ To deploy the VLM embedding service with Helm, update the image and model settin nvidia-nim-llama-nemotron-embed-vl-1b-v2: enabled: true image: - repository: nvcr.io/nvidia/nemo-microservices/llama-3.2-nemoretriever-1b-vlm-embed-v1 - tag: "1.7.0" + repository: nvcr.io/nim/nvidia/llama-nemotron-embed-vl-1b-v2 + tag: "1.12.0" # Optional: disable the default text embedding NIM nvidia-nim-llama-32-nv-embedqa-1b-v2: From a67a48c794e449ffd6887b380bb405fc8748c22a Mon Sep 17 00:00:00 2001 From: nv-pranjald <150428320+nv-pranjald@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:32:48 +0530 Subject: [PATCH 43/52] docs: add RAG accuracy benchmarks documentation (#434) * docs: add RAG accuracy benchmarks documentation * docs: Fix broken links and format in accuracy benchmark doc * Update accuracy-benchmarks.md * Update accuracy-benchmarks.md * Update accuracy-benchmarks.md * Update accuracy-benchmarks.md * Update accuracy-benchmarks.md * Update accuracy-benchmarks.md implmented changes as instructed by Sumit in Slack thread: https://nvidia.slack.com/archives/C09HAQRT1UY/p1773470561423909 --------- Co-authored-by: Kurt Heiss --- docs/accuracy-benchmarks.md | 123 ++++++++++++++++++++++++++++++++++++ docs/evaluate.md | 1 + docs/index.md | 2 + 3 files changed, 126 insertions(+) create mode 100644 docs/accuracy-benchmarks.md diff --git a/docs/accuracy-benchmarks.md b/docs/accuracy-benchmarks.md new file mode 100644 index 000000000..bffa6cc33 --- /dev/null +++ b/docs/accuracy-benchmarks.md @@ -0,0 +1,123 @@ + + # Benchmarking RAG Accuracy: Evaluating LLM Reasoning and VLM Integration In the fast-moving world of Retrieval-Augmented Generation (RAG), the gap between a “good” system and one that’s truly production-ready often depends on how effectively the pipeline manages complex reasoning and multimodal data. To measure these advancements, our team conducted extensive benchmarks across multiple configurations, examining the influence of LLM reasoning (“Think” mode) and Vision-Language Models (VLM). @@ -35,7 +38,7 @@ Our analysis centered on seven major public datasets encompassing a broad range Our primary evaluation metric is end-to-end RAG answer accuracy, measured using the [NVIDIA Answer Accuracy metric from RAGAS](https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/nvidia_metrics/). Each response is rated on a 0–4 scale by an LLM judge, with scores normalized to a range for reporting. We chose [mistralai/Mixtral-8x22B-Instruct-v0.1](https://build.nvidia.com/mistralai/mixtral-8x22b-instruct) as the LLM judge, guided by performance on the [Judge’s Verdict](https://huggingface.co/spaces/nvidia/judges-verdict) benchmark. -> Full evaluation pipeline: [evaluation_01_ragas.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/evaluation_01_ragas.ipynb) +Full evaluation pipeline: [evaluation_01_ragas.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/evaluation_01_ragas.ipynb) - Metric: Accuracy, defined as the degree to which generated responses align with the ground truth answers. - Pipeline configuration: All experiments were run using the default configuration. @@ -120,4 +123,4 @@ Google Frames targets complex queries that require synthesizing facts across mul - [Enable Reasoning in Nemotron LLM Models](enable-nemotron-thinking.md) - [VLM-Based Inferencing in RAG](vlm.md) - [Image Captioning Support](image_captioning.md) -- [Best Practices for Common Settings](accuracy_perf.md) +- [Best Practices for Common Settings](accuracy_perf.md) \ No newline at end of file diff --git a/docs/evaluate.md b/docs/evaluate.md index 33d9ce758..2485682dc 100644 --- a/docs/evaluate.md +++ b/docs/evaluate.md @@ -7,6 +7,8 @@ After you [deploy your NVIDIA RAG Blueprint system](readme.md#deployment-options-for-rag-blueprint), you can evaluate it by using [Ragas](https://docs.ragas.io/en/stable/) metrics specifically designed for Large Language Model (LLM) Applications. +For published benchmark results across multiple datasets and configurations, refer to [RAG Accuracy Benchmarks](accuracy-benchmarks.md). + ## Ragas Metrics diff --git a/docs/index.md b/docs/index.md index 956e9f809..d588b5034 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,6 @@ # NVIDIA RAG Blueprint Documentation @@ -179,6 +180,7 @@ After you deploy the RAG blueprint, you can customize it for your use cases. :maxdepth: 1 :hidden: + Deploy with Docker (Self-Hosted Models) Deploy with Docker (NVIDIA-Hosted Models) Deploy on Kubernetes with Helm Deploy on Kubernetes with Helm from the repository diff --git a/docs/readme.md b/docs/readme.md index 0248a4a00..acc20cd5e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -113,6 +113,7 @@ After you deploy the RAG blueprint, you can customize it for your use cases. - Evaluation - [Evaluate Your NVIDIA RAG Blueprint System](evaluate.md) + - [RAG Accuracy Benchmarks](accuracy-benchmarks.md) - Governance From 7e5ef787e8c748f658ee7d038f0278a20d463b6d Mon Sep 17 00:00:00 2001 From: Swapnil Masurekar Date: Wed, 18 Mar 2026 12:55:08 +0530 Subject: [PATCH 46/52] fix(unit): avoid real Milvus in delete_documents tests for CI (#440) Signed-off-by: Swapnil Masurekar --- .../test_utils/test_vdb/test_milvus_vdb.py | 80 +++++++------------ 1 file changed, 30 insertions(+), 50 deletions(-) diff --git a/tests/unit/test_utils/test_vdb/test_milvus_vdb.py b/tests/unit/test_utils/test_vdb/test_milvus_vdb.py index 973088df8..9fda5c695 100644 --- a/tests/unit/test_utils/test_vdb/test_milvus_vdb.py +++ b/tests/unit/test_utils/test_vdb/test_milvus_vdb.py @@ -33,6 +33,19 @@ from nvidia_rag.utils.vdb.milvus.milvus_vdb import MilvusVDB +def _make_dummy_milvus_vdb_for_delete(): + """Build a MilvusVDB instance without running __init__ (no real connections). + + Only sets attributes needed by delete_documents so we can test that method + without touching Milvus. Safe for CI where no Milvus is running. + """ + vdb = object.__new__(MilvusVDB) + vdb.connection_alias = "milvus_dummy_test" + vdb.vdb_endpoint = "http://localhost:19530" + vdb._delete_entities = Mock() + return vdb + + class TestMilvusVDB: """Test the MilvusVDB class.""" @@ -749,81 +762,48 @@ def test_get_documents(self, mock_connections): ) @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.Collection") - @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.connections") - def test_delete_documents_success(self, mock_connections, mock_collection): - """Test delete_documents method with successful deletion.""" + def test_delete_documents_success(self, mock_collection): + """Test delete_documents method with successful deletion (no real Milvus).""" mock_collection_obj = Mock() mock_resp = Mock() mock_resp.delete_count = 5 mock_collection_obj.delete.return_value = mock_resp mock_collection.return_value = mock_collection_obj - with ( - patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.urlparse"), - ): - vdb = MilvusVDB( - embedding_model=Mock(), - milvus_uri="http://localhost:19530", - collection_name="test_collection", - config=Mock(), - ) + vdb = _make_dummy_milvus_vdb_for_delete() + result = vdb.delete_documents("test_collection", ["file1.txt", "file2.txt"]) - result = vdb.delete_documents("test_collection", ["file1.txt", "file2.txt"]) - - assert result is True - mock_collection_obj.flush.assert_called_once() + assert result is True + mock_collection_obj.flush.assert_called_once() @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.Collection") - @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.connections") - def test_delete_documents_not_found(self, mock_connections, mock_collection): - """Test delete_documents method when document not found.""" + def test_delete_documents_not_found(self, mock_collection): + """Test delete_documents method when document not found (no real Milvus).""" mock_collection_obj = Mock() mock_resp = Mock() mock_resp.delete_count = 0 mock_collection_obj.delete.return_value = mock_resp mock_collection.return_value = mock_collection_obj - with ( - patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.urlparse"), - ): - vdb = MilvusVDB( - embedding_model=Mock(), - milvus_uri="http://localhost:19530", - collection_name="test_collection", - config=Mock(), - ) - - result = vdb.delete_documents("test_collection", ["file1.txt"]) + vdb = _make_dummy_milvus_vdb_for_delete() + result = vdb.delete_documents("test_collection", ["file1.txt"]) - assert result is True + assert result is True @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.Collection") - @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.connections") - def test_delete_documents_milvus_exception(self, mock_connections, mock_collection): - """Test delete_documents method with MilvusException fallback.""" - + def test_delete_documents_milvus_exception(self, mock_collection): + """Test delete_documents method with MilvusException fallback (no real Milvus).""" mock_collection_obj = Mock() mock_resp = Mock() mock_resp.delete_count = 1 - - # First call raises MilvusException, second call succeeds mock_collection_obj.delete.side_effect = [MilvusException("Error"), mock_resp] mock_collection.return_value = mock_collection_obj - with ( - patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.urlparse"), - ): - vdb = MilvusVDB( - embedding_model=Mock(), - milvus_uri="http://localhost:19530", - collection_name="test_collection", - config=Mock(), - ) - - result = vdb.delete_documents("test_collection", ["file1.txt"]) + vdb = _make_dummy_milvus_vdb_for_delete() + result = vdb.delete_documents("test_collection", ["file1.txt"]) - assert result is True - assert mock_collection_obj.delete.call_count == 2 + assert result is True + assert mock_collection_obj.delete.call_count == 2 @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.MilvusClient") @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.connections") From e114a68b7c3b3a3ba1d135e5f010c14002f8ce50 Mon Sep 17 00:00:00 2001 From: Shubhadeep Das Date: Tue, 17 Mar 2026 19:39:09 +0530 Subject: [PATCH 47/52] Update release date for v2.5.0 --- docs/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index f257d95b8..6dd8e6911 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,7 +8,7 @@ This documentation contains the release notes for [NVIDIA RAG Blueprint](readme. -## Release 2.5.0 (2026-03-12) +## Release 2.5.0 (2026-03-17) This release introduces support for the Nemotron-super-3 model, updates NIMs to the latest versions, upgrades NV-Ingest, and adds continuous ingestion along with RTX 6000 MIG support. From 2f26a0089a1c37197a3eea4df2dc41e3db1106e9 Mon Sep 17 00:00:00 2001 From: Sebastion Date: Tue, 7 Apr 2026 09:34:52 +0100 Subject: [PATCH 48/52] fix: validate file paths in MCP upload/update tools to prevent path traversal (CWE-22) The tool_upload_documents and tool_update_documents MCP tools accepted arbitrary file paths from MCP clients without validation. An attacker controlling the MCP client (or an LLM agent making tool calls) could supply paths like /etc/shadow, /proc/self/environ, or ../../sensitive.yaml to read arbitrary files from the server filesystem and exfiltrate them by uploading to the ingestor. Add _validate_file_path() helper that resolves paths via os.path.realpath() (following symlinks) and verifies they reside within the allowed upload directory (MCP_UPLOAD_DIR env var, defaults to cwd). Raises ValueError for paths outside the sandbox. Both tool_upload_documents and tool_update_documents now call this validator before reading any file. Signed-off-by: Sebastion --- examples/nvidia_rag_mcp/mcp_server.py | 35 +++ .../test_mcp/test_cwe22_path_traversal.py | 232 ++++++++++++++++++ tests/unit/test_mcp/test_mcp_server.py | 4 + 3 files changed, 271 insertions(+) create mode 100644 tests/unit/test_mcp/test_cwe22_path_traversal.py diff --git a/examples/nvidia_rag_mcp/mcp_server.py b/examples/nvidia_rag_mcp/mcp_server.py index e93744933..c2ac626a4 100644 --- a/examples/nvidia_rag_mcp/mcp_server.py +++ b/examples/nvidia_rag_mcp/mcp_server.py @@ -33,6 +33,9 @@ Environment variables: - VITE_API_CHAT_URL: Base URL for RAG HTTP API (default http://localhost:8081) - INGESTOR_URL: Base URL for Ingestor API (default http://127.0.0.1:8082) + - MCP_UPLOAD_DIR: Allowed base directory for file uploads (default: cwd). + File paths passed to upload/update tools are validated to be within this + directory, preventing path-traversal attacks. """ import argparse @@ -68,6 +71,36 @@ def _rag_base_url() -> str: return os.environ.get("VITE_API_CHAT_URL", "http://localhost:8081").rstrip("/") +def _upload_base_dir() -> str: + """ + Return the base directory that file upload paths must reside within. + Controlled by the ``MCP_UPLOAD_DIR`` environment variable; defaults to + the current working directory when unset. + """ + return os.environ.get("MCP_UPLOAD_DIR", os.getcwd()) + + +def _validate_file_path(path: str) -> str: + """ + Resolve *path* to an absolute, canonical path and verify it is located + within the allowed upload directory (``MCP_UPLOAD_DIR``). + + Returns the resolved path on success; raises ``ValueError`` otherwise. + + Security: uses ``os.path.realpath`` to follow symlinks so that + ``../../etc/passwd`` or symlink escapes are caught. + """ + base = os.path.realpath(_upload_base_dir()) + resolved = os.path.realpath(path) + # Ensure the resolved path starts with the base directory + if not resolved.startswith(base + os.sep) and resolved != base: + raise ValueError( + f"Path {path!r} (resolved to {resolved!r}) is not within the " + f"allowed upload directory {base!r}" + ) + return resolved + + @server.tool( "generate", description="""Generate an answer using NVIDIA RAG (optionally with knowledge base). @@ -503,6 +536,7 @@ async def tool_update_documents( form_data = aiohttp.FormData() for path in file_paths or []: + path = _validate_file_path(path) try: if os.path.exists(path): with open(path, "rb") as f: @@ -818,6 +852,7 @@ async def tool_upload_documents( form_data = aiohttp.FormData() # Add files for path in file_paths or []: + path = _validate_file_path(path) try: if os.path.exists(path): with open(path, "rb") as f: diff --git a/tests/unit/test_mcp/test_cwe22_path_traversal.py b/tests/unit/test_mcp/test_cwe22_path_traversal.py new file mode 100644 index 000000000..bb6031f12 --- /dev/null +++ b/tests/unit/test_mcp/test_cwe22_path_traversal.py @@ -0,0 +1,232 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# PoC / regression tests for CWE-22 path traversal in MCP server file upload tools. +# +# The tool_upload_documents and tool_update_documents functions accept arbitrary +# file_paths from MCP clients and read them without any path validation. +# An attacker can supply paths like "/etc/passwd" or "../../sensitive.txt" to +# read arbitrary files from the server's filesystem and exfiltrate them via +# the ingestor upload. + +from __future__ import annotations + +import os +import sys +from types import SimpleNamespace +from typing import Any + +import pytest + +import examples.nvidia_rag_mcp.mcp_server as mcp_server + +try: + from fastmcp.tools import FunctionTool +except Exception: + FunctionTool = None + + +def _tool_fn(tool: Any): + if FunctionTool is not None and isinstance(tool, FunctionTool): + inner = getattr(tool, "func", None) or getattr(tool, "__wrapped__", None) + if inner is not None: + return inner + return tool + + +def _make_fake_aiohttp(captured_files: list): + """Build a fake aiohttp that records which file contents are uploaded.""" + + class FakeResp: + def __init__(self): + self.status = 200 + + async def json(self): + return {"ok": True} + + async def text(self): + return "ok" + + class FakeFormData: + def __init__(self): + self.fields: list[tuple] = [] + + def add_field(self, name, value, filename=None, content_type=None): + self.fields.append((name, value, filename, content_type)) + if name == "documents": + captured_files.append({"filename": filename, "content": value}) + + class FakeSession: + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + return False + + def post(self, url, data=None): + class Ctx: + async def __aenter__(self_inner): + return FakeResp() + async def __aexit__(self_inner, exc_type, exc, tb): + return False + return Ctx() + + def patch(self, url, data=None): + class Ctx: + async def __aenter__(self_inner): + return FakeResp() + async def __aexit__(self_inner, exc_type, exc, tb): + return False + return Ctx() + + FakeClientTimeout = type("ClientTimeout", (), {"__init__": lambda self, total=None: None}) + return SimpleNamespace( + ClientSession=lambda timeout=None: FakeSession(), + ClientTimeout=FakeClientTimeout, + ContentTypeError=Exception, + FormData=FakeFormData, + ) + + +@pytest.mark.anyio +async def test_upload_rejects_absolute_path_outside_allowed_dir(monkeypatch, tmp_path): + """ + Path traversal PoC: tool_upload_documents must reject absolute paths + outside the allowed upload directory. + + An attacker-controlled MCP client could pass "/etc/passwd" to read + arbitrary files. After the fix, a ValueError should be raised. + """ + secret = tmp_path / "secret.txt" + secret.write_text("super-secret-data") + + allowed_dir = tmp_path / "uploads" + allowed_dir.mkdir() + monkeypatch.setenv("MCP_UPLOAD_DIR", str(allowed_dir)) + + captured_files: list = [] + fake = _make_fake_aiohttp(captured_files) + monkeypatch.setattr(mcp_server, "aiohttp", fake, raising=True) + + tool = _tool_fn(mcp_server.tool_upload_documents) + + with pytest.raises(ValueError, match="not within the allowed upload directory"): + await tool( + collection_name="test", + file_paths=[str(secret)], + ) + + assert len(captured_files) == 0, "Sensitive file was read despite being outside allowed dir" + + +@pytest.mark.anyio +async def test_update_rejects_absolute_path_outside_allowed_dir(monkeypatch, tmp_path): + """ + Same traversal via tool_update_documents (PATCH variant). + """ + secret = tmp_path / "secret.txt" + secret.write_text("super-secret-data") + + allowed_dir = tmp_path / "uploads" + allowed_dir.mkdir() + monkeypatch.setenv("MCP_UPLOAD_DIR", str(allowed_dir)) + + captured_files: list = [] + fake = _make_fake_aiohttp(captured_files) + monkeypatch.setattr(mcp_server, "aiohttp", fake, raising=True) + + tool = _tool_fn(mcp_server.tool_update_documents) + + with pytest.raises(ValueError, match="not within the allowed upload directory"): + await tool( + collection_name="test", + file_paths=[str(secret)], + ) + assert len(captured_files) == 0 + + +@pytest.mark.anyio +async def test_upload_rejects_dot_dot_traversal(monkeypatch, tmp_path): + """ + Relative path traversal via '../' must be rejected. + """ + allowed_dir = tmp_path / "uploads" + allowed_dir.mkdir() + + secret = tmp_path / "secret.txt" + secret.write_text("traversal-secret") + + monkeypatch.setenv("MCP_UPLOAD_DIR", str(allowed_dir)) + + captured_files: list = [] + fake = _make_fake_aiohttp(captured_files) + monkeypatch.setattr(mcp_server, "aiohttp", fake, raising=True) + + tool = _tool_fn(mcp_server.tool_upload_documents) + + traversal_path = str(allowed_dir / ".." / "secret.txt") + with pytest.raises(ValueError, match="not within the allowed upload directory"): + await tool( + collection_name="test", + file_paths=[traversal_path], + ) + assert len(captured_files) == 0 + + +@pytest.mark.anyio +async def test_upload_allows_file_inside_allowed_dir(monkeypatch, tmp_path): + """ + Files within the allowed upload directory should be accepted. + """ + allowed_dir = tmp_path / "uploads" + allowed_dir.mkdir() + + legit_file = allowed_dir / "doc.pdf" + legit_file.write_bytes(b"%PDF-1.4 legit content") + + monkeypatch.setenv("MCP_UPLOAD_DIR", str(allowed_dir)) + + captured_files: list = [] + fake = _make_fake_aiohttp(captured_files) + monkeypatch.setattr(mcp_server, "aiohttp", fake, raising=True) + + tool = _tool_fn(mcp_server.tool_upload_documents) + result = await tool( + collection_name="test", + file_paths=[str(legit_file)], + ) + + assert result.get("ok") is True + assert len(captured_files) == 1 + assert captured_files[0]["filename"] == "doc.pdf" + assert captured_files[0]["content"] == b"%PDF-1.4 legit content" + + +@pytest.mark.anyio +async def test_upload_rejects_symlink_escape(monkeypatch, tmp_path): + """ + Symlink escape: a symlink inside the allowed dir pointing outside must be rejected. + """ + allowed_dir = tmp_path / "uploads" + allowed_dir.mkdir() + + secret = tmp_path / "secret.txt" + secret.write_text("symlink-secret") + + link = allowed_dir / "evil_link.txt" + link.symlink_to(secret) + + monkeypatch.setenv("MCP_UPLOAD_DIR", str(allowed_dir)) + + captured_files: list = [] + fake = _make_fake_aiohttp(captured_files) + monkeypatch.setattr(mcp_server, "aiohttp", fake, raising=True) + + tool = _tool_fn(mcp_server.tool_upload_documents) + + with pytest.raises(ValueError, match="not within the allowed upload directory"): + await tool( + collection_name="test", + file_paths=[str(link)], + ) + assert len(captured_files) == 0 diff --git a/tests/unit/test_mcp/test_mcp_server.py b/tests/unit/test_mcp/test_mcp_server.py index 2473b3831..224d75762 100644 --- a/tests/unit/test_mcp/test_mcp_server.py +++ b/tests/unit/test_mcp/test_mcp_server.py @@ -289,6 +289,8 @@ async def test_tool_update_documents_uses_patch_and_form(monkeypatch, tmp_path): p2 = tmp_path / "b.pdf" p2.write_bytes(b"%PDF-1.4 b") + monkeypatch.setenv("MCP_UPLOAD_DIR", str(tmp_path)) + captured: dict[str, Any] = {} class FakeResp: @@ -618,6 +620,8 @@ async def test_tool_upload_documents(monkeypatch, tmp_path): p = tmp_path / "doc.pdf" p.write_bytes(b"%PDF-1.4...") + monkeypatch.setenv("MCP_UPLOAD_DIR", str(tmp_path)) + class FakeResp: def __init__(self): self.status = 200 From 2eb8f59d38d19bb2ff2a93c01d467f6658bd83ac Mon Sep 17 00:00:00 2001 From: Kurt Heiss Date: Tue, 7 Apr 2026 04:07:54 -0700 Subject: [PATCH 49/52] Kheiss/versioning modifiers (#462) * Updated Vidore Dataset to Vidore V3 Dataset (#443) * Kheiss/rm early access1 (#445) * remove early access from title * remove early access from title * Update documentation per broken link reporting for Brev and support matrix (#458) * updated versioning method for RAG documentation --- docs/accuracy-benchmarks.md | 2 +- docs/conf.py | 4 +- docs/documentation.md | 47 +++++- docs/notebooks.md | 2 +- docs/scripts/build_multiversion_docs.ps1 | 165 ++++++++++++++++++++ docs/scripts/build_multiversion_docs.sh | 111 +++++++++++++ docs/scripts/verify_doc_version_manifest.py | 146 +++++++++++++++++ docs/support-matrix.md | 3 +- docs/versions1.json | 4 + docs/vlm-embed.md | 4 +- 10 files changed, 479 insertions(+), 9 deletions(-) create mode 100644 docs/scripts/build_multiversion_docs.ps1 create mode 100644 docs/scripts/build_multiversion_docs.sh create mode 100644 docs/scripts/verify_doc_version_manifest.py diff --git a/docs/accuracy-benchmarks.md b/docs/accuracy-benchmarks.md index c046481f9..6ae3a4529 100644 --- a/docs/accuracy-benchmarks.md +++ b/docs/accuracy-benchmarks.md @@ -20,7 +20,7 @@ Our analysis centered on seven major public datasets encompassing a broad range | [HotPotQA](https://huggingface.co/datasets/hotpotqa/hotpot_qa) | Wikipedia-based question-answer pairs | English | Text | 2,673 (txt files) | 979 | | [Google Frames](https://huggingface.co/datasets/google/frames-benchmark) | History, Sports, Science, Animals, Health | English | Text | 31,708 | 824 | -### [Vidore Dataset](https://huggingface.co/blog/QuentinJG/introducing-vidore-v3#public-datasets) +### [Vidore-V3 Dataset](https://huggingface.co/blog/QuentinJG/introducing-vidore-v3#public-datasets) | Dataset | Domain | Corpus Language | Main Modalities | # Pages | # Queries (with translations) | |---|---|---|---|---|---| diff --git a/docs/conf.py b/docs/conf.py index 27aeca848..9c2a17c11 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,4 @@ -# Copyright (c) '2025-%Y, NVIDIA CORPORATION. All rights reserved. +# Copyright (c) 2025-%Y, NVIDIA CORPORATION. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import sys project = " NVIDIA RAG blueprint" -copyright = "'2025-%Y, NVIDIA Corporation" +copyright = "2025-%Y, NVIDIA Corporation" author = "NVIDIA Corporation" release = "2.5.0" diff --git a/docs/documentation.md b/docs/documentation.md index de15a9254..53b943dfa 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -8,6 +8,9 @@ - [Documentation Development](#documentation-development) - [Build the Documentation](#build-the-documentation) - [Live Building](#live-building) + - [Documentation Version](#documentation-version) + - [Publishing multiple versions on the public site](#publishing-multiple-versions-on-the-public-site) + - [Multi-version build script](#multi-version-build-script) ## Build the Documentation @@ -40,4 +43,46 @@ The three files below control the version switcher. Before you attempt to publis * docs/versions1.json * docs/project.json -* docs/conf.py \ No newline at end of file +* docs/conf.py + +Validate the manifest and that `release` matches `project.json` before building: + +```sh +uv run python docs/scripts/verify_doc_version_manifest.py +``` + +### Publishing multiple versions on the public site + +Use the **same** `docs/versions1.json` content for every release line you build (list every published version; `preferred` should be `true` only for the default, usually the latest). On each **release branch or tag**, set `release` in `conf.py` and `version` in `project.json` to that line’s version (for example `2.4.0` on the `2.4.x` branch), then build: + +```sh +uv run --group docs sphinx-build . _build/html +``` + +Deploy the HTML so each line lives as a **sibling** folder, for example `2.3.0/`, `2.4.0/`, `2.5.0/`. The theme resolves `../versions1.json` from the version **index** page to a file **next to** those folders (the parent directory). Copy the same `docs/versions1.json` to that parent as `versions1.json` when you publish, or ensure your pipeline deploys it there once per release. If you add a version to the manifest, rebuild (or redeploy) each affected tree and refresh the root `versions1.json`; invalidate CDN cache if the menu still looks stale. + +### Multi-version build script + +From the repository root, you can build several release lines into one tree: `docs/_build/multiversion/{version}/` plus a root `versions1.json`. The script reads your current `docs/versions1.json` as the canonical manifest, then for each version checks out git tag `v{version}` if it exists, otherwise branch `release-v{version}`, writes that manifest into `docs/versions1.json`, runs the verifier, and runs Sphinx. Your original `HEAD` is restored at the end. + +Preview which refs will be used (no git or build): + +```powershell +.\docs\scripts\build_multiversion_docs.ps1 -DryRun +``` + +Full build (requires a clean working tree, or pass `-AllowDirty`): + +```powershell +.\docs\scripts\build_multiversion_docs.ps1 -Versions @('2.3.0','2.4.0','2.5.0') +``` + +On Linux or macOS: + +```sh +chmod +x docs/scripts/build_multiversion_docs.sh +./docs/scripts/build_multiversion_docs.sh --dry-run +./docs/scripts/build_multiversion_docs.sh --versions 2.3.0,2.4.0,2.5.0 +``` + +Serve the result locally, for example: `python -m http.server 8080 --directory docs/_build/multiversion` and open `http://localhost:8080/2.5.0/` to confirm the switcher. \ No newline at end of file diff --git a/docs/notebooks.md b/docs/notebooks.md index beff3dc94..35b2b176b 100644 --- a/docs/notebooks.md +++ b/docs/notebooks.md @@ -124,7 +124,7 @@ Use the following notebooks to learn how to how to extend the system with custom Use the following notebook for cloud deployment scenarios. -- [launchable.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/launchable.ipynb) – A deployment-ready notebook intended to run in a [Brev environment](https://console.brev.dev/environment/new). To learn more about Brev, refer to [Brev](https://docs.nvidia.com/brev/latest/about-brev.html). Follow the instructions for running Jupyter notebooks in a cloud-based environment based on the hardware requirements specified in the launchable. +- [launchable.ipynb](https://github.com/NVIDIA-AI-Blueprints/rag/blob/main/notebooks/launchable.ipynb) – A deployment-ready notebook intended to run in a [Brev environment](https://console.brev.dev/environment/new). To learn more about Brev, refer to [Brev](https://developer.nvidia.com/brev). Follow the instructions for running Jupyter notebooks in a cloud-based environment based on the hardware requirements specified in the launchable. ## Related Topics diff --git a/docs/scripts/build_multiversion_docs.ps1 b/docs/scripts/build_multiversion_docs.ps1 new file mode 100644 index 000000000..4aa131798 --- /dev/null +++ b/docs/scripts/build_multiversion_docs.ps1 @@ -0,0 +1,165 @@ +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +<# +.SYNOPSIS + Build Sphinx HTML for multiple release lines into a single publish layout. + +.DESCRIPTION + For each version, checks out git ref v{version} (tag) or release-v{version} (branch), + writes the canonical docs/versions1.json (so every build lists the same versions), + runs verify_doc_version_manifest.py, then sphinx-build into + docs/_build/multiversion/{version}/. + + Copies the same manifest to docs/_build/multiversion/versions1.json for the + version switcher when the site root is this folder. + + Requires a clean working tree unless -AllowDirty is used. + +.PARAMETER Versions + Semver strings without a leading v, e.g. 2.3.0, 2.4.0, 2.5.0 + +.PARAMETER CanonicalManifest + Path to the versions1.json to inject on every checkout (default: docs/versions1.json + from the working tree at script start — save a backup first if needed). + +.PARAMETER OutputRoot + Directory under docs/ that will contain per-version folders and root versions1.json + (default: docs/_build/multiversion). + +.EXAMPLE + .\docs\scripts\build_multiversion_docs.ps1 -Versions @('2.3.0','2.4.0','2.5.0') + +.EXAMPLE + .\docs\scripts\build_multiversion_docs.ps1 -DryRun +#> + +[CmdletBinding()] +param( + [Parameter(Position = 0)] + [string[]]$Versions = @('2.3.0', '2.4.0', '2.5.0'), + + [string]$CanonicalManifest = '', + + [string]$OutputRoot = '', + + [switch]$DryRun, + + [switch]$AllowDirty +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path +Set-Location $RepoRoot + +if (-not $OutputRoot) { + $OutputRoot = Join-Path $RepoRoot 'docs\_build\multiversion' +} else { + $OutputRoot = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputRoot) +} + +if (-not $CanonicalManifest) { + $CanonicalManifest = Join-Path $RepoRoot 'docs\versions1.json' +} else { + $CanonicalManifest = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($CanonicalManifest) +} + +function Resolve-VersionGitRef { + param([string]$Version) + $tag = "v$Version" + git rev-parse -q --verify "refs/tags/$tag" 2>$null | Out-Null + if ($LASTEXITCODE -eq 0) { + return $tag + } + $branch = "release-v$Version" + git rev-parse -q --verify "refs/heads/$branch" 2>$null | Out-Null + if ($LASTEXITCODE -eq 0) { + return $branch + } + throw "No git tag '$tag' or branch '$branch' found for version $Version" +} + +if (-not $DryRun) { + $dirty = git status --porcelain + if ($dirty -and -not $AllowDirty) { + throw "Working tree is dirty. Commit or stash changes, or pass -AllowDirty." + } +} + +if (-not (Test-Path -LiteralPath $CanonicalManifest)) { + throw "Canonical manifest not found: $CanonicalManifest" +} + +$canonicalJson = [System.IO.File]::ReadAllText($CanonicalManifest) + +$origHead = git rev-parse HEAD + +try { + if (-not $DryRun) { + New-Item -ItemType Directory -Force -Path $OutputRoot | Out-Null + } + + foreach ($ver in $Versions) { + $ref = Resolve-VersionGitRef -Version $ver + $dest = Join-Path $OutputRoot $ver + + Write-Host "==> Version $ver <= ref $ref => $dest" -ForegroundColor Cyan + + if ($DryRun) { + continue + } + + git checkout $ref + [System.IO.File]::WriteAllText( + (Join-Path $RepoRoot 'docs\versions1.json'), + $canonicalJson, + [System.Text.UTF8Encoding]::new($false) + ) + + & uv run python docs/scripts/verify_doc_version_manifest.py + if ($LASTEXITCODE -ne 0) { + throw "verify_doc_version_manifest.py failed for $ver (ref $ref)" + } + + if (Test-Path -LiteralPath $dest) { + Remove-Item -LiteralPath $dest -Recurse -Force + } + New-Item -ItemType Directory -Force -Path $dest | Out-Null + + & uv run --group docs sphinx-build docs $dest + if ($LASTEXITCODE -ne 0) { + throw "sphinx-build failed for $ver" + } + } + + if (-not $DryRun) { + $rootManifest = Join-Path $OutputRoot 'versions1.json' + [System.IO.File]::WriteAllText( + $rootManifest, + $canonicalJson, + [System.Text.UTF8Encoding]::new($false) + ) + Write-Host "Wrote $rootManifest" -ForegroundColor Green + } +} +finally { + if (-not $DryRun) { + git checkout $origHead + Write-Host "Restored HEAD to $origHead" -ForegroundColor DarkGray + } +} + +Write-Host 'Done.' -ForegroundColor Green diff --git a/docs/scripts/build_multiversion_docs.sh b/docs/scripts/build_multiversion_docs.sh new file mode 100644 index 000000000..23643b35f --- /dev/null +++ b/docs/scripts/build_multiversion_docs.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build Sphinx HTML for multiple release lines into docs/_build/multiversion/. +# See build_multiversion_docs.ps1 for behavior and options. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +cd "${REPO_ROOT}" + +DRY_RUN=0 +ALLOW_DIRTY=0 +VERSIONS=(2.3.0 2.4.0 2.5.0) +CANONICAL_MANIFEST="${REPO_ROOT}/docs/versions1.json" +OUTPUT_ROOT="${REPO_ROOT}/docs/_build/multiversion" + +usage() { + echo "Usage: $0 [--dry-run] [--allow-dirty] [--versions V1,V2,...] [--manifest PATH] [--output-root PATH]" >&2 + exit 1 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) DRY_RUN=1; shift ;; + --allow-dirty) ALLOW_DIRTY=1; shift ;; + --versions) + IFS=',' read -r -a VERSIONS <<< "$2" + shift 2 + ;; + --manifest) CANONICAL_MANIFEST="$2"; shift 2 ;; + --output-root) OUTPUT_ROOT="$2"; shift 2 ;; + -h|--help) usage ;; + *) echo "Unknown option: $1" >&2; usage ;; + esac +done + +resolve_ref() { + local ver="$1" + local tag="v${ver}" + local branch="release-v${ver}" + if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null 2>&1; then + echo "${tag}" + return + fi + if git rev-parse -q --verify "refs/heads/${branch}" >/dev/null 2>&1; then + echo "${branch}" + return + fi + echo "No git tag ${tag} or branch ${branch} for ${ver}" >&2 + return 1 +} + +if [[ "${DRY_RUN}" -eq 0 ]]; then + if [[ -n "$(git status --porcelain)" && "${ALLOW_DIRTY}" -eq 0 ]]; then + echo "Working tree is dirty. Commit or stash, or pass --allow-dirty." >&2 + exit 1 + fi +fi + +if [[ ! -f "${CANONICAL_MANIFEST}" ]]; then + echo "Canonical manifest not found: ${CANONICAL_MANIFEST}" >&2 + exit 1 +fi + +canonical_json="$(cat "${CANONICAL_MANIFEST}")" +orig_head="$(git rev-parse HEAD)" + +if [[ "${DRY_RUN}" -eq 0 ]]; then + mkdir -p "${OUTPUT_ROOT}" + trap 'git checkout "${orig_head}"' EXIT +fi + +for ver in "${VERSIONS[@]}"; do + ref="$(resolve_ref "${ver}")" + dest="${OUTPUT_ROOT}/${ver}" + echo "==> Version ${ver} <= ref ${ref} => ${dest}" + + if [[ "${DRY_RUN}" -ne 0 ]]; then + continue + fi + + git checkout "${ref}" + printf '%s' "${canonical_json}" >"${REPO_ROOT}/docs/versions1.json" + + uv run python docs/scripts/verify_doc_version_manifest.py + + rm -rf "${dest}" + mkdir -p "${dest}" + uv run --group docs sphinx-build docs "${dest}" +done + +if [[ "${DRY_RUN}" -eq 0 ]]; then + printf '%s' "${canonical_json}" >"${OUTPUT_ROOT}/versions1.json" + echo "Wrote ${OUTPUT_ROOT}/versions1.json" +fi + +echo "Done." diff --git a/docs/scripts/verify_doc_version_manifest.py b/docs/scripts/verify_doc_version_manifest.py new file mode 100644 index 000000000..6392064ed --- /dev/null +++ b/docs/scripts/verify_doc_version_manifest.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Validate docs/versions1.json and consistency with conf.py / project.json. + +Run from the repository root: + + uv run python docs/scripts/verify_doc_version_manifest.py + +Use before building and publishing documentation so the version switcher manifest +is well-formed and matches the current branch's declared release. +""" + +from __future__ import annotations + +import argparse +import ast +import json +import re +import sys +from pathlib import Path + + +def _docs_dir() -> Path: + return Path(__file__).resolve().parent.parent + + +def _read_release_from_conf(conf_path: Path) -> str: + tree = ast.parse(conf_path.read_text(encoding="utf-8")) + for node in ast.walk(tree): + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id == "release": + value = node.value + if isinstance(value, ast.Constant) and isinstance( + value.value, str + ): + return value.value + raise ValueError(f'Could not find release = "..." string in {conf_path}') + + +def _validate_versions_payload(data: object) -> list[dict[str, object]]: + if not isinstance(data, list): + raise ValueError("versions1.json must be a JSON array") + rows: list[dict[str, object]] = [] + for i, item in enumerate(data): + if not isinstance(item, dict): + raise ValueError(f"Entry {i} must be an object") + rows.append(item) + return rows + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--docs-dir", + type=Path, + default=_docs_dir(), + help="Path to the docs/ folder (default: next to this script)", + ) + args = parser.parse_args() + docs = args.docs_dir.resolve() + versions_path = docs / "versions1.json" + conf_path = docs / "conf.py" + project_path = docs / "project.json" + + errors: list[str] = [] + + try: + payload = json.loads(versions_path.read_text(encoding="utf-8")) + rows = _validate_versions_payload(payload) + except (OSError, json.JSONDecodeError, ValueError) as e: + print(f"ERROR: {versions_path}: {e}", file=sys.stderr) + return 1 + + preferred_count = 0 + url_re = re.compile(r"^\.\./[0-9]+\.[0-9]+\.[0-9]+/$") + for i, row in enumerate(rows): + ver = row.get("version") + url = row.get("url") + if not isinstance(ver, str) or not ver.strip(): + errors.append(f"Entry {i}: missing or invalid 'version'") + if not isinstance(url, str) or not url_re.match(url): + errors.append( + f"Entry {i}: 'url' must look like '../M.m.p/' (got {url!r})" + ) + if row.get("preferred") is True: + preferred_count += 1 + elif "preferred" in row and row["preferred"] not in (False, None): + errors.append(f"Entry {i}: 'preferred' must be true or omitted") + + if preferred_count != 1: + errors.append( + f"Expected exactly one entry with 'preferred': true, got {preferred_count}" + ) + + try: + release = _read_release_from_conf(conf_path) + except (OSError, ValueError) as e: + errors.append(f"conf.py: {e}") + release = None + + proj_ver: str | None = None + try: + proj = json.loads(project_path.read_text(encoding="utf-8")) + if isinstance(proj, dict): + v = proj.get("version") + proj_ver = v if isinstance(v, str) else None + if proj_ver is None: + errors.append("project.json: missing top-level string 'version'") + except (OSError, json.JSONDecodeError) as e: + errors.append(f"project.json: {e}") + + if not errors and release is not None and proj_ver is not None: + if proj_ver != release: + errors.append( + f"docs/conf.py release ({release!r}) != docs/project.json " + f"version ({proj_ver!r}) — they should match for this branch" + ) + + if errors: + print(f"Validation failed for {versions_path}:", file=sys.stderr) + for msg in errors: + print(f" - {msg}", file=sys.stderr) + return 1 + + print(f"OK: {versions_path} ({len(rows)} versions)") + if release is not None: + print(f"OK: conf.py release and project.json version both {release!r}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/support-matrix.md b/docs/support-matrix.md index 07843a774..e0e2f88ae 100644 --- a/docs/support-matrix.md +++ b/docs/support-matrix.md @@ -78,7 +78,8 @@ The following are requirements and recommendations for the individual components - **LLM NIM (llama-3.3-nemotron-super-49b-v1.5)** – Refer to the [Support Matrix](https://docs.nvidia.com/nim/large-language-models/latest/supported-models.html#llama-3-3-nemotron-super-49b-v1-5). - **Embedding NIM (Llama-3.2-NV-EmbedQA-1B-v2 )** – Refer to the [Support Matrix](https://docs.nvidia.com/nim/nemo-retriever/text-embedding/latest/support-matrix.html#llama-3-2-nv-embedqa-1b-v2). - **Reranking NIM (llama-3_2-nv-rerankqa-1b-v2 )**: Refer to the [Support Matrix](https://docs.nvidia.com/nim/nemo-retriever/text-reranking/latest/support-matrix.html#llama-3-2-nv-rerankqa-1b-v2). -- **NeMo Retriever OCR (Default)**: Refer to the [Support Matrix](https://docs.nvidia.com/nim/ingestion/image-ocr/1.2.1/support-matrix.html). +- **NVIDIA NIM for Image OCR (baidu/paddleocr)**: Refer to the [Support Matrix](https://docs.nvidia.com/nemo/retriever/latest/extraction/support-matrix/). +**NeMo Retriever OCR**: Refer to the [Support Matrix](https://docs.nvidia.com/nemo/retriever/latest/extraction/support-matrix/) - **NVIDIA NIMs for Object Detection**: - NeMo Retriever Page Elements v3 [Support Matrix](https://docs.nvidia.com/nim/ingestion/object-detection/latest/support-matrix.html#nemo-retriever-page-elements-v3) - NeMo Retriever Graphic Elements v1 [Support Matrix](https://docs.nvidia.com/nim/ingestion/object-detection/latest/support-matrix.html#nemo-retriever-graphic-elements-v1) diff --git a/docs/versions1.json b/docs/versions1.json index 67cecce82..2a3a5ee92 100644 --- a/docs/versions1.json +++ b/docs/versions1.json @@ -4,6 +4,10 @@ "version": "2.5.0", "url": "../2.5.0/" }, + { + "version": "2.4.0", + "url": "../2.4.0/" + }, { "version": "2.3.0", "url": "../2.3.0/" diff --git a/docs/vlm-embed.md b/docs/vlm-embed.md index 0bec55afe..01ab062a8 100644 --- a/docs/vlm-embed.md +++ b/docs/vlm-embed.md @@ -2,7 +2,7 @@ SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> -# Use Multimodal (VLM) Embedding for Ingestion for NVIDIA RAG Blueprint (Early Access) +# Use Multimodal (VLM) Embedding for Ingestion for NVIDIA RAG Blueprint This guide shows how to enable and use the multimodal embedding model `nvidia/llama-nemotron-embed-vl-1b-v2` with the [NVIDIA RAG Blueprint](readme.md) ingestion pipeline. @@ -182,8 +182,6 @@ After modifying [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml) For detailed HELM deployment instructions, see [Helm Deployment Guide](deploy-helm.md). - - ## Additional Configuration: Extraction and Embedding Modalities To configure how content is extracted and embedded (similar to the Docker configurations shown above), you can add extraction and modality settings to your [`values.yaml`](../deploy/helm/nvidia-blueprint-rag/values.yaml): From 1c0321235e344c68e8b63133d0fdf8edb5592bfc Mon Sep 17 00:00:00 2001 From: sahoor-netapp Date: Sat, 18 Apr 2026 16:14:11 +0530 Subject: [PATCH 50/52] Add GCNV data ingestor Helm chart example (#475) Package the GCNV data ingestor deployment into a reusable Helm chart with PVC, service, and namespace templates plus installation guidance for Trident-backed storage. Made-with: Cursor Signed-off-by: Raj Sahoo --- examples/README.md | 9 + .../Chart.yaml | 6 + .../README.md | 178 ++++++ .../templates/_helpers.tpl | 80 +++ .../templates/deployment.yaml | 99 ++++ .../templates/pvc.yaml | 35 ++ .../templates/service.yaml | 19 + .../templates/serviceaccount.yaml | 13 + .../templates/validate.yaml | 15 + .../values.schema.json | 510 ++++++++++++++++++ .../values.yaml | 90 ++++ 11 files changed, 1054 insertions(+) create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/Chart.yaml create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/README.md create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/templates/_helpers.tpl create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/templates/deployment.yaml create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/templates/pvc.yaml create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/templates/service.yaml create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/templates/serviceaccount.yaml create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/templates/validate.yaml create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/values.schema.json create mode 100644 examples/google-cloud-netapp-volumes-data-ingestor/values.yaml diff --git a/examples/README.md b/examples/README.md index ba86d6765..b2e991ca6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -9,6 +9,7 @@ This directory contains example integrations and extensions for NVIDIA RAG. | [rag_react_agent](./rag_react_agent/) | Integration with [NeMo Agent Toolkit (NAT)](https://github.com/NVIDIA/NeMo-Agent-Toolkit) providing RAG query and search capabilities for agent workflows | [README](./rag_react_agent/README.md) | | [nvidia_rag_mcp](./nvidia_rag_mcp/) | MCP (Model Context Protocol) server and client for exposing NVIDIA RAG capabilities to MCP-compatible applications | [Documentation](../docs/mcp.md) | | [rag_event_ingest](./rag_event_ingest/) | Automated document ingestion from object storage (MinIO) via Kafka | [Notebook](../notebooks/rag_event_ingest.ipynb) | +| [google-cloud-netapp-volumes-data-ingestor](./google-cloud-netapp-volumes-data-ingestor/) | Helm chart for deploying the GCNV data ingestor with PVC-backed storage and configurable runtime settings | [README](./google-cloud-netapp-volumes-data-ingestor/README.md) | ## rag_react_agent @@ -39,3 +40,11 @@ Components: - **data/** - Sample documents for testing See the [notebook](../notebooks/rag_event_ingest.ipynb) for step-by-step deployment and testing. + +## google-cloud-netapp-volumes-data-ingestor + +This example packages a GCNV data ingestor deployment as a reusable Helm chart. It is intended for Kubernetes environments where application state and source data are mounted from PVCs, including NetApp Google Cloud NetApp Volumes-backed storage. + +The chart supports configurable image settings, PVC creation or reuse, health probes, service exposure, and runtime environment overrides for connecting to an NVIDIA ingestor endpoint. + +See the [google-cloud-netapp-volumes-data-ingestor README](./google-cloud-netapp-volumes-data-ingestor/README.md) for prerequisites, installation, and configuration details. diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/Chart.yaml b/examples/google-cloud-netapp-volumes-data-ingestor/Chart.yaml new file mode 100644 index 000000000..6fa7cd8b9 --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: gcnv-data-ingestor +description: Public Helm chart for the GCNV data ingestor deployment +type: application +version: 0.1.0 +appVersion: "0.1.0" diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/README.md b/examples/google-cloud-netapp-volumes-data-ingestor/README.md new file mode 100644 index 000000000..50c0fd09c --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/README.md @@ -0,0 +1,178 @@ +# Google Cloud NetApp Volumes (GCNV) Data Ingestor Helm Chart + +This chart packages the deployment of the GCNV Data Ingestor that integrates with the NVIDIA Foundational RAG pipeline into a reusable Helm chart at `examples/google-cloud-netapp-volumes-data-ingestor`. + +Create or target the namespace externally with `--namespace ... --create-namespace`. Chart-managed namespace creation is intentionally not supported because Helm cannot reliably create the release namespace from within the same chart. + +## Prerequisites + +Before installing this chart, make sure the cluster can provision or expose the required PVCs from NetApp Google Cloud NetApp Volumes (GCNV). + +1. Install and configure NetApp Trident in the target cluster. +2. Create or use a Trident `StorageClass` that maps to your GCNV backend. +3. Decide how you want the chart to get storage: + - Let the chart create PVCs by setting `appData.storageClassName` and `sourceData.storageClassName` to Trident-backed classes. + - Or create the PVCs ahead of time with Trident and set `appData.create=false`, `appData.existingClaim=`, `sourceData.create=false`, and `sourceData.existingClaim=`. + - If you set `create=false`, the matching `existingClaim` is required. +4. Make sure the Docker Hub image and tag you want to deploy are available. +5. If the Docker Hub repository is private, create an image pull secret in the target namespace and set `image.pullSecrets`. + +## Chart Layout + +```text +examples/google-cloud-netapp-volumes-data-ingestor/ +├── Chart.yaml +├── values.yaml +├── values.schema.json +├── README.md +└── templates/ + ├── _helpers.tpl + ├── deployment.yaml + ├── pvc.yaml + ├── service.yaml + ├── validate.yaml + └── serviceaccount.yaml +``` + +## Important Values + +Update these values before install: + +- `image.repository`: set to your Docker Hub image path +- `image.tag`: set to the image tag you want to deploy +- `appData.storageClassName`: set to your Trident-backed app PVC class when the chart creates the PVC +- `appData.size`: app PVC size request, defaults to `50Gi` +- `appData.existingClaim`: use an already-created PVC instead of letting the chart create one; required when `appData.create=false` +- `sourceData.storageClassName`: set to your Trident-backed GCNV source PVC class when the chart creates the PVC +- `sourceData.size`: source PVC size request, defaults to `200Gi` +- `sourceData.existingClaim`: use an already-created source PVC instead of letting the chart create one; required when `sourceData.create=false` +- `env.nvIngestEndpoint`: set to the reachable NVIDIA ingestor-server `/v1` base URL + +The chart validates required values during `helm lint`, `helm template`, `helm install`, and `helm upgrade`. + +## Install + +You can either edit `values.yaml` directly or use an override file. + +Example override file: + +```yaml +image: + repository: docker.io/acme/netapp_volumes_rag_ingestor + tag: "REPLACE_WITH_REAL_TAG" + +appData: + storageClassName: trident-app + +sourceData: + storageClassName: trident-gcnv + +env: + nvIngestEndpoint: http://YOUR_INGESTOR_SERVER:8082/v1 +``` + +Install with: + +```bash +helm install gcnv-data-ingestor ./examples/google-cloud-netapp-volumes-data-ingestor \ + --namespace gcnv-data-ingestor \ + --create-namespace \ + -f my-values.yaml +``` + +If your Docker Hub repository is private, add an image pull secret. `image.pullSecrets` must be a YAML list of secret names: + +```yaml +image: + pullSecrets: + - dockerhub-secret +``` + +## Common Overrides + +Resize the chart-managed PVCs: + +```yaml +appData: + size: 100Gi + storageClassName: trident-app + +sourceData: + size: 500Gi + storageClassName: trident-gcnv +``` + +## Use Existing PVCs + +If Trident or another workflow already created the claims you want to mount, use overrides like this: + +```yaml +appData: + create: false + existingClaim: gcnv-ingestor-config-data + +sourceData: + create: false + existingClaim: gcnv-data-for-rag +``` + +The chart will mount those existing claims into the Pod instead of creating new PVCs. + +If you set `create: false` and leave `existingClaim` empty, the chart now fails fast during Helm validation instead of creating a broken release. + +Expose the service differently: + +```yaml +service: + type: ClusterIP + port: 8000 +``` + +Tune runtime resources: + +```yaml +resources: + requests: + cpu: 1 + memory: 2Gi + limits: + cpu: 4 + memory: 8Gi +``` + +Pass extra environment variables using normal Kubernetes `env` list syntax: + +```yaml +env: + extra: + - name: EXTRA_FLAG + value: "1" +``` + +## Supported Values + +The chart supports overrides for the following areas in `values.yaml`: + +- Naming: `nameOverride`, `fullnameOverride` +- Labels: `selectorLabels`, `podLabels`, `podAnnotations` +- Deployment: `replicaCount`, `strategy` +- Image: `image.repository`, `image.tag`, `image.pullPolicy`, `image.pullSecrets` +- Service account: `serviceAccount.create`, `serviceAccount.name`, `serviceAccount.automount`, `serviceAccount.annotations` +- Service: `service.type`, `service.port`, `service.annotations` +- App PVC: `appData.create`, `appData.existingClaim`, `appData.name`, `appData.accessModes`, `appData.size`, `appData.storageClassName`, `appData.mountPath` +- Source PVC: `sourceData.create`, `sourceData.existingClaim`, `sourceData.name`, `sourceData.accessModes`, `sourceData.size`, `sourceData.storageClassName`, `sourceData.mountPath`, `sourceData.readOnly` +- Environment: `env.scanOutputRoot`, `env.appDbPath`, `env.defaultIncrementalSchedulerMins`, `env.nvIngestMode`, `env.nvIngestEndpoint`, `env.extra` +- Health checks: `probes.liveness.*`, `probes.readiness.*` +- Scheduling and placement: `nodeSelector`, `tolerations`, `affinity` +- Resource limits: `resources` + +## Verify + +```bash +helm template gcnv-data-ingestor ./examples/google-cloud-netapp-volumes-data-ingestor -n gcnv-data-ingestor +kubectl get pods,svc,pvc -n gcnv-data-ingestor +``` + +The service defaults to `NodePort` with service port `8000`, matching the source manifest. Kubernetes assigns the external node port automatically unless you customize the Service separately. + +The default PVC access modes are `ReadWriteOnce`, so increasing `replicaCount` beyond `1` may require different storage semantics or pod placement constraints. diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/templates/_helpers.tpl b/examples/google-cloud-netapp-volumes-data-ingestor/templates/_helpers.tpl new file mode 100644 index 000000000..60cb834ba --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/templates/_helpers.tpl @@ -0,0 +1,80 @@ +{{/* +Expand the chart name. +*/}} +{{- define "gcnv-data-ingestor.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "gcnv-data-ingestor.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Chart name and version. +*/}} +{{- define "gcnv-data-ingestor.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels. +*/}} +{{- define "gcnv-data-ingestor.labels" -}} +helm.sh/chart: {{ include "gcnv-data-ingestor.chart" . }} +{{ include "gcnv-data-ingestor.selectorLabels" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end -}} + +{{/* +Selector labels copied from the source deployment. +*/}} +{{- define "gcnv-data-ingestor.selectorLabels" -}} +app.kubernetes.io/name: {{ .Values.selectorLabels.name }} +app.kubernetes.io/instance: {{ .Values.selectorLabels.instance }} +{{- end -}} + +{{/* +Service account name. +*/}} +{{- define "gcnv-data-ingestor.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} +{{- default (printf "%s-sa" (include "gcnv-data-ingestor.fullname" .)) .Values.serviceAccount.name -}} +{{- else -}} +{{- default "default" .Values.serviceAccount.name -}} +{{- end -}} +{{- end -}} + +{{/* +App PVC name. +*/}} +{{- define "gcnv-data-ingestor.appPvcName" -}} +{{- if .Values.appData.existingClaim -}} +{{- .Values.appData.existingClaim -}} +{{- else -}} +{{- .Values.appData.name -}} +{{- end -}} +{{- end -}} + +{{/* +Source PVC name. +*/}} +{{- define "gcnv-data-ingestor.sourcePvcName" -}} +{{- if .Values.sourceData.existingClaim -}} +{{- .Values.sourceData.existingClaim -}} +{{- else -}} +{{- .Values.sourceData.name -}} +{{- end -}} +{{- end -}} diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/templates/deployment.yaml b/examples/google-cloud-netapp-volumes-data-ingestor/templates/deployment.yaml new file mode 100644 index 000000000..c1fbec3d9 --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/templates/deployment.yaml @@ -0,0 +1,99 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "gcnv-data-ingestor.fullname" . }} + labels: + {{- include "gcnv-data-ingestor.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: {{ .Values.strategy.type }} + selector: + matchLabels: + {{- include "gcnv-data-ingestor.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "gcnv-data-ingestor.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "gcnv-data-ingestor.serviceAccountName" . }} + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- range . }} + - name: {{ . }} + {{- end }} + {{- end }} + containers: + - name: gcnv-data-ingestor + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + env: + - name: MOUNT_PATH + value: {{ .Values.sourceData.mountPath | quote }} + - name: SCAN_OUTPUT_ROOT + value: {{ .Values.env.scanOutputRoot | quote }} + - name: APP_DB_PATH + value: {{ .Values.env.appDbPath | quote }} + - name: DEFAULT_INCREMENTAL_SCHEDULER_MINS + value: {{ .Values.env.defaultIncrementalSchedulerMins | quote }} + - name: NV_INGEST_MODE + value: {{ .Values.env.nvIngestMode | quote }} + - name: NV_INGEST_ENDPOINT + value: {{ .Values.env.nvIngestEndpoint | quote }} + {{- with .Values.env.extra }} + {{- toYaml . | nindent 12 }} + {{- end }} + livenessProbe: + httpGet: + path: {{ .Values.probes.liveness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.liveness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.liveness.failureThreshold }} + readinessProbe: + httpGet: + path: {{ .Values.probes.readiness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.readiness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.readiness.failureThreshold }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: app-data + mountPath: {{ .Values.appData.mountPath | quote }} + - name: source-data + mountPath: {{ .Values.sourceData.mountPath | quote }} + readOnly: {{ .Values.sourceData.readOnly }} + volumes: + - name: app-data + persistentVolumeClaim: + claimName: {{ include "gcnv-data-ingestor.appPvcName" . }} + - name: source-data + persistentVolumeClaim: + claimName: {{ include "gcnv-data-ingestor.sourcePvcName" . }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/templates/pvc.yaml b/examples/google-cloud-netapp-volumes-data-ingestor/templates/pvc.yaml new file mode 100644 index 000000000..590166056 --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/templates/pvc.yaml @@ -0,0 +1,35 @@ +{{- if and .Values.appData.create (not .Values.appData.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "gcnv-data-ingestor.appPvcName" . }} + labels: + {{- include "gcnv-data-ingestor.labels" . | nindent 4 }} +spec: + accessModes: + {{- toYaml .Values.appData.accessModes | nindent 4 }} + resources: + requests: + storage: {{ .Values.appData.size }} + {{- with .Values.appData.storageClassName }} + storageClassName: {{ . | quote }} + {{- end }} +{{- end }} +{{- if and .Values.sourceData.create (not .Values.sourceData.existingClaim) }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "gcnv-data-ingestor.sourcePvcName" . }} + labels: + {{- include "gcnv-data-ingestor.labels" . | nindent 4 }} +spec: + accessModes: + {{- toYaml .Values.sourceData.accessModes | nindent 4 }} + resources: + requests: + storage: {{ .Values.sourceData.size }} + {{- with .Values.sourceData.storageClassName }} + storageClassName: {{ . | quote }} + {{- end }} +{{- end }} diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/templates/service.yaml b/examples/google-cloud-netapp-volumes-data-ingestor/templates/service.yaml new file mode 100644 index 000000000..6d0ebbc36 --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "gcnv-data-ingestor.fullname" . }} + labels: + {{- include "gcnv-data-ingestor.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "gcnv-data-ingestor.selectorLabels" . | nindent 4 }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: http + protocol: TCP diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/templates/serviceaccount.yaml b/examples/google-cloud-netapp-volumes-data-ingestor/templates/serviceaccount.yaml new file mode 100644 index 000000000..67fe6bf5f --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "gcnv-data-ingestor.serviceAccountName" . }} + labels: + {{- include "gcnv-data-ingestor.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/templates/validate.yaml b/examples/google-cloud-netapp-volumes-data-ingestor/templates/validate.yaml new file mode 100644 index 000000000..60f03ba4f --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/templates/validate.yaml @@ -0,0 +1,15 @@ +{{- if .Values.namespace.create }} +{{- fail "namespace.create is not supported by this chart. Create the namespace outside the chart with --create-namespace or kubectl create namespace." }} +{{- end }} + +{{- if and (not .Values.appData.create) (not .Values.appData.existingClaim) }} +{{- fail "appData.existingClaim is required when appData.create=false." }} +{{- end }} + +{{- if and (not .Values.sourceData.create) (not .Values.sourceData.existingClaim) }} +{{- fail "sourceData.existingClaim is required when sourceData.create=false." }} +{{- end }} + +{{- if and (not .Values.serviceAccount.create) (not .Values.serviceAccount.name) }} +{{- fail "serviceAccount.name is required when serviceAccount.create=false." }} +{{- end }} diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/values.schema.json b/examples/google-cloud-netapp-volumes-data-ingestor/values.schema.json new file mode 100644 index 000000000..d61f87d29 --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/values.schema.json @@ -0,0 +1,510 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "nameOverride": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "namespace": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + } + } + }, + "selectorLabels": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "instance": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "name", + "instance" + ] + }, + "replicaCount": { + "type": "integer", + "minimum": 1 + }, + "strategy": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "Recreate", + "RollingUpdate" + ] + } + }, + "required": [ + "type" + ] + }, + "image": { + "type": "object", + "properties": { + "repository": { + "type": "string", + "minLength": 1 + }, + "tag": { + "type": "string", + "minLength": 1 + }, + "pullPolicy": { + "type": "string", + "enum": [ + "Always", + "IfNotPresent", + "Never" + ] + }, + "pullSecrets": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": [ + "repository", + "tag", + "pullPolicy", + "pullSecrets" + ] + }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "automount": { + "type": "boolean" + }, + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "create", + "name", + "automount", + "annotations" + ], + "allOf": [ + { + "if": { + "properties": { + "create": { + "const": false + } + }, + "required": [ + "create" + ] + }, + "then": { + "properties": { + "name": { + "minLength": 1 + } + } + } + } + ] + }, + "service": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "ClusterIP", + "NodePort", + "LoadBalancer" + ] + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "type", + "port", + "annotations" + ] + }, + "appData": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "existingClaim": { + "type": "string" + }, + "name": { + "type": "string", + "minLength": 1 + }, + "accessModes": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "ReadWriteOnce", + "ReadOnlyMany", + "ReadWriteMany", + "ReadWriteOncePod" + ] + } + }, + "size": { + "type": "string", + "minLength": 1 + }, + "storageClassName": { + "type": "string" + }, + "mountPath": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "create", + "existingClaim", + "name", + "accessModes", + "size", + "storageClassName", + "mountPath" + ], + "allOf": [ + { + "if": { + "properties": { + "create": { + "const": false + } + }, + "required": [ + "create" + ] + }, + "then": { + "properties": { + "existingClaim": { + "minLength": 1 + } + } + } + }, + { + "if": { + "properties": { + "create": { + "const": true + }, + "existingClaim": { + "maxLength": 0 + } + }, + "required": [ + "create", + "existingClaim" + ] + }, + "then": { + "properties": { + "storageClassName": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$" + } + } + } + } + ] + }, + "sourceData": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "existingClaim": { + "type": "string" + }, + "name": { + "type": "string", + "minLength": 1 + }, + "accessModes": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "ReadWriteOnce", + "ReadOnlyMany", + "ReadWriteMany", + "ReadWriteOncePod" + ] + } + }, + "size": { + "type": "string", + "minLength": 1 + }, + "storageClassName": { + "type": "string" + }, + "mountPath": { + "type": "string", + "minLength": 1 + }, + "readOnly": { + "type": "boolean" + } + }, + "required": [ + "create", + "existingClaim", + "name", + "accessModes", + "size", + "storageClassName", + "mountPath", + "readOnly" + ], + "allOf": [ + { + "if": { + "properties": { + "create": { + "const": false + } + }, + "required": [ + "create" + ] + }, + "then": { + "properties": { + "existingClaim": { + "minLength": 1 + } + } + } + }, + { + "if": { + "properties": { + "create": { + "const": true + }, + "existingClaim": { + "maxLength": 0 + } + }, + "required": [ + "create", + "existingClaim" + ] + }, + "then": { + "properties": { + "storageClassName": { + "type": "string", + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$" + } + } + } + } + ] + }, + "env": { + "type": "object", + "properties": { + "scanOutputRoot": { + "type": "string", + "minLength": 1 + }, + "appDbPath": { + "type": "string", + "minLength": 1 + }, + "defaultIncrementalSchedulerMins": { + "type": "string", + "pattern": "^[0-9]+$" + }, + "nvIngestMode": { + "type": "string", + "minLength": 1 + }, + "nvIngestEndpoint": { + "type": "string", + "minLength": 1, + "pattern": "^https?://.+" + }, + "extra": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "value": { + "type": "string" + }, + "valueFrom": { + "type": "object" + } + }, + "required": [ + "name" + ], + "anyOf": [ + { + "required": [ + "value" + ] + }, + { + "required": [ + "valueFrom" + ] + } + ] + } + } + }, + "required": [ + "scanOutputRoot", + "appDbPath", + "defaultIncrementalSchedulerMins", + "nvIngestMode", + "nvIngestEndpoint", + "extra" + ] + }, + "probes": { + "type": "object", + "properties": { + "liveness": { + "$ref": "#/definitions/httpProbe" + }, + "readiness": { + "$ref": "#/definitions/httpProbe" + } + }, + "required": [ + "liveness", + "readiness" + ] + }, + "resources": { + "type": "object" + }, + "podAnnotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "podLabels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "nodeSelector": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "tolerations": { + "type": "array" + }, + "affinity": { + "type": "object" + } + }, + "required": [ + "image", + "serviceAccount", + "service", + "appData", + "sourceData", + "env", + "probes" + ], + "definitions": { + "httpProbe": { + "type": "object", + "properties": { + "path": { + "type": "string", + "minLength": 1 + }, + "initialDelaySeconds": { + "type": "integer", + "minimum": 0 + }, + "periodSeconds": { + "type": "integer", + "minimum": 1 + }, + "timeoutSeconds": { + "type": "integer", + "minimum": 1 + }, + "failureThreshold": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "path", + "initialDelaySeconds", + "periodSeconds", + "timeoutSeconds", + "failureThreshold" + ] + } + } +} diff --git a/examples/google-cloud-netapp-volumes-data-ingestor/values.yaml b/examples/google-cloud-netapp-volumes-data-ingestor/values.yaml new file mode 100644 index 000000000..483b51672 --- /dev/null +++ b/examples/google-cloud-netapp-volumes-data-ingestor/values.yaml @@ -0,0 +1,90 @@ +nameOverride: "" +fullnameOverride: gcnv-data-ingestor + +namespace: + # Helm charts cannot reliably create their own target namespace during install. + # Use `helm install --create-namespace` instead. + create: false + +selectorLabels: + name: nvidia-gcnv-rag-manager + instance: netapp-volumes-rag-ingestor + +replicaCount: 1 + +strategy: + type: Recreate + +image: + repository: "" + tag: "" + pullPolicy: IfNotPresent + pullSecrets: [] + +serviceAccount: + create: true + name: gcnv-data-ingestor-sa + automount: true + annotations: {} + +service: + type: NodePort + port: 8000 + annotations: {} + +appData: + create: true + existingClaim: "" + name: gcnv-ingestor-config-data + accessModes: + - ReadWriteOnce + size: 50Gi + storageClassName: "" + mountPath: /data + +sourceData: + create: true + existingClaim: "" + name: gcnv-data-for-rag + accessModes: + - ReadWriteOnce + size: 200Gi + storageClassName: "" + mountPath: /source + readOnly: true + +env: + scanOutputRoot: /data/scans + appDbPath: /data/state/app.db + defaultIncrementalSchedulerMins: "0" + nvIngestMode: ingestor + nvIngestEndpoint: "" + extra: [] + +probes: + liveness: + path: /healthz + initialDelaySeconds: 20 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + readiness: + path: /healthz + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: "2" + memory: 4Gi + +podAnnotations: {} +podLabels: {} +nodeSelector: {} +tolerations: [] +affinity: {} From 56d3c61c26b3e528d27cffdb5ea003a3c659cabd Mon Sep 17 00:00:00 2001 From: Truong Nguyen Date: Tue, 5 May 2026 11:44:57 +0700 Subject: [PATCH 51/52] docs(perf): add RAG performance measurement methodology (#490) * docs(perf): add RAG performance measurement methodology Add performance benchmarking documentation covering TTFT and ITL metrics across four datasets (KG-RAG, RagBattlePacket, HotPotQA, BO767) plus synthetic workloads, comparing LLM-49B and VLM nano configurations with reasoning on/off. Signed-off-by: Truong Nguyen * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss * Update docs/perf-benchmarks.md Co-authored-by: Kurt Heiss --------- Signed-off-by: Truong Nguyen Co-authored-by: Kurt Heiss --- .../bo767_h100_performance.png | Bin 0 -> 222423 bytes .../cross_dataset_llm_reasoning_off.png | Bin 0 -> 299594 bytes .../hotpotqa_h100_performance.png | Bin 0 -> 236010 bytes .../kgrag_h100_performance.png | Bin 0 -> 242018 bytes .../ragbattlepacket_h100_performance.png | Bin 0 -> 252060 bytes .../wikipedia_synthetic_h100_performance.png | Bin 0 -> 182408 bytes docs/index.md | 2 + docs/perf-benchmarks.md | 232 ++++++++++++++++++ 8 files changed, 234 insertions(+) create mode 100644 docs/assets/perf-benchmarks/bo767_h100_performance.png create mode 100644 docs/assets/perf-benchmarks/cross_dataset_llm_reasoning_off.png create mode 100644 docs/assets/perf-benchmarks/hotpotqa_h100_performance.png create mode 100644 docs/assets/perf-benchmarks/kgrag_h100_performance.png create mode 100644 docs/assets/perf-benchmarks/ragbattlepacket_h100_performance.png create mode 100644 docs/assets/perf-benchmarks/wikipedia_synthetic_h100_performance.png create mode 100644 docs/perf-benchmarks.md diff --git a/docs/assets/perf-benchmarks/bo767_h100_performance.png b/docs/assets/perf-benchmarks/bo767_h100_performance.png new file mode 100644 index 0000000000000000000000000000000000000000..05e20227909efe5570151027c016250d511ca651 GIT binary patch literal 222423 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%0ALz@FM%)f)nTR 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 Mjh-KO~~qA}@5hEcLm2?X(~CzkhhPv2!=p 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≫5ApfpE)u!c^AI>1k@%G>m@*oNy=u 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 wv3P_M3^Q*eEVp=zLp!5{S5!r?NctC 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

E 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⩔Z@6~Z|G_n=ZMd6>p$@wiwbj0Ky=nyJM9 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

`t`LAu$pJMw2HF}p?x_mk!Rsu9+v0)W0$fz< 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

h7D){C8rzS|YL4*Z$#9+W!pRG2xO2T~SGpQi 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�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 B0mc9T literal 0 HcmV?d00001 diff --git a/docs/assets/perf-benchmarks/hotpotqa_h100_performance.png b/docs/assets/perf-benchmarks/hotpotqa_h100_performance.png new file mode 100644 index 0000000000000000000000000000000000000000..a4190fe686c4e5b1d62b020bad074526095deba1 GIT binary patch literal 236010 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ʗg0EvBT;b4^tAePk{4L->Kp_NrB*ZxukoulnOX(< z_;9~%0Xn~M9SLsDhdzpVo+L#xlfXV>eYUj?&dnHi+LT0IXB5F7h^MjToxKsU8t;~^ zbSwTSahN9%J-H6TuWVO$vb&T7UYpCHjqVTWemTvHIqg2a8P^$9PI#9x^#+Bj!ch#Ud=i?Dw@lZ0XR-^mjlWemhI7YA?SSW#61%|e=+ zCHy^BXd=%1ysip*PbVSeEr1*T`3#z>BqWCYHtf7qqV2u7!2te!BYJXaO`_Qg#6?mW z$D>X^YL9BW95GCtr=1zzxdqZHG=hjw&(C~nsyzr^i+lHL2ul4T!?Sa>1@uHX_KbnZ zuBs^%hg7Ah@nDn%mz*O{0bcp&mJOpSpSJAm5r|+3HXBB_M=8%ejqF@u@GbnSE zhkDrv%k1{9++8E{X|?t>c^h~3SmyzT{DF|tl>u-0$k+ zkW(3dfSJO_vnu6KQrDfYr=&Lk6&%5!J`hSon)yUG-03~Jw=e^(T)&XwG`S70QYWUb zU$U`!sY!c!y`GtRhE=M|x@xfC4aG?Zm{o7E3bvRN}Y$4?t5dM2bhib#Lc}*T0wQs&|+XoYN!<9Cwkx>+>V1jWODCB6F0d7ON8 z>+&(C^!?d?_X6&lcs+#}Kn;}op3si!^th9wYy8IO3?=t1zpdwC^~tqM#WRchShw&g z8$tdF#x*#?iWG2R@*>>wD-#n1DB3N1@c5cdp=_4?Q3)yjh=y{pOXnU1hA1*Wh2Bq( z@TwW93OUZaa|J9OHjpdS7?)B#{jmj7u|~IJ)uvUD@NVKWJ%Exekx)OM;ldAt2y4%2>uS!6;*DRfXPFraUMvoOy*Fh?g|6?mh1072ZdjbpfS}%Sxhpc9A7Zb9y*>vG_O7FYOJ+`N zB}eAa%36HcINKMJVJd5)TkuCzWy0=w9bLv8{=7tADAdch$O`I&iL$woawuBb%c^Wx zjGraX0-|^we#}}YOb5M`*~1ag%hW6LRE_X@zWH$<4iC|xAKrOi#X||F>S^=K{c^DT z=%!e9bE85zo_KS4(9?AdgRu z^piU-rkjQ$8s?(Ph*b&MuAa3i9_p^-$bH8Rb%5ES!<#XmofUaxN2 zybX^eXA_0mj?~h<_J&;ILw!UVh^n^RyVzk*%a0Wf1%kuS2v6#Ku|8wjz;dKxw-|@H zTS3kI0jDVMFVm|WJrgkqRq>7$6pbbXrLdTr1?Dn!McOS)o%@H5vj8uPT)6QmoGo7V z)fJ_FR`|p9f(t`K0{m+CeKia!`s>IK=3*G3_Ga5{(vhzBYi?o``$#<|e0EZSbIpR` zwGUSg(QMamXT5OC=S2w)e(U*rF|q17HkZn@)OakmA9h*<5e=X?9?%OSja53E|2RZ8 ztVJ!0D}w#9z)E8?GCaB)c0trl2u|eDm8<7SiGAHdfG?b3Xi|r)NPWoJ2PowmYUR^( zrs9yx!ZU_?ve;LTT<@44IZ-b6|E2drWf8EGr$tY_l{H;BqD7zteyp@}pC{S^? zLKnhyet~%FSMrjjNQ2lLIRRpI+CQDRPf}3~A*N6Kfs#zbYj>_Dp!$P?Raxx^?PTJ% zY7|3`1{YDKv=ybYY9;QwAcpofh|F~mgTMS8U2cIb4l zKV7}YkV3WInGtC_j)&9Omuj?8Y&=8$RDxu%c^|+Tz3N{5@_a(K*4H0&aX2<@6sPL#rs|)a{*sySSKmGnY;|fdcd{{lVX`;>wR7)o zd88Y#C6^qwdc8{9G&Z1}N1brXUAvj(1JvUQ{tWGuGbU&7U^E16di!q4*1EYpaLyd> zmRV0S@fh!}*Yp@KkvP2hp_P`t`SaaHaq~s4SGpT8@>L;GZ6nerM)nh*wA)*7!~%VG zp1s=nk_M<}^D$>(fSFihdZI0}NBvdVe&H$&wq;_t`RUx>0J=2?aGj-{x*!jzTdT1P zI9$8ukpX>xs~kNWOD)vX6Gv}2?l>gc*)+ZGCsZ1DJ`bin5*~~?SPv%jul5Xoew={m zow!U;wStkV$jdpguWHek7bWEj>#x#ucEl^TPSkFnBfZlHsU3$%7+4GSe>FkAf`MAPKD?M?KAEJzN@1dh1uCd%kzgCgVp z1-tV&RqKL_Lb2(6hE@MhTN-pz52o4!>49C9=W1TJb6Wi*^CqC=ZW`Ck8s!E}1+b9l zN7MsaOO#UNp9|~Xx92@HE}lPby#Fj!fL@6D0MH8@sod{8|FMkzT2jf(sK>x_{8!oa zkM963L8gj?O9-dkC-`542!}My{`}B_XBFkj$b%gqpbcgPe3y+# zPkS8@tWWkqi+JO@H_daS{qcwHjT)(*8{r&=Hp_Y|T>^}$_dh}_qM&kB_f)|_!&^~r z=?5tCofBHY2x1mso_X0Zo3Px8rh%}PgbPL3esTzzPul6KNYL`h-R_SMD|moJos*>R z$s$lqi-!6k_mGFNG%xNJRpsrX{zNbK4f459CJK1-^cTPlV@c1y_2bzew~sm;5><{0 zw#IkLA^Lkv*ba$`6vgi-Hfq4-tPO4h&1w2_>`w%a+O&T+CjMSX`H&UL;BAQBS?jo_ zvK8$8GcX#JP!00wWWs)rqW*CY?Z529yi*)96FhJiJ(vphzqZ>LmjIv^IUZ*9)&PIF zfAE+(k6m5H{{@Rl2t!#o5o z{_o2Ge*+j7Vn~YCax<@83et5C!kvF0k?*cRPi(AlvIj=0wZQ(;1MEZ+$SBPn;JNB6 z{)+4h)`A3s2^g?+2x-3rFbGdz>^1=Ba7U0oh&r{TxE+m_K z29$y}$h(Ehe{;O}|N1?4w}Y7-tHH%T>PWWxOq>Ad$AF9tma4`}Q6j)5=6527)cu~d zPs!tJNw`sgt}pOQWlcM6Peur$S&!8CI~f%0D06dlOX#Wnct1fIRa5Nwnztc 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

OTYYqj z(CPoE_3MjRv#58M#T2Z&{@J{H%e)IhRJH|_fT7bEKyNbnTur{=^{-{;72F`aJGijY zUqxvMF%hGFbjS-(b3ZR{TkYA`g+CW}@0ih1e77*nG!P@gJ`Q(=77prAC_h&b^zVT$ zV~6X%#sV;v6LI9txtv>=BqDI(a@xi3VxAEJEpuyVKHAuv3WI%!-3IXtaWPl-^=r8d zA0O|}GlD3LPd+6N!Hn#mUr6#27HQ@&fYye2mOB)3s>lGM`sF|p$ReX^eLfSaIr;4J zXp5CqBIrX*H0VS3l%o3JY~3r<>a$rASi@A4c^e>X+BtM^IsVEx;nd8C_N7a693ozU zs*=x?3`7f!E|pr_Fi=qLl-TRPJNbJ~q9r2sMt*XlKn~j@ZFAnbDP|=6LImG}@mz$bkMI!#ofp{}1pXm=1Z9^3D2I)a-l%QM1p3+m{!S z_7Sm1uH%Hkk}sM47A1PkJMhmjN4S;llgpR8TLA&yN`SdDeZ_1?2Ghik+wA9hssA~M zKwlG$iankV_;kq4MuK`%Or3IoEyp6onR*a5$a}k6cAV}k)YnIEVdiOgg zxOiq#D!bSUYkE|{JWlaXTz;#ue(r~;=6v*OO8-d{m02XR9DNwGC>0Hok(h>4PqiOP0wTEPnQ^1Rz@2N4MIrRP{#1qC5MG2 z<`J5nWeNZfl=%Im%+1mMvt7KgkiC`RsTr7e?;kXHl-}J(fPu=WKX;?1VL2R*)02G@ zMd@gMuCWb`W6JtF#QBG=r1B)>1}hTE!$n_bP+8cdvwQX6mEJ28cZX zf7;7wCUR#y59G6u0n}F)$c-SKZ$=|0VVA4apkoS@K3(Un_n#D@Kz1%H=`EGtkGX+KQ{LvITJGp0ejctsA)TF3(No@48mY8a6$x zSKdxjVH@A5+*<=M5RRq3SJJu0a#!+v=#Tqtj?OW*+ji)~3P>iCbCm&$Vb2WBTIvOj2BXNU|`trA&<>gSr*6?7KR7TzP#B*l(VgQ9m6p^h#bG7rR+o( z-iDRUn~dA;kiB?AR3gxmf=U)At(ylCL1Bv15?sd_;4EW2`Gv#Y0KeIG8P3V|k7EjS z@Op1BIoLxl77nXK7;OAESgli3BH{KIIzgjnT_D9y0;Aru-o3v)^~N&arTZ`RGPwt} z5EwV1*Fe8PHxTt{a`{&+RcmP@qOtUoenAQ#kn;IyGZX=2j*haEUC1x-s5_k(j7XPS zFf@`Fx(>cM&N>?r)M&HhnM{!}Csaxczoz>0-UAF~Yj)L&Rme!^Yq36?8f@MF-=sn7 zF$7JO{V9@GRRu*teFzC4HZKSF()P40p?AHxLGI~(t++RfW$fkk81RaNy}wx9oP)<4 zA#y$Myns|Nn#D9dHDY8Cqsxm@pykwcFEfCO+u_qko~~uF`o?P|NkI-frvFrak;wKwPbD{2Fwagdx8TzMv*I&Bu)Q%{vRNv6EMoN3OTC!LUE3WPgHrLHNVf zAfF1#Q1r3rDK{z-E089!B@$?r(4Li-38ct@-PZYe<;7Ism`9r`{t8JJr^t5Yg7E&GF>t=sp*&iagg z0?<^|>CWks{~_1kX38MzYMOTTtM=T3pv8r#MP9ON5`wD+SXaZrjPW+xF^eI=wLEAO1jnmqbp}bHPkJ1n z`kNo}VpNnqg)nr6sM16HNI3C^c3_vijQ{wj0-`0V5I57d5wTB}o}dmIJL|b!s{eNu z(AQYDR<*bCkoH&#vUv=@zoT0(W>r)#7_TAL+_H2_G*pVwt7*5*4YyA}?(pG}bLXAuexjfVEkD&UT`1Q2g%* z>_rHYkSyxkln_v}Ev1>`lnUK1c#2b*6C4-|0v~a39`!NvU%%Uritr{6`}$AMI!KRv zAaL(Wp3sQ&+mXvWC`Uck>eFc$dNAd`A47M zS4jp@YY-fq5 zbdG0QxXz(6!p$pjgH2eq-kLMVFMNDd4|&l{wv)5K4)C86racacj18jlgprY%o`IRf z?-!7cpHDg}#S`o*SHU zGZo?R+^!=4IRhKf&U{Psv8*^yr845-z^yHw7Zr^Ic%ycME0~WHf_u07c$AKGsRqN~ zbSqD*SYw=9`!?>6=QXOpxj;b&`3$f47cY$q+5Laj>x_`AY?)9R#s^3o?rHJpw*siI z>7^7-;wYpi6;i!Pf&vMvJ=t$xo`)I!p~j??g5=;c{7u*QPr>}4p~3N^g#zBwcf5&b zRB43Yllp*h`o}jT17n@99-ZYKp=b+9HL3y~s$AYNly}d`2mWCF`Cb|}*kRV;of37kulB(#^osBnxD4cndmd=}2iX0XHzdf?^y)u~>){7(BIn?CDvBku zb;<2Fw(zS=7(|%Pn~qe310QkGSj|IFhVgp8Q43T9|BvZzV9dp`^sg1cdXf3jsQsK} zF=Vr>=KN-TZ=+q~%@X!lXuR~S916K2~J zQFgS3TEa%8r2*NRlnfi`GG%xywmbC6L9Bch0+iOi*5BUocXvMldPisO$IdE1&s?bByLPdV(lj_mxY0|63I-&k%C> zwZ-BrCOh^jEDvDzF1AH2FDb3|xP^CipTLc{KGlI-q-X#S>%yVvY6(VZ1-aqC#>;X5 z3zFf{d~oJJCU;ZAAh73e3xa05&$o$4vjt-a%+8ylU(mm{4*jhfdes-=sT(pzPr6j# zOvK`%=A@5K{rpV(kK{rLH)3`Ime^JHRZ^J2odFvHYmx%11QH^I2?WBCr}*}QV&j;S z8h@7Aowt@U28sb)xJ9jyMcD+qRFrRuBcfCg z8|;C3x`9o$jH_GicS?CcM-Oa4J!RhSU?FE)eh=&w9)Y9xv)^Kx{5 z>5BjJ)RG^EfYfX(EPyM%^_(*q|6GU7-J3d8XmnzUwb?R8aV9(=xO#X)Ju66v5T=!? zfr$NRR04uPv0z$&JLqgM@%1zC+1%`Tx_k%1kWE@rGuT^jjBhEJOJWqpIi8XNL}j>Q zJ9l$19qr*_D8iXJ*N6-%xz1b<7QREM{-#>1{<3le2b6$pThuc)_Al_2ySzzXp!~tHq|fku@?B zi2MKT5Yk&3J@MRJNCKBWhrW2cp#t~H!?SPhNbHv)++iC45e1<6X6lyH8N(ilbS^qus)Ovgup zJkykIsu%q^6;NyaaDCBk`22ea!JM+z- zzvILLJX1^l9Zk~tHXMM$?mSq>GU7GCo|FyGH7Vx=K0RaoVu}AAbRfPT6@vC6z7{E0 z#$1@%%LfY~&M3>6Vn#}(7@<0pR?A47sr!9YmN_RdOq?y*1C8-n&@J)1SCsjx#0N{{p>P;7v$`PiMkM+G4MjdLg`{id9kW5_jr%ZjHG!-`z!7 zorlkbRZh~L^RYgN0U;@bpdd`M;`IQ5r+a=)C^&}?dP`Ib#*dAZ^p^DLgnVXXqHO5+ zZdcvEH4ETmWV>`zEeRkfK|t#_H(|>Bx>IEa``rlFsrePSsn&ZIDiP5x^I5%j^cNmL z>ACm38b@`Pr;C9Hnj7%x01!ja#aYAw(b%qx;h(uMcxf4LP)k^ew4@-;$mh*YZm4}? z`u8~phZq;|TN4`BMWw=i463qR1?bgp3#yBd-+yj@nl(O^+n494wq0V1s?*Iv81S3C zTiB)lrrKs@vJtD*)O2O0h9b;hWbR;+K24)+bZSy_{Y zcS6T4fv77oB(I0C$(L70PJS{LO9|xuaQrBvaEATD9SFG4T0eZpw>wS`7=wB7Lzv$g zLUf@>s+l3;aCd1!vPfuCDv<^`eJDL!fYw%`z_jVbK=Gw5egfuKt>N=c2mR=>zfO}| z6$q0Dli$fPmPUu<2+iSpBIs;{m4jy?o~ch`?&zWzf>!$2f<6K50H^A1%E$Di19c2j z6FAImowh+GHoHBa`@v4=-&;we{L$=$7tW}UXdFfx{j&2w&FoKSE<|Q6;$kYWQ38Q0 z_%tXqC8|GarU?F7!GX#l*#@_dsj4! zkc!Z##xcr)5$C0{_H*lv7{5-h0(EaJRtyZ5>k~59b|<0RsH9HNP#rZt#%uj8jl>j3 z&b0f(k+m)0LQ%$AQgXXdPnK!4#I4R!$~lPv182=5aLF7<84~5P#6bdvWAu-mhFJ=x z{~Hqj2$NLnK`)xuf}k_=kv?ROA66{XNsjyq)`rbLQ(=d{&r2>pFh#Zr?z8hB_h+*n zg@cq%S-^*m{=w>~UNX%;!5}JyZDc^+*g&6r9~b`hL%&f4jE3{x=N|VZ>c%U!0^iDJ zg?NCfsOgK7bbIl%Y+{a6H3D1)m;4X_#|h;RMHm&d4+{b>?>byzeNl2Q!6lD4-74b| zx+q{ONq2XF7+nUF_rpQO;CH%%M_hE#6wB2mqs)8+T$_KB zFEgE?#5B}yo&ULq5EgKI9DAwTQia0&oQAplzh40Sgq95nbERTJ9um*~uHZu#d%7KZ z$EHgz=?3K3rYfdv-m?XT{h~=-g{5yEW)$TqDxX(X{%9 zovr5^M_x~DKJOV|nVGiJtLS{Qf?3qOcCQ_`pF+6jmNBqcRo|>oY(!-ox`k04q)8g} z)0_n493=E(P@keBa^rL1bFlOmp4ih8#^EfBO70guW|vAg;gak^;$}b-Qc&peM6lK| zTFUO)^7U`l&z~EJ5p!i=4_;pEp;P)v-?bZA#^3bQ7NJ@OmIOurniIZB{xzwmP4Kh1 zo#-L(QvpK|nBf9IOIM*IdKO-9FT&T%2z=P+$o$E(JNFNU%l0;Wuxe zpq@}6H<>2(0u;>7Hb3gfdz_BNwiSSyT*RYUx!e8@@D<3_BCayjS1oywFusrivY^=Bj$Mi1&~~N{2O*tixWPhL%Aar}**oV{j}uI4Lu31}ls@S>OxI zzYWDtgB>bX3;yViT+|=$coqZC24hi12%IT7`85(qximfQ2;1jvuz|H*QF{90HfQZc zUf=8%42^O+_&45*G^K=)H$zWPM*=yZ=btADsu>vH*_(bo&Do5wWg3y=uz!j_q9WJ> zW(|C8*Iu6;8_}ao82S+^^0J!;dv0|M!@Cc9E;-k}m+OiAsQDCySk)+(C6D7HG`xSG z*xPUTEd@Wi1-?>jNY8@pc^|aJ-p>B77>IF4+W#s@k&FA-PBZzQVj&q=ifJn5yH`k*^B18E%-v8trJdoaw4}LigF3?Hh{rS}kl@1%%@}}8YKbFJ> z$;1H3gB73%nw_$yAfGlK%sB*}@w(4WUG2$|XF?8ndOno8RR8?4fYP`Sn+Qmt;fsM> z4jT@681=a(gB+a@6K?U7lR@6+oueJtYr!|c)u>sFkS0x;?AKw4i5jq2Eox>)i|o($ z)L7qZb(zPPoh)K{u@nqzm5_qPg@5PJhD7fF?6Tgq;Hb9|H&Rf&iKDP=^SZBgeG%oF zKn;>$?yQ5v{zYN`ePGFGp^!{P93C%qhE$VP5yfjB(}(D)aT(1@-{lV=fm zPYT8vJ+KnLK9`C8kwhr`=ttR8{>;lZ^27b5Pb%6DpH$mpMc4(rKG}w5n+DAg z_gluHv>T^j6NXB#9Szh;A91fzxwdws6i6zuDlG2|6pJ2s(gMc{27+Ug>lr?Nu59vS z)(i!q0oYp0;y>6G<%TxWB6=oJy!%B4Ff)VU;lWY^dT+MV^N;osyQ!uo8d?4XcYUsL z)pc_QnXXW3#MJMv4u6OITj`^?iz7F?8EpLrW%-R(9u8%W&@vn`&40)KA2E}^D9FrV zva+gH5B##w`rYueKFl;Z0=z3HadsW`9I071zc($=jMb?FkO2!>84;2UXsh(d1p~|K z!v;qsS}7Mt1li&Q+Db`G#~WOOZ(g^TynpufH6p$k=%Hj0f|#>qDa0OVs#vHp33z^w_MUZ^t>$6$m`xi6YA3iS;E2>^Psv%}r*7D@)=FTbbpve?c& zyd8yVyl2#R?BRGQavoMi}w8>#+QReT5r2GD9V zPFKJLCY=3_Y-9ms6nKR=cn3^eu}iP}^PWt|Wv1$r;le+b<5tyrqDf>dqSpLORWx?$ zQFFc@j!EhT;WiC+cjl|?zjRtl`BioGO_Twsu5M&65ZO=MK~?279jmn(gH$EAF7iemfaGtC?2T>T_59$ zWf3@9Kt?UvycB+S<;n5I`Mxt4TN&NVXP50o5evYa9zA;YE9uMD^%6>b9kQ@6QY$ag z3$(+nFRR-bg2*FpHdk7|y%)I^aDz)t#hos~*rAmP=kMZVl-A}KVMB>2XS(JPl1lYg znKuK1YRXSK6GCXzP&15h0p6&*(`0tPng&5x+#W9Q4-bA5uD<-G2Mv*~+EH`IcQ@^I zdvj}%85}@O5XR8SJ4661GJ)btKOI#BS1i{I6RJ!0DlU% zXSzJktb&&H`dW^}nXDGXv09j+=1%lOJ4`w|MnXms0ATMSC5&NsZ=A)E_$m8HKbf)O zAmd|4S7ryrv{*l}a{gzv_rt$TM-s-ME`XuIN0v@D2=AqtjFz(&`me_pR3Of}vbEr0 zdLH!Xstb{v;^V*9-RDbjl?($wwtPW;`8M8rUO&9FeijNM#F2johx=FHT3mzAiQ^zW zh!u&K4(Ouu_wtQW7Qw_UVPw9~=$OaB9E%;T6ZQ+L$Va55MT+$#g*+KuR&M9Owee=G zW*~U)!pyys&X8asP`s{2pd-tzBg=ouAEQI+l8e07IK*y0&FVIM?bs@d;MnwPYrIw2 z^T#)Rl0E@)rOmhRSL}C+B{=J2DUgW>|NTT~cQ(Zt@;Q!}YZkfsQk-9sWZ3Uce`?%H z3#j`2qi!G9`e&s2fUwoC6>t8Lioma#ogYy!++Pfl?g1E|xR_UON8LephtP9t-v)b+ zvw0%X)2*Dw+L^%0?r!FLeN81lgq)a%Psdosj#fAXZNy^B%_9N}9y2>7Z6(*sqM(xT z;BqlCPMngxU&kzwWoCr@=R^1#l~pL(yAZ2IM(GkdiV4s95XprsLB*wllt;m{{4Kvv z^z18pmHMSfGegD!cliE;r>mVAZma7*K8bd?(#dHmy8Qept;<%}c42NS5O&wf(yq8N z2hE)wA{)Hf7a5!=rFkKuGUhhulVZ%9;opjbiHUw7E?yi+G;7VS!I*iDEw9IGas;< zWF!r_gY89C)HLT$ts683Ed1h-E|tj6xsk}6f@?VOMwz3l-<)puD*EC+Aqx9qJe+U5 z@@(5Uk6o(d6uG_SP+ZMbm@4ikPq)QBtUDN7*}j3L&)S^1$G4jNJ8YBtBIY!CaqP;FT66!~EsLs#F|OIgB0OQkvPAgvnXc?? z-qqRJYS9*FDjo(g)G>;$#bmG@`Q-XwN!x&r1QMR6!>P=Nh8E1HZ_JX5&drolcp12% z&>R6WH7V2|I{)W|3xen%MF$mO)hl4beZBTj8;+Jf?alS6R@JDc(da-L126eBG%_NC zP;p10{qYlZH z$V2vANw+&EEH1INQXkFmcK`bW`TGwKh2@62oAI+6Y3!p8%*N!Doc{N_8}lGC_TW3RErZIgcQymenY#+8wqL`%5@=Wka%BfE zdM-G=SW3n6n8z>V1jokHiND-zbjR^D9j)_HlSBPM`hT803PglgKOuamWU!HC852HM zX-2c5ILdQ{Bc2m&inZKb?;O5#bn32quAKY~`RqNpuAB#OJ>=`SkC zaNk~FGR1ERRK?phtYb)Ruy8%>(R_1PsppTj0PXNO_<0VyX_{D6w>09Qxr_SjvX|9yQASs8334_TqmcT^-R9l9Ge(7On_EU~rm;v6f( zH1_?jv7!^QsyZu{JqA(2L@YR$Qqp6`#cwL_i5+);$WBYOrMRv8a-7{9a^MdhnVtMyg7 z%fN$Fj#JjRuYA+m;wqqCeeYH6CG=meCo1u+bYTq=W_FPP3M8A%3Oo-4(DOmAs&FW( z+SE-llk8NHtPCrb_hQMwtp~3<{XS5&(H=Q#R3TB{;2a@;*pW2pxw+ci0cSn~)EKLe z97f~yI`4nK(bTf+uHT@K}p8(|6IB3sD(&` z>_#LSdG$vf@=6K~Crb_WTL$IbE1J@MJ6ESJr}p3R!XRJlU|~|BUer zkB`jMrhXV^6|EpAZr8H^w2uEz(^Cf{F^max_V@s=dC?s?tgz0m8VpGYObX|EIb|m) z_o{cZKBQxHJGKPw8RV5UHAQ};xhO`ZdcoVbX_K)}cAF+wuxQ@4tE5FfYOBqT7Fr8<1Q!LL+l<80wh?9- z=KsztaHU3@LDdJ7E5qbDMZY5a~(fyXO27?l*G=gh!q zdG-8|AFdtBzFwAe1-}Jj6Rccsn#`rzuQAPeHnSVUqIX+4qDQ0Rv)|iMU}Z~48AEUT z5`FeP{I<0+jcYL}8r(1{NgTUUBXWuFCGW(rCvU~AaENY*keXCOB8oI*>)V4;4zkfP zg-=yrI}?g8o0%1t`?0oh|M!)nX&}KKx}R0lF;OMD;T`bAce|)$55nS;B&=~R9J_O< z#}qe56h|8t6$ZoAlfQU5z88rjn-I}fHlpk{2qFkHC8vaoy01%D%CT8|if!R-O(J-B zKA^hKP-}S8@w1UN_L-^^n=*H0D|WPuGqGskIUj7{rr0IF<05QvTdX28-~qUqKzmM5hK~=Ap~wiclU99T^UpV<|o)MK**LA88V;P^(@%=sVDDVd+&OKkja&V2*3A6U}C#b^_KW@9*FFC(2OG+f!)RH z%{g}eBwFS(iB>4$_m-@OMd^>mlcQ94phJ56E-T3t-G$tWsZQlhGwP&R!+!+TR7{& z9#pUHBXMUv`2&}-x8FQBda*LoAEE`A2UE>|Xz1(^aE&^p-l3PV>Kpyo{rf|fqWmSm*Q&P{TJ^3PbT?+ zM3JC-*l+>qKdd_IBL%YK>bmh6B=RGd%bj`FnEpCc`Zl45D zB!xi#bWP2^3@m%FTpV+oo{WWsE&FayfBcgzyH=-k^LGa|XE7pA`VTfZms<_lNxX+w z6XZuCJhPM=UsC1s&LWcU>60(ShrnNYe^_vSIPGOh(slQJI|*kj<)Th1gS|2x<61^z8CX-JoLqmC|!X4bEnL94rZ?avYGk+F4)!h5K^R7T$fu4 zH9Y%@*|G8J9z4T-B3O^6*?Qzk-wGCnW@cZ$_MOM(NcZ6B3=(Sg+?IFUq5NT&;k%;z z-Bao1dHNCUN(JFUEc~OPf)LC`tjtDt2z37jhh;he)C#8hr=67qxucri;FL0xUfdu$ z()=Y%=7e9~RZlYSez{=PtJj$$toT%K)SN0y#kiikpr*Eq0WWzE?l#Q1f3{#IMpWp9esZ>3iA1AT>5}Bh9k6;I6@@pD_xtnhMVrLP zg~g*_2^Fi3cbsGsLeF1i9=3;vy6<9&Fhb6Wt5d{jCYvhrmPqia?%ah{b`desZ|;Hwf0 zh!l6^u8EY~PgNy}9jhunA$8BsP`^j?d*bLxOBN_s0v{v6tKEU2^YA>g*$*2W;dN-Y zykk(Ra`uM)I}jU|#qfI;0{dX&w0itLY3+{eFdPE&j+(K$`MNLq;y^hb?e-IjZh_9Z ziaYA9v=9tS_k()Z!n9}gH_S_lIBRylu(?jXxbw?6(kxTbt0&Z|S_O1Qp(9?FSn!oPj*|IjtbC91tI@Gs=hm_$#wZU3B5^|E+B_qrAn`YB27_2 zL8|m#q(cHyq+>yPlMYIiUJ^h+=~4uwB!E;Yp$7=b_j2xcf9IaHviOsgywCg0?Af#T z%mUmp`i+OL`cCZB*fOT++v|6^f1Y*jj2 zFth^ps4V3(MB&4>V>OEN6meV6MTP3{*=Rix_Wliv2#?*%J|2|#n{vW~lEHYrt_&_P zKJlg2>h*u|4$WLCa<)PY-?D|Y?;wqZ6g3_a_X&8IzXl$PnihxO`w)o-KdsCEwym1tgSIN_XcK$L zAOOOrq|8))(WMIy;L5)rhtQ|bmw?{&L-Z51=df|(g;}{5NBmcp^(_tf`D`HtCb0O@ zaH$xWts?9dTexy1Y$qN_PGB}ZunUy%Ndo9PHPITJdgun_&<2IxPHI9|`R2_OiL0Zr zo{wp+j`~bEM#A!|DgNMDM&3@eh@mnO8NN`9VVx`ldhv3~eg1MpBmsJHtG)9~-M90I zUea_e=45M1Q&cs({DiKmy_u+Y4xa3r;jI>bS>1gvBE}~TSf3frh;hI42O#VfkYyQa z3u_;==(602aSrh9D-3|1U8FaLvdVHSL;^h0%EnpYZRl!9!s{Z+yRipP2Maa7UkkXX zYTCd5&QhYI*LTQ6nRD=$c+V9_^!h>@RjeyMD~ySE5J{SM^5L=d)F#8&YTfS3c>my^ zunF9VL>fJN>%)(- zyYU)QB|YMghZs!V*I0pNW9_7$F3Fgm$wb`8Yxa zgvoLEs0$*DFZ2lu=F3g=y+D-hp~2~o97K#ii1R(+qWHCT6T*Nd8ngizwG)EK3WUjd zgEhTDuj;|o4q#$C2(i6Hf5MW}-gQ~K4DaYC;LcA3367)*?qKW;m>Efwj?D%H6kvO5 z$QqnN1jk@1BiVwqZm=X=&bi7#m)Ga}$c@NQsU|4}&$W0#W*B5cWhB07->+HxC3gFc zl8iHv5M_4U3cMW_>+q{E9{(cJ)In*sd+004)^W^~i{eA%g%D&Sozup!LMb*C0aPM2 zTPUFftV59~%0r<*aQE!&#w4rR;B~YKu2WLMa4+HX1KfcEgIC6hH#4mPn4$g2SFLHB z72fLo@Wbmn_rEj|Vd1i8j8bMJUn68GYT7To%@FwKJ$q>H;L^O&L z)$$|t^Ou>w@`3gp;p@jXEdS*K{t2F3yQD>AjWG6*z0mCEo$P5R+jllLCqO2$S`mHZq-qrd zjMisSciyrFK2k$7GGwagcjLe93&>WHW-F4j&(m}fu`jT5kBszV<;J9F^DT$A8u?+8u~$+_-B3`D(%-H9?d$+rm~q@UTD+f_h?*u_ZB|A3<0cCG*5} z<})tzO9CrX{li78$Sr*3>$0e=J7Dtf$XgM|O^H^QqJV(4*lK}vilj)c`{dIS1VgP0 z!b^3ZFRdvc=Lr%dr#a?`F{=-(-jZ(IU+(SPvee(*){o1o!)c`2E2Ar=Zdk@Ne%m3D2KOf68E~Kyk7#+Wt|4C*b4a)~g=(h=ZE_Nk1*F^|$_{sEN?5D*m2^ zA~PrNh?I%>X*H=@=n96uv&_oJHJ_KsHawK)090E5$bTVgo#?Y zbsY^Srav?2ReZNxkMTDhUewA zB5K`aEy>RdbaPW@{!ftyy1u0t@YU z9nk?LTXeUNec?u3u|TIE)j{YXBl&zV=?DDr)46VZ9||SSaAZ z(_MCx&urF`Q9~C$A&O}r{3X~GLzRCgfsqF^$Bqp~aK3_Vh9+>ewb44Nf)m_G5}fS` z|D3XP1&Q)tsz4zRuhE)RuoYk~dOh75g4B6LMycCVnO<|5j*?RjP*O@q9EWSys0h6J z{!SP>pS(wD$%tQFK=_{$sD6R;y@c>75$SD`4TUhG2F{~6A@^elDz(9X!iZ({i5f?U z{6vXP@NR@#+GSyXDHNQ8XY3C~S-TWcBSzS?ACUh9M&3X#k?QXRmvBTygb(c?8V8_^ zc!>M}JI^LCdj`LJn&Ml`PgJwiz|aB1)?U|^21`dhz?O1N;_UcC8>NVU8i&? zZMEEfAOKuNgFJWwzqXj>CQb^yoGd(cI7DJff5iOj3?Dvu8@khq3s>Vg{5ToRZ}gds z=KaaD%n-6@#Om0fX|SWHwg+=!+B!TzdxdRjH*Z%v@sz8!}cIt-A zd2&_vb6S(`+blP7k9I2L#%FZHSdLxcD@|1m@JyO*9Et z_J~{`g-D;N;O_BgLD?~9KiEhxv9x2q){q}QBJue31@Xn}yOGtZ zKLV-BVgLu5*%P!~XRC6dTF1i*&1k)f&n-}sVdiF^-FJVq*dos8)d;)JiMWpF5dLIB zg7dd?B7KOFp#lVV-w}Ls+%;@egw3ahN4yNTIVC7IgRCloAGbnef+7BJ@_Co@Q36ha zVL0J4D77z!F~FA({Tq@$M~;&3Y3WBI4*!HR$%2&SAmZ+_yOyY8QN&<0RD5E+1yj?s zi4XN7sAk56QsGUgLDd|RyAor20%OSeSKzf9?at!?i-&)PWIklL3=`bQApRm33O^Pw zPFvEh+V|^Ts>pn;CdrFR)0_KjpA~L$ca$mRWWOIY7=i0m4!>7q#o&o~jB^8;(ef_JuDoDLTJAZa~fwm{Ps6V`Kj0}KT! zt$d&aD3fS~3?vH0_!-YF?G$_10=2-*OJ}BQIf{|*m<;;@h~V(mFXU#`qebNBC)@2BJETCkQgxOw`s!SaKDC>WYRUd^(!VZSURvh$boGr^VHWj2j*HADEy9|~WI!^e!~BJrJIf_-t5W0Uf@ z#NxfLKcf>pH+qsiaeFUMDsW>y>j@xUk#XT?INaUmowvP}Cg`GKPPBf|hCT?w+^FD{=FwXV>9RHYQprvf386F1eu(6q#6O->FQs)>!XzPY^AretS8jke1f&a{ z99k_CXm*&S*1J36-A?~P|1n;2kD&de7r>gIg`re?H%6ZbN^*bbkMNp54DhCgL0XX( z5;`l*AKza{P1JVyPFO^X4rTphJJB_*iq65s?X!u!km@1z+-Rmty-=K(JlwkkD`Xaz z7^PG?3UU=?xkWnHlMwh&T2`4CXSoon`5+-{tl@*^JVt-v1NR`l^T~m@;F_pJhCWdY z2&McH^foE@KK_i%)p$laMt|w|@~BlcYE@{lSKF%P1+&^|8tvB1F2&5nLva}hf98+I zoR-r;IQRpUZ5ODAo-h|62j0MTw7V>x-w)pq;wgG|OrQoXU-_9pwDA*1I&? z2PeP}OYTz7Xxt;`YpIw$nqbCzA>DVUh83!1-Ij9~T-!v^GVQM6)fwwNK8rDdd+EWa z#IE7ud;1B~dX^gm7mD4up0M7|zOZkd-@^uRSX+{r&^%ib~Mu~ccDR=PDu_NeYgVd5F#s^R^PkFcG% z^g=Ibw_UHO1XTJ6DZijvSSgH*O{ad{W1Yk`jXIyqx9X=KyNTPJn4FsWVO9%Fj@==0 zBng$#z0N%gZtYsTeVfgy?bDj${GX?w`5P<^baVglA#xnj4u~LtP{#gby=tbZ?oyJ!Jpp)X90~0=~0Lq$@)( z&`2Qk66~D59>j5&;BJ*$f0`9MJ`|G7DLGnw0%zuK3XJy6@Q7R-1e3!pF(i#@9qW8E z8pmn4S7}Ba(@ALgFtf5wk^nnS8uc3Y-B+Tfa;F7(`%-)HYnNpgLivC45G!l)Q$Lgr ze`T)Ur#Q@Zv&o&N($6bLQa2ACl{*^}Fe(uEXCepa62mq82?8rY^1FnROvIAE3C6Qz zW7795TC>%Dv8!de_GRoDJNNLUp6wP5fe5qq?s+QA#M1~Sb5+BvV0GkTkB$yWL}KX+ zpf^)qlWwqOy>@@{$z*DDK5Q`V*yb@x2C^>pyswbwLxC22b3?^XCMj1TUYr2gquQ06 zUyG+B>}uDNpFOkH)mO%SOWPww>-@ZTzq?-|(z-i|45fRHapW_}biG{%wv|m%uRiM- z^9<3PmpKu8bXia#iV|0?Q@WzPn?7^`3?rvJl(1ARxBJd+EW;~ZGy8)Itv)(O<4;tJ z701_0r1K!Vb}he`cmaO#asOlHxd0K1@`E^0)M%O*r)2R^s)jCT~u6vQ#|hM7r7T&4=_~Ar;Ek4zw2CM>z=+txBj}-yz=R5(r?6 zVHkd?a)Woh$R>!kEKz5Ns`<>xo#$4jq9`e;@3?zJsKRj=k5)aA14BMb_;{PIJ!J8E z?iuOuJVZ*|`sodx^pAsXNqw^#l2T5b<**^WWyS7WJPZ^&d5ry>!>^tn$J_X{yq$T} zN+|nYC@Lq1vunTB)+?j(+%Q{hhIcw7lG+8&P;l3BL*!smKH}r>NulXrsKBB()4}ze z#3LOQbrd!iork0R6l=&D-r_Z1ICI^-gVLzx&IbmAQMrf>RaNPT--d}bGo(h%kfW`J zhd_be`O4FSghm}R+TNJ*lhoC%>oaZpOvHS7&9Ky*h6T^|YQ-FtHpo?xhhfobURB%l zAeok7;(@8Q14W9Uo@|nabd`4T*iS#(M0B8Db0Lj?1Fg}-Y_~HH=BW%_zAYPjQv}4F zyiM>(RwHAz?}1g*_I;T9=40*_Q3<*3cDTS@PG`bR-@J^%r8*$e#4xcrP#_bnVju`r zRS1w?N{q&?&$EHKqaqn@j+S~N(W{G7zK!TSU66K-(ljZ|34!2W6EBEF=qyKUN z!p=&>!M##Z6~K2*kV4)ge+u0q4KOEJ@u2o3lFa$cK7O6qCHu4VI5G1CYd#%WR0CFJ zu8LQpHOgx^{K2r5;-cnBN4f&JeT#|QAH4!)IJK){V9K;_Px1iAi;)Y7{&~(tsm1$? zE4^#EK2b*x4>^_^W~*nWXQ*f8W<6%}X5mgBu$G6RGm;cDvP?6bpqUnCRK=09Hdl*> zjGE6Xi5$qhBg~h=(^b*a23Rv4{?1pMH1O>RX(UGhx;(VT#B5HuN{~whlwG3enADeZ ztJ@}7k#cNEKB~V_(Yd?t$F{tQ=IuSw(tC?;*)+y#|Oa17h4W9#x+tWYZmQi~;lDGvKrFY~C$9Ey|yBV9dm)v1OLKa~gaHJ)E{l)JW} zL=@0{wCV5xqWqJsAm}g1aat3&Ll0WgU@n%u!hSAXB0g6+rO&zRa=u+z0uOrfiOo6) zZ>t8KU?NHHyxVm#xTd&|MIxl2;OA@Ue{l9yc>1vcrO~WRhr`RY*0X(m9{NBAFh?VP31zH{| z-W3G0qWCEaG-QC{{3{!oo)Wy-?Y5)l5-`hMf~f%EJ4(I1syrYGWS$+ z_hMCLP6@iYC;V#dY%ozICMGPH)B}4=T;iS;cg2p*MA_c<*-Q{cQELtnll4k60A#fX z@>tgXOS*N|y5%o-!6$yEu`)S7QKckd8pw)JFKE}r8#yf+Y<6O0Vl`OwW#-71*p>a=cXlajhXV4kd)60Pel_)PE;js7G=`Mt=UE1gzNglN>ag!&d zgH%PM)GtH`Ux*RE5G8vd?iSf+7jDiNuJkxOL<^$^Q|_OZN2jwyA^i&ic@omo2@ zeXbI7zaUMIs(bs7x#_w8BA@o?f?GtIHHlB~Wzyld2gjzE3fvRz8Dx++xr|ceP_Y3IP-9_;qPq?# z;@i*6msqR_Mbx=#^aYH^dZ&^ZyzK{@WXT`9Bwuj?=}^};rtoLJ%Hf&f<}XJH7~W0= zTq|3=tdNSRcV2)wk0P!YJxG7jAiUl2zB0WfcEAwAQ0JxCr>lMYj>*0k!Y`RceZH_y z8S;KFWa=ek;HC5k>E=L#oX!e>WL+Hy&0NJzlDv;AcWy{VC@b*Hw-xeQDe`O!+hNAA zzTZoLpifhyMGvcL&JeH5S-K0huGNlq>8TtcrHY@Y7-N)$NYAlY+Z3EHVJQ{>=;O7w z5%Ch#3Td#WxDlA_YC_*A@fPyEeJZ?ts+%L~ePbmg7{586n&4Tr{McbvO5b5(RRYq+ zu3eK+n6OFUAKm6!9PaARY(n0Meg_#7;RT=2XD*b$lEr);aQV=3(LcBLkruP%hFw@V z2!LLEInI z`U7cKZFgu-YwvHz7wf~Vpw>u1kMmR=l*_rp%LxycOk7CX=8C!U@ZQ&jOW646e%tDtg0#jH4ZbQmhxaH+FWy9bYo8B=-0GkDzX#+CgEQv zc+S*i!Fah%j%twsMfHli5(c>wTl^xQDH-xdT)%E?wY@5!i(aZQ1UX`9C zJgdzFW`5?j7?UjSJ-y=ki@yEOjk3KQ7F6vlf|1QX`BjCmdi+?bS&2qVXThr4sm zRO+-ud~vr#?V41#PQ!wHFB7aj0WUi!pJknx;ccgTq`mLj;(`ld$!RC8*xj~K(g^{j zRf_?tVP&_YYh&L?X9e^+kfe_`S>e<(Bc!Ms5|E!HDv->Okf~6JlH)nzRbJ+b!%`Ao zk=v;}qElU6WX`=!Jkct7S2%xl+9YRF@iS%K6CPMhXWNz@(p{N6-$jbG=I`)nM((Yz z8k|0Yc5{eq_1)xfA}oa6%8B zb7MDv(@l>41UHdvY~&i5W;m7+rav+3Yd9hkI!`JqsJ2#N*;+*b-Zj$?myF8u9cAVg zxF8#LpZF^Gyva_jX%$iLtG%JuC3Qi6+7agdvYF=Bb**YD{BF$^kzMJ_Q1>g?!$S>& zo6|<0FY`t2bzO?1=@y`%3!&%YGB2KQB{knIj<-px62~%;{lR*cu|;{> zEWKg(-Yt)QmUO%2W}`UkCs_-pk6z-577g^zw%Sc>`M&@vss{v6Cj|yao2v+V@nN&A z1o>0$^2~jrfA-WV6DuavrbO~+kJu`YXlL)gdG<^-_^U3#q{v+cVzL|JquiqB= zp33=FZux5ZT$Ycy40*Xs2lO7^D&0%^D_6ELVGTqj8xqs+ZPW zA(A2`$MzY$Uz>DIs&JW=b=xH0wjYCR+jHHS?sc_wX3BK=+a63Wma7z?K1IvXX%vLm zYK4(rZ`{=289|syj$P6RP9+VuIgbqVi^@e1oq_oa8?K*7*A$wn82T0vq_mPo<5 z5*X0cSuE`a4PVr=S8IGE{cV`WDH`_$0fLbasQLkx-W0kgB@9-DX z;CHPA9={>PEb5bgGQz))Ld=h$XY4dvb&X6^1BW^UHMWiN0JUQ-IyX*gD_B!(m8W4z z9wkUwsrm9;8`ZBFfFY!K{@CaTy*jL&;S0;|owIf=uvh&Gg@%LB2gA zMG4uHPd?02AS~^P3f1x4h^NI=dr7%|GUZ?!c-&Ru+)!JMrvduq#05;6w0^vl_84|x z-(7QlchsBJv-Jl7yX*}3V9~g0%j9L@4?O*d@Vk#|Z(<)7N2Epoj0|bK9;Ua2%xuQ4 zgj89Fq9%cW8!Cj2*l0;pkX^OvEs@bqtFy~*tmqjv6F4vQisp$%a z>og<1L~*-Ea(r@G3E(LbRJaE93?z*Fv?U0MoaY>zzh&~^AP%v^n_meOrvImEd#y&t z`Y-{jN=N?7$3GITboS!byQ>%y{Z`Vr4X`3sPDC`Xpt5#^HlVSMFkHO}P!PtH4VV z+4lmmpHCNKdh)`e?<@LqZ+~<;P2FH*c{p7WWxfZGv2l4)h*ACXz@|r405kXEMOYmL zq~0Nhk=e0?>n$={({+JAl?UQwpvH9N==#qmd`?TPFk+7IS=zmJ!V!!1gsXZ5+fj4dxTQi2AXi!Pb~KT87MU&$g9 zPv@NYIZ6@UMqdmUc56E*94s6h62sUp$YIvMspfkEm%}4cyGpuc3{c`ymeY!%Ea#j= zHw5NOvwnpL5l=Bk@DNFd5*!%NTM#=C5l5!mUbh;rM-)(L&v+0nKbUPTmEUk41&wAMl9cPe zsIpB-%W`kh2oqwq*MEwr?0O@6Ztr6atPruU#kd5Xh<(=h4^2Qv4VHR#S1I+e{vFg4 zQcu@mqKkq)O{V8h`6x=Nk;xA)9r9nmS<>q_Q%p{VZYLG18``>|>A%^rfK|=$&1sCM zJi)`+?`mPf++IOFlnG-sMxKoLxJzO5Y{lpp(9oWfh6Au@KQ|Asp53{;6*}kZ*`UL0 z{)EQ8X48MQb0u&Z;Wq$xdV+NC{-FAC^zU+=~vt$qxMLC6weTRi^~lirH}^}{qj zP^wR>wgxq_&DR`cMq+?LnaCQ!1{%(!M*hFr6XKR=_iBbU4M9GQt`k18x2QTOBC_ZASsI z?U`6jPG|&vYpRLplHtUo!ie_b{G@kwb+Ak6^%m!xT_i@(u^Zo;E6{G$Df+uKA3k$n z_w81`>cbC$1nEi-7~gCx3&NH=4!!sj5L*Y8I(m7pwH)sl#8wqlcz)zF+P{TL?Vz1s z;>Rb^IIP>^(q&9rw7A^KHUe3iLLRqXUH88v0fXV)7jk-29bS;Ms>tR2H-iuIM;YGV zP+}@39#-#CF?KV?Rr+xpGJM;=_QU&w+4p60U zrWAJX)hi1Qf9;}R8@94a&VYS$)8UP#2Eq+1{^17iwrV}+yCywtWK`$_sClSS4RrmR z;sg5^ES`+eb(Wl4t~(N+yJL?~(1UL(IQq>sA==MB_a)fy7 zJw9a{@p~7+E#-Ie+c#JQR(bkrIKB3~9+dj$f|KMttMOf#N|NhVAukP;N{@}G5jLUmTY?yfKa1?>2(kra_4#LERlCTb zy$--=w%+`ue~18+`zJC{t?%G8hVeb*%Enbit*W*!Q>lJ~^;%5g@z>jHwm9a;W*PsvOjmzVC$uRL#E$J};a-C9=lvre9|70rcr={U zq*M@Z53CMRoRG6QZw+nsK~c`jTk+Yup-29BfbAaM_as-TG6~e- z4>_eI*6@kAKO2 zXeD49eBWE8!`TxXOHUiLYd&fBD+0$w_+mJMa%V1355r^$A0fS*<{GUTeN$zvX{xUN zAgus!ZH5=!Q~L~vGygCD+yx9OO|*?-FN4WESE2C*@t*s-j$fE=DEL$4vUbpwj#!%Q zjM6_OHr;r#EP|ujjE=QdPT3m2r5&R<81ho`I-$imQ-C!=ifnK|h*GFG5k9K&N{y4( z>Lx@icH?y)T$b1boyPX~8l#ShRw`#j{>qmmjPG%&X)Cnc+GoF2=0wV^7^Z5crdvd& z_bP5yXt`X{a49Po-ZgL{T-&{5y({qz$OoBn3+KNrFXq%8=WK%%Nwp7{s4m!vsffo<{ zB>UGZp7rFf8w}6D8*Mcn2?s*&NdI~WsrcJ(^M6R~FINb|vZVr(JFnHI31Vyuk*|0A z)LzUN`WQRGK6&cA``#lb9;yPa*Z+Dyu7C{MkGlvI12v(cx6LI@zxE*G~m} zxha#RWy=4=Ur#f#yI@q~^3UGoOtrEM&)d*Vz=13u-;WcBaqHT#*?yM#J>g}Jv32+{ z;lPk1S$3O(QxR#0buqcv0C>kUf#KL!pY|^>!SBRLNqsK|ze|1dU{CW!sz-JrN zy1H03o4@iE{;ku2@YXmGNh~>8dCFS7rgfbNLQLi&x{ha9nO{E(^s&`P%SfHfRz=#Z zh0~XDrW2K&DUF57pnq}akBa3_{FVR07)8wZ%_DlKH%GB{=Xwj^1% z%iD$zh53uvOIeva1DrRcBcN&J zP&kOo@G%btN$#6LQ8d6jTYf99@GLlUQ}yLO!?ircgyWd$m9}H#Qj7fPQdO;g!F-u_ z88*~zlG%*Fycg+XVTi5SCvjD2l-FMMVT5iWCi7vc{+aWR>2U7zC!6trp%gHhtnQ@E z3IFZ0dPAl2G5Bn^>`f(cfI)&74Mj#-WQwE&ZwSX{YE0j)A^nVN=>n*`uF<*`QPZ-lhMQq~&Ey%uEY|o#D8Vrah=pQi z)9Z}ZZ*a`C;RzbZf5SPLws{z^`+8=D|M9-XLvx+jlNVoG&s2Mgksq4#CG{{^fbr#tX%(21 zeEjM%@xX6P?@_RiY-_@^@V)CE{csXP%O%@_*3^U3(pE>QVyZ_l58b6t`L?>)LSYM>Gt5gI8nkDt8%ZcdzY|79QlOVuu{?4cXCs>z`-LiKa6 zbi45d#}~mx;wzu1C`jwUr;k7{Z#fnw_8+IsfALdn1Z*{#LU2646mSZUzWr{zss>j( zDjz4wlg3_uqDF2QVjj>fX4x2ZX}$#g{inLN;rpu@({qC>!KNA8LUK z1CA;8)`ILY^ghw%i>pV8=FZRmr^K9h2asp;!q=C|;*iD^{Lr24TN=1tGwPLBNV%l~ z>-Yyndyj&7p)Kn0!gI6wADeT<%qmdfpSvnxA(8;bWqLFat`!6j{MSuc*&`YIMDM23 z{fYRPo=r*$iCbh6!|dG#cShhPBA2kMhquF)n%zpEmv4?S);-h#fXSvzwW_lt4!%YG z_`ya0ujbRV;2*XI)CuPf3_m$697ZVPjrftIRm+C9d`~1xhZ5SI4wo0PryosI+dyt=eQpmPiy(N);`BmsH+b4f~o5n_Eto-(`rhJR=cX|ZMAt42$at_UdgQnwxJQT*k3?Yyn_H~5^n ze{Yg4v%8k@0g7>S4?pA~}XZW3vb&j%4`XnL*w zzGwccgAEYFcaMk}+8AfloebbasaUP>i16D@Hbftvky#Ou%yr}5`!B|Ry4rl(9VO7{ z%o{h97t;+N)f$LntTNLf$5VK%!Xxq1cEyyy*OAjyTmQdw4%id;QT`X=<-&bT;e#d` zKcM3ZlL5i9otHj}Nz4UGL_6_KH`HUQ(?0%Gtgk(MJxGb8dUOQzEZAjMcNBuIyZCYR zn@}`<9Q?+ZpMkRhTD0;!zpI_9=9Uld1UU5Z4Ix8F*TpPiPsl&q)6W5R`zEHqt=1;X zOq7I!qz=cEB{*JXam zxgDF&2MSKZP%|Zl3R+~f^`9J9ku;HPYsHZ1+S7H&u@TwmFct5Sc<5&FR`M8)vPt@?excG=WCn-&XL$v;Fx|Y`k+w`P#g4ns`RtbFm~18 z(D1+t23%tb+~|XJ+2g;YEF6s(Y;()vs0eoZ&Q(%tznFOq?Y<|loK}f1g_L}puS=tm z_(Corz=O6ILv`Fl^xJu@#{asl*us`ng=M|C_Nn^>q2_w`4Fp=G8!IQ7(XR4&>n)Ek z>)Q9&7$5uolp&T|fpZMo3Jt>ACFjP(MUieLs?ROvGgb|h%$Kd(L#OA`e}-gs#Nh$+ zz%pRFvbC9r#pI8WY=r1IydQjaEj%n-rNPJuh>h}eMp^%slBkdwVc-rAw6j$+A#)Ca z!eI7;Q=JLqjqaI5Tjj@kX*W4KTh`fpg{Oz<{gn+jGNhWDgXIiHEn%V6ozePJ6}CRb z12f~O8{3sQnloqL);UF_a;d+)B~tH=6}pTVTzwRNqO{Ah8)_*{4nV%IH^GemV>aGbM~JJ4WsX$`?l0eAR|=rH zIN9j|7BSNWy{xu`*GRZpC=kqOXI%zSZZx~=3*4Be9t zuf6ZT-0S6HblTA(@JlX)7yn%Q8}j#r>JN`)1wD&;rVB$Y6)S%ek=y9Z6u549LO_W- zcgWoCwNIh7BDl>54EH&Q<5YJQ;~tzjlO5y63*$T=%mLxclsAIGc>a`-SFCdy7${n#H5b(Wq6^bckNGX85hND?rx3X}@+!@glDC;)w4pBj8e-)6Aq_4a|J z@jW15Q>@md9)3Lw(iqLDAI9XJ{Sn`WuIOV;O2uR!m%SD}X0tL$6T6K&93@o_n?L=6 zu7RsUBptMYX+_`0cCCMNoN>f?+2JG31a5mluEt0R-cN-@oi%4bA3-oDY(*7MhJ6~{ zPJlC|rVa!$Fkpoz|Hq0O9>9%a5;xJ$XHpKs-nIx_Wh~Oqx;_gGZ5hdyeVY1wq zgiY4+$^psi&4UgJT z#(7}dZR8p`R9@BmF&DZ6OyLY+ z!nKW<(kJtY59tS%Fg1#`PNsIBSRZh{D^Y%Bh{ut47=HTNvkA-8F0&MaT09y|V|$sH zS9zSpS`gNjJ-h$#=MY9(Ge}Q2i?#CAE6+(iX7kdZH+&ZvGmtc~g84HFN3%~d(Ua^E zvD5}%^pC}X;aSoXc)%Qsez1^?Hr?zuk`J~kBEM@lGXA$(ZC(Tn>`?os)&OFxCc*@> zH@Q6jhQU-qRWVn>hud1)Y9v6YPxa9eH+BZ2Ge&-6Wusfn98ug6?B%zr%YKfrb!~y) zl1&x%$l6EFmP;iivd7-PIF)kIau_%nP5*KIBu3gJS}6~jWK;R%|&Nau`KyP=2yaZf9&h(@uM!p%J3Kp$#=VLLp?@wJf_Aa)_c$nD+-&KiW+Mng#h*=Mm`oqbUi;Bj6AK z&DEole}FT96#(oUSb_STOEU({`A8dSMN(hF;SnII1Ovu&^1Z1r>gp$3ukK%*EX{Ap z+{X4Y&ESk&Tt$qp;}@P&E8cJidM(sFy`p9}#mDPV9&0xu;uzFhqp98;UY;8H&OT&# zepD*DJ$ly1cGtM+8unok!tHE*J;vOUQfG~`U!xm8r(w?YujU_c)A=E9y;;%L*4B;- ztulUli*6b)yQgK(8iZIg!9t=S)E2x%KP%|KJW=<3488r>g>mbb+*WECjZ>+T;at*{ z?{vN_cP6s6YA1sq@K1*+Sz42HerjPxe&C2rlRW*}p`Og+Kd`=kTFkWHhf5p#ooDjk z5LrEj`bsBFrV{m2J0EYF$#kwATF`}D#t9qBAvviB<fY(CN z+#{c#v)QqPIZfOwGvY2P_m}f-S0TM~eMsjA?|mcBgTH>CqX{DCfga1m*ATGAd#2VX z!oi_?m|{aH1@FeAtBCqq9=yu%550)F$wdUx3Vim(iOlaUDVzE8X>P~Ai zdTMrbzxz$>Mv$K}aVcf5KldhDXY0{S_D!OIPU9zf$qZXQcho<;f0`oxT;=^YQwU?- zV+4T8x(fZ3;=-oRtpi>yrH{LkKk+pUFW&iJyAUA{ycF~uXvB?|FY7`a+OH{pKcCJ_y3cr|M}VBbGoSC zlTI^-Sc+Sn_FXwlUbqfJFTd|}T;!EOVS>gt?{q=#`bm(+Ms-zm4#E%C=fuD7~9-KlA~CtltZm^7qMCk9AV z&90P)W}ZWivbTlgtW1sxV*Y+t17FZn%9hqahnuDgUNTqn3x|N|O<^u4Y+*qt{)A=^ z!o})KmE3By(v=KoOTLoFqqUv%h@yQYm4`3pjL6C{BbbLzi}ui|*PgT6q!94`A=)=ksUyDk7^)w;W^zJ%mBs(xn?CyEDKvsi@^IAo$JrO5D$}X?#Z*{>b zya~H-eM>wn;Ff(uW>l;`zs=C4t!p?w%fBPmVNf~V{!ZpBIk;tfEh3Mx>A43mIr1)J z`t=6ysRQq+dJ~;QcA56&^!@`1M1fCCRgC85)erY{A@uz>B|6UsNgJB)&1DK&9G`lK ze908|`Dr3BV1Z{(&XVWwo#D-9(^@kjWB$Kybsbl}_CFqKH6^;&W`eFtvfJ8X9WK*i zh9~*?Ttx;szvxT&HKQq&0_JON`;~BCf6MQ#$K2wkHNh*o)g-LP+VZ1Ex_X$OpBiI- zY*7RB05P=DGAV<4xDP`D?JbKgL;LifCh$;y-~ag7VyG-*t87X3 zwJ5tG5|t(UUY4Z7*vVilEtEl)WDi-hBw4~(Ga_3OSx18*%h<9nzdJ4O-kjmv&#J8DeCyYx_>Qefk0LwO|LlM`)46tqiZj3xD^=hhw#EF10N@x*u2&SRBZe8vw6&gHG06-cuY3 zEOyM_c2e6f^7)=%x-C@H_xxlEXdG%zE;k-Xcd|3-VzNLPXnjhjv&j5f@Gq?UC4F$1?zm`&Hbpx{K1p)a!p$zr8=GDQCxE<-&fuXY)}QhC|kOGjOv zP1$4mu-b(SsTl!^ek<7lp32v9uFU$vI^9~lj1Lr!$juQRLY=NDsB-?wqHG_d+W&V^ zZ1P+;lifyIe8jA7YoDUNfTB%w(~p;Zr!=Xci|prpVJEc{)@rDKu2j9_CL`{3w3a5 zsif!gAAAqauVA?yGL6YImJVKG$xA0&j?XK+ z^F=y+VE;>z1p$i4*d^8<+-qXqC$z;h!*mX(MQ5y;#=SUaoL%41u@|2spO8~D!Gcw! zJNrfTJKT)y(%}tbFR)c!lBP7&|Le>cg4NY=xW-V^H#d*Ss^IRhNHWSa7Z*5~kvfBl z5=+UgFgEw2jl~vxH#Dyor{tYk)fGkFrAnxUns(Qrv7HGJYxWY zZx{&?Nb@BZx|S`8S*phb?0lQ)&005xl+^FbuP+R zmzSSiY<+TUx#t4?jkzyH;V+;uU!?8_(GH69(8zVxUcA@GC13~I`GS;)H{`;Z2Lvg< zJ+UAGGtHmF5qyF-;-T`U%TT~4ur|QYlq{q`8Sk(GTRee%1DlnWGuMhIg-m=H& zq{<&l-~SC@;ttUW7XE9(;UNM`&Gaf6-U~yf?>zHOtg4N0Rc?5_7iv_)yeJb}+ryh% zC$XsgOXKfk$lWL5jasmYXZ%-f?}mT>iL*naP`Cfp-W7@hwp=_GRylT$uAK6^Spp~Q z`0)tfng#;{N8GTSrlr@(@|YV+0gv}5Vd;NCPB30~$-!vDYrGc}BNev%?O;w=Wi~fKmAmCj=V&^I@nu%9h@< z*7W*+2P+tYU^XHMCgg3f@CQttF zL`Cy}eoGC6bB4v1#}0PJ50-X_^>&LR)fvzRG^YZuq-2jkZpiPCGr-8({~Z=YM?+Io zg1Bsg_TJ`3$azew=U;oNIB_xZxZmy?a<%ikV%r~>n4XLTUj3eFo*SfA6-`z21^0ix z4E);Z4DUamB>jf(7P)|K+FF6lgTu#ao6O$a{6XtF`mSH()t{Fiq+C1sH#~ucfzf1Y zi`0|(b%Uq;WN)?q+zi|mK;{`#5T7G`xw^!oyn=!>LIyc&Eu6~8LHsk#-=E_km;XDo zaJ7>F(hhSMFr@#rwzDt6o&Tb4zzg6&vn0&CQ8NgP{qG+`U{e)*(E8SkSni9TZ(no$}bDkTOGOexQz}Tcki?H-4RF_-|gwqE0M}~1ylfx zI`ePTA=va(M#%5+{W;J~@?{oGZ+3&9}@#;^vIWy{G#C3!sinsza9D4 z2p++k1fuz`9xu^OQ}p{Ja^*fmm`patZ)MW0_MaKO3g`akad@MnNe#wMjvndP_<0i^ zAx1rm{~0+lvf>#Dy$n=t&dvsLV{`WFJ1-G>E|aW_NB%w|goox|m&_ntQg!sG(Zyev zoMq6v{6CkZAX`k`+#;m=7iiN#7KYw7$BURCHkWQFZqH<;m4je#ZE@dK5sna4um5Rl4UnLBkB+KhhYaJ z9^EHRiY?Ph6@Nc3?Mp*RNePPbU#kdd{)+J!f#gl#^R{{E7R94gB<;4zox1S zI`h|)@J26@W^!T_)_>{Of4>Hi+h5Q1j|&6xE@iT}0CN8G8zvsoDs4#46#nJN_*}>f z>i)G#|9GxP@J|96tbgqRuEs}lHNGZ>0>5A53f${|B;$Y5^Gn;5pVfX_2zbv+(O`p( z=5c*2{0mEt_J!t6|6P*6(f|3Qm5wt1_q@b{dHIIJaDeVg-O4+nV8r7xte%NmEw#XO z`DWx>&ov9ABhP$&D_3!>Rl{%eo!4dKJxOu)f{#@}W35%`{7a7Y+u9==@td~0pAB>; zma*IetA^u^l%v#ZqF3K_XKdbEf4}aix*nI_)Y~t(V z{xkl`sBK7k7BcPo4)@M@kW{2n&b@?IuFF3bBJZ41rX z_kKF1dux|^#4jg^3hHO7hZNgInchs4xns;MW-e{^`wcr1#QK!Nr!ZV>8$#93j`am5 zjmYKiDScb;V`XQ5By3dBGW^4TMvi}7YKG!o!n&SHxT?x5W0aq~_xvO0;fm7{JJMd? zKPR6k{r*Z;pr_2?GIIT^BNW5^7Hs4-K17|RA5S_R)c~pway+zs|McJ_n~6iJq94=7 zcu@V~WySsd_x*KOwWltwb0x@y4rkv_j}Lj#{$z*Xe0V2++nxMi;d1+U{N1l*FAx(=-$TELLf_HPm>u7! zH__UX^HCTwe&^`~(Cc%mK~Wq>f!D%RlNEMFc#wa6iyl>ziQ<7IxRN ztq!n_J~(nB;qFkf>2Y~*7>|EVYs)p8{7dCdNU&0C27IW&MbA`N9<+Y;A^OV5u=6xh*2UHkODBk*m1@Q1dsOVU)Y#%YA z(`aCzIeb`se|IZVVr%zxClxL8CFLVJd}5uk0`wpQe17S6;cb2sat!hHhxpK;R0MQM zuASi2RP^FDKRG;xR|AC0dXL~C{LzMit1ltZ88y#^f1&uGTSs2OKc42_Km6D0B$0N5 zwFUjDUoI06xc}4U>(90@3)7?f>v6%%kYYuQO2MczZ>eK9*Aosoru3kFxdd^mh|<#1 zVb7`Tfae~Bt_fgG*k?9@;UM&)4cgyw`~(iUe>P9^%DV3Oh(O|=KZt!k6z|9!#Gm)cL&p6tYk^%IaC67{*T4Fg5uhcKL%qI&Q!wh^l#U zZDuzmi1XZCcFflFiMwEPeD_Kl7M#qFnEiBG)V#cH(J_UXYdHr@{k)a6nyfxp?Gxf7 zt@0^y9<7TFZ*TXj65Kr2fB?KUW2hOTv z6^J|Q&`!A+1>#u&$skfvC~PELyY}gFZ(3nX9(1xZnGHN_h1{4&x6;g~mvm{q-=Lpg zne|~v31r0DBzT75jn1+rV!DHd;&jI|iBBFZLgeSQQQh#4h(WCAoVg{aoSYK#26y-7 zmM|&iNW1r4s3wR_*pLPUm$;6**#bh>C|#oA>B<3$^?matz1256llAb|PQ7T+)%7~>Wd z8R_<+=!Sy+^5iEQJ{V(4Rz`z~6zD>OTpjZKiAWPX2*xO|%N)b2KFwuK^`Qw4CxQR& zW%UIy;7-8fEv|brClz<*-&Adk`7eyu-23reV@rX>`Gt0QykMC_$IXK=LRabuF^h_p zv)ziJU_oWUgULzZ)8=9HVHVe6?G>o33G;JB}Wy9oW5e7<`>(mk*+OJTdq#%-R(Dlli% zkC1xa{PdJ=4kftX!$U`o^MGyi5_st_N5XwNwoJa}*8$7l-a9{s$MRoY0&(UGztw5r zXUfN+iCq037Pkh9t!lo##&TU~453u0#TX&~Ngb z>sZqEkGarQn%%1<`dJS~yJg3_$9-C-y3@HI9_5UfH?-JyFk>7y5}PyUAh7W&ZJkr! z9({0Uh6x%52LrRvD8U^EQu!7D9xE-b&GtdF6V6*7!5nrTEG4sKyeO8Mm;k?eHn2Jh znDPz1p#5FHS}@S-IO&`zp>Y{1X zfVVNBz(ooZLU3qe~Y_8U+kAL_nH+H@EDt!34758pHCM1jlnSKH({xMDI1QWUn zHHv!Hq`RJH=;E87GzB49Y#J6~Rz;YMIn{EOvtTlvxrJ~F~O z)vGIh9|(Lb2P4bqbn=6|X&WKx%#4j;*LttAIYDyVW~v=Wm(R6x@1+8+l9(;U+34hjr79urEro~5Zqir`d3$u%ri&4E=p7=R6N1;kM)#my%LHMMw1_IUo z%(u0-S8?>|>B}?mHBj=;Gx*RLn3Q22f~^rsmSp0It==ndgZC+879-D(Asl0EC~faU zOwu>kWghJOm?Ib|+wfg{!afm>Z)d?6-UV307eRaS^fyRMkAwhu4DoH(j*agR6P7MK=As=Oc8w}$;yMbN*>j1otSS-Gr?hEuB84D6~C_XR<^*- zJFhu=&YlbSRwc_LS78Or?1-Z%MHZFbA@ULM6c|ppU$!3Tu*e@k90V+H zIM)vB)etc|^FAU1c<{}6sR8iMIrU)eU`q%tT#`%p+Opr>+I4Q0b3%4Fe=|57#fkM9 z0?`%9ol8NDn-|9m*QlK{d*$g+S!XCbp$6+Z@Ys)Fm0X})Z5;nGVC~s6n}-_||`e>oPo(`Mw%S6jSym_dJm&aq1*sv($s2bMY&>n=gsC zf?^w-`o`A))z{J1oW2#_WF<A9EFTGpl=qn+2x6z^n>=LBC&ksl*LIDLxv}X1qsyx^N6&IoTPt2;MCCkx{t!J3S*nW+lw-uf zQ@iEO&pn=XmAty!H$=N5ERZXhCv5zR9eH0r<*qwekDqdkq%h|<Ai-Z(B1D^rr&O%0~bf+^crt~Xi!QxsZc0?32&dnB7w14u9ozi(L>Z66L1Fg z7PJYLohgGSLOYN-qO5y)Bb~M|Xm^>qz_{qUQ3UbqK*r z@+gN-h(8>DRn_dGLU24K5?X)%_h?lCF}0zG4$4DoYh~)OaG}X=t>3ER4PmRkZF;Pw z1ZFeSS?Iminic9p(bp7d@$E_9$TNp%J+}fUvU{0C*u3O!aDuihE{)BB?Np~M`-z37 zOe~!3$+8I_ng~n-AGRYMWXM$hyOwcDeE@BL zS63Nf;eR^tHq75-ito^{otg0yx~a5>iyucMdY}f6pV)W`d}Q;!U8rz_vqTphMsB^X ztc;NhFtO85#3gIqV7$~undkJqm^+>AaNpMLdUqz&vO6#04dt!W2Naw(nqrRTd2a15 zorZ4r7sxfzXs#QKpJBg=4EqS^>X&FrOO~uU%mTQz5@__LYo^hYsCMX^6absZt5(Gu zjwCp|0Lc8Jp#SL%lQ4iPqo6(a1X(Ongs-XgmlbzvZWnLo zetH~hqRCjLPoCiLb+r(Q^-@4bnU~+15JJ<6rabYYy9+}fFqHBx1K6bISi z80YkRB7K1`21HJ8*_`FIEcb|;e#p6^O5ak>cv*p~N%lKJ5(tr_2uysy7aeJC%dgHy z)9S|qcRKOU1pHvQH|V{!i|NiFbt1zuVb){^jx#ZR3(v77i@&bO7~XJ-3Z;&SHGDrP z(7>X$cx`lO?+JmPE((nwLjm8AEP&(UdmR_SBC^7epEWJbJp`US_K@KkqMjY7=y+J6 zh_~quW);VR))m&z+c@90MkieK&CYtD5(SwLC3aAm@C-qLCott-YNY0F7-h>>#sp5 zy575$DCd!b@7D*EPY6rVh)8HY18xU6SaueJ&3%xN5$fW*ZdNtRcW-q&%H0@0C03w+tKF!m z95uIdVkX(<2IHe6MK7n1#3lAs(@pCPTaGSRfbkk^*VjZ ze*IQx^vCkv$%$v9sp1EbFr51B`?M^I0`8J5qqI)N<(YV>;uJ{(kX2w;7?*cpXMkPmi844+t4|Y9t{XVY| zXq|M``MAvMzE-+&ALUY{xqZAiG#13mqF$7^+i;Kpo>P~Oq{WwK1QKOE)YY3*b%{82FGcFLc|0kg6@S} z3}Nh0^xGeMdlS~>PoCAtkdNF8A?D}^P0m+TeQQzB+}L~^SWKHOx1Q*7FcX)RMbi?7 z+Nn(0i;P{b_8_#5RC_4pax41w;Bz11rMNgXU$S7kvvyJwZ^)76!sRk-_lO5&?m|GI z>>n&ZKmKY5i=q3(7n8@VV&r1v%Zg@sk0KJk*@ZStcP7_GXeFO>F2CW}m_ww!W214m zoq43&x)Swej+_=WwPC`&Ngz__NR9A+T-NmVB)m)~78x>6o=c3Gs^@eAVhheVuSiPG z2C<%8EsJqlclWQH*np=aH?~}#K&bTrG`9RnXDY&IT#L1%tbyC@Hl=$|0f<&8IYyd) zxPsK6CqkHw!Ot}uBC~5zaTmIes^0_0+C`rXHSq%*vv`V9FULe&Y-3jNJ(;GmS{BGH z%=g<*!Q|bc%*!XHp}1rb8G9p7zLT6xq5ju_iEYgZW`W$1`iI5#ej2z0XjSJ+5BCq1 z@Jgskj+}@ovM6_R+Roa?kC~4&DYlOx0^dP)G%qCXG5rytyQ=^N7wR4dDcA%)O6AbF zRe+d9Pf}0mjpm;$aZpzDPgWV<;QR7r&!V|>$0zbe^$Fli0+2c@N^y=B3HR&ww>tt8 z&dkepl)?8wDBA*12`GlR-PPM;n_+j@S|=n@11i#gv0ML;3|X-a;_{h z_k(1Xl)=Ni{WrxMd<j(f@hnB^Fwj||9NKIQHUssya zoRP`6M3G4*c`s8&g#oBrsy5ce#!zgLf;H$pbc*H6gmHk3ErV<@n4$y($S1Niqt++E zp}KE#d;_YpRZ*~AWPUdM?VW*Q-0h&F*|&H{N1Fh0JES^Zq{`8lpL$Cf8Pt~l(KZvb z0|17s#-9rZ%Z88-^XA?fv8r}Iy?OYiO0HdcZvP1Z?wxty;E0r9J2O3v`|^q4TH`u_ zX^%%AswWnp+W0p{rySbjK2+WUNp24GmHAs*fP5{bbnnmH*&fiE)D~WU4~mPyY803n zcXF@Ir3oF@ojk&;NL6m$XdI1RH8!7l{252R$K=K^h{*~CKgw1o!el*X7^;3`W*Iu0 zvF`7#>LNtWjbF>V+cB;(IYH+4)K#eBK|_mN$}M zvVcfj#e#}`sA+C7r4N>EGK4L)s$-HPPGNqI;7^W65)AU;ZN8Owk0I!eu(ccru^KuV zuB!yhzs7>24vD@xC4e=V`Bc-5k#5|=X*P-A)+;g6f?x-)Lkp# z2?pHdXoC}hS^)wtw$vuGS7(gbubYbBq?Bd&me#kZWGM8R09P(!lKpVp3<*d*b(umf zD-n8tTo(d2JpRd$ru5YvbAT4xb8sca$T;*-J_tN^r+Qx$5pL z{3ZroUs4uNR5%u2V#>Ap>9x(OAV|`*QT}TQ5;kK7hwle4)2P3snHPZ|bO3Btgnt_a z9Lwt{0QN9mGNlA;l<)Zr*rF7+C3W^FBGC{O4zIl0cX?)eQQL7~%iL&Sq94b*rSC8bqDDAX{S`aFpqLi)dCh*_i>hc@;5@nIAepBj+UuMKc zz?c2)xss28!>M5I6x^*OPcjG^6~vjyaKOfOva{kB!$y~?2 zq|ctc>Bpq{ee_x%y0utFFa{5vY}mhPHoy$YdrOG>7!JX5i^sXEK}<|-=*Z?lPo`a7 zL?Pu!VTx!6ISa(4{9_F`Q*j>O<(CrewF@HoyG$h5sxHE!#=FQQQRL+)Ba4V}c_@pp zVQEws7C_@1B>`(<4@0FCfMFr%C}qI`8D^LrkIWA~D??g z`>ak?$~m-3*kDqqqwwdv(ol+0{BYGE05(m$ikLq8ln5cAj&8?vS^;|sdxtyq%mPYT zohJcGNlon^&%W<}bQ4qPGFJOlB%N4Ou(Q|nGt-4p^6RF}dwuaI))83HS}0m)LrTQT zO6tHe03u#%Wq8efv3}qCtwbS&&SYfbD5w0OYNcP$#Z6(S?JjJY?%8wztI?9}cL8$7 zXrioJW-hz=p(H80L!xs@DGjl{oVsd$uJPmzUM_J@WBJK!VB;Kd@?tMbyOq9PfpNP$ zCeu;o%;Ik_3gi%tIqjhjizv*i-cFxr8(AE!X=Le-fo=r6)dyS9F*8QM@nVOu%enLA zJ=cXPVg%fxw0u;u8$N&VAf)=pTkrS+0C=a3e*Fd)C-xa7wj_&YM3_w2KtFU`=pjrC z?OhHdaBR&-zH&XADx&X{6cH$2-1_aO(}0ugHNw-C&>S4^-_O;lC%^YxI|0Il zKJiJ`!$2150`PhP+Q;V%3QtNm{A@4cD#rsU2nKDjj(WIq=izfpavcc! z#l4O3hr||4Y}9atTp(Aqk4)6hlt_7R%>beCmbf+lCgm&wJ6!ncs(R#c8C=>a&nvHz z`F6So)Ftmjy+f4Majei%8|7Va%FeYxdSKtP&>E$WC%*a%uPz<)Uzyap=S&dEE<5$( zB>8JN4zsfuYLVEzWxQc&RAODHX!7C*x2E-l=OGr$PlrnpX&oQt3w@_j2-#pM0Mwe! zY^zwsbxd)`5HNIrh2a67U*4=AaQN7n7Q2b|^WCSS6u+^s*bs}ZL6+Vf(NbuD24Mz2 zRdXlZl@TyLMwD$m&02Z0^EjyzSh2G{>{?e)2Tp=k7}Fv=dwxtX(SvxQ4d`$$RbT1P}N&+oT!_NjS5^i^=DP@VS#G6=?nP?D4s$pG3uCsmWNklq=fv(bf zJ`h(XKv?ur#usliKk%EHieMCo)D$K=Lvc0Vpj207l#m{DRQR=C={BH_H99YLo2Zuh zZ!9Xl_eQGVgs-Myp+*UI0*O()8WHNI_Z>2SJi7t~^G2Vxu@5RaUmufiTy!|_Oo`px zUG4VYTl5Ht*y{!ey!*Ega#6%Quw_H;hm>EeZw2MHE9b%_CO451ypR#+}i&fpfW>9K^q8n?t0KAB+1+fp*1S+ssfx{X{S$= zD*KyRv7O4>=;8TG0goDvFl5|+3ksLP#3T?wQtq5ES?W`x>fDpp=&h9|ykM^orR!E) zJzQ6Tw`3F8lFOAuZX$L926QC30Sk^S-XhW@h!@d8IXECV=tKL>H6FjA)OMDvo=NZV9cqKb&F zAHxq3N(L)a9sz(KMa=q0nDad%z&-8*$Wl>5Z|@A3X#`3PmgMiM`Gv+tVS_2CR}jB; zo0ZKPo{f@kkHu1nbb{QNCq5}>1=00Gt-I|bx>=wAld&3rlQ1o9Ksz4i40>gHrqjo{YTvwFq2n$xTX(5t-Wp`2i1hMtN}c` zw=M*s_kMM#!kJE!=l%ZWzPq((g1X;Rnc&7yYX7mFfX)}!B)j)#WvQ73b~sg{I7DRq zkWne6wp^8XNctCw%`RavX+RpSIs4eK1SS#6gBpfNvZt|h^;lf1eIdyOf)YAZ2mhjO}`6Hofl79owpu7F1htRJ;c1-?Fwt@f%^o1 zdLO{hAuMTwDx8&w2{j2piMq9?x#?xD^5K@g-!}aKqL(9(Zjj(HDU*m@nC?!eZ)g2l zxi!}AOffQ>0((>vC`<*}NR3u8W4tv~F0|<|9pTaOt|IdpgY1Org6GC%M*P$_G=xZT zpA^HQ*La^0+Udr_hc2BJxkg!h0YxFO&!Yef#C!z?LSK9cr;c(x-WmL2^o7= zRfOu=d?6zf%SrO{TBv#r={`bA7n>%Q`)f>ye#g7@npTehmYrMEQLsJ5i^dPWsq>tp zA(a>pc30{11gAvu995>;Zyk-}tznIqecpv!^h{`8$@|c`GU~MQxM6{}LHQCLGsjtV z@2kRA`cfq3Xv}F)AW-KCX29`rQt_qe0SK?g95C!~iNS>eqsuE;F|uL_J}tr@ETAPv zEj|lAgCVV;Sub<13DeG0ey{`%leti-H5%H-t}u%(Z=@wanl@fzya7eE^xlu#i2LcT zHdAlHL`n|!Hf$!l(+`HUR>Hv=$XGcZRFc9wB9f6RyAiYZETA_$tD)Vb;r*7RaHV01 z_2`EjfL}n#gyY?%gm+%srUd+gCWa8!3ZKVc@~w$&fX7(W;Gs*2at)Hy8OjI;~~aJ2Ox zvH2Prn6#C34bm}DjmZ(u627p^%V0omxjeb+ZGijWd?!=knFv``Q`FWiDjrZ2TfZz2 zCH+C?h||%1nUgG>8ZWD%J=;200tcQy4CUUap7)UM$0}nT#i*ApXR!n#U=&<88;CO+ zA($ml<(%a%Qd(AYIoaTD%GKdTcxFSHcehEI9Y*N+rX8C&>Bv=l*s}}YqP&ro?aoq} zix{)`s#)nWO6yYn?T%5K9Kc^6fiq^YN;^ zen7f+*)UMPkcaH_Arq$GOv%ik8IdNVjY=7J7JlKrDEzKzbF96(pUv8r4C(r^$pVm5 z8paj77!fBRUH)3W$A69!(n&l0d_gHMj2l7k?U>Aq2YtFKN0N3oWFFowQ{Tp3N}`@j zAW_@|O^@Oy)p;tQOYeKP7N*s2*(bfk3}kC*V|+hNs!;DEXD4H5jigJupd#ZQ%&fsx zeDDrotylu$y6bXoX1eiWq}WwkqRU@oM^M1bwfry$9ELSe z5n){l?|-0EX6medXwwY5(4aR@n@Gf^`7efFQx!Y;S*H4P@Ls$~;pWi>f+$hn-Kl2C z&mY=D4x!phmG>gYZXgf!&_1eR)!&jU{dnx1wlHrwR@)Ski%MZ1>}M9n=^@h{?ebR12d*uQy3|NQ(P z!cjPyL^v81Gr0eYO!O@sb^8w^@}KXJeu!R!=H37Mn?FZGpX~?DR!HP=40LAu?|=X6 z692tnH0g#DR}Fq2X7TMAs#yFl3-j+K{(D1qp!ML98k70MNL7Z;4E_D||IZDdl5RNV zZ1D2;8%Cjue<~mT`{n=MP?mHo&AHqFN zj?mFH$DGnE4cPGp^gSy7_4&rFRYI162B{^=|NR4vZs;_e1^yqD4|A0SNv|Y1SmkD z2>J*J%7$slVpuaS{eRK}!_;|Ny(V~3=f{zKX>^x>h6Avc9J_y#n?T$|VyQ?0_TJr* zDn?6L27JgbUED8PnhR;HU6@jI07gMVk?^ ze^Q%m(@{3_Z23&T8F@TTOo+;YTfLdG9@9@0Xo2RV1q^2>iGK&gN#5H)G6FY1eI&-4 z{h=-YZ$4RNe) zxm)D`2PmGEc%%wYy@9&gHBv_I3pj7R@4W6PZcWB+0z%Ic$X~y;$BSTceVbwhbX&oA zDlLMYs-n-7&E+zX(@=ZP4>EECDp1@s%>t}AuvDBxHUeUgy8|x)&1&Xzer!>Tq+RP_ z1Ude;@s4D`wrBTSvnZRut@mi3AiZNDPUsT~-metCuJ=6+iS5Dzn#%kpsH_bSiQ0UYXd4Zq_UpE*j{7&@w**>V9}tJ4Cwvr;H7F)H-PAD z@s;&H__nXrD)@D2AHd5FTOT}kOl!PAuM`FnT}A*omh}~Hy!M(iesb*WAtnEHbwESz zt#Gy=QTx0{KdmeZy5lJqg|B3=<}+f zbo?MdB2IxqLK>erHFB+2vr!tY3;sE)<>4>u*zkG@@B|;8a~>MBGeVomdDrrTeC3Wjb zN{gd!9HRzi0fBS%o$@d)e&RN2*#+yi)z7yB&sp@`e%^q*OO0LZOg{hcETMtw*hjqV z#%2oM5xp$;utDs&DSFuIohLM91D8TaINFws_Y` zgf_=a8T^us!9M}PBPu%}s9*l43j(SfL=n$8S$29lw=wWvC=}|>b0l(v6-8m3e(RN0 z4J+^-Z%il<(HlS_zK#~q<@#J;MB=8S@zrs5o|EqZ)XH z<#);@c%9rJAVs(Kd*{8-eoI0#=T$vmxU%1%pQmFHwyz9z#z6+2-^I0r$3DYa*WH@{ zo4E#1b)80UZQyI^Yf>d;OZ%)+={6Ee;c!owjy;UmR6%BTP zEcI|EOt19~;e1D+wG}~=<60w60EYTvDK6K9fL$K1Oe)(pxz`fqO=0jcB{V>oVvynD*j zYYEr5UiA#5sJcOPK+WTQ5tLBzQ z>>7S~nN5$~1|;LscLy0b@E%8l+aaRkZorMcC78+*RW*=ZoYCj8t0`}UtZS>3xE!~- z$sjY+{=5Kz)Kodoz_3Y3eG14r1=kc<-+)1 zmS3rj`@Z$LmJmTKRNaBUcx}-%S=hWZN#fZR6O;*ra8dbum9SLA>XWQpL&k#^)vE=$U z&p=b`L9rjo3oA{w@8*8HB`3#lO!sDzyq9oQdq`j3{WACq$G4s~EA?fW|(KUG6Z@Ek9__sg= zh$?K$k|*qLtybtI6)t79<6DSBZ|Mv#5!kbY{I6Z|NxlXsK| zHml|kbs5kOL#TZ@4u8ovz=|kzykMwA3cU8T?UYuEbxRQ1d6Ht??bD6rl0Q9`Lsqm4Wb~wLRw;o?bjR#m9 z?MhBVdICp^G%ZnX#-}NUQ?3zCQq1}LQwy{AdNFtMm=PN{m4N!}^-4(|j-v7V4U z)+|P8np2}Za`{tbx|Jg-WB)dh0j;vML^UDs`Fp1zjiH)o0Qk|;fK1n=&VC&*EsMy=DOFD@UmY3BeQ}t zJEIm%T*mzi3QfqKcUO|cw?Nog&aN)2LbCU<^yM3i7-N+mC z^tw|S5tJ%g01Z6Fey1&1v9ozN_?EKZ{5}8$o%;euG^27TN54K_JnEsMMq4)O2B;Rv z0efT=hX$ug6^r>Ypl}~d)f({gEQYr-j;Wbz$+}NIsxwkE9GHYB-`tif%{7A;=Va)m zZVAm0dfg0jOv`V@h*b%=(sl{?`h4=s)>5(&f8wK1%!L~Z7ERA#tDXu`*dxU%JXI>E zh9}XJ!c@hFGd^6&^PqUX|gxgJ0nk=msize%mxgz zzrw|Tr4s|E8JrK<+z>x&5d9G!DT^MLJ?fd>EVh5^T6%~*dJo7H=v;+rbJePMQrdRA z*q=@B%XPy&Ru+-v+JvKhhm{E@M%*~?;sE_--SD+j?G}a9n zq$Cm!YH5^gMtX9g5iBX8uSH!?BR6UxVoYbEJdSWxzf1SZ(a3yF0lk&}sS?wK1vJ9AUBL}Q?sPlLqgz&B zG+_QqQL+NN<31>8peV)wmhM-q>Wmy`VE|0533&R=?I#1Pnw>$5!Pq(qrI^;(vyM9c zy6~hCj}+Ihmd@}#B)@-lP|Bl;$tMcE8`~oh!J!BNdKN`cvzDIgh8|0JUpm~o%iH-w z&JE}R-kLao3DxlN1L1Q_tUY(NS}2U)``>!pKhrA3!w|rVZbo{jpG#Zq#Gja!{ah(Z zyFa>B^|pajZbOGyB+kbpWE!M+O!jS#3{kt3I0A;*rg`?x{tmGuCyZP6A*ojPkQ%8R zKsbSXY9~{5yI;7f=%{JJecv|qcT5-h)Oks6jAgPa1lfCc?=EG*yVjh zOfU>R-^Q}TF`(dNiE8x6h52Rkq-)BXL+t}*d^k!Pw~h+Tb2zay6j?S=ku^U!;_-9= z56C-rxVKbKL%%!^loys`7Pm~amira^*^`>Je^|;X{P5d{UuuX?%^9^+9qv%gUkIO| zrndiV>*JW)TB$I<=|nrWyjo?4J<65o2@27X(bEc>+3=AM>V5Y=!+n|*-QPl@mS+Vo z`Owvun6MoU%QA;H_8(zxsp-fFS4lGTR47Ix2rpFR=^&O?4bWrL9+rS^jsL+hDk4-B z(iD4Lab!c{UD1&%tR*VNhY*@Qwdr?xrfA_+wbZ#ezn(myO2&AUtG-dY~MZsJtDOs0G|>@dbD!ONLvF}YQeMcmiZ(%yVu$J}1i+&9C4 zMj&z+@F8fD7_$8?31EPtD#N4cAF25tl_HA9GQHu4J2Mb9VeqoFURiIr*vy{COhw&& zGJ7Xq{GF6=$%QFju2Jza_f$_|$Y#Q<0{z5f-B*0)N9f4RTUqV~merV4ACUnc=EYMY z&)hKWEKlCN$|F!ASs06-rO&!x=c>gtO1a%?;4ZxV&T^_e(_1^MMHn z=bqNRy`A`L=tLQpgdgk_ZP9-AA&won9p~@xguj8Tnpm(47)F}+4sE>h5KzzQBh9#*22;+)e5Pk!?Mw1#r2v#q>s|G&BYpZaJ-VpLYh*N*o{d zN6K(qy6ds?u$*H-8-5^W_o23%3|fBAR<}|IT>X*^#ih=mjup&#YXPDEv)FiXmvQfI zppA&{=EO^o()lV5?aU=rb5y(rwYidG8;UlMLwljC0o{>Wi5?ux!ZoX46CdS#^2ODR z5u1uxFs<=+E_Z0TXzOXoYj7)Lc7LvuT5?qEZj_UfmJ|!cXJcj8*=%(@Qh) zI9f?(kf*05g6!FUcWoCC8J4U@yZg5mJrbit8vLG!VO7hBEfmyMQvJ~zGDZY{F6uJr zN^XlEb7&~Xy$;9m3ba4!U(o}PNfdd7IP?hKb!}c8Uiw_lgaapqFpPNKUmFGU;#t~>{7OrAhrwvN$*hj1ETd^4jvDP z<{Rga>W|4&;5az?1{Rcu$Cg`%dQAj7@0^K?$T)U5(#lO`H&7;OBjeH5ppHdMnFmO3D8tE# zA(vW{aT#IvT$~?Ci=?9v{5a)1riD+8U8OXfoZkE@MK(@SNfcknbqEIV#Bi>=Wt-7= zj9nAJ$%bZMZ!H4>y&ntqJ=9WlAHYeztnvt??t93W-qbBX+4H1!J^;uLjpOhIm*+Fa zrj=(~nl*twvbOjga?4#0+U)EVW48iITy!Z|LDNzm>wq6dB_6h?^Z@QdgZ}{n*N}~* zRq-yFio0LQ#g)yf5HDa;UXv*g6he#zQxLCh-@{2#fM1Aa8s+X&$2Y(ID(DP&EI7{{ z$mdQrVS+d^lmJYY0j&e@++e7Dada;KIqGMNWx|Ge5E~H)7+Oj|%PTbS;lghaOAsR zT=#Wd516Mwl_pndfog5JW+h76IRa{F9XHS%v+Gp`?XJ8!X=l|B(@1b2y|fn8Lu-5+ zt4d7*xu%qi1a>Fs;ymy4YAtPCUtg0lCE8j2)X+IHJEfpSj4U!f2MnZGy1n8oSa8{s zEYT4=_VzIxXAzhgL)YaHt8a4!;*%}8`^wY61z4jnC#7oar}HIvBnR>U4&5OD6U!xt zkhAQH_*8WqPO--PBmD6@<0*`3oOq$y`^IMVfg*R)7{7-gL5WL6b$bOx7ep`2S)Si^ z+2~*$?GwntR=L?t0U?O#`eZBsYPGzp2`{qson6@&DSfSp{7BR}`3A-YWnvD8ok!5qt8Iw6Cf0Xe7c2|5(8(fz~c-1pM~rK)sI|~ z^=miwDcWa}FV)Cm)jtWGB8l!pl8IdqNX79?H&XCmlFUqQ&%fDSmt?F?PHVojXDyX=u$9)k1{igJ+DKSf_a0s8Jfs-awK|5l1f58 zwMoDZ&H^m}(|SsTRFL@@-L$|HD8@Dwa9 zVXcvY?@=i6DJ!Cx{Ws*2WO_u*pTO`8cRo8%DS)lz9{KrxM@dc>% z>`9$C?>snEb5hs`R^epRwefDYtA3_Fa1}r{Zad4)>=^lS4V2Ev`)xeEfD;Ce8U{fEzDQu>(*S^JkslR+f#p;6Ftb4Unc->OSL4!M}d zLuI}n-Pwug@gVvI4WgR6yLyVRBMS_ISTfqOd5$P5zP4I~)vKztR=eo;k`T#cpN+Y* zkXT2y>JvmmTwl=qO2a;)UWxDh z*p$(+IdV$h+9DOo76+hfW|Uh^tq$K+`3kM{Mr9NE+)n{Vn#y&XYf`LvQQVr$Kfr$a zxA(j>?ncE?o;lyJR*IZ#)OuGHR(fJhx~I1QmsLN%xWJFOLZ`n-DN1s!IBG+YxZJ@7 z3!|qm!xn#%hg?IJcZSl}x%>_os(W5@VaSqKWgo3%+p(&HE<7h4D+pr3u4kfc;)T8r@-?L?|C0MLcy*M)fHgz(2L)a4 z^kV$J>+y8I_Thamu9_2;i|yloSmxaN?x7%Cv%dPoItvt2nO3-kXu@-6&Q0dp(tFGE z13ans5*#;s7oQ!e{FFDPli`w@iot2so>1|j!%e8nNWUNE)7Et?#e)*n=tvO9WX+Oz{?K6Vi$xhxUd+o6|1&6}`o{ z)8?kr(s560xFDM)R)@*u#ZfG`6L&Bb< z#sPH6U91KX4V;=XWMuhqsuvs5Rnxμocf3F+&>dUt3G?1#s*8p|#*6Mo?XlIfOZ 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

;#M1q8VDuHttku<^-;S=e{{FErA7O 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 kwK-oTwPE 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

Zk+BU8QfakF?_=h|Hfl}%o!mX2&w;_*_JFRKj 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
l)D=oJF&pi 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%0A4>$DDvME&j8!aqHH*yIE;!34EDa1^pG)E)h0{ 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
rGQ#tCc#i z3N6lqE6vwvPFGmD{)R$@WWDnZq3&iuQOJ?2+6E`P@Q88IVp(f;dN0iUlbLa{dt$J0 z_RpMd40JhMVg>t`nNA?rd4~}5_R#&?m{jec_X*`|+zx&-fYNA}wJ-GNh z&fv{}C}t(o9R0G$+tY?Qn3hRB{z-4PV9x0$5w0p4#tSMTrz{heS8oi=GlKZZ$FSyR zp0T;PMbDRaUiS)uAq>EX?Gzgo6cl7-7O{y2*X~2gtXV%WyT>R@m$EhP?!+#5CgjV9 zO-FGtWxx|YGlO9ff+~SCeaY6)nEZM>e#NpuzI^gE;Lc-tTTZg}rU;6Z=5Wjos_1 zGd7)yeI=0d{8Ge5%Qeef;Ip}gr~*h}=tLZ(NL*Z42PrzRr@Bv5P!+)o!+~b&Y*8an zI%#<2yoy@WVPDULj<((|_=I-XjdbP`vQlAwDT%r5TaH%Lugkr600+Zvsdf93aO9)v zcf3#;BNl?vJtPSSr<$1+1N@FxD@Xu_jGCHKT$022Tr4AznDd*Hj*k>MK=`2DgDg>| z4&j5e(*+5;d1#eH3_BYs`B}T(v)*OC1nS39uf1-5TU|GjMbwb_a`vs`gl(@Ok4Qcq z6lcy~-Mb6PyJc}-`(eHiZU}EfnkQZl?tzwPQ4KfT(QZwN2P1qJ&B9e8-Y44A{@RiC zX~>r|imrBc^l~XC&K0M9zGKqYl;7x%TLhyw%l`E#`;AB#|DGo%YUqEzPde}!h=+}C zNMLHkuwJec0pL_tuP3Le3eQ`%S$Qu9LVSFYVwHGRwo40Ws+?-9hsO~1GtY*RkdZ`s znT7mgm%|z8I{U7P2^5!aR+1}0_4Up*`}U&=jBJHH7KoHt5*@~!rlPCGW1O6vLZ?sg zyE3?Mwmp&42EUxyzj)%`uk|6kC5kSqGt@%Lqm~Am5JymEMC9ovfh6X{b?n~0Sg4FA zgGVf5_g8mgi)10<=`nL%6*dOsB^JMU7Bj>tO)q-CBJ)NS$~!u7Ty%THRsmu4Y#mxf6?EeCXLjr=(iFnR zr!7s&ug}k5%!_8*JiZBxkqAPe5D5>hf4JySrfqwtg~D8_(wzo_d{H5%<%!3FCL)?b z;<7~rG(f-OQJ>s%eX66tun^cW$Gt%30%^Om--w5}48je{v~jO9;>H5spfdSf$!3pK zFYvfr4IEs~>{ zo#0k%yIcp40PXBdWgC@c*dK1u1T^^ftrizPpf|6j)3Y-a>sucOGOe3<;R7A=7uy*u zB|krhw|sFhWJ0M3u!B}P(gy3^-_nWbPDkH~LU`?gJ)YuvnS%ufIi4t6A}KZ$t*oP- z0DE4|w?$gKKr}43LcBV!8zqvF2j}M}d)UBX)yQMf3q6x%gSkw1q?on-lJ;Dwkk6UX z+3>!c6MM=~qa^geFHuW(d)X{mhl3%X_6A z{94%Y{j8q(Q3c>@4Gj(Fw8BgGNbL&Qup8rb!wniZ?``X?c3i?D& UtD_5j&8_%8 zd?c&;e&D0mQ{V-l1)|uy79ti1=DOb7ma1H9(yGR=*&PqzE0EsSjr!rXSjLlyN*H}# zsz$zZ#PKmC#3vCP{+9)AXKFGo10uG+&g@S+VlI?aE*s%HKf>M>9Vi~h3f2iScp?~78v2^hHMbKdR7`b16 zl`OV6mwJ8x){U5Iqrt7m>wSWO^>w{yv1N!k^%n(zbEu2;g?YuKk?l(3vfXLQpHawB z9I|OR;PRVlb-h${nwSd$l@OKUfb&}|BTd4fh&nBr`PGE#tL8%S-=7BP1n33IfZizb zDD6)USyfgPkul{Jz?xN?$E?!uBT5+ua=%8z&a;(|@GZq~WkP44l^GRLLxEg%z2Yk+ z6lAIyP`EkjFw~srP+;kmv4(fZP6dm8&@*aVjZxh?4~QKk}b!%oZ|mx-h>Vn8yJyK1p||8g4Bkl~e6S%CpB?`(IU6)&?r$sW|vV4CFr; zq9A4q2^7|RB6Lfc`~-1@5g~$Y+QJj=*EVhkHBO@lXsg94;h};*ZLDvj#@d#l{yb|g zk6Umu%sid^|(c+u%vqaA{2 z@=sX5mpq$2z06ynkX=&9`=#kqpAYC2x6V>;<>q)nAzB@v?uw8fs$EVsfeF87WCS7a zQ&jx6$;fvp%1ojzoOUYVbz^{Z^#mxRU<-8Np?XU?+DIazSeqwMtifd`i?k8t^KruN zUF(W@do4%H>2a_t`T0&6&ksJskC*6+nm+UhKN;UW(>CtzU4yY_xMFaa5%nk1lCm?Y ze}sFAL8>f3sxUUee1V#HIySF{EWVRydNAFpM>lwPsb0p z_yI#h8I1F8bKsJn`+sy&`oa&>`TSX^U zrB18MjM6PY_G2Yt*G=ocKHb)=tgH|o&J-m4wvtK_X3rHt1XE$v+_yr;EC?GJ9<-&p zm=nSinRp;?P{!ZAyN#ex>!^Sgth7k$?c-G-Lw)U663CLv&rr`p>HT z*Ug!t61*>bg>~owoW|yQEZhtjr4#(3*U*JZ#yx@-BEze__rL|*ut0pIxrA9G2|65n zOy7^%m^gJn^I_eZ6Zhdq03m>5Mn||e3h zEjRnntJ_%Mf?hT2sdGR8ZFgyN$TnpN#(QqLtAt0`ySRjJx>VEsc?j%;fWyBu<$SX4 zD|Q=mddU6Khl}Oo0DwiKXh(bDyFYT%i|ctbyTwel(O~pqjS1o%Fgaix zn2n_<1rEF=zE_43j$9cZM}`KFrK5F#B%zk`Z*@5p1x7V=5)vlp_gH`o8v=f~TS$-E z?ycW_^}3U`UzzTE(e@XKn(N6%bT@o7|I3w%yqg_J4aPw4>951FNB6tL5e5>KY(rVy zx{QJ4#TmZkLGZlaCm6;jweh})mr$Ncn~KUeva@`M=3|u2`u zTWwYXRBf_Zg2atBzqeH12>NaoE*X1#=;T712J=6MpFA1+Q~Fo$JYlG4s>7$9+a%SR z=Cg=?iK$t99F<3+=R65H&2PFsqU2>32>Ci2)2H-7gC&62B^*8k`L}e>Y`^A2sTI5@ zWSJ%IVE)qY57Ajzwqmedpg2$7nLU`6EHLTY@LQw*VLxSsBf4_MBNPGfZ^{e;7L}w7 z{Rg=i%*kpq=EF&;3SIRThz;d0EWiv11YQ}0lg|sxHXKV~E&5VRgF?dpz=M)36YV{`6%C8TMI8tw2UOYo)V@)UAgF zGi@RlX{xY4l%z~wN=tK9tt-AZu`;rwCeFv()e6aTef)hQ3s$7Qu>Y9u{hv)zV|A6yjNo2{zjUd^*~9*&nEAAH-!vN zem~f)RjKIIx6PA;Y4b-H55_q{!q z%L`xeH_xhK75L~3-X5=`tf3lb#F|;Ax9veHkKq1@O+kAIqUAd9;v24-dbxK}cK~bs zPY$HyAHy$iQ$l*6A10j78`5;vuMgfbO1Zr~XqQj7jp-$`j*|U85yXsB_lkO+aIa*A zr&gPh)zpKAIvLJG=ka=X3i6}9jedAlXKZwGe3914S*46(U|0FI{Uh@IRRt37W0yh3 z!y!5}8w+Sg!>CZgrK>d|m7CiYSmt zF2RBAd(X}Y!ELtz1_1#fJSdmOxe3gt+1CdjApRUXD0r);T5xnFRtQpcdUY&U`b>Ro zlqo7SJOaP|LDKAlzSoqRXHLoAjda1S89W0ElTl)j^w|a?sDBQ;uRPozT%lr@zq-WH zUMjn-T1%?#eH?l|M_1pwGpwtqeejg3ZJMKT-e`WKyX?`L>-euvA+``At-qY(i&Zj* zs`)CF?j|A4LF-?T%MoclA+R&1>$WHu9q;;Avz@&@;yAGv61;6e=j*({qHbllnF*4z zLZJR&?=BY&+SDp*s*3|1G8XpHul1{{)Dmir}`y+!gA<##$aWdpLoJM+oYXO%v8T_b_+>F z!=zwYY@U&cF1@1eMGWk>2y-PKZja$Q(fQPq(@nTvHyFRZcwv>}WOQ%4c3%~I-|Cnf z%|gNbq}ILj$=$#v-l^MdXHek}BI{SEFnF>Bw9^=mm*7^_(oMCFmnN+a2;hbPZrR@L zle*CB1SeOopx}KcIUym66BGt7yZYMzpgOEdRQ$w3^fvDXsf^dENY;{>$ybIkfdfp> z_R

mO#^Uxo}rSr;0GZ!~{=0{Py>>-Nl8W=)LX!{+gXB(EuYf`cvqEuMjB*g6qil z#5o!2dNQgPg-(4zh6pUFqhj7LQX=$7clEuTqo7811aSTO1emk=2s$Kzg%3@3CFBe;a2)LDn;)|r+6B179PM&YjRZdkc|R+=FaLFh;zc0`-QnZs-nB+) zwRED-=ozD?esQ14Lkiesob1YCKNKV~Qhy(unZu)U%LLf;Xyl5}UPyX8J<_luBqkM~Fjkt-2TwJ}EIUQ8R+oxy5|Vg{I=k8HY5Xa2Yuyp#_kiN>RM z{SxQXZfEeo286(s1#7Nd_{~N=9bZ>UGt|I{lFltHhJ$Uu6?!W%1zbLZi1B&$iL~r5iGy2;BI@yG3LBf<$Dh={jTU+2phw&KG z8~AIS_nFWIkZZLR6uW-hWOk#Pk zI2j!~7I^FZhREzsx2bIMmxT&Q3ZqV*PoiEy_NyYHF7Mxyz=O)eaekplySS=2VY#fX z`-LCV8P4~A3B>`a;*KAG#~uC3)T_4(^3My(1Av5RcJ61dLFDhx*Jy8imx|lZ{<=)_ zDNAi&F1JjG1a|qW??a4xN8vyGmlh*i0*ae{SVI5y^q0fdFE-VH1aY>nQ3;jCZb<;? zZPosQWI{vw5#*eFDfmvCDG<;Yw5OB`=B4; zjX88+9(IP^R+X!4uT7$=`eY@%5}%qs360lZtGGL3A0;nvZ zm?6~aEPQcMP-LY3$q_=t%cH(>IRTNkHEJV#XF%Z7(nNZ2BMYFn5DONMAUoYUUqqkZ z9qpa#g`Ao$gu@sic>dP|45N*$kw6RE7iL|;g9CrU`IW}wXs7OEnss+bS)B8EvZXqW zFulc9V!k(IgVmA->*vp-ZUPsn2O%Fp@8Ap2%jmUvTRCem5wX95LDr7vA3KDU35|hx zIrh-@cZoX%rqMKooRqd_oDo@`&>#s?^Rm8)6XT#xg8cx6^_rrDNn(V4*FB1hTUVN{ z!hii)e-)07G%(s&S*T2}Z^3yA(IdrN!}A{-YN>;Tot*0O>DssSLZ&Q#iMPSQg~6kB z=1)8?d`_k625lTI8JNa9-L6sK2RtDzwi8Z1p$gY^3Y{&0pf@Ko@m1bg^-P8y!b@6A*tWz$8jWK%)HiMs`NVvzLE? zC+SXR+EjYJi{H?5cE{*A{)+B#LFLIU5&;2-hqUY5_r^dM}G>-kr!=y;6qeF+N-c;!h3vfF2u zD1x45A3$rB8N|@)0_^m)B)p-rrRu%g=ZBJf=4dAboKVHLT+#_NnRAN~f6t&(5)jf} zV{?X_hz_NpPoS1r2#=yI+CV z>F=4vg`&TKO2}W-cXvH-!TCK2L%T~S@?Hbw+`e7l(FyQ|sBzuv4=wGcINK34oZ|(H zTt9c!NqF!-mecCWr-c52gb!{kKDNzkzsnv;7*HD9#^>Yf1z?l>VqN4Jrnk%OSo=pd zV*ziZHQb3_$3YZhjXE@&P7q3=e1^VqF~%61*(bxg;;-5f#DATN6klD84QQ~El^zKI zbtC=?az3{2v=Mly|B9@MUy-S#S^aeZ&^EFx z1*R{jIdM5D-rl?=)O2X z{P^HdV!mH**;qzj-a2gRgB;A&*dZgoBk-K0F~rD&)2Bf=N`obq;M2<>C`WYP4 zdl=GVG>`*tgth=prmI57jgv*~p^`4C4Q}JnF%g#(IbC?ZZvFtk^ba(U)Cbl2Yf$>9 zDpY8UU1cE23!{}=KA^4{Y)zyU@g3ti#9YR)n^?77Kq4A#W2HzR2TAjMk_5K z$P%9cPtw+QhnHKU@q67!(wNb4&E86B>^kBF0z@wwgR9Ht10Mqp&9$`%2L)sD| zK_SIV2^(?jnLkWNpi1MZ4Hk;fwV!Si_k?P#e*2#!Gf^B*Qx4U+AkWBw+6z{6#9JOc3LL%pXM$drZ!+#NNY-u?lkys!IxIeyqvEgyfLgF3R$ zZS)PWH`c#`Eve#eQU1@n+@QtW7^Ow^6N^FJ)EVM?Z;xn%I5+ZHrlxQ&>)QK#+Vkyw zorWDjGRgDkJIRADTv>c5$nRo`qprLAX)1m=@i-vV^}&H)hxtJD5$^zRjuz@Bv#vR8 z^-32!9)04wgkSJRM>{=dYbJIsZYGyVJJ15CfbiR1TEbCXi&;plvp!i#ddzIP@B__B zlOIrRg|je19sN=G{h9R!^6okZTCer^+<#H{qNZ_e_x_Y~6L6%-M8WfgjUw1~iOaoU zR3kzqU=LmD7qY-utJHv>mD%6GxFJFKjF_8|owPEvMzYuJ*!xqHgBxI_7pje7H#9%zFO&XF5X{=M+t^f+W!ye3Wv(N?1mdkn1j`=-<1r{XLZwNBXiQCUWI$)pQD? zg@*SOiiKPj8PNh&pZNS^ND}Xi!+c~URpMKIW;kzC^yxC76&>2ttDd3&OD^{5v7VRJ zzy`7QfD$I=Vd%`w$HNtrxgk-c%=s!df7tOoA50+htB-V=Ad^?nZYj#E?=dz{P z89;{8YYpBgRY&)HL@?*_nd*;#Yq0~rai7kvHA8pXLxNDq5ZxF~faCv%t!1zjr<%)cj@#+D-XY2L z>NC`457{=EA>NQc8|(hc>-Hi*Yypev?=|}~n@0|liwj_*{=+^*^>KqQ-wU4FBJ?eN zKTFD&_std{T3k!jdbcZd(A`c_KDm77MBurA9az>J=WL!GYElt z_q%QQ^TW2$O9P0@X>A1`6@p~QB85(Fx>8Z-f&E4#F;dkSv{UuRWpQ~4`|iUonWXdb zbGF?hn+p!j%>6<%N=4{|No^N@rft=5T&HOm5~Tba?(*_2#jeUrnLii+Ch%k9 z?!J=Q9xGAXt7hK8ZViu-$K(vad$82BXjldo;7D{$WVmr^z=47kxmitUz>i*+oi7YV zaiC6?0(^B^(W@MBAppNji1o^)!Li!=lir`>7wb*%Z-kt_f|Q4yE{toHUjl#*yZt(% z>elHAy~d~&?Ftw*e#cY0H9iPU@z1RUMO?UaL$sv|k=pJ}^rrdjZH=e%xCwZ0n$z9` z!_dXIr**g6&O=-`R?mYR7iy|=z{-^o?s+2p+6&&yK6<@)G!-X1b5>8vckntbe2D7Ga zQ%&&Z`;P&^$d>>K*rW%-{o}s9Kc0iOZ&NSDIr~)<<^hDc>((DNX%*0&sa5#5V$*K| zD(xoa4v4ewjIe;xbW^K@-BTvXA8=sJFx{<)M@aE4+v3tiYC$7zs`>dhB{B>Bp|R6| zRl~GmwJG%ju8rxWpB9h9R#MgaNCs@fx}2WWF( zx+31=aTK+%&Iq03LHM_?c8fMUa-A>n(ZL>Y1!<-}%OVBZ;L=B{E5ChwNT3BEq=^b3 z%xcKx1#c!C9HTt+S7>$!%JME~@K8w6Qkjv++d+deB&wEUVDo3lw4m#=u}%RY02!Fj zzACEy1MulX6z8K0SxM3JeX-hs5!hhX9PP-O;pA!VuReHJXd zkSBaBC%jj~I;^z_o+vTMDnbp@cbTw-ZAI)z0{T}^`D^xb67ijt#DO3@xB5OizWp7R zhsbg0Y);?(h7-FApz0|3 z-%}TggH6Jqb}N&=^I2|W&b&JgDSU<9NKK)o@Z${quzc!QfYg#B+ylO&O*CU7(DI-4FE^$(sYl( zg7!X*2edK1+U&+>OR_eh=i7*gyLFoDb14sJx&GP+b)Lk~-)b_N4-J#Q{Q6n~oZ@fD z=HNgmgYzE{3OMNgD!l+PF0NUHR%Oj+9>RT@tw8R%S_?}WME0<~=Jn9zBP2-7?W+Kb zx;eYq`{1+#2N6RewsE;V!{oeNP2%)~WDeU}m+<=H4yD)j?$nO9{+3+5Qkrj4tYRJP zG!V7j88N5PmhxtT_;B`z!TlNKNjGGmfM+O+kWiqIH^X>;kdR04#r`DvIV7prHtcS@^REpj=@}|)AdWOAnz2t>JMx&c>W@_A-Q(FM=YbUBnXt0|Jg2J z0mKE2jHk=t$0a5ihkl*(v%z&rVl!!25jN;p*7+&)( zfi6s7Q4JWz&3#D<_F#z0$McaTeIpx&5bC9?P57!j(u+b|J7Wa2yZ4;%K@jg|;13+J z&2N}DesFO{TL1Nd{yk9mWy$(7S9(B*Y>F(}nFXZVVV={}2oz}Pdct*Y+^Q?uFOyq! zJYXr^ShP(BO=PX1jPl|1{$Qe3810MJ# zi%dlWQ<#DvXso0pIIhM%T1-SwESj!43{u(hi?uO=^D(Q7d4A+Ijm53{AYxt3-H*_LhD6rhdMcEr z-GEB3Z&0Cai<&9=#-~KwyiE|A*pqOWluAj=`TZjQ_8*>t20yb0YuOmG@C*V&zWWoJ zaQ2;vv3v%l!%b=%iPFP^`~DcQvNt5wW&}%PXR;E#ViE8xOk6TRp>TQyR7Iz|h&ZVNAb>%O7}9E=BQYnX#D>K{8xmV^UP zcvy;)lDT|uJ0X1bAt#t1ByT(EkSQIrSM~L$yt3z2x|jm!0{=($zyj_?-t{O61My69 zDyHw2{k^8VnDMJ|1^JTl)#OSSOXRWx^6nddD2E|G2|9l3zEEo^GEO~Xq}*?roVsLX z>4A&>8gU76@{5x%7J~xCWibfR8Kn~ zO8H48KQYC&hE5qqZFi)nMHG5br3@}jI#FM_0Kuw6Epe`-uviJw;_ENbAItRyT8$`H z$G-wjnLE20L?9PAzQVN*L>Vm9!Jqu#2=I#7tFm3fxGUG{?vNwouCqi8xjWrl+*Idw zIe{CT$k)hT6O9MH5J$Pm`wdHbFf#afvD%K1Oa3$DIp?Np>?hfKDa=j5N7N>TRHS>M z!Y+WivI8A6=RdEr=cm01KXVNC`TuVFIMX@Gh1D|XU$Og@1G>Up0wYc2Q2u#Uhq1hv3Dll z0zvzLy75a4vG_dZVBF~ryfL3M#*%!NXhW^phSwQG+hkV2ebDtqhZFh|5EK9yvP8aK z*6MWzpKw)B4vrHJ*&ow5#Aduy==xzv^xby2fiLwpyEA7yAIz>pTjmC?=w2FwAo|Fr0qy?P0-)amOexn zWfvk?EVm-j@d_MNhENQxfQMfP{+${UKgD{-tmIIQoq^lfk1(sjnC@anGdw>0iH(Q1 zkmrM)S$Ki_`@X!Gm>q}IcXBLki*_eARfHz|BSUX34O0{Tyk(b*(zS%19heKA8fFEOnBkkI#ecqAzXd?e{%Dl6so}SD7S9 zx`Ujb#Nbj7g|Z1Sy0EBya795?yqNIi$&A>dC)7jlpqBBas@l!T*4_x*O5_ zWqRx|!Y9+gQOT*2q4ub_L6&qgab#ZMXhjKj8G$E&FZA?&eUnocZbJ%90pd|fltt(> z++hf62tE912$INhcR--99^5tTO^8gVx83221l)^9Z*wx*3kXOM|K=2;+-PHWv@8ys zwv##ZXky;3%LTp7#SUU3b98V<8uQ--DCL?x9VUp2T(&44ns&m70KpinllQ60auF>b zNViHCj=(ya53%&uD6^HX{^r5~m4AV(BhBbgc2QWH+8 z0jbQw;A3%oRc;VQe?)JI2CD`RBgBuGS_T!kBxuB*8ewn?%4qS9wyL27SR7!%=T~Q% zD&!fjH4ABOY;5COi*y+^8-|!2OC>+eF3h+&oDC3Mv503P{zH9j9U&ytSs0vbK>(2h zQ`%Y80g&d`F>x8hkyh3Q#zv3@^&CKOjiRQesD%y6@-ODoGcHze3pmxO3ZEbfrTTt= zAHuF_1)4}YH+vOCG)jptfQIC#>2hg=v7COCwA@RQnVhmcHNncPLj9w9YqI9DdpRRa z*_jAH%C+fU6@9MbK0~8aM#AU;mChgTuqabf0}jxhVL;21=Z{-97P-sP@Rwu)utjf3 zpab}zP`DFai@cLD)mo)?*z@gCZ>0&n=V~i10i?jRd!dN@9;22peY7>5I!uwWEm@?w zS#QyH-ju!!WcoL6gg@$tV?dDGJ#8v6&EWBYby2;j&G}nC>-9wGq>|B-(#;ny_p)|fXSO#RD_97L7{vV}$pf() z;EV1>Y*g`opN_G%TX>(GYT zcDpH-ew9$-yezK3_p}W)ur1Q#GfBKc4er3-ebNtwK@|%`(nKM+>$^nwH}U^O;Jkkq z^hZRY|LK`~|D(A0_CPJzi^mJNCKG>@r_p`(>=GIh<8N%LjTpxsGgJgZ?G{4GUqbHq zVLUv`k&cGu;y0B?QRg!~Qt&nZhIn_mqA7Y0HoxCeVG2Bh3j~JCeWQV%MmKn1xF!K6 zQA;YDbWTQ~{rCa8yZ9d;9M7!?HlNNyas1yN)%WsdTBH}9TrmMv?k+>}?Yju;POIK7 z3P=MJL+a<_84N~@71wQQz-SVFM$fcyHyYDJyQfgBm<{+mfiI7LU~U|mOUu!7R!CFF z&KsVk^<7m9^~Fw^WN-xo%FVtmnt2Dzfiua_0ul=wjUMN}O*sGOwQr~G!k-iH`vc*A zjA+6U8DPgZah0x`LIHh&J(KnarEcM&VU(gzPA_965bpKofMY9m3iARKTl9O*&%30+ zKA(AW9K&Zax+xM6?AEFeE?S`jkcRtG2X6`4>n)$fn_gMr^GPxTAL}AARTh--`lW{6 zSzB*5>%f6BGBCq?-ek#?pdu5ccNAMhnWOvuRiyoPsA73P(ZXZypb?|}*&j&i zLD;VJ>VBZ3U!hNU^eI1g%109uRLo2SJmK!fe@N4H46VnG33hrFHRsTn>ZZTaK9XAYYixH%k zS=o@dd66IVnDn)nH|qDckloZ^md_!0iNV@RArby_4fyBr_60}ZC_75=^$8VfSX-wn zT<&WPv*L1+`Do0JL(=-k^rl}jY2Aj1WwbXLl&jOeeYA_y(-Cg2NhKf~rTjLhxtJ#b zpdTnC4OZ{1pWK!!Nd?bV!$Q?71(m}D@LF4Xs>NW9HLgg3u#~8qTC?5?Z?D*IZmJA` z`vaEr6niJzYnd$Xdh0KtFa#ba=1 zESUZzoPG&cl@9~jh02GxHbaQQWBcq|7&0R%VG(s0kkNlep8!V#4-b5zq5}TJs}9$o zE=8N_#PHC61fLEu?d>h&-h(;tu>-I6n&}UJqQ%V4#jH3Xh5hhH72aW( zLLjb2XYi#APkS&*EGdI8_I3lj35f`dezUMnQiF_+5hCT1tBwO)vIkVI6?m~OzRh-` z4>2s_`?Hyi=Sz-=2>fg$7Hjor@4*}NR zj4UKb3HVJ0)28z-b8UrKi}>OXkOJ3Q{~!jE^c{MjS1v$c$39VT-dvD&hg*}uhq%Ai zuK$BP;8Rl$w_vbW-v(vCw3El5m<^KrH@(FB33uLY{e*y`b5+t$BRfm*K8Mvpe?Vox z7u?e?@m3-uGf2>^vpL(y@RN()mtkT)UN7DUA`?=p1#-3qlGY5O`+P1QP4sO`z;`X# z4+arzVfCwl)&Hs3|E5!bH$(z)(sS0QQK zz@j6{@7|=+N*3!%W^4&Ph?H|_Wh36If9of{YiN?c5W#Ntm|iv_Jr^600pLc=$&DKj z#a-l^O)@hf2BfrYXJ6D`*m8WfoxdQ25$jcFlrRG zGPk2HjT*z7&o98Lx@ha%Xj&tr4L^IUOht8nxTU$2`E2jWeOu~)JocP+T69F-;rfA3 zm%?nt6C4kvl%9(F+W4*)0tFJjk=mSx2A|^HyPV9YKgtqbG{sV)Z;cj9NnZ*lBrV>; z0>mta0z2kuGeY(kfdA*uRQE-@gudw3fBXeZ7XE2JG??=Pn*vOavUNG@@pjw9z zggLStr8qi>TJH=E3P!!1{Tc;|wX%VPUz`!}pwgq8etunfh5}=?zLI_Xtqm~1az75E z-%99I5IR=ZTm2SQ^zNuf{mcWk;wyGT>~Z|)<;%$+!?OB($~B{mIOM)wdt+%fYwCnv zBpC3F^I@REPO>KJ_SvbqcyCp7sLE}{;sjh(p` za|w~}Y2fBob`+e{h)4Xvizsf55+393g{i2J&Oc3vATW1eEbLwLCoQ!PMc#_S&xF4? z_PiJU@Rub`sugjwJEqAi=KX$bj`?Jn72YoL(}wKPeu&%g)Iz4BeZ2 z*9{EiI)Jh2S)Cm^>UfL5eNTe0Sfj42mNRtsLb)BrfV*|*?Jl@gw zf-+mLel9lp(deDw-ITII^-25VH?oMonB!H|eyP!98_8G<|F8SWFk^fq8OSO@0eukf zhsWaia6TpAL-_z4S#8H%z-))WXNQv3V;8b8`f`7_4TXQOnZlmwDgX!!e~}Hy17^Ub zsd}zx!#gWYb_pX18y@fIE@*dfW`6OI$X(6)co2Q77wXJo3fgDf58016uMw#4%6*CI zx_Gpui7W;YFZ-*6@A~ClU>0@ zalXYkqd_ZRG#mRs6qLczkMI);khTZcI5x}KMgC=7Y%qWa6S-l{*k0&#l5`?pQbqK? zy8C|2YCa_eGknm$ZcP;mfmmrxp~+*U^rnCQv}j`+o4n5hG&Gyfd?A1aBJM{B&Pw;j z2$tAu!i2qr-upR<7e`J&69jOhAx<;KoN~n|1c8Rk)ctcZ&*8uzVTxN+K@wK-(JSdf zX`=I|0y(|n^y`#$r%M?zPC{hRKK6qcw1KYO7bE@BF%9A4c_%uu{6%5cF%rk$L^)^$ZzcYfEUe_fW9_r!M3nT0Lc_-GcGM+@&%o+ z`zF?5mPk9~HKdEBj!+{!Q2p#4UZ#!H;irFk0VIk%b_XaCy$n;c2Ex8=I}(5B$$uSc zNOk7CtBA>)@-#J7GE}u0z@xnRxNo$^fDuU#{%wh8CXd;un#s0v;z9F}(Bj1l5LZ-~ z$#vlzs%XY2Wh>%i4Klp}TFzlJP%buYc~Q+Gr^tqT1^B3}NdrdplNQzI1unZ#;%n9ADZ_euJA z*0yi#lF7m_Q{`z*)W1bHZvW@kB5nv3-UH|8Y3zouC0G;~l$=G0qKG~hxuE=jh8nqh z+ZBXKv?;iNBDl@ww2i%HPkNq#jZxvJ%+scKdfMK1OOoF}05$itk#6eZzQWbT!ZTv( zHnOn2_(e&5wMQ*M=z^{s5iP#`4E~{xi`1l8+tgSp-!Q1NKt4X)rCiT`3t;$(0uU=m zBN>SJYAOiz2XIOs%SpsdVX>nh$CHkbNl)nEgYF?M6y3exYhW1P9!w)NvY$)D_V66^ zF=i@6&PCmLEucx*4V8T{A1Xtj@TNyh^Wf;-XsKE6Ew6F*y#@gwzU}_;`VjxsAxmwY zh?XB0Hi`6SYyWnas<_(r&SG^sz11&h#VI7{HNbs?9t^2%zMs~_Re}|O`T#C=a+7&& zck`Z@l1vSc%4J?J2_y(en*Vv6M+i833Um{EP8T17$Zp!V8(wrIywN_HA4reLbCGsZ z!9^2H&`da6AC){vMb30l^0N;jXCB!mjPbDoyJ<5)+Xz44U&>C)xuv`~ElzOA$aQ1i z7}^a~SK7j;Y4yM%4`v?cD=f=G79d;xF$7n+8Fi!DepQmG`xg$fu9LzIM!v>$|h2h4g$6b7<=+o16$}1?+-vH(KXjwOdADN3N|s zB#)YcsuO5(nHw}=-1c2dNcs~>EL77=)Z>cOB@lnSVq;^d);a`))}*+QX?QsMDUVkO zH8GZ7TO0f=m2<)8W=MjMksVUQJt*DnMsn~-xN=7H>q7W4lLmt?I-rUEQqE6AK@tK~ z{`8X@;`^Ttw9FVgCOV+DpNV_RO;R)f@NP2mJN2(A*K}azt?sgmwt6tuC9!us;AeSB z&AZ60VGl8W!~NG;&bS<+zk(m4E+Nr=P}=+RXy&H_>kqe=Fu;uXf-jV|`R0MmjW4k* z%XSa|?6qn*yOWx8)Crh3_pcs@_bhwu*rpx1c9lI6`B3hyxRVE+8x*uC%i^~WT$1FV zKihmFwj&7wYVP(OM%e>kniy=*77IXOoj;lbbDD+)|FHF(xjumBY#<*S{BpjTJ_Y5> zScCarGu?mHE%!6wZw)~41$$-sODg>4@HJJc{M!1bhpJgA7~#+A?L(aPuc<)(@1gAk z?d0C3oL?*NM*%AHfbnZq*TI1(egYDj6b+}u>tAnzfKe)8pg-Mq^98jD!3XQ-nUp3^ zN|zgXKVnC|bXWPjh&m@Re(`K^?+SVS`1V3|Ghm0fK!JjG(Tcs$o~r%zf?K2oF|eB> zm~!?H%hCfjjlT6Pb$9=$p8#2=-q0@;uo4%1t?dM$m_MoOFtby0Ix}6=omO6hUijZ3 zF*`Z)IH9^vT(-Cq9dY-!F~GyTwd(JxzKcjPoxQd#A7G1brv-?KXEqa7nm1Xg=*OmJ zKCUv*`Ksq(m@#jV4SLRE#JvcBT5_`r!zU4K%VXC8rY!N+$L#+?VvE*yR%$*@w-~?* zNk{-rwm%#ZAyBOq2tvp0jbh95HPttXwODY{o2Eu`VvY-3lf9spd+n$T(X9i%?G@ca z)X|tWwU6t5#X&1+02FoWi5(pAu0CJZ92Pi=w!_?);?QGxSw-0#ap zl%0BXwvft)OP0TUUyI;|K`B0Z~dv6OtF8rt@qI@jop9niVRx ziFlV6DV^sDPn;}G?{YdA5pzXaz}+VW0Mpvc%1-9%IiSgS1Fcc`#l59LT$IhzuJDB# z|73#zP~8*N^tZvKn#o15fvGa%@6Iq`T|xI(f&Gh8d`(wX26CUvwQr3R(c3#KT`%$e zt2X^tJ4cHL{Bo1SYm?)HwHKcGd{(0@D3tBW|Ki~O%&tR}(iZy?vJed`0!wjJHOI&O zW4d{GQ=-I&i0ElAnoqrF#>dmWSh!IngGpKb&P|MsB9)JnEZqY6UNZ}7W*#**iphOL zz2f`ud$(0s+n%wj9cQDFZo?a`PoISM{6t+mJor5BzXmJ+-n!!+?s!oHO#_RDNvXHtd)J~Mk;B3prD2lg=Q=fL;b{dLUGs%AsrtBOSU zt?3Ba*s{^K`|&8E86Y_vFbGo%Y=4(Xn|*5015jLPP_>-FkJ0*90Oxa~Nb94udhTFA zoV)nb^sYR|n2h!J!k?$dI7OY)1Sx#gnL-a9=|vtQwkrkG_mv$09|Q#;+uismIWUY7 z2%@7Cvm1c`oZQ=v(Y{fQ+{{lp8d_Rbx~Ij;44sLn_~SFa+ZkBw&X3;h-$D;#h|(|8bX!xO1lN+UF&@edfY zU~$iMn4Qeo_e)u;i!t%{RhSGtiP8N;@s7lBJc%))RRlVxyZ^Y78bkP?XF|AOtqLQn zs~4^Yuha;87W_!CMolL-fU{ooCp|Dsk9EO)J8kiY7*h9AC#e8J+b0-T|hS z;2yJ*%1j)PN08SS>`Pw%i!yfhbx`kK5d7^;VKM*84d4#Ub7sH;fbhJx))fH8zMRR+ zpGgLf5v%N5wL%wFCSs`7?GhJ(NeoU{Y-qOQU}7eBo=e^P(4_vXmNS*0e)&RCVeL(! zT(f_i?$}M(Exr%HMc(qA19=Q&iuNa8!1AlB5!#d7`?D=L^JF~qogE?7>`(6irqJQ2 zJ>ox4!Mr_aB7MgAX#q30f4NAi;%8SFH-z_Noh&Kgji~6BcmdInTHe{K0GD3~AXNo=N{v?+uL}Af{2E}6acy;7Ubx&-5G7xuZ4Far$P7gN`u2O}WbdJd zcmtJ6OwzumY$)1~OSB|@2xbqOl;gvi_(JaCJkaXNt(cZiV=b2P7U^A8P$=Wof{!Kt?Rm)U-Lwe6RQaT& zw2Ao|IJGlX^o%UUPXy#&d|2TWh@W~IoD#e zk;I9el?N_t`)w_)qqrD#^eQ&-m<>I;^4C|t;0yRVc$Wp@Vo<&Bkn~k>)^n_2KbV|mDjlt1<)B(xQd2pY!gJ+W`-|o}*p+C^EJF1@8LTM#w zYx*VGl@2rI4##rvDW#L2Q z;bA^qPXZX|*{s)5u{*pIW7#Op5zTau*R)FSI0F(`<(7TJ*F0c{qU*gmq2ASXH%~o9 zJv$XlnCAps%v{XthA4YIXS@Pcd`#F$LbV_^lKtxE410QXDgbC0tS@>a|NRT>g4E=& zfWp{;(8az^o2VWm>eWCMJrwl|@k1059zWHnWR~|^&v?Tuug4rY%J8@A9+)K_3qmcM zjTkyk%aa9d`oX66T5f`s#&FFpT16X=Hy=uKnn`Am?HY*rvCk-_QoozLU0%azINn>8?(vE`@;zthvown(?K9RGv8RI?6!LFqx<6+2 zFFNethDe`k!>D+~q$C*5=suiNLIOJx`QX-(L8a1~S{&uAbrXSbMa5=VWQw*92K%^&^fT`~#a4Xm!?4Jc30!kyH`*%@s! zDs`X+U(3_w$^bqW&ufe!`MCC-f^H{KM+q^y2gD|)d%aEl3w;x$fc%GX3ox$w?XZsm zB8Wq35&E6q>Y*_>7hkgu|MwV14&BQ`F+H3+SV}%hhr_Kvl=k+)%N$WFePc`jU*_#L z<^Aa4HVywQdpFXcs5_zeU;|GAB825+js6CC-@BNa!m?Ch~@(PD?ki4nIrGB0u~0*YF8w+ z?<4zU__GZE_|CvA`&`g7YA(o&!3li{BX>dW)c^|nSpwo~rAXj?`C=a@Y=<5Z0bpmq zSBNH4&efsI0SAm;UDO>wI{==G(+Eg>0mU#5kW7M4f)5%6<+*=0uVd2yQ2@E0r^Qiy z?wiciqF2~v-roD%{xJ2~+`BXg$p>A0u{hYbV%3&UL_&8@{`D4%+#yFVaBv{Cs-8{; zl95CNnKKd2=l&Q0)7>k+4XkH_cw}xDma9osU!YoS0XnrH`jB}kG@gkX^&t&sd)ed{ zhLjBp3v%YJMFx!8Oxjn^PSmq(-EenaH3VfOWKgH-_4gz2S7}y)zLDiJ0^n=S5k5;f z8M&42F#PdMXJY1k9^OGrfd5J*zrXNK!Ao&p^0QiHQTc_WAkCRLS9NR4hAVoHfzI*P z>fJk^@$L`@>9O*_+v9+B|L2OZgbFj*L$be`kDOc-Gy%y;Q1ibB{rB)Cd3C+8_g&{4**Ey?Q=Fb?z`|30c3^?gY_;~ zMCyn1UGxVtH;HcsN4|X*Az_IZhEzF(KiVYi75>&w(~CzWE2i(0(wGe7o(|IlK<9U0 zZHI0i)@l(j=190qx)uLeH=iJXO{c3(iSW&+sF7ilM*;WmL&jp-FDpZ41G=$c$U31( z4Rcw7Ti91skP(yqm9FeqZg9Dg8ZT8p*!?Z^EglTqgP1PAa{$eP#G~bbp|4z2E7sg#lIA$D?M(W z7ux}<#gQ86>7eF*x~o5`yla+teNxcIsi6Y;r{}MHVZXV*L;+KD>$3CG0!x)7g3{+% zJqac9izX}JkTdn01zW)vo{2@wRu9ZgelS+^s;EIGIOg;(*-)9Cfro1Eo$L?r(AsY#~6*(tiO4EoBC zH*fD!u3m({Illyzua|sn!VC|S#yXGsMB4dNFzv&2+D^5=2WH1?3auUg>u2!9QE!5H z1$!`E4oJJ^@rB=L#a8B|M!5Xjb{~c_6eyX7zJs`9|Idv90Rte_J}Eo|Y{?_EcsFGs zsE1$h_G$rhBD;p(85htb5bD*H$G}vcJ#LFqU&<{qk0zGe&@Ia~7Ye2$($yu$Ieh>6 zN&1^kx1%yg@j3WVVK-sNuuP;~0f?`Z$d)mn4~u^P;r<2bq6c;32Xx^Ea^VLgQ{vF6 zDVW{_C|wu#%!t-F@J%|^hZtLjJr&B*>F(tcK9(F;?E*UMOzOQmSG(+Md81%|a}bLq z#?2Aoq1HUI)&S|+egSl>20B0=3S!t@l+h0R(Qw$oQEwqpwh+{JjMRxB*SMzw7*8K! zK5c+b)PpDLFa^Ue8_GGVKZ#b?kyW?wR6icD-J!g*O@VP14Lykg<6hxXXF;iR?);jk zdxM*m4$I0y%gVvbG9=gL;pRl=eh=lY0#{a|RXRqO^EcF^HF%(ToIp-b&`$Bt@Z!L6 zYLK`gkopkX5D%EA64=fljy?em9s#F#AmFlqog1V6AL~v41XU&&Z7CmQFNaOC;}V|kamR~Q zxEUU6AaI=pQvj?Gav&C*b`rZBK5W|Ws8{?xqsc+)&G zH79;^vBaT3%^hRHBfLD>@G zUm>V)%YYo1m!Jg+*n$|k@j8D!G$*(174Rv8wY&Ibhe*ZEh{n)}W*Njij;>ZvNpM=b0ER0Fpc(6pAgv~J6 z#1(n8Sml;wUl=(Ud%aNDnH-QtzAHd(FBilR?I33#6toceR-uywgglXXu17QMxrbt$ z8Ia?%FWsC|w`Xs;ERUI^pV7B*5jsVTV>oW1m8;I*pRw3P(|Eqa51Hbaaj@wu_Cr{W zmD@}HW_5C+aM9W<3+N^pRYL1OXfRJ3I?so8Fm;Z& z_^Yoz&&1Z0RI)MvifluB!)&l{}`xFe}tWu@v-T~$v z^e{<9Hl@H;%-*i9w?I5z7l-OVqEk+AQ$HmHxDWHeT3N77K8j_i1#+46yruG1$Q5A* z*P26<4r1e>L(B?=5ibd;9rw_v@ugd|rT68NJ~I#daO49|WbunL~GfauILYpiVt2tPHt7GTy!WhQI<)jVy zrv1k`J;*SFigAg>=;J0MYy&-Qx@IACGjUUS^MBOPH94-iYVmfap z%cKxRkUD{WQ-OZP0VR!k3t~p*$uif5o5bQf#1E$i(|jC0sX?YfnUeUy%$ykt9W%8^ zVNLm7U_S~CW;YU;aWnXK-K0EfO9^u-vXte@hR!j@84UnC5_{O!t6iuH{yF2)6!XDV zBEkxr3*&$ovi%Mz;JD~70`_XPTt&0MmO6dcAq{5^mO&+h6!cUe2k0b-kQ9(8;ySM zQv5L}*91AAfV|;D1~?4I`39|PN?10g4^W=pjQFE_dIsv2nxR|f*8 zCCSC*(ey{2ukSgz-F31ThaVr7SdBT@qdDw>d_2&KB2qSgUk8D>jQ5Fl=9w5cy*M{7 zwKey04M90Z7%xVp2^lyGLX>OMSLejLxLF8>hI%dINt2;17#q`!CYpL6e6=X0E*JSB zI#@XU%u3NDwnWhFMQr~iWV}^F^l|Rj68A9DI!#fT9RjY;MXfY-+H|yNH-3COWU!Wn zS&SD8#P+if7enwPwHKmDhs8zEtfjJhyp6EF@9oA|i$2-&v(YZMFBVL_-E?@k;;e&) zcreOTC75Q`#tLFe>N80%Xvpk^>XybEiewSv^<$=pYcue1D{{9T#6>$bg7?bbSlPo^ zn@nLZ0jP4^cIdg_MW+LAEs$SX`1W#Nwt>cZ^t7k|xae8KTTi1WvA?J^=-gjWEaoO6 zvrIuPD`oz?O#TB+apvxTSWUZI^g#Wp)5K*o!wee@scxE@KTui# z>oE`1yDnx$b-psn1MEIF%G@c9Kf;ZG%Ni0GR-|1@KpcY>mKV(!G|sbBh62SUe;lpj z(@;e80l{dQH$o0;gF(+b#CCq*CEVOBWGNPF3N-t;Gru^iA{?lI{{BcWAs|Oi$Gvv1 zBV<23c(L|yBMARNf<~&l=veSpMhIvpYvv)8ere-3M!dTniid|DXZ;D}dIaTPDGwT6{ELo=P8i1n6q7cY1w&rdHK zeAo2SWyvpKIk{GKllJqk*qoo-)l^ zOAh4syYb`C=q}JkW1kWZCh1?E{{(BbDCvW(*RS*l2XWe)mFRhJmJLCf^6BxUc{H*` z7`g7W*F2bT(M)SyqH=?|X}Y{@?NSuMqyeVlvEa_s%_8QN%90q6fV|oy;LH=eTXfyt z1<4x{%+v>$OyFyPUHJSHS!e)_Paosdstt(v7gjP2REKPPV=SC&5-l4S)WVJSm=>+@ z+AP{5#&frWtobu7np|saZ!>|PjYV>`@){Q}JHw$`aCyc^2lty(B!$9H3pR5e71c+qE>W5oy7rkiOQ7b9|K-DYLnJ$yRyb$7nc3HL~k zTXUJ&;g-GO(fQ&TwDC++(x~3nP~I!2Z-{Wzg@o0y!^mr*_AA5rv&UW-b^Z1%SKpRR zm@mr@uk3`#Y3&EZ0Sw`z8pXVu@+ta%A#@lg5DVEy`70S zDqiZnr%9|}1kv)%^Gf(c9bXJtd_s};DCzy9c*F60YBxmIF~j$Y*%*YFDx&!t;);vm zdrM#eF6j-4)*I>LkBw!y_q;&&yurm6UE_vtJICjf>9BRkadoKCb!c&P2##Ae_uzGk zd_y2{oVpTl9SUP}5V{$$Ues#k;h%vgpob-JMP}6uwPMUEFX2F}RUk-h!8UChp9QAwHFyW?_q{K3(AC#tnn<9=l`gy&`R6Ii0%ni<>$_R$RwSQax11qmHi z2yHG~*Q(}2S(1E{6;4YjoQ(*5a*M`!zc(FG#W8ii-eigKhLAh}J8trH^^4_r(YLk9 zrkkVmeZPB!Zakx8x-vgJH>~u!Ve=_cc=MJ2>K=S(TVo`bf-z@F3J>H}@@x8O%a30f zrR#0$=p(dQL~^yTIW@?5+^<(MwG?59*n;Z~`iq&iUGU5fw9Gf(J#>c3#-r@ox|Pz3 zWrpdOAxF87{*DmLn6a=abh(DCzGI!)n?ZP} zB4%Dy4P7G!eg+J@42Uj9+_~2!nkT%dDbq}b4RJUJPm1yF;dnU;zm7T$*B!VFp6597 z%fB#Lp*)<=KaKm8JtO+%q(=O1QERE(0^-Up*x#pz@}QNY__QnqYwW=hty7`Eg$&mZ&@hq)j;v~}cnOCHTjMu@&QNa@e&U2p51I(_n4B<>BG_cs3r{GZ%2=#*q zm;s+=g$A=wac-_e%19qMy$SCe6(Mp|JpY`@mW)Be4b4s*bQv8lm?Rt#;gm_N{st$n zn|RmsbwDspP`lAhho)* zRc3On>?-H+&#^~h3Avsn%d1;8bj2i91VeN` zRe#(extZ}1m_2a1$M9yrP;4kMJ6QiTL}Lq=chP;IrNDb)J+jeXx@=Avt8T+@v>;`- z)=GAEr7|}}-*r!Pm*m)3sOSQIT(I+0Q(h45y&y=(auuDkKY&F!c+s$yDuxusa0R#w ztQ@z70+{ovI5v%UP*Q|;nk_}{%A|HM2FdUL-iyfs-@gh%e3Y(!@)=J1oO6at0^NI$!ysqrjf1gUjQf`SVZojdQmzU}0~xBNfr`)3+^Fr)4zts+d!a;G?VO zBH+{$M2IKdn;%`Z3*)g=Zy<6#pgOume_)!jaTn~x5)<8ZXbnkEf4u3Uc+GWiN82bW z$J(oYEZj0{e&=Xl>~L=bCTvLpY?sz9k*D6Y3ce5Y>DL)%;*Mkycce9P%88kmMC$m* z=QWy=RBI(`H6Aos#aS)U)Txih6?%xnewJTrwd~X8$YA-Yfciq>>)`R(~QCSHP4KxzTL1!?YD6`p&LA_j{+8& zfwc-YcZWA!B=dGi6{5&z_K5AGbud8)Q-uva#wSJWx2 zMR((RbdP1&sM!%C@^p~?;3gmFc3dhPzbG&h+v6?IjfbNU^AR(9ieKJO2x<8DY8lBm zvS;t|7mWKRdZjX2%9lbr7(3yjkjN^|*i_Nyf#Ww7+<6FyHP@J_?e%-%kt=BA=Lm2V zJ8wd-6<<7xoG^gMV+?FHWR?q4nlvAvoyH( z(8*fo$(^bcqLb8uL*|a8qXxA*iGZWhcT8pm{ove=Qg7W6U1LQGjavWNrARp5U&dg0 ztWaU91zF@|7nDF?`o0#Q&;$8NA87>J_LmDX5?DM&Yc*U3(-+?LL&)=T&vcgC0NQ{< z+ObkOhkvptmBq;xU0`BI0Bb0?>6>4rXr^j~>sLI~*d>7EnMsGg)>cs%eP+5$uY5eo zTNBlU1%G^|?%eiWq2o0}a{~0x0t9o_=`d1cbHprDCmvJNKMjm`*I2i{L%ahShT0#0 z0qyJ;WY3QdzUGrs5|w?@G+Im(Ut66O1RXK1t`}GhGT>GPgf|D3$jp#W8nWx~73>_NKPCG_mPyn`euEF;yf zxUxM;RS7KzF6Qq~3EiIsE_X$(FqB|_)bYOSa>*;g_F_r0oc z_L2&9sc0uZak%KZqVHv17_#g%UWXmXQC8$dVQawFtT8K@lTXnU*d#Ta$ema%)`@na z3+`gvC4)Saok?_RaWBeYGljBU zW%i8!^aA>T$fB%M@34s|wNCFYZsIqMbf_O5m<&s%9q`}^)U!7fOwaXg0ZF6!%ujzm z84?w{cua?S(#;W9%LZ4FjXI_R4XLlZl66s2t;oGUek_Pc0T+_B=7I0|(mYss%|Z)X z`bDf1`X!c0YdQ}Wz~WL&M`4_uLH7NjXW0<$_aLDE|85U7PzAb5fRWqcMSEZ<)XdXS zAnrdylurj&x6xNO2jJh}LDf(}eOulHo}GAjzIM*u*;Kps>Nu#&R1MuOd5ZjpH*#y5 z)!zu&g0FcN=k`D0-?W7ky9npuuegp*$m5(Uc7LwJi3e)DEVTVfSn}CjN|a3g16^Mx zV9c+3rk>z!h@sH^u>7OLC)z}0TaP(HM}=$;G8_ofCkI|GjqJ2SN=;(vcPzt1l@%1d zuNGYS`ut7hF6f;!I5u55`EHEjZkBvhg4^Co3*XWBuqV$k%h}yf8k$h83MCUD z>6uY5V#lmNpYnM@tFCXBogbk~#Xv|(#1j=HnO`Sk!L2&F=&R&Hx_F<^1mi6pJ*t`qxvlvH`20{x zf~odDMybY-3q^#>u{cz4TJK9~DPA0WV!Akl*9Z%Tr4gLSyFhf-PnDj7sb_F^YL-SA z=)J3AeFd6$()0c6w$By#;4;+&^9way*<993O(P_6xQ`glV#?ih*i>RnmhCXpJ__#d z+Qr)Wwk*x>kdaSP+!c6+;VO!w7|sw~4u1J_j?Y z$1cuLjXcBWD=aKLSY07y-q%o(_8fX zY7Gq7Ik^UeZtkn%qvHhoKIra`vUc(gdc%C??>vE9iw_rlQYc_;oWMgTwl*Mj(yzPd zcEn7*o*PiG2QdDg<_#{ue@VaxKO<~kAl$EQSrZi+*w}GUL|-Bc`2uuQ;e`4l1;kuT zu=llBYz}WjyF>m7V=&HxS~UdQ@;6G>ZJTx)t` zRr{X6#_nCf7>`=NiMeyVQ_sF>r*rMJ<`L&GksPe73ok0VBk8nV&wW&#@MN8CJ?L{y ztl4QDS#or!W+Z0+!x)jb>+aq43rfmOSEqnjYU1954!!caQ4V%OECM2Yx?CJEie_;a zytq$n{8HOEk`>O5!DoD;kr5pFZ&bse!U_yW(i>jD6`F{P=`yeSvs^9%a28Fw+i3GxQr zA1m|jtj$gKF&hf+`C7GLW@KbVO*!jziU zgC0;LqR=j?TB50$JZ>|D0Df0y(XNq_%ubm5xux5Oo@&ZzXIme)EXt=am**tXIj+`- zYWU)c^5Ty8;sM3tk(jZWrLhHrF|eu1do5ONOCycQ^=@#SoFI!$R9KOhFmRpn$G@pD zwIsmYCWJ5`eBl=iGq{<*`>;ig_u?Hvo09@tQG)-aArj6fL3hk8cl;Ay(ioA>Pyg+; z_)w6}fxOU*$bFxd{iR~j-P3H?!lRwV$yR=#Y$awD!sh3{bsv=O&c)x7io{=$|GLS< z=2ssQc6M8nviVe}GCpkSxo~Zvl2O|MfBVbL-B~UC52@(>Ay&~X`2_FnyrIYgYI0ki z3@hiXn)il*&x)`UxH#`{*l>35hBj&PJ(i7WBuE&!O^dc25|>M{4kG{_D7m14XJ8+j zDoKEV^{tT-dwx)FPC~NsE_-;(&S;@ay*h78J;sy>zMclf*&KTM1~>EyXaoS~Tp4z? zExwa%*K2Rxl?<jpsC}xCmbs$ab)v1o6QQ3Q-z;2|I_`YP{us82AaXlB9p zx49>v`yCQ*TAHmO1Bf(rrYuQG0%XkHRw)lNUUh%@6C#+H4f^7pbj>LF6XG-C3CnsQ z)O*jR#c3E=(xkaq(-eEDGbn0$C+frq*0zX@yqs4L@5 zv7Dp`RpHOisvhzE(B?BRqQqF=S4c_oi9R}&9}dCseB?y$K*kn+S!c8I?cH=ZHdg$K z0%-`Or?RrTT#MoslUF}hD)si-e(QTk$CO@V$Nyz3{+}7(-|r^=3Yz@IKO)x!z17A5 zGSaa{=glBP)_Zni0L0($F-l_>7~TLmyt04=J^{%0y9IrG1Nxx%f0MjoyVB5qO=1{b zZxZ@Ng<4=IN_5TMX{4DSH}j~+qL5)@-<6qD{Ti>a2@lJQqxk$goi+!3jObz_TAv;v z+YCjS&~vP#I5egO&|}(etjnfskiH|7JqGe>9BieD)FoQ~+tCnD2y@CHl&5D{YTLBy zB~v=1aguf|f-DW?G8v!J-sU^Db}J*RWR-y&b6gqO@&aY~+fa`h^MJ=q?)#6b>7L=y z1osYPRQKf?VXxEurSZv~HC zr{V)l?XG@dmg+q;=&3(>uTf-$c}nm?VCG-Y_*Y^OpPy9(SW|UW>gs-mXFR1u@8jPm z%H|`SXKJe}0g43KK-lT0nnWA_OVV%o0zzp8`*{B=kpdpw1|HxR|DHRr!O5DXK@Bnb zab5K&pcXWH@eUpSv(#BY8EwiEI*S8(@dT0B0*oe1-FK2QZ=_BGa>x*|m%tJYcohWR zqm0+Nv}Ha*qwZOQQ=5^-Ev$P=(kuJG{_@2PGl11Lqo5GLBR%GK-Ac~d4lgFGkD!|p&nkpVVzC`FQGNb~URt6^PdTD6uL`u6l@eYWIw~;1w{;%;3*m&S^SVdicx;#dRjScH`Zy~Izif6w0 z_2JyF>uqR`pBKh+J|`YJ;^;1Nri3N_hpCYFLTGg%`)vxC^uBz^ai!qDO2~M@E%eBY zGYA+k1x!s;oQ9Aa;XVMuv3^W_FpBNU|I62&=K+bHpe{>Yw z&>)nRovry!k1tEudHDy?%@<2Az0%Hsw5lZY(I1Uhs|JTZ|MIs0v0(%#%r^#cxhiRV zseD}}8%3nnf#Q54BxP++!y5e;r<4l34OY|)Jc_;_Lqm8#nH1#HZyz2TD|fi|?O?eZ=a*}Ubb#0KWzX*P&-AsU` zCL)%0H~|9>gj7fbOKQlsQ#Wfg%; zQ=Z`>MWR}}X+r;-bJj|uwj9pMOM(bFdHKS_R_=7&F|5nOd(BvgU=*4t`O0`$zvu6R zFL8a8M*q@B{Cj1HqKqW|=>`19aF7@P$9@jlw+~L`F{*Sn9V=0N=`olkk|6HwKCS8u zWVy06-R&?nheUhc%P3o_sxJ5{BH%g`*qqS6MDv-LWMIed z<*LeG)()JVG4Ha#@8SpsL}EQY|NWECqyzd60CjTcR(9n{1;w+f)7~NH1u?5kUZ*4p z$pWwGu`2G8!PenM^D^+Vg(6*~l3aJ$)?BE2A7%Asq*^Xo?2xkJdjFek^^f%bUpxkc z3BCrUoFGv~zhkuf`R|u8{QQ=K@0L#X_v7aTruvsQQ$2*E^c|KVhm2_w~?ojj}PvY7-VVwWrI@ig}NFnhlgV+?zcHENQk@~#VFHZ zzbEt!{pxtqXw($+r4#9=5~IRE!N;>G;m*08DUhISyu2IB`Me%EJHfyb8*W>~o7g$s z@t4vYNJp|@w$*#B#@nz!xxcFT&PLP)7@OE*_3iR>PfBj#u)P0D^YucDcj@G*Y}(I<$v_rxO#W)hVfn(2 z{;PSq%F^=Fc~;Ao(U&t_Fp6$SUW%edMxm1GnISnVX_w`4b6MX*pi+?wSH<6 zz@t9b)NegAGh+&@XP}VA#l=mR^lzn>I|L`Os3y)~*lMybJ$MDBPUCvH)Yd*PGbsGW z;-m+XA+5mhD|D6Us)&dP>X`AHFR??U_L3>s$f5R86JWDw^t=9Su9VCMmY0_o77_VX z$M6lq{75|a^8j5+8jF)_221Jj^>G%9ADALj(9nuWl4gc}p?|uJ!zk9#6W-UjU;GDD1-p}*wefBx_ zvXAWSPF#21+gmz2zYIL3JHEuLp=XyM^^`hI3$}m$zO3GKtXSn|fWq$=2X&45*P)Ls_Tc?AgBS$+S z9-flDeeMQ3?k412PM6ao487C+m;dmXe|(Y}xG+w~%94kXEt6fFMzSMvN<~GbVscIn zjWHG6)cDtL`SU*j6z0AG-pk9YzM)}hNd1rjnea*`JYx7X=Gn!{{5XaX0rBUu>Q`}WRFdxBfy@Uk)ij2k!Bs<1)ZYc;j! zRx5%MRQYW}95+=h3)Hhm`+d?fvVyE(VPx(tK**% z>mPfEQ388a`tbx7BC8NMWDD8K1TO{jl&nLP%eA4=Hr3& zshT@KdU}?WFbQrwPX(8Ku%lt(Q8AA1p3ZKA>v()dLb4l9|7}shjIm0gO zUs|$dDb;N4Z{NPXim>%u>WV0_tkcL>h`W^LKMt3?`I8jggv9nN%9p zd3JtYEX5{rnu}?$w6?gITe0`AW(c1lRwKrRc+2}7tPW5KI}wrLM7+9%yfV2CRw%#U z>b^54kFSkpp!9Dix_-N)V64!^>gOLnew3z!tu=*QXAx8|;fGczno7&awDSL(LINS} z*M)M~qQP}KxXEy|vZSd^O08PMu}S-@J?w@*no~9e9Jj^ONx5%~307BEuP)xp!1{-; z_+xV<+Cw`%p-869VPtZ~(f&%Gd>WcDz4>PseX~5fC<3}%mg|3sL*V;WpJiz_eKRJlS<^;5)iQ`;}`gfnuFB597^-}oiquD2w zMg8bF6*)gCObG_o|9=>OWN>_0XPlf$OEC^VKU_bmB$`#`fK zQQN9a6Lt;*;U?$F?mSJZkdTl9Fe#7SdF929mx?U0{~p2);^6Q2A^l=m8UIEme`D%n zj9|5BHqF=He}@~?2#-abFTR-D%y$rQ$~~7f?7LpAMJ1ATtsNC+C_}oYqod%*SN3B_!HFzT}{s{M-t2J;M;C!PE8FTwb?%X-0L(}4>iF;R&B-zN_L2MkaNq4oar zoaUdltOmubhcLcmTXgzAz8H;k?!*7}#cVd!;ET-_;{VwM>kplUiVv+<;{O=26BM%! z!Z^ToflWMH|MXdwOu%~pzWpCd`fbI6Ynj)4=zV(1G*kJIB|mB(yt%b@c{J&UETU(> zsi<@3bke+}ptJAI`tGsd)+NLrmvesN0x7d=xc znV$iNmMt)GDUm8%S&WH^d4H)Z#os!iu+eXO#&R3L5=S|a*Jh~~Nq2a+Slk(yB-T^; z2J@d93Dv5_49%ToS@^&w4NA;AjlX!m3NnUzlo1e0-hKi>P=*T-+1@}Kb? z{PIV(I5y}C<)+96`GXzuV%t!tOgsKO<=)WZ=So7k9L$|qPnjkE3Dv%JXC);J?z~S+IhN*qB zDdT!s9+s~si3aCop~u7r4O#wAp1>*4Rs&=uLXhWX^Ph!%Wq81m! zKbU<&U5X>!e|6E8f{L$o;V4<(!lNapp$olC#c|AMiU&^xOd#MqhSQtzb>5&S`-5 z7HJ}nFwefnG_EA?r`BSF52@X5diN@89rux|FF59wm)VgYbuOrH zCI4 zxxODk%HA9l9zO2=JebJc+Z%)WvIBo54Ewt-d~)!lrmQrWc+S7b`uLd_X(}X0W2&o* z+Z7&qN;L->SF!vU|4YNVzjB(!P)PQ_`f`AsQsBRMA!-k?|9Q0U&!w|@f+61if2|@m z)f8C8)I?q2P#Jd))(n9HL3VwyPY4L`v(?pArHoenMioYJajJmhtyXYrbD8@ugS16(KfEa>_w=vkMPFE_la*Tm{~~SBVAgY}Am67+py6{0UC+FvA`<*ZQmr zJowO;xbl-=C%CeYcx5}&d#rvS623X*4)fQ;FN?-p+3&?l@t{~^|mt`8D z1bs1KL|BA`?Jr02O1NWfA4 zAY<1SyPMUWr3D&e6kZx~-d}&MKs=hop_}z2Ia9zk%%~lgj1&GFGapV44UvBS{CVv2 zWAplzG7eHT?$CFNQ4i~jk-hBp!KX`J-BWrg`uHAFCg1n7evZoOK+es3RSzS;R&JS?pjl{(i%BJ$ z#*JV;qUR>_*gRYxO*pnVvWKcOxdY~{)X?p0o9J6R9ECXLd+4>Q7G(TTabsfx=5@bv zNBR7g@14+G67j&3vV-{d#|NXaM;GLA4UN7!K>SQjV~fv@#@5P|H1>}gnkoRCOXXaB z?=Am3v-&6H!yMgNGIhc;?`w?GG!a*j$G5arEhZqa!#DFHu2WPE{)Y;2G$O303V`R; zWWOe=HbPzhYPs1@)!y2wOtZj~q7mn>@oV=T(xOIaFjt-B&|DK7I3S>tRNKEi2rf7d z%-4Fmb9T6XqLZ@t97lPou}Fb1;Mb&@|fH)b#<238tqU<)}7{Ql^Cb3k4%rlboL_}~PN7{nHL>e;oB4H!|P8ee2-Sk34e}ZZ0@e zg9aTYb7scTifKU2eL_+)!%N6?c8!9)bG@zVpM>6$@MPqY!d$fwn?cZf?6dH*k4D#O&r(uTP2LYKsMWGJudSs)oDBp81?8t}edI~2P(k5Q zz0AF|4&8fQx6-jM0-?hp9jW};mHXKfIET0N&G$}r?fNm%e)_)CiQJr3fKTZ8tFpEA zQ>kCl40BvfJe^o6%bg-#zi* zy;%KQq?@%WTVSg>TRf8_xxPHk-caSyLmJ8}+>B6QIyIEi+>BYAV_@HThE-ixca7lL zbuhSt29!|XmpE|H@H2q7xPb~8#ID@)2f`krl^a!@tzIn|bmkq+OzW9EA(Eo{3==$ov~TpM+e^4#MLm(s?GQUO*P2ZzhFRtA-YtLGq*29j~12&>qz(k z*sQ4(W9W&BV%jaweBTfv}PK*)5~E%#PcjK7F8pc9h_rzeTB%fMQ`YtYq)~hMA{d8 zel1**$UuD$qiBm=4&(>0`W2)Hh2)RE(+twXXtkZLvX@KMSl*y4iegw)owbDi%;`+iJ9Xf)(JT#d+^O(s=Lc&!Tf=2TTbn0?)!u7KD$>3Jq2XAGsGx_V zPH0OYAd*Dq|I$Sb05rmQK7h!WFf!)r>A%e!Y5t!1_?xuJDur>NvJ)RjF4 z8s#DF{GFMZ8M25TF`_v`Z(X?TTUA_RWL&=TL~YZzKJ}Mps|OcD&?qq7tlXrP=^H;k zntNId&MRN?JVXZ=68&r{mfIa92e^3{NY&vO`js6C>?maKzm3jrF7kx0wiu?M>1X9cb>ESYxe9aE;f>Pqm<Zpv6*xmd_!?-;k+y1@X~jda`$dY}x}LlA5u=d9PghGGYNMlo%7*>{wo zxzsP(v@0LUmD3(Ltu)ozWLNSJ6ojhjC`*+XQ|8_%65d958eWHDlyi$U93$oAA8*D| z-+=|;7*q977H$M;O=GN*N&{bqy0wWaP-cC>q_D?MqnY>`cTiM)At0?u^2rXOZEd20 zHxBj-QI2s)TZ!+?MEO;2sV|WNazO^;T6-^CBNBY^WX+rxRcwOS5G(vbeo3G6zV9r1 zzVtzl;^C7@O|_-`6sH43@zk)biS=#7-iqBf3`|0Lyul6x9k?!3lE#WvtAEGlCTYJH zv=UuSCT7^GaOxn4DJ3)BnuJVGoR)n@1e%)}IbrZv5uIgFFGn)lXy`SkzQR(hSFV3i zCT2~ioR0mG4Rn_XQL7NdJHbY$>t1h_>#HpT$28&=%QI{W4oV?K#TYs#y_zb5k#zKb zLU`qgY{w;TSsJTDQ@PCNIwp(dnxCkd}3yBPq49oSu}i)L^@xb;1x} zuMBP>0p1Jqbz^H`3)V)~ zKXg5bO*722sQoS{8K+4}MlGe2epTd6sW)(XQpU}9tZxh)UQ!ftla=qkoT$5U9*E)Q zV|WW{rvO5A4o+#?Mn#_0;nRw~O3t})TbPn3z{#wT_pY3^L&0&uT_aziPz-E6>zG9W z9A%gERGY;H@uV6$$*Ocr~jzK5+XXQ3JXe9;v(m>ctfyDb5BA$?z6(XM0Wf_!<}}95YPlM zbdeEOw@-$111PE%vy~=>iWfO4CG(7z14V|Nd#9>(Uc|FsKFX>tp_88QK>;y+=5Lw1 zT@cLml}RyDtvJWok%#yDJoX0vz`TGZ@z^KF1QmlyJ>_Y|pmMg;V&A=?5+l!IEwHPM z@2N%QODklL3^Vx4v%INEYPS% z%?i5y^W#u8sGEe~Ae2eGji@*2VB8_H?3vA(QhvfZU2g<)BF(4e@6nJ)zww}{`Y4Rl zjJYwgP?m|k8V2EojRdC-Z3UlsNBEyj1Q|!?(3ecFEHxHM(tTc!ZcgUVB_bkXP{1-`E?J~C z@Lm}VifZ;&30bq8)|?bp;%8hRZE|6T!#qi?7%-td!932)C-JufkCBm|AaivwT|oOb zg`&co9zA;GEaKq838Q275tG?Kxqjxzglw7`XN%{3Jx}#jOHT6<@@A79#3=^EBW$=X ziGh|Kp$RpTGhRcYXpyNj!cK`@w3Xj2>loP{3Ox#8V*F+jgK0J`1Ob{~4reduQ^T~JQ>drb)$WMB0aoaHy9-K#5AK1|l> zxAIm;=iD=)PCF?$FmjcXlS@fS(1JN)sKr&E`F(dzlmq^eKVSFqW;9F{#XnX5l(ce3 z!(0#MF~%AOQMHA3YQZeAwEL>h93g9i6WiH9h)xzu-)4nnXCWGq+|=_K{MNE5cY0q= z7^ZNHtH&@R?Q}O*7>rkd_|F;%X=vf1r$aUlFmuHEU;$<0BJq? zpp!>eJi+ec&(&$n#G__;;>H9InuYxX`84&UUj9gKs5|w&#nc- zTN#dtV!bf2Uelee9;RpLKLlHUweIHub!Y@GWmw~3!OV;SgQ=wqPi70d2ZlHWshTSk zbx(FU+kC`lWL9Lc;L3_cEIpSLp^Hq!WJHCXO=SE>3y2yV$BBRz=w`AUiB?cd87~(0smT*3vi_4?GJC zGrGH0c-gO!b&<>oJ=kL>FJ!GY$%LVwzGP7?%Gi5(*La9;9sMQ&0*J+k(9w}%lk&h* zPYD9E;N>E&TG{kh^6NA5+5m#iaPMy%NQZWqa&h1~ zs$&-5k@xUM)0r6BTE+?c@yHO%FP_%l>n{&SQ3UzUFLow-dL9II0xhM>H6_CLg8{9k z!uO`DGu(rQ9e{+wCBYsx8LT7LDMYX!X=UQdNt}sI)hq6nAY*cD zSyXg$wa0>R67%5|4ShD9(XpuORG}cp-1au%8CmlR=U`~%M-2*)0_^;~;f(_5Q}|Tv zI3$zwDG!Ds$@f?kH-+g&@OwOvbt5Yb!i0gbkdHTmF$zg@xjAKWCzAVznG7O^^n<=a zYQ|aPA^72Hhif0rzbulf73!it9arpmFSAyPQ)J!L*u_n!{+!M1Bnmt5W@z|bwV^Ob z1hcR40np_0iu)hR9>((^b(5j8*G_(Fq#D;=-m(Irtl-||L1FzO$#MWT)tnMP1suEn z*3QX|B@I>#MLgJ^KF`h`K_gFHZX(lxUEbju?tA~j=#Y76v9UJ&40rLTl}p|7T9>l`r03yW3IWLW0F98f`Q<5_wGc;+Tt!0*j@MiwBKe)9{SOPv3|` zg(Y-(a-2=bGT-TO!g!FKiBeoD4WD2u`gU4iIvFLf^n!rSMhQuT!F=e_u=K{OA`FI^ zj3WkKqU7kt1<)*s^=6lH!FzEbNol@7tC9Dy@S)f4DB1|Lht{&{eY__Uw^(@tF;T>h zOUZY;mEWi^(c9<}L{&qFl=z9Yej6>E-@Kj35)_C0~H{A5w zQ%LhO+RkNfGs1j6fkf3Y=V`|1yEg?P3*W=TJVPUzERyITi{ z6xG!Q1ge)Ykg5rX0tX*{-NKm}tovQ== zW2J}?twxn$)+rR)tj@*q#u56HwAR!4J7Dk0H_QyW-rjQr4pbWve0c_@D5N(E^De6H zNDV))NOp+1=eAiz$BOH8EjgL>(z`5zQ)la_i!p)FTp~nf08mICb7ZPY=>`P`_UCC* zda^rRUF_?A=Lmd%BNQZXhQxJ)3d&U=QuA!hS>P5`BBy>tXq&@>-VCm~H3_RqrbF7xc6qjJ_?#nC9$IjdkbX|{F>{cCa=<&0OwCByNfp%;pkG#Wb*ZxN zXk$F+NxLzj|1n5t#4PQO-m%GM1)p&d+3*UMG51enk1At~WC3~dHtS3LU=3l|~`D_@wz?hlvqap^2%uQoyseCIwbj_sNyiU;*E zZ5!jC*gieky&remy3SX7&m>}~MT=NXpx}!4Ef7S)=yw1F3G+n|-;7p$0EAa>n`8{A z@QWh8!1vfjB$CLjXQ;v}F35z3G89tFKV04TWEX0`dc(Rg@M&D}Xt9ZIOAi5iq7f8O z5j5u`#!y-8R3|}*GjwejNm|)dkl%QoKmqV}dGVFnrDUcb$dSZt7rsqT*QATcU5;6} z^IJSglRNaS3c0?*R=$DE${*r1T7rD53c0rA!-PpzT@caL8DTU(S1omddVr5qJB*uI%YCO1^o1*^#6W~FPmi-d zN0soDTXio1ILH0~1pS>qJUhGHY%O_m^80gwkn92sAN z?yFA@%PBHd75F8=HKEolFx+#aZC9G~;`Z69N2$_3--{2gQ`a}Q=tLgA5)M_iwKsZpUlAccJ9M)7Wfu0} z`*UI%GobkuF6!Fa7v?Au$Hv9UKMRmdN|)4QwFh;q@Uw6_?N$A#%=xlYU#PH|?nPDH z(dB!dM8;6b^2+Y8A8<7E4s+3K$8G8654THXgRKrfwIc?`$jid?nNTd{VTB2s`en=% z(JD$@?}?OQOlpE~C{4HGN{|FtV4=VZ#_h7bn_p*6Tm{`E74i zY&6teuh{yY)!%74t_bJ@#oIKZE`@O;c=&AG8u06FtUW69jk@i|qn#GQg2?*S_c3^> zDUFHvA>jeps^e-+W!g!sTgNeOIi?uk_57(BI=c(*54mSLj(ucR#ODbrH$PAI206D1 z#2A`rzP`Py9V>}9FLXoWIZP0P$mU{phtIM$_nz&CmG$SBZQM}lbgr2=1{{da2gaS8 zi{odAx$UW%Pkps-qzj5I9xM=sbD#af>~~Y!J#7`C?HeCcx>jm7o7ty6K+7p0!i{8a zB*>vaomh}Wq5Ycqf@`M(3Ex!PfBIcuZZ#)CR3{4|-drW(gJF-aL%i;VgC7yd*xW|3 zCySPMSG)k!T;zE}IZa#60(DpvBd@JnYCKPvKI~&UsV7zAKfv>I?z?ux8$F(Q#E-ju zDZSUhvbj~ao&Q3C8f#~o$u<6hp%FLxL5`g3`Et73Jz#H-`(%!VrSI-$GP|@KzohdW zW4di{52H)l=Hg@*P>DX;kf9cB$kpplwsRpSJBc$50p`_$5nmv|el|*KGNFBr_3|n; zVOX!CAR#$piF-k(frs0(vyPwIT*^e9`kS3Jn>kaOu5(lqdMn%ajZ3W>gr2e~Ay}a< z&F4+i=$LqFA`aQDqr{&^70?gR{AQjywna!r>Fu6GHAV|R#;U}|zmddRjx;PtyPod8 zMvE;yv5YAv55iit15rW8KprC_N>(;RRx4eUS2+(9LcDEIAUHkMAjmoq7NkeJr1H2$ zV?6M>*|ExFp{?y0tMQBHW5|+h8GrXNcJ^#iIUeLQ>th+7Qmi?l*VjKX#C4Olbq|Ep z8;NW)>4Ul6TfE!D+MypCo^US6tdI@gD=w++D(mWI% zrTK>ep=Q$e8Y>1V-w(+n1}R<7VG>w#_tUfoTr{3uz246x)rIV?4YI1c{QO|nhTU}X zscSn)(g!sL#L*AFn-fcKQfi;W^r2LxyT@Il;U>ms6#~oq-HrpP<+kK*?(Ya&s+Yn^ zb#{8lVqkehAQ2WN<({H_{av0s_NI6Sjod9d9xIc9*~o0iiCvjEe3JwGMRFwsHx`1l zIgL>1*MGDCPPQw)W{af7kXiF_j_3Ff3TF-EF(KyB-t^K@{8dwCDVonikjq~WVluyJ zi%r{`hAxrJ%L!|z348E0mg%(Lr~LBSt?BZ!N0G_ht5#BC}tbSRh)?4CyJLl*kQe0|KOsJJOZEwJ6^JFhn%v?M=zq=D*Ihv=LP#N%`7K|5dE zrfg8H2RXGvY5iWb)YCHiPLhSiGPhDwjJ;5Zit&|{va?|N%~OFDi)HxzH`fI<81+{X zcK4t6=Htw6p4L6nYDUsY-*mTORBse3!aL`(6hf;nS=^I#BJHxA+E1upxB}5wr2fMW z9pn;H3k}}B&#JdKWXuDtA*PakQdYP1Y&+e8RNQk>O|fys58@EGvM336$=Q`6NgcJ2 z_&ysfTW~>O_Qf@m*Q$O@fWG6Wt^>0kJ=BH%v}w}fS>#qxu17UH{xqsbY}SP;*JdYD z2j28_6{{K(0$RWPs>t0n;(!v(eHRY-;)UH|cCh;n)b6!ytJ-tp=_aNNpsbsM8$9TW z-6-3s&=5^aQjqL`#)93t)NSll>?Ebxq5BU+B>GZhY64}QpN(SoMe}fQb+hf@%_eM@ z9W3oXPqi&6pF4+G@t+1aAO9?pC1ON}Tz`HDn`#K~a}13pf9Q-h3KM~-KX?%+xbmHk z@4EV&Q~jWw1-$@=#Kl0wZDz_nJF;#zHmM0``Mv7h5U2~(W}uJGp!#B^+o7bmO@`6r z?vCGzRU9j`n13>12Nqbd+uL6$2cK46H8j1}s$JesbpGPLqg#&vL@ zQ{7ay(Ije4><1>q*fHb7ycFaN zZAM?)^vlocCF~u)^tdZfzDK<$(}X_XQ#}75!lL!hwZj&}j9$<2B2tlKU z7(udXqv2RC>QMjJSkK}Ac)$)h5ai$mfBltFI2;ect)LsE48!<*3r$8HLkaee2jYyh z9|yePufO6}f`Ku9E9eCSGmnO6vHX1`e-2ph65A=_e~p}+g%KSdy=&-3Occti2-7A0 z`$+yARpAvzrNZAvNEC`gl86SofmT$9^}P8tBf{~o!}#xWqY&u5cYhn~-$Bi!JYw z6AuOH_Q`_mV3aqs`!(;U(#Er*W|Q2tnE%Wu88cV}vU&5N3}r+lalfZB<1F@%G?8u) zeeCNtKc{bkFTGo!Lnn1I_v~@g<vt8eyw6FoAtvuhV>jPqepH~p+y~H9T2yME)-1c35>p0OIiXM}J(oOmveA2=&K^VM)u9=kUPLcQazxHiJ z3pF=RPjO-b-#U^Ezsm-FsxjBENLc08c*-tP7j!hy7+{ zlX!$|>-w)b{r5?rE{uzqlMy;<;T5NyDJddiH+2bW)h4gbMw=uFLqpCdXI;Rtgrg3Y zgt6Ur27Ws@8Lz{C#{s;^-4u!HHVR->1BYuCC>(R3-llEckOF++OxtMl3G390RRyQZtd(#=R(*RpQDx*ro&j~tJu zyb>Btm(!23uP-=UvNW!L5r&k3G8u<&=i5!&sJFPIBo;m+#=eImCX@Z|6k~i;S0!D} zzFOpKr$11nmAt(4Y3q|={DUmgsVNMD2sV))2P zpDrfbR%tG%e;p;gKJ5$f1;L-+ZMXGqP) zuJtA{jbn>FhrR2+X7GCjVKc=2;YtWStop07b)7A|m2T&d#W66QL%|0*3=Ty*1iwoi za|lkoqv(7)h9j`OF+0A2^y+8e8N|)V-;$3Xg4uN(6HWd?aSN z(5oo4nn1&p7#o_O$@yWvAJE4yzv1!=mwL^?FdqkEEqN_E+6Q`LSqwCb+n$UzT{*1E z^w%Jy;9`5yHzj4SP8Pt-#nE@j^|h}u+K2-AUh(*u4y2*?WOpHshuHw#zSHJ*m~1!? zYd^<86990%POPua2dGaSNdgGWA}hfk&3mZR;rOUP=nrQXiqZz&eB}J{p%1g(Duyty zPfch!q-Nx<`8ZG_T+$zZ=JIW{z=b!{h`qVv7%$>7)}M(9>j>ESdh;S7ph6P2V)>3&9 zreh)3mz#^px}VcsYdhcID)4+``?hN4n7K4X6&ZAXxSr}S+lRfb&Tm;OJdzJoGO_}w z*4rjRPzK{2b}4_8Zt>?=8Y1_G3bfW2ZkmDXAHPfhq`v{;u{#dVN%>DevGcQ@!J1aG z0%#rq-9EwaVHsLNC`Op54>-Zp`_Oy0K<<*ODjqEG%4blbTnvgUej_6KCv#|yO7PGQ zYR3#)@Mc%kxb5dGwH8{6NtHz^^7xoZ1Ny{fssI=7UwmxERaVy;=3J!g`cxharz zy~H2l+)n40!4%+zy2pA36oik!5Bx;PqdUUkuAz+`MSjyhsmTEn7eu~FH@|wd5dA#& zHq}jg25pRgqX>vE8g)p5%#nK(P2y7a(`D-Y^!WqANh|QCwg-WLU~g`G>J$BKl~m{g zjf95zS{{-V2lQ0)Ie(a?WO@ss+*Id6?DqKy)u~({yE7i9j?|{`qru!4(?Q46IvjAm zh!!$cI-ghj7{AV=1U)7DW*?O{Y=q(RxkvH!KSQj~xno7rw%_uKy<>!6xb-B9u-?24 z!H4%GSBdi1BymnM_9Q3oAAwB|1WO3wLA~Qg0}Py3@O)nAsVby9KqoNjj@^)Wc+!N&-o-0J9l`MFqzV0FFe!ET`z~ROhgk1|Ni*vSEezp_^`KPQ+9*$u zgY*qgz1Lf;0%&!}?vSn=?`H{$ORNLV8_|y|%b{%DAV2LkE;=T5*YV1>C&81ER+pw> z=NoPt7w(`f%(*#*sHR?bHo*`(gLC)kE4=bLb(EfGI6X_e^qyJ^Rk-Ahzu}4wYiQbA zEHX^A12h-rs87>?m`^P~F@3F>))TT=wUQk&Bd%;^kqx4BwYIa)FUrxjRDSc%KLpfN zRgN5Nf3P2R^Ech1s1d#i?bmf5oFZTRO1QW=|C3mkP~uJ8fs1E_TMY3H+QYo3ojG(- zfJk6IDjQ~}WjSYt>SyIZUBq+~z1UrxTOX@eT|GW^5yZj#_JSj8wZyNv8KO&QYp!D+ z{*+9NihC|z@OC>Xgq~Kf%{>BQ7rFn7uk*yK4GDM08=RSDafTKMC$Tai|Bl;FHDPy1 zL!nIYP}!}OmbLq30K`p3DNE!@L^Oh=h>>|cnhm0F&Ao&WEA6tT<3Cye&u(+OF`BJe z#sfPlGZJb_v#dwU-$XkuV;Nop6yn&cNY{pMds^O3|ETGtm4B+!chOR(GU_lTQzhi< z9RY-DTS5be-U@of5lC8FzK+|=zB(v~j8Zfy+r2q+I2Jqm=EsGipZk7~*koz&AmesLF|$nHo;G{>wz$Mdswmm#lNz3wB*H`~lc?YK-Y+ zae5X%Ybvl4;HYyp_4`w1QFTEUN0&!mnyTckb`u&WTCw)3qx{~IfOWbYvrCW|4W7!0 z;`v%8acG-$jy2vv&pT`7PDN{EF}$Lc3zzH;i) zn=S^zv|=Lgyduda3V)+Ldnxc`HfV$b0exC?DXi5I$s|N@oilV+?msnb%`>m6-b~BJixc57TU7D+N1n(nvfA)FBNd)(qb+@ zRw6Xi(IQPs_@v{+<+B&LwMU;4=5i4Pk0!BTm*~0cvm=X1h}3D=^r<={8Pz2Q zaZ#Y;sJLug{G&Qt{(6Ct%$`Y79dRb(Hp^gS80WK;bBUFVWNwFzvgYhp#}2!Z!arB1 zs8`lossZ}2{X94b(AFoW9U$R0zIC*TW5|Yg7`bXOt}f)e*$M3vc86>=FKjhy{c8Iz zd%m%-h4FnUi*R==L$q0Bwr$z$`rUJR3DUc5eJk9U;qJ4T=!DtqZ6D=usN0wsFsJzE zOZrmj+?&z1j3WE?&G6c3CA|wvJq0<0_1;v+2LBhs{{aQBNcw*$A#AW1qgs=D*!x-^ z&r5qcNmI~X+e(#CIDs%{YX9qjXVHB_OzOns>+}k~d4HMIu>+PPwd3!kx-Lbh*5L&2 z+-4iEyOreZf)tG!%szp`jFtWCuq4*I;M~Lg?9kErH7}k#vnJy{(LimJc{1G z!=$bn^jhItzNtd;7Z>T@-6s2_YGLFeXJQhR@>}FSOUx=jb@966Iac~$~j2&3Zz2LrI zSNYT?oS0VjM3!o)-JfuAMUMd{`TEORUuWR_JX9#iqWv)5)~t}Una9Rd)gR;oSi86^ z0(V} z#>CY`-(_<7OF@wgaiCTC-_(mmUNmhj44mi9Oi{P)m)~m7cU7NRsNm%{KX(y&uz@UL zrA}F~xutt6(q-Yy9}hAwXeSfyW|(4cptMmFGow$mpy56ANpSFB+WIklBp1L7EQ3RiRCZ91Z(m`4fSP1UYIw1`Z~sU)i1i)rW7r5C%h{hgGJ z7|$5A1j+3xT2ScO>Ix~p&`mn1Q};1Y`MWm`hvzdvBa|R!hXCWLYg4D5^~qfa7JaDH z5^V_(U04V%Z~vMky1&p^vpC>UhP?IJvcg{d4X$H9wVL+>~(7WV0x*=9;Nhwbnc--2{Be3IA$??-%{7KwDQl5 zc*p@ZBI-K6O5wLqllBQ~Q6v<&=)C3j{9})HI>+zGXhy~9?=ML2uu)n)v1Qku2S+8o zoOsI>?6=5PE?|i7F5_Tzmd~T1iJ9P@ju~4{5z15eou@x)T%wMW+P?oR<|ad6+4pxg za?XRfB$KZS_Ad7ObbZXrY(g8(P{qiKK_Ng;N+Mh_LE_)FPxjm;pB4vsr&pZiAJOX# zJf9tULlptMit_Rg=k-C+UT^>~Lqgge7%U~VU28PCau*=xDcp)Pn&d_T$*eCxnV`5x zmnMIMeT~&4qV5U2Ma#itcB3jCxJ28iL_DonJd&JN;Qo_e5tPv%`Q2K{O)T_tA32}B zs7B9XNEdnZnyYQy7Ia<^dU_xYxP%FNY51Sh*7MtcrJc@HJtXe%f~I9*EsBK#6h#fS z7uOU8N^N$)mW18Vtk$BF1QiVkt2LXd6vTV1c zm{_ePr}M^(pPh&^xuF9SDoiO2&RRQ)BKsPNWi!O*~ zZN**21QuR_deEShw-j5aoTo5+g`=Ki3Qk@cI=uVU>N9HT*jgj2^}nsOC+U$J_yiS>FFcF8X-YpQ$PHoIaQ!d8R`Ps z<%iD_+rQ2%bUNwQ^mtVe_4DH+P;shtIDYGGvW_J3d3|skUdOzJntG965$P6zDZw!n@tge)G9jD~MI_sY4D!sNv}@rZ;FcbbAIy(0<$P?CN~` zOT*Cw&j?VAtiQ0ti+`(ThSz5L&5Y{_g9F9FBPF2d2Pv%}OFQv_mgNN~xz9xvbMFI{ zjq2{+uYGWKX8})Qu1C`2I6(pP#toc=vdv|Ug}6D;{6H5!Y5=1E^5hBt3cK%Av9WYg zq9c4icHYi6ggeLvZB~t*<=+20C3AzOWnz(Wh5}2Qx=$En9k?5D=R)=9VmBegh3fSv zP+t2<-P1C(gPNv*!=IVgUIRTpYU0f8W$fKW-v;RHP{{5U6sujS$>VIT zV3MzHWB!1w1L>IQ^SZTU8I(QSe-PP_a-V~J@%4sMjeRPo|7oI3gML0{`m2*8yAqvH zy1$2k4il65LsZX`68{e$p2pvj7!=QAi0`XgzI;zQ1}qJK`YY&`Fg+4sE0sM1QiUG? zo|R3Ho&-f3T|EB-*zaJw7~e_#XbcqU#XD#{+88*Ykn}dJ@bW_`uXgn2BI9DAl1t7Q z^lvkT`Z=+N*S2)Ds|Rs676I@b$@+h7TzfcF`5T`Z;}*(o-DZtka%*XlC^RmMGV8ic zOp(Z?+!iI*Fh%5HBHC=JXxZEgm0@?3mPHCf$l`IEaa*Br{gLJOo<_gtXV>$bzs_@> zbKdWJ&UwF|@8|RRzA>vlFjvf`3pHwdCntSXM!jrUVYv=h3rkbi5@wQk4valmzi$2& zs3rII*CX6wXW%48QvBY`VAx>v@zgOak#SOOlI*p|i(pi>*zKI0(yFlz<>{?i@rDvj z?<;>G#cv1XZos0PPAM!eOjg?ZIt(#)(*z_n(Ry^SLHh#pY9n^|J}rjvY4|H@NU+}r z&}I){OpHYdA0hd9(Yc%}|n*F*BF;scV2vVV5=^U1+!B5 z;NLT04F@7Zjn;wUBG>b}rmG|<^5pk^JABYeJnX6)7@Tgm98`JY#aT`M2f zObts{v7l+#7d`XsY6ddY4d@)oFV=oQ-|e?G9~#TvD=*4q@w-!A3Gxy>g73GnR0->E zIrxei!@;Bfz#U>}8w>1?yC|I_cy?NpVd#em+vao5qI03pvBw`=WetL}Wj3%Ga+*eq zyas!EaR~$N@;>a3Q8U$zho}I?`R&OXXj#d9A>o>}xZ2RZ04Mh0rZ=2RXx*~`{+28b zm9a}iPPYu&&`4Kf+TRCG%Gf1OIDV`QA4yX4KKRI_VF|b35@CPxqCd!k@9cKJXK@r` zc3wDwmkHm1&=n%sH>H*>=<%(u=nh=H!ydF%Si8K4Ds{om0WAdUw4*3GM<9(tXH^8k z792f~24V1JK6o15NP20PO>F(~48x#gw0Z|MgQs7lDLPhtS5Lth*&wUFDlz|bf2L8^ z5+&(VEgxajz1AWJ)|QdRCCffo>;AQQ>zQvnwM$rl#|gID-?&sUSC^)zvd*0y>wa>^ znB`du{TXR19!F9~s8Qn*Nn&{@6z?S~b5n9p_;@vOXpZD(=YZEPgJaZxVD1J*d0w^6DjBTxa?P2P;VabErYYt5$0F*(pp8U*c4>r>`g3GmMQG zi74lT6u!sl|KLK>vuqHVvdRwnU9eE^PtTLRdtPq{uiPAM7bHl-)hBEp9Z*;hdPYMU zFbPLQzPic_lf8wVL9d1&@*Xq{71;KO8^NeIB;>+CAS`oqF=L zGFfQ234H^)g2)=k7@rxCo!wQl^P*2*WvM$sbZPwW{Z9y;(D}fBq$5S5^6)HU-X&r! z?nTu~qE@!pf@B%%KFfgv@Id1%K|EC~QtMcHJ)el2q`reIm(xl>K(0tM=TxP@Cffc9 z)^Z7=$wHws7|(g!6vrj=8S92&F9N|SuN5LE9y)f61wQt;O_!hd)-H)FMpxC9Ju4T@ zo9ZeaA5(B%&+Mja{&iAf_WD7c*>u}Jr?Iu3s__BSl4XalgNx!S{M*bmsO3Cr=FpGC z`=N8w(sUoruNkov{6F247KYVT6e~!;_UKwBXs6yw`kDg1K(*t_xo6#6Ph2NlKd0SP z&{T9!*Dn3%2BG1#L*yV}<8g^7CN2Kb%1@U3tnoL7b>YTVBv%iwQl5Qc;hORC`RnaZ zJfox%d6|MiH01}FoRy@{a_B7@3NO?yIOGrne!M%aPymAmWOZn_xYrjr zAhKkxD_v?YCc@4{f~#h$^IrkNTGkcY+NOc$=Jn30{;b$$gTh z8FjC)Epf#&3gpF*)t5@UyPS~Q6hRR~k>5!ddnK8eiZk=LK!~J5@aa%NprXsr%u7KD z)t#+?2&BzP6IfEm0zk4T7J@b(?Yf;&yQDPBbq zzZlyH>b0aW+J?O4KAXwdR)oW#Q3jcJ2{<%z;9A~@k1CFW<5uwSs?mo4knFNxjGYM_Z^QqxrgToGw|(a$CR z6($6}U8K!>Akya1;&uq}f^v@Imfz&dFSDJS;x%?%0fpMwSQd_JL7`B5NCDN$61TD&U2iB{p@}Bbzk=mRC*$Vfli8!goK14`{cB;rjhF^3t#3~#tjGCvE?GNiuFU1*SesGx!w8|@LV_|_MmN-vA@35Y% z<`%@H+Z1;)Y9*Zitvdq`Q{Po-G~EgROZZ|DjqEk+Mr78NhJlD*j5zXGP}6v(rtI(9 zE3n3v!T@!j^&=WnW7mpDYzQ4vKIOW$P~wM_`;NKYq5EzHeK+N+esO+=wucwH-5gFA zXYld*lg*diY$YLQiyU6E)m#h~dtBmPyPbE)BDa4kaEFHSPPvReJbsAx*rcRSq~=S_ zW_8;`cdN3nuCuj*25r4bg;krvrW*$XDvF_9y0C_tt$MeJpA8phTTUq}{gPOZhpND< zmDcp0Y}RlydBy)3Y* zm~{GZc7C#DRfe!LB#28Nh1+^j`Fe5pm;(2=D74Xb5_57g@K2%BUUM;et}g^vvp%f{ zbJQ1VU+m~!Y}j~}Y?P1pQo76_pUk|$3UN^1Bd7E{oGtI54JI~D0|()Cuu(A`qlP}` z=411zFf>-rHQVEKr`<5>^l#NCKnv;Ye;J}7a;ezZ-uT|KcajG3E9Zi6+vPG;J*nCk zG;(wq#G8cS6Ds!Xt4H83yAKKgw^I71(Q)`$ta~|-XH8I9-EJ3aXR-6NhqLu4YeDUg zC7_~~w3JO3JMB~{%qX>obHOq8RRqzpl?P)lG14wXFOC%5meT^(Td+j$<6|nJr|K8f z9V9$+_{QTe6-c12*<=9?r?&lI=ug5{V9mI{LKk_O`=va=1E`RxwzeBC>ot``k>ll0 zW2|J)=FZ^Lip2#|9Rk%k&ECbepZ3U``z4m{Q9|f}ftu`!>^TBK!%QtYWG%iwSC8~O zT8!;n(+d7^-Nk9buH8hKyHBXZ??S2Z(PX4%3)rDs%dCw1tSD_MUgx7qy%Nk3&hVVv{DeA1e)AV-t#8Hp zXc|kruXCsmZZ%w-`{Ry{+cYGhjUI){@CO0ex-_I_@eQh~kf19>N5x7839c8u5#HrRB359s~($$Lv zHCoqf!~~UtZE=$*()%RF>U)yd1WIbE;*vb%d`|?qH6=y#taK(;2Kb;OwA*A4dN^MO zi`Caj0t z3nG;(pFgl1%<5kp$DVDsU=i#MCZt90(VfceW2Ny+EU0Rew+jotTJGl)(2){+-k%zA z*c&!8C0>;I16pMC)GCE_P|fQ2y9|{TDp|`ZaCQ#%`(AOGb)He=nhZU13kO4K_O$&; zXa-WoUCf!Kzys7X`ic1N@J`si+20r+KCH|CIy31!{VghxWl=t3&#Kv3oaKuyc^|?* zJd0X9f~EAaVc|=${2g@P$x|!5LlJu<>3OO8kFXEtsV?H)mKB>ZccU$6HU%nisxh-V z``;!}j=0|h!5gXF>(#+6Km{=lG<)b}hzF4!e_sh5Ns15uGB@$Q zk9_bmL}lb=q%52-nUnPu-x9z2X8I33*OiO~bS3qRb}Iu#MNw};N?g*2lQQL{p`N8DI}p17mNK0IP0(C$R>-Nm=EREi)vX1!Sg}60F&(UEDmaX zcRV{x+mJc+JWjhtx*R#q<6ty0$;8q19?jc@x|(kG8VHwh?Kp74abiR#n8LsM7b?7M z2tQZv)yudiyHef6qD(|XZgIWX3HKsK4>r^-J14WL{k~Z_k6CkeFdhoW+aiMnee*u4 zE=*SqW_Hkj(!Ru42&{sv&t!eqGcuuuci)O)$Z$lCsl0NwubJ2oKIzQs`Qb=-)TZIg zK1IrR`-4?^d>Zz0ce_lvM&LZEvEy?P{CHtM`WT_grWXel4Wp-feKzwU+?ZPS6kElI zHGW%R76jGytm;0Ok@*)YN^4RSiWnD`-=(~$WvR%}Eks@Sb2+;ZzSeHUAV5?!c)K zt4(Gr2~Ow_**hfY;=qB`b20Y{kJLDCVbU}Td96||2{%#B-D0maDDUzL6&O|ScrjNFws; zZN9}Za<^i_sC0kngUy4fifm^GUG>=^`oeyRTO>Xws$1owW*>T3koxEwzMURWIrMP( zw-(Wb@zJW$t1!{p+$*{pVP3nTwA+xd6{|XGLGVUtHNh<5gYt~&0m?Zk^(il zEHX99$J9$Hr=zATC`WKGlpX;;#+`O^Ws2tho@dy)aW|J!9&YG7Do6Q<5qZ!A6qaOu z?Z#~;ySNRuu903O)==ero1#`iKT<9R0qXDmLQQC<^QU>cdjv*Puii@$a`mv&m6~K= zyCtzVobI;en5GmY(BUoR@fi*7gB-*pH?KIT$o|Ekng)+Tn0*)`8rOpWZjr;fAWq#R zk}=TzkRdk}_^#tTYv22RAv`WdjJ`PipL1r8tt)5!AM^?Bjo_;&7|w?Y=F>d)$H{Hb zG7L)*QRJKT)e?cUq2ep=Gw0hu;(Vt);FDG$gM7eP>b9TLw;gOQI=EWWE5JHlA*ArZ zu8qVBobRnq_e6u}Q1go$mqnRE3R!Gl724YNlAOE~)To^cx01ykA-ETIKpjVSv7Cze z3C&Q;`d#yv2x~|(;voQKK&ktHpxAxQoM1J*4j-IS4dL2hT z#oF@E{+!UIu4^Z?2d9a#bIEMYCzYpY#MqBMDx}ic&TXsCnMUMSp)mGJn%j)`%@Dhw@LPLsqOhHn^UB2-o#Ta%^T}?T zTKJ5dAa%eEru*9ccflJ%gMG5feT`r^r|{BdN{NvfbCsVNEjp54fXEP|po~bbrA9ex z;uBEofL{pO#Swd6`RIcZx!1C$VcTlgNNeP-VZ6l|YSdHdw`-rjp^|tAqiW6x`Cgn% z`yCh&{DTEt9J5KuC`3k53izIz7}37-@X%WHSE58_$Xy$G85R8rq^_|v9gH^o0gVA@ zBi`3;lERERmQ_7_n|ygAD+d+&e-JYA;ndUlX6pH_qPABi=l%GUKft4qBpPxH>f)8m zgPMO!4mEH6$>dAJItAOvaI|qP^Q1vgb9~^`I=6oG+C1!Kf`x!jqUz~?(mSv7y%edP zxREnZnFY5z`_3utaY8 zwV9NUbExY9w@Hj7Y1>cIn2hnop3<{R#@#h&&V!|R)zg&Hhr(aR5gu0s;5^^)l%9!) z=ilm9-Gy&Wi^el3l7#luHthN822gqOX;lvxI?`Dp4SN%;8x)lQ$tNgj;? zj4X9*TE9GQ=ELe6ERocp^m#!PKNftW%3dM5&Je5j?(=C6gCgby*78(_x~>P2GBJHD zZLA>ZqnN&4I7@b`6$hWHP|lC@102(_B8d?$+ULCP60>qG2%CwqF>o5U_l@$p43&oo zsD(3k?ml}mindq;OzRi_AT^{fJs`|RZVaXG8}IZ)!kD;ms2 zwDR%U@hB0a)&SQ^5qGS;I$rpM1DPD9Era5^a-ZcaM9uZVs7d6+ngUsuS_`ocJl>%d z-90MJ>&+Ns?a*%9(9(PUoRQth9id0~1BLEiU=Y)3b(& z|5L0U+mc^!M;qVs-6h^$3ld;|CoS8k_lejo&ps z-1NRl`5fv)UHpUH7n)~4n+S_DG25+CuCLt<4Qcm5kwE2puq%{PQv}(p*%Fyg4(;>V zrYc#q6Eaj6v=5;`iRWbBKc^8^M3Nyzp$IGLR#AStS7skKSd&MLaYD?>MPn-tW$Jm8 zZSJdKsC#nSnxT1?P%VQ^PY7ef%urY_aXkciD!Yk^-zLZHAu!&qP}E`p9{NaJgbs?oxSFED3s=qh~5d_Cn*Fq0CN z7B+Aa=Hj`=|CzkuLKCw?MOqE-F&{sRV!`~&EvnpiB9!Wq@{=(9v;n7c38tdfE3V6fvgHn#L1&3sX&%kspqvp!*XBK*O(_acT# zwAM-)(>Z(%&6fpTe+g>TfjT-;h5|m_WqKfsqM+S|mM2{Ws*T`}^90Fx_06Zx+4=+T zXznrQ#NTHZ5WjXUuQ;55n2#big%cyFKfqR)0)?0{CpSAX&@}40rf^i3qM(*|j9f0h z^IYH^?Wu>$twj2XT6kHiH&+yHPyv|6U7b)V#Got( zW)BKT;w>?%r4AMon4I5e`rra?yo!%bZ;I=dN+Llsa}!~19Zpo_O&Lmc{P`{PfWVzX z;d+cyvxPH8Y6JLDGo6nDjI>2mNRdIb&&u-p>#bYrG$+MziQy^6z9S-aI5PNt{#aCc z$~?In^0b1)-i<n|JZkDR z+&=F0(aVZ{c?_L-17?D0_Jp_QB;qprPW(gncAqq9KO)yJ;Dq0v78o3mp(<*O=<2RG zi#Jb>mtzT>C&_zYBRk7NtCaZ2RPg+8UgdiyAAYGaTHf*nNZnOrpOLn!6WCoN?c@`Q z{admdsPv?4*Iz^Mr7oiwsMP_0@*${K!Rr_Tq1Mp%D8n+7X=r|awm;n0e2(Qqy#;&A ztP)nV#&#X)AjcG!SLDJX_lsFvKCmtrRYu5GXRW6b%eDKC`Q_37` zs&C77p^8;T+J15VcGPSOD|~U)Obr@0d{X4!RJYtByx%dzXg4j%iUbP$SNJUI+AlIV zb0*JK#iw9)LR-0@5_=HV+l-0}tuXPPQOp}@9HJMuUus1`Y1UC!r=y;q46Sm^n@oL+ z&)RD0C$N2|HDPsbT5oO{Z%gU4Xug6zY%RxjLOOr&G4Es)&rL73SBuU<41C?_11ne! zec3sD&MftHb`ye3Vz$#?9ZyfDFUo73u^Ql8^(WT4UtMk%J0i0-R8c`?G9cg67tcJE znfsWAGbXldlBLq33Q4%nvPE_$7aMdc^>GsE3MmC%Wf@`wp7$q@*y$S-F^Am=xsF|5 zGV>-v{j^Swk{mW>@CIXhXu&KQx}>0mn|rNRu+lRdU71|@NB~zxhjRyu+jIV?O<^#= z$DF)R`1Q&KD4~O%SzbeyBq!7@47u+7V{L=)=CG(*0hNCGkGV>K#@!F=YA*fY7K9`x zRI6fZNhYSdt|UM${K{RhgupvIxEs4o;;kaQ-99LqiC%^MRobpDF~|P^_*y~U_(;mC z_xn3W4|+qn=?X*q$WWzl^4g&|#hl*t%O8UY$9mF*+`8MKYb0his=nt; z=>{nA9+h0NL7cD8Y4E*i@Yb_Coq+?}(dm1To!?9M8byB9(&PA-tQ65)B`>-+dEfTe z9KGVt>gssmlElqii(@4yylRThVa6#dnn)fk7`((N#Q-H9$ENi^Gu{_%PUp-|Iyd2h zCEl%BFJ^cFW&Df<+mEI) z18w+Xcs6(#F0|XB&_M?C01)OJlO$@0y+)##n|YGs5MOn7vu-YlSzOxkY25DTjg$3~ zaKCA6lR0>TF&zb~zsmkzO;S+7gM6Fm`nEd1`432~pAL^w*?p7Usb&FAx7P#gn3FAs zTX;WRSkJ;KW`$$el@i5A-)#{b@@brUv^hv=yI`KWmr&n^3EnE#%=95>*8E`%2Y?-! z`mMRcT22(_JSIa5UpS>lD&{eE?5s}JnA)-3s714_5o<0;8GmknN^Gvs&0$lRbJ^YX zAZo{3^6;^T+_ux4by;{X^T^`uJSj}A$K!o0VLRx135K!wp4o^F2W?5~o=)w=L!ZcE zQS=Q=IP_VG^ujR04JSpY8m=2A_xGve8xo5Kkub5%#Nz#v*nkX&iT2&?N66HHQtvM# zqmmr8;_Eh+dntm+{l6lO(%bQ#9sg zMn3(P)5ifKX*a3xPZ32|7j>@JuESTKw1!dQFm;nN3(t*;VEkS$L)70#Z}Ko*}- z#A3CmC~*1F5TnnJt;yJ-G}+|{q116wF8Xq=pS{el`V3M8F1^4ITcX(|KO#NN0qNwQ zVd%5LEInvnyPQqtCzyt3Z3-ldFbw&^9H`fW*Nkr6DpzE=bLgw68#ev895rY@$)UUj zQ}F9|tvK`hdk`<|W`~sPPphT|(9Q0(qa!h`<4{!(>wDEbs=BYR=UIPE3FVieSI9K4 z2qi03_Df$0aXBsIbLJcM^@ndw8KZ7DyEJFp@a?AhwiHeE@|z27Wjm{qw^ym4zh`29 zM<`g1V$9%1-d8b1`Z5nkW|%>j;|Y_|A`eq5HHVFP>!;`M1OQC^zh3ACNNFEAJk7f- z-lDK=d$lZR@o0#0lbLK+GGgtSDs7Eo&)nw;cq%6BteiEJ5{yqby2*~ zBBdVKq}Rn+UB&WS16slneA6ryB$X_Mhje1b5lNlTj#m@A)yD;3Y^GCjKQ(Z>-e)FC zazVJ4l2(*+xw^}W)P7brD0{3e^JF|pMhh!q)!^-WlsFxbOAvK*`o;Aj8X7CB;YDG; zH#K*Nu^O|SyrFK*nJM~5%f&?2PH$Ru;?Ay_!(##1?LC;G$Z7VM>(+Jq>hqId1ua0r zbiW+J2f)AE;vA-=vq}Pw27~X#hr>LVD3)5zJ4zgz(e4}?{3O>(qhyu+wKY900#c5T z4~)7Jr(b{UxB=mmr_vZF=@aN8LDlNs$1&|XOwRZMprM&}ub3=8& ztf*>NX=s4Lm3zXccMgwtgW$s}`C4Np_W-!vOEZ6$K& z(Gt+2V2#f~nsf^s*xl+?NOl$aj6<2C8$Q{&4`<* z;Cx>`87f#gY=SyNVEiSKAt@_mRZM9`!g21mSdDSN!KeRV0YoU-NzPNF0zY|_X|6}s z$CZ&97vC`c&3Jz!7W_~ciz{i}S{#d8-{|1 z6sRqSIAIQ!rJsa%_~+dCpA^UtGjKvSHBUQpIZAKiFNu$SubP|of=T;T5w+A#gM2Tdk*5a}d4Yd+;r+CvCuBf}5FTFrZ zEi!|po%?;}veKzKl?316v@zq1wP7BOl3m)Oh zUZ2V_1HS~=M z4_GPSe1rn6E52H$U0hD<0Tk^=wo@!mAlcBqe2($RFfyysr{$y~1%2yJ>p(Ylfnk_b zpX=hmxTQ)&z{}1dCVJ{pIJ$*z-MHt;2EzC3cHk)r87}(T)FW-FK|Y*r(sZOJDy}Vm z=Qr0Rd%HH9i0U2Ke(!es$VL?`*-rJRZ_(0RVz(7m5Ekev_dYH#msqWm*1S0%mUvT2 zWQqsm^3OLd>^I5qXO!4vlY+MjRo&g@<{S(dDN4?v&9fGL@8Ne?Gc(&)71+P*sG%NWmWipz-IkAzaC21p)6p}2p^GD>WU zo*7TAvJjG6s+mF2*2?OOtvd~x$uZhx2<$|wYEas+ZvR)w*llFFsfYH$eNxY|)egV9 z5uqP@khj|wm+3JoXY;=5&A84UjZeSI?29_yQhqH@4`aTT%6kgnnWnm;zSk-NlVIe| z+6XO#Q2TkBi6WvwWg_%m;Z)-j5;iN-Q^J7$P~)z}$+d5lA2|oldxPdH-VAO*VJ*$R z4lD4-dhw@+UCgHlujdtS_2nn(-)@g+gPk`okt7yvDXpLNC%b*?+JIUpZD`q<@vTfmp7@P-wZS2y_EGk) zZ$8S%M@AZ78YilLL9_d+PVfH^{Ml5Mu(!y3+B$t?_-yeb5u38a#pxWX@kN3Kcod*;QpiG^?KN<1j`g{B<(ggaNUQ2^0^ z-ybRL1QTzZ4__hTI{v5?8g)82x%veXiMWJSVz;8qxzQGZkUqRIx&e$mKTq{_f5WXl zH%1RvMWH3MzNco|ZX0E50qI={r$L9Df-&^iOCXuZH+;1Ox7z(&t-IxyI7z>_fC^lS9Nst%>(v)r#wy$ zczj)w1}Qytlws%cy z^|kMvxNTO!NVk4&!-q46?M;HOt9Yb=~qdiZmj&|-2 zE~g}@ek*LVi&G~ly;py8oIRuCL(V5*JE8e(duFLGsv`LpvjJ4ea>Zt6Up$XjGUdmD z=Mo$;rbQV@{%%DH2a3hl%OR)UCTI8?rx3HyH`(P9@BUshU?B`kY?XKiTHuXD%mVD7 z2_-cv4be1hliP_Z^zGadik#WE?gpZa(^&+~yoeXG=?pQ1iL6QZCgBqZf{BIWxo^ z*PRUv4IRuh=R~4KMmB;%T>dAl@ZR7(+|PBuhSh7`OclZl3tcTIv)pz^%vHorm>HmswOH}rayE_IKI98 zMaXy1?%60hZHWBQsF9De8%wAgE5Y=j-Ncq1*@MYfKl$VgFM;Ca9Y`1QvKsr-7nep# zjgkT3EYl->+4fMEIED9VE4^nNN8faV*F|l5bsKkQ$PBjW>q;X?M^f6`*9K^_3htij zyhT*DA{_iNulclJVo=C-Qf9mT2l>FoCe4Zge4d;#ihI#A=* zjw}1TWb;e!LH`sL0YC3%S#R=EHfyRFv&ePD&8}vjI?8rqWVmgj6BlLhJ!KtqZWZ%8 z4pz?j0MCI8wr}Y5HLnIfJRf%_L>{e4NmUz1^;9(KpYIaY5>QCUO@-S@Z;F;L1kUd@Yj$+V z@9p&ROU^b1?1g1J^FbAbb{=9fgu(fyZ!w@*;f!0?SdC97!$r@nenI>(DJ-&WB+AWQ zL3ZR(UQIQc&tRj2vdNR|R~tD6(CwJxNg7f3Ptd;!t60%p9i0OG&=2`Z7Vj=gqE}qr z#hI}vVp>9S_GaDmNvR_trGJc3! zxJ6eu&}?DqTY#V&SRHQ98rm3BhzT&$;kN}n25o|wPjqtaC2hmtrpzOHA z9x}fN*~(jOL&2M9zBjQIH3|^H+cEC95DYlOm~}E5$ME7aynX+mHvprJ$~eSVBfdTa z`g{sp2+)vAJ-*$F{^XhSr0JccSGZHSute6cXMobjaVUw^<)g_XM$+8gu-$yCt%B&b zd!p#U+Hjvs`-!chz!SlZ(wNMzP6#IA5IxW>-f@tCguwNt4#@XS+MG0@4Y{=rMNxKu ziewt3iQ0EW*=U5?<(9p+me{>n=2~8)=lEU2lVk4K41r?(kN=p3bUEvzBP|{=sa?i? zIaF#kX)*l>?1!8yG1`ld2-PPFb1=M;C8=j8;?O2*-Sg4tP&MOE_Z{;~f5?0237s;O%5^y{q81n~s$N7tp}5c%<>qyKY3$=tsn}DQM{2mEgx>v8 zC)wY0c2lBf2ZBEUlE!Ts$;1~ROjcO5j#v~U3W4=UYekzF?Mb?8;sGL1I58&?KPzJ&)%ba*0f6)8cfKDPH^ zb4%9ibbR{UEYC+K&BvWNz(|GV9Pmi{@&gOjQr}>*6ro155Vfa0d|gJ^bDBB!mewrC zgNfhXTA%7XYL0kC-}5NrG03l_Dam6Zl^r8uiZ>-Fgj;uX&;wMxMhOF^tA0Ka{8o$m zTyIMC=-_MGuXc-z+Tv468_-L9&`BR@GH#Ztipw(Cwy4C(WiCFD2>S9Y@ZQ$&|c6__p4`fazHZo&vU@$s{G zIj*S_DXD*x-$L6#O{4wz$El&dd=L62%+&5iTyZ4SLl>uoCYq6;1P-6Y066`JrRjC5QRqJU0kMW{9xgFO&=N2+^|BVNdx{ z&Fd}CQ8&Qp{q0Qav0|Z5lZ94T^lr&mO7z6wP$<+={o)D#VpMdFy6Ze@Y3`C-?iL8N zUxW4qG>)h8R6F?1a|`OLlR)YpU0g0m(WG0|9POFg9lf#gBHIQT|2kPyD}QVaL;rgj z$`_&+rw*MjiaeWHp2I9^c%iTzkzm$`MR>t%>FqBJiB;C~77CVx*Sh&NbjX<7osmBO z*xR)8Btw95bW}&Vc9vzYaW#_5-dLuLF}NOWe*3ALI? znDJ?&g;$iqT&bL49?Fhj9mr-iy2eabgesW3uRqHmo;0O?WptU8WhtZ@h3OGEFul1H z%UdxtWXA7t*gXz9mPJys@ti(&YI|L{qSS_ZylmlAG#pUlSYCb#7+qq;$FR;OK!9T& zcaC*WlVoyPiZ}n^tI$FD8R|9V`%0Z@r=8zi(*HJ>x)K+3^G;rp!{`DJ-WHTXV4o{l!?bg$qpjaiJjW-d@Gm?_TN^#l@)Hm8eI1hfQRARdIe9iG8rd?w*TY zfMWP-X(|-{@a&@%QVw7^_k-4^INd4MzBoS%6?PwG^f^u>8{`;!Giq?K`gx0qI~A!B zeR$+OubOjiJG6Y~d)3q1-TNsg4D2#|TLMotraZ)|EGUNKs&~nAg#da)WNbl}=y!Z{hk+;DSmSYCV0>^Wu55M3r^J6*hwZ)i?Rk8dDMV3$H7KHe-LFkLsGjM`-xWn&|AY60HL7}D_EUTucIg@9mIy!&>Ons3 zWD5oOUZ_>FLF@jaPPp?3s7s^KF7(|tc=XekI}Ws^a2!$l+Y9QCS;jq)GM;VfkMQ#@ zjS29s!c$5Syg()NvxP`Swv34giy!WL-^{bktw0(hYswKMA3bj0eK&Y_;d-GpzE0){ za2g?Qq6HyymvcL*=D1lCFPV{4&CXW7qXLci--fJ6%(s7i9SxuA`hOWR%>oc0adhzIGF{6s$9Z-Nuc~XTQ+VbJ28@ zirDAaVs#tp3g5I+$SRqO542z1b-dT4IH4$dW&(=%+M7oh?&teJoumxJwn!<(Wf&cULy+E zNMi%S=~xdt)t(>c07~O$GWHui| zluI*oDWESJE)q3LMS! zu~NWfHd)y?;&$1dF%jh2NYc^2iR)sMe*9W0+9EdzOidIIo-)bpu%mBDUF6e;xuN|W zTPP^sJXn&1G7NIe9e$Y|&Cal%s)^co8=Mjg@D_gP7m#lBYA2{i#lFO@cn`EQUVvDv zFqm2({{}-7UvB_Tq7gs!A`9AKrTR^BTQM^~CU#LkemYT!B4-YeJ&~J6DX86*`}{~v zSsdS6j7lnCU~3D3fiV*+``VNqR>W?DGWoH6prqjrU{Y|6$xfbr-ou2AHsLTPuXQ=U zbr|$0p%!YN5bV-DrA%Lan2pVl8X?U<%jdN|;>rA;C%DTkc84O-6!qK^q$Hi@hM=`` z%RY2x_jbFL_t0;sNS=>9CU4~#b`$g+n- z>O-pOkl7v?Oi?wx(mRorl*L?p&4<->Cz!4H5&0KeOm9S)LdeRJ#uqGM^8`mK_|FiZ zSU7{yu%;1BvR<*n5cchJgi^N zu>w2(g5IyAs6o?_ln5A2asRCT=fT^IA=N2RY#4jQwgqEj%j=@XaB5mmVYXyaa=7%7 zIf3^v?%{e#pCyv2Q2`7m;o`Le%F)>R1vNri6rG&`W+6*Yq9?hf$P!1`bJY0RT}kn8Uf~h&`p=#8VpnkN;d%K*BrM_t=k4 z0#M%Q10wg9CjiKL03Z-^YcM-ePGX+;{RSrab3jjS@Bm~YCqVj2aLDhy`_%E$)q<%Q zC2FNI8L+KS%74F^cNi{W)@Nf-L;tQl0x6NnRf8hle7OlS;P`tB-qP4{w#{f(?G(=U z%d1>YHYzs2ELSN&<>(!#+_$}f+Uqz0_Ql9+Ap|20Y%+0_E7^fXTRT#Ytlz%2&!L&~n#uXbt47jEafq~job)&Ao z?DWr9pDBvn{iCdZKRow9;emr8A09fDKim52Vc7yj;D6Xe|7-?~3uo1VN!+ZYc)35@ zx_l5D#_hjq17IXLMNANJGEQPDI9D9`vK)%QRVVg)K3NfnqMy=&whcF!2yuP21BO(pM70E^dCW9{cWUw#00doe-k16$z@fg zE3``B!|UF^*k6DB#be0WG|;xvlq9(;-bf4vg!sp}{(b;LyonGZW~0i}tAwb4A`pAU zks*Y*{0JdL99I_BD-1OW2=R)D@>_@$2q6miD*9bz7bU36LYMpcYuXRkg)u^i?T6W= zSG>_HOyne~D~=2y#8-tth|APs1Vn#M`p<)RFA(B?)cuQ`0vTTR*Zx$!Fq`5hkskp7_p#CT#npT z3j!Jb+k&nLE!|JguD)tPPl)P&Qk%bC5~1h_rFn^1P$Mx%;i?6J4F5la_|N|WvpD{} z;9~aJRSSwhRD=I*hYpAZ%~Nx(x@}?=$nf75bVZoOK4Z@DRSV8TRR7Hmv)m91I;ZDc z=~(5&7O(I|zk}QvxHsbUF*oSQ|K*MT``pik!txi#>GB&OPa#Fzrg0xBC+D)=bLn-$ zu$8o~@VAJ2G5X&kA^?u6+FwcQpTj6kd3}|G|7}kG2y?<{%8|QbC#iK1)vHb#;U4dQ zM#Q+=8iOoXIPwj;ROQQU{dEGsAB!XW@tejR?JITy##5uOsBC@*fKWsLAeBTouWJe z-l?BI(UI^9TY7}2^cT1Gk1zP0gry@k7%8@P|B4O1qfOQc1wU?B0E;@qyoSa?42GPgq&5zkVY5 z1rPk$(pV&vFXqY6w$)ke;}a(YFf&x3-#`locSRg6P&((`)Yq5MU{bCHll~j^GJvF4 z5wTVWNM3}KWM^ZW(fVFzW%T;2(x(6#wBnWb!aAG>Gtk<+59Qg49(M9T*S=J&s(U#X zEQ6WVBEZiuEFXdx7oMt=ofj~l4dV|j0uDeUz)u!eH%TH(;1taV# zz5My^nXN}fI{%PObZ`Rg1Hw8EaNEqv69KoEDgIT}fI!v-hIJSaw>MZO+O-wE_#EH% zV8r0sNyEj7=m}^%=A!f`PsiI@*XKksMWU9Hd-txr0||O~SuZ`f-{}boIF4kRkS_t_ zfYUlQ8Z^YAj#XbD1~|J;0)^MGgbF9&o#2Xy?<*T$+R*@zp*C*z!76A%{3gY~Yz`4< zfH;->g1;8k17o!m*5NE!3I+=u@5=#EjK~x*Li|NBMMTPOADuce82})+20ehZ%0agu zzj=4y$qayfdu~j4feGFXFv9=)4h=b(JX;q)5Vj1yTV9A5TMh#=96>=82*yTDvf<{Z zF`ax|U=0+EssCUtm0&(4V)|}UhAL%vCaM?c&)v0?!d^eL(~=1Xvm^5IoG*r+klQvQ zcfwM08~Ug~ckJWQjpT5^d#TdmI>>UONyi|1cna>4C^Zh_|GY?ix~6}@vicKTZ1jOS zlt79_i3fR!7z}9feJ~1Qwgf0m1;7Y@0#3JsvEcwPkLMbC(2O(_uEi*oF>uF?e$muo ztiH(6CJX#S$*`WATAl{U!_*A4S4OaOr2fD{N0$GZZPp!R#?OGHA#^JVvbj$`;jubL zX)st}vom5Go`kXFUO6QLOPmCVpVVNn#Z5!Ur|BqaI0hMQ;9?QeINLnc88Cra4)|Mq zDwtVO8Y|#bCf~f){ttrF?+7^z3wy>hZSri`C`k(<5RMc>H3z%qZ3rt9Gnd0@q@5N$ z-wFSi&NHO>iD`nx8;vop5g<{T%b4RRd36V4euYAVcw&bQVB&og*qBBUHX%}smKlkp zY=M!PDR7)wfnw%nF~H6I7(zs#gdnj7&$B$Qi*u>`r@5vda^@`*)F3f{?)A~_lh=6N zTw-oc-aYU;xCTh>fFF;a1ZV}%Es3Ot_&JAl9ZVTlJ<2SR3&BMVN2y_IxCWvvcgsgU z((~d`fSU$#9|PXRlI~8;O{o+YhKI7(gTM5_d)9jL6Hug!_QKf0RU?SS1dLx5!^_DxVk`I``rZSiiBE8%-=%?^;78w zu}nhXKr`otI>FAyBn=P_0)XTXRv6<=A|hChY&*L}K_{7TH|5n2S^v(we&EmC{eC4c zbs9_8I)lk-vGE@|V0M#r6B znbktzCWZz3Wb5h`Whs0O85D50)~BL-bb0o`Sg_t;?Cc2{K&Ilx<<-}DC5&|$!yOiup*>`u-?7lfZkX!bmqcj8!( z(ZT;|nl}F?>s^{077FQsl*D$c+!K+u`Hy-1`$r&*)mt{O^f0#`b@`2k2cFeYdi?q> zagppYht_i2y~=*@bJLwZaNmh0!(bxd!23CUUBuQqLRp_YMBEiJO?O`Y4;Fy9#sthw z-aEbkHzP zPpb(sL~O9bvYSHiED=;P)bq+skpj_TQKI3bPvLtqyOD%7+GC#JHk(EJwZYt9KMld< zSbg895;7zu&%g-7z0$#zJMd;exlpTc8{vN5ha@A71myb*2;CbMl4(@5|lB-SfRMxM&|FB9os%G3JF`Y)x+WmMCvd{pxE3w*YAQV07D@SD}V8ki0=H5BhZ1_7jCJ)ly%V zu!i_fvEC;C>Svn)!BQ)Bzp8*@zmE^w+7M)kBl9?C)IM@J=CE*2Z!PAYWSSdr_AP7- zo~!74^9WY1+^!m4lcVVb$fQq_Z+qdaXX(UAu1!Mr}1`$D8=@1xNKtiOuI|Lb!Mnoh82`P~fMqpr2 zK)Pd)hLNuCp67Y@-p~Ht_rI39W-XUE^P4-aIM3ta=v%Vv^y-&>YLqA03+_`$LSOpI zhWiz*{-h?38!E70I?p#6EPtLi-shmd4Lf8&+*4T=Ul(%pi|{M6#xMXIQ!X-T$>Z^;d7$Rj|l7d0Leg9!%QB~$yTvg*)oMe={V9^ZX z7!bzs^l!V|+ensJBwAuxk(&gEa*S`ngF5%OD zI01V(;dae0gJhw{YW5#uEH^uh;@w?k#Ic_@e6*H}s3GtzfB(suK0SkJOhP;^7JRyd zvc<53%!R?vl1ogh4)dd5gM;4y-F$5gEDi4BUq48-Gw#QKIb;)DQ!u#_89xC;3v~f0G5jmrZtAI3A+1 zABzVTTdeA>feUvouR<{4K4joRJt$B4bPHwcv)b5(*sJ)|cV~5nHLo8-o^)u7LDfx* zw4JV(GpyL>yDnAvu>s>0bhYW8+`OP;G-jY3y8W!u;ot&0tdbb1CF3= z-{$3)UQCE53Ofss37?in$U+|6b<&}-yc@H%4HKU?|L!YENzrB|z8)#GvtHm3>kV3} z{BgzRh%!2r2Az;dy1~R|i@`)GkKp3zPBXrpDRQy#c9?g&6vvS~y#z)Hcr$V&M`%na z54m8n=Niw~TP{9WS`895U!2@rkZBF_RG~4M79O^S2$zKNcLwhm{RuJ>rnjx&6J=ou zWh8gtviD<+SJRPmIl=()xDhN8@3}7a~MfyLzq)39I7a z3V82;;Q4V&OP7;RUS>58!99#5?E;;D#eZMKL6tfhE!1T2Fh)iuTmuWUIds)eIMkI| zx%6lv(_4X^CZWT?pYUQNv;`0j)eUMPO$sJ?(4V%Yxi5uzr1$r!c5Xd^joi%faK>%F zX#*ob8u;aPwUwN^NrA}68MuDL&)wM;VMYth^OVCUUw{jb$;~r?RUPU+4RE8*=WN}3 zWe_C$SFVZ(Xc~{}qYnv-;D}|`Qn>YZ>0pfmyKRh`W6zw$u=|K&Zi#4VsV*ssX+!As z3^)_|O^&Vcg_d%F39)Ip<%`*MssgTgkVcyJ*>2R`t6<_VvaN{BT5F{#_8Bw^_a*CF zUMZ(V29R_bMg&+{M8FHfgy$N0=RSSMQi+E=U$ei&R}|S&;$?c5ffiu{dG0}C&|}q{ z%#695H%<8xm3*DNSZ2T^V=w^AcAt81-NlBemYQI!Kn3iiFA|w1cf$Kxf&0rvean z`>Ri3&8o073~j?-Xn=B@4xAg&+mpH16yTlJ1UseD4{ zdr1h|;$=-nc}I<8agy#V3&8sA#_LfwH(o@h7~-q7ddIEq_QNzoJU^*Wt(~tRrDbi> z;ss|R2_97gdLd(sfq;vQ$mk+BGulVam0CS5-)#-1qmUYs@y9AaQr?t>Bj!$K=?_O( zhSGbJe&1ss6~5Nj!UNfvl;(9F{q;L>d{xzUCfqtzm8@!Ao7~y^)2Pv3T{ie}LiOo; zUR>wNnm>!lkp*sOv4sjGygfsW+2HcHQ5}xaONvdqb*WE~{023F22N*z`!{7Y+#nOF z4?jrV5$)}E%4sEOJc5j6X3pmM@FaG6$lyAose0-^v2)7x$z+`RfD3ROq%S7vWc1LC=Cu@`I1qYr z5Ve$7l6I5zn59=XBp~6wut%{dGpcWB%`J3?2gU@`Ft_iXk%ahIkbs1h=vn;KjcNlP ztld?qo=je!Wjlv`yqGn4SwVzR>Y0FsD)t>R!zFuBiLet7U%{s3 zYsw3&0NN>#Igkeyh+<;*XpG0W%y6tsg(3*B0DPl zZl4BJVU$6DUvNYNA1_(db&K8k(#T@9x?H^(sz*uDFUwoIm(=lr)OY!Q0qWY?dEO)J z0(90!0=Q2F6he~OG-SCDR{25*!R{Gry^s!y>_!+=X(O}4F^7`e*8n$qFQB?G@sSB5 zlJL4+%`w*3%hL^VkpnBKpnIMZugHS!r5hm%F-U2QHk;=gqY>t$ZK zd_kg;9pXh!I)1=UxY)jWL@2ajcQ4LQ6g!z%6miN=KlG^a2~FQ;Es)4DYS~=CeEX zp;#2&oS=t~Uel^4j3@Q=$^pV{ZZ%kC@}js2U^s5$DlB|Oi-eaQ#?`RdQm3i+R2_SHuxOzrUU5FOt6 zJUe{s9hab5JzF}^03TAo`C#%_CAFXnk7~bgEe4XZw+#FTcD&edx*d@2M!v_`)6RX$ zsF-|=^f!kuQZqW``Y}MPisJ*Tq#T-Vqqb7xdZhTF5j0UnuOk;E`iI|Wv}rq6WPr95 znMPeut@NgbM4ix?A%pwHVm?TyXR2?|<)zhJi@W-hxpzHh@$v>@2Bbt?)^ry{cEHj; zn?|=uzm*BgL0>Ml7*M>@SI zlgqRX7=jI~OaY8WMvc09U}Ke1Xm!G1Olb(@VnHQM(&Vn!q? zn!%HyY-0e2UP7lwx*;|OZdXTx-GuB)^^F@5<)}=9k};sH@Pk+kZzKF)*~5-#(d>YO z>)N2{XqI#qB{3_T;Ore*Or_u7lMBD-|iDm=y z1|@_G;tieM1TE=8utn`4O!sP;a}|T8d3XpG8(ZDfkCS_ZUO{qh@x$>boI54X!96U- zk8k-dmslP_`%~+u5**DIg+AxKgc#YmDPPyYi^+HF6Q)`mw}3}HFrx3-!!v}jy?F|i z*X60HHfk@(^QVgB=K0*HEo7XncaM1E{tr&FrC^XsOZ;Jkza^z%tWemPY583EB8(0X zWk!ulu*Pe8{E$bycLz`@S|9;I5YN8*vD%c$WJ6tU{^u1j&}(t1V%Zga&}uIT#C!60 zKe|8;&uBD}D&qNpGKJ*LCT3By+kN8Kp*UtA@B_MD(M$CZE!(O{m!IrKC)Kd2BLcL! zWLBNo~l!pdI^zn*{7%&Oj#BgH0s5-u0YsSi|d0osIR#?G4<_W~PlYmZd z`-kE2MzuBBbre_L1`WKl6LH;AjXH7?lKXs#)^k;*GvGMuxmCF$?dHWrNJ@O&kPzYH zE$@3u>OFt{(1qTnuhUz4Hp!k>%y-Tk10lrstU|K+Z(1d|XW1XaK)!A^1 zcHTE&I1?+D7_6UihqDGgS%^u=sT)5bBl3N`q&4`+pYiFZuuA|F_%vQ2y#%FI4KQCx zLU2K}l&Ig>Qz*Vv@5uJesAgN`ur8GOJuQNm;`n1vMk%<8_q`tt?R=9g5GdNy6qxtVb zHx&49?9R#pi<^2gnPIwl01jGqb|)F}EP4u`qfs7U`D*8w@^8r)oDGuoe+x?A)7#0A z$nU%XrYAHPX#=SY$+{G1J}Z2_6(T9ej}^gc^}HOCz)o%`=ep+)FX%kGNbSfv^0cu; z3cU!Z==Mq-jAn-q2}vw;2_$Tg$>XArW$Gl^A~<)gJ#G%htCi_&$Y|}{wL%Qko^rVg zhPyAm%YA7=fe+`%>~OP~6Vik)s}w4T??N16-t=PS$})8M;H}(_q$8MExM^<0^QYK0 zIkpCp6iU@;NXyshb57Qtb+2oOwGRiTbYvB5V|k5YKh~a7Bq@U>-0hHn3|(_&#n^-V z@T>xV{&_OAh=*Sslqz7JCp2L-)t8|9F3#h(zb!k7XqS;u-BexRPKsxO|E{D3PmkyB zlAKUYj!9&v>>8lQSzbc`stB=K`deVdeOyd2d!@m9^2aHeH>t)tQcZr{I)mN{rM^SW zW3Dswp}`fS{9J=z>;K)-)FH$nvZW4-(C4%y>pI@xY%E}-y|wh*eii5V*A3Yh;RNKL zj?b=hFE|-g2xo>A2weVJJ-aKTT6{s2y66d+RpMoX7Z(ir+)VoTKmmS(s6pYIhrSUT zHmA}CghD<{pl|+&LX0RB9GoXrNjt=YU2%5#w{~I*k8!1lmaKUUE7=@nd8Melh-c-(t8Q&)| zwue~}P>4#7b0tuc7LHR#ePNG`bc>XXf6NF!eG#01!m{Qj>_H#`KRNVf5+_f89?VF3 z+$FSuf_3m4ZtD4h>e-`sLxRVuT&&?aBX3Fg@*ksY>m6CNeeu4! z2Y8c+P-!~OMFq~|N|V)ofP&mvgg}=FMfIQ-C$G2A$WxAgtRiR|HR7c~uD;e~<~h(^ z_+5zio8En0n;TTm7uvCK`EYkNY(9@fP=#~wV|_!?FEmn2b}QUTO73#zq=LH#E=hFm zDP%P|nK!EE2>YTq$l+hQe@OEP>LM)NlB)A#$qghSHL)IHNW*PLErR2Kk&TOb7GLkZ zfP0fAJp9ib?O~a*6+kB}lyK)bE9EZU1OMuEp~*L_MoeOG7EhbYUCJFd{@C3lR2%8A zl01n%CeoEJe{1B@SsOEf1a1Ug(-IX%Qpy?+(iIqt&-?EFcaZy!#D}c^-R6Ezh+VPS zeE{K!Uz#k|U>Z~OSsN;3iDQY!Wt9F#5o*ZK@phh&J)pg?LK!wF?l+;4n*uX&Pnht+ zi`n}PSQI9OXYII@-;5u15}i=@MH5%N7&vp)P82_GQ*pe}MiKFNOOyCR7kWvxAmeoQ zayb9;zDFulu9&wd9wz_^J;3V5H!u+)RAJBY%=*;mS!_TJA5i9$NGcZvK$<-YFUzR> z4XPF&nMbqf-NcK^(bweT$N<1L9g-tN?KoRfCEeLK()cS=ja4_o>L38t6X(R@jz|?c z?A&2hUql!myH&y)1=FMP;U+9-_d0q0SIwnr+OorCo;{@37lnkEOa}YCpnMPMRci`x z@j17z59;?8&R;`0?gWs$mSL9NQMll5jG3CAb(+$SE(O%zzz{++fYh0A*oX%&NT7oT;^y_E8I7P`{Im$3tF`a8@ zLB^qUWPD839$^IH@0cEw@h+KvxbO`;u-#A6IJJ_i|B+>cD&Q_b?;{bE3Dj zd%1jcVsG1wr-rfcnB67uri0F@*dLd7mh0Bpnl>KGm{V&d_EPbp>bVTN1kk0XuCP?|tM-?BNX@>kIjEG*Hf62Z2BbVsM`QdY+l7bYI^m%lq z^ruDnyd~xLnoJFeF$0St1fErYF797Cu|VMx?9erdDm@9&CQNT$&P@KNkzUc4ha*g{;YiU1qrnn`Md{2o zRR8GX+vJ>qu;|!o^+21^Yp?E>^@dm9U6%x%HGz`vw5yN4Ji9Y9W_7Z?t?GN@BjxSL zgb|yCCyzBVT)NwT@$x>Uvcn-r+EwKwqVX*|`!J2?Y|#>fVtZJRPs;oj!xz8zI>hO3 z#qU}k-tmKNhyL=K2Pvo98*9EB`m;^v?uniyj$2kQf$oKorw`q?+CBCYo=z9qC^NSV zs!1cq8OK#GrVT4>zJkhO^CzZU4_9#8i)fh*c&W`ukER!2ju#%Ye@ubjxEXc@qymzI zu4OJHkAyQ~9}?T??(#mqnU&5=$RpCA@+$q z!w(P=QryQ$=sI-?(XaDouk~8hD#z^$si&+9TEn;r=$SdDQ88W&{KQ2$3mXTj+XsiU zq`X)sex1;DW3u5DhERhWL;8WKI0}ZTW4;(6TLD>Ux`KTy_iKp3j==^MM~0-z%&VM7 zlV@kNoGnP2PEev-L;3pX-RB;c zANMZhh@^*&_owL#lFb`0v`eQ4LV)`Q8gF>pT^@df<)y!&M`Qk{baKBBQvh(qxdSSB zAkrGrB-Rk>rsWn;jnUhZ$PHjU3F4FV4d>FkSTPmdsY`giROi%loUpL?{4w5>0K(F? z`o~+=IkJY%+u0w5&7L9yaSIaZ=YqZoIark0J zEg1Gk%Vo>_^pHnnaax zC!!uQ7a6HnL2f;BjRM@V?@Rc9vjXLc?VC#wi@bg-2)bz8AZ*8mKbbR1$C&CeZkB9& z3A?lJTUbtMR*dYr&L4wGSWG(vt2e(hG$*wRKHk}=b_EGBA=TQayC5O%AKLhac?v1o zSe$M-aYNMT)zN0mqyTq1a&l=DzvxicH{Ecl_C2DfaB;)6MMJKhvVh5{NU`88j8 z{P*2=3KOn*FMv^J@x>%ob*H4EKiCZN5)Oa8u=GeFE_=}8#H2s$v;BEQE`zMd_uZ1z zolkzCOv<3xh9#q$@lp@9L@>g+oD-G;#wD?g3YY&?3&@<_PN`gJJSz7Z zq?;pbyYypOI>BB4#xHdwcxl3lPJI74+XKC(=5e@EUwUI)s zUomJFT~$_F(2Z`|qX^Y@axIz8G2n;woChP?CfhKBKPC)l%#I#N&AN;>lp&&GJSs-OWlMq&Rm$OEAqDXuO=%SUfqoQ)6SE1jahYT??h>8S*mU81*XsmG!r} zb)TsEnz&Y1!u824_W;HR??oMkIG`UZA#Eb(gH0MKjQR0_NC=9qhwFtBu1rFgJW$2( zWDq@k#_bLCUnY{|o&>A6huvVQq>=^o*Q54o^Ua+`@f~x2WBin9!?L^WxMI%Z)kQu$ zNw5@NfII|a+E@${uaWi9uUYPu-F-UjB9eG!-T(1bwq@%_I4RgW7F z>hR7a#?wG`Pv>|xfJ6G!c_OtH^xK_Qk!RsIKug(NKb;IkEPi(S(PVbVZpS>R9yHX0F;nv zHpT98D|8Ce(RVLuRR!s78RMdFu9~QmWp~G&7iJz0_ z-80m!8?MD5w0b`5x6(j!twASWP_0ML3V@vOkVimWG`4yj{gt^q73$#~%#Nf&K9op` zex#m7T#|pBx8%*6mrq^>N$xo;-fM}7|?sJ%tHAU~ia~@snV5ec8mak|Be$aYyZy;G^$k<3|2hAc^vb9c6 zcfslpR{&RB5B|j@1Yh+`@0BeOiaj=}tH=lV#4#&oJDZ^ar&mLrST2i~r09jxF95$` zdnn_cU*sHa-2u2*&-g0q(h6$^ZJyMUR|$3XEHeb|-u)GKw8x_R!g8kpK;-%%`rkcc ztqG3UOf{;lbM}u3SF(YiY}j6q#WC8GIf5bNI~NZ5jRL2?H+y!pg1QmVP5Dm()8?ld+NBJgM!3^( zdWtXT_BUY0Ty@&EwJ>V$ZVeo<#$`I!g9@&HhVyS5%}YvbR%m}?7SW1v?$vj$WJdHg z*r8njHOdqi*)dYM_+rm4fHX?0@sk>C_ddjDFw=>7IcQr!N?k0=d&^VvB*{)iW(>!3 z(>}*N3NvUisy(HK9cSi;O7>~H4*-e`u1Fh<4im3pOGHr+nB6+<_p)w_sZocO=E!?( zG17>@aaHpur$M9M;C+<9>UdHi)sNLeRz$F+agsj)DIO-I=*dR}?wSdeMixM5T^r~s z&j7pP%(Rb$aBJ0gh0VGxtcK=j8lZ!6j9Kp=XOA&%2d{GP|EyWc~`v9V-TVV3krg>u&K<~E?M}QNL4%pJz}Oh zTmGz~CQ2wXc117mIqlN1fUU)il94XyY^mk@1-Gcy`sBb(&i8v``mdW@xn0tpDDqae zLs1b+fElJQ(W82oU6;)HP z^bLA>j24d#$VJ}U&U=E9h)gu<4uc+=)HuReNumx+3L<5+`|Z+WmkXT`|!tRhh|?J^x$vcXC@R*L}pm=@)=eBPV94X@IE}^UJ;0mpy_xcU6hw zj-*K#T1kPXh4j&A_s#1&vFaLGjn<2iQcWLg_&aR`c48X&S7ozOVwdQEarC`3w*LRb zhWGKPCgB5$Wz3;KDPtyM*MSJA$VtRXs(~o!wzV8TxWd$d1LlM-EB^$teJ;%EG%AkM zOde*(d%$ZZGOOf6^0|7Z!cPudg`Mv|oCk<>-5o^S@mN(0kjVkS?Lc;Z7U~yh)__2NrC2Rlbh(J#gom?H(3|%7YgVf>ud6s8&hWmty)$T|Cks7M@72(U`GFI4XEfnbu0(=XeNj>9FJv+iz>POH62k+H`dSQ zVyNB?XYp4iFk{jssYO-d8?#~^W6{R)J18Pk1Nr%#+ey7_5l1ZOQ!)k~mLDU?{>Luo zfPM4oc1#WP`$^%9eW6qcm}iOjsZ!Fa4${OmUhIs&vqc9U9HMAlg{l(G3MbnPsBGJo zljL*56_ws>cX~e6au;0aCs6Ggq9QOfGHnZs{=rv4kxUIV0b*Bz@5vP3Xl3*uoNYb%!PJ@w7(9g?2mE2q6@*(&^H z{xxPz5{Z&zv{gQ+9!FMU^qYPqe3Vl@dgHsVz5!lylEPS?F$tD=|5JO+=j6badwb$T zWUjs;>6ML{Kx-7p+6?Yp$&C8`#p6bQfH)+tG~i+C6UE!|Gl&#s?2)l(YOQ}K0ARsc zOzSD0dA=g3oGs8~orsoxH{72~ip|fSP86_VMNv83!C=3krVI&Q z+4B)_T??89YO$K}8N9a+lPiHJuN0Mmf>BoC7X%2*C@zyZsg%A;&mtf55|J<#GcNG&4@>5GUgO zJA;VozeaGl0zxV8?Nczuk$JUEDf60-@ERF<-u@bA71gl%+sGtEtsG?ZtvnxVXc|@r z_t}^^h+CqF<>X$Ttf_LuhS1f{5M5Dk3w6x*k1fRkkmxHhD%kf&|UC*UK7G&&h`VVT()O6W!I2J-V=ulhgg6Zk%vDV&m7|KCl#l#M7&1!_b%ziqvAztvPefTbxRJk0h3l* zjaE)Hq!ZaF*YNCyz=}@Q@)>aTADk8IwkAex$ghQ#Y$2+t zw!N(Zak01-qVG%1$9E}_(O=W_6wOpCt=dGGNF%l*Xta1u$@{2@MC4DFX_PzYtSFWY zS$H?)O#-koyV1tLb_o&bb$ zd;asMZRN%y)RAS+_g-SK}5@sMi)FZ z7DUU8{9t2n9qSpl1`ksFl!WjUX|#h$gaE=ua4Ib<%2Fx^bsN(Rym9-t&NoYz4#sF9 z%W18i(XucEsnu5MSy<)3I8Qh#ya}r(iPswP%PNS-mYMe6qFvrcj8s|GBg1wWI|=(^Q3; zWqybkvOz@HLk9Z;9CDJ~a*`qEYNV`VvG;byx3Rg|wi#x_5urj1-_F7|kT|4O@=KOK zb#V4vLo|V+AG{iJ|sU z=K){9qT!=nTW@=aT~PipWbiK>ivBf)XRia(b<8ZEauXPB8h&qQ;N<g);{=iBxs>Yk~w(nSi;1uKwRbb#(`WuriFzMD{iG7=apkHRHo);<TFYP59+w)~WRx_7HlJKb2lR3b?;EyUWVTE8^cbLLv7#qz{dV)GWFx<>YnRp|@D!m4#l5hvh;EnIlpiBrIUI5~zdrh46-Vd69Po}M{23et>f2P_a${>IA{ykB+KR|xQa9=iifYbsW-wl{B!&`vv!z$!!eO6VdfTAa6J zFK9lI-8)TmNEEna5*vLSS-+<53m3%lsf))qzd2At$hR)JD$|1))ulu@xjfGP{au?+N6SYwX zRCb{FGCmnniL08xs5|lzoP0|j<=1o`@-n0K!b zNOVtG0GO|%$_vE)V?F*CL849=`#|#_p5_7-VvT!0?l+9+J(>Vk5Lb**?O=PRMaBPv zD`?4pAMix6;Pxl5qY=QM2&hxQ-R0^h4l*pU!0Q_W?tl^|Z1!bCKaln7od4p*`?GgT zN1BF^5$U)77N4sc4VzDMrpp_vT%!l&KLhul^~4e3SeJAKf6T!9qu4g=2kt``L+32b zs}?To3@#t_?Am9$)iAS>yfWuPwqGE}HdrTV&ddZ~CRVAAy$k$GnAmpr!`DB}YZd~w zMQ3kevFtP+-=?oHPBm3F-?s-FV;>)**1bN`Kg~2~)CMLzL|RicX4Kx}o$?26BVQ){ zj-0r*U)jB{T{?0?nLV=^Uy14l^F5VO$l%M`FJN4+=Fa#|sdQa!o{P=%5)hUY{r;#a zdp2#p?gwD_Ugpgm{>3fR+KJ96V6L@meZ=fbF*g(z_zzNodaURcDZw}MI9cF$@?l^wsZWfzCf37W^ zDg9%3^kG|nJKWA20}U1WI=Z>_+YG|?g ze&}ZZj}7pjU-*lZkhp)Y=YRhlpn%w6m|WQ_>hJ$CG~};<7;L}FhYErJFBFjGFop*( zP@Uacs(;L%7%;;V zjLrVBIfdx|M%5H}5xu|l&qM0puK`-42?J%4GEW8u&RGykvigJgL3#!Z)&A!jQxLyV@GcIk^9!b)`5=MO3gmXaAQkjmWYk3C=SmA^FjJzs ze@`YfiM`Sk%eny5QVs!to{2R6PtQNmDr;?c*_A6my*5v`fSWt|1*EI-2fv5mm%GvO zlS*AS)9f5j)W#+s-?!B8)y!h{L0KAxq4l$YRBF0$m zdYn*z)7twk&(G{W-wnh%`=?%Eo_B8wT-Fp(fK=)ljM!X3hPSzNgN z@2&E}9!GQR&_Z*p$n_UT-3j%sB&ylCf7A>tkii>6<%S=fJ9oey-mZAO{$J!Pb1en( zwcmIkJ~n;hP!vm6ZYi4^c?yD{6m84ay`KJA8fS*I{73&7*usG`@TlO#ga_cfENBqN z7(+2@g-SOl37h0%mAl@9JwGU~xcvJV#yorl1%+8Q?T`~3;w%4i7Y?i&po@SqYtVnF zpM^mb{ChhVG!fmidIm6&(eaBp23KR)#UK8&%Kye9C189rixF z*Zk)L{`>VzR>^h7f*4Vn*LwAbr7>#B|pXkWTd9r zJm~3u20(#|`SIaTx35bz>ppMYi!y&XVfzxS$#J0Db_5Er{(5^*J4zYo`bfoKx$q!K3&tp%-6q>Q(K%Q^}#ua;0eGAPc`YnZC@Mn7~6q* z!rc+@m!PjHktHaYrkLefJcuziZn0w66d0}~#@$P|CEQZzxzKs#5^|8r+O8yPLbn04mp6*BA95x7P5ZMNdG-+gDmnpc_CO z|Ht79m~#!llebT4<}oLa5yMocezkZ%e*^3YA}d#b5kFu68rqnBBnSIt}&>O|L;{US?REX|969;^T3d47m=J`QqwE}*K zKruA-XVbY0*IK5jX@0_4*Nxl@SR&&@K-gFVkND;lSF(&X8BZ6D3}_1+0>E;Vt{0l? z{v}XCY=KD#efL`CfW5MLv@(uabsm$$r;7ondGoSUaMcFrz}sSi_*~!BEN*g1Q2ZdJ zQPrRj0E@K&(CO{G(y}tHMnc))-{^k><1eM+|9;XhNJ9DY{ot!I$69Y3uXH^LSnQ?O zD5OyxpQTpL2RyFzgw~|cJ@dlw`jxX|sIpGN!1Rz#Mw(f0xsXdH->DSDUQGw%EbOEK zEno}W3U|wLHsVIZ0Lwte^dXhK!Y(pUMJE2i>CsO900TnyX!^dbC)qTEalsZ6cxJOp zYTZf%-e3K5*H@|)IQnzrhwyy4_xbB?;l;5$TT)F)SBi{Ac=TY{eyFW=;g zeU_~EE3$QK0E>%3zt?H2D29Rnvecvg+{{QEXAV?o#6G_?h|33N8DL*8oxr&6-z4J^ zT4S>RYEa#hlH9%d(Horbh30M@_%A_2WCHM5$ZHSf1iWna!~L#+Bbg~|7jzSE zX9zICEf0VQV}PKFsBcN5qQFvTlT~S7LYomx3w97|Yy-8@&NUz|q~Gn@v{17qr^d8( ztQ0#LthnW&DjFvLss$iGbZ-SZyTt=wbs1mVg5PNez?|I33-)~<`N<3K8LvzXvZDb6 z>-ImY2V`7=wWNOETrx>(1HHkz?*F=SLx>aEicLbe#MI^GgWF;;M zeod?^ejePtDt*4)BVH6c*zR;uEeS$bKgP}KtLMRIrtfS913JJ?FnteXIdRL8G2gq? zk00G_`lcm!VuRtH7mqA*c%>5B9)SC2miGv(N(TElC5$ayC`x`62Hga+i-ddTbzc{8 zK%ul;Fu_J}P?Cpw>qZG<_gR1slGS@|)fJVoj3Fq9MEaeELRA9Q-f@KK|3Q2?cK1(t zvfRDM76|p1pVUb=QnpN%J^XI4eRc!XJNoFeb#9H(JqmmK-zdbZv`lCwPhkYX165NLojCtLl?c{>6P4kf5!uAYikzR)^iGyY=W0DK`CGdAM zd75LlI0qv=zF`(9;svYkCq(hpG!Uy82caHhC^yPU-4=-zXWu#AT-J=@oI_ev=Y}f0T}LDV1PHhUj)#}oY6#qq zW}rMxj5fo;lZv^pW3J@<2uq#dc5FhM1B?*K_^V}}O5KIx+RpTQF^KPS4&NljV*b^+ zy+Mzp;qY5E(@{*g@$cw+s1I*MoZ0~r%6d1j=kjRpvn_Ieh4SlU`!M_MH>GsfO5eCP zS6t`yE*{FmpS*;yxk@uGDJHRcf#Pyih>)x)?WfbJvDlw&M`v79gFJ15T-sYLL8(F80e5m+B72k5LOhFmx_E!qI_0&A^w`Nk!9`!k{n{w3 z0{;j|ujqTegUW)w$)Z1hRTuteF&EGg-*eCR6Lp{Rvl-TzetJ>2 zPVjr~{7?Co=N4K3NxbDPbpmAeQ()F>%-9eW5*!yv5{oenUk|R>Nj@)iph27e*>&gY z(8Ak;S-ICmtGkd2ESy)MM5ItXA9D-*pI}-}SQgY$2HYU_L6<3+q0(`<5$@nyX-~+1 z!4kw`K%`?tgugi`nI=rw0&wE9ZD5!4vs@`Q;gn;$%e*GMV=NlePxXK}v_V?@9l1u2 zrEBtw9Y_tk)@upzJMzC61M-}bYksO!v?|DI+^J~Y@ADDCB%8FF>NcsT|Bd)%!sMw?VyvT4_ZQhWk9_siU4P8~!vA$I`OA*@q zk;baw$n#q^OV~5oSz>8aT+cqyQB8+c zlK<^TSGqw&O|C%E6Lkasw*&|}&VU%=9z83SVI_co`{@3H$;a}VumGlvkH`_o5lkhX zrmCzb|5rqchnGFU2LOWRxs+|EKTfy3O$S_iE}qv4wq2F18<-z$`ipIJc#j3JO&)G( zf7K7nt3m)tASrYCxFfFJI%Df;2TF-lzy@=AH zA~%0tuiV>N3iK7d#!NqDq51Fr$yilDQy(v&s2h&zkrRuzUa)2pdU-Q7U_r%kyo4T;_Y5l+*uO54_ z=-qzN(zX+lF{YeIpGLdiLF4nzu)i2m#P6u{9AxZ|I;b_7iOvM_D2)*96A_H^!^}|CcUiY?>lTGyRc8#VA8>g#|_%UF(78le3dU5 z2K=Sz{5?(8-!t*A1vAn-CV;;rlIsWI^}-i@_FT^>eC72{Cj}!2?MO5q%FxWc8wU;o zSuuXSj4PmW5L^z&MvF$qw1?lgPjzqdGiC%l^eV%^q%0*O@`2G(_04wb{uH?`kSn~- z;0yvi^FKeN{)x4{CmH(m!MSzj7x%2h^DlQ)*57R>f<)nT|GhhvrNYk9h(D&Eswj(V z_VW;c>nYUWSPYfB<3iP%be8(^xE)}~)wB_{HXB|W(FdVIZN?*v&@?>gym2UD8oa;! ztj=3(sHs>57IM$Y2%Ii}xPv)X_bAFvX_G4bwKD%p*STKaQ{Al`>R5lUBpYt*dMhvv9J zdnr;D>Or{Is2EP*{=PTz7rAwnl?oWWNE|-l#T%#d?*JTA!YLt2IKS{VMJK2_S1Iq* zJ9)iHQrNk-pz^8&&Umu8Q#B+E`EZ^uz4Uh-%~no1LTFBa==*w36K?5p+@f4s1A3`JrO;y#TH0CEJEW+>)FMIZK=8z2Yl$E3roiA6_K83jiVO}b&;!i5>zhKn>kvS9iE3kk(Sz2{w#r>Gneg5PrPz|KAUtTe|XPFFtMk6@@x zGm^|D4@_RE+xp0a>v2eQGsHkfDd3ReEx(tgFfyMIeB=}chG(CU;8eYDPZ5vh9{kYA zxu#o!)=UCsw_li|QxdI)pFb*z`ZBQ`P6^Rnyuj3xj_g!VvwfY8X|$NDi#R{>l6Bsz zQvOS%`R{d5P*23~hqyT0lzzJMa(!aq>4de5uZPG;T0tuaR&uqlJdYDHBRbb;v?L0tl9h1c>8UKZrZHZ9(ztDYMoa2Y zWNRl0pU?A`Z_QR6&%$xA(WEZ3MOTq38Qr0U%2<10{<4BhX`pbvK z&sn}KnfL^Ys;(vnw0XEBq2yb=h@Tp${v&G+{l&dI9FxDfZT0`L_10lkrSJDJjg*wq zaS#Ml8l+Q{5D*aQE&=K8mXbPvg0!?K-5t`M(#@f}rGL+v`HbVd-}j%nxJEskz4x>4 zSZm$ub~d4$*-N{&Xx+Qdg!Yy%@hlR!usT>~Cz%Sm8_j2#J2`JxJeOlmaEqQhZAy_D z1DS=p*~RduLqS1r?@PETT>V!Ja_c%<#i|TnkR;nl?xd4OvJF~Q-2OUxuP{qq4DT96 z(1V~=Z<0kVvlO;^Dz6LJR>(BAbE?V?`wXUE$mhi&D(?m=6#|z=`+j)SO+EUb2gr>)b@UMugJR|dmYWW2q)c@7hlMbV!}jT)wb`4Emz)LhW$)RhnnP{=a`QtHD?k= zsD8~5a4D)HuCNFE7mh#WDVW$kM`}+1#*)@5)XNjVg@=#RBo)-Rwgnf(Tq-wHA4pWt zitjFbx>Cn$(n4g>+MtfuVdsf_Eu@K@&V={)AVaN*0@JZ0f;-4oKe;t$Rlq>M*8b;N z6u+UWoG0jzycCP8l3#=Amo#aT;AYr@a&`A?l(gA(C!C3WfAI-ri; zcyn&@ zj*Pzo6oGAH7$iIyEtbk_e!6=UxflT@+c}a6chCanXCmnk1`c6!pSm zY`b%TT{sBW8ZZIsDK__;HO%urS<*D=r7EMV2PTOeFM_AvcZ&4fUZhMI?44vKd4gbS z&Fk~Fh+3s1(+5``3{eoh+q(Q!z#FKE+~n7FZ$1LFcCOYxJAfNafvNHjU=O48n#kTS zZSpz2`Dd+1U0?t5X8j)$=o<^M>8>wSosCr5{Vr_%D(eH0@3%Y0n=9{9p6namc&a9} zw3C{Ts+A|iP}Yv=PCYeYQ_&>EF?{p+;U{F_dDpe?rRr1*fQR%RF$7P{5L5q{%; z(g;W&Af+AJmqf-J4epL|3ZTI_-Y=k)C>@GfrILI;|)^+`23j$+q`2n-sPqf zeQ{Z&Rbvyq^{7gqofhEdk+7wyd)pxB=oT8&+9!OuO^<~Dh5ow<0s#E`%hn=O$=37g zqeGt5Q{xh0z2!)|Z%YFI!2%RMR+1wBj3i4eK4G4q52PKpXjDstl-jM)C+lem7%T{W z;^gM-fMai;>qE7>9Vq1V*~r%(_BMS!7VKyueu&W9vsco?LR1hu@ML7}7hLiD1ChZU z{Q{BU3C1%^x6@Q@u&sBv?$oE7PdD$QVA1ne zq2t}a4d$Nm;P!c-Tr@1e)oR4r4vKFt@Q6nH%9k~wO!A7`OK2l_kqVRksDDL634R@n zZ=!FBT)p<3<6>!pOF$r3y7lv;%E_i7>h#~G)xUguY2Mqa7IxsZ4d*h9u&jORTp_)$IIPMgA6mXI22U(@T5FbX*VmboI}!Sk^Q?cPfqy$%T2@3OXXA z;lQJn&aeKelsew`H(}~sJ+XcO>qQ%S7!iOW9(1>lh!`4ok!&boCacmu+0Q2cAP&qZ zT)}-G_EmZjLl{8*YN3J8#ApWFs8IG8&3?TFrgPrxc~6JmPIW!QNmc2K;!6S`S1I%p zANek^T8p1+62M41zNX584_oj--tYbss6IwOk`(cB&_8M1vwDlV&?5K*L0I*ON>X}0 zq0&>R#CYVCbiRMuL5$ozZ>d@Hj$!(o;k2Uw%Ui>zzA85Wrm_UE7PGCXOpxX24G0DW zbgV1t%{xahN|1FO#HXnit#ne{+ERf{+GK-oLjwqW1MWLu_(z^5T#WYnX1rdLEG2S} z4V!!#0B}RZSHX^Rd|GNNQ0aCrz4m59z9X(eQiKc9xZJi}KT6j_;Q5h9vj1c1 z;j9FTDDqW2eX&_DYO(jXCdrPmaOYEX;p;!iC{sBkT8SGS$BuVhiFt3L9t=vOywb`^ zWgb0~SF!rYwQNK<@lca>Mw+=>q!0AopDAt)YlM|IYqknxR&hIi@9%0X+IwFh3SQ5DL zXICgXi*d!VL@eOBzTvc-$V0!?j@e{Q>-|J-3ymF+9mAnzy-7+_gy>9$_HvxX*ZMQX z*ykpN6(WRZR2?dx-CvTsbsmm64`U>ho#YSY0ompx9_(@sfEvQ>Ut;#*j$VJw@15^y zZIod>y9^+v;+zB^c+P|@XW#Q7KXF7KL`#|nhipYBF=+bHf9K;`0I^~X-MK0z@>%h7 z@$=b19E>%AQoOoz57RjGvDoc9W3jMd%N9kV zVhdnUqV%PvSgSEyg8iEvTK7U}-&MZIPZO0UzMhA*xyx~ku_rasmQDuh zg!+$l5ysro53`Z#mD5%NnvOq}L*Gw@g#%@Sf}vv5y1@(dRV=V7Q2X8*@|{w}x_$l4 zA4tft7z)_?2NY)1p;)E@N7$`TLpAjS?r+1Lyg)z4rSzTzSU-n*8Cue83yh~Ha4V%$ zqY+5}7xg$_)L3+u`s3GzVp2&3z$ot@9x7PW9N*=-XWX*dJg&4fMc_%Lq3-QfxzPDg z%)^aY8iP^BVu7+U^a|TyjP}xLXYC(4!X7W&FFz}9=Nqb2tf4bon^>2830H1;u0lJg z?}l@9x{@?bw{}G<5msd9DoxJsz^Gn|NU6VvXJvxH=UqgxaAfd`l|hq(EUk*Q=fgfP zq5s>|es&5v1FIWAoE8IQvX^9A=>`xuTossK_CVuS#mDpR#}J>1>7n7fNM9IeQKRCs z=K;JJT2*A=%MEG01?(tJj3;aTadfdV!uLA`z@e&_j`9lsW6Zb!ZyHKdEFvQV*eS@@ ze&hAI%n-Wu^v3fBa9xcyyFi}aE@>k95o?__g1@A*LIiAXWco-Gj_9E2a7tb3-+B$| zW{JyE@F%j|czfEKZx%&t!Ev*GTLE={&tFBqe-sb=Q7|#63mmq8ayQ$7Vvh6-sdt0i z6ONpQ?>r8~rMALiE>kEWI~<{>h~c)SFQ7K#mE~*SfEzLYehy#}*Vy&^>5m}5A$a^O zH%mWg&t);>k284jWds9zo5!-kW}%!`wM4(8z@U)2JX*+e(5~X(f%ool#e0T2(2|F? zhliyabWHTdiG3l@>CkVl78WtIO6`B_LY>P0z~ao+{3-bW$#9&p2le>C2fNgM+X%i@ zMx%J(ny~{ft=enym_z`?7GC_w(6xQt0@>%w9I+nufQlCrfu0Ovdnw%U1MXbd6zz(h zdJDuc+3a|CPP_LQAvc({Le%g9P6LXjM-rZ-50%KTHwFHRYv9`^sKv6mc&|4+8mI$Yz0@Z#ykbM^J7CkQ|KPv{r+jcSZ63z~E9 zywIJUxANQ?y9!h69&hxpl=nP-gEuWy2h)^o$fnPJi@H7WShETtQ*0BXPh@fDtkPZI(92AD}RS$ zFc-rFfh0**H(Jj(w5x=w`7AsK7C{k0CvVOYgR^$*{zdvh7cH|;N-a{FzdP0KRA()D zxlx(=PWXHdcU!0(Q?xIuHrog2v;%AV!OECH2?QMXUb+YClJ;P_YkQ@`2~AD8=^n)Y zsv_?{`~d{18b#vfrZ)L#L9=388g~M>F^mzG=v*Uhu};%7&5!lUWIIwvie!hzY8Ex8 z@_VJGg@f$n-Nz6A;vf{fk<<`A12c6T{YHD1(oMLj&jj3~+_D4C8(gHmdHN?l2Gn-C zk~i;EZ};A5t|l07Z$zfD9Gm;u(ii2@fBuG70BgnsyhZq9-AC4?nP=}}1OJjP2}L4g zUry}d<9MAOk@MLZRYWd=Q4c*Z9A}x0J$Ur>Ysa5AsT}Xu)upb*PbKOKK*VL~9Rj!F zB+9DpB&U&nZ=NfLJfXsRyH?DK0nF0rs}VUPtbt!Sz49{$=C|dsBUy)!|7MrLCJ0~( zo`7-j*Yf0s1wgY_fT%q|<*_f8=&}?-ZX==m=g!5!$0&0b7tESR*-v+ZJ0gp~nd+@s z>79J0#BAhT=vg^i)1H4iDk1-=NH{-`tKnZsJ@HO(^6O4bp(9e(>uegWrHm(?Jo^5w*cj!vqi;hh+f9YNnStY zDiImYpRf7vqy+Z2{yNNIwVX#kD`(!F`q6N8-tz-Eh?}C-I%LW0tUrvXaLZd@eKUh2 zm_WLLMl3D&r=02Uhr?F8bB=4d;Vq=sB7B?XV>tGRp;zU)@M7Ey(>8g1DEH6X^!w); zpQhj_+Uf?<++_{8vIkNl(oQRZ`$P+|DVe~ zy5n&+VqI~xGBEYZ^6t?so{gn9w>oUVY0XMz%7`#xoM`jIE!-sR_tpOXLDYoMjP@qbnb3W41+ z)EAJBlys4PzjUig9>CQO%GvxgK0uFb5iG@9z$Lh@F(2AbP{DTEt zj*r|vPAZDqGVbXu3Y#rnrK~u$FehHx+S#x=kw*MdtIb}n`bWb33-HuPYBHI>wn$96 zeNXAs)5C25+f%ph*j=$GTrq)%$X0q%?_UE35z?Og6>lCvzViEPa5HwJ9WDLgo&O@9oids+@q|&J@s^Nw z-K+vI&NkR!QU+J|dEI)Bwl3e6I#c^7-m@g}nXf-aky@{p*{cUhk=4&YO!2wm^Crv( zubF((Cm0X0hTVe(-D4itJ4-=OhwabSG-tXVqDVnjSX=`plaptYlU;X%<7eY1U0%t@ zNl%uVjL)a0n~{MTaO%XTRbJQpt9^+<&Hm^aKraN!U7md~qAK2?si>$J$z187TI;m) z!D6zc*@z>zCNL;yYfY=cgA_CxkL^~v@0&L^#9(16_#oZ;f`G~>>T5zY;n+_s*Es)y z(&YLL)j3D4{)P{~OU@A1#h&pyblQ7geERN4P0LITN$j7Tcv?)V6zb$pEo_bDk3`nm zEc(5S5%lx-KRNDThFj37b@cEZlu!4)J)-T%*KNG9oos&@ha)1=DA^xp-X#=%|B1@N zuY+Z%d1o;;E^5M4^X%KaF)o~Wph#gNx&hjLHMaaIyfoL~fb~ z50;7_HrTCv@Uo_{o~yI5u~BfWw_l6!itsoK!Y1{57J%72G^DsA-|P8Uh?a#>6!Gs% z6y+y|&hPQ)PKK>wB^sV~O}=dDg1x7D=%%Nfqx?0lFy*BQokY_;LgQW)RHmYZ-g-_p z&E8y{)-Y1ebWlyWwMdakN=8zJSIQ0tZvuZw{#X4m$iz%)Uxs0extT(bWRv-oDOtz2 zhO@Ai%-NWA8wAH}?ee&mQa)Cm2%F7x@!5(;QV9)~7|VbS16WYByvD}H6SJ`CB2gnF zI-m(n8n@TiR2e?4Uw+By@eBEBjb$ub&Amu1OwpC>my zbnB)^MrFj46y3ewDdf-F^{8QEbMphh61B_wOnN`#Ta#wi|IF9qah%Cj$~j(k+MY-o z&b0IbZKlw*ZRn>bs_pzb{gZXBN3=onRkq7f%MHNmS27}$K}??C`{s&vt*6}WbUUQN zaylKHysv7-5Bih%GN=avcCe&^!p%mqX-xW)Oy?Vf+9Rn8oe!bhH=r7bkO(JBGaX8Y z?itNYNp&Dp>q5?mGDkvT_s>YWj)07?ElVbDKhqc8c3_$NpX-^$I9Kjk2M!C$T)&%Q{p%HUC1HQr3h=_CFV8p0F{!~PE zG5UWmCU~e2cBHsHw2YpQ$-i8W_(+bds`&);EY=QxWy#;K5-3$nC6T8}Y(Rls$Jq@) zc$d0pS7S6E%EU4O6FdK!UT}op%kW z);GqTHYFux6M>Y1CK(ZS^Zuo52R^A3%^4=$$0w@&t90$*mtSl_*;FE)+J_$lin zctgDqoe+ZO+upur9;$3?-=i#yTLY#}xWlEo^HOaATnk}6GUDrV_3rK28a;1Z+5-g{ z$GG|?Ja)^&#?H>pRL{33OVNiXR4}CRqS_OVM`MGUI9|_m+8wR-6;oZ2Z48?x3Au9v ztzY5%J~R*p#F8Q>`;)w{#+Dw^_N*+HaiOfB1%y$+SHdqW6CSn5Mm+U{v5rj0w3OJd z^+V(F=x)Ga)t_(hY}bFs%tOg-H4_X9T@sE9?YA5x>;@=KyECNL=z))vvYYN4>S+qD zq&elhN@g=1{Nj@HEAAB~c)tQsGJKg(CrXS%yJciy`d8p=K^#BXU4hL!2Q{6xhcy9S z*B4?E{+(RD92Ym>06i9Qedw>XY$jIlB;z`K84a5R!L^fhgwtxiLEE`AiLbOlQ^$~o zC&{QCK^m%v0FFh}S4=y9NgZSer&#CO<8b$KPX1_CUE%Ae2zIi^^tZ47NePIUbkFqK zqP9%gZJ$JFg>UGobErz>c~w%mz3E8}PI6&H9ZvO`p-6OpSDz;UYBhoCQq9 zfA5Fp9xS_SC&{K-Q@m;P^6YGJFuZgf9}aAEJ<9HucuVf0C7?-pT-9kfoF&AH>yuRv zpLhM1VMOmw$nff`CG7*+cH_Vwpt7YNPKm21E^A!zSSB&NUT8+{#11m-i1z{DnfH&ya z$SkMl`$ZF9@Pyr5r_f1j~!lmoe*fb{#*pcgCPjATD$X z=Qf;OTyIS{7{}<`ZB!6R{ER9pai%G=Z7 zFBB#yldL$Jt`HIXRoTu8htE)T?99bSVpI#{9WwqEZYP@;{vNLTb*4iPn6#=C_VihJ z$jP3*GotubxT0+zJyvCF2<|G{u&v(iO!bAS3+XjWGJ2k*Cgh?KNXy72pt*QiQ7l4B z#H>1D(lPWKIL;e_c%EM`p77iWvxlMi6pNF!NYrqBGydjb#wMekMKP>b_&V!@ZB;|; zMRy+_>BD_sx1o1)b^dETBT}Kp!AxomkyeC#Sl*?3>2j}8RkE`$6N8X`9mkGVv4GW> zvmz^|85B|{ec(UhREJySx4`LFCI~CH@}$}>c(jXGqJs2yF@b=Z!m%Xwi==dx(0kwELxrXs$pLiDgGk83MWZbr-|2R(W8DNz(HTp^=pZ{VcTaE_zS@@Qw zb8jqHe1!Me?kvGi|FXtoDFUXjyOSIQ^PT;s;@z@V4_|&{izY1dni7l75frTye#Jkk zX)Pn^K#It!^g}+07ys|qg4%dY%yq%Svrh1H+}%x%>GT;@wGyL{IpNq1n1I9jmjOM7 zWR$#tDEB*-Q)Rvsw8@09B%t?41t8{VPD!P(3V)LfI!bj11b1lVD9RqDWfs|Eeh40k zq34GhRfusce=vM=SWH?molAz57)y4-Cn4lw1_V`2IF5KP=#+VY;Iz1ncZAnU(?`D zg}0l5+eni!lO@K)AYCKa8e8QEB_}+@(~y|xIT-%MeJT@ayLI=j`FMdcCq-};7qr%K zE3n3K>lwcg$y%4j%nD*-*pE0%Vq)UXa34_Te@9;!R*M}%C=qF3^SW}m&h~>DUSpz6 z5*_{Och#B#6%xdYQo!l?&Q$qNeU28;fdGz3r)6P~MJSfEMOcAP$$}7z3Xh5lsQvRwy^OQ_MBdVb#iXsV^wbZde?YyyEFTnpFUk5hEy%{qp9OV zD%p6l5wi8rWL-f!>LsyOf63t#C(lP2jtv;$GUPH;Y~&q1{%=STtu1=|4KlPJ?U=_S zHcgz>nY-GsxNgrk>QX;q&8W=sug#PeV2#QsTq(;zBjvNApxb%1tmBi})pW-!v?thp zGFE*Z)({|Q6lXa(pZ)@?(-UX1;w}jM{5YK!z6Nv!CAx>v&1eL1HrE%9*EChFPv!gt zW#jCP6hu7=H)EWNh8DFCKi(y`qkL2gqoG{R>LrL|l|n}#MWFE~%bV^SmezU861JDr zk=3<;N{UA_9`gz%;nZeT%5?#(WpQv!2s@7RWo&nToeh@$G1|?G?mqpBOo$hC7tc;^ zSz*IM&vU*-D(;flPNZ!jc9QiW3o>@5zfWs8>lD>iO`dpSy5$>wS`6X^?N9GDA8vL|H&-f} zJLRuod5`~y{jO=CJv&T@_(XT_e6F~H&`rA<1MkVva|<<*k3!D76q$=}lURht z!m9*Ff0hM>^V06PjtysM^v9tsg76FxS~AuoD;Qq*=<*t6qm4yrP-b~zeFFz!SZTfn zpOJ^#NCK%k+X1`EW)Ue9y*}(X=3wU^EPyd?=r)=KN4865i2&Bz_)*vDWv7%EpN@{9 zp0?M<0;J<5uN^En?-)`)XZ)sNd%nVw+^AcWl(}#(Os8`TKP>2WC=qICd?6a&E#Lo% z3xi|Dz(a|!yWwqL!bahJsE09EL#8w|ifnjMYzYG=A$t+1p)MHF9~C+Vp>B|}o=ef{ z@P|uj`N`gAER~X?#HAL#l43yTTa{isTDfE(z$oEiP)%r%5J&@m4SDdkh$9 z{s2ujjhF;^=b7f_gAEDjfaa+T+wgyLagzb!&5SGl4*gye>OA_U&t^cWUf<Qw{!{ zmxk|=%#L8s=dCnE%oIWqkvM+L^Gd#DV_P~D!e^L_ zbr<0n6ct9Uw$__*j`GC7;Bfbwv``7saGp>I8N%Pd)32B3*LcpbmCT&EOTgndgARq9 z>1MWs&TO%tc4;)+{-1Dv5BjPrb>|m$VZJ*K5A}@4XOjBfICg3;>r;V-ZK%*e}?CdgVGG$ z)rM)~0M!iE?eJ?%%&nlqIWM#8a@z>gjWij8yKkJ?1iBuY`WfR!d{}cmeq2!dSTZu~ ztxvIhb`Iwf$91X$5frTe#(Dy^liu@QCW+^33YHo0re(o#+c;+l*RRHagnN;)>utvc zX5q)--{lBMEoR1@TTqiiq_jj+&up| zt5W>F?#+wqJoX|DYEBgRin!EGyq~$S-@T^rKKmY$6t5MtYtS2lbWiFjBB`>aP){=Z zk`@(HPm5uO-WVlnSlOC-kCat>vK;8sOT!4_s>=MsCsB+Atf2jS!}OJR!5od{Ov`A6 z19sdc>ft7znPj)@dk(dxnpswK@{IO}ESw|KKkJGEK<`t+?e+tqvW=n z?o8A8_Yx0gUy_hSdZ)IiW_OQRh<3ZXXEcU4J z{TGrajZe+T-aDe7cQ}5A4#>W>g2v>T1b(#Kuz_r~qQv7j^Uhs(`C@ai4};|wM$d=9 zwrbC}60~Cc>?8;8^TH|+s*_N& za|+>a8iECTtz76EcYOMq^1bGz32YL6$|h%ARLXp0?-SP#-@?aTF7COH;dGW5_x!l< zt@2v1XMyP#aCto>H^U>tbzg2UlsnZ#H$&*T&l2W(l`e*lqkcR zS`Q^8^Kz#vv;pC=qG>5{#BY?N)lmT1fUju zcRKwwGB`LkwW%a}#9e9SvR}ka9)#^=YV>?T>5`c$5EBz42sptGbbFQ*AXpd2UF`^F zA_kTz?w39?j{9PkkivL|0ek9YOFi7v>wSSZgcYwXKf>kM*mTicSr*d9aKW}cTT2{( zdEXD)4*CkouCRqMZkxx?7FV>JrV#z3gPEbs+Iv8<<8mm7PPGpoh%y=778`aLWub2% ze387-G))NPFz&vpMD6UwdDrsTU?vfoS#{>VB&-!@m~zkPOMoL^-tueWWvwlj@^?SO zj65x8%|t;5@xUs+P^YX5=T=>s4G z+CCTkP-cIg(RO>P91G0-ndO<2oJ4*i83-KWjni3v?X@iN9Ne4t|@*sXEhfsd1twi(MJhHgsLGS z!BRmuTPO}<_nFSHVJR&~9#n@^ZibmvW#TJ89fnEgI+f6HTP?B3E>ldH&(OoRUJ4J> zgH^%K4--D`w;CFwJ&zC6$)4u{gE9!H@!#eyo^RQy<#Z79P}w@?Z5_ zBVmlKsn2slRY$w(LjkH!$PwPa9<57t)$``Ou<@n_v_o-m4Y2_yUyvtb^=5^+cm1Vm z9HHtrVU}|44VS26uF@$7v#d~ut-TQT`;D10&`X7qSEf+0yX|?=b`fU3^~Jh^B2b`GaLG$4mqSm{P0p35|{Q55O4^N#D5;)q-Vm+!F_ z&nvkCwCH03YGAZ!OWHYl`fiy$3J3?q$U4Ww=2CAzwfdiVsvD=FZ)TO zBNaf)15WNZZzRzqlZ9R7&zqQJX0>ZzXW?6EY0kx)w60vz6E>YuYE+(sU zPEd%}Ye+JNX~LKoab^ZH$JAb!1v_I1(m9(YhUyFP{z+)$E`8_<@WG*({5#T zGnK*aIEI>Nn4Q=6H(IR)ui|7glUbE!danHdjx9ZG=s!~n^+i0~MA&%FntU(JUo}rH zO)_8Ce_?MAcZ(DgHs|XQJ&p^?+$R6^PjXk~De4NFjFjYIeqCW7Bm`;9y*?+wK@`D0{EN2fL>W8bX_(mNP!$68f|%>1F8-$%)*WDcChvJC)wo{<@Gi!W38mxVv< zJRpF1`IhOgAf7Dqb98?7Bn>+Na{NtyRM^cAzy^ha=Kdv-8}!v7L077XNEj(rW-?Q% z|HtLl5W=mGF=1^R6(1GKx2v!>R<&ut(&37cdzD&Zn7BFKh!{3 zJ_)vq%mkRjXD+j7FV;0sWI$CJ)f%~xnL zM!?d2Q_074-DmEQTGo|7Cc*KRmf3-v#BsdjYkfg=)6*ASZa)5uA5qH9;8~if5<4e9 zBEa1bJs-W!yz_1>4c!@A8Ew&i-#AgLZ<$_K_3zoT zvf_Fz&#^)+N%N`BCaR`~`?ttF8%3$!cFdlY?X+&ksczu_1}pi&&MinNThkmpGm_Mv znH|Puw_g=A6biKX>g8BV(K~%GH$JnRNx&f0m5Kli4cQcv_$EVMVcZk@RkbiBg=qd6 z|4@(lT^LPySwPRnNB-6JYv07fFFB)cv*i*;uaPzwAuCXGGE1WkA2z3LwN4@8PhyWH zVRIiG(SuR3h(vcIrO+|r)qgOiWDC{d8d!)}4a@)B{09qQK6?i9`Y?nc`qtv3yQUp$ zjzQZ8(A-jw=5l;l1{wAB^q+yAPFC;eQ=chUA^wfrEPRFaX-YaU= zwvdjtS@k?Mc7R~x;gE4D)WXEh0&&QXUm-na=TA3F@Jez(F`X(ibNH}xb$^;!vqBHd zkU`H5q;m+&OndHqup?|?G&8W83l32WZ5+aH>XZbXe1ic4?X#rhSh=AZaVZmR8{U#%>z!oK_!jZ);k6qzid%(MRb(v8mRxp!Qu&4|$- z-M=<$Hpl`(eYC~{fuR1;$fZeNf^ssCLF=9S)I3&6H*CVYo`if&Jv~@JtT^Ov zAp_+ua?psY!q04}J3;oO)gC)k(B0+ftVaWo8J``@rI8*LLsq$5*6ASEo;d}jeJ*YDD zqS=4kMd%8?WKvh{RmAt~IhC_9y?y4MdFp+6wik5#md8(4jG?}L`AOWd3r~dm%+&fd z%UX|=%`)#s8nc-^W{!NSim`O>S1Yq3^6@A~fI;W8AWH?Ixl*TY$Lzyd@R|H$XO$VH}FS*wD}box4%~_9jI5 z!KrR_Hh!j(?}-k$<>!YhO0}6`(e!DrvtB;F1{}O{s_&@MPH+TBrjk1`HP0Dx8#z9R zOV|e|)-{OoqjZMjZg}W}41E~x@@&k`Ui7N%*VXwEbU;FaZ>H867YGQ(Oo)T`Y9A{p zzpM|#JJUG4W;^ZTZ%Aae9a#LAIG6g46ubVRZ^{#cnZdFpH&JXz6Zcl7b1mTMo@<8j z|!*)x%ijH%qKJB9x2#Hfg)y{q+(NUq?~WVcw}4$Eh)}2%gc`F zkT*oFJd?78&Jl~szK1VoSu(i_l2OS;k^TxhGSUVlry6=!u$`H8k1$3%HbT4D;Y+{e zj>xSu^h{5do%1KE1)@{Ahu`=~9jz#yWDGm+Ord66Ki=7e&mUa$21QrmV~cOdvLYkE z?)w8uXmq;HZa{R2!+sGwMoXjoF}po6ohOhpE7&LB*HqPwkTaY-6A`XgYz2?DxGkqh zx-K+^!v-rXHHGAehNGIL;s&IL>7-nZV#Ox`2@;}j3wVUI+ft#P7^~blrq8T(mebmm}UR|ql_?Xe6b>bacf_p)6P^Ra}LFmWU9#_b*Z5%LPcFYw|v8l++&KqUX2lj|DTQ@bb3H2#iZVJ&mqpYz$@VyQ8p4dcXL1v=#rTmrZHL!AW^BhcI9_U#MI07syVdrDBW_(Ev>8tcR|h&g zkg_o3ZMcf&%(q$m0gZwTug8(z2O%oEu}0KNtJ86XruL*4D>`-%67JNcE1U>U8qIDP z0!Cj@hvL7%r82lm`*B$lYws4gG%Oi zAu?Rs^z%}6$i$HvUW~kd9>^H6S^dq>a;@AY_gH2y{q`$mSY|MS07B*+PF8~fBqsOn zJ5sD(q}6P0$EI-Wri)`Q4h`J`mSk$9v4GZo1jri4KQ#_uH0UCmqP`=Va_c`CyLOk+ zhJ*%j-t4Lp`X_$!$v{p_#0k9jLlKMifI!@86{x1ok>WXK8iY%Sg_#OZo910WbX{=OpGovPYeakM+YgC7A@ z-2T0H^fF$U6#s@UL=*1ux?Do0n9TgmIr7Iis7d@=seum>C7y6vy}RjMigC$0J5p&JH9c~_2YswfrW{jj zgiG4suP>E~#PsxE}1Q;;s7XAYmciWW|q=#AlBN zbRNwhNgtoNzmSj+Rva`a7M&w+&}&A@(En){2YT3+z+@V`4zj1c7~Rc!Bm1Xa!)s@q}>E89?op1Xkil04_ShPb+A@D#-o`)x{N) zE?M;Xa}-(n#G9x@hfl_@{wXmsF??U37pBkslH;|%Z1^T0WkI!1szuG*v-)@OeSl?} zfL2|If2)%{6^kGv?mK!P>ML6Z+|^PM3;($~pf5Z3Ia)8t!vfuP@It3vd5b_Q54Xdt zPedF4kLFfHM9g+HTJB4($AW@d-c2G_GrTiE8HdmB`q}Apds1>3)G3j0{ZFO!d_=iC z+3fAr5$?O6-va90&)TQX1j%)Dbl_6T6egE~e9R)Pds5GUqf;!XBk6jh)PKikUl@Kc zm*)QFvF9(nIsNw%&vQfT)En=NHSC3Z#Lp^|0cB>>GAf5rr%zLpXs3UD2X&heH%-$s z1;~BW-nX3Ktz1}N|NJ3qA?x4MbX?K%RXbzQQC&5u#`$Gb6@Qh%%a}*MiiaXQ7gNNu zI1Or%xTg^p!1PKx5JY^^0$NOV?;DTpiDG2n=NP!{?Bt|!`LM)sO9gHpN$^v=+HR%D zc3I*I*bLa%xJ(ePfPFPCz@P)tsI`SIwDx0UL}_ox`(#u3ePH0``O7)%Rikn!Ib)Y) z?^Ws!C(ML!nwgRDP8^H&u=lW0_eW`6d$HeV%tCpgdsc6hyb_;WzRlsQlf7Nj)lE5D zo$(`Hz6lG$VHIih0tY5I`;Lo?nm?meuYo`u5(* z(UCv4O*f#rYn*}n!lGpJ_lZewN+EY21lqG+0U_MQRU74Ks)z1+N|kb76DwI{$@VZ9 zNysB7U<{5x3kg?y4%GvDGr(cqsd3own<_WYco9nU3z!SpoF5uHhCLSsp6r10(7`XV zoaW}Tn83TfIB5Zj*AoZzBz{LspjrB~ZjUZEqL3+tFV&*cP3xNDz-zyX{rV!I!lr&C zN1nl?KHmAbF~;OP>Abr2#$s=#8hL|}PJ|)l68$bGJShFm#&tT-$KfDzK^BpK)J_bfTRc2GpV4=@5Ar8(yxUK2OJ_J2ObG1< zM5(TV-oJyZb_)P(F@E?NU2a=qm&R`hT9H0)TbWdx?N0f&fszVG_h`!&Kw& zce?cseE2(;@ZDFRP7Z+EMkgye!es>HuAp~){;n(rmQX#sDCFPzGk)jxV;y?)0R8GE z{jKoIVk5S}%L(~=RR1QMXcfJ#$%m$bjqKdolkXX2dY>SOd_6=|anuOlQ11~9y@@GP zHMV+0qxUi-l`osc$-@VBjW{ycVOn`#pSJ zD(bz7^WJ>kxA=N3sjT#0j4izR~9Lxkqp1JdA|3Ec%6h0K1t+v z44ETtcJKDz-9P^#d@;s?qIA6H^tWCcdjxR~pn* zdxJpnxyWOR|xLMl{*#*^Dr`Yy2X8nd!&RWaR(dwePLl?AG&MY7om@wk9ruBp953x zIIUEiBhQGv`o>0)>y#GON4Lprg$%b>v3uXHn9@dKlGkG{gb#0PLFaX7^2EDh>dF}+ zbo1(6p$Ki^d`65Hv>>DU8sY0@ z|31Iq_DB&Und~Nug$6M1_blq-k=;L%Ew}Y;pP2G|l0f|KE_#f3O_3Dtr!sJ*2}GW2 zk0P#sy!KcftWTf}1>(upDFGnK^PHaKJ9U3#oQ>ca^z#RQUrS5NJ9*h-5P;bH>byjC zg!HzWM@9%MblNjnbjR}4X-Wm@92;gW;*U^-&1()l@tAh^}L{aAQ}eT zM-J!3UVEa$Zk^Rjj1~t}ZY;{r_e9;UlDTqkl5>oNgRCUehG70xRCw8BV)0s(#b?X( z>sqB%ecl7nKe|&Iz9*cTUaEQ8z|kIQNE6e49&ZI|FA=Bk+zoq8{(7MKSz&Is8?US0 z+n)H(gL+7U+Jegbv}WS)Q_0IKI92#S2xFSR%(r$ZmE!Zs_n1=An-bWS2dxtdh^)w2Gs! zC}~3;JU{~HuN~-h_Q4>(!7QMn%HH3T)uE}fnBcEVRv#+8F}k``6bgNgGE8Wze_?`3A`k zSmx;f?l4&SK{cWtouJD;0*}=U8@GNKY#@%6`sGK)wBamv>+6Qro$1Q&o&;vIHRX9E zjCiU(esmi!V_{_fv2QE>?PmPohxd{J%a_+zbRuC))9ExfAjX{vYw-`va({-sRk(*6YJ+pBie|?>Wc_FF02jn&{n*C5|3a8Z@z3NYlN#rk) z@@W2ZLz0?)j9@Jajgwzm?*^UN55x1FGNo#oX!Zp1w-E}2Cz)6iM(92OBHaw&Bm%#$ z4Zy{a@GGzrfOd?y0x)0~`U%RvxLIu;VsdqaWUe;28 zR3KFTKAiA6oEv3IZZ0CKCz5v9Lh(M?TnSwTj%6@5OT=Slp%<{^px5(9h--XUKBAch zyq*_aNXW?g+=~0r;eXQUABkQ^YvF_eCBAdv_R?6PlvXW8AUdhVN-kS$WA|n}@h3t&%y|PKR%HDgE ztmk(=>dn*l_y3#sJm+(t``q_+U)Oc^SCMb&`j1Hh^#vLUs}^kn&hlMYG=P8pE=T-3 z!4~ee)T-1+t|ucH&!hu$UPkvkBKhHACE5F0WZL{e%1;y`X`z>3Cy?IGOnMB$0mtw< z-N^@jIH21PwW;Q)G2u`NsaXTQHo?Ti#8c1xl_5wIq0eM6-T()4j=e~f6>(`s0}MNb zzcnBD-wTsgOnK7GOXszK_Pi+LWRm)H3f^-hW!T?8@&?}8#*_x{JqGnS*oN1# ztnK%flx3@Z`%&4D!xn7z4j~4iF1zEFakSc}>2!BWo_W$k%^l_w7=_%u28QAeN z4qr{w>D))*jGxVd?Du*V@eXsG9+|#blFU#)l+B(kYE^y7eS$!UmpuxYId`XmXSwAJxv9fBk?Tf z){cpbro}SXjWjr$hVBh^)W=h-NZE%{OJkL{=Oj)61!Zsf7=-8Pz*E^P9`e7KPdamPpsSUio%dY(!g`XX^9 zaf?|8dipV9(ffQpu^2N_?ahEBm=2Q1k)+CU@#_;8)L&i$!bl7w$n$-64eJF`1YAjUfqzocaIjdZIg+- zmN66gBmAkPK-UwHAwpS8QyUDP?NQQThPbDI4t?oi@}2B9lj|eVp6GHFfg^S@Enl7B&3lc#|B1F7vC&63^OA~qsFqkk9ata zua0^);@=X}31M-SRGZdKr8$J}YO9v8$5L>G%M5Ke5l05qzgpn+zq1vtWB(RSTnPQM z!lKIxrmWc+XcN_8{U78c_0}0MyJ9(Fqp?8Ct!W?3WByfKgSk77OESF6RXqHpJR%Yf zm)ehZ!uU!DXY=y*hKJZkyP#>^{wEMJ8#Y#}mFEkXw<_1J<|h3pm)E!2ETZCYt2Ia= zHp-4tYj~4h$#olRv>2)Dzt;LZzX)HwCU8vSbnMK3Begqc&bn){HCUM~;#)xNDY`9n zDAsKjK%^A^r6rTDDqcQ6o)>jZ(ZmlJ@el)jV-68_T1t`q_KXx=PQmOKEUkCq$Rguf zlwv-Q!=wYQBFRA zsa(cpZK>!|KrY^b6WrzCExnojH)L8t=eOm$n1zM^d}mTW^!JfjM?)U0Vh$o2k&8bI z0%<(e!)*34H|^noYfC z;I+J?RZNpIBprMp8Y|rArg#38tg)D0N4>kNu;m(>qR{m5u=as2i;t%2NXjJq*{|bY zzZxee#Tvx|mg6}!g>Hpk_01PeSxrHQ-`m$7vTucl@+j=RlW30Ek>spyw0edfxNGLO zxl2X?KUklye#+N|gW6Yr#h#={r_0{OAqr(#7N-6I;fjFiVz8brl6QtZ#-2Q$&Z!aP zu9`Y*U&zv640#}DOHkYr3g{RwcYI^oCK=!l?eryp zv~1Y_pV{a}N8XK|(Y^zqOgo)Y~@YH;1~RY?)^WMS$S7r~TfH@v>5&_m{ zVgqzo#f`CtDJXgWz@1iFQ=cX;shv1bxg_qYesFT(G3@c&=gd=QijGOX;UH<;6Udu& zpvmi1dd$*}gCu>LM3uO$TpDiBPdGdm?Wq5FKh<7ngz4rLV&Xs$$TOSuMy6Iv1DW_> zI)kb(K+PltWQk8cPyxN~a9N^R(iV8zcqt*CM400(yXNHfuhXgYd4qo>xRuKwZnUB5QWIO_6%Wg=Nc zlsuM;@&*abN*MywI~R(#&P;Fn-=$M0=$haE+{Y3B{$9R3Gh6MwxkmVxRbfD^XT$j> zgi5f0q3-Oy1%KG$T3G+n$BCnls@ij-bwbb6C~&Apv+X3Vq#E@L3UlOxz+sc=|6*D~6)g8(QBWMZJE1ZONr+fz`aXaM5-!d)8AH=XS zfnw#7Z)IH_L9GBt7ofY|1Ta2jaidoJRfE8PGFixZCabR#6JlSzmK)%yQJ$wYV1Zj_ zxs}95l3?;l89?8KefZB?9w;O0g(8LoAHx+&lHh|VSA(&5)f}rZdR)36(TzKP&eJns z=sY0#*@|nnJ&A5B8EDH#>M<(?pA7!^)=1|4V85}B^yhRBmhIh;Gdc^Khc+)L9#iSk z+`k>hwKFU93k`^6{6sttvaopp^5wp}&;nTMC$bpW*aN)=dP4tU0pVazzzb9(v!4X) zrjVq=sd4r7^}T?)y#fflZ?3*q&($bQKAQA!fwi~G5-}{G zL3mWm%Fj!f8Xo6VcALhL5l6`Uc7sV3)y}JBk-bDF_Pig2ZbO4t2xYmOh6q|tol55(W z#??UoTj9D%otQ{{`VgwDeS2U|S6<}4xg|#ejURvz_*Het4~zRSXT+`?hKq48o^E?2 zxPA)sT$&qg-Np+GOV9h9WsKb0=IvbN#nyc(4rTiv69588k9T^W(i1edJO6Bm1n4EN z#Vd@yv=C;`nyC;HoNCjH-a3|3zdiuZrLqvXfqzyoSszBt;_+a@_(&rmry3bdS~f{sBmCuMHV7{jjn64Er&Y;zP~ z2{^B))!@Tl;mk!N|J|@&zuzE91&I@G zsp&{lMeVGbGLL!I>olx`z3*wg4w){Tcm2$WKyaEpo3*fzh7lv~k$4W47HdPi5x=rg zC_ek4sFDd&L<`jq(Wfu?<_CJK&uq{qTPap2#CHRGw!ts$Y)XHt#Am5(we$&jKu2Ci zIyMx1vSm%6GbI4cNi%rABjCIaL6Xgqj%HTh0xH}YKr@s8opB?)KM;upvFh|wyNdA& zr{zTw-Pb3|*ib9zU~J#xU;pSdkc`a?ghBKd476k!5Oc{g;foo_$Qpi#*(Mug8|pv_ zN4NyM98-7?p{erUBvRK*x1f>@?P6eFigr_c^d!xiC#-# zYBSc!_=2smV{gX6IYo`u9_0=Kn-7MPO=q|FMOiID9T&ToRSnJVE9G7vAW_>&{92m_ z&=HX=7Bm3Fl3FmVM`0C23>uaDQ)BDL=371VtRhaj(X>$sSI@jY2-U4<;q~>_se3$n zH3mqv%GJ{Er)!>TdD>g&JtgT>!`OY)+njn35+SB~rgdBaO)F^oQ11NmgPB=5sI8n%6Xo~6UO9`1mTf!&ZE5T)d6;|-=2ie$3)Y@W$misE(&v&(ek z-MSSFEu+B^oAVN4rJnu<_U%dQ3U+Rvf!-y`3U6`U;g&|UO?4mqT;Y$>F6C?IDWeq3 zEN_-F?iMp3$3!E3J|^ZV8p<%ujLnuzxv=b2A>p_!pQtBV-0$$XtAw)mO0HpI;WzCW z4M2d2FXbtn2C<{(T_oZl!jA4mv*u$EE9qFq(m!)ntxeWDp%^`R^MnFf^f zcq9&agx<`L=n6(Sj}bJX=Qd&32$EXS988UUCAZK}7Eb=&O0;+BW63Xm9&RE?7u@WQ z%k}gpB{Qv5 z__f%l5%tJrE|V_-_4nHS$EsjML4K5qb0^RtGI)zvDMXB+@T1)QDtOcg|BP!k_+c!G ziZe%2TYg;!mp#m)_&y0WF83f_+orV1oUjyWyi0o9IO*iPKLiN zEHWzLM<4Ok~Dgr&{NiuyvtX;S&gnytBGH0Nup+9!9-{g_8DC1 zeIE9fIN$GJoEe_wc#tJ>zjIxl7#(${o5AO4M zWB%zKrbeRv`yCe^!Bp4m39TUuV!&?t02p@S4Z(`!?Ce|GN|nj6~1ZwCXZq=x;veVj3#D?8tX=@wsWKX?Kt zF4rD;Gs>J&Fh0(VhXRgVgd;HrrAFYNt(TZu0|^YcG@O+mzwR6#=)sKgRBz(fWoa;e zpffP8!%9L~<&9CRanO`&soxR^!_Jvt$746wt>7JY)(#>lm1!97&|UvMc6o`pbr-R= zmPJ8+yKQq2{Ou^z72TFbO%R~+Q?ukWM&K~~o)zEq8-#AF!NJ8+RRtI+0zDmMIIMeb z+2X1`ggeos1HYDxiy;Ed6zK9>U{VR_(I;*t!WcgPyeUTQQk(ttWTSIsc~m> zD;l|xNoMr5RS^{KpqKyX52(-~+NP42`sBX&t?D<=916kCGx87GINvQ+pr$ga(4xI0OPgAu{ zT&Rq@rd}J5d8aywUwI09TJY><2BCbn2wbIf#A4fj`ulxRrVIL zUJPQs>}B1eok6Y}|M_v^td{87?SYgMR|fw0Q2qEbe=s#o7W7S)^_wFETQ~RGq>hiZ zAr-L-rL{P(LXRe`T33wt)t`f&+F-$Qh%3+Jesm1U4Nd?2si^gor8H_jy>D4L=@#TU z^EuD!b>Od<6f)i^W!(L5(==h4nTVTKjJZ}(cIhKYX;TXP3=>EOI-~}8oS292r)=l@ zoE^RXERLK2J||MbiI!HDF|ok{VlcQp^lN9t_L=Y^Ny=RNTsvhKtpf&kh3>MFgUqkh zKYa6?)7WAQow)s|e!FOGj9Z=R?L;9EKgA7u#ZyD4?057x(4%T${PQJLrXFrlc@_S; zv^V*c##M4Nr#Qr&xOt4vk?Yfe?C*&rqd^GD8j?Xs3Rc&fJe2Ole|DPAj)4(}Wj)#J~j#$N(`RPSk$vaPGjIAsqJd5@-Fm*PG6!uuVV?OCW)E z>W!uX0o%s;zDk1V&REy`f~e&X=esN;20BJsw&4d}wu%heP(!Z?PrYzmz0w-;oI zm5XLjCy?jf<*tSEPfQv}RHJQuS){V0;fK=qXdF_ABx0&W#Z1H(2#+193GH?9dJb+m z=dhX$8&D%J>Ax4FEGg;xrmr3r$-~xwhh%XyYcfwwY=DKBFbFxWP+PQS5w~rB4?V?r z#kopmPX4vhLG;k;D-z(R_YbVO699XKw{2!kp85Rr@Y`hEM~zvEHsNE}lr2%`T4h3- z*g}oZWs{?YNFmaQYh%EECKWh6(p;aq`{|2-({Y5*C~#&-TM2C%Tu{BfZde*XfHfKn zQrTz+ddm~~NTlL_%G(=g?7u|BS82)&zaNCG$iH3iy<7hBFkc5vK**eVoSIHKhOt|O zSJQV3-g9OP6$>6v1^RyI`4j8d(xt~W#gX0Tz{USo3R|dtm&);$@Xr`%;zPmoPUw^R zw1!rnJ(^I>SL8=~H0R5TgqGD81O~J9NRAf_3owwoi^fV~aCXLGez(n{Em7bZakxd9^M ziJWP+yg0V%4?cV|GVY3IgG5UM?dA>tVF99`>d&7H&rZ32d-_8^hoNQo$D?*%Gjuc0 zJnAl}yqXe2&POa}uF%=dHsaRQPFvTqt&y%|y1W5Xson2inx}FCrdO77plz##)>rH6 z&+7aC)<;Ww=`_A{krY-3mr*pb%k=5vdvZNbnZ+`z8+o#1=>Y9ojshZ+#s>FRU zirmBTm#)nVHVIsSz}r3uFLH5!G6nQux)IO(3z8R~?oC6cRO_)NZDp1}QdOzXoL!yT z!2Q3ixmvGu$8q~ysD_If*RQ0jMT~Vv877Qas%#1Pp&LdnowiYj>Nu)U%^37=-CtFS z`}GLd*uha{`G82!Ymrk==a?M2G{X+5gVpz3q<$Fc#yTJ74V$&E#S8wI&CHNvk`S|5 z=0HlSk|ZE_&U;!%EKAWl-U0?VX(0GmBOcg!oUlv8+VA z(TAjM8^Vg~p86hyvO{U?Xow}Xe~rmuVS=n?b{Fp`ZzZ6X|7R<+$AG^=QC>55XudFc zrHDh@_cmN8WG%#D>IkPwwhbZ{9lA`GFV&?AhaFRC5^C!f&r}~x8am8&w9iM%L9ks3 zBb3FqRZWs+2n+}jLyVgETIHI6UVIs89(^eXph zy@2`GucdC9tg96`Jy^U^H!owQyF;zyE)5q;7Pj35O$`Jmi{B54_vV5YEE$_MSt<(T z5OY#QKzLMBwFAhbQmo*p*0Cim;5ZtV#i`daFJ^_lWiy?L&8UCpG~5q$U(hb8z@k$x zZfsds6l6o>YoAOoCLY7-^C#+aR`eNFub+I|QS{PVb^Oh4eJ}oqw6LK1k;UnN!Rc!S z7#U)N)o?}2ZVGE9Z2Z|(e*e&$L|+#CKGN{|hn{H$yIjGBHdoUtcyy4&MywQ<}PVBH+fEg zH0u1ePD&=#O|eryKq14P3X&FE8aN&4RKI00M7+N*m*wXnl+xlJqypY77!$MnhQFpM z3QvNj&HK0NM{0ZroVA`m z|3%T1UR0TB2j$cLV5D3G^aL=TQoEJo9L%x4v)hE6mLKGtZ)&yTGJ;gY zfRyGVvcuxvWTdB0yUFz?fzndAKnh&eQw9!&AwoqFP^NnQ7)FM00i|2`UG-sr#-t}+ zhi2gJ&aUEP8N`8*gk#e0#kXrz3~!1+jgiOM-oX8FZH0@=$8V463OQNegzs7-u$v?x zGYLIL7q!;K+MUP??A|!wBA!Z&a{THg;V6GcfB>rM@CSw(iBpCTi9~h1_Yx={;z9T! zv?lPYR}ot12tG7WXPF6$O8$%fD5@e5ShC;1}x zOYCb)A*{-Yv2eA9W+n97i*++^_4V6TLQlR4J8yZ^-uTM%BW2gON?sY9eC}%rRknA3z8;6Jjz3Kr6?WlYdMI=-l^|W3#}7Ba|0~p@cG(!=!G-R_ zoQJvKKK#g|WvLO2(#rtSRqtY3v9%ry4S_}>Ym1VmaS|fxyXH3;V5h!6JnvxibE}mx zrC095x2N}PY@D?EkwN#|7X6MTCytzFuiz_9LY4L1$xq&qjZ6XG+b}M2)Su))%Z3Sw zr~oh@>_>q<xoaouWOH;IqxLi0mMvja6>^=^I3~Ch6GKE0dbq?>^KA{ zt$oMBvVU;U_Mh0P!PI=7gugs2(!AwXA<&u8^ZbN94sP(Z!0(qFc)g z0m#JY4P8SA!d#!3@0=H?>Ccb~5b|IIJt}LV!EmrP)NDgC_LVtvhtCYWqK&T)*b8WIbYOgk|~mlJ%gQMk9pW(gIls? zwAN^wcE&G9DLPTTFlUx|kMY~3egg4z1CZukXaewZ#>DUmIe`H$LbcIf0jq%n8X&y! zU1gA+7^}+AL9|d{A=lBg?|Z8mJF#H$DyKP^GD5(D`gR z(NP=S0P83wJ zE}#8$&{Ml?G-y6e&=vU`UD~kpAKnhYj96p~R{uE<$fvRXQVv2Vl8OieAMI#Vlik4wr4>P@5 zHl3U&Cu;MIk)w;^C& z&^*whdj7+1K86M$HZ64P@-Se<{Wl(DJy9Z+O>20pIix?HkiI8S&heMjdDAdcZb>I2 zO&V>=5(()3IFo#o3Jd&xy(Toz5}MWfuslgx*EEVDrq7#h6+*yp{@wY%6K_IwBl`Qd z-@(?gpt3Ynl!v^LfQ#yIME7x!hvSOj>d?oWDRQ0~)nzKHl`_l^#W5P^4yeJT00a1( zu#Izk#efN+6zq>{m}WW45;UbNORfumo1O$EL|uxhQ$@GsbwhrLS2k3G8IIMKHJ zPERsVpdRJD^81ymfKGFDAYQR~<{(6w-BU1*=Ld5&Gvj%hwbF;(LdZ#l%E)A=^_vT# zwNBn62SdW2U3Djj1Ilrv#ob`Z>>)K|J5LlAdEJUn66{>;VnE>o*wv_RvCD#6ZaHk& zg;xKaSSUB%pGx4({5l`2yH!h*ku&t}p3B;=bS~z(T@{P6E5Y0{hW&o`%{dE@7n0&h zE#CyhhLC3Py!9VS(ty-50Iauq-_8n*i2>`2#Gnsh4*>W7Bd+cB&Kb+~kX#OgTD z!kz$DMO>naxHNpTzEVu%n{mb+y7y1NwYPDI@|hy;f%6-2FQU7(3{{I9 z;gB7ZjykRIJ@r`+NE>;Qfz}=0NR}XK1b9Nybo92=rGpd#$$ zfbJ&Sc1kNn*e!(pDRlG;C!uUz_1y^SMlw+QU>WT-%2h&uWwy)tTE)9|dq?`T5e|kv zQ#1<&owM%*4P;BNk4`uW3z4j1Xv%2*fn14Xxsgo~GJrt<3c3wdLtVo044d|Q`9LLl zowsg3xgr7JP7c-2ald*+`lU z=pt;Tr0BL(_f9MKw5 z@_4RLKP-7{YoBkjTA>X~m52rZPk;sX zGC|)9vwgt&f4&z7GTAa5)PIL|XNJ$DT0^h6ujVuGwsxpqcqejb@WZ`Y^fK5=^NIoTr8UdKGrl-5$aR>i*gQpj<2N@ePU7C-k(fHxjc|KO@^aWZ!L5|=YS z*8&~!bbaJu1gMd=^n6woP>H+MRwQ=CBuu5nj!!W6aNx$`NiSGY=5>29v}9#y07?*+ z`7H}61YzoHu#3XofyHb8if^k%FlyVY_XTF7?}|I83GC#>K8%;GtiTTKWFIQz!dS;o)4r^+mcfk)Y^ z2|QP0%Jg3XHKGxqJcK`!QXmm|U6%cW{+`y8%cxo1P=AuF1FbV5f^76PQWt{lhrth& zpn}7+?nO(xy+<%+b7@T(tJAFOdLMPJJ!1ZH$idIWd|E(wF!>#S8N+~xGs=-ZZ?i_P zJ~Fa}H{=ichLxU*#DPHOjrfG(X}cjcRG;&tc^$w>0s);LRr2C*4N*Gp7b!M<0)drRM?;iXtBpBF0}oWw^!g}JvY(jfr4uJA&eMdN+EmQ zk5P`Oif!}x$Z*|dcim)r?PXSu;yK)GK2+{Yu*fd}P>eW&Qut8{vRs5QO81Mu#i~*R z2^xwdozki$%}LSv-Mkrv4Ry?cBaM(c`nJ-V)o-Yy@k?QfPaoPiX5&pR|2%1UL#n&qt9P z=tVD++_;Y#hk21c#d9z~&OFLG0}y?8OkCfoN*r6}=DU*@&J_gLm=iqO*Ozm`k8D*% z!|Re>V}n?$%v@NZJM$Ghk;{|o3V_p)pT1Mn)w=E(_-CFCL*cvGkcyBAZ{{}Ce)WXL zgVG!1WUE3DiatU5GgY5!kd;-G3?|N4viN4C=N=tHNKolsblhsAcACo!;g@scfvwI!>_A zOz&XU*o96A$F+mj?r6ed@K-gS>6x(Ve7*&FJc-rIQ;rRp{jnj4u5kV@F2SsQMOk(I z(cEYeKJx?2#5&?<;KshcHUD6t^FKC|<$m=)h{=nHjiE>&KVx)btSj(kiY`?lKdV7Bq~_iUsP#FWF0;!5+Z zO?VwXoA0yW(!J!@MioW0OrmASyTts6)yfad4I9L|^-Wm#IacOB28nQNG{$)t8T60h zs0f@7^a^hylf+(}^r>HH8-Wv&1<|#fcC+GusHFZpkej&ah<$Z`WIp@exnle3R1!z^ zal6)(8)_UvDHL%R1-~hk(D}nE^7~++Gi|)O{V9?v)>Z$QkVi8|0jtc|=sn2Mj%uyY zwdfd?GHvXjatx(t5R}#bQbUp@#r48}{-H>2V`X76*M3~)t}7%0hn4jkTv^-iN06jK4$-OkljW}*A-^zw3c5R+;K zEh{VQgkzlfzyAyImzbzQZSuHr5g`k^4vOuD!9uOT+8F8~0fV9lKJ4Rc!n&_bm}&nmZ>k&OD8@CDz(Spawe z4`0cKH+(q+sv*GVVEK0qw472pPH!wK%9aXa~+H%!J}8``(+QTdmg-^E!_3yoSu+`XgXHu1K73 z<=a!ld~2=);eV0nOJJ@t3wT`07T^a!mf+jqLF}v&;Pkglg{+1`cfj7TgMbm=OoQDL z+B6zLQS$jLBtL3L@4+(d>uBQ8!tn9It&?YMlw?;(V3_Fi@6N`KwA@S$^U?r5m%?`T z8DL*q{+ivJr$u-(e|`cQ@-PAkiq;S1?ERUW?SSV}br}a>lniPAy1mfmUxv+@v%%QD z2R~P;ipLWc{sO|yD{!ETd%*#{x>6(>8ZLSyYb`7&C_#My%mOZfhhrpw@y?Lw+gnq6 z%W1i!h?XON#=Zf4kmdk3C?_85PR@BwbfroSjzqZMu;^L!;I&TW#&Z+VtUp<%P zB@t6<8F=GJfjG7uJnUiIVz7zADt2*_#9w1;v6AEo`dlThe;i)gXE}@V1&_W)qny(P z$MV_83UT9?=~v3R<{HK>uKpyaiIZGMhQ48;>`QvQVh3Jh*TADq6q)o-PXX@$%(>GP zRRy-E?%mT)9sC7+Mj@!#5Lg46$JGNHEeO*Gu~UJRD|3O`Y~Ax?k8Tm!C%fq=aVBuCEysr$)t{|AtAg7>$9 z248fzY`+BlDNQYVR5ctZX*FMytv!adAa)#&`~V@OZ|g@7nDfz@u?ea0g){)lR*_z8&NeTh~LA{^TO@jd{BzgL)a}qusuWuIr zbOCD(CGa{+^up$5!3?3jqhnMw7CSpTGkYWqhNLY*2xJwP=uj_TZW7~PsHmCNFJMk7;s>} zUeoh1ajiBqAx!VyuSm=7@gvohpGIs#8s&uyd^Nh5udpgb_Jt2w>!T)Wyszdy%)1+c z#Rm=S##O4I1rj8I*u-dJDYs%w3+rp4Nd{3Q0}^W?3f ztNXz4((oC0&SJTMsy&7+L7_lIM8vwvt7DqswvaJ+{%U+kSdcYJNRfks!-t*k>^5Dt zn4@QpVqNY|@n^lRUWXqC@Pw$H-44oAcr~l`r!7cHPT%F)Fc*WVpH~~IX-Xwyey@A8 z_t))eqwOE1+an3ik#wF1sJg;j^--o@;>k#FaJ-ug^SM^#%DC~Ybu7=$(MfXgNm5bK zv9#CpDVoD#d(yjAhWUEoh3GQhR{DkN`0tZw3IllwxuL4Gr&xuE9G@x(js|ZBK$v2; zn25e&RA6zyZI76>;L@a#Q~=gN3?Zy^BjhNyVG*O&&}(8ocjlg#JO9ejEC>s}%J7K6 z@a!Z=Y-)}^Y#)!T*0z?d;7Z{{SORzkt#w@z_5mSq-T070(6sCig>u3tV9_$RS@M+f z-(J#OwSj|AJQ}MbWURA3Vm!6*tFkr+E)9o{KG<`MfrO_fFU3l(6S2DA}NsYG=^CU zzX`045*7va6AR@@;dhe*&j&uBv-xzX0X#QEbKGI|ql|614m^st`~%pN1cro!P{#(! z&vIDOQ3}{KyjWR1+FxC@WM2b-DPl()Dz0TrF020~TOH?kfiN$1QP#=7bIv zi%Nfg(Ps?K3&v1?LBq0VW}k^RY3LZQ%60RvO2UfPd8;*e1#i5ie$OTvt`v6QAwf@@ z0Mn}L_{RbM)DwxJFJ3eNjq`RRI?XkcpbQJhYi7^d-MT@?w)R7tiTzwVL3cc_98I7T zcw!+GyzumTe`G9TOXLAe@C{_9;&>oXyiy{3>`Eg%5ty5GGo2 zm5~qp(P62gx`42#e$>>Xue??jB@(^IV61rJa7YRXyGkC7VX=HE_&Q*6osOu}j3u0m z&D6kScR9k9=z-8Vcn>B7_ff_!3X1+HnA4|8M&#WRx@zz3`8ZSyV2T(LEhjFH1cdzR@fj8#X0V}L&0L<~x%S9Q6#O>#CTiSG zRPh@d8*>I=9mY-XBmy|ywhX*!NL^SO{1HmpI}+lTe{l%8;qJFuH}yA<3`OG6sz9|34qydFeGHrwI z#Wx(sKz)WbHET^XyYE~eIV0VIZNmIaOy?{ZnmzT+ncsATI?<^j6uxiBkO*7=$eSQT zpc2awJdj-O0`u9vLPN>D%}E-_x&aiu(@Td-_*E~7S!RGi1C&!Am6*Rz(ad~ERRdl{ zA1$|~p_h-_(!$v&Bt?#ix@Zxm>`}~Y(B9AX3U4*mNMZ!PPzh3JizCw!d-@_=C)ytZ zsFUd&X3ucJi+b)>c!2k^s(O{(yc!7%HN4mC$Nr4g{tZ#^R^#GP^rB`eDEEs@`=f$R zPdoRU0g|Q!z@ZZSMsk40-KRH*qC!e{f!w)(CN~gR6h8sg`BAec*|?ZVCy`o#o-Urd zRm2Dy4jDHpwy7;gMH8`Mq&Ej9fhHUV}Np<%dpv^B(VfHDE~0` z^zGv$&N5ZO?oE9P;0>YXp>nTvor=_!kG|&_$5Ze3lj-JqS?)D$oM55=wP0In$kZ1o zT7N~u(7W8F>M9P%7Y@jK8P1pmbD+mQA#mm^58Er2LJx4xRIo>_&|&HMjJtBeD0zrz znw1}wnKx9P#+6LtfRtWZtnh1WR50(lhZ@Hf?`H8^4EK#k_H{?yOpI#W0gVTo>Xz#8 z;NL&V?-|yXz+WUI0=y|of}KH?@EmIRr`7{<;sMHen73dS*XMBA0kCGz=YT}A(5U{V zbV%Fxe-Xj06xigcU5(h!D<1G7^pR7^GdVOY6X&+I5laJyg3k7-mGu6o8#m|(eS~)? zCbZzKSBn$2H*sWTU<}lslm4uz4ciX?;wFhg3EHF_ra4g*;FK6r40vvemS70Hu9h@X z6Mpvw4aFGX`K;os?L;*qMCUMw2novZvBi` zy%HGuZmG_uZ0QDUPHoKPpW}6mTY1nNJkQ|{hcl|1`o->kL3=ofbvIVuWhMvgis#MGHa6!4 zdYFFQ4>y+Y9T58*9?wk{0_s@KaJ7>yR8H4=?T6kx=e^!3hb>n+8eArll|a^e`S(1g zYx-@;-mcSe#ro(w8g?mmg9(fn!>;|vrd+5|z8Y9;rnG#YIFbo&@~o+vVp7Un%{=wf zJS$T0%uM6?XEM0ezU@7pK9I@}Z^MJKJ%J8%XtA&17&>D^7OAFV#%%WmzGav9w4U_f8?aIfv$n&yQb>$(0qq=Ky{;AwBWUd1KQKmWHv54}L^gaZF1Y zut0e?CEL^PmrY@g7a&F-uI`h^4W!Z*iuMCTsWxJ_ zf=*B9{1!D?-qH&xwO-rQ{*)<{a?S(;JMqsLzEI!q_RlZx7CV!#6}#dzr8TKb0h|v# zg=S4j`9#SyHWa1%LQ0k>I}F@!eWzyjJ9h+D%gIgG=^)%cF9Ht9<=2z!5%V+W*O|vV6#<7Z}AF zc#33TauZHHuHn?oHEEP{nff91Va6H%VRnaN`$F;^YjuRl8{p@dkv@wK}th3)&8T#untx z+Di|EY6vGlFQ~g-$GrN;{i&g3EN)h0_4D6mUm$%)bfa`9|2;>euNnegpkJ%A7C0){ z1UA71xekjx5u`xE*!JQ(9WB-B5MY2oeFzL>%ycAfjz}Vu?hEREx~^XmNZwve8Yd;o ztW7s)9#(UdTI+Lh@Znm(HimX2GvKi$>3Dz!>*l~A! z3%;ss01EPTf@_CA#oV^TMJlF_#fLccP5HY$YA@68X94x&V{pOAxIF&hC%sD!mC($q z-Ry6<>esz$v{s~|TmR6%Xj@R-uK&$^2D2#q8a^DhseI`KLKP?#x-rN-z7bmqXO<;>74ucXN)bazrQEK6qWrLEp3%Un9Hh9dCdDt z{)&1wjC2?;CkQ{DB&fPL;DO@G3*cP`VdsLG3k^3!2)4HBq1qWLygEKBW8q6d-qj#j z)viq4`kQfJ3RsjsMZ&`5gjWiBv0ve+n^DQ~& zI?&OhGWw&0ZqS`usc^V>`+d!di^M93_QaJFW|d0DaesaEx;y;$fs1fMPQKt+S;?OB z92t(#GD*RNHT%JV%5rQWum>FM(!r09@d61-;{40sRB3qAKI<-krt%m@dT}otFav@d zp>3}a%%*<5Nva+_hg|S>CG>pKC&xFnK4%5ET zTvKzulF2BK=d|!U-}T;Xh;rq)v-2Mo@Hgkc*q{R3;9akHP4%+w7*^efmujQMoDlo# z`q1-+<_=(C3Qe>dhM`&MuqZ(c!+&0cPz3_h#bO=xC5CvuLwyN-_!Bf55|=O6{K&e- z=RJzV8)EDo7Z=(k7Ixy7-&H;H{nT=W8Jf;y*d!>?x#Ipkj18AjwNNjDcLl}q;TH*r z()Md*VX3S}iX;3ou4FE53jn*7ge)yYUSUK3aTzQ4nq?=;g;q!}F6z}axhYd|#_#YF zDjIfy&}#f9_35s;j%Tm-hr*W&oyXXTCinY zFSazL3;vVNVXrU>ul=t1zz)#vpEDK!0>cR|;O&<*yeQ+GsFjQ_PjC`0k89!S3(@jJY&73iE2U=I2kYE2-uOILsoIH;!#K zB%{z23V~t*favYc+yW*IHD$YBtJQ2D{^Fqh{pgc{CkiXU-`JN$Lk?;2-~8zt4vyqZ z+$NVgiYB_ImpI#&(c z|ArJ$e^ORDqCyqN-s}JYIdKmAWWKp@-ewiG0yeJ+g9ii=F&ZdB+XNmKIAUS3ytve@ z9*I*Q)SoT-cz42Ce%@8nzFjSBIIUBHs@U}zG4xk7E1;U z!Dm;)gMy-h{w4@OnPNq_GPwK-vp69jzNX~p*5hhl1zr9G9lmZW!K?a1r&vH3`6s&{ z(1VCNI0*ibyYuPO-!I?bf);b)h|3E!8_%znLnbtt8OV^Qdr~ub+JnY!{Hsy!l?OAA zc=SgpAq-rzb3Kv*AW(O^n%E5c4_WH{!5j?wYgPa)xwtc!=nBvJJ$Q7#66JE_Nx!-E zKzFZa$983X>46}#M95hha4&!k9v+$-|5rS*5+_w-jSC;e(RbqFvZ&zAxR(-+_>E#h zHbh0TeE3#W{Z}Bj^@QSp$V4kkcg6RiS`k1y=~wj-Os=89=3H-_{`sTI;QSC3S|F2z z{jC&k7Q=kw#Psw|xi8yNQ?8b}Lo-0ARLtxb1~{nPf(oZ6bnKYuz$P)xcIVy+KvMiY zT0K9FH)+#O+VVt$f!X5JTo0`}4+HvDpEYgFRk5t_6YVjO*roGmEg%P(|LA)hY`F}QL+ zI9dR`{8@FHr^@4(?&qh&-S17cqs1-}>pNV`LdL)8fL0x~wLmnuKc%2$zM5#=Mt(Hz=N`g@6wkxNrm&kzgv){=%V3fDuM%#E;V{aJu$C>dTjLmP6M9bBl zYNaPTC~3sC>Bi>HF-IHdGJ>G#=?l$BxJm^SHxZ&xt>^9a#YIC7flra1*D&OHJ!@}X z=%i+z@5?a=e;;xwxa zt-~jaq;8JQ2^g=i)JLpz(%HPKj%aOLYtFs4p%p-y$iK-+X^ITZNBD%IX3=Bz$&^%d z0D#3eSA2t7)=8VYh9G?#bVb)f_7)z;pV!~jvEI12BQgD6G1#QdnOPQ|hH~VYVtiiR z$C_Om`KWZ$^Z6Ub=T)uX2>^(TMUteATHKP??`v~%TY7||M;ZIUv)>)DEDK5=`o$m2 zYxp>&leO>4cH*|wrd80Do180jP*Z^pWNou~?!i*?+o0hA?o#M5o}ZE5y#uwt;{I%E zL>>x(vMDsc)P#=(0nc3Lj{w&YIFh>U^}ULRQX&3rYpnyno zEDTDKE(N5!QOclGLb|)V<9zc__x9B&i>oYDO@tSD*)$(SZLTk@;FXUqThyPMcotoXgpM5X;4{qZxCeW~R` zfX&8-v8su{ta;qeROe6do+<;^Hd3Q^lU_~vcDgAu1!qFExyqTD_UA+3(aa=0c>cQz zPLd%b4@o;JI4q6nugc8{N*l@r4II*YuZDax{KYk-fP6FzbwOD4C+V_Es($fb{4zXU zKzjPc)64(#hu&~=YW1>SjcO+|k;n&&Nyjf{)i#4nhK|5HGH*zGm)f3}6hr;MCm872 zxN20?I+17D{g`nMlM)aQ8DKn4O3IY5FV%mXC`#c5dg3{Y@9uIGa)`F#Wpm#FJG9*O z5i8g}u87Cq)=tmkynn3!$wNt*quPyU#8a>do2wh#PHs1&@N?rP_u;*%i z?$74+RD~OoxB-!hA0gAR)BQ_huTw zMRYf{Gk0O|ABn*%hhHYf-sCa~OtaFkMC=RAz=V-#v=GLfyLT^ga-Ifd>^tA20|rJ$ zuq7oW$q6Xfy%1{#P%aiVG_1RtEXh%S7WD51c#qG^b2pUV+p9PBthgVBey4!#AMnW7 zfoTU+Hod0g&mj{-QeUe|-O$lpDdv8;PpV}aLNdNDo3F5Sw@VEj07;5cqI1?&u7n!# zO^l8g7|RY10UWBgY-?iiP>`--+QK_$=w%&H9EV1Ud$s*=Fw zztjJm7BOq`>`~ zCGQ@~!Sd&;BCO$_EOL8g^WQKP+W;{@cYVSV6ll6dteOBBRwt7fZcWnFn4Q z0Dtm+fD5xQ<7KE7zLx(w7wp73b~{jJ8Es#jj_~nT@zk$r&^|=e6g}8Z=2-XBLYu0^ zgtW+RQZb9-!JIBQ)8K)y?C;&}>oGMWznEmhoA9_?mh1Vls{cQ(^;Yz`GC%IdDbe9g zZzHAGr(`c=B(*T_^vyUNRh10d1Mg@soYrPeIzBITN4weEDpJ;cDuzy2Xk2+|ph1c+ z5k9^sm<7m3-+3o#Oyp;9p}80X^EhOPM&tat6tmD5?mnFHT{c@y!{?Z~wVl3rg~dF8d^U~8*n25gw}YX9pI)i9C?`JCes3oI!8)RJ+k9~O}+RKin$mHpT2|u%_5d&kf0{ARXz*oQ|8dMpnfm%s-5`?Ee1n45( zHm5E`H$NS#JL&H3jtJ34ZRxh?&*?zZPnM=q>M1^_+|f>K&Mw=1yFHN+dtxhXlF-r0 zP#vLeifqt5G${D^=RkQA90AFMFwQ%E2Pt!x&83bkBSPAn&pn_AIsjsdcfcTLRbjXJ znyAw>c$-qBYUcimz2|Q;+00ms={D7R4Y3?qTrJ^S8V|*y$2<;dQeQm0R^}I4h>_X1 zUzh5qQi>j0b?hmKdrc}6$DwIQ5+bghEu^#~UmgEf3GAvi@?M(WMGJr3MU~$4qtY3} zi%x_VWDPxng4D8XH#Ro@e6Ik)Ok19I|JK)1J7#=hV%6;n_w+H1k6OXdjR0MQM}3Ko z3Bo^k8}P!_a6y6Syn-_}uYET*UuAGeNGs+cG98U0BPS?M+XaexbO?h8lp|k$x1YW> zx$u0wc;OczI0T<~@!|!2%d8PFnfiuU1Cr2?$ zdMr$vV^0E0^>tHIlVsY$a-&G4G)Q_XrOf)!2qitv?(~8NXwqYePpTe;$9sPdd!+3- z(KzoPn9ZYaK@XRrrH!t=F6NlK3#=UU{lSBN(I+wFq=QJ{LW`Wp&$c$6YiX} zbA@aOM&I-jy#rp{@*a~9D=EtTD2ER~)FekxyG@97Ve9AcQBSiD+me_H)3ugr|& zxTjsId)FV|>W~#QpH!zEkJfXoyA=0&C-_9L49bvV;P_O25v9h=E`L$aWF6BrK?HhP z@4Kc)^u&D4H#6t`uOuvs@diXTlR7#K=ESrhqxw?XY3aAcy|sA||B17;+A;@df;{~N z>@J8#w&M+xm(l6q!s5GDYBPwHLN}xpjTYf@nCrpT?fstZ5zdf}UDYMnnyyP%=}XuC z8*Fj0`1tq?YXj@YUdG4A&wjrCx~SP8Vs`MyRihCTgKZJ+5nKH#+ym`ocRY=31MU&S+a5{QS3d)Z#Yj zKs{~GbPw+a+iFc5!5`$pLET{GG^jXr_L?GtDiPt?s~(_zUMWolewJN;E0gPR4Z|DR8G|-pD#t<0`isT8AfS6u+^Hu z{zCX<@jT|0|L}bsMA+wo5Jz-H`z=t98oMonOCbs6)>&S+4vg2?juVw}6~C|u*Lr(A zac!vvT*@3(UhQ01Hn5Lxd!E_);iw{&a>TT@o>x!D^1FRf;+mk{gx;NtG#G~8wF6Gm z2!KH%C<|M^#3j<_J%67WbL?oOLzI%d;0YQR8ImK(cX^9%`zp!kT^-{9jKiK_Ce|}? z+=oMhvxwap+2Nt8{6}xnKpxBRX4a5>l9)n27$}syK@>n;xC7|VhOxLVJ+fw zg5=`i)r^6DNOGrTK40%o*6@3A+~#)nQ1Wh1-e1$J`o1g5c;DpjSm}B7a-U02b5+q% z=+o&alD9=Beu2zK9aw64eP<84?Fk>)EE7J z4*!&Q+4z0muWcmVNSiyW>f>J=_abHT?CrK!K|lPZ<-DtarGm6-jibU-+2`jbd=Pj5 z8d}NC2lF4OKPQ2ID@HqUe_?s~gMHTOtxjuK_pk6|9X}j6;2K=9HH({y5ALS+ z_1b8#__P7t`vlsOjUmE~t*xm=P*@igB$=T4`+1&0MoMcxmX5 zB4yCTWW`=a24}AOaD_KWDdWOj0D8R~1%-u^>GVbJ=n_E*W0udi2_;?tQw%N%?*~Z)butUh*lJe}kW;)Pc1e6;uEJq;oq}?+ z`ZKy%dhORv-tyqwh}8kb3~=qMexRyss##b31~}X54#a-ZXE`2HKaKizcDG44&$}s6 ziK!|X>%h!RN{|aAqg$Hizx@4 z?aD^?g*$cF8Tm7d!ANLu=hG{1+RvvQoriI<1og~In)y}{U+A-mYTB;IMS6h+z-?pv zg|#&a@F`S?zH@Sy_72#`0mmH+;cYK*>iYOnxor&MA*LdrF)s?`+rz~!a%$?7s1n<8Sy#3XU}jJwO%l46p;l~ZswtzLci}3=nL{w+s-G3M zk2aC$hj&_w06pbLtE2rS4y+->2b!cpqZ^ zU9-Wwqt%s+*)ZL2>GcpfQb5N(LV;h&X%^KKDftXqJ$dkVcR?yOC>VTbg_A@UMC>L` ztAg}6j3-ovUZlTOqF?_0(c(}!zsp8O-vvE1_$+W45rBc@>Ha~P`vjO{N4;R^lLD51 z9lhd$Xp9S`&CA;_*esO|Fu%+M7826MI6yF=sd>?Ff*7NjKF|{#8i$xNw^7w{jg~5d z;kjYmhD~cAH5oZMW3|&;q1DL9s=MFM>5U%M{!-gmx4(WoU%11MSRO5xfH8rs@zbB5 z0h}JQ;PK=6{afQ(YlHGzgi}VLR`T}Ac12=x?PoNiTil{41CZ*0J^KF9`hT*}+hqK< z^QW9rCQ!&!u#Ig&lBjnN&%WdfTlab*4M*F(!`u2j0dkU8K7*pql7~AI#Uj^(ICTFJ zvSpvB+TJh}j~n3z<1s#^tz9tL&jy) zz+1f?n*}_(hyO0b&qXLQHUiq;{||l6{l@kQjh?Kd>#uAL`%EvU-ar|OQ)1bo>$lnN zH*SwtZo6AQ%ggVkk3ylcnj6b!*8EENryUWyKKA+JOeU2;d!6K`1^3$5{<>X!f)C1V ztRXCy&z0)n83+Y=`in=q6RB)6s4&z}zn))n_vHImiW%~+sVd*>XPkPs5!;ecl?lU> zBY>}rn{eS6W|_IGH}73+TACy5CbA_o!sDoDz3ed%##*0A&qj#ll%WVNgUN9G-y zF7RQSzTKzTMU~v_f!mal%cQ}J^s+?44A@b98GJ-szpi6u5GaX%H9;Ob&C^Sn@Lk`P7-COqertWT9# zF8~7RjVKQwZ#|X`^WU4JlOIO0K)Cw5vinwr#q`79h>))%AxOV?o#S2rKnQ~GAsPFa>XISfK>xf)f0mFS@bAIcdvoi2Y^jHi8X_No#3`xX(O;+c#>e)Q)Hhra zN3X%)c1ZWSN@|vg-OrJS0i3%5Q+@?qAK!m$s**p|EqLm89+CJf0kMFQazze(*)Rc+ zCe%1?;v%#~PO_tN5PYhi4j2_>&Jh)RyNFzh6Xqb%dY>DCr;L9T<7gfkrW% zylIQ)!DLk9diHr1#`+Ne4`Xy7?>xI0maw@hA=CdvL8bC%>{m5!EJVjdGpI49SxM+= z&pf{-oB8Vi3e}`&kj;CAkm4S-dEV|bOE_p+BfSpMGbcHzj=$CedSG0%_&gBb{x+LA zK1Qo@)9kXl>M}Q?zW2Z3Zw*oXT3Zk4bbqIDEV(|TIiRX{^G)js zS4i@&>s<#K@E3DNbO(5#c32FD92s@F350O^$@ zdA2lY*bE?>Qp3~8)UOnEgpD-6@t8Dg%TQ;19=VXSQz!y`xsu~I?=O1i^95a2VV`II z4g)Kx^eQWLbFv$WYK$_|T0{a|g-I)|K2RIFGXKtZ=@Ub!c(S!(Y?(Q~6+hQ#O8?Kk ze@ehn$lqm^efNAvu z=Nsx|rd|H)T6DF~1;Lu`cuA>HDw5V;YSgF}#?w9|)%`798~mn;lQ}+o#Z>2(uXO%v zq}G@y%whr}I_@Msz0p%Hfk(;_3g^d#efW5>p*Y)rSU*Lq1FRqUlGV(osV{hOW17$w z0@s7!h~PB1gqz#n_LrPac@qObAMs90qy0H=q?%a6hQ-wb2l(gORi79?^j$Bju09FV zJAW$a+`SOv6OQ7QGhATSIZu`*cWd)bqxjH4g>g_f<#8axDd}|`p7|y;cfcE#c>yS- zdet6+U=^G-?7*J7(nKy81Eah!+!1`d`&#%pmeAVi9F0F6_1dk zg4)jo>zq2LVfX1%T>|(^W&H)sAcT$#2p~d$&?dfoA@^zfuc2oj7NtAY0JTd#Y&?mE zo_#(Pq2-uBr;GSA12*shN{L|z?&=wOs69XTKW|BcZ@CJB)e9;~C3%WZlY&k26=YU!z_1cb9>1Fw%V1t|#>)w(s}ObU)pe@O7$k^aIJnqcss6B=lW z5TB}vY=}IsWzFhlu|~CIR2P51_7X+qur2%{##mHwh0nDPa;Zy1D z=|Kuz#CHY?`rsR8pWJRzOzL>79!>9-sp9xNBt&x8>(5s2-3et&3MRTgQZ_f}y$C9c z%=N05nC2tz0IxiAWkwOWFH(gFCu~H*f7Ri_cm4vYGl#=MP`$915gr9`KW(h$yk{!Ki}igKzZi2o1j@w^Tsm?{d=8m9!U= z>B`~_GHJ}cs+Zh%w<&R0@Nj^q2L0>@`{RmHiZKZhIBj||lNZ*NVC0y_t zF)P_I;=MVY_XK3J_sNVZCT9UaXnS(>g-MYw46~)zA|7|=B|h(|O4Ipqnsjj?B2(cn zn8-Zufth9~ZOx088-$oo(cRj~jDPd$)kHS&{?w2~y95{%E@)MKlQ5 zod^J&*K=Ebr* zH{EP*2qf7Z@s-a?@nAj-K1}yehGz<(-vMOhcYN!CueJ0=%P~Rk&c{K7Y(KQp@8Uyo zAPT0UtDc)nlo*Z+{pq?QhK-RlU zk2maZsutfXPfw9mZE&`7%|Qb!(3T1QY)+1hw`+vlcAmRF!{{`Yx`(;nT-<+9tPj-C>$Rq1*1 zr^@nX2fGak#9dfLxAv1;cyssI^iVi3gCnucI7=6{nwQ*t+UT9rjN9D)212b|)|0kW z0SNsho~P&KUn9mT;dI~@k67y|&Gdh_XW25&x=R9R#x%%DfT_gFHwFvPFTjOnRO@g< zgWOyp@3R^8Dof0*pMO7q%j^*7WE4CZ#;pltql#Esn2(Qgdk=$dw|WM+jIf`RA76he z0~@&*Fz`hVy8U8Ocj|~xZy2BIwJ%6aXvLqVmeEWKyk7F5_5`{w*=g7}#vNfJ(+zPLx2-( zO=EiLzyeA}-Ev)lHeU!?l*`gdFZ4)EfH9PrkP_7MBR>I0rAZ7<=QYcM zA3vr^I(Qy98?w2@NcvX5&Go46JiXaTq9u|1NMYwoH*TEe@czB&+ftkDvwsy{|j}ZEGL=3k{gAZ01gi}wK7cwWKzXCNaTok^F_H*u!=h@J-Pv!rw8|rL1h05~yPE#w$$-QAnKN63I~jWnQ$GE# zKt`-g|J|mFiRrMI`i9fpvvskZGmVp_K6|f$GAJe?c%G4CRD~d$vR;h5kr|)hR90se z)5`L_+RAU0{r6gy#zO+9jluFhi5Fj2ujR3RX? z4*sz&TIg=cUj^TXIkTp!B%4K&H!tjn?Rc>sc4zIbE@;zWe5##>l;zFM;3sGL6x5Ee zfM?Gtpp+%E3jx?wy)r~5Gx?l@5EmEMzr_lPe5+nP?81`dWEM-|4krQs-;L$vIA=ax zwjSGb>EB%0a2;_N+brQu9Ay#_XNdi`>Zr55(-&JYex+aOq}-1+Ml`2|UYVPV+O$=K3m$s`@vz^bfz^|a8_JoXbxSeZ-qo1xNOb$GPUR!4M z$Zg9scf0Komql5uEk)$V&nLy1O_^M}eD;62!>B)mQt^$*Fg;@Mq4hIg8Mm`1>k(Lj zAga!x6y|$UrZBuhIr?}-%4vDRz_vgHyq8rl4!Mw4IQk-t?r+RDwzr2zs{P@Pogfnz z%f1DvD*~v{v8SJ|@@Mj;b?^FBTg&qAB3IIH!HYL;v^tT2Z)#?qH<7iq_4(VNAI|3q zFX_3!M{PG*S=s79ho=Do!Xvefciy9}>~1T*l{(RF=?Amv8^ai48*=44Hr zx``WzJ|pgS<=hm$0S)^;15m|_VMivO@bHS9{~Ln`q52(MR?&8o%?a{fe;J5_ts{8Le+d%Nqpu2HODbFA{+veI?542VjM8aN`Q9ZG%BExa>pz#xDV z%y)F9Rx@S{_G|5j!F3&EzEWpw6w0mj;R;h_W+s`xrAmo3yk|4ZUVxFCdjgSAX>;ovhQ=z+k!H|v_4dnRDrQYx7|CX>K{%>|#A7Hvi~=&nA)z&ZVgwKYtM1mt2p?0ooSw<@-&Sf?{i zZE}rq9}+Tv`{j?2`COK&nGoX52xjpslketS(8wmuG5sqxT4d5lt6gAo`!O0aWYDjH zyRGK^#Sole;iL5eK^F>YpNjs+KS^$x*o;2J|8=?kZ>hs=_leT^A)TDPM?&V_i$B5M zf>TR>iMBKC6k_kf<#;oO?ptMSx2EN~cP}Cv@3Td#a<_Iek_;K6%0JUP@I~MVj0OBC zgAOzBXxstCL|7$-h8k6GrY{o_zQa?i*B}r3@mK;B`M;Qj9xo)!HA8D{V0f6!m27LU z*Z4t8d8 zZ4m9AtF+1BfVjQZ>gW#DuUcQCXBE{+vmgAoh41+*GSkFX5pRF41A)jqgz+=Hj<)N~ z+j@wyGdK^(_6n0vC-jHy`XCcY$y)yqi^GG|{iZMUi_YNT;7qoItO_gKX3jF0+U_W^VeemljwgZcy50>1*rW&%qP(5{r?WE8r+{@*Qq9gD@cr!3O~ zb@G;xQ@?resuR&j%}D5gt>UmK$k^p*wBmy1rl%dY9~X=?a3&5icrDVNaA0x(*P6p} zqRa|M-R5W$4v`@>Kq^IU1zqJH)X)Wju||V|9c{K0=3{9m{~v+*B!ak?{r-Id(&3nS z3U=+cU}k^GeQ&2KLWmDYkEpzs>ch z`d=tc>T_AYGY8YuhK!Xg%yqmIWco((@UR=ovYwuBp90J#Ex^Yi2Fv@)U9^JGp)g<= zIf!ljj1Y1F`oyDbBg3DH3LZg6Y0LmA3bBUDyWchRR9;4_zjMT^@bQ>__&UF#O~XQ! zfXv}rV0ew3s@7C3H61?Nu{!3+GasQ_JV8S;(uM)l*yl5hMxW7;b3H-txk4{_2$a11 zIx+g3=Y^rkD!g`cPD&GV?1+>rI)qr31tpL*^cF@X3@a@SzvB}3uJ#k=3v!|H) zHYbLY?DjZO9yJ6T=q_P-@5$5niyYZ2yx0%u862wVf+1(h4e5W);2>rf5obUa;(D&%#{{LEV66@hT;W4CWfQEPX4;}uVk_emZcbgu+T+|;4 zG6NB=Kw~z67z>}?0|S2e$fqS8b2XHoV`r80hrx9sX>(#skd#&=6jk`+k!qy|vwyaXv@*(e{{=88=+y;Z2bG>6 zLHns#*X^~=%kv@P!6cbF<*Yy6vzUxsF%iTLCf%-ki{a4c7@ud^so#7SSrOzT>3i|( zdWF>5@YI4rD*sgY`m`47JJe6!kQ`HZK8gGC%_Za&$9@-81i@K4e4uvx*%Z|SVkT5C z{=&>2g)Mv1jBQ%dI7h3D$w&V7op)RIOI5SsJ*9VPk1|SqnaQgICuXhJ-VFX7G#5!L zE>YjR`{E*Ty(i7fFcMW<-N?t!vvp7S_`)Y?`-+yhY_{e-)&eNAuCLc#p+AdNBhY9( z?M5bRZD+-d2`x^BS<4lunxyGmSN;Y(yNvwW{(FESujP#&+gEzy$Bl*KTRz4|JR`4? z7v&bJYkm2R_OmZV44Dw;!D3vyDxru~289;;filc(Qj9TMkGF5}?@CD2 z^vt@C)m@+gKI@o1+O|=c36KnDk=m@8#*=j>*sy1EY-NsKGAf6ebE;n}*C z%q~2}<-(G{;p457XRzJ}RAy_nUDtkV#VkY?lcQ+iMV$d=-COrD1&^`o9({d%Yvp@o zWuibOy$g)Tc961RY`KW=>!<#uhHdkH3ca|0mf=S=gN@)&j;60!D#5Q>V z2f`n$%GuHNcO;(&5$r!5s`d1Obj>}TZp}a=l4TQ(_Q)S=k9>~y0=>;cdM-f;8aM6O zc8N3X_$-Wo{| z%1OhuqMYcN)VnnR5J|e5`HKHe(XQfG)$lX>hb9pVi3eS|7Df`Q&J$lf&r(xc(p|bn z)^eR5*il>H?hNfn0i+;)A`+hxOa0<8>0uPa^gTVX?jYifUG$hKzuNA-gGyHs32len z7p_gCr{97vA4D{Btb3CfdTL8ANp3X(1eTaKX*m(_s~)nE6#>fa{n&*inVW?=BDxE)aNmQyzV`Brf-?1Oy!Tg{gE&<6UzKE zNjzD3GrdOe*<7&gUfo_uZbI!P9@ZXt)gE0F986^AduR~`f8DC*-mbs9L&)sZ`!y;m ziq>(26zKQU-+h1j9L8P`j~^$g1#P8>-taX!Sd=K)S>`Ae3*tZ3|=YQoM`}n%?`=bSm)kRsG>?Xg_ZBV(R?DbN9GSE_U`}XDD zK%p3aTbz=McglM<&^{#|93G8% z#O68K?bKh>-`!tLcJRUnh_N2NOT(tx4}|;wN;{~m^Q~#ylz^6N++zjHoww+%oCjt^ z?ny~5MLlR>YX2R#Ymag;>nm~eLFJF8er0J)lDg?@hEozi$GET_c{;$w`u#IHk?9;E3%_*L(sp?r7K*1&=cr}+u4@F~s!H3myZe+; zYr3M%1L__&MeC&1`d*ROG_Z7iv!iX&O+$`=EaL;jOGl74?|y!|L2dZ9!yQzo3EC>J{j6m zU^rLs0qy;nr;6it9}R`RzCo*#j72f;JWsq+Agi##*va}ieh9Dvj{89ub>lLSSbSu4t&@i_(4`lGdejmKLe4eK4q)Msq{{ri_$yC4IXm^#>|9OpiuWy%#^7wlIi z{t{-rIyxbuT@?IJc*TrF!s9O1ItY2MF6OOtiwCsMqu$FlM7)f7B&lL0D)3mSus$&^ zDnH8jS#aX`oKr(J>A|JOgE4)Ju{8-#4i|Ee1@Yd5Vj{AG zcz;^7j0^`Pcvcrad#+ZsN}lm@GhyFogxkEgCIanRMb<45u^)(FRXYWV><_Ky90 z)#ykM4`*rR?!r1rl2D4(3lr+Tz*XMu3!VZmpMJM!OQb;FtmsqxKQzM-$Ff*i*gp}R zV>NB89|5eIi~EOq;Hag+86Z;gaR0Xm3Rq*p3po)j%8TjmHJ_NiR&t=@pet6|q;al7 zTVi&G-`tN-7+8_?ehI`F6ugGh|5CY%_v%R`{PRD!vw) z&*)Y)qUTQc;Cb2`r<`lEq>mrETNX2;RWv*sX`f9|%}}K<>I5aUrKW~c@4raV&~q77 z`5?RLY{fnUP1RN(aWOGFNJfO9cFdu?VfqD9aaB-d&0?)>nD3)OPf!W{#6HSbZNyO% z9i~KmH`IANTU!Ikw8AO3oVSy$BT6W{^b=mVy!>yuuK2wLft9e8^?rAu-#;M_RRl7f zJa9V0y@6613ryz8*?bsF#pZOt3~s<|kT`hZ$PS`BjbxR&_Z^bED6-GekXhtcX=7ar z;fwj_-Xz7iY?Nm^ck|u*cg;eCeF{M*bqsG|KWWQV-P2qvM??0n_FiCrABIx!_m{zh z$@1-<8=MO}jVO@su4Icq2k}`y6yl^obL`wKYr9?cu=;-QO@_-Do= zKg>6iI_FVTXRQW~Zngzwl!XIEL4wiPON?4By2tl{ne^|j#W01)&Vu;1EQZ<@LBTF^ zF3E#B2p0Jv{f(Q_4@yGg^Ci=5yM|_s5}k*;c_v?L%1TO^e51l;YKV~UePqw`tXn<) z*_e-~F6C}Cnr9=*|MLFz0q%=v9&TU|Ju}n@b!Bbkad@l7YM;Td z$*lW@ld^BkJ@tNZvxNI z_3{yq>CQ6x%k zt7dmpW5wd2EbK7~V!a_fv!TUAnxNH1gQbQs1%Dj+UE>oH5(*}EM1ZHijHz@SGR1MC z4fIzxN8DRSk~d`uS??!3u^>);9*)Nawjs1AklLxyHsu zIZ%krUH&f%cR0;Y<)})>x+OYWncF^D)gl`pGrm5^<~f}m8daUe$|QG?8H;L?7D(WE z+z-=8QogqB8EvGo1*c`D!@$=J1R=Q^PuzSm^a?2B@qo!qeG7V+CBn%m30n1~b)MK) z;*PV&$g4K2H^c&*>Y99Q^99=0*B6_EXll&{3?iRY?xZRGPtu&kjyK^FY|@8g|Y0zms zRUd&7Xfz42LM>Cj{4=+S>}b_T@z(_8%~aM!gW5ZM-K%rG+gy^DULVE4=Wohhxcesj zVrsG9k$I&wDAxHRXR7#(xvQER(|8(cb78mL1*gtn z?qTri3);x7j7tWWBEFxkRh04Xl5I{7cxZokY2e4Ippu_q6lllPXJbIO%sicoVtsai zi!8e#ixpt;>I<|BTwxy%l0Dc@J*q&7ljy}wZFVhXx1BOAznfY++EUa$#oW=zWK~^^ z4j~bli+G;ylsdtAhth~N4j4z#-n*R%IyF|bo zH1jM&@~r#Q09p|W+k*>h&D6vAP8ps1&flW|0Q>D4o1S1vV~?;B64b+sZNppewy>wl zmpII2lCN`#qlFH)!)dACSZdQ4Hs!{05rQhl>h2K#P_-r9{n`r-Ms8U!!`2ZwvM zu=?ODA>T`mw8MVJtH%At1G08no22lc{gUEQDM{&q{g6%wsJlMTrSvsTS*;xMG;JXN ze1BNWw;c+2yYVC?zWuEJGkL*KC$m~w3Lc${JODTahEy#V%2Z&)k6l9!>*!*VI*EE| zLOfM+Z+)`7lS42*+c43xAuF`=Q5-GXZh}A4ekc{C7sl8AcA_jGjPC5&ScEY0==YnY z_4Od5@?HMoFTsOY(BYHap7XeL_r3O}Xv+RsM}D(MhMqU=)^D{Lg@`^|yDOIjttNL$ zg_aCkyVj{D=Pq?NF!emAB&GSbBj5@@mJD2eFbZ%zCif*^ zU%z={4vOkd=-u^Is63~}quf=cq~7lBZCj5vL~K(^KH@o!-nX!reqU;vti0=F|3|Yt zG!o;9N;d0ch+z4R(Q?hDrT1EIYYUf4PkhBg5|!P|MUwbXM2UV`G2+Ly}9)E#mkpuKZLrEYc{Kk3+sKS zRrc)S0%vaywlb+nxYxTB<~_7{>gjLWPOq97nrZPy)648D4U+?Z8|$aSw^#B)5{&PL z9)T6u>}LzKsYyw{!f>y)@musK#`{UY(@#?TR-;lo>LX5kynSRbxrcO0b9Y06-@}~WZx0)}Qrf)O%y&y) z3sPJV8FBa4vNc@{TUEJXf}1ZXK;|f6-QcXmQSW$f?DuJuV258z=VBH(`@B?d<;HD8d)hULHCKp{@durs8z}K5oK;wN`Z;wfbZu_g@bN>OKy8y=8xi4lpC~I5xtFG1{4fXi=w*HYG3y zO)fVkReqwK;>!FWdpG*o$J2?Y8WSb}GGv9!D1Zvk^Knm?YU z!f?1sfz4p8l`G`Ro!udib=@{cYpulhks9P1NU@QXo>a&&k?MPxo*&8s;?rqKeP-R)=IDl&_3~;f)6pW?&TPyaR zu(m*>>dH#V+qZ8Av7w$s5e3^DYaLfGe&Ja6*EkgFzw+=HE}tIHxE1I-$MPiVN?t^Q zqJZ>qvVWSN!={rMV_Z$g&1tZAC%t&BYn1POt=r09$u&IOKqE7lka+LoL#N1R#dDkt zp_EBEfA5qEm`BiVx;nnNi0%$-OrPXvQ=OB+OjJx~iC8iGb1&rS;MVZC&onRrCR)Bl z)!1QkPP-Jjw5DqF?fzXMF6s-U?b=f)Ik&8}^KZ)WRDS4sgVZ|#3s03*&D_Wl@S-NyDl zvPTVOwd8ftj_GYGFFzGVgbU$(SmS8!x@?iVH{n#@$F0?$x+hB_=c>;1OOC%SMJ*#z zBA>E8!_x=MS&vWo zJqrE{=MpQ?+F#ox(^L%Yyj(GC$%d73KbVcQv;o-C*ihL0XQyPflm~5_wAvgV)N1`$ z#6+NpNq%0%RZh2kTl03w?c`Z)lQFzovMY(cTaxq6^$Mdx_{w|KofYWq1~n zdCwj0I}apz+LC@JhhN6q#GZUbz*nky$sZc-c}17yQ6??6f?k^oPYO zFjHIOh1CHJ%h76oNzj;YG4K_R1CI4h)~I=<0nJemgILYRl$TK7!8?_iE_L`?`VP_IzoVF)-5S|=rqYlM0ZO&M#4Kw z2nQiT7zc}HdtVz9$KsH{$mk6|%&5!iDUedwA$w1yl8$29)by1tOiPWorl^@Giw&m8 zHqe|{-uAyRhkj~rp<7h^CjWez7FYk%!nTov{UMc7rjjoNrGjdjwwbkoM&K^3I{;Qw z{iV?AT5YdA=WKB07`nQI9jza2C{aAkS@JS3qGxR8BO85KN$PrPNh&%O5b@*O9iQRh zKr{sQzQx4&NJRj_W{K)OTjI#d+I8JiWg`R=p)KVE({kjLl;$KPrW%38I)HG@3LL{D z1IC2Z{Eo5_TWqTHW?E-ys4Suy+Pm-1=d&j6k@TjX!kiOk{yX`$Wy`YDMAO}_SZDnM z(<)(GPcwii+b4ESKFr77LH^D0R-HHn->wPSv^-q~F#tq9NJ@Ni%N?I-1C zj^YCep1;2C=-`1g^uPSPe<{S-%JF&;f9Sl}4uSfCGpJW^?loM^e)ae3rkC!0-#w3! z{crW6hml6mNZnT6<8Efq0=g1=T{xfvLg-BStSgL29 zS+;BT#YC{Fcc-94y(CZjfIF^krW;yoS7ckij+{>q=U^YbBQ2=mbw}^!!=DeFh~f)0 z(it^PIj#yC_WhKG8R@166G^6(hJKKJpwN?>f9>Vf4b~NNh?&BlKD+G2pslxRwy&hcQ zy>WtwQy=VzyVG1SA3QIKdp~|l#OY)9uX-%ob@B0c5vy~*9PBUMELr|FnpC*g;RNw` z;c4>WPWLlKB|qkW^b333wa7?gX?a=c-5EwZ*a68f$Q*Q*ThpUjcQ!ah{P3)=uyHy! zTJli-mHN#~a=)p1;iC}U--3U`%IqeED@1pu4^~A=0yCY^Cm^=z=DKy$m)mQ27)WEu z^F*Lg=Q0dK>IgfpwH7%p8%ms#YDRUQKThRdl>Pht)qHanYq!KSzJX+e183WLI;_DV zQLZuz`UU#+}CYph9jE| zc7aBTQXek~I;|uC{_=(KexT{k7dUFHYQk4p4mS{Fh2i`Tzufw_cT&d5&(IE?|6KD> z+Ca; zs$t=xIq}I&dF`NXf%kTXiPN&2Hh0Gg#Y-nRJ0qW_ckY36X5QSjpvLM#EgRmYloFlQ z)KG8pII|ydjD=2AK98HCcRze8q7ypIFCPv}wZQGzMHQ^DXWo|d4J;%&YX6JWvG`uS zf~pXvA1su#SJq0VD?h*cGL%H|Z{1!0r~G+$57+Jv!5;?)f>sl!rRF!U2k&SAvu!vVO}VG6GTKNvG17AlqT0tzj9BP& zZ<+PVy?_7iX_A9l+K5tLSvoK8gf#56 zcZ9bce;5|ZHGX|E)691=;`RK5%*X{3oq!^h8-=rm!gk~0OIe>rn_t;l9$l<&DQw(o zD&jw#W7GNGN;_8ygXKcdc%hR%L zCeX4in!X6{j5FQ~6?B^`u%$lyCTCb|QfEPIAY5`Q^|orke|=m#q8St%fZ87{v1^t0%Q_6^k5+Ne8(ZD3awN(cdo* zYdK0=0Sq&ti-+vu`)HunjD94=gp=Es;!nptZyOpcE@4jBxYmXk;&V{2NZ;rZ!a{9VM7~1;m6J>w1dM8#^Vz*8&`!%`G zPGWLBn|~M`vmdXCN<4fjdU8J$!=u*ELX?Z!p8FvOb!47o*e1X$0f5AYtgjo@2DSy% z@Q~Eu9#vGvIJ|WIDYC;tiH$Y41S#wr%;FtOg>rY5p_1fr*ga}B;rD2x(1I8YvkYTH z?)9NOxyJRB&XUWhBAOjO$JyL=J7}NHc4kXL?bBT1y8Vals8$_t!tr_YX1YW-C~^FA z{d+H;Op@VQqsdugMxmXgl+u2J9e*1G*-PSy-i~ih2jOFcD|qbKDYT5FWHLv zWaY^C?0CunL@PQlMyS1g3V^v5AYR!|G+q30!o#uAx(1j%vqc&XPX-k*+n*T`9(;Ur zw1;C(ZxBnVa?m@SoXL$V@$cgNxARv^RMNYOKbMPqI4YGfP+=T4V3v2Lu>~j+Q22Mz zVQX8d0aYM?U8hT9l=4E^z5k%McV~ltY`zV3;gKWEuOJE2y{hF`aZ~AmZ#y$H2M5bX zN$HUKI-E~Wx+@YV6XLcUdmG<-y|a_m7L+t2GUt_wYTdX~_~PktwW269ASh?(R-%)t zJ^rYZ=rFk6RM#8*8elG*mkSO8Q3J)+{^r1<|)b(H~8 zrCpd55y3!2QNjXg6_AoxgOHN$P`aCu1`AOMLApe`r8^WA0Ridm?(UfHTp7^s@2f>PJv|W@0~A;$#~c1VGh(lCk{;ipfLC-oWP%~o?yrAMIW5`anE4M zqc43a@0s)S!@>DzY^qzKRa7h+$+;U9o&2zf@K(c-5;(UJ;fw}-C5242S>%9z#r^y# z!mD>M{Tx*j(`s1EHrn0A*)6KD;ki?8>y`ngcDdW>A7p=E^)HKZhD06{h*KOZ`6Nlo zW7ykhE1Bj5i%)jc8*1;&e{N*`A?f?s`5Q8MmqGZo`t-spIY z@PwsZT6jX%k9?{ON3lM}+D#&mwxOitfV?LuzTh+Mpi>{=^uaoGg_8qldid9+ncld{I z%h7@WH@_b20fci=)nb_o<6MDqmk*e?+4!%KMqz4sP?+VQ<~6bnP&iLV-U|bXR{Hnm zxq*|=@L;k}GcdX8o5Y*fZ*J1tf8Q-y5NhUS-&ka*iJ-5jFRUC%3D8X18kh>rbyRpZ z#a@v6-p(_-V?W^TA@301vrM|qw=g9f~Xf?T4%>Mau zQ4{86v(R*BZ248P45%V=AtNF~S?KDmMxvu-n*hSVWQDT0<%|#m+~5+pX(>wH{pISU6@3Cl%8W@#MWt zS4jI@d%0V%i+s~LhOtUq*9<#4S?dSLs<9KKzM3Nd$nV}4`!2_-k>oFG`+RoOJs z({ABsaTT^4UkmSxVIeL0)9_b>;9Bi2W;(4%l$x_=*4;;BF#&q}(3D#@(bf28K(DrF zy6(n-#*z_~D{CbnSl9v+<_h#u1`rCe1^4O7j|=9B(6=xElnU7zsc?J^K@xC`m^fET zTY0YWeM9>3gK4)QzG5q8y4A`?Y`1I>_29lRS2v2OPZ~HQ1RvM=zH6&+P{rN4j|IQE zR*37?S?z?iFzh2xQ(G$rY z+V^(pzC6U>NfzerjBRGHUbO~X=F8c2HY1!Dz|f@z)ctR$<*P_OlqX%}XJLW~Q1U^b zY7%I>3_+EBt8y=FR(%_AMNrRA03dY3@Bkn!$aE|0K(9T^x9yMJ*5UbDoE%EW<+EqB z7SxxvmHtNw7lP@$-L{E3I99R}+HcNHs>j&9M|`u?~Fb{n^g@LdvY600Zqtc zORIk#qf3Ch`{}!i%{Lk-0<3ID&_lGOD6)bQc>*{(9{Z)(ua%V?RwE+v$#T_uzbi}* zYK{8}{Yf&?lyQQLwiwsc-;>tTBzJ>~iMsCX_4{4vsE$*(fof1gM(W+RwdT&mY)K9) zg3-2xz{=T6=w-Zd%LCHm}Bv3*dLRY<}jr+?BTaOYS(**WN4Zw_L8`dZxCE@x|xSq`Nc(z zo4wNe!fwHeO1!<4IbvI^jrntHOgC=)rXqCX+`3JGc`uquE23q`Ey*b){i3SY}%}|Bb(jX2*N3p0^`i5Tw;KUpY%3C9fG!Zq*F`DoKOuND+)`>=4If+VS~6Buf}hR|X)) zR-}SYqC5AIZ+E%V!8zJ~2p?&`Jw|_fE+gp`NP;-$U1YD!pQmpotQa|X>jpw_(pBhG z39zgMmuJe6u?BeEMs-xLt`CXUD_r*JgFoo&f_H?uH!hS<-+12hMnYr$c>PEGjCT3D zZY!R`mZP7vKdm;+1hv@6$(nNIU7EY%qm)1)738cUR>UOTd)HPMv@HkM!$2WUyZkaN zGfq@1w1O|V4H8@q{rZdB(ylPrH*@wo#gI_)B;NNteeBq=qV(ZxQVd=h8!q^>@T6@> z$AgRpM|HX@l00-Q5ivROxnc3?*NSS|YugXWU^n4oTmfrVpY9(?kc^N4_1WzBW{Cb4 zK%upL%V2b6c=XZj$O$Twe*DjNvnr)Pk|(R=CPNP~V|svD3U-}+{mxq&X35pIiB%}7 z=uNcD^P$jM3`91DLLTQG;(OSREsfq4N-h%t zwGxyLnh<=hiw9pq-(7%nFG=Xi1;gFORg&jXK~c!Kja_%-I?BTAZ~2I1I;pu1<>MTO z(FfSwngWH0o}FC|pXh9kkKQZ1Kzvh_NDA zjP|H~xi3AUIQ^0S;Cg1kCyC-q9g7J~X1)_>_qA^rr ztf)H?MZgFNR&#<>K8llkv(5sop)q~}iGSQ4D%VwBvqr=Gk?FvRa8Ulb&P?#x zW>2k$qzr?w`-lQlxV%`u*+lgExRa>>~YY%CB1Mv)q4jwkEdvM3ORrN+i38CAKbIrvOKLEE+avk2=iXJ>sGZgjx z@Wcpqoh*AT;=}wLlQqR#BolGC{%u$rogll(Qyc z`xiGBj^RBT1|A_>$A(5!c0L2`?KZOyLDyF29tl-OCC4A%# zy(L~dY)0};I?o-e@hd7#ww_bu$*j~FElE)+ESNr}pc>~Q73QXDdS5XoTP$y`#J8I_ z$ob&MeI90W>718(D2S`EXQ+O&J&lp*f_OORvhPPqQ9$dLMnuQ4K)0VW47O$l28NnF zi(N0yzDFQ@(GkM?thmSN`R*P1<#72KHcWG~pygh^{vvV3qgm(G9*Uz`dy!(CoZ|jRqWA0*VpUN4Lj(9lk7V1CqnZ)?{$LB*; z%bW_WTM($v5@_C$@p1hsy(d)XwZ;wztOY0c@gr#pDQIRnrTaTa1tcDhHYTGkFreby z*w?{dr&xw%S1igLShYr%|ysKw{h8K0=< zGiU^&uA+?wFOEAr2g1_!V!!kWaeG1GuijU=#2ZxjIP#m-dLw7;l+TvGy;|`Y`_G+H zNAz0|^UJ{_*ZWrNk#Hxqo|I?W=9^(cbSG|>v4V%PL*Y>E(z18?e+`{(2ZLc#c*S?( zhf4`7%2_&@9$WCZ)r;*~CmLfo%zC)C3j%fNp;H0p$6ie?z~@#9kk zKAxVJkQPze(RR){vT3;M;U%A|w06ckurPZtZPPrd{x?sTjg$ZGwr9%e2=n<=vh}n_ z&6g!chizTHU2f;WJjPg3>`+h42xus$^`#6y`Bvf^g^McD9R=m_A%RS#8k4qlGhDLV z&(H1F2S374L3B7l>})VafTHO-%nBzuPf+tCjVs)aE&QMDuL=VCIP-LGXLIeg-X$6u z!LqWlL&C$2;8@P~^J>LQ~qIogwv{4M1Cv9?}s@c0jc*C z;~wg<8ul-u?E>;g=V#|1vh9OHu%!F9&cTB>#u?i+&z7!RS$UU029r5^hTtn4su!OQ$N&?HM+h+DxsUvj0rcV~>nwf&Mr5A;p;_7MUjQ24YnZf!#C z>$rK)bT2Tulj>iVTPmejDV)Cl>`jAXns=X}w2Vez0F7%lIlzl10^JMGzf)ho%=497 z{j>RJVt8IMKEGpDKi20lS=97vj4!F!UW~SKO~bRGc=#-gE7wtJ*#Zq zUbC%`>=M0ILT(p*W;<)I|(;?x>Zc_lke1qal#L8M1Yd+eD#T7t|PC z<*wz3(hQ<;JGp6((mcER$?gCP`15H7&g0}d*0t&T@9o z(X0DEN$eGOa|P#^CES`?aMo~icQ3X5K6{U2DDu?O;*w?D!Av2zb*8{1B_jpbjoZ)1 z$7ik_?NYk4REtdHI?9BsM@9f@OMjwF*dDTbP_qYIaJqe7Oy%X%8MkL<6EftlJr^p< zFdD=w3@g21J1!Tn8T0Oy>G{Bcudcw3#VXYYwx&dQW>_(W8w7YnM<7!>EsC$ zM#s&Z=f1FCRt;4L;EoB{&JK6cShs$*UI~(u)=+}BM4dH1!wYV07?e!#GDreCZ9(+4 zGw4U>2;L&T?U`)o>9Tmwf`tB;MAdT;P15u5xNr@eHA{6 zzlmpPvHXRi+adMVrhZXp>%hkTqkfu>#j$@tu&{m3+=x;yzlkQ6zn~^TPO@%3w-wdt z67OQP;V+!`_914HtB^Qd@bz&<7k0(*an#G*bY4>z&%(BPxbe68AsFlP=g(rdImq|m za5-*nZ^O(bACwx!rtghN!@<-`tEBI}`upCAB*+(d^XARN&))c$gPaXU;&*dlV zdS`DuRStAnyDi+;fN0 zVHyj2>@3Wi4NAOX$vK%#_fZZ@$DQ%6KqlMkeP7C^1r#k<8jR0y2w|A7Fb0db=bE{= zZiPSjVU7U!Ov4a;f4uJHHVjJiTwEP4`~$gmIZR^M*w{6p94U~(lms(fZ8xt=u%>kh z@ZytS1qetkK7yAg;zK9DW|>n>@XZ+i?$3{(iwk4X{fBvndCmsskG_6b#`!?_?v;x# z-(mBstp9hVwUkenU+wi9PnmhLQhD1)@@d>zKaxCPJbCrG;kijBFa<6jh8P2Mfw z;QRfD^Zw?02S5ppZpOd-wXLn#`rPSxPVDsQQpHuizf&oVMRWz(Fz8lqT*0!L?P@JM zi5_@8RCsq|L}m|D|m&Kf&qvtNb#PKrNj{gPR*6iwPii84QB|GUeHKh@C&q4XMlN5 z8S~JgnqECwu=G`)7c01zsrD8d@wpp<_RC|r9(@_|DS>-C%Tgh{b9FE6t=)S+z{Zj( zsQMHFE+ss6GX~~ikQH4DBjBwFY^Ld*%o$t_-6uj+H%5mfe)aCeTe~ruD=${@Ik2S+>+_}BU+wuU>y-)L{l-n7O*C6f{Z3zOE#E!y$%rguizd;6=zVxp4LHD+rk zo>6DiZ;c%P<#7>tW5F)4I+LnD%)i9dkNqY5O17)sZqo+2mnok@CvJAwnS)W??{dW< z1+s>tA5)*a+p*?g(xpu}x7BbVTybm><Kc5=OL)n5om%3R{xp8NJTk{8~0K&r>#iPVa)X zxFUb1_42uApv#y5FHk+pwTB-phGx6-c~{x1Rn1FVe}&6Ht^(cdFHLb`b?^3PFu|fz zuRXWz`JZ;0$$J-XxDJ`@_@WLpZp8dattX0^ zsT<+Os?bYG;=rnfJmI4Yi=sWBzm9z>1KlQ*n2U=Gq+$CxQ`Bb3eH6q7BVV`1ZPMkZ zIBci@AbJb#I+_VxURYht)n-Fb+`mr&JOZcE>IIk^!(!Sdo$~psyK9@oh~F67t@9jP zSuRT=0c-Wv0$##b}Bgr@6!`Z`bxO} z6W*ET;pW{hGrp2|-X4^SNDo&36{W+2QxK{S&V1&nERnjmw^0Y~RJfGdPZvx%WTd2f zsVZkac>uC$3D9N9f;j)onKL??zWGeGE-~P`(^Nxd)0^b8buYI76`TZcX-yk@h{V1U z4Nz*rOCs_bwH6d56BE-fvMy^`Dc%CD)921jFojf+aS@T@!5%Tcl;A3gU}1^^$D5{> z>pZlm-Cyng=ZP;D`qVCZcSYEkefx6f?1<2se*Xnm;v3@O(VHq9{bEhe+x;Yp!VoGGVE1lnI=Y;mxm4xUh&|tZ`P5gP!jD&@0O~$4!Oua8a)1zqhL*taaMdDrXaTx=ZqFfpbZb9tX*wjZdrVX_Wvn!BT3>%? z%;dm`Fh_gxJZX|<+wLcx$|(cQ3H}6!^G<9NK|vz+5!}{7Kw-;( zNKoBFw5g}NTNSowNYC{Y2GGmW>i4&%D5h8pe%+c%$p{bERhBe)R_px8>T!M6X*2V+ zo4Sy2Xhmi%+02h({n6Ju9o(~O&FApvnOb$A6U@JTaS|wazuUR~LlgHnslGv(LfyJw z&P>2HV>@CcXSE2lQUcql#<8k!_2e^k>16Y_W#YPt`WDcejGMX*@71GSqNjKgQZ!W8 zLZ1xpbzM+XZ=&o$OkG{ zItj51O$Vd5bk1oiZKM79KR|}RBXC%l6>y5BW)&(2iC0&XGlu)fqNsg980dJl#6Ds? z$*Ql$OkR`WIc5-|x@_TPL|cSg@vhus_SeNH?b_Tn5VT1F4??tx_~V!&o>2o2!@ z5#gIRdX@J*y@E7egyCGI(SiLezuVb{sqKg7hvOQ$1KND)l7D{VOsTGFplm0~)_7Fg z$ZR_?b7`C%n;+XDbwxY0+c4dTEb-7MEeOMH`ug>F*>7Gjt7oarPj>Dry4>06V2eh) zRFF&ZSWMV-N1?Xlwz$W}W9C<#5ktz=XkT4ZPaAgIn;e`?-)Tg=o(G&=0+ z{nrBo_D{9-o8lgO7nsL>xJ{>H@$Od^-&c5TRLUtwDlZr;2t1Ea*nGN~cyEy;wAC_j z?x&r{q2>V_54_2t#ZTo3u$n0}`(N-~sk=fbUIzNBW5N_qFHwL$o4y!JN=YtoT=b7vtIl80#Y`cUdHUHi&efm zZNS4Ee2PF~+{4(k!C}8IO}}QrF!Z6QVKUv3u9uOtIIkh-Qq+%JiiGv#GP>sf`u>hH zN7bS#a@VbjNwB1)rHgjgx}kZ$Ov!sz;EoKaqyw2%<5|l#z8VJTDuYeoP3g@1d`6Il zL$!AuED~Y{q~7;e*cO|9{nd^JP^3fIDDOO|o}ni@tanl=oVlf*2hG}b`QNM%7UH*X89;C`LMS2mF>Ik);066t55u{a9~QsgpDPC5;Ka-B z^psl97Vjm@g-)4_&(CHg)bgj%nW=>1;u1to*T9@ZK193T=a_-SBqQQ?2)4BhSw;hM z%-!oSoi!Thn0Qr-WpaE7N646e~2gD%iNG@?*N&tIok76*=282zpw@_BJ+_)qFw9ng{h>W z8D0VKzSic5$V@$Ccq%~F)GQAmV&rrBz+u1LgbdJ<8>Urb%P)=)Q~b0!&1K85CoQTM zH_5Km#s_5Nf$(1N6W%Y!VWcBMLTO;%r+!p-c3*n6Dcy94RYONacQ_{h24;Z@;$v6! zQ2Ai?eEhxcHJ09Wfn}lI06?+8o7w*LC6PM4qnX2OSTU0A29|lx!tMT5cM!lruNttm zGO4YA@pLU1xXV6%?=Y4j@1Wb!e~S`ZT-&JxWa%l1w0xRkoopLna%GOZmkzLitU$0> z@8*JOfl@(H)=tvW+f+F)c{C*+KY~0FQgicAmF%%qg#&l>r(3VhAu;ksL*_17=Wj{tWtjZZ`H` zMT|C%XIJ+u4_5Htri7F;TX^~TzH;DJ>>6B_f0O_NW7Hh)_3ObNxNEv9x7B-Ukh!Rb zcmXkGr5Df1fR9mU4LtmFcfWt2U%Oym`!M1J2TM9*?fzJ%mQk_gOedewa8W|p^*8r@ z?*gJd1k}tA4R~aQPJAn$3Z31jg=Luo(1WAEq06?dRIS z)z&iUEqRzc^M|N8jpE9m#?neQW!YMv?N)?Up?UqU&7oNw^V&o_f%>bhsmjeJcHf`$ z6}WCqCV(JeQQoW0h)K&QKRo!-S_qs z8T{(1uPJ`!&`CiOW2zGPA_pqHF1mBC7ndxNv60gVQs`&NzAT_j~DlK8=Ws~0^G|l!2sagTc_3I*10=qe)-QR6) zbd)(4Q?l(leqC{*Peo7V<^_ST|1^8wyVvX8Tm3Cga@MAI&Y}}TAgKTuM0dM36v1y3 zu0?%d(7BZ~w=SWpuxi&@j2D88nq$jpmvqSy;`Q-=JQ3N~U{s z!Ey<%W&QJI*#nfh=evcvrGB^`otqv_Nk`eVB2tiLvMP)+*!{vu5vgJ-ekZQ~ ztl4ya`f7Fd3+$Z|8MDNW4<>fmSrlfoehAoILxz#Z+8bN@yaPJ-nb z@Pv2&7n70J>I=M*-0YYhXd*Dz^660?jn|}tx>O6p)QI1e*RQ>QS9syJYCXmE>b-sY z%cYd@l=KJcXD91w7EvwNI@~bW&PfqNcOAZ{!U{90Y82l!Wk#!)&m;JqyOg!(dN_Sf z*_^ig@|_X~LP~)$pv+&}fxINBBks#YXbDyg4bb_x8VCQGZDQ%%n@=UIHg`qM(E4=T zy6Y%4FCRtb-169oV3wz}wmwX>?D*!2b!Aj*f+RJ7ULl7A^T~ov98dl|Xwgl&azxXu z1~1Isk8;Nd!UYG4@arE4rGQk(;UoH11ffrs59xYH?i_uXRI_G!8W=*etgAUf%28!g zBq=GZ5Rwg|m+{Z}`lafXswC|`#IAB~Qf`N>j(f{lk+S8mx1-3qK+xtpt6$qRvKqYt zP$w57o8GU9Z|O9UAD4dPixXr>)n`F#KXa_4ox+6u=&m?EDf6F0NeA}dAD z#3YUwh_}G=R-o3lQv*OlY)l}O)(%Mx$ zkF`T$k(+S>r83^9nX;%U44#&vuBaFD7OdH$5vV?T#Soy+bn|EqMkLOAS^%ROEyEhY zJFdmn8-Qp{HAknGOZg0b~HBINbPAfKf{#M_)j9j%U7(6pMz`PZQd-raG=$vY}O9*Z=TnqdfWbEw_LyHS9^c2&umb& z6mC9APWgB-?ajU45g0|_GdrYc*0g-A8~p1tkzVn^BX3uqT#K1Vyqw|bfJ;KYXW zyNvaVLnyZkXn}@y>6&vztL;yVGu%Z;NN!664#MgZ8*Nt*cbpzNY=Sf5!QDD%xAxs;>dyQr8`bjFPITPb& zfW(EJ{{s!tP!hiDxb?Mg8~s%;Y`qF`)5WNG$v^@Ti`s)f<{D(Ohz%B$ksJvK_F{Fp z7ZL2-aRft7`mtDSseRBmzn8CZF-z!u$ERm*Dhh~vn0VPxKQuv=B!Rou9Z;eINk+VwbI_pUu?wcDIi6}4JG-FoX^q0$by ziaA=}lGRKmF;tJG$ETPWf3+v+i#uONk_|miLwYo54=hPc%`}b@5m^V;sqX~Z?yV;X zQ#-d*hgBROG#^WEf@xpT-~`&Wfp47)<8uSj05oe;x3k0QA4C>Fqoi%SCtfuKiQZ8CuzmVi^*txGozDEBpGRm9*<)&$C@fF3aaO>s8 zItPQAdk%JE**vH1zH#g2xtTa*)HotBE;XYJ8>Z0#? z%#;|H78aP_p6JX@C@CElzV0vp3-cd<`vr+*L~V_ z;X1Zdh6$`pmWSrDnBBCC{XgqImu*wtd6jvJu5zRg1Z!XREC9j*ust0g4{ri2`F2YH zXUTwg3W@b$H}AWn-)Tc}BZg3?K3o}u1XfF9T(Df%CUBqsYl;_Cl7s!|lhHwAC09u` zQv&qPxWT+_msWS^1v%Pt#yysb9lkI!ummtEFBLtMVf?X%X((*nU#j{fYs_G2o;1aA8;`A?6y#%Qy+wunKd6b!w`76>g4F{*)^ zIFosw0BMqi<>3))VZvu2C5*ck)M$;chebzu+yjkDyAG;Hz_RIBGd6tg0=g;i=$7T%<#V)2h zDTqUH=^vFF&43|Jq^73zsebrNgNKHPhuJ&SDxz*YeugB!3sc$OhbYL*`JwXpvTb$< z24z;s5pi(|qJA(5>PBDtl?s5Uudz-2LEt_;AVR#T;1iOE2|QwXU#tns&6c@BPN@nd z&KBf#oy}sd_R1EABcwLx==Asoadk&c1 z5n=;i`0|l@*Y>q^dysn067BiZN;EygCt`=)86hOtA-8m`Ouz06u)GLg2)&xhwa=KW z{7%ilFt(F;voA0o$<$Ej%a=)+Qw{hiZp_2{CChjhq~7A%x7Z0VZ)lIk01rbjh?&5z zXCBYYXt_L{NLq{m;m7cv46Cs3&F>~R>wCA&5PGWepy%_{&Lah6$_W*{>^U9&>V?My z@{U$+kf>N`d0L-YcK?AS$&A?fw@$7}-m3je`79{8kECt@aBB;WpO8(zYm4Q=SI4=z z&hFp-N!LDu7dw}#TLqavlYRMUiV?}yFMMYKbAe5VZCWM`a%OaXAH*VYA2}QVeNW@G zTYF!%#hPeI@`rTZ(rJcvS?;xc?LQ6k(HFwsCst3nP8WVjA)`94%~@URN0sRCmbP3; z?7NlTXX|#hn<$p!f8INe>?qB1low_%#@o4fQS>{MXr!$q|DWsRX^CLgZ5pvarkW+q zL6!}jxKXV?Z+6mktv+gW)*Lya-t(xxXk{dG#g!N#dgPZBED!Q#Ai**0n3`fPqJ)RA zg!a`B)5(+=oPS^tNA-k9a<$?zyUBNrglp$_oL7dO$yTIOWR`C7VTW^w5{=D9B;3~S zP8?r~fCH&%AZPjB`{gM)t~f#;7ijR!KJ)m2i`5s63wNyH!=HEg2gTbjYv%) zu}RbNn!E&yt4q1OKhcn!0ke>(mgS)?VCJx_U}3M56_kZuOTw*Kjd-@7$8%D|6(=*Z z$(#t4*cfD;J3Mio;3JrvLt9JpC<1DRNBk&exC-w{NxI7P>e)5d%koC_vTf=%@dora zg3eW*LsR}hxB<H9>_7Uuu(;Pp`wc}RbcjI5mQ>hA-IS)pVw&bjS5QL*be!712pULVdj*sQXNk_b zD8(Ylvqunh{<3CD%ENpeYb#O0UlDt=Kk{T7M)?rq?2HPR?BiAA-%?|pFCW|@Kh!PL z__pC2P-D6|wd*$wtFCgK@4OE-SBoZu3I6FQl&qT>`0q1fa-Ytag;6eed?VyZB!N zQ_t6!%Gp^a<+UqILcMGc@d2`)haPMXP9)xK1onAMR z=@y-`5vJR8*nA_f-yzUIOpTe)+d9Wx_>3`T7fJv8Iso&|5cj4ap*^16bUZ0_zUcmT zbf3%p$o!!E!$B27?4$>Trkv_Xupl1mL&`;7yBVSS?`4?YgWg^7)ovOcK`N*TbDGl~ z8OfU;C3 zMm?yeJhnV^Hx6Ja@xPc!EVlav7h0?_$fy*?|n9oD^p+6l;*Fd zwJZAJw?m6}$;&jPR>el_tUL)3{V;L}J>vOeAw4!i9mff01;lk6(RFz8wTcpB=U@FT zg(BrRIR{-Z!$QNNs~k$H7tg1LXoaw)A|7{DWD+b;3OoVPod@TF7T@f%bUVcFhjZS( z-S!J3T#vQKUS^w*?hl;Y&Af1ij1s*(Kli{T{h(zs)_3x#lp99h@~09~{l}9U00hl=zJ&8g)AyNR~-JwK6TA-)}uk zza%n@!`W4GVG7G4hECsg{~y9Ggl2cyqrKj(%9@VBvyCn(-xZox^RfmatE61ZKU9l`qZ%nX%3j<>kB40Yr4 zI5G4X>a9NM%-t$|#h`dS#lKi#ztw0~p=RAws;Z~7zTH6Ot1iH~qwIQnSN1{n8Qa5N z+fDE;b|6NaDS$(?#{4KE+BRoyf+wNz{O&CywQ0+u@is?L!XM1Aum;ks5eR_&)H=5Pa?54y_JgYM^5zQmZ2j$96dM2~9$(;i+s>5mRcf*^$tM{)@h%su z;_T9lpp3rsoxJ$GsAPuCag#G^BpWZH(*}YKJJ5k0bM5UDXo1s!X7Iodz!aN=Y zrgNywK2gn^uf4t6=*0M?dk1ZEX9lwHx^s)uBI5I-((6$+f#vzG&ulS2KNM#_uh*OZ{?T?Ztp)uka3Tk6p{xc( zc1$iCPh?Li?me{5Cf--Y0P1!FwT#ZS35ul7ls>VOcUM!g|C4X_d&yi4%Ef%DP3J<% zm*2;`0va-O%{hOk6`o#q`T{!)5@EvtMUj2t}AfUK`pl}2#TA(PCdZ^ z3Y=8T5H3N4Bq|VlaJJ<6cZb`J5!EN0r_@V?9DskS6rB0$rV{8fH#UZST5}9#i-YU2 z;*Z30#?zvz(IomkRvEDzdQnFi67P*T_O%vHSW8}sy1x7ZNww_M2{}yD4tClYo4ud>z;nwBE!H)9FJB3(TlFLcE7f(_D0O>>Tegw?q)7a3-X>eh zYc;JRQ1eTD?}UVlNLp218-!AZYd=-(Ly{=htdGstTLYG|5i&7w;E@v?!tcNoSoZ6m zS#6>yd`wcg9mlxt?Wtz1C}x+hfF1}EPU(pALTUH=Q#eVAr$plAIZJT`Z?*bY?hhdr zy*Og1i|uaLiih|giS%5GOmj@pLuJI2gy3crRwjrE$hUrx&s|!0h=5nKze_}{5q=NC zKp9-kT@^$BhN$RZV+L57G{T*tIYnebC8)n>5vl-sn!4#bQo2@w|Fr6Y4yQ%Z^JtTC zfm_Q;oo1|-m-gOQF;uJ`bK+g@$E)1ZoaIvJsY#eVUv+Y^X3C2n-PL!YTsd< z%n#c|se+e2?ZC;2gv{gZhW zR+BJY7Ud0p`>sr98>GQ}T$}%GZ@w7geo1b&pc2=fGt;MA#JH}$_L`eK{R&3DiH&C_ z;H|SH<$LNsK^zosJ?bCoK+I8o!$4@9GXi+lHWIb9B zD$yKEf4Qt2zyIGsxbYI9#Z^pcQyx%j;m*C#F6nBvBdfRJs;G)Mnjm{Ig(iyTA7Q3% zI=)}_qDDK9k~16geT3dR^2CI!m=W=Cy~x3$T0z)2w3qkSTpOcQ+zVi-Tk_DfDMRuN++d0BQ#|H)E*iwJimOkY+S*mNbiG5bomcMGn!Advi8S&R=Dy) zAqQvag!H39>Y=vpCnl*!^bHdn3KO@E_3@g)KR2t4qSn{0U54E+>}lPorb4HAGSn9r$XshFyX#VHg>5fXz4Yco&^*08 z!Cano=TbwUd1n5~dLtPje=M?tBhW_uQ`JL(9|UB1XAT?=BJ|kx``3mW9pStF+ZwhW z?X7!k+Yv_N4ZfPoybVG2=BCwFf4dDVoJE{!%0_IO!Ux<`NJ}A@eDuChRpRXW+m)V= zt+z5$PTQI_BuP+nD!lxv+**8(JI%g45+^$ym8S6Ht1E+;=ZP~RhRILP%#N^a>mn&y zI&(5d*NE-@{uP492rVcuhx;zg?b`|szn%)2vp!XYs8h*^sOuU21Ud)00H`G}7BlTB z|I^^ORb0cVj{Jx#nB{Nv$D`x^;cRgB=I4hcybn_BqAcdNIBhqkGi$bH2a8ANhfUAU zHtDw}ym>>Mug&I}SN~0YO+f!{!cNl!dENXiO-QBZ`8zAn=;Cu>bog_up9O5njgBWp zZ*GrOl~N0!U-JP>oBI6*AFADYDQjb+b64|$(Q8Ztn8fJ2qc7EDt_9M^fS@2nzqk4M zsHOJsD}N@*SVyqRDfMpG%J8jpTRr$5j{f-Nqa1kf!UPPHVGD=IjS4Lt@mSjVV3XU0 zCib6JNVm4#%jLPvM+Os^L+!2LrUJ524?g<}TN@#2$6%>G*@b4NT!M+EKNenoG5T?Vr?SqZyZseQ45X zX}xeoxTlz?PyVUR(`U2lL_RzAG3WgfZ7 z?pN@EKnNm73kU2a$F5ej@vagr5dMzM^S9Bg-v0OafE>oGMhJQlE#*KaM#ItgmeA+> zKa!aH>bB(68Y;nH>eW5T?b0C^7Q|-gADNMa)&IgL+|$#m5jbl3$6ENNgq=e()`bJ0 zFYbD3GCZnq=d>{>+_{yF3lrjY{<>*jlj3=wFI<@BJOv{k{Qu-b6)Tc~AAJZbTx%NU+dCf}!#II~=W7Y~m{< zCI|1#(H=Z(l6{k!8}Cs_ibuS&xnEjyhDS&e9zxQPp@YYyk#cIBKPEy6`D7wR6?I)# zfZ{MyxW>h@KlP9k1@rC-gnpJLE)P%1KGj%1b(#BF?#%2FhghLgbow}K7YCbg#SlY; zS07JT?2KOv@2SYp#WOKj9aab7W;6kKT+I9byCfEP;Bh`@U1O^f1Xv&)Pwpq!W(4zr2LM~3t-76PNTb!Ovvz7vMO}=#lOUY*Uz|A$Cme$@ zwvcf>9s7thv_LFyks*K^YPBreV;ALz2~I&;S56=^?I6awE_?LnpSMl)%nj78E4TLB zsnw7a|Htp^`|z{z4fl*!SMEOV4i6{%MM(%jd$OyST64F48u@3v-P9Tic$4w7C*Tnn z_wl!mXvpA{;GUVVHtaJouFmqQJZum|EU_i`e^NmP!f)Sx7}R*v8*zP#21sJ2We8wm zBfF~3WciPQ|Ne{x|G9@1IDX>6QaVe1*KEkRBKptc8^W`rpa4Ii6fGBr&l{jDL?crl58$ux53$i&{IF*Q%Zfba)h_>ZKr5{_dYf6u20Cv$0d@o!A4ALe->bIXazD zt^ibrnVve=?LXy^=gjLj-i@rw`6+f8+&or1E*pCYk2W0F4!QO(p6XkS>E z^A9F@Qbf-^%&EW_3`^raUS5YgWjL8EXs(J&l*7Uep$uzp$gWPRQ{T+psB>U=&*|^) z^Uky7HS-lYeI-g}{?8rNNYs5{ab)vIp1(Yk*v-Eb!qKN$Shg5-svZ$sVa6mGnf>}1 zcXFDz)nV}(MVnPH73*%n{~dj99M0`TPZ)-?8NV$4IrdFufa#|SWppJZ!_hDIXgI-a zpc26tQ~ruiSzgnQfvz@7yoN>fUS9HQaNCx}qQ_cRh*Fl^B^17dv4Dis2m7zF2U!ws zuodDX<$i(fk;87T-t*uPnAodcul+Wr10j`?UwA_MTzDvFh>spPfKC={;&ARB2h3>l zTlk;bf8ckLwbAzO-BNZFujh~Fw#@DHshFKp${5bnPmMI(kCe_BEp5>=xq!f~)%q|w zl-X`g!Kr0@>w5N|hZ9STt+Jm(RZ&OEWwYjs1Q}89?S0|)Jg+{#%P>EUtk$X>(td&o zPDlRof+QSH^uUfDYT1h$7v4EvSgia+o5Q{r8wq*e>vL?f{7E&F&`+HnJ3pU71I`*J zNb5d3s~PK1IW^hf<=Vx~wz#+?4Y_0N*_GlP*Ly1dc{p99A0J>I4j%ThLA$-ssYm1TcD~JFug!gp4Iep0Eqfzwh?|$T-PdMEvR&bES zK_Uw75p<~J&O|)~^|J1H=W-sA%Q?#olm0t{`F_Hox5@(iO5{C-<>^1YzYp1d%Cu6t zu#4tk&X+gEZJNt@7ghEoaXHG^eEj>Jo@96V7-t|G|Joyt1~SRl$cjSjRa>)elwIDn z!vVZ04=W8bVcO&U8eT=lE--%8yvrNsF`B#>#{s*?T3O}#|F7E`!lAdm%YzmBj!hrA zT_BjCgjv|8UAUP;yZLcK8nB-&3HP{(pXOval{TP%a>F(jz@?kYHp+KL?DV@q$GP$# z4VDL5y6T?zHFwBNV7ari3gYe$f+9!UA+4*$*$h?6fo2{z7_)RZ5#S&K&9vWn?0+qV zG`>qX^fo`16}3;>XN7ZHCqIh5E6RW*k>pE_(0BX^dw`Uj`WM2JFE6F6BJM{|FIywc zTm!r9badY6a(7`zfi2NGv)u0BYOAZWD=hmNt9%{!K-CE1QjAn$lm7NA?l#v1wf8?? zfwVhu)$NXX*o7WCcd^DM2c5f^;Se{(_eqk^sQ9; zL#6LC9HM+G`9hy~zH@~N>w8&p?;CGQsx$WbNz_8t32;uZ zi7&Ge*{9)5{xy{^dnP;t?_jCa3f}>RBrDY!uq)Mx%}Hc(B$IF%(q||W8Vnkg;nuF{ePJub8MCUT3=y% zl&Q1)zO6^wpQQBCW!;|%5bW4g8Aokr^-uq2cG>q?D-6lUo>(K>H!A#r1bDLYrGR*9 zr}=aQiH}vNmEspBl{GtDK85VW2qGVnb|h^S29+F8PDil$!{&xn<+_Fy>zJqjkXinF zh*+^OL$Bzm8~65HbC($T_;)QIe2;WBH{KmxO33=g##>LHblSQ z{Wl!&ggBtgRmhCv0Io{5Zyfvg7ZAiCz%WojdUIlHhr*WaZbm&noVKmRE#WEo;{UOA z)?ra@-`j@)1VKTi1SKqxRuPcKK$Pw-1L=;TQ$#^R5s*$vDe0JD6akg)W2}_Bn02MsQzDNxQgDDr=#U1WC zW=EonsZZAWC*)?A#xF9Q47OpnH$G?rd;&ek9C%K=FlYek$L*(7HqWL5Z`p;oXsTvi z)x-4-K&i+P(gSixBVc-7;_ts1pMGD~;%2X8L+ z&;E$I(k4$1)Nqb3y4#zEc^8Zh&%-ji;hUxFni0)fJ%7(bJQyum!7nikvSPOSc}8i?=eAj&_vGddsDlm=C#?|*=A-{ zS+3dhDo3Ua@O&Cc!hQ`Xjhex9IPm`TPlUNUV7(B5xKcjPBA&pBx5U-|1ww!^Vm_+zlwg` z)R4PbYm2*jBG;}gh{r9*hJQG!;8^yJgwVnde^2Ox6Q1JKgKc*3E%0g;3k+|20M6q; z6+HjW-MdCYL?ZiZozEgvJaPMQR(>BkF2GzQU@2|{a~xR!r@7Eqc(*Q)y2$?7%7A(P z%*6NbUzC~|eaI*m(!1`+4yJsYKHD%}NH2MF@`H9Iuh%mMakwr|i>DG#ljm*7h+}Z* z?F`LH3w}i=^dv<$nU;+HYHqX{3E~vo7VZI z1iNY{moT2lY7_53g?$F3(FBN0^t11Af<;Y4``+GoZvC>*3@XsEKFe83KWms$ib!rv0XD#o|uc(;5F29uwVITlUzZk3~FxTF2hNN4}5r*AjPqLTyHPDzmXZz_7i+6_v3GIN)8^xi^Gd$sf96(Q zef>bAPMK9VAbHRC$x27bZw1)`y{qg18yNDnJG3xiMjW3Pa#FXfF-U7=u~v3g-P6NS zsB(Rr;ptVc`=YfDgbDMQlb(h1Wd2eC!uPsY)H=ZcKE%}qljyg8`;9@V;292-)32<) z4+i?_o%`X~UdYVB@EZ=EU7ZSUC+sNUgJ)OWhyjEKR7VbfBwP+-cSNkQyJf3hvSk%@ zoP7syhf@|7_lZePoG6ZJPKC{n*1U3g4jE-P0?RZ}xLi-Z0haU-8Z#q3x+y8#wKnIX zU9$VUb5WOWpKlrzoin@RSJp$iF^ysxnB(y!fZS~yYlt>n|Jbi0&cCPR;=6k553v;_ zFlnR(qQ5>3WG~zGSd}bSu)2QiDgaN{moR3?m%-lx1;c-~Nzj`CcCN}9EsUM`sG%7g z0E}J*Mv`w_CnGfCw!kWvw}7pF!vp95VDzYA(i+MnZq>?@;C`@;v7=sf#^=rUuA)`Q zGPV;2MT8#4^$XC@e9KUnxu#~{$%Rl{@U}s-_3mfN*_F;S9@nf=Tke1L;;~c{%BUAt zduX(J%xtzaYh(2oZeUHrSC~SZ#w^RWxjsvMQCrB3#c~flZoWR{k*l{b7v}g7E+CGT z{meb%^!~#~E!X|{clkx{_Jw4~t;@EwMZ<5E1)=jl9iBU3{Dk0oxAy(5!rG5G4M;|C zGuvZ%gB}#c0-tjKoH!y95z%Ch$X-0nfCo^5YhaLl7BH)A-VYR;rqUX?w`-A8gfb-z zT;}n7j;d=sefspXBAGy!uiiQDJ*p9i$;CRA*_p+VE*=+TPjhc7n=?zOv7I2fPKCne zUV&;*8IwH{Yjkp@lV>xk7h@^IF|hl(l4m+kgSI*wT;UqmGi=ho%%TfwMsip3ZV)}S z+rGs{ zBlOoFKQFTI1#{rHx_p`w;#|}W z3Lij13ZE>)5iyfGRuy%J0wswU>az&5MEbS(9H2J2W!ZFgG}UucqYao_&M0mj&MZm9^NO*N15t-Jn`{({Y3 zP@|@(Kk<+fek6HIqxR`C4;TbRC-{7Cu8syJi>!@9(-XX7C(hb@uk$Lp{pRBF#NZOk zLH=;3A3zca2ruP;4f6}`ktXN^%CoL}t<(yr5D5eJd zY>Sus?i+iADTG1YUVv|eVQPepQ52j2mafp0PV>i43LZfu-jc^tl+k77Y@u+ zvVoevCHsR+lo>Q}w&I^k^8P6Rfi@kEn5VJ%e4nGp)K3(QK`w`N=1CY>d-Sezdw3bR z_cS{wi#_^-;s(o_7|!~O_hqk8IX z=0F044gY$t`=JR@`#Sq0(|AL6?o z5W|w+B)dKypxiO*)*uZfhimVd)oHBP?BGo`gIf~f*l%?22FRim$9V{fjz+D_ApuI$<|2vK`U-JZPI zKVNmwNPSWx>Q#IfkVwXd+*-J~yeI-Uw;TNKn%Q-CWpe|5^vWin)hsuzIVC*3B< z1%4<26iksW_fUXdM+K5%Mp4(RKxdyN+cE>vZl80snw9(SXV(&Pujg#CP1VHi0Bxv8 zPigUidvS{AZbrV4{B)*VgMc6_P*AF(g*?_(QWFn7J^yp+J-oBsfW`e9tg|G2Pj&!d zi?QcCH2`vN_<>BMYu9vgtGQ<$sGu#J|J}X^zk2OL*g&pBBajLBJl2W?Q}Xm|Ozd@MlxKFo)v5v`?=N*C&bw%@gY)`dJs z_@3?v?x(<@j~h_B9-jG=*pUG0UzasI1D-ODFXd(Uv!*^vMdDVpxH#bG~e9u#s)?~%d4-IjuR+}KbF_ziTs{E1aCt1)abikNBRB3 zqg=4^L%r3xxoV0{;9d;P`B|%bs!W}BH%%|R`KLD~!E6e%D&S+l8yb~NB85Im1Ys!y zx!Qt$5pc7O58T1#c@-_p$yxPMP|)_skYRg4eD4IVW-OLbKxbkK-ER+fz}PY?Kk1)! z!5l$!>o9KwVBND7Mri}V<{85oaN(2e(TFri0uA&^ugJH^$^PW_VcbMR$?Xn!HUSzw zLeP_ z(YL=Ne<08)>v@wNK6B@C2_UIWh~NGwrn{H?CFLhNLtJfx?W7661Zd*AIFn_G{SMPW z^FmTycVQZDl!k{O0t%Nui8ku=mIUDYxLej$pxzQk-P%*7mN!y_kj)PTOO>>0^WA z&H2nCZev6FljhfDMa|X}S!!heNy~!?GY?CL-VLOXyw5SAP4KhZ@(&kwbL}cq=$_S3 z8Wi}edj*A9(D@b+ybPAbgntq}*uhAm5dRlBR{8z2!X7~vQ+{H1yfO#4T#@UxqJm`+ zP;*0b)xsN>sn3Z^VJVCK2rBY>QI8I5L5C3=_}q0JKfE8T61Ln==tfXtyESr<%0N`o z=?$^TAx{hzFQGBvt4H>3sdtqucop3FgzI2|J8&!w`Z8p1Y;!J%(0KKFK^=natp#ny z^%$h)a$(E81Y39MryR-s4A17;J3@b;2hm6Gx8)!3A`a^=Km*ksppkR1w4?3jkgKu^ z%H=na>&}i`?GaK~!QV3^hij0eTC*r#e9>C>mk0%+S-o4ZL;F==wKD3U< zVskpslACpvCPVWu_vmS?GPmO~IaZ%5fdr^$0rgvPiU(WdmiRShBXVc->DEI&rfyvV zesV?G`E99HV0kc7;+kU}j|791OD~-iAO4r-Kyl@aO=_nd_4i=?6WO4zYHBd5`?MHJ zS{G}3S->-BVbD^`>p106KKl?7`ba!oZcVwKTi?gT4~)M4criiIvh`F_66Z*~lEb9O<_W%i2F~2x zpp%z3LCM*nT%{8-Is-bKccRy-jJ`NOlM?mci9TYQWYSVy*R^^8Zf^Q0bqkV}gA*}-59G6M+` zWtoC1;J-WE_?f0sqjO{UF}yzieMZVZ-<(+qW>xwoDz-ad7TN};hcdWL_bLenPu-8- zrom;5Y>HIPX1Rk29*2u*^9%rg zcKC%eb)8#9D*{cgN`(~GU3FYLR0RS`9FfpSFc-oWOBq1)S)fi91D|>srb8aDURcIr zzJLsu*Qu_4td{)~&;Kdz-t|Dc6~HduuqWE(n{Qb14I%w|qOcH}5JGX?``mHJhk^Q- zdK+ZksvS*sKsg}W7DTO|#)~|n3S|zvhhH`bm>^c0Zxt^b#_N!Mnw<8iRVvDJb2l%U zhj>xD@5T;zJo5|Z{rm(jmi|F6D%-H$_b0{Q<3pP=N%ZgqE?&H5IF846zWv%F!gY_t zgMOhGq~y(im?o!HN`NH|87L++x(PWJKAmZ< zLU$EJ)*cm@0T?3J=nLTveAGz;s%+2)O1NaWxaMVsBJJLKT>2Bc9ja!NVEV&mw~91K zY~f~oZ=;4m_r5f*aML^BS8#M)J#d2+lR&E)v?5CVEmZ1!s7o+wj2RcB_imV4WmL>+ zr{w(eVn^>24%r$A;gyf)+=&N=+9g7e4QBkr%J#fF84$OljbwdS_%(LrhY*Jts|>ZB zMqBo-Mi!tuA3e&Ln@QWQK`RPGCSaL!*y?w+FwZzPm50lA$uWBr<^O{|tovx_XSVT! zSyc#w1+`+7tPB=Z3$&G8pLA)f?OUX$q~NNs=s;z~vBY;{Jv?VrMS*RVoCU8^jlj0( z?S$W87w`*~?95YD>fMOR1SIh=z$c6>4wS+YBuabNt8Yy08fdn<(!4ZTB* zx9P5zNe#|`EYxRU7qYXTMgOUZ@44YecV7XHaR%%UVUsEqb0GWKj4C$>s^V7vEK3wG z@@FM`O9-7xdUd#}*tiR3+t}3@RMj8{3``4H5P)B&?x#b4L9IohltmdM7Dx9YuiR#FySfMXo|Cu!&S2R!~5Dp zXMSVr!9Udzi?{IWz9(K>zUFJcn$cqwqz`AqPd3Fhofl=lo z@(kv%JVFyrXm0^c?s*E~JZUerj@Np;alA2`Qv|W(U6>Lv_|c{!l(v+KLTSs!hvdf~ zZ5e=pMS*>0U4q@A5Ikq9(Xb+X9zKR6*v7;i(!q+7-1AnnU9rl1Y#m>ZcAaL_y;VYiB~c75r3DgX`rQ#5hi#)3uXqhp~fhr37R1>wEOLA03|w{5}k+L6sv5A zsjwL9ka#EzW8`U+p@?$Tq#TeB1NJgU5+#x$PJ96|Zt%so>XOaC7heIg*TNmkF>+{u zgFQy8;iI|zv@i2Sop_`|y_{_$1_$7U)6kfuRYOH3A3(>2%l_)%K)GrM0`J$Dn1F&I zCM!OhzdYtZY^BWpZIfp-Ut#k-gzmD@(HkuA?xeB7jDxCgyaB^3Nc-5>4S_0WZBp0c zmS^hY$5$FLX}*D;Stwtxdaiqo>kTK^Ze@(0N7!zj13JRr)>Y66b(k%J9|E_TS@`eQ z~Ay*>s-FGIF?Z9_G1HJS|jhmSR zn4;o^Z*<=S-x+K~|F9XvtZayL_9jAoEZgGu@3!PzlZA}fy;3UrPxBt zE@LAmOxrV6B$TB3#Zvs!#3o6L^L9&o1=jigjv~NF0pE@Qg@BxO9L;RW8*^Y|r{Ko4O8av50_2_wqG5L*->&NZs+M*cHjNWoMEXtO!DmTi(UV@S zW@0j+@#|_0_5=fJHAkCwcs??O4-Oyvqz%pk-@?*2_9vTsw; z*MI5ApsS1oa~;vp{&VolH#J`FT=16oM0Q?~Aumsyr=9F>&(#}3(qeSIY+F686R|G2 z_T2w*f8;F1)xa3h>HnxChnJ#1pzF7{Aw-OsSa!uREBfj4T)?m0V{WaxY`rETs;(iL z{KS!PXhu@LFSs4tzhH2Y9Vk1$UpaM-Lrxzq!ROY}^LC+Oa?(xqcA0xV3g!hy5*+01 z-FgQ3vKB5wppcc)>Z6er3{}w>o`lvNDwwosh_4g5Wi5nX9-?;BUJ2xI)olvz1SWC)t_6&&kPpqB7Y#RH zfvRzk72-dT4`{*bs(AkzUdz)N1sW^_|0W;e7Dm$eo0m+a5EV8F)U`QM4eIB_`2Pcu z9_|vbP#A5l@la%5Qo9FUN}djCXkFaC^FsF`pH$I-AZI>GZ^PdUr>k;>LwzcATQM1M z8#ZikQ?*2YT<4%QA4Y}$i)T_^DPb{mZf)OqlY@T|>#t*Vvfm6KQ9zo?8s9Hov?H)+ z4etP-DZu}52^{`Fog@@VCUwADdam(G=%P2u5fEk1C1V0Uc0dMS(V3@+CeEdg>Uf4l zVIN|gw|OUed4fIL;p;Uj$IvH^p6Yl$mq`i^tEHgN59$^eBE;O+ z3QAYIf$*KD-&q4gf=gF{GChH9_cb&I1R7|<4S#DYfD3VE>GePxK8jbL)eLOvS5&vC zX|j6n@8uHj@N{Nn5|FYFDfny?H?EMvLli+ZaF@;(kSOK;)!Tv3GI!eWa1hZa2Y5;1 z;E4m4*1T}6E+1pRW)KA@;6q+kuwguDdi|b4l$$o_q5oyTfz7o^xfDjEF? zh+Z1a>V@~v#qALX{2R}%1rhB62R13bC1i@u*IL6<2Shh@P)qxFdk9=aArn|_)~1y# z0Za~Faswr#xxL0fQ=>@h6MI;77Y#`E44m_#KC=GOfF2$u-m%A>WgWq#>pBlL zwImR09RJwoq8ki^xZ+WkiV`DZ8(}zo&xUw8vZq%e*lrSJl?@F59ZAs%WJ8l{cNK!_F9oV^X{FS}PY z{wYV3T+YEGmQnKoriPh-sT1`MbR4c^M$t{x01%cu1d9Y_4xIqGf}LpMKY5P-kmv-c zKOu?2dCj2vIQLiA>j}WRo!4=o>DKoZ{Pk zhV<0>BzxQ6Q{C_&`BQakyaAm67pPf+T4>%PbyedDwzE+`e}gcfqeno}+b=y2_3h0i zK0phUk*@LFKchOo+`t5nL{wK715a##LV!b)C+oEo;z@U$JZUKd3c~621{@YBym=z1~2wuK8>R!b37S9-(Sb_nci4ryaWTh;h-OL$ zt?VNQ%dD|qS+*v^6%M;hy%!)adolgJ!i4DU*YeL3%co9HVPo@w7FFL~nE&wL`}G49 zN=0VZK>W;%3i@#|%#-Lq&_+5Jsgs0_lGRXrlRuegU-`7|+j@r5*nRq5*Ek_`yH=!8 z#b2>2IE>3*!Ojbn5CKcce9%@m`WAK@KH`#53CHGvp-_1~8} zHZ1N!HxW=L z7xZO;lMJN$ z+K<7mqb7)pF>49;7O89cefP5 ziOCxQ3f_XQs_MIVVY`W!KxVvRI7?ZjLy7+l=luf`iJqNN7ngq^B%9@(*Ll{M8 zHztAJ!OEav!bvvNQr@ceE2H&#OZTiuUXkb4F0o|8{Xv{@HJ3FXN1tqbpi^a9@ptsp z+qizK;StA~#kre>^%hY=Xya|Mw?d2V2-fC(;6y-r{P@^z2JxO)eeZ_SvIn0ju4X&t zp=dsbM{ebyxt*;Pe3y2ajO93bQoq3W6j&J#xO}GHYwq8)j-|(07ay$8rlR(0pSmPE zW~)G!>6X`3c{JR$4n_|CJ&CEPMpd&ew}Fl-gP6M}z&Lk-ufhkcLV+&s8*&w`c7=By z2ONrxd14v}`aXMwbLfrYtV@83LGO{T3$R&SP&(H1fI$3UD-}jS5OGP0yo?9PY@Bw5 zSjwKbTG-p2)1Pzhw}0CEI+b_rt1oNu_QW#`PWp|kB;;ZdvpuoGkH)?lp#vT#^eEJ)**H0N1Tr! z#ASwt>xp&LmluP##ut3W_L~ z*WUeDuszI%D@%%6PT+9;#)*ebZ{}Q~V(EqYlFqG#afT3(A$q8<@E-VYf+KOM-a77QT3bN?bJoO4>XcG{}!1# z(!GsKL4iDByHd%VP(<5sZB{gUb?%%t^=h`eZ<(Owb=qBY{tnV(8Mk3kc2xa=PdFs) z{Ua>~1;n1*5P|VVlg$CG_?aXyove*;Edd(nSyQdZ_#EI{6wbR_0gNBDBK6&uB|X-= zHCkL@gdIhwkZ|EHmpjJ$g<}+TU-$WF0RGptTDj00(0XYnW?tOTL00z4WoNWR9M8>*$ z`NcqqIV-Q7H{A+)eseO)K>riPaZp?Ho}pSD1Q2ddcQ;GH!a7q=Fyph(vP1FCRYISJ ziB;&@kt&a=HcI7OgY|sE&JQJP!Xa6Jf>*EAm{$tj4qpSpKKe@cvXpVVJTrOy8jTW9 zH&vNh@vtb|X^jC-^-+bBUdo{HAHn-FnV;e85fLk#I!Ql^GfWt6HEiE1>WW2d-ZVtv z1v83jKYM@!B4~A>KB+KU;A~U|S)vj5PUzX|ckaG8Q7BCCj$3ahd$;fm7y!26oPW<4 zvlF(HH=pxznxyJ`B&R5FH-)ClKs>d={I=DFj%nc8BT^cz&!}V14g@q-mzLI>xY$=e zt0IK#O41YOKiCqB^~_!B&Fb1!>2{kHceQg6M^t`{Z`sawn6+3u>s;M54vku5Pv!h& zZb8|yjWxa*1lmax^?5kRnl<@8QrpoaqO%-AtGk7x*MU>%1aNZvXX`v*f+Y+}gDc~r zxm5FM8!{i-7ax0~I&s~=z>sK)>6AtWsG4nQd`h8awAbD*tI3wkK!#6xsOO1G-k|P& z8F^`KPp_2&#fti;gasVH74Jetl9K?p=l?!*UHf+};1D0NzMX$)(`f`Oih3bAh#1`E zpJJLs<(Z|?dQhMDGZ4uRkuZbAGn(TEBVI^)TiQL38(+nlu1!Z`BgRS;epL3QE&(ZZ{@ge&FJ#0K~X;Rd3!ArkCl)z{79A3tf;_=4O;2MX4l5?CkiW7R8| zr&ibI6>3tP)6x0PXM_g)^%-4M zQLF+nUaRMRrYJ0n{nTm4gAg3*PR;a}s(r`LiEUnJbq7r)$+|rORTR&|*T_`T(^xD^F19@uM5gXN_xgEEgl-M&X4I zbO%{%qjO$&K#lILLR$!j`lSMtgj+mq?ZF`V@4;`dwtr8SCS0`<`dR$nkm79sTj@L| z6PW;p;O>{v=lar-#r(bhKXCGisFI%sjHUIa0y)EfZIFumePp&r9SSai#vF@(mAyU_ zRDDeFpRVwikIxJ(dLd~7J5r3iB5zOj?7Bh~PA0IqA`cuD=DO*m1)xoYi( zJD0dc8;R&TucKZcJ7u>qJE&I>r1Mo z61H7&6evAX7e-kULQ)Cl(M2%RiS0E2<3~;LIH*_E&6Su_{me1$!%9V?@P9-TfYk)3 z*QXkO#qFxuxme0dr=rcY3(6+#wBIy_x6&%?4fu32M6cJq7yso?4jmX0jhu9>%$jXSyou?HV&V&Zwc z`00}9eVEwIT8o^rXqJ*?TS^YIDgS1+%%hKBs!(s=#@y$wF-6u$fh#m9uc83D zH3mJ~$~6M}dY~Wr`=Z%O<-8U;Ip8qbMa^N&{UbyRp#a!zc6FATZ`@&1uj+3&*d<7B}y>uZZuMd^v^?eumIzDpUv0G1tmZeP&r)V;bNPv*uc5x()&)D=}3> z5ICOiz$`&@go0S%a14#uK;?{TS_`*oRavBVGAiOs4J3dXaG zjJ&FBd8$B(!tO4j2lu$CHz^jA{)tImC7L3Awq-LTTUD2BdxUCud68=PpyerbC`TDW z^kLmdOj?Syyz(0XmVwnlbaYAwldOurLVC^*LO+t^q%2l5!_dlkIV z8@ncVn|G@@}w)uew)ozy>&qZ+U`VM<>}3XxVxVmu+;Nz~4N54W1WMmD608FO7(r z?I{n^n}S7G3=ZgOIwr;_hp%0=*-|Z^vV4UT^SVM{&ozhH9yH>XH!zKz_|Z^b&khV? zo)P*|8ee*v5P`sa)v^>nV+L2V8p z1diKVp|plo*ZK6&S73>`Jfy2CfVtslLIr<>tScky+SrwZCN@ReyrLO{s$Sw`hloZ% z64jn#OS_3?ERvu4Ic^-zp9LQxTC#p-*&S+MmWA_;r!ROK^&+n?wLNFk{O14xB*mVx z5KEC93^A?Cqtve2d>V%}wG?yKel`){MG;`86SobRqQ!uwyddP%gFJ_>rg2kv-p@b@ z$p`#0T4t7JY^sXBw71zX2vc5Itpuv{V&Us?LbM;+mafLX;xN9H0wSL_hyj5wIo`Av zZuGv});B@cznwFGInF$q0E^0x*v2_+^DRbyAn;Tm+txcDY9&Io=WjG)az7D+$*xOoqpi&3~e%=<{O7@bED6 za27KxC58DIGwH>hRA%;zDSbZi%Q5JqBez3?9lF>IN-`+jPK2ph0Y)pg2 z`YRb_Z>;an66e{BZs)y#8rxP{nQ__3FN1^niS~}YWo&!T`kr7m3$)7F_Yx)xcLz^< zQzVct;_<9@IK1O08XY3i|4F2Rw>Uy{ut!DZ;>rGA-)iZEIjdl3tsX{hBtEoT80-_wacS-yoLpjf*5;T^&?5>%kpG~MO!i3tYX6$JaMWf`irc`UU{B; zN_xCH*;?;_)6_cCU70o#CZU{&&mgWv`QTx^ftz!#5x336i#O%xo%-pt`?kN_s#=gd zVcJH$l)>rRq>5Pgo$ANYce?)672EgbOsk47F}Glwm`O~x8*Ef#+j=aubJzLW z@hQzXy-`X5&E63k`zCD5P+;i65P8gVri!Pup1Mflaoj)T=hKiDN1Rp^@Nuu%x9 zH;3+;(|o;KLY8domonX1ek;LL{|tdnMEz)~(e?Sf73aCdz^$c`A6xT2*<0y4j_$Yz za<3P(&QT_2`muS;J?iN^c^%h(Uvo?M2`7CUVUS$eQ$tEl4F6Wu;3pS&oBKF`6#!r z+hI`OJb1w&A06T)SN3FUVJk(Z)^=(1^X2=wwHXpHw#N^mty=+hD<8%y5zmB z@x2z-Qx0nnId{~lOH zRLW47IOQnrQH#I#%#*bqx6b6LTYByiEw;GHEA)NQNo-S}Z*PXxm$2{WmaCr}^N27y zwx9fl!v?3(*tT3$XR_OVQ3Vf)w-?gw@}#AEcqG5oM`u*;NuPzP0E2;%;ldWbKU(d@ zeHwRR;TnU4M+`R%T_zCIvBG<@X85}02f?R9jXDGNT!Z&YM-uwUChxYWPr zwcMN%U(l44zlY7HTU#f-X1;o%kx&eKA!j2F^0MqX#?GV_|zd=GT3D@rwujw?sFGY+X;VOq>eVA5+U znUC#k35Ifp>5PS_`Kp?xQLP6Pd5Hh1gmdcp-ilei zQ390G6Vjn~`a6=nzYBRqo|@p*8)>@mEu8Iag>39X9k#7#s@`f`}pV%+Kj>{!!Cv#p6XMGYEi^zZGb1Q1y-`5k_b{s4A{KbnDFv|Qb z0zm6C6ez^S#T$ppADxZ#0&|C0cE)Aa1ZUWV3l}m~GRR#w7lVnJ+)sf)O-Vp*D0JVp z3;h)MQeyA;Wn2u}YKx}CD?7M`Vt?Tg_fp53^jlSv)DAVd*VW0*tVAzg#>XSa?^F?UhDP2eTU9MY3jnXWP=9j5BA8iJEj4JbQT8P1(<8gL*{>0vuYQvCaYWt7Yvb)6C=qI?or%RK*@n=iG(R8C8QBbR8T|jR7qn@5;F5+QSpb8cQXWHAMGVJS^vHcyN#(Y>B$}`N+_uoH2Gy--r}3 zT!mmu(>Fej6oq?OpC350(SmKxFZI>gVi+d3S%jel_sA7VkPGXBg|;Rf9~p;Wjk34* zd@=M-T5$A_%+NdUyQ;YBMb*y%MCnPiLynI^m1-UFrh2^&T;r2dWldK)=doh;;EdRa zd)D?M!*VnfJ>1WC&8Y6}W^daDd=G5;mKXWGAe*g;!5PsphKKlr49Yz7FBCNG*WfmH6Q&M$ zroQxB@-Y9rN7m$?ef(G{cQ78V&)W0*S^rAz|JgXquV@|HnJW+&GB4AwLI>^`rZ$$`itbjaxbKE|u56XJ}bYW9q}qRgY#{jKJ{vFuGH5$Osk>!=|(<5D|?!Q2Ek&J&NgO z*;O>(I)BsMoH3?GoC)q;OmcJ36CKs~@nEyS$tV8{?~~rgqxo~!omY-kgA0_xyeV(h(D)>KeV7Qhwo4wf@ox0eCX|q8pZPhq4gS3fhox8 zUIwtO(fb!ASuID(4k&cDWJt$9L$kIM(69r?-0r)D58 zj_q=u$?+g@w(b_KU=v4flbO+m+Q*8iZ5(N1q3hFK4Ob-A2e zX5Ii-^~GjBM{CF>p+gqZlLq;HtjJJsq^%kWa=oOdZ%LY?u}P@8pw+AN`ZKXkJTDO~ zrW!GQTJiOdMhlNIRvV#<`GIg@wR*7mYKqPu8&*1oGgtaCFthlOZ|5vExvK!1)p3-4 zCE-Y9+aQ&beTIM@O;nq(%7Y{q&+_qDZoG{=tb-{mj6Ybsd{y`>l{;}P_f=HuzP8ft6d$P*kMQDvHQ*h@Ro(&~b9)stSFW0D{|6DFa|UxIBC>)OsD2WG>*B&#*UQP$d?epnr^P-Yyn z@b?UNQX{tM5Sfgt4?NaG<<3^wQQYV0_s`E?fMMjP1=6kRIcg3sw5hnQl4;>*B^h*U z3Q{|)X zqD9Jx&{yPqoL-sdIa7VHF2k$-hGI*<=FWcg8s>V=$to*S#3V79Ev6yJW)mZ`jM8Xa zUdoK<3E4o3=axE^*IYC8D;eJpHpPwZYYn2}m!4jCov{jU_Ih9L%9Q?5+`B)LZtapL za`l!v4u0m`W@3G5t#KjdtBVarz<<~w1hu=T=2NCKEjRfbT<~}2Kjgq{w4aQISXYv1 ze6d-M8E>%`V!x?d=;Re}sjGw_4%0&8EknY%+by4bl_)b>_JVWX>)Thut`OGQrxD8~ zYp}hiD+zom>362Tz1l+F+}J*VeY>sVKEA^^_tG)R_6%Fwy?mF4Cn^y)Ukqu{)DCJ~ zBdUv7THaS%n)K^l8m{GAvJchXT7M< z99(jr9c1jema+$_>a46~ruWS_Ea-X$?1t9meE5mb4ub|&!>2vnNKw`S2(q{X@mlRi z!;M^WKJNC;v3HozIpm)6`ef*sCtKAreD2N$)YZdTXI?}=;+|H=j>Y=tvHJ#DT)VLU6B#%_~X#z>rrNF5N0ee~<7o}bpnn#uCjK*)r zyjqpL;FI3QdWz0>1{O9_GA^taY*w`mME4r%YQ(H*88% zdO|j|d~Z&ZOT4eQ{GA%z>2ftf_Q}@Qa_MnCXw0W6c*ix0dvhnO(R%HbQK}s8x@FrF zNiK`}Jf5;%M)U9scuAS3xF&Nh30RN1>m4@`zC(7EA$uwy`rR4>;xpscu;KPSHpcVk zEf)4_+_!IBMPWNW$i#&Ro3t@!=)O2XS70-CN6{zgQSFOJH9X<){8MLzojx)|@W7TY zgw4;&4u~D^_WBs&zf0r#ez(T`Cx6`VjT4VBLF1T&lI2(1VR8%2PX#0#HUEG6EJ5OJ z$<}O#M!V}{^SBE4`kA|V#T5m6K9iZzRq5eVL6Biec^CC#>;?E|iY@;tlz?{CNX4!P zA>4tTduC~Nrb-2Bv@{c3g@Is}x7HGVszKV6J8u;Gna4_AI^RZxF zc}jYJ%)(TugUZaV%!JRr9U)(6VWJA6dSqj;Fvn!wa60p<4>$$g_}HiBVHu`N8d!Gg z1sD#xl7Ey@bKg~TRC(!su6{E+7@hbrD0-jXP#Y-T1fT=iwnj`yt7j~3Z}v5q z4Yvtqh4D~cT}v+_LT|KKmo4qik}rV`=SF+0lRwsXtt9=F`*zYZ&09&wpW*V8jYvIy zQaUcLJ}J4r(>}P=?Z>zDeP3-kK`?KOf#WAN2x&n=in?K& z(6ZKfpLGq%vPDk1R8=ab)okUybLY4;6t=@@jSCV;8T+$+y9Zki_#!WfuwJJ-$ z%nG8?M~oodWhLLS!d~5H4t8lZ(GcWG7gJ;6*hRmHRuCv~QeM@6YZBk)bB~#v5C+Z+ zbo=@DOU?|OI0LyQ$1AG0WX#2iz9v^H&@4YB!h0(n%g+VWUv#g-fjE*0NKMAygF@stS*ZE`=q?>43BIF?ot z?%smoUM)+<`Gw+!rtCddKajVJz+WWcJyl?OULEbHblFY&T}wQE1nJW|&0n+>^PQb; za>Y}YX?*kBP+J*8(i1HiE_|8(tT!qbIPTRf^GI>-21s7unRqej`x6K3Ta`MFuRV)d z>Nh_6KMbK;d!cDT8iR8!r0bzDJ-#@VUIe!vM3&^ZX_{*unCCKF5Fi?Mh;$A8m`m*NQ@ z0}PrZH?NDDJ?a9HRv(yW7@6M^}r_?|TF9TK*xrn7OX=+;Qw<*IP3x%pM7<0SV$qRFJ8W{+;(K z`v!Bbw65G9ikI)8E}PFixg#@Bba zlQr1t>=mf-Yx6|Sm7Uu>G5wDzR=@;$2k z?w}k~yjEv@D}q~=X7>)Y(#bhXcW#TseX_QZ4fdU`H>(JmafkJ*A#X9;@{x7O+6B#A z<;)>>;R`z0z{LaieN_VYGf9m)sP(J5!4f9_@Am>ItMKYDoV+O&`u*jMw;)B@M8GR_ z(vk0{YRBrCt1@A|yB9|giTASMHKv%@-&Z&#UkO-OGf_SRh)4J?x+6rSZ?>P<)=uH3 zz89Nbc|y{B$>ij02yk5xg-SkrwB=b1tl5js26b;HPO(4AuDsz&+L6TGx^iA)n~eJg zO>P5GDff5B${d>9Mn{b+7q{0j@^_{RgunkL|K~~`iKDB9K$>Ft#(GnTp`b=`O^EYp z9DlL~s>nPDryNxqu&20D4U3BB8ZokA9vD1_Y#SwSA{*FsTv>ZhZF$4*zv*Di=-ugX}9?Ne#)y z`v;pMexTt<;j_0oM+#zSanz)LZ$Ft=GCwknZJ@qRsT%-Mw zs#fvUN5%RLEs+aTj_fDIk8VTiDEAE7BTpd@Pp!Xf|ArINgibJ6%`R9Tz`bvFG?JC7Q1_J zSa3wL(%|;@k0RKP*kjh%UvqKidPWpck%e!xqLC;XYM9HJ%_iMlIgbOln zm*Z3OalqOG>@tL%l>cEEBauEvU@`~PA`w;X#Ni{!jL3%^0@G0{zU1*B;D*n zp|+Fp?5W3C(FV4Jo~R9F-#a%P;uob_JN)qoJyZOevcLCDqiKpdeH$7^3>Mrw&DrjP z`pe4A+T{Hh!4)0>o8=ukeG96*j+nZ}hpw29W>&ZaJP==oDlDkTg7u+wFCL-GGM3;> z{a*Bw^G#Xl`L-%hOG))TT$9C`E`PlS7lW6gID(`%s|nE($qg%aC$u0)GTquOdSLaS zLBEdlMGI3fjP2%A%0J=shruR?O1)AM!%CPBn#D%&=JK%ebnh;kZomM?$@oIP-=Thd zoz)Qi9fmi+4%$T@#{qc*8$mqSjU=ecl-SWB@bo-;+CbhjlKEe%zNz5WoRekTQF$kNn%9qcNb`416a@4et;$@c=L8YsLe;_X!h! z*$Wf$F$!rNv*QRi0?K8Z^WW$%k&#`p-v9g<_@(LolPOOV5sUDg&s(KpE#e=$P)Cyf z`;i_oLO}aNGMHUN|LADTZtKGXJIAfxuRIM9c{&yAhk?>eHfZSX8LK~)SO!nS`&J*+ z(;^cWHRQxm6f2IRAMxC3(1_u4wObt$nzTF9>Jw~fS27>ST!>^3`a8LITFzAd_i6o) zA|@B5R}Vwk+j+@bzw{~D=zg1g*KREKJG}`y`xDQ(nd~nm9Y02fL4v=sAxHaPx=yh! z9+R$fDR1cB&9%Mx`pA+Cz#sgzuncj}zT36b-NwXIlR)iL;;zaE8?EJxi|q63X+~vD zddNJpu0=fQUm+iCTWy+O^yJ_E)SH&&lL;Bh-{sz5c8y#?bYy`u*bA@EdIn~D3A^Bz zTaJZ&OTeCD^}fx;Ktmr?GzJYllNjslFr_Uz?j$=h6#jM7tn!BZNk-jhAwsyl)VTE% zMYe(9#-T0l$Lo%vO`frNpDn1>tTMWZYx-@pokIU83MVp`gP^l0(t0nOYJ&-2eV$DV_!s&PBs` zEQVQGtQXooD$jrg8{591hb$QEiZ?~TFJgr3?jpwovxIj+5``UX7jOe5r*LlTD|&K0 zpqum+mKbRc$P>4BXpUVm#B%{@SiyDk^lb5O8Kl8z0Ux* z9hB59Ozc@cvl@8*-2K&EdYWnoNOFY?bM&5>Vc(GStE1jqL(?ePsuMzFaQ`S0||Fui58VnjNXfYM6TBvS$oH=b_I`(bs8~$5ZyG-Sd;l5cl19w z{<#jEDXY-kK%1W5Vt8P*ecN}^$%JLzjGw-HZ#&bVhj8v5gmMP~CZ`@wYqL7#-df#O zw&2UVU5%n>PiGUu_n@>;5RJ+)5sk6*Gir_4r3;pDlQ-_g#ZWKe=w>N=LWps__y*gHh($RlmV92r; zq5oTpyuZpcv5=#XK#Gj1vNiH3=-oDXM0cCeet?xP()@W3)C9cSs9|eLS$k8kl>uX0 ztb{uIUqa@|pXuMIh<}KfJZEI$NQ|VPGy5lKx-D)PMRe~e!?jTKp$wy^LrzcQ*{%gY z^%y!>Z(vWO$Ph!H^x%SQ=YcCFs^@#s1_fD^&VK!lbupHKpEqQbxb+WW4?0i`GS6TbIk`qbv zcV8q$a}Dw91IME3{jsvd0{2r^5-D<*^MfW5U#A%~xGL+|d-GAs?Cs2#IXf*=0F}ec zZ4k!WlF^LzGFa0`O$4?{JR|>(V&jE)@cB*AgL+{K4w&`V;hA6X((a<^*_= zp{Vs&k3O>g(o~S&usD3999|*tO@BAvjwPnrr7xjO?YkvoJnWFXSqRij4kV`n1icy$)-{Xgp{ zssM~Yge7&JG{R*z2q}|WK2e*W&A7WCtT?Jt&*|`V;^7=9%v-I2XkR)8VZ{uhIgHm5 zLqyXXlv1i*Q71Q5asTz@lI$$W6H@GrDEu%%(yBWc{RZN}#@+rY)9H?a&fCd$A^2~2 zli$uXjF&9G=a`_`PB+89(PXBdyAWJBGQVObxc1+VyWuG;_FpdG-*0yt+gtFV?Hw3y zgEoTTH)51Npr65UoxEEQA6kw;OML8L`-=)xDK!BGt;A-M_}~m~{gqCX?r=6P*o-B= zbs5b}tF84keji>JD>G33L^;GW{q6&Tw|*{}b5QfzzpP*HTVz1OG0BK8VWCL$bd*o* zDz%s%i|8cZ<(*{(mv<7wfqUBOQh|79Txi38)E=B7~X;c0y0T8G~1w?YkR?288IB7Q%W?kg86H~?iE zrqeU(&K~QppVJGIRv0At6wc;19fV?~@oyx7jQVrVPt>j3?BStBB#?W>vjlnrX*2Ok;~=)dzHMZ5yf(cmSj~Lav*v@$6xf5FL!SJOM;wNun6vjM zOj&&H!Z9n^g5QUad~Z?qzb0V^gc(PPF>17hsb%-Pu`zALS99FfBv@TorACy>u9NB@ ze*kOpMq=~oo!S9d{b}X^lwup^Ox&PXjF!daE4;KNS(A){Pi zOAiK*hRJZ!l_EixSNkv9Kr(i5zD6#A|Nqc?0`C|dvG;rEXYdJ19|{8Ls?(%b7x)s- z`$unHJPvJ(k5EaQnxi&{!y)+J>WC+AB)uXgq@1%(wmGs#GJ~G$&+q`aZ#wWK*oAGq z`Xr&GgDfQYx9|o^x?`WTXMI@*qve@xhFbL#ZKPpcbWLb zIJZ2g>?j4=NhJ!KlMpxMtF%Yij4<(sminkd&f3csx0K0C;7b%SV~xb(bw#)ql--yT z6Ne9R)>JOJ&RaN-P#JH%$t@sz%m{D&?e?0BKH-SbwP(|NAaElSj{BM;@L7R;ns$_w z`B|R{%TTD_Xmmb)SSxJT})z#@_80Stn8T0 zZCjio%gtCL@A*U&Ub?vd{0IW?D#yTqU2H1}cZ$VPF8ReIUE`KfoH1Lkz+xjk(E)l& zxEmF~cSsB*1L_GtdPwTYss@&&;XCh-D#PvSPn8(0mx=O1#4j5I#uD(*j3tjY1b;>~ zg*v)}(P8F--)|vJg2Kq;-zopo;~s_TPj1f^D<-u5?(nck=+^L49oi7o4dsyF`(Q#6 zIj$FMlUtHQ=VcG*S}qLl%WNILNdAL21d0cjqO?8ZcEfXAePdnbhA{?w5q6}bslNu; zsyq&qoEv00Y-H+yI57`dT^BQ0gzE2<@3rn%7PEhr zw{*yM**qvpN#dd?O6hk#QAS^w8uMAXk=mai?rguDCg`@5ug(Qb#7W)6%1QPhC6(wK z5qj#1UoI?+uP&Y{t(5nmj&gl&&G|nf#wqLsQ7APij18bqLzI5qD)N<^v(*x$cqd}f z+pOzZjFQEvw`acD(|%8@WM6P5se(LoCNB7QE26whS*sHB$NsGkwml67;!G&F%&Zk& zA)2YiQ}0IIunO`+i<<+^$i^(A4*SMI0Q^d*Rz&m(;#HJ@bJ(hY(>7C3yvq$MKE*0O z%OD5-VCVVnL)c)2)r+1wx4R!?x>J7_YoELtF&Ibu%G~Rmbb8V9kZ?pYxv?SW0p(>d zgOp;T7m%q_!&$Pq9bs4T-|V`CiE?F5E2fKZ^^^Cf@21Y%)t*!Fk{-vv1MI#VAHOdv zBo1rp>Hb`}TDyxUzFMxqDq~z{XnkArz>^e2RxPFkLARVTjaUr-?aKgfyek0tB7F9n z`We(cSdydu^n75}{8UM)u6p6Aubi(?M&3$pm{8*K9CYMcyDlGGsNc2<=x!)wkg4Mwz{b#GgDTE1okdJi6gNy2)3kwIO#xts`vL#Ckbo;=nei z0Bn4OaI7pk6Ws$*rkkdfAr(CrDicEOKOY*+HK-jMk4~2jl-ek{*V|y}cvhjDtLWQz zeeFs_-9JNy3&A39u0O=Kq^8K?JdvZt-_pHI+Iue}7N`);8s!%Jgp{QrvcAL?8Y@!` za`O~1zm3@~Rtnx;6Jvd$MP);|UCEaTzsrQ$FH;~=dSeS}84lpsyxRC3ma@lv>?idX zJI*Pic!KU{xJak$|8yb*dyJ+Hq)CkA8!=fw2^6TST#+4Q+QB=XnP~f)duK`xKnC)y zL1w!>zudS_XMc`apsb+{EmpV|%}@L9FbOh_E57(&Xy-px?|(fSy|<-;u)`Xef%X}0 zjq*kiU6!Ms5(Y2EtCj&_nLYiy9aUD-Td=>!2K>&JoTFP}OJ9FR@=!&%j=G%d2fDu_wk&0Bdx?O|Stl z;Fg0w<84OtvWE=Pi4Wmz`A-89c-KHx+lbG|-nLlezrgk-{T21j5D7~;+7+Uz{oD8` z^we)tO41+t>3dV<*k7hkcQ+vE_s+y&zi!q?VUqRh-CUduQvj=~uEAA|cYv*wd@LAD zBlCe+&}r%BjNabe?ej(Pe2JQwHxJ)R>fqPBCGxCWRQUQ+6Ej+-bq`jwk)Mb#y|EInxUbPp7ryk7oCl=IUNwu5&TI??{SrWcCJ=7e>7Q{l7 zPk#PBK#AQYF!FdYAZb1zpj#ruaPlH3H8u!hlna_f{{80ZHz8_Z72Jv%bG0Hw-ATE9 zt8pfb;Q_*{IFLK5;=A0WNyjwbK()Qu2`&7@?HGsO-Hp>zXODrYP#Z?L%g0?OgW-N* zM39b$YqQRN$yog7N1J{0(amaMN``7@$|+3*7N~|pX1gkmmWaO?bKxVD`kH@Hz-Zua zoP_o^Z%ypI?Y7=~QekEkgDAlFxb(DvlDN!3T*(}z1rlgkiuZ?=MTHli;cIcDbaK&S zAqL68-K6P+wrm_4Dl8}WL=__n)(~^wxS|%Ca)LQbs&oBHjgaJlUKt~P>*HV7hmjDX z%wdF4j~UvX^a3Rbnd(K548gkmRVfA_%vGXfkJ1Io>f7# z5nGM2(oMoEF7DGWq&?3_d=|>mhtBDz-*OIZ46kLG4FwCC1P?#Jd2Rotfhh3h(r0PH0g1dMrKA|_mA`R#E`@Uf*P z1X(yHSDIxPRtl&IWTD2#NmOVn!_GrbhSv2PGBgN&$I`8CWPbL4|M4bM0MLbP`>TcI z`&+v!-y{YdO?2IB=XLdNG{R?^DExGcECO;?H4AAW7m$L?27@ZJN@zJze6JtYy=K@P zQ)_wF`r=3V_W2#N7`C%Zy!wB>=@Bc0;i8(r$+H|A_<4soj)eR7GXXj4+51>NF8DZh zsFQ8whOdGzGjK#sU+(26UlCVPh$ox%Bbv|HvMNT1i3?|wBT{FJZFNDLZ7+3h=ot)& zt(+?dYk$N($cpp?WwcX>5nlZGQtIeJU)?vbRf8hz%wtP0aHG8JSIi7j#Xd=*!$myl zojgjZkc}nX#$ziBVzgxKJz2D=dItT~&a$%T@P|84L61}WuVR6c=-*xddcw#rN|sb`itlvcNJ+7+yGn-54cUDe$-+}IEj!%%V(jrsXPbuG;6b7b z66VU^kG7UuNdGSvAfQhJ8&0Dzuu`XxLOom8Qa!kJjU?d6&P~dR3@%(?XzkA`X0-$Q zpS$73TTq#W|I4|z9Z9l(kN9n}ydA52nhgO2LdWk`OXgQ>QvZ&6Kp_Pasv?j2g7!#^ zC^pArV-E=oT63hXhh0DSX9PrQ5tAzNAdn!wNo19g)Oa&!MxwBVZ*gqlpk4J|D8p9X zpH`R|ZHi-wRY=zr9Em)l+eF^XW}G?{C;spf9Lhppp#sMo4I%&yq(fV@6Zu8B->&da z!MnG{JzOgj$qH2Np7S>NGD8V}Kn!huf9~_B3(T1WB6Kj%y5F5!3@6Cuse!eBRYmq8`EqzJi+cX~O zHt=Wql%C>**ALy~fLUN~U7!5ZgQt?|i*?^O1@N^5QKDP96uN>r=Lf1~v^>l!?&(VX zTdP2;eJqkj;qAA~rPlxyiu^mOj}J&EjgG!eBxg!^Q*jTZNfW|m;79l~pMg;DdDc{TQY9<|Th|WZ>KJmXkN{s#aMzXE?@Dau zM*11hnB?K3!I_?*ZaS?$LO>Jz|56}`o>u_O3Y1&hMi`6x?{vZrG;TS-V-wx z)5QjL^P?GeSh0I}26hv^gq`1fMQxYqpH_iSe61rd(75%LFJC0`o9C8so@hIv5X^oH zkU$89SYn`N!aXt2F^P;flo}jqi#jVV(Z~pf?E47%#+)g>Z>F(Y{Vz4ciN?;y- z#N1_`d2@0@{yWXb)YxF&pYL~ie+S)2y5HQFwzA3pwKtyF4nBrWILwX?wSWi;{U5Lg z-y1Kwb>O^H!})KPhTwa6%_)5Om7VdtUFcw`-a9?M(Utcx;hNBw^)ovMOGC=8W4!u2 z^L;p=>4_2ogNP=8oF{SR{>24s>+A~o!Pt(xmmskUHYcOS?;)*`yZ4fF5KeGcED6Lu zA2Qw_a#Sw+wfR{>bs1H)w(-?`)GlA4;rh9P_0_ewVt!! zh36#3^Tx`3=b-Zz?Fj)Bp2OVSe zp0U*6bx!;?iuXO^`Be$e`yjq12~aV#)ydc3zegcDhQ7tV;$uiQ34b$PG{RL-Y>!aO zQa<{$d6IU)V6K@>jlbU*@jeYT@~U%uBKfP2%-vRfVPJC_@htQ{K$=BkZgdirr_^`K zz8LJhTeX6PR&U$3*7crD;bFaJ0FeStH2N(b7iKiG(x2KW#@PZ*TkzMLVpp1c?uhjTNn6ouQ;0 z%JQ>0iSNBy_8b$$p7PT7jZ0n$U3uDSbbhzT)hiF=LNyA=~f zr{&+<`9S2<5+U*t)4I1El)^VBV)bC}^6frbVLz#}6nLo^@2@zPD1EKzekeD$MXft< zlqNUsu$eHV{CRUN|HaN>wjtRYNUDKMiCK=qhq+6+{>Vh|gw?CWa&(+* z;ww)&kDmI?C1Y30(z!v!OXMPeiy}p$f1-Yswwj?Czeq?zi10EsGAHMAU8U-?Hgn=SUdo z(HmkAMQkubFVN90Tw(VL`#Ue2WEeA}b@vKC@f7al5A!usc{RbL4C4dZw+RzcIK9^~VklOZvli zAD@tmKG@)y65n&&6e{anD{7n%?_?Lp+(xk>F~vbopT%vQ^H|kxWCf-8D1K#G!5Nq+ zk=L50KtAvH&@HKpN_bnxMOLCPs3+hlaiYV2`{Kr%A=&zkBtHqmb0PZJJ#1xtabTFY z?>R7dR_{~(c@*CoG+Rn2z;XHYbG(oj-K=;ta!Z~Ol5jG1k;56aX$aY#t33amE=h{a z37F zg?e%Z3T+KIsK?PYa9_UvRfh(A)2euxh`9bfTNQCrAGC4roXUQZF?~HCmFmzk>F{F)oT?=;p`DUs%iUwV$@n{yWU&-)nr1HLK5 z8%`!~lotwm?o|zeeY=wGzYo-V*7@?UMD>{{vx;G4Ck~TE<`UInVFrThe*^$-;9KI5(=b2 zp^|Ies9Z;;2GYZg$0TUof&HN> zeTmH(p#OTEE&?;;z5qC_`4BG_S z2aPut&tqHQ$2$eSAL}=mJZ!x3?ElPt@lYDDmsYoV+SGR+@NX1TwI~^-QKb~ixY^vv4mRdgi%&S^0B3o`Mo_W0~G4-G4^k`bQ$PP9tSUo?3&g)MY)JKU7`g$9mO(}*A0k*s?Q{B;5z zc$`%fR9Y;ca;3+;1{IlYmq3gWO-=V7tm@_ct=@e;^kwjsg7S-?%e(>dLQrlb)CB(J z>IVTR!UebKwcUTY0M7i9gN2Y?Xn+OQ9Q2aCxF83s8WoHHES-Kop9dK|0V%NDsn&dsONUf80~)ZgWg3Y z`$Fvj&gGM4Z%;5;Lcsk<(t&EQ@xuU{HP)ZY_7%LMqy-D8@qwzYgbLBu!tGvSmpWcZ zs1*gW)rGsH_7b2tc;=eEMK9J~R*c5N+tCeNug;rPR9xSfyR2fL;*k8BK;o7z_vlcx za@zd3zq8emX>v#7IT2X#)C6-|5qS;w)UKdx$9yW6=CnLNKG98Iiwk@5^CEe&+o*G+ za&hC`2=SM@P19e=L8Akb)7+%;6nQ}K#$N+%W8Sa~wwzKhSn9q_OAgn4yM9GRfVitM zgZh}FWRNUB0nG*U`}-_3P!wQ+e?$-4|gUAGp z;+hQ$`=W6=mVSMy*2?A7Vz!f{-D88Y(Zf`-@#yY&V*Y(Ze_p|}YRRaL*pKAD-e0yZ zfIeWvAQ`va4axAGb?afb61SllZk;XOA#?RaM8AqG0u??~ye<))KXwYE7?8!tqxMqt zYtQ6vBuRuIebH(2lCq;eKQ(vlkX1S7uiuLMv0lPg&|6Lj6245}G{(-u?O+VUXC)Lf zI;T-=1C0$45*|jbJ@sas@)c({f-4gxKT>9%y7l0306lAEjHT(z2=jbLC5<>c4HA4;i# zH||{qb>Jn~!XzidDLP%AfE0EcD7$6?O%WxdIKkMhbL~ER!&6xarJnu_-*V5$0Ijmt zeHW1kznUq?@k5?&cWhXvAXk8X$2}zjsV$z05>OYGg#oDRFcE)vV#v^L<%t8?&#aj}YpB)OdxihU; zcK&2Um9Vu>p+sbj)n8Ww3$gu}fj?emB@|iPVBgzwpY$jtS zxs4~$v+7XMm3=k+iWma4Mrb@kRTw$+HzX*2s#l5zA$$4o|M! z@h#*Xw?xoSe-f&mb+KTH3%3qip%4ui*xlH{!2hchepEfvoZ09)*IYC9Jmkx-%#Jep_81jI@lfQX0}+<#kI2I2J0XGmKO9aC zZcd*tHN-o$w4miyG`!*W+H^#7H&ogYdW;@1Xpje2_5 zNQ|`qLQ^U-lRdcwdxDWL!%H=ZD6kcq#`i-%1O37YS{G_|98?47OMm6-&5bAynn(H1H`RhN9}i5tW<9(Y6blM=Lh(cF-hcQ6e~^@4Tfs0O_6$!#5wT z>2p!zE-HDy3BwN1-eG*?iyD6S|YZ(;H|86f8l51gZ;dyg@d+Kmff7BI~ z55HA1;=@z!f<^w9_>cXA5I23VCJd#KX$?P?i$*>~%Hp3eWY0=pz&9ssS>KG-Q!&DM zSeO|Raooh?V?GubMp`PH1)Kc~Ae{S&qzf*6rM@AO2u5%k_ zCNw&ruK$U9YMA;TlU2Ec_(?*p`<_1)@ec6X@G9%Q2x+0K>31r<&aN1voPWsP!B`1o za$V~H@(yw6IFO$tb`ZGBU*%qYtM8o$vGK}d-Ji|g@N9JsS_%U55HR{mK1~gj9KM97 zY+vUfgYKwu>k9X5&0-7jq00$%fEr5fV<^FAQ0#UOIy>QEE?UU541YKlw2#bMA&LAc z-E{-`h&=Vi%kdTC2+6Ke1+$7AS|gbw&SGVVLTd>D53+(lfqp(G4vIm#`JD-^Qrwv^1m+i!xoQHa8lr$Lfrs8cu<}bgOy>IDO3B{ zWzVi(M~k-@=WlEa0$bJy4&5JIkJdz%Az`$6d0PnO87pTA{fHJ%ii~oX7`D5jAN5Fs z7t(*RRBW>;?06$SoE}L9*z69=+2KMxB;5mMb7k`KaV`d4$s``1l*pbeZ313rtPQmpx#u$kDhA8l?=btNbRrVdi@nUV}cY-e-;T*CJ3?!U?in% z;ANYZXh|L4@6a>!3jcno+Hgu^WGf(b<2GMy+T_7ILD%_gzOKMx`zq&Tw7!c;2Go?( zXZ1D|u)UWa-96Awi__kZ4eI>u82nd)Mj*tGo}zaX(XYz7hh@ zDHwBFR5!>b$XC*bvgC=sX;*Iv==diZ{NHXrCl+|UFB214V0!#;?<#!SP|%HyOiJjr zHZyjYc;^9gu_r>RoJyeS_hIATxyO84zYS#>Ig_%z%O){zO=r}szQjF$fp6iDQ@@_9 zHxn|3n_;fvbD6*bD(3xMhLM8_pZQ@3x)Tig{fIALs17rdAZxMN}+e`!Kj=mW#Zhzn>}fN`)};E>F>s*3e=xn-z=ZCQ*nJ zO@q&i^*WMQo~0_oD3?JeV}PQ`GTEug))jS2x6NUbGW}1l?7z&P_ptOFNT&tcW@GS?we^JY4c4jdv+k=)r(Pp3 zNUrqDAi2#t>~Q)5$cgNP9WJ8(9fMbQXB;3CZT@tBdC+B>!||;3x^H?e}SmRpf7##z@WjMnpr+XawJ!y#ClddyzURl&v-WUC0uz=rgAqiCRu4(8kkqt?t_|G}kMyJSmD=6r?HhY5eZVVz|gkBn67= z!K(N7x05a0`1d@({*l$u0$L^}rhMnpGj66i*evFMxd7GlKU0DZLP|5I&c%)?f{{<> z{rE+%dyGBws&hmZ)HBE-0i;&u7DpI5bvUUhRHHBZCrR`t5VkHzQ#V(m)s3}vLn9+b z3QoO1u0|LMnA6ZPq$1%V&v3BQP<=Xh&DGeBBX9dSNMRmv-wulCu=ZZzD>?70idyZmIP#f~o z!o4!R%;NcFfQ7_osNTWGl!=Fss}#xp>h!2Hi2UBBQRyC1n_g@*tXDbFG=@zOdIn(}H-=WIxceCHP%n!cg`>s3ktl|o40h*KJ-PB!S=s1cuWr>BxP7ugjZ`;?!h8v7|)m_>|A|FN& z2+0Vs{&`Q-{mF^}4K9zf18iK}8X6u6CuFzkI+q{m!0u(6^-oQrnmB5x>Xjpzqwyw7 zL$TRgXt$K9S@-kx)g!19_wn&b9A6vCN-P#pEz}bl4||oZHMg-1)*kI1ir=+Ign>b5 zq%6v9yxVDA5^*Q^C&FRA;YR* z9v)3AhW4k|$iLg9*&{djxuz2u7-*yNf*j0u2#oULz53i%$fn$;+1Xk8h@}93f1QT& z*2Jj0PidT@NN1|vGhfru{h5LuEdL|fuZn1{Rx;;o7%%`M2^ZIPYm5Deccu-~k z)V^xyZXnN2k`&RG-Gkz^6aQ_Wl7hk?7)-(UoarLO7;3js2_9cLaU^T8L^^*^6=&zOUmdu$zkfFCjfO=;PrrTpdA4dZ&b?1eIAG$#6*vL@g)PZTp7jd; zpJPJy_2QDV`+{~}AsO99&wA3=Mqq%z!ObzS$x=QEC0o8|#R?=m9aL@-x@!Go+ln9^*(wy6Gy zMp{~0HCYnXYNl@D@nYvdii}u32KQ6WSHuJ6SA|KoDZVNDSsHTNP2JzFdnaJ&VTn;Q zkjvCB3^NCp9z8O=$z+#4T&?2}Hb19O;=Cb}vq ziNV10^p)s-51*zU16!0A=G`lI!3d#8e_%E46~N$0Wo&G$?Aj4Rowy6sY_P;sVIVa* zBHOEoR26acx$CggSus$;?i)6^Yc;qF-BtM((%^BfbN6DqdW9aJLHs6I(pKIBqxNr3 zwf(=odJM(N5xtY<5*#XL51l3)o?+=MMi#R=LikbsK@QjB@NKvNRbaYUHQ{n%8L26newOk**V2 zxCFdPA)yGfnDm6bc@~P84(mELhOEb35k1M5AW8`Ak!AZC@8x=3O(i}0-6t_~#6?;q zsS4m`|I+UPMB>~=zNZLT|9bV~$N&;0!XUeI#|*Mwd*#2qu$@Veo+DyzWhKDN3#AHy zfq_w-HeP9+q>%8rH~yhd5oJ29knQb4y=s$scCRA5-B(UP1YZvJ)0#Qo{rx_pgNNmI zqwYjh-_wJ!t%5&=->nmB3rA?pGB-H(ViU#OC~7oGlWGeOPk6>EXe8d%%#bAM+E!~a zGcgH14INcPWz^yCdK85|{O0Pd67MnrZ-!CyH71Rc%OK=ABN$&{K3<%%#Qr=Rg`Ff_ zF#@w;8_aL=Xb9gkp+Y+!7F_my?VR^}sCw_ht)oEr_-BE>eqN1cM7MxaM#rCDFAn(} zphfnEPp4NTggg)t-4!%%TGk^OF;KV|gYH^Gu7+&DraF`Uh~`R)ARAHsxVvk^lH?7# z?|<#vJh7av5m?QAQFH?eg$~$y6^U>fow_nC4mHNCr+4slwzWq8wSV{FOS?yFbYX1z zdT^nJex9Pk*7T3}Z@qtq4nNb@pHy@pSZ&C)FUiB(95L^bKE zJ(p$V^#&_fU2cPM3PtpLKOVGp4 zVR(b>tuE;edmXgOu&HXhlZ9Ze1VKYeBxGhO^dcltjlSk7k%j)|gRIQe#H!Hio{}8` zhr^v$5}gwAeXs~qttLvVR=&A`Nf;P(0{v7oA2h5R z$zaBc_zu|Xx%kgG8n7~v)x2eJu4g-aQynN%Ooo zQ!W4sWC}ak`$xk%cK%K~&2F`&;px>m6N6o+iWGK9f9!iBpf~mMr_>i?NI&SBl&9E5 zxO;MuF1qe6nXZC$c0E)QObKmFx(rG*ybCOc@AoGHaG;t zBaVi|4l;p2FC32UatB4F>Ve2#<}q-t?{!^1?oL6zeJsz%D6OFBz1 zftpkHp$rsB+`+2JceMl>xzG1pC~w}}{O6v5GgjzJ5Go26p-#y1eLa`A1yE$N?NXQU z!bdu}{cwK9_2=PC z%oJGV0a!NB=zB-8Q8(60H#wcxEJSZmwi}S(fo$+ zHLooddZ?s4=AO`TGr$aTarbolLVKmdssE|82SV+!NR6gL!QBAM*do5J1fjD2e)0%3 zrNg~Z=!%13{b7g$9Si>Ae{OpST@x3DXYiRp>8<3jpiA{3$o!=q4wb+6=0q7Y@W;s} z=6V}BdcR0j39l-i$z7m$F{ihKr@&aIyTP7!U$W#({M{_;|NXS<_!eZbBh;s{v61lb zkv_3^HclPvZ+A9Oy5ZQz5t+nbJ2#Ld#B>L zK1`of(ZN8yT`$dC**VH;V1x5mE+7^b))nVWfB8B<99vdOX^ zNg9UfKyoSdj%398o1x6m2Zq#4bNA5AO|(QvJ(jGp8F+{v0aVYkr}}M&WT0Zbc12!H z$r<8r%=oHzaG~ST?0(hz$3be~nLu-72qE&BQZj`+~|ztCS+x0o&VgkwL=Gcr$2(d(+i8?5*_^#h>+F* z!dX3^-1`u5c}5Nnd}OE$;Hj@-#NGL>$a~_WG7btz;Nl+9l&g`qd%BWj`sc^z@p?C> z1QD0bah?!rz7AJM-71@uZ_^7;lrgNoHUVYg0b3*8FAm2}*7J&rjha8uzLj1AR{P7h zrl-e7U<>2PLK9=W)_=KxV6I)D{c`~K)UO>4H#tDSG>`IS)g;d|Hspd@tJ#JN*Gy@q zPtw?%vyBXC3gI1KtD}G9y%(9Ep1q+$`WPEa9INS?kvN|{YAYa;@5RT#bdX)Z1ZZuP z4`n_ba+ON>nPcRRy*XVwRx`t@3oJK73D~}S!oR5Qkd88^p2fVKSnYmVdMEJhMCl!{ z(RHP&lirPEJM=_LQC#pvjv9vh7|*QXODatK#ayt{QW3Z)T{wy`NDZ`665w0DaejNu8s*RKecLca&HJO7gcFp)ci_F-SO8I}COnBH?dcuGo2wpIy~b7H0C*bR^KBf0aV zEn*Uq4-4(VKGHtU4v!;cn}VBwV@{m4I^J=zpGV@yH^%G3Iq!38o%c+#!a$oUC8XZ% zSpJ)+3)uRNd2)K{GNoi1zYE5Sp#yD@tFRbB5;%*W73iftGs)$z2q_PwiNO5{Xg`5{ z^x(eH2t{|j4mU@WrO-ASCJS^cS996K$VuluxPj9xSW$zE)B3Mii>)X;o1d>e2P6Eg z^|Qd%P?>OgG2{|vWPs%D?ac~|&GV6_MQZ;Q9(-n7Sm?usJK9Lp?NOL<1ePO1-ra|Nkke5`fBpA*{L_{?Ao?%J8 zIfhZyIH>{pUC$o|UJQ zbP=^+1e+bJzbU)r$}^eYIq86v@J4akvKymknW$swND0#LZHahc;DS;2pupFX!OExK z?(Oe#)QZfaJ0decEuU3bXxsz=$FH`;F)m7+>_6+z!9dj~5OC-WaM-@eZuNn@XGBh$ z?e^6FSJ|7#L)o|e<74bg$-ZPuvSp33j6Jes-^xy+WM9X=Mxm%gvPVU-WgQJ7TM?tA zu?)sK3S*fVOZa}K>*~7i=Xvh?_j>*QD>ayN&hz*j$9p@P_0TzELS>K-yrRGIZ`d1C zm_y0Lo&5c?;)gfpdLvZe>yCway*r(Z>ds*7$MQTzS^D$#TbQUOg6hJKLlvf5E`SLW zL7a+^JbU5cxy%!1$*MYel!HWV6FttwfDdb9=ptkHcox7`s+o&2HN=@Vce7B<_~=cg z>}40r_~6pB63~q^?Yj?Do6-l={jjmUE;qRp8kIjD*tEO0rwGx-O-Jt^tEY4qyFFS!T-CEEwASX|5Nvk{w|t2$KFeYLsu|~V z6Wbp5Ip;DC$~pkmT|k`eo=kf$_}~OUXXVS6?|Wd9XVrSZvH?osa}UtrVV@`WS%)YY z{|KvoGq!e}uO=?~yeh+9wyby9U4-7Ga z8n*5zOVUo_tiUJinia@KlYAl2v*42lS<9IJG#$7ZT-{sKu-3LeXpV4K6eo^*R@)eG zP7!Tv-BZh3zG!3JY1hNP?uWbPebd5tTYP`OF1fNV4lk6lt^Z(qvCOgct;J)y2K$I0 zX4VrRb6DeSRQ3Ub%2Y`kz&VgvRH?{`fGLxSR((%(Zc`d zlG)`M1Q^%&*wl8gC?pd?nfW9gl83q~F!atjDK-Z-g>uwC-eJZIR z)1YW&eU!_&IMxy-ce55%^@;!He(#uZU6d4_u*GVol(YJ#|074-Z9$F1%(5zlm!IWd z>?_vYK!p^`y9`iU9xkaL{UV6kHCALO003AIX`gRMsV$I3v4iYNHnmJ+;hvISeR;Du zOSpU$F@d{gVd*U+S!ED}-)1$Qq)_abX}EYl8vp1X{e94`d==|S6uD(%;qn6X?Ahl% zvohcy0u({{x&`;P(CJl>&SkR5kbThdwC`6tC@IT%hP3_qsuN38#S6Xg%gE}y8LuD( ze(>OOF1;>j8z~3KzNnnX(c$;NGU)=?52i&yw!%}z0WFKFbY}&j`i8Oe6^yDsPu%oQ zgjqjAz-YrlO?U52->|Ki!Kh2!N3somOoE;Es4b|benzQLL@-ZU8C%1+Nmg_!!jF(yX-R9gc{Qj2`tS{N;4 zHR>i720dUat0$9OR&7(uc{>r&$0E=c_7uf}8Z*MxFUmHH4N9 zX8nU1($J4jA&FI(1J39u)UW%zkxWd7FX)K3%fp6mvJtYYhyI zsfNk$($unIl1satSf)Xozo*p;?RGd#=vjuhaMn#eJAmDk*42aH4IdFrz%Wpgb>0oH zGC6bMuxG;P3-@t0hgEQ)Z@3>WhQS)=9IXAB?j}PPOfY8Noc>Opvu9snyaVL!@!qh* z^RjFeOOUO()P*XCGJvG)n+l}GrtI4Bz0C7tVN@5~H@b0B{Xkcv zisl~8`L?h^@`g(_YEL_dWUp<>sN&KMn2fs^LUZ>Vn|$)0mcP3#SmV-3mHrYzK%Jhk zz0Dod0%%#Xy-1pcLCCEm%_D6r`-!V>t|bOnawWO!wk+i_M`_$LM9#@?-RNxV5oqbKQQu}jq{ODePvW6DZdbBWz(X*~M3M^$UbOB6A z!CAl~Tz`6$NXm&3Ivy9wlgNG%GlCFUN*f-}heZRz0&Y2zD^%m!dpw;T5M+x?aNQj` zg-)4@`_y?zv(Xx_v2$=HaJXpF{#uJUs*4^z)>#utr|%U0{Y>alpf9wjeR`r8v_h1s z<$7Twmd`20UOtjXVM!qv2m*R_%Ql-u(=**Z=xx2nM1TcdK(KR!4`o{$wNZD|(PZ6L3{gE=u#car1nr{X=9I0_N0^#xf5^10sh^bO9)0%vgN zRAJ?`LEO5=aIE1I0IVY#C}xM1-@V3%PrbWh|9p;+;n3sF^~pGcUmf~u^$4tK$?uDl zT5qJmUe6($vNJ;M&4ra%vC~zMjzLla;)5ZI(1Fiy9QG_Zxi5HeVqkk=l6*=5w{;|R zS-x7gZ*_Q1uq8IdKZBXLFd?|0Q(42&y9-Y^bLW`;N2uj&5VHSjRIB^!6#QwHr7=id(sArdFsJlH^Ustz52X;c80s+=Y9dkEK#`jbf+$M+1O=-exqx*P2IG=^uyM>Xe zbSBLu(`mX3;HL6;%$hO7gtYBVqkl_y&5Qd+YS!*UEkh(CAgZ?MdjJc)YO~2;cxwq^y zAj2SYU+Wbh>!6W*+m53#V6o$=eg>agbkh=KhiT|aI}UIO_|$giSu87UM@l}vJKUqM zZrM!9T&!ee9jSW_&)IKA-_i#61B4<}5=4`adL*-3wN$H$3^RZ;u##gv986lCRQA0K zukF57J24)BQllShU=^}&rW(_XbHwA$6MHxv--VoPT+6?7sxn{1iGn!=64UQd`K2xG zSZ6yJzP(!;zwC3i2%ohozwZ5MZ?j)|#ydZeIC;T)JoL)tdW2IkuRb+%9K{*G2H9Iv z_Q%KogzzRI4V7HVB1=9ceE3P|U{W9s%(|@6 zcs@4Q?2a;YtivOkupcKdg~*EczdH!kfnuH1ydSK>I$IGsm7mq3_OPFx%6)4vJ|$6Y zLsym8=ZPZX6( z1^RqS0a_6{cVA(pYz}}P6K~O9zVzK?I^B-09jf5xaNd>h;+PLypZ}P#S_VYVaduh) z(f7G~f}c^_VTn)LCYpy^a8&pJy59Q`N#c+)9qxLC3n$g#&KzU9!-!!i3=C8aX>#tL zIDf8;u}HV9$2UaeR5xF1TUFazIt?S`g{QP5{O8)0SaFV)Qem^nA{CTTYx!SUgbi!b z<|ON9!?QAH_ST~dg4Ky{Q^Z7}3OdeUbm7~IIXZx+BY0<&m8t`)Sk`Sz=jyyZK0321 zm^J+_CZ->x#bd;wmg{vBA?luE9`@+k&LsbXFv%N(c_FpuIRXXI(3Zw(N=elIR)Tf0 znl@IL#xEL$wx8%b^Ek>(X~V0CXh|A}=Vt#2}+L zE=O+)`c*dh5HPIrNRC{a`L1M^pFBVk4NIo$dJ6G%2R(X-{O+!+v3F$7QM%`8R_;)f zDtA-?FZu^a2Kmf<*Qa7sIH5l)Gt+-~%j03#E|#3Y`Bo{WHt9+F^{=(XU;? z#K)p@r}8tLsagsr<)0}$TawnK@w+w%-!}W+=d%t7)*b*i+|pHzH2$f|T&1%!PB)cW ztt09C{mYB2YM@_>v(pwR7zmF^Urkg&Dot^Sa=w|Hdanj^h{n+P;k(Xr=gQ#@<@5U{ zUz#7m3~`@TD99OkLk_l;}I|F|!rz&h%`XRl6L z9WR*i5x5zDO5ET?Q@+3lU!mKdOo2*#YFwH~V^3KHO(ntx3iu&PG{eer8j1>__bJyU zKd2c44t%xfj)~jVCM(0W<>``j$uv@&A-Of7DJgbR-00S*%UTPh{oFMHoLAZzV=p@r&9bZ0i;IrLQ-?;$NNgLYwcA~C&?pviDGUH9i zs%`oXxb~{E0RGyz3+51o%drNQkq(KI$Kfi?A}S%_UeO@@StVtZ@ikvE*zE^pT0i6^ zI_}hS9IkdYi!5v{qnk1$j|yWB-$d>wti7u5{0V3-XH_L8KNjqJu|k_w-dCUIs$z)y zub#4B=}okRkClZ`yd{vNEeIv(Mx{|=Fx+7+tkllt{sLm+P8;Sv3s$8(bYr##zH5ef zcdeUF-h2FK(n$jQm+-=-lRl0+Rw&G#-L>om`Dv@1Vxt%zIz&{@vQxpWbQiYb;%I`!QxeU#PRy+AeeAzF}#} zgG{x2;@f2~y*yn(oVlE(=t2dSwCR|D>XQShmaI}AL*++G6?r{mr|^BdA>>60HKcL6 zlcqpcD%cPNK7yKopr@MskMs6VC9kdCHNN<0VWC)L9Rz77#GBjMCA*t6Bi2-9;Z5sb zEliX$V&KXX&IPbaFbd$yK)sZPsvrDhS>gA9liU9!vd>%4w4eKWFJ$fKOavtC{<(BK zz?7cj6p8cG`j#M{I35D?8?=%aQD(O*IIn_`y1(=iHO zqgekg*jmeQa)dhafV6pzZw)Q~#9hR1^M#IOZqlzE=AuLysnBuy{3I9WFVFQkWt=tc z8MOh7$k|~{%f4bZqI~i@K*d9JNgtTrRp^G#efmCt>;?V^Y*MET4T&cbSe(S%aUauR zabPcdJh5YF$n-UHAxjzd3c%IQ@{LflIXAz=7DS9KP#x#FNew!iPcCPGTX9x1Ha^^k zdv*;0-vSBhVZRoUlGedXLmeeMfB?u-%&hWYA2lv|Pmk_jLHvGBT68yqtwLZe?*QL0 zmKMI#()eA4xmssu|AsofoY>1P`^Em`_5tSg#=}5>T9oRG)4#1Af#DII9|>)^FS9T5 zKPvlN_Nr&UTJ5L`vumU2%7vh*!T9UhDt!08XRqdmurQbde|oeO>$V#9+kXE^f6blBuTPX!VBi9$rUib{Ft}d7haBx|g5LzNe=e zG7PKkEtPz0=(~VPdoQ{;k!BEU$=m`+&q^XW!6qv{f&2U|=Y_t^5ZNKY9-vrS>tZ_* z_guLBJfwu@0x}`AH&4$=*TUW3MLB%%cZ1rZ&C%~*`OuxP&J(Q=s zcKX=K3%4tG-b2Gf&QLbr7ODYq+U2HxpW%q8Z^oslV41@nuo7%M+{c}pw~#W_$$c~g z5X)~80ODy4#ZL#R`8J;g-*7nW!d&Z!DBad(`Z>#9F@nlZHagW1X@4Huw9) zeCP+kj6X)Ph6X(oWz_yoUMUIO(SXXMQOgg~YQU_JP@Hs7L<_MX*&~Ey$%5 z1-Ie28jkT(6K^KfO|jUM6gxcq=MzY9t|!Ig;qe9WD^*_9ER#>58pWsI?UDn89EH8c z$~#@X=<{%`=K9;!o!I3;;(|;J1%RN*mDi{<9FQS4-#;z8W%UsJ$33xw3?gRW0MUoqx8thJa@Oayrz*3xEFRlC7yI%j5l>9Xt?Ym~h3*^gx$AfH zDQ?*is4@Bg)n|@{z|;LUCVhUq>E)|SFx&fee|ZwAP?p^13HIA!u(wm#Z82$yWacy2=*IEpK#?8$@ColGrBusbQAdF4nz_*N zX*$}tCiECVv876BY&I$sY{WIJweQc8obf(H^$qEkpJ~U%?Aty^(Keo>Vk3KM+~hrR zK_oTnOx|6tBDb?y8zG;?=q)~6Z4D1z2KP9*lz8^Q)tHtZ-FuwRZrG$IsYND23ZquH zAba>|GtB~zMnI6{uVuJgDJ?7Vi~;B=(c#MR_Cy63s?yn5N4|+t8LwO_$j$L&HBssC zLMveq#`o?D-~F@9l4RoRVK?5QGNO!^jsPw&rPDDv9GSK7D;34$+MBi|+KF?086hMN z8UoE&qNZcIeDdHfYbSoO>}%hCTz-!cgjH}9RmaLH?}OoNNjDw3^(12cp{}4<=V%zX zQQIgm$XunE;*q}W@mbZUecr$FaSgerb&n6>2-ObhsA|m4wcdqP4KE6WSq9NS+1E|K z&HVoN#=6Yn(ALecc9|-Cq`NZuMCjUc^OPt!<3$&#jw?Gmh<6XGOWODb#l;DEGOym0Tex_AiJ;Z@R}1V3rf ziLgyyNiw=uJ4GHQk1)D(PV27 zqS`E^5op8T(E6P!L|U{K9vfv;>tF1Dg0js6huq1Gc|f`QFvZ=zxeeG$NF}BDD>PD7 zoW^ts11i&+M7QrLGTVchsKpNY=!g;orJN>Eri98H*bFxm|nrWra+INw6LsCd!_2w6coZm*VH2E zzERM>1+}fNAwl?HjT*7XtmIPMvcps_);PwN7Ass)wt17($!a~s_gbs~u2ykWx~(+in=) znC7}d^>1I(URui=(GXfpIot1;=Gb|DdH(U(_N8tefpBO`52EDGbi}QB|Bk!1gOMU< zs+I7@3!Sc(lIvuT#zXU*H@Fvq)dBY0@5No|thd9@cK*dA1DUb4XiHN}|LzYu;&s*X zMj%Y5%yBN=^$>6MCre4wvNXn`0X!gO*!|7e()+ArFpFZucEwg?_w;5q5Bzax1~EWO7t-9$e#i91zo(pHryeAK+3j#jL++h~qwr@3Z#*9`_{gi(qedrx(z zu0`D!*hixW_ZLF>#_G8_C*h;{#ya}xDem)QHwq~^us07gU}K^ct`T6t8g*i{JJUgT z0z|YQH@6P4yq7Fe0Fk5|zUF-In3x4c3(b(=EyH(f zcokv~+XVs;?%ASw*Xry`7UVeBx+Axo4AFuMPWI$;dyv9o2;=@W2+loHGS)>qs(KM@ z@{x`Mx`7@K?^Lt|B~eJ{a_qg{?hUm*<;J=w@>8@3njYcPz9p+RXD;l{sUHD^ zI_+w9A3u~XioxIXbbmJVJI8Iy^Oif1=~gn~!2atL?tP()&?<+-;?&DD`%W1RZxQ45 zFh{T@nq11@McQZ2$NHMFjJaUCqUN~A7xmN3ubA(Oo2XR$CienkI5K24s8YC_*6>X4 z{5<9WcDR`9+oK#nx7{q4LvLgmUg8XHr>}r?ii6o+^Va?gIDMrrUhAh26x3&J#+#FB z4z|Bg6*7gcU0_f}>U8US= z8MWH)`H|PUQ-DyN`sP9jz~5S{Jtj}bzUEYW1K|ha<7g?ROZ5eyxN{!e^p(DfO z>0E}g7HtMY-ZVS|y+0d8XyF34WMPNT_0<$)*{)Z6u&s}b3_~RcCWTOP!8Z+3Nx!i- zA5Mk_E{%{tNnv|$KuhvmZg|W|5ybF+WKI7cLMaa5HSQwGt=PzTulxsf^?&my%#xyF zH|Ad2>R;5*{SQ{N|EjPGC8ZatbiA@PKQScxA5^HooAVzHD&UL-x?QJ$@+UhKLE8U+ z-l78OEuPF57rsEo`+wE^|J{Z$ON#y4xKWyud|5;H|0>h}yNE28lpU2DTUws1uhC1A z%EmoE?Kj&+3pSvhJUZN$-o{U+5^yLHhZ{lH|Hm5vk$Ccoq_2Qonpzhx`U56q1=Bnn z0JK~b!;erjUoHOp_qfM;;FlBIsCd~W>tRTeZ$0=xalv=$9%$0}0Dz$dR_&T0{9FL- z$3scSssXGRBnMCA&diH6%IYHJ0_uE94{zA3wDGw{0m|CljM>FY@7klgXH0tVvNT&mn zg)3OXh6ws@{v7`W+79ZfKhP-8f-Wxo@xL#BLX3br#P2oj>x0_@MHfilQoxKRPc3Sk z`4B5H4m(*i=j&@o}a6{hR-hb_%in9Zsiq*%_QIW!9D4jXsmyuhKO& zHM6!f_JDll-wZd&hTsE8X0X4L%1Aas+M5&Cb!rIM2SamzqJYx%(mhU>r>n5&Ux)=l zezg3N(=)BauJ74t|1(wOReUVf?*$TV`mtI^fc;5WJepLk09&g7K}2iIq7t&g2@v+X zBn&=XbvwHux!u9q<8fKd{|FPaK))}D1Me< zQ~a)w?Ykm@7}2iTQe_9A@Fcm;Ga!}7cfyePXpze(gOlO9PR)W z2sgNcRWJBpmcHiPaHPi1ox@)brwsLij{)U$c&6#USq9A^WXFC-Bu74m;7T~8?AMog zaS?gdgkVTL_stt$4BEjXcYN>|z)3@3W1Xap>;Oad9_UBH*NzXKY~I#Ly?ye6*ShF( znqS+Gf|KVA7sv-c1G*yl)WxK$%ZwzwJMMdZ&{7q@RkoX=6ONl~KLF4_MOUgFNy5__ zz;@|XD`6v_^fvI~Hm6ntyh7BXs#HUt%tYpgHJ)b4dIs$#`8Dq+c#0X8)b)?GwPw}C z97Vsb26HGSnzO8;W77wYZ71_AI0TqO{KDJd<)n@T>V!FAb+`>YdzoQ-V~G$ccLX4d z9biz{0Q>*!mgN9DNz7iWGYm?@eFrE=&9@vywQXGH6-P4M!M3Mpm|B=h6D;_L<0*{G zC;gK=bc(>{+A`Y)@obbnPzcXee4GJF=JcNXCr<Q*Y_OWwW7r$q1o zwr>n!iUi0JRyzy`s(}2(tKNXd>q*A&mlv1k8Yg_nIV4uv{IouN^Z|(R1J#HS_Fx`1 z$`#j%wu~^zb{%S1`r+wxl>kZH+ulU##j&(hT2PI3ffPT_{};FbqmACVRvv(?;G2v7 z@v5{vIT8p`!NsAzynX~ObgXNwxcxqM_97Uwz=A-Fmi71a?BE_aL|`S3i`4aaxU z2iVrDoj(A$y{Ra?-$&4Ow=aA4g1QmaINs=;`eWpEo$X^h;D(9zV8f3Uff-9%_-QZ6{U|r@6=uoo{Uju(cy9D zRCY-}03O26pEabX*lroxZnlXH{G4;%bC%8G!KW>1pG!r~&7kkW_E5d9MN?0NgX{iK z#DuTjXL6kRZk;qBoT<^L5KrSgPv%X$eH~Ec!=I~$+XN51&7H8uImM^=5X%cSgzpv) z4&EIdFxr!2xsUaB>>?cQxM9*fIl%LaZ0Kok3z)p^$uRGNe&$z5w+oz)@4Kw+VhHn4SOF$@X%?(oNRZ}ouA7%toW*9A4_ zB70|f!SR^FE|Y0G=}Xuw7VL94@c;1jetay(()U%S&p?CnpN zt@JAGP0>^R!1z3b6EMo$(>W}0o@N(E2EMvK1>BLM(nf!QwwrjtjXpd}~d#+l}OD;hGP~F^TU4FNr&d(`Yppt4X zFr?oT75qAXBLLi56>#lyhD`uW^Za}VtmBTSt~P_+M{}TF^FH}EQ=8cB*uNq})bwM& z&9uM(A*;@KIc{qAb9ZxHKJuVsV!#`nd)|{B?0ltfOBpgw9V~sJLRVkXSIXRx51--^ zONDjq8YCPZ?AujK-?bE=Wxgr<5K2Vw(rx$f+Q#BxDr?Dpz%5hh zuqkh%>%9du;KWhYS?JrMBMG~f3N^v0)k~~LP8yVmZY>_9tp=hG}a6Nt&WX~83q_{-FQVvs&M<} za|TbOyt_;Gv9`wvh#peo=%*TM&5?acw?px%oFT`F6A*C*ztd!r%zjg-poxct3L-Gn zc5}2VXOf#yEN%sGp__^~B#4LmYr}y&PU;%3lkH)C%mWZK6T+^r8Xmdwrq{IGQcP}Z zk3aLM?)t}Bp`n}(PzIN~xUk_&l9n=t9IULY9{F+K|IWI9l^*N#<7Dlnyb7AKy5H?C z4FyQ0VEQhar+CTW-kCV4yYYvWYe_Cw*PfM#sT-?!Obrwijk_WG^9>*&D#}N7D7DUP zS=aU}G^Wc)n>u$s9S(`BSf3xh0!wp_z%z2nVX5%UO7rJ!` zpXzu;(4awXC^6f!G%WH^lcHI_Dqo6|T2l4EK_MKL7&ioGm)fwx=&vPnT@YKz2;~x1;27`%LGZ(_0cv zmz7k_-Q{UnH_gSS248CA1nT3%aXe9=;1v1dhD&3gRQI?lD(iNDDtt&k=%kds746Np z{Kk7PNW`Ro!e~X#3)V&z8yM8)^I-XSy`@Ioj^#YgenJZV`aq;Gk~y-KFO&O2(n<1b z%I<)KcmoVrerQ#;^E-5^RXD%ZcDs%Oc21z@R`6@Rox%**99`g0$zKff@j62xBAWu4 zSO_OajerBK60Pz!Rd#?Ew@B|J?^pWfSk;{AsoS1Zk@;Of%SvubSJTgxMAqiho;HkY ze&&mOWl=h=*)PIqTVF5uzbeGLHAY8OlJ_UBO90C*IMG`|)y$jr8bw=+^a29?4%#-@ zeW;&roPx+0{@j$yt+j~U{L=4=a3vxPpN&5_u68TSVlFRiDy^Dh-5AN{Pxn^L5Er_# zEjH9R?SG@RIqKG8lgDsR{`JQKDaMdP>86*86KRp3cCGP%{#nOA{C zsdG>;%0D>4W29$#Xsz5SXxf|PHq2f8G>DC56i_gT1Jx=z^FyJV-O z(iysDry=QQ>xR1C{(YO1?rdy7#a9JVi@$CK#Vb9X)a^v5>DCWlBY4zKIlMF}RP#G~ zS1#T9NU`+#Xvoz2aMa)gVFG4z(>qPMPqiST#SDmo7;o-0)vZllR9t| zrO#rNun&vw{>uRToB_(6Nc!uJL|ob8-KjwQs50sbRRI_vOE{hEa1Z??wN7m}6fF!C zs7`E_?5`KB0RdT)7G&80;TKo1fN-6nL#T~6`L3m$F?zuM3sXIiTFGI_S>&u<9#Bt% zHOB2sdQygrdUM4scqqk22UP(s+>d_VMheiDKUH`V34mMXMTDYjF#O+MZW|E!gM4k_|%ze8K*`MVk_Qj`r0>SylX1P_ti&BK9wc8lx` z8RMl%x8iZK!ARek8auhX-KjyO3ZcX^H*6~2|D_puL9~{Vae;5WB%TNHP&rSVFd1Vu zvQeO+YbE?vevL5iAXUZUkK`L*8F$&6LNgCv3jmm7hbj>>$LbOhsoDt!39PlYm_0%! z=Ysc-#}@x|r2xY@PImvV3iU(-v~#C})2i>MKIXjm9`s%9!Q}9VON$?pTo9=?)q@1R z^_2O6T=ZGWhqu4tq4+BtOXlZXF6?Q;SJdt_r!sN!C)(e-`x9&zRoL108^n-zJkk{(3Jrp3b zY5nrT9C|(KY3$zbV1{IE0YybldHyvu9ym~n;sS9IcL0lF)X_X#lB?)5R@?2s@R_z* zl`c$&p)*jkJ%2!lVvUzPGNu4&Xz0>;7t=_*JvA||X}CHTCMza6>V}Cn%vdTD{sd5t zI(ExZbaozbnFZ5(eN;v;eOgAa^o@iAAk>zW2B%urgaBdy#8BzxPElKIHxH-tKTIAe zl;>fqs%!prTkc5wo%R@+tpU0|!7Y4Ax*9aG=Hab~c^MvpsYl+MR=v z!Qn7k_gEOxNV?-tR7lFI{IPC?eI7PqNrky2+3(s){z5C{Gs(^;n9PlOBF*38Bq9UO zN6!6r{msW>7S|+L3Jra|e}VBsmwBr9n-2gyW~>c&z#O&?x(?V#8G}piZs5Of-H2n= z(&z+b^#qxduxWFE+t*4vbV({kM&pzOWiKCB6%DS)(y}SzX7S|l;h5D1w4e>2O6a=t zbM)la_u)I;ET7n<9aYRho3v&9ynczfg>nt(n6^a2!ARz3y!~+W5dV^s)FcEWJ({E(<3sBMDcQp_m)X~fm*(2?Dv<(8PBcnpNju?BeZWQTj$Col0rQS5&?T1kfg$AkZ7wX& zLj%aRCmArQIblL3y!pJ}^(b~XtN#!F78qQcE zeh;YV#+mZ?75yNUwhi$_cbYoe;zNJQUONHf7vWWF2q*dvTTg*c_M>w8Jo9AafxGe% zz_KDNam&JC1!0qZm9bA-sG>n@1LUxopY_tR_`tZd_&mb*3&1nVGD`ozhYx_|SWk11 znzron?~=Sjv4|?<_qd{zNX+vZHjDvoI?4OD+6b#aOOent)(D*Ay0uWSrZ$f!ut`~@ zaEj%|EsqEjDwb>WD7Aerp^ih~9R`fyF;51b%K(R!qy%s4LMYFpRAzZuoH40~)~F2r z76zmxlQ#P7B(1e(BmTG_S<)hdk{a@z~jq4ZSRw!REr%SLRJayj$w$=2o+WXH6A_n3u{N(c#VDGI&-_w{hA0*+5o z{y;-PdjmP$BY;icyLjh#bLwm>ajLEl!D5p}(5d2D-wFWMO#d4RDHKUAq0cA1Zd;Qp zvG0Z0<&_3F_D9e6W%h@6pORcNGdlnV!4TR0cR;QsIeq9%lHXOlVG}iNH@{m$c+|&8 zOADf7@N z8;1iLU7jaX+Vx7Ed3Zj4nm>SwiVvW3I{z=dRFR2 zu@=%?7Yh>r1HfE9o0u`)-hN{c3#EgbhL)a{`lrZ3#Q;$JJNfhDZHNNY?|7e54*ZGI zmBp6AHh2MVDP&1;TRGu6% zFV;=slD&7^sZchz8^COzL1+|}Di8Vw!d=6MkIPKEOw<&_ZRshQ*_^p&sYgqC0zj%+ z8h9qmC&f(o$gQPGfC{h^nwEVczW0Y80ev5j$2l+JYZAD$f7sGcC)V|%`4{N-ivf|h zP9!$HzS`!(NwRIX?fp0lQu*jGl|F8mct&ygPlf9I^7m0!m}-2d?#vtyZ_8?j9ROuh zFOX)@-g<^4j1H1Uwv3&Q+ARVh-7}wF7(1FkJeiP?@@Zr6&iC$qZFTfw% zrrv>34J+F^!Fc@mCBQXalV3nxG4P{~MM|+5v{avlx4r-C{rrBe^JvS~{`v#W(0dHf zm2QC05gVw3@65U0_st6ZQ2<-#l)Xlg!K3@yH&O>ui8i|nj$r7G#$F>@AOPwF`ozG? zAa^t0W4vCh(QE9Q4VS3-^Sk*gJ9&4$RXLorrzVZcxW*NyJI8t3n`@q4KQQZf=JYQ5 z+iU7(K9@7X8`MtC2q2^}eHxzyZAH3rQibwmqk;v}J|YdZx;%nobLFECwyvy?`vulb zlv+VI>*l7^mP74+4KW-+AXJk2+M1T4f%CzasuDs{-7!C~$X=VY>696I8|-PCTL6$OFLSfl;O4VYz~!|x)fyo* z92Wv3ofZu^Z{7RB02xpct0#QthlP9^++0`Ed~W*G+q^C>>e(#Ww5M+6Ay7iitu{?> zm$~YeVcYYU?em;@Z@sAF&8fx3Q>A+%!8AEB!V?297rwcxEun8RIIOU7)O_orD73V; z)gbJ?0iFCql-g#bKUu;IP`cT!Neh8c%x1d;cdp4Tu?J1P&ih^{ha{Iw-Q5)SHSXqy^5A(wCKnX=#`+R{S2j@Dr#FOrZ^Y2KcqYSZc`&SzV^0~RwewPav zlr>fz@Ek=NPY5kQoq|3$Y3vr3)O=h9#fl~V7&+T#L|HN2DWwabzul*tHG@9$` zlU!y|p-ZesSDyr$H}w~)$2@JeKK+Jd%&;71PfWISwz`4VIy-0x9|^~ zYOg1ssd2YyDv>z=VILhNf#eqRU{tg9`vD4&e$WMUqbiF;dlP^3jqEH&Ow>>7q#ImK zflu7B36b#VAeX^uF(KUeaWvNsw|3gY6dsir6~<3Egs(ru@|Pb@4pVBv zpZ=y+0>wgTaR7`_hi3xrDK7UwK;4lVG=P-5Vd!MB!2vPQt)#xXyZP5yZY~DEQe~fG z6Q#6efux%VYe%tjTqS3@G`R&7h=jG|R8PiNWKJ!i{_9b36(>gq*Ab1ubqil!8qsej z!WP8J7PZwIFDUK4xS+D^7te3E`+N{g{Q4@}O(1v(%XzFbP*>;L&(AUpLlQzt1Powa z6m1h3jej;H-X~^CIlPTcl?dVY@Hkc8%>#4#w@*swPs|kf17Pm3Rndd{WfCYExLa?q zWnNvDm<3gZiHlU`>YS~@v;H^Ie_!sZQ(YkpRsI0K2%Ii^{N43+VfHS7lh}8`DNLRA zkezZE*v7LR{L;FYlStR*-BQIx^QW=Pl$Pe$q8!D3Rbi z-28citv|VC=1g(mgXFTm{>rXNO7U6K9pmL=)opv_yqW(F*`~){68cm6Pe{fAfw5<6 ztbt%XpGGQuh>Puont zDO!l%CE%t*bw+-7#7uhUNu&BL$ql0~-Ndu3(-yiZZ z4(uID_$PVg?rAdMRJvjbY(p8K<(0chCXi-j^;b&002_(&7gzb-rh0wr&+ RAG Accuracy Benchmarks + RAG Performance Benchmarks ``` diff --git a/docs/perf-benchmarks.md b/docs/perf-benchmarks.md new file mode 100644 index 000000000..7029b68bc --- /dev/null +++ b/docs/perf-benchmarks.md @@ -0,0 +1,232 @@ + + +# RAG Performance Measurement Methodology + +[GenAI Perf](https://github.com/triton-inference-server/perf_analyzer/tree/main/genai-perf), NVIDIA’s open-source benchmarking tool evaluates end-to-end RAG pipeline performance under realistic load conditions. By testing across varying levels of concurrency, it offers a consistent and reproducible way to compare different RAG deployment configurations. + +## Key Terms + +| Term | Description | +|------|-------------| +| **Total Requests** | The total number of questions issued to the RAG server in a single benchmark run. Controls the size of the workload and is kept constant across configurations for fair comparison. | +| **Concurrency** | The number of simultaneously active worker threads sending requests to the server. A higher concurrency simulates a heavier multi-user load. | +| **N_Times** | The number of measured benchmark iterations performed after warm-up. Multiple iterations improve statistical stability of the reported metrics. In all experiments reported in this document, `N_Times` is set to 3. | +| **Input Sequence Length (ISL)** | The number of tokens in the input prompt sent to the RAG server. | +| **Output Sequence Length (OSL)** | The number of tokens in the RAG server's generated response. | +| **TTFT (Time to First Token)** | The elapsed time from when a request is submitted until the first output token is returned. A key indicator of perceived responsiveness in streaming deployments. | +| **Inter-Token Latency (ITL)** | Defined as (E2E Latency - TTFT) / (OSL - 1), where OSL is the number of output tokens generated per request, averaged across all requests in the benchmark run. | +| **KV Cache** | A memory buffer on the GPU (HBM) storing the key-value attention states computed for all tokens in an active request. KV cache size grows with sequence length, number of model layers, and hidden dimension. When aggregate KV cache across concurrent requests saturates HBM, new requests queue rather than execute, driving up TTFT. | +| **HBM (High Bandwidth Memory)** | The GPU's on-chip memory (e.g., 80 GB on H100). | +| **Prefill** | The stage in which the model processes the full input prompt simultaneously to construct the KV cache. | +| **Decode** | The autoregressive phase where output tokens are generated one at a time. | +| **Batch / Effective Batch Size** | The set of requests processed simultaneously in a single GPU forward pass during the decode phase. At each decode step, the GPU computes attention over the accumulated KV caches of all requests in the active batch and generates one new token per request. A larger batch means more requests share the same forward pass, increasing per-token contention for GPU memory bandwidth and raising ITL. The effective batch size at any moment is constrained by available HBM: once the aggregate KV cache of active requests saturates HBM, additional requests queue rather than enter the batch. Configurations with a smaller per-request KV cache footprint (smaller model, shorter context) sustain a larger effective batch size under the same HBM budget. | +| **Reasoning Chain / Chain-of-Thought** | An extended internal monologue generated by the model before producing its final answer, used to decompose complex questions into intermediate reasoning steps. For Llama-3.3-Nemotron-Super-49B, reasoning is activated by setting the system prompt to "detailed thinking on" and suppressed by "detailed thinking off" — no model weight change occurs between modes. When active, reasoning tokens are generated autoregressively in the same decode phase as the final answer and occupy KV cache slots for the full duration of the request, increasing TTFT and ITL relative to reasoning-off. | + +## Benchmarking Modes + +The benchmark supports two distinct modes depending on the evaluation objective. + +### Mode 1 — Synthetic Sequence-Length Benchmarking + +In this mode, the benchmarking workload is defined by a target input sequence length and a target output sequence length rather than by real questions. Synthetic queries are programmatically generated to match the specified token lengths, enabling precise control over the load profile and allowing users to isolate the performance impact of sequence length independently of question content. To support retrieval in this mode, a Wikipedia dataset of 50,000 records is pre-ingested into the Vector Database, providing a sufficiently large and diverse document corpus for the retrieval stage to operate under realistic conditions. + +### Mode 2 — Dataset-Driven Benchmarking + +A curated set of domain-specific questions serves as the request pool. To prevent unbounded generation from obscuring true system throughput, the maximum output token length is capped at 32,000 tokens, ensuring responses remain within a well-defined generation budget and that results are directly comparable across runs. + +| Dataset | Number of Questions | Source Documents | Size | QA Characteristics | +|---------|--------------------:|------------------|------|-------------------| +| [RagBattlePacket](https://www.eyelevel.ai/post/most-accurate-rag) | 92 | Deloitte public tax PDFs | 1,146 pages | 92 questions across text, tabular, and graphical categories; visually dense corpus with rich tables and figures requiring cross-modal understanding | +| [KG-RAG](https://github.com/docugami/KG-RAG-datasets/tree/main/sec-10-q/data/v1) | 195 | SEC 10-Q PDFs + KG triples | 1,037 pages | Entity-centric factual QA over structured financial filings; questions target specific named entities and numerical facts with minimal visual content | +| [HotPotQA](https://huggingface.co/datasets/hotpotqa/hotpot_qa) | 979 | Wikipedia paragraphs | ~113K QA pairs; 2,673 source documents | Multi-hop reasoning requiring the model to chain facts across multiple documents (bridge and comparison); plain text only with no tables or figures | +| [BO767](https://digitalcorpora.org/) | 487 | 767 PDFs | 54,730 pages | Varied, heterogeneous content across a large-scale mixed corpus of forensic and operational documents; high proportion of image and structured content alongside text | + +## How It Works +The following sections describe how benchmarking works. +### Request Pool Construction + +Depending on the selected mode, the request pool is constructed differently: + +- **Synthetic mode:** Synthetic prompts are generated with ISL and OSL set to 128. +- **Dataset mode:** Questions are drawn sequentially from the curated benchmark dataset in a round-robin fashion. Once all questions in the dataset have been issued, the cycle restarts from the beginning, continuing until the total number of requests defined by `total_requests` is reached. This ensures uniform dataset coverage regardless of the configured workload size. + +In both modes, the `total_requests` parameter guarantees that every configuration is evaluated against an identical, fixed-size workload, enabling fair and reproducible comparisons across deployment variants. + +### Concurrency Sweep + +GenAI Perf spawns *N* worker threads, where *N* is driven by a concurrency parameter. The blueprint sweeps across the following concurrency levels: **1, 10, 25, 50, 75, 100, 125** — allowing users to observe how the system scales and identify the point at which latency or throughput begins to degrade. + +### Sequential Request Dispatch + +Each thread draws questions from the pool one at a time in sequence, taking the next request only after a response to the current one has been received. This models realistic per-user session behavior and avoids artificially inflating throughput through intra-thread batching. + +### Warm-Up and Measured Runs + +Before recording any metrics, GenAI Perf executes an initial warm-up run to bring the RAG servers to a steady operational state, eliminating cold-start artifacts. The benchmark is then repeated for a configurable number of iterations (`N_Times`), ensuring that the collected statistics are stable and reproducible. + +### Performance Result Collection + +For every request across all threads and iterations, GenAI Perf records timing and outcome data. These are aggregated into a performance result store, from which key metrics — including Time-to-First-Token and Inter-Token Latency — are computed and reported per concurrency level. + +## Configuration and Performance Results +The following sections provide further information about configuration and performance results. +### Configuration and Setup + +The following deployment configurations are evaluated: + +- **LLM-49B** — [Llama-3.3-Nemotron-Super-49B](https://build.nvidia.com/nvidia/llama-3_3-nemotron-super-49b-v1_5/modelcard): A key feature is the reasoning toggle — setting the system prompt to "detailed thinking on" causes the model to generate an internal chain-of-thought before the final answer; "detailed thinking off" produces a direct response. This is a system-prompt switch — no weight change occurs between modes. +- **VLM nano** — [Nemotron Nano VL](https://build.nvidia.com/nvidia/nemotron-nano-12b-v2-vl/modelcard): Its smaller parameter count means significantly lower KV cache memory consumption per request compared to the 49B model. +- **Ingestion setup:** + - Default ingestion: The default ingestion that RAG 2.4.0 uses. + - Default ingestion with VLM image captioning enabled: Details in [Image Captioning Support](image_captioning.md). VLM is enabled during ingestion to extract image and structured content from documents, and also enabled at query time to process retrieved image and structured chunks via the multimodal pipeline. + +| # | LLM Model | Embedding Model | Reasoning On/Off | Ingestion Method | +|---|-----------|----------------|-------------------|-----------------| +| 1 | LLM-49B | Default Embedding | On | Default Ingestion | +| 2 | LLM-49B | Default Embedding | Off | Default Ingestion | +| 3 | VLM nano | Default Embedding | On | Default Ingestion with VLM image captioning enabled | +| 4 | VLM nano | Default Embedding | Off | Default Ingestion with VLM image captioning enabled | + +### Impact Factors + +TTFT (Time to First Token) at high concurrency is primarily determined by KV cache memory pressure. Each active request occupies HBM proportional to sequence length × model layers × hidden dimension. When aggregate KV cache across concurrent requests saturates HBM, incoming requests queue, driving up p95 TTFT. + +Factors governing TTFT: + +- **Model size:** A larger model (49B) has more layers and a wider hidden dimension, consuming more HBM per request than a smaller model (12B) at the same input length. +- **Reasoning chain:** When reasoning is enabled, the model generates a full chain-of-thought before the first answer token. The caller's perceived TTFT includes the full thinking chain duration, and the extended request lifetime occupies KV cache slots for longer, accelerating HBM saturation under concurrency. +- **Image processing pipeline (VLM configurations only):** When VLM inference is enabled and retrieved documents contain chunks with `content_metadata.type = "image"` or `"structured"`, the pipeline fetches thumbnail images from MinIO object storage, encodes them to base64 PNG, and injects them into the prompt alongside text before generation begins. This pre-generation overhead adds directly to TTFT per request, independent of KV cache pressure. + +**ITL (Inter-Token Latency) measures batch contention during the decode phase.** At each decode step, the GPU computes attention over all active requests' accumulated KV caches simultaneously. The more concurrent requests sharing a decode step, the longer each individual token waits — resulting in higher ITL. + +Factors governing ITL: + +- **Effective batch size:** Configurations that fit more concurrent requests into HBM simultaneously (small model + short outputs) produce higher ITL due to greater per-token contention. +- **Output length per request:** Longer outputs per request (reasoning chains) reduce how many requests can coexist in HBM at once, lowering batch contention and ITL. + +### Dataset Mode Results + +The purpose of this mode is to evaluate the performance of RAG under different usage of LLM and VLM, along with enabling reasoning on or off. The following Helm chart configuration is applied for retrieval: + +| # | Configuration | Description | Value | +|---|--------------|-------------|-------| +| 1 | LLM/VLM #GPUs | Number of GPUs allocated to LLM/VLM | 2 | +| 2 | Reranker / Embedding / VectorDB #GPU | Number of GPUs allocated to each service | 1 | +| 3 | Citation | Whether citation source should be returned | Off | +| 4 | VDB K | Number of records taken from Vector DB | 100 | +| 5 | Reranker K | Top number of records returned after reranking | 10 | +| 6 | total_requests | Total requests sent to RAG server per concurrency | MAX(100, 5 × Concurrency) | + +#### KG-RAG + +![KG-RAG — H100 Performance](assets/perf-benchmarks/kgrag_h100_performance.png) + +- TTFT is highest (~210s p95) for the LLM-Reasoning-On configuration due to the large per-request KV cache of the 49B model and extended request lifetimes from reasoning-chain generation. +- TTFT is lowest for VLM-Reasoning-Off, consistent with the VLM nano model's smaller KV cache on a text-dominant structured corpus. +- Both LLM-Reasoning-Off and VLM-Reasoning-On configurations show intermediate TTFT values. +- ITL is highest for VLM-Reasoning-Off (~260ms) because the small VLM nano KV cache allows for a high number of concurrent requests, maximizing batch contention during each decode step. +- VLM-Reasoning-On records significantly lower ITL than VLM-Reasoning-Off — approximately 20% of the reasoning-off value — despite using the same 12B model. When reasoning is enabled, the extended chain-of-thought output occupies KV cache slots for a much longer duration per request, substantially reducing the number of requests the scheduler can hold in the active batch simultaneously. This ITL reduction is proportionally similar to the LLM-Reasoning-On vs. LLM-Reasoning-Off drop, but far larger in absolute milliseconds — because VLM-Reasoning-Off reaches ~260ms due to its large active batch, so shrinking that batch via reasoning has much more room to reduce contention than on the LLM side, where the batch was already small. +- ITL is lowest for LLM-Reasoning-On (< 20ms, near-flat). The 49B model with its long reasoning-chain outputs effectively reduces the concurrent batch size, thereby limiting per-token contention. +- ITL for LLM-Reasoning-Off is intermediate (above LLM-Reasoning-On). This is because the same large 49B model with short outputs permits more requests to share decode steps, leading to increased contention relative to the reasoning-on case. + +#### RagBattlePacket + +![RagBattlePacket — H100 Performance](assets/perf-benchmarks/ragbattlepacket_h100_performance.png) + +- VLM-Reasoning-On and LLM-Reasoning-On converge at the highest TTFT values, both exceeding 140s at concurrency=125. +- For VLM configurations on RagBattlePacket, VLM-enabled ingestion produces chunks with `content_metadata.type = "image"` and `"structured"` in addition to plain text. At query time, the pipeline fetches thumbnails from MinIO and constructs a multimodal prompt (text + images) sent to VLM nano. This image processing overhead — MinIO fetch, base64 PNG encoding, and a larger multimodal prefill — adds directly to TTFT per request. +- LLM-Reasoning-Off records substantially lower TTFT than both VLM configurations. Default ingestion produces text-only chunks and `enable_vlm_inference=False` means no image processing pipeline is triggered — generation begins immediately on a text-only prompt. +- LLM-Reasoning-On records the lowest ITL, remaining near-flat below 20ms throughout. +- VLM-Reasoning-Off records the highest ITL, plateauing near 360ms from concurrency=25 onwards — the steepest plateau observed across all datasets. +- The rapid ITL rise at low concurrency (concurrency=10 to 25) followed by a plateau indicates the system reaches maximum batch occupancy early, after which the scheduler begins queuing rather than further expanding the active batch. +- VLM-Reasoning-On records approximately 6× lower ITL than VLM-Reasoning-Off on this dataset. Both VLM modes go through the same image processing pipeline, so the delta is driven entirely by output length. VLM-Reasoning-Off produces short outputs — the 12B model's small KV cache already allows a large active batch, and short per-request lifetimes keep that batch continuously full, sustaining high decode contention. When reasoning is enabled, each request generates a long chain-of-thought over the visually complex tax documents before producing its final answer, holding each request in the active batch for a far longer duration, preventing the scheduler from admitting new requests and shrinking the effective batch size significantly. The magnitude of the drop is amplified by RagBattlePacket's document complexity, which elicits longer reasoning chains than simpler text corpora. + +#### HotPotQA + +![HotPotQA — H100 Performance](assets/perf-benchmarks/hotpotqa_h100_performance.png) + +- LLM-Reasoning-On produces the highest p95 TTFT of all datasets, exceeding 250s at concurrency=125. + - Reason: HotPotQA's multi-hop questions require chaining facts across multiple source documents, which elicits longer reasoning chains from a thinking-enabled model. Each request holds a large KV cache slot (49B model) for an extended duration, accelerating HBM saturation at scale. +- VLM-Reasoning-Off records the lowest TTFT. + - Reason: HotPotQA source documents are plain Wikipedia text containing no tables or figures, so VLM ingestion does not inflate retrieved context. The model-size advantage of VLM nano translates directly into lower KV cache pressure and reduced queuing latency. +- VLM-Reasoning-Off again records the highest ITL, rising continuously to approximately 330ms at concurrency=125. This indicates the system has not yet reached batch saturation at concurrency=125 on this dataset, consistent with HotPotQA's short Wikipedia paragraph chunks producing compact retrieved contexts that allow the VLM nano model to continue accepting additional concurrent requests at the highest tested concurrency. +- LLM-Reasoning-On remains the lowest ITL configuration, near-flat below 25ms. + +#### BO767 + +![BO767 — H100 Performance](assets/perf-benchmarks/bo767_h100_performance.png) + +- On BO767, LLM-Reasoning-Off achieves lower TTFT than VLM-Reasoning-Off, even though LLM-49B is the larger model. This reversal is driven by the additional image processing pipeline overhead (thumbnail fetch and encoding) that only VLM configurations incur on this visually dense corpus. +- LLM-Reasoning-On records the highest TTFT, reaching approximately 165s at concurrency=125, driven by the 49B model's large per-request KV cache and extended request lifetimes from reasoning-chain generation. +- VLM configurations carry additional per-request TTFT overhead from the image processing pipeline. The BO767 VLM-ingested index contains 45,819 image chunks and 31,030 structured chunks (49.2% of total records). When these chunk types appear in the retrieved top-10, the pipeline fetches thumbnails from MinIO, encodes them to base64 PNG, and sends a multimodal prompt to VLM nano — adding latency before generation begins on every affected request. +- LLM configurations are immune to this overhead regardless of index content. With `enable_vlm_inference=False`, the query-time pipeline performs text-only generation with no MinIO fetch, explaining why LLM-Reasoning-Off achieves lower TTFT than VLM-Reasoning-Off despite being the larger model. +- VLM-Reasoning-Off ITL plateaus at approximately 380ms by concurrency=50, followed by a sustained plateau through concurrency=125 — indicating the system reaches maximum batch occupancy early on this corpus. +- LLM-Reasoning-Off plateaus at approximately 40ms, well below VLM-Reasoning-Off, consistent with the 49B model's larger per-request KV cache limiting the concurrent batch size. +- LLM-Reasoning-On records the lowest ITL, near-flat below 25ms across all concurrency levels. + +### Cross-Dataset Patterns + +**TTFT Ordering (Time to First Token)** — Across all four datasets, the following TTFT ordering holds consistently: + +- LLM-Reasoning-On produces the highest or joint-highest TTFT, driven by the combination of a large per-request KV cache and long request lifetimes from reasoning-chain generation. +- VLM-Reasoning-Off produces the lowest or joint-lowest TTFT, benefiting from the VLM nano model's small KV cache footprint and the absence of reasoning-chain latency. +- The relative ordering of LLM-Reasoning-Off vs. VLM-Reasoning-Off depends on corpus visual content. On visually dense corpora (RagBattlePacket, BO767), VLM-enabled ingestion produces image and structured chunks that trigger per-request image processing overhead at query time (MinIO thumbnail fetch, base64 encoding, multimodal prompt construction), adding directly to TTFT for VLM configurations. LLM configurations bypass this pipeline entirely, achieving lower TTFT despite the larger model. On text-dominant corpora (HotPotQA, KG-RAG), no image processing is triggered and the 12B model's smaller KV cache footprint gives VLM-Reasoning-Off the lower TTFT. + +**ITL Ordering (Inter-Token Latency)** — Across all datasets, the ITL ordering is fully consistent: + +| Rank | Configuration | Mechanism | +|------|--------------|-----------| +| **Highest ITL** | VLM-Reasoning-Off | Small 12B model + short outputs = maximum concurrent requests in HBM = highest batch contention per decode step. | +| **2nd** | LLM-Reasoning-Off | Large 49B model limits concurrency, but short outputs allow moderate batch occupancy. | +| **3rd** | VLM-Reasoning-On | Reasoning chain extends output length, reducing concurrent requests in HBM vs. reasoning-off. | +| **Lowest ITL** | LLM-Reasoning-On | Large 49B model + very long reasoning-chain outputs = minimum concurrent requests in HBM = lowest batch contention. | + +**ITL Plateau Behavior** — VLM-Reasoning-Off ITL plateaus or slightly declines at very high concurrency on several datasets (RagBattlePacket from c=25, BO767 from c=50). This reflects the onset of request queuing: once HBM is saturated, the scheduler queues incoming requests rather than expanding the active decode batch, which caps batch contention and prevents further ITL growth. + +### Synthetic Mode Results + +The purpose of this mode is to re-evaluate the latency difference between LLM and VLM in isolation, removing dataset-specific effects such as visual content and reasoning-chain variability. A Wikipedia dataset of 50,000 records is pre-ingested into the Vector Database, providing a sufficiently large and diverse text-only corpus for the retrieval stage to operate under realistic conditions. The same Helm chart configuration as dataset mode is applied. The workload is fixed at ISL=128, OSL=128, representing an ordinary conversational use case — short questions, short answers — where each request occupies minimal KV cache. + +The same hardware allocation as dataset mode is applied: 2 GPUs for the LLM/VLM model server and 1 GPU each for the reranker, embedding model, and vector database. + +![Wikipedia — H100 LLM vs VLM (Reasoning Off)](assets/perf-benchmarks/wikipedia_synthetic_h100_performance.png) + +With reasoning disabled on both configurations and a uniform text-only corpus, the results expose the pure effect of model size on each metric: + +- **TTFT:** LLM-Reasoning-Off records marginally higher TTFT than VLM-Reasoning-Off across all concurrency levels, reaching approximately 65s vs. 60s at concurrency=125. Both curves rise linearly throughout the tested range, consistent with a chat-style workload where each request places minimal KV cache pressure. This confirms that for conversational workloads, both LLM and VLM configurations sustain responsive TTFT without entering a queuing collapse, and the model-size difference has negligible practical impact on user-perceived latency in this regime. The narrow gap between the two configurations reflects the fact that at these short sequence lengths, both models receive equally small inputs and produce equally short outputs — the KV cache size per request is nearly identical, leaving model parameter count as the only differentiator with a proportionally small impact. + +- **ITL:** The two configurations diverge sharply. LLM-Reasoning-Off ITL plateaus and remains flat at approximately 40ms from concurrency=25 onwards, indicating the decode batch has reached its HBM capacity and the scheduler has begun queuing excess requests beyond that point. VLM-Reasoning-Off ITL rises steeply and continuously, reaching approximately 220ms at concurrency=125 with no plateau visible — the 12B model's small per-request KV cache allows the scheduler to keep admitting more concurrent requests into the active decode batch as concurrency grows, continuously increasing per-token contention. The 49B LLM model's larger KV cache footprint caps the effective batch size early, preventing further ITL growth beyond concurrency=25. + +Taken together, the two metrics reveal two distinct saturation thresholds: decode batch saturation, which the LLM hits at low concurrency and is reflected in the ITL plateau, and full system TTFT saturation, which neither configuration reaches within the tested concurrency range for this chat-scale workload. This synthetic result serves as a clean baseline confirmation of the theoretical framework — on a text-only corpus with no image processing overhead, model size is the sole differentiating factor, producing the expected TTFT ordering (LLM slightly higher) and ITL ordering (VLM significantly higher at scale). + +### Cross-Dataset Latency with LLM-Reasoning-Off + +![LLM-Reasoning-Off — All Datasets (H100)](assets/perf-benchmarks/cross_dataset_llm_reasoning_off.png) + +In addition to per-dataset views, all four benchmarks plus the synthetic Wikipedia workload are aggregated into a single comparison using the same Llama-3.3-Nemotron-Super-49B configuration with reasoning disabled. This chart reports p95 Time To First Token (TTFT) and Inter-Token Latency (ITL) as concurrency increases on a single H100. + +Wikipedia synthetic serves as a lower bound: with fixed ISL=128 and OSL=128, no retrieval, and text-only inputs, it represents the lightest possible workload for the model. All real RAG datasets sit above this baseline. HotPotQA and KG-RAG show the highest TTFT and ITL, reflecting their heavier retrieved contexts (multi-hop Wikipedia reasoning and SEC 10-Q filings). RagBattlePacket falls in the middle, while BO767 exhibits the lowest ITL among the RAG datasets and closely tracks the Wikipedia ITL plateau. Its TTFT remains higher than Wikipedia's because real retrieval still adds pre-generation overhead. + +Taken together, this cross-dataset view confirms that the latency behavior of the 49B LLM is consistent and predictable: for a fixed model and hardware configuration, TTFT increases roughly linearly with concurrency for every dataset, and datasets that supply more complex or extensive retrieved context exhibit proportionally higher TTFT and ITL than the synthetic baseline. + +## Key Takeaways + +**Model size governs the TTFT/ITL trade-off direction, but corpus visual content determines its magnitude.** On text-only corpora, the smaller VLM nano (12B) delivers lower TTFT than the larger LLM-49B due to its reduced KV cache footprint — but this advantage is fully reversed on visually dense corpora (BO767, RagBattlePacket), where the VLM image processing pipeline (MinIO thumbnail fetch, base64 encoding, multimodal prompt construction) adds per-request overhead that outweighs the model-size benefit. As a result, on visually dense corpora, LLM-Reasoning-Off achieves lower TTFT than VLM-Reasoning-Off despite being the larger model — the image processing overhead is the dominant factor, not model size. + +**Reasoning is a force multiplier on TTFT, not just an accuracy switch.** Enabling reasoning on LLM-49B produces the highest TTFT in nearly every dataset tested. The chain-of-thought is generated autoregressively before the first answer token is returned, extending request lifetime and occupying KV cache slots for longer — accelerating HBM saturation under concurrency. This is a system-level cost, not just a per-request latency cost. + +**TTFT and ITL pull in opposite directions by design.** A small model with short outputs lets the scheduler pack more requests in parallel — this keeps TTFT low but creates a large, contended decode batch that drives ITL up. A large model with long reasoning outputs does the opposite: it shrinks the active batch, keeping ITL low but consuming more HBM per request and causing queuing that raises TTFT. Across every dataset tested, the lowest-TTFT configuration always has the highest ITL, and vice versa. No single configuration optimizes both metrics simultaneously — configuration selection is a deliberate trade-off between response latency and decode throughput. + +**ITL plateau is the earliest signal of HBM saturation.** Before TTFT shows non-linear growth, ITL flattening reveals that the scheduler has already begun queuing requests and capping the decode batch. In the synthetic experiment, LLM ITL plateaus at concurrency=25 while TTFT is still rising linearly, consistent with a chat-scale workload (ISL=128, OSL=128) where each request is small and queuing remains well controlled. In this regime, both LLM and VLM configurations keep end-user response times within an acceptable band. + +## Related Topics + +- [RAG Accuracy Benchmarks](accuracy-benchmarks.md) +- [Evaluate Your NVIDIA RAG Blueprint System](evaluate.md) +- [Enable Reasoning in Nemotron LLM Models](enable-nemotron-thinking.md) +- [VLM-Based Inferencing in RAG](vlm.md) +- [Image Captioning Support](image_captioning.md) +- [Best Practices for Common Settings](accuracy_perf.md) From 41ea4d6603d297bf7ba789640d1433ddd5c5b146 Mon Sep 17 00:00:00 2001 From: Sebastion Date: Fri, 8 May 2026 10:26:48 +0100 Subject: [PATCH 52/52] fix(milvus): escape document source values in delete filter (CWE-89) User-supplied document names sent to delete_documents were interpolated directly into Milvus boolean filter expressions via f-strings: collection.delete(f"source['source_name'] == '{source_value}'") A document name containing a single quote could break out of the string literal and inject arbitrary boolean expressions, causing unintended documents to match the delete filter. The same problem affected the fallback 'source == ...' filter and the document_info delete filter built from the basename. Escape backslashes and single quotes with a small helper before interpolating user-controlled source values into the filter expression. Adds a unit test that demonstrates the original boolean injection payload no longer breaks out of the literal. --- src/nvidia_rag/utils/vdb/milvus/milvus_vdb.py | 32 +++++++++++++++++-- .../test_utils/test_vdb/test_milvus_vdb.py | 28 ++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/nvidia_rag/utils/vdb/milvus/milvus_vdb.py b/src/nvidia_rag/utils/vdb/milvus/milvus_vdb.py index 3b40dc288..51528ed6b 100644 --- a/src/nvidia_rag/utils/vdb/milvus/milvus_vdb.py +++ b/src/nvidia_rag/utils/vdb/milvus/milvus_vdb.py @@ -617,6 +617,21 @@ def get_documents(self, collection_name: str) -> list[dict[str, Any]]: ) return documents_list + @staticmethod + def _escape_milvus_string_literal(value: str) -> str: + """Escape a value for safe interpolation inside a single-quoted Milvus + boolean expression literal (e.g. ``field == ''``). + + Milvus filter expressions are parsed as a small expression language and + do not support parameterised queries for ``Collection.delete``. To + prevent filter-expression injection (CWE-89-class), escape backslashes + first and then single quotes so that user-controlled values cannot + break out of the surrounding ``'...'`` literal. + """ + if not isinstance(value, str): + value = str(value) + return value.replace("\\", "\\\\").replace("'", "\\'") + def delete_documents( self, collection_name: str, @@ -634,15 +649,26 @@ def delete_documents( for source_value in source_values: doc_name = os.path.basename(source_value) + # Escape string literals before interpolating into Milvus filter + # expressions to prevent filter-expression injection via document + # names that contain single quotes or backslashes. + escaped_source_value = self._escape_milvus_string_literal(source_value) + escaped_doc_name = self._escape_milvus_string_literal(doc_name) logger.info( f"Deleting document {source_value} from collection " f"{collection_name} at {self.vdb_endpoint}" ) try: - resp = collection.delete(f"source['source_name'] == '{source_value}'") + resp = collection.delete( + f"source['source_name'] == '{escaped_source_value}'" + ) self._delete_entities( collection_name=DEFAULT_DOCUMENT_INFO_COLLECTION, - filter=f"info_type == 'document' and collection_name == '{collection_name}' and document_name == '{doc_name}'", + filter=( + f"info_type == 'document' and collection_name == " + f"'{collection_name}' and document_name == " + f"'{escaped_doc_name}'" + ), ) except MilvusException: # Fallback to legacy source field format @@ -650,7 +676,7 @@ def delete_documents( f"Failed to delete document {source_value}, source name might be " "available in the source field" ) - resp = collection.delete(f"source == '{source_value}'") + resp = collection.delete(f"source == '{escaped_source_value}'") if result_dict is not None: if resp.delete_count == 0: diff --git a/tests/unit/test_utils/test_vdb/test_milvus_vdb.py b/tests/unit/test_utils/test_vdb/test_milvus_vdb.py index 9fda5c695..eb1f23084 100644 --- a/tests/unit/test_utils/test_vdb/test_milvus_vdb.py +++ b/tests/unit/test_utils/test_vdb/test_milvus_vdb.py @@ -805,6 +805,34 @@ def test_delete_documents_milvus_exception(self, mock_collection): assert result is True assert mock_collection_obj.delete.call_count == 2 + @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.Collection") + def test_delete_documents_escapes_filter_injection(self, mock_collection): + """Source values containing single quotes must not break out of the + Milvus filter expression literal (CWE-89: filter expression injection). + """ + mock_collection_obj = Mock() + mock_resp = Mock() + mock_resp.delete_count = 1 + mock_collection_obj.delete.return_value = mock_resp + mock_collection.return_value = mock_collection_obj + + vdb = _make_dummy_milvus_vdb_for_delete() + # Attempt classic boolean-injection payload + malicious = "evil.pdf' or '1'=='1" + vdb.delete_documents("test_collection", [malicious]) + + # Inspect the filter expression passed to Collection.delete + call_args = mock_collection_obj.delete.call_args_list[0] + expr = call_args.args[0] if call_args.args else call_args.kwargs.get("expr", "") + # The injection fragment must be neutralised by escaping the quote + assert " or '1'=='1'" not in expr, ( + f"delete_documents is vulnerable to filter expression injection: {expr!r}" + ) + # The escaped form must be present + assert "evil.pdf\\' or \\'1\\'==\\'1" in expr or "evil.pdf\' or \'1\'==\'1" in expr, ( + f"Expected escaped quotes in filter expression, got: {expr!r}" + ) + @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.MilvusClient") @patch("nvidia_rag.utils.vdb.milvus.milvus_vdb.connections") def test_create_metadata_schema_collection_new(