Skip to content

Commit fa65370

Browse files
committed
add branch parameter, change resources & clean up
1 parent 17438c1 commit fa65370

9 files changed

Lines changed: 207 additions & 32 deletions

File tree

demos/demos-v2.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,5 +305,9 @@ demos:
305305
supportedNamespaces: []
306306
resourceRequests:
307307
cpu: 8000m
308-
memory: 16384Mi
309-
pvc: 35Gi
308+
memory: 16Gi
309+
pvc: 10Gi
310+
parameters:
311+
- name: branch
312+
description: Git branch to download demo files from
313+
default: main
File renamed without changes.

demos/opensearch-rag/load-embeddings-from-git.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ spec:
217217
echo "[OK] Downloaded embeddings file (size: ${FILE_SIZE})"
218218
env:
219219
- name: RAW_FILE_URL
220-
value: "https://github.com/stackabletech/demos/raw/refs/heads/feat/opensearch-rag-demo/demos/opensearch-rag/embeddings/stackable-docs-embeddings.json"
220+
value: "https://github.com/stackabletech/demos/raw/refs/heads/{{ branch }}/demos/opensearch-rag/embeddings/stackable-docs-embeddings.json"
221221
volumeMounts:
222222
- name: data
223223
mountPath: /data

docs/modules/demos/pages/opensearch-rag.adoc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ To run this demo, your system needs at least:
2323

2424
* 8 {k8s-cpu}[cpu units] (core/hyperthread)
2525
* 16GiB memory
26-
* 35GiB disk storage
26+
* 10GiB persistent storage + 20GiB ephemeral storage
2727
2828
For optimal performance (faster LLM responses, reduced query latency):
2929

3030
* 12 {k8s-cpu}[cpu units] (core/hyperthread)
3131
* 24GiB memory
32-
* 35GiB disk storage
32+
* 10GiB persistent storage + 20GiB ephemeral storage
3333
3434
== Overview
3535

@@ -159,7 +159,7 @@ The notebook shows how to search for relevant documentation using OpenSearch hyb
159159

160160
=== Response Generation
161161

162-
The notebook formats retrieved documents with metadata (title, repo, relevance score, URL) and provides them as context to Llama 3.1 8B.
162+
The notebook formats retrieved documents with metadata (title, operator, relevance score, URL) and provides them as context to Llama 3.1 8B.
163163
Responses stream in real-time, showing:
164164

165165
* Detected product filters
@@ -198,7 +198,7 @@ The `rag-documents` index is configured with a k-NN vector field:
198198
},
199199
"content": { "type": "text" },
200200
"title": { "type": "text" },
201-
"repo": { "type": "keyword" },
201+
"operator": { "type": "keyword" },
202202
"has_code_block": { "type": "boolean" }
203203
}
204204
}
@@ -231,7 +231,7 @@ The hybrid query structure:
231231
"embedding": {
232232
"vector": [0.123, -0.456, ...], // 768-dim query vector
233233
"k": 30,
234-
"filter": { "term": { "repo": "kafka-operator" } }
234+
"filter": { "term": { "operator": "kafka-operator" } }
235235
}
236236
}
237237
},
@@ -298,7 +298,7 @@ You can also test queries using *Dev Tools* console to see raw OpenSearch respon
298298
** *nomic-embed-text:v1.5*: 768-dim embeddings (~274MB)
299299
** *llama3.1:8b*: 8 billion parameter LLM for generation (~4.7GB)
300300
* Models are pulled via lifecycle postStart hook
301-
* Resources: 3.5-7 CPU, 8-14 GB RAM, 20 GB ephemeral storage
301+
* Resources: 4-8 CPU, 10-16 GB RAM, 20 GB ephemeral storage
302302

303303
*JupyterLab*::
304304
* Single-Pod notebook server

stacks/opensearch-rag/jupyterlab.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ spec:
6868
- bash
6969
args:
7070
- -c
71-
- curl https://raw.githubusercontent.com/stackabletech/demos/feat/opensearch-rag-demo/stacks/opensearch-rag/opensearch-rag.ipynb -o /home/jovyan/opensearch-rag.ipynb
71+
- curl https://raw.githubusercontent.com/stackabletech/demos/{{ branch }}/stacks/opensearch-rag/opensearch-rag.ipynb -o /home/jovyan/opensearch-rag.ipynb
7272
volumeMounts:
7373
- mountPath: /home/jovyan
7474
name: notebook

stacks/opensearch-rag/ollama.yaml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,13 @@ spec:
7272
value: "0.0.0.0:11434"
7373
- name: OLLAMA_DEBUG
7474
value: "1"
75-
envFrom:
76-
args:
7775
resources:
7876
limits:
79-
cpu: 7000m
80-
memory: 14Gi
77+
cpu: 8000m
78+
memory: 16Gi
8179
requests:
82-
cpu: 3500m
83-
memory: 8Gi
80+
cpu: 4000m
81+
memory: 10Gi
8482
volumeMounts:
8583
- name: ollama-data
8684
mountPath: /root/.ollama
@@ -114,7 +112,7 @@ spec:
114112
done
115113
116114
/bin/ollama pull nomic-embed-text:v1.5
117-
/bin/ollama pull llama3.1:8b
115+
/bin/ollama pull "{{ ollamaLlmModel }}"
118116
volumes:
119117
- name: ollama-data
120118
emptyDir:

stacks/opensearch-rag/opensearch-rag.ipynb

Lines changed: 180 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,43 @@
216216
"id": "279b41dc",
217217
"metadata": {},
218218
"outputs": [],
219-
"source": "# Operator name mapping\nOPERATOR_MAP = {\n 'trino': 'trino-operator',\n 'airflow': 'airflow-operator',\n 'druid': 'druid-operator',\n 'hdfs': 'hdfs-operator',\n 'hbase': 'hbase-operator',\n 'hive': 'hive-operator',\n 'kafka': 'kafka-operator',\n 'nifi': 'nifi-operator',\n 'opensearch': 'opensearch-operator',\n 'spark': 'spark-k8s-operator',\n 'superset': 'superset-operator',\n 'zookeeper': 'zookeeper-operator',\n 'opa': 'opa-operator',\n 'secret': 'secret-operator',\n 'listener': 'listener-operator',\n 'commons': 'commons-operator'\n}\n\ndef detect_operator(query):\n \"\"\"Detect operator names in query for metadata filtering.\"\"\"\n query_lower = query.lower()\n for keyword, operator in OPERATOR_MAP.items():\n if keyword in query_lower:\n return operator\n return None\n\ndef is_implementation_query(query):\n \"\"\"Detect if query is asking for implementation details.\"\"\"\n impl_keywords = ['how', 'deploy', 'configure', 'setup', 'install', 'create', 'implement', 'example']\n query_lower = query.lower()\n return any(keyword in query_lower for keyword in impl_keywords)\n\nprint(\"Query enhancement functions loaded\")"
219+
"source": [
220+
"# Operator name mapping\n",
221+
"OPERATOR_MAP = {\n",
222+
" 'trino': 'trino-operator',\n",
223+
" 'airflow': 'airflow-operator',\n",
224+
" 'druid': 'druid-operator',\n",
225+
" 'hdfs': 'hdfs-operator',\n",
226+
" 'hbase': 'hbase-operator',\n",
227+
" 'hive': 'hive-operator',\n",
228+
" 'kafka': 'kafka-operator',\n",
229+
" 'nifi': 'nifi-operator',\n",
230+
" 'opensearch': 'opensearch-operator',\n",
231+
" 'spark': 'spark-k8s-operator',\n",
232+
" 'superset': 'superset-operator',\n",
233+
" 'zookeeper': 'zookeeper-operator',\n",
234+
" 'opa': 'opa-operator',\n",
235+
" 'secret': 'secret-operator',\n",
236+
" 'listener': 'listener-operator',\n",
237+
" 'commons': 'commons-operator'\n",
238+
"}\n",
239+
"\n",
240+
"def detect_operator(query):\n",
241+
" \"\"\"Detect operator names in query for metadata filtering.\"\"\"\n",
242+
" query_lower = query.lower()\n",
243+
" for keyword, operator in OPERATOR_MAP.items():\n",
244+
" if keyword in query_lower:\n",
245+
" return operator\n",
246+
" return None\n",
247+
"\n",
248+
"def is_implementation_query(query):\n",
249+
" \"\"\"Detect if query is asking for implementation details.\"\"\"\n",
250+
" impl_keywords = ['how', 'deploy', 'configure', 'setup', 'install', 'create', 'implement', 'example']\n",
251+
" query_lower = query.lower()\n",
252+
" return any(keyword in query_lower for keyword in impl_keywords)\n",
253+
"\n",
254+
"print(\"Query enhancement functions loaded\")"
255+
]
220256
},
221257
{
222258
"cell_type": "markdown",
@@ -259,7 +295,26 @@
259295
"id": "ab6kt8bl6kr",
260296
"metadata": {},
261297
"outputs": [],
262-
"source": "def build_knn_query(query_embedding, detected_operator, k):\n \"\"\"Build k-NN query with optional operator filter.\"\"\"\n knn_query = {\n 'embedding': {\n 'vector': query_embedding,\n 'k': k * 3 # Get more candidates for rescoring\n }\n }\n \n # Add operator filter if operator detected\n if detected_operator:\n knn_query['embedding']['filter'] = {\n 'term': {'operator': detected_operator}\n }\n \n return knn_query\n\nprint(\"k-NN query builder loaded\")"
298+
"source": [
299+
"def build_knn_query(query_embedding, detected_operator, k):\n",
300+
" \"\"\"Build k-NN query with optional operator filter.\"\"\"\n",
301+
" knn_query = {\n",
302+
" 'embedding': {\n",
303+
" 'vector': query_embedding,\n",
304+
" 'k': k * 3 # Get more candidates for rescoring\n",
305+
" }\n",
306+
" }\n",
307+
" \n",
308+
" # Add operator filter if operator detected\n",
309+
" if detected_operator:\n",
310+
" knn_query['embedding']['filter'] = {\n",
311+
" 'term': {'operator': detected_operator}\n",
312+
" }\n",
313+
" \n",
314+
" return knn_query\n",
315+
"\n",
316+
"print(\"k-NN query builder loaded\")"
317+
]
263318
},
264319
{
265320
"cell_type": "markdown",
@@ -333,7 +388,24 @@
333388
"id": "fdmm66xll2h",
334389
"metadata": {},
335390
"outputs": [],
336-
"source": "def format_search_results(opensearch_response):\n \"\"\"Extract relevant fields from OpenSearch response.\"\"\"\n results = []\n for hit in opensearch_response['hits']['hits']:\n results.append({\n 'title': hit['_source']['title'],\n 'content': hit['_source']['content'],\n 'category': hit['_source']['category'],\n 'operator': hit['_source'].get('operator', ''),\n 'has_code_block': hit['_source'].get('has_code_block', False),\n 'url': hit['_source'].get('url', ''),\n 'score': hit['_score']\n })\n return results\n\nprint(\"Search result formatter loaded\")"
391+
"source": [
392+
"def format_search_results(opensearch_response):\n",
393+
" \"\"\"Extract relevant fields from OpenSearch response.\"\"\"\n",
394+
" results = []\n",
395+
" for hit in opensearch_response['hits']['hits']:\n",
396+
" results.append({\n",
397+
" 'title': hit['_source']['title'],\n",
398+
" 'content': hit['_source']['content'],\n",
399+
" 'category': hit['_source']['category'],\n",
400+
" 'operator': hit['_source'].get('operator', ''),\n",
401+
" 'has_code_block': hit['_source'].get('has_code_block', False),\n",
402+
" 'url': hit['_source'].get('url', ''),\n",
403+
" 'score': hit['_score']\n",
404+
" })\n",
405+
" return results\n",
406+
"\n",
407+
"print(\"Search result formatter loaded\")"
408+
]
337409
},
338410
{
339411
"cell_type": "markdown",
@@ -351,7 +423,54 @@
351423
"id": "y1wrd5gztoi",
352424
"metadata": {},
353425
"outputs": [],
354-
"source": "def search_documents(query, k=10, log_query=False):\n \"\"\"Search for relevant document chunks using OpenSearch hybrid search (k-NN + BM25).\"\"\"\n # Step 1: Get embedding\n query_embedding = get_embedding(query)\n \n # Step 2: Detect enhancements\n detected_operator = detect_operator(query)\n boost_code_blocks = is_implementation_query(query)\n \n # Step 3: Build k-NN query\n knn_query = build_knn_query(query_embedding, detected_operator, k)\n \n # Step 4: Build rescore query\n rescore_query = build_rescore_query(query, boost_code_blocks)\n rescore_query['window_size'] = k * 3\n \n # Step 5: Construct complete search body\n search_body = {\n 'size': k,\n 'query': {\n 'knn': knn_query\n },\n 'rescore': rescore_query,\n '_source': ['title', 'content', 'category', 'operator', 'has_code_block', 'url']\n }\n \n # Log the query if requested (with truncated vector for readability)\n if log_query:\n # Create display version with truncated vector\n log_body = json.loads(json.dumps(search_body)) # Deep copy\n vector = log_body['query']['knn']['embedding']['vector']\n log_body['query']['knn']['embedding']['vector'] = [vector[0], vector[1], vector[2], \"...\", vector[-3], vector[-2], vector[-1]]\n print(\"OpenSearch Query:\")\n print(json.dumps(log_body, indent=2))\n print()\n \n # Step 6: Execute search\n response = opensearch_client.search(\n index=INDEX_NAME,\n body=search_body\n )\n \n # Step 7: Format and return results\n return format_search_results(response)\n\nprint(\"OpenSearch hybrid search orchestrator loaded\")"
426+
"source": [
427+
"def search_documents(query, k=10, log_query=False):\n",
428+
" \"\"\"Search for relevant document chunks using OpenSearch hybrid search (k-NN + BM25).\"\"\"\n",
429+
" # Step 1: Get embedding\n",
430+
" query_embedding = get_embedding(query)\n",
431+
" \n",
432+
" # Step 2: Detect enhancements\n",
433+
" detected_operator = detect_operator(query)\n",
434+
" boost_code_blocks = is_implementation_query(query)\n",
435+
" \n",
436+
" # Step 3: Build k-NN query\n",
437+
" knn_query = build_knn_query(query_embedding, detected_operator, k)\n",
438+
" \n",
439+
" # Step 4: Build rescore query\n",
440+
" rescore_query = build_rescore_query(query, boost_code_blocks)\n",
441+
" rescore_query['window_size'] = k * 3\n",
442+
" \n",
443+
" # Step 5: Construct complete search body\n",
444+
" search_body = {\n",
445+
" 'size': k,\n",
446+
" 'query': {\n",
447+
" 'knn': knn_query\n",
448+
" },\n",
449+
" 'rescore': rescore_query,\n",
450+
" '_source': ['title', 'content', 'category', 'operator', 'has_code_block', 'url']\n",
451+
" }\n",
452+
" \n",
453+
" # Log the query if requested (with truncated vector for readability)\n",
454+
" if log_query:\n",
455+
" # Create display version with truncated vector\n",
456+
" log_body = json.loads(json.dumps(search_body)) # Deep copy\n",
457+
" vector = log_body['query']['knn']['embedding']['vector']\n",
458+
" log_body['query']['knn']['embedding']['vector'] = [vector[0], vector[1], vector[2], \"...\", vector[-3], vector[-2], vector[-1]]\n",
459+
" print(\"OpenSearch Query:\")\n",
460+
" print(json.dumps(log_body, indent=2))\n",
461+
" print()\n",
462+
" \n",
463+
" # Step 6: Execute search\n",
464+
" response = opensearch_client.search(\n",
465+
" index=INDEX_NAME,\n",
466+
" body=search_body\n",
467+
" )\n",
468+
" \n",
469+
" # Step 7: Format and return results\n",
470+
" return format_search_results(response)\n",
471+
"\n",
472+
"print(\"OpenSearch hybrid search orchestrator loaded\")"
473+
]
355474
},
356475
{
357476
"cell_type": "markdown",
@@ -367,15 +486,32 @@
367486
"cell_type": "markdown",
368487
"id": "ln0hj4qo5tm",
369488
"metadata": {},
370-
"source": "### 4.1: Format Context for LLM\n\nEach retrieved document is formatted with metadata (title, operator, relevance score, URL) to help the LLM understand the source quality and provide citations."
489+
"source": [
490+
"### 4.1: Format Context for LLM\n",
491+
"\n",
492+
"Each retrieved document is formatted with metadata (title, operator, relevance score, URL) to help the LLM understand the source quality and provide citations."
493+
]
371494
},
372495
{
373496
"cell_type": "code",
374497
"execution_count": null,
375498
"id": "i9mu8uy4dr9",
376499
"metadata": {},
377500
"outputs": [],
378-
"source": "def format_context_for_llm(docs):\n \"\"\"Format retrieved documents into context string with metadata.\"\"\"\n context_parts = []\n for doc in docs:\n context_parts.append(\n f\"Source: {doc['title']} (from {doc['operator']}, relevance: {doc['score']:.2f})\\n\"\n f\"URL: {doc.get('url', 'N/A')}\\n\"\n f\"{doc['content']}\"\n )\n return \"\\n\\n\".join(context_parts)\n\nprint(\"Context formatter loaded\")"
501+
"source": [
502+
"def format_context_for_llm(docs):\n",
503+
" \"\"\"Format retrieved documents into context string with metadata.\"\"\"\n",
504+
" context_parts = []\n",
505+
" for doc in docs:\n",
506+
" context_parts.append(\n",
507+
" f\"Source: {doc['title']} (from {doc['operator']}, relevance: {doc['score']:.2f})\\n\"\n",
508+
" f\"URL: {doc.get('url', 'N/A')}\\n\"\n",
509+
" f\"{doc['content']}\"\n",
510+
" )\n",
511+
" return \"\\n\\n\".join(context_parts)\n",
512+
"\n",
513+
"print(\"Context formatter loaded\")"
514+
]
379515
},
380516
{
381517
"cell_type": "markdown",
@@ -500,7 +636,39 @@
500636
"id": "6tbawkzl0gd",
501637
"metadata": {},
502638
"outputs": [],
503-
"source": "def rag_query(query, k=10, stream=True, log_query=True):\n \"\"\"Complete RAG workflow: search + generate.\"\"\"\n print(f\"Query: {query}\")\n print()\n \n # Show if operator detected\n detected_operator = detect_operator(query)\n if detected_operator:\n print(f\"Detected operator: {detected_operator}\")\n \n print(\"Retrieving relevant chunks...\")\n print()\n \n # Search with query logging\n context_docs = search_documents(query, k=k, log_query=log_query)\n \n print(f\"Found {len(context_docs)} relevant chunks:\")\n for i, doc in enumerate(context_docs, 1):\n code_indicator = \" [code]\" if doc.get('has_code_block') else \"\"\n print(f\" {i}. {doc['title']} (score: {doc['score']:.3f}){code_indicator}\")\n print(f\" {doc['url']}\")\n \n print()\n print(\"Generating response with Llama 3.1 8B...\")\n print()\n answer = generate_response(query, context_docs, stream=stream)\n print(\"=\" * 80)\n \n return {'query': query, 'context': context_docs, 'answer': answer}\n\nprint(\"RAG pipeline ready\")"
639+
"source": [
640+
"def rag_query(query, k=10, stream=True, log_query=True):\n",
641+
" \"\"\"Complete RAG workflow: search + generate.\"\"\"\n",
642+
" print(f\"Query: {query}\")\n",
643+
" print()\n",
644+
" \n",
645+
" # Show if operator detected\n",
646+
" detected_operator = detect_operator(query)\n",
647+
" if detected_operator:\n",
648+
" print(f\"Detected operator: {detected_operator}\")\n",
649+
" \n",
650+
" print(\"Retrieving relevant chunks...\")\n",
651+
" print()\n",
652+
" \n",
653+
" # Search with query logging\n",
654+
" context_docs = search_documents(query, k=k, log_query=log_query)\n",
655+
" \n",
656+
" print(f\"Found {len(context_docs)} relevant chunks:\")\n",
657+
" for i, doc in enumerate(context_docs, 1):\n",
658+
" code_indicator = \" [code]\" if doc.get('has_code_block') else \"\"\n",
659+
" print(f\" {i}. {doc['title']} (score: {doc['score']:.3f}){code_indicator}\")\n",
660+
" print(f\" {doc['url']}\")\n",
661+
" \n",
662+
" print()\n",
663+
" print(\"Generating response with Llama 3.1 8B...\")\n",
664+
" print()\n",
665+
" answer = generate_response(query, context_docs, stream=stream)\n",
666+
" print(\"=\" * 80)\n",
667+
" \n",
668+
" return {'query': query, 'context': context_docs, 'answer': answer}\n",
669+
"\n",
670+
"print(\"RAG pipeline ready\")"
671+
]
504672
},
505673
{
506674
"cell_type": "markdown",
@@ -582,12 +750,11 @@
582750
"metadata": {},
583751
"outputs": [],
584752
"source": [
585-
"# Try your own question here\n",
586-
"my_query = \"YOUR QUESTION HERE\"\n",
587-
"\n",
588753
"# Use stream=True (default) to watch tokens generate in real-time\n",
589-
"# Use stream=False for instant complete response\n",
590-
"result = rag_query(my_query, stream=True)"
754+
"# Use stream=False to display the complete response once it is generated\n",
755+
"# Modify k to change the number of documentation chunks that will be retrieved from OpenSearch to be used as context in the prompt.\n",
756+
"# Use log_query=False to stop logging the OpenSearch query.\n",
757+
"result = rag_query(\"YOUR QUESTION HERE\", stream=True, k=10, log_query=True)"
591758
]
592759
}
593760
],
@@ -604,4 +771,4 @@
604771
},
605772
"nbformat": 4,
606773
"nbformat_minor": 5
607-
}
774+
}

stacks/opensearch-rag/opensearch.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ spec:
1717
max: "2"
1818
memory:
1919
limit: "2Gi"
20+
storage:
21+
data:
22+
capacity: 10Gi
2023
replicas: 1
2124
configOverrides:
2225
opensearch.yml:

stacks/stacks-v2.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,8 +713,8 @@ stacks:
713713
supportedNamespaces: []
714714
resourceRequests:
715715
cpu: 8000m
716-
memory: 16384Mi
717-
pvc: 35Gi
716+
memory: 16Gi
717+
pvc: 10Gi
718718
parameters:
719719
- name: openSearchAdminPassword
720720
description: Password of the OpenSearch admin user
@@ -734,3 +734,6 @@ stacks:
734734
- name: ollamaLlmModel
735735
description: Ollama LLM model for text generation
736736
default: llama3.1:8b
737+
- name: branch
738+
description: Git branch to download demo files from
739+
default: main

0 commit comments

Comments
 (0)