opencode-skills-collection 3.1.10 → 3.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +8 -1
  2. package/bundled-skills/browser-testing-with-devtools/SKILL.md +334 -0
  3. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  4. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  5. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  6. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  7. package/bundled-skills/docs/sources/sources.md +4 -0
  8. package/bundled-skills/docs/users/bundles.md +1 -1
  9. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  10. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  11. package/bundled-skills/docs/users/getting-started.md +1 -1
  12. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  13. package/bundled-skills/docs/users/usage.md +4 -4
  14. package/bundled-skills/docs/users/visual-guide.md +4 -4
  15. package/bundled-skills/drizzle-migration-conflict/SKILL.md +179 -0
  16. package/bundled-skills/drizzle-migration-conflict/references/ci-policy.md +87 -0
  17. package/bundled-skills/drizzle-migration-conflict/references/conflict-resolution.md +163 -0
  18. package/bundled-skills/drizzle-migration-conflict/references/report-template.md +69 -0
  19. package/bundled-skills/drizzle-migration-conflict/references/sources.md +51 -0
  20. package/bundled-skills/drizzle-migration-conflict/scripts/check_drizzle_migrations.py +721 -0
  21. package/bundled-skills/frontend-lighthouse/SKILL.md +348 -0
  22. package/bundled-skills/pre-release-review/SKILL.md +198 -0
  23. package/bundled-skills/pre-release-review/references/checklist.md +104 -0
  24. package/bundled-skills/pre-release-review/references/report-template.md +91 -0
  25. package/bundled-skills/re-create/SKILL.md +251 -0
  26. package/bundled-skills/weaviate/SKILL.md +132 -0
  27. package/bundled-skills/weaviate/references/ask.md +36 -0
  28. package/bundled-skills/weaviate/references/create_collection.md +152 -0
  29. package/bundled-skills/weaviate/references/environment_requirements.md +34 -0
  30. package/bundled-skills/weaviate/references/example_data.md +24 -0
  31. package/bundled-skills/weaviate/references/explore_collection.md +50 -0
  32. package/bundled-skills/weaviate/references/fetch_filter.md +88 -0
  33. package/bundled-skills/weaviate/references/get_collection.md +32 -0
  34. package/bundled-skills/weaviate/references/hybrid_search.md +47 -0
  35. package/bundled-skills/weaviate/references/import_data.md +160 -0
  36. package/bundled-skills/weaviate/references/keyword_search.md +38 -0
  37. package/bundled-skills/weaviate/references/list_collections.md +31 -0
  38. package/bundled-skills/weaviate/references/query_search.md +38 -0
  39. package/bundled-skills/weaviate/references/semantic_search.md +46 -0
  40. package/bundled-skills/weaviate/scripts/ask.py +106 -0
  41. package/bundled-skills/weaviate/scripts/create_collection.py +359 -0
  42. package/bundled-skills/weaviate/scripts/example_data.py +945 -0
  43. package/bundled-skills/weaviate/scripts/explore_collection.py +295 -0
  44. package/bundled-skills/weaviate/scripts/fetch_filter.py +261 -0
  45. package/bundled-skills/weaviate/scripts/get_collection.py +122 -0
  46. package/bundled-skills/weaviate/scripts/hybrid_search.py +157 -0
  47. package/bundled-skills/weaviate/scripts/import.py +701 -0
  48. package/bundled-skills/weaviate/scripts/keyword_search.py +142 -0
  49. package/bundled-skills/weaviate/scripts/list_collections.py +77 -0
  50. package/bundled-skills/weaviate/scripts/query_search.py +135 -0
  51. package/bundled-skills/weaviate/scripts/semantic_search.py +139 -0
  52. package/bundled-skills/weaviate/scripts/weaviate_conn.py +231 -0
  53. package/bundled-skills/weaviate-cookbooks/SKILL.md +67 -0
  54. package/bundled-skills/weaviate-cookbooks/references/advanced_rag.md +274 -0
  55. package/bundled-skills/weaviate-cookbooks/references/agentic_rag.md +360 -0
  56. package/bundled-skills/weaviate-cookbooks/references/async_client.md +428 -0
  57. package/bundled-skills/weaviate-cookbooks/references/basic_agent.md +270 -0
  58. package/bundled-skills/weaviate-cookbooks/references/basic_rag.md +219 -0
  59. package/bundled-skills/weaviate-cookbooks/references/data_explorer.md +336 -0
  60. package/bundled-skills/weaviate-cookbooks/references/environment_requirements.md +78 -0
  61. package/bundled-skills/weaviate-cookbooks/references/frontend_interface.md +104 -0
  62. package/bundled-skills/weaviate-cookbooks/references/pdf_multimodal_rag.md +635 -0
  63. package/bundled-skills/weaviate-cookbooks/references/project_setup.md +75 -0
  64. package/bundled-skills/weaviate-cookbooks/references/query_agent_chatbot.md +163 -0
  65. package/package.json +1 -1
  66. package/skills_index.json +156 -0
@@ -0,0 +1,635 @@
1
+ # Multi-vector RAG: Building Multimodal Document Search Systems With Weaviate
2
+
3
+ ## Overview
4
+
5
+ This cookbook provides instructions for implementing a Multimodal Retrieval-Augmented Generation (RAG) system over PDF document collections using Weaviate Embeddings multimodal model for embeddings and Ollama with a Vision Language Model (VLM) for generation.
6
+
7
+ Weaviate Embeddings handles all embedding generation server-side — no local GPU or model downloads required. Simply upload document images as base64 blobs and Weaviate generates multi-vector embeddings automatically.
8
+
9
+ ### Architecture
10
+
11
+ A multimodal RAG system consists of two main pipelines:
12
+
13
+ **Ingestion Pipeline:**
14
+ - Documents (PDFs, images) are converted to page images
15
+ - Images are uploaded as base64 blobs to Weaviate
16
+ - Weaviate Embeddings generates multi-vector embeddings server-side using `ModernVBERT/colmodernvbert`
17
+ - Embeddings are stored in the vector index automatically
18
+
19
+ **Query Pipeline:**
20
+ - Text queries are sent to Weaviate, which embeds them server-side
21
+ - Relevant documents are retrieved using similarity search (MaxSim)
22
+ - Retrieved document images are passed to a Vision Language Model (VLM) running on Ollama with the query
23
+ - The VLM generates a natural language response based on visual and textual context
24
+
25
+
26
+
27
+ **Requirements:**
28
+ - Weaviate Cloud instance (Weaviate Embeddings is cloud-only)
29
+ - Python 3.11 or higher
30
+ - `uv` package manager ([installation guide](https://docs.astral.sh/uv/getting-started/installation/))
31
+ - [Ollama](https://ollama.com/) installed locally for VLM generation
32
+
33
+ ## Workflow Instructions
34
+
35
+ ### Step 1: Setup Project and Install Dependencies
36
+
37
+ #### Project Bootstrap
38
+
39
+ Initialize a new project with `uv`:
40
+
41
+ ```bash
42
+ uv init multimodal-rag
43
+ cd multimodal-rag
44
+ uv venv
45
+ ```
46
+
47
+ **Install uv if needed:**
48
+ ```bash
49
+ # macOS/Linux
50
+ curl -LsSf https://astral.sh/uv/install.sh -o /tmp/uv-install.sh
51
+ less /tmp/uv-install.sh
52
+ sh /tmp/uv-install.sh
53
+
54
+ # Or with pip
55
+ pip install uv
56
+
57
+ # Or with Homebrew
58
+ brew install uv
59
+ ```
60
+
61
+ #### Install Core Dependencies
62
+
63
+ Install required libraries using `uv`:
64
+
65
+ ```bash
66
+ uv add weaviate-client
67
+ ```
68
+
69
+ **Package breakdown:**
70
+ - `weaviate-client`: Python client for Weaviate vector database (v4.x) — Weaviate Embeddings handles all embedding generation
71
+
72
+ #### Additional Dependencies (Install as Needed)
73
+
74
+ ```bash
75
+ # For loading Hugging Face datasets
76
+ uv add datasets
77
+
78
+ # For PDF processing (pdf2image requires poppler to be installed!)
79
+ uv add pdf2image pillow
80
+
81
+ # For VLM generation via Ollama
82
+ uv add ollama
83
+ ```
84
+
85
+ ### Step 2: Prepare Your Document Dataset
86
+
87
+ #### Option A: Load Existing Dataset
88
+ If using a pre-existing dataset:
89
+ - Use Hugging Face `datasets` library
90
+ - Ensure dataset contains document images or can be converted to images
91
+ - Verify image format compatibility (JPEG, PNG)
92
+
93
+ #### Option B: Process Your Own Documents
94
+ For custom document collections:
95
+ 1. Convert documents to images (if not already images)
96
+ - PDFs: Use `pdf2image` or similar libraries
97
+ - Office documents: Convert to PDF first, then to images
98
+ 2. Organize with metadata (document ID, page number, title, etc.)
99
+ 3. Store in a format suitable for batch processing
100
+
101
+ **Recommended structure:**
102
+ ```python
103
+ {
104
+ "document_id": str,
105
+ "page_number": int,
106
+ "image": PIL.Image,
107
+ "metadata": dict # title, author, date, etc.
108
+ }
109
+ ```
110
+
111
+ ### Step 3: Configure Weaviate Collection
112
+
113
+ #### Weaviate Connection
114
+
115
+ ```python
116
+ import os
117
+ import weaviate
118
+ from weaviate.classes.init import Auth
119
+
120
+ WEAVIATE_URL = os.getenv("WEAVIATE_URL")
121
+ WEAVIATE_API_KEY = os.getenv("WEAVIATE_API_KEY")
122
+
123
+ client = weaviate.connect_to_weaviate_cloud(
124
+ cluster_url=WEAVIATE_URL,
125
+ auth_credentials=Auth.api_key(WEAVIATE_API_KEY),
126
+ )
127
+ ```
128
+
129
+ #### Create Collection Schema
130
+
131
+ Define a collection with `multi2vec_weaviate` vectorizer for automatic multimodal embeddings:
132
+
133
+ ```python
134
+ from weaviate.classes.config import Configure, Property, DataType
135
+
136
+ collection_name = "PDFDocuments" # Use a descriptive name for your use case
137
+
138
+ collection = client.collections.create(
139
+ name=collection_name,
140
+ properties=[
141
+ Property(name="doc_page", data_type=DataType.BLOB),
142
+ Property(name="page_id", data_type=DataType.INT),
143
+ Property(name="document_id", data_type=DataType.TEXT),
144
+ Property(name="page_number", data_type=DataType.INT),
145
+ Property(name="title", data_type=DataType.TEXT),
146
+ # Add other metadata properties as needed
147
+ ],
148
+ vector_config=[
149
+ Configure.MultiVectors.multi2vec_weaviate(
150
+ name="doc_vector"
151
+ image_field="doc_page",
152
+ model="ModernVBERT/colmodernvbert",
153
+ encoding=Configure.VectorIndex.MultiVector.Encoding.muvera(
154
+ ksim=4,
155
+ dprojections=16,
156
+ repetitions=20,
157
+ ),
158
+ )
159
+ ],
160
+ )
161
+ ```
162
+
163
+ **Key Configuration Options:**
164
+ - **`doc_page`**: BLOB property that holds base64-encoded page images — the vectorizer reads this field
165
+ - **`image_field`**: Must match the BLOB property name (`"doc_page"`)
166
+ - **`model`**: `ModernVBERT/colmodernvbert` — 250M parameter late-interaction vision-language encoder, fine-tuned for visual document retrieval
167
+ - **MUVERA encoding**: Compresses multi-vectors into efficient single vectors while preserving retrieval quality
168
+ - `ksim`: Number of similar vectors to consider (default: 4)
169
+ - `dprojections`: Number of projection dimensions (default: 16)
170
+ - `repetitions`: Number of encoding repetitions (default: 20)
171
+ - **Properties**: Add all metadata you want to filter or display
172
+
173
+ **Without MUVERA encoding** (uses more memory but preserves full multi-vector representation):
174
+ ```python
175
+ vector_config=[
176
+ Configure.MultiVectors.multi2vec_weaviate(
177
+ name="doc_vector",
178
+ image_field="doc_page",
179
+ model="ModernVBERT/colmodernvbert",
180
+ )
181
+ ],
182
+ ```
183
+
184
+ ### Step 4: Index Documents
185
+
186
+ #### Convert Images to Base64
187
+
188
+ ```python
189
+ import base64
190
+ from io import BytesIO
191
+
192
+ def image_to_base64(image):
193
+ """Convert a PIL Image to a base64-encoded string.
194
+
195
+ Args:
196
+ image: PIL.Image object
197
+
198
+ Returns:
199
+ Base64-encoded string of the JPEG image
200
+ """
201
+ buffer = BytesIO()
202
+ image.save(buffer, format="JPEG")
203
+ return base64.b64encode(buffer.getvalue()).decode("utf-8")
204
+ ```
205
+
206
+ #### Batch Import
207
+
208
+ Weaviate Embeddings generates embeddings server-side during import — no local model needed:
209
+
210
+ ```python
211
+ collection = client.collections.get(collection_name)
212
+
213
+ with collection.batch.dynamic() as batch:
214
+ for idx, document in enumerate(your_document_dataset):
215
+ # Convert image to base64
216
+ img_base64 = image_to_base64(document["image"])
217
+
218
+ # Add object to batch — Weaviate generates embeddings automatically
219
+ batch.add_object(
220
+ properties={
221
+ "doc_page": img_base64,
222
+ "page_id": document["page_id"],
223
+ "document_id": document["document_id"],
224
+ "page_number": document["page_number"],
225
+ "title": document.get("title", ""),
226
+ # Add other properties from your dataset
227
+ },
228
+ )
229
+
230
+ # Progress tracking
231
+ if idx % 25 == 0:
232
+ print(f"Indexed {idx+1}/{len(your_document_dataset)} documents")
233
+
234
+ # Clean up dataset if memory is limited
235
+ del your_document_dataset
236
+
237
+ print(f"Total documents indexed: {len(collection)}")
238
+ ```
239
+
240
+ **Performance Tips:**
241
+ - **Batch size**: Weaviate automatically manages batch size with `dynamic()` mode
242
+ - **No local GPU needed**: Weaviate Embeddings runs server-side
243
+ - **Image format**: JPEG is recommended for smaller payload sizes
244
+ - **Large datasets**: Process in chunks, delete intermediate variables to free memory
245
+
246
+ ### Step 5: Implement Retrieval
247
+
248
+ #### Basic Query Function
249
+
250
+ Weaviate handles query embedding automatically — just pass text:
251
+
252
+ ```python
253
+ from weaviate.classes.query import MetadataQuery
254
+
255
+ def search_documents(query_text, limit=3):
256
+ """Search for documents using Weaviate Embeddings multimodal model.
257
+
258
+ Args:
259
+ query_text: Natural language query string
260
+ limit: Number of results to return (default: 3)
261
+
262
+ Returns:
263
+ List of dicts with document properties, similarity scores, and base64 images
264
+ """
265
+ collection = client.collections.get(collection_name)
266
+
267
+ # Search — Weaviate embeds the query server-side
268
+ # Include doc_page in return_properties to get the base64-encoded image blob
269
+ response = collection.query.near_text(
270
+ query=query_text,
271
+ limit=limit,
272
+ return_properties=["page_id", "document_id", "page_number", "title", "doc_page"],
273
+ return_metadata=MetadataQuery(distance=True),
274
+ )
275
+
276
+ # Process and format results
277
+ results = []
278
+ for i, obj in enumerate(response.objects):
279
+ props = obj.properties
280
+ results.append({
281
+ "rank": i + 1,
282
+ "page_id": props["page_id"],
283
+ "document_id": props["document_id"],
284
+ "page_number": props["page_number"],
285
+ "title": props["title"],
286
+ "distance": obj.metadata.distance,
287
+ "image_base64": props["doc_page"], # Already base64-encoded
288
+ })
289
+
290
+ return results
291
+
292
+ # Example usage
293
+ query = "How does DeepSeek-V2 compare against the LLaMA family of LLMs?"
294
+ results = search_documents(query, limit=3)
295
+
296
+ for result in results:
297
+ print(f"{result['rank']}) Distance: {result['distance']:.4f}, "
298
+ f"Title: \"{result['title']}\", Page: {result['page_number']}")
299
+ ```
300
+
301
+ **Query Parameters:**
302
+ - **`limit`**: Number of results (1-10 recommended, consider VLM memory limits)
303
+ - **`return_metadata`**: Include `distance=True` to get similarity scores
304
+ - **Filters**: Add `filters=` for metadata filtering (see below)
305
+
306
+ **Accessing the image field in results:**
307
+ BLOB properties like `doc_page` are not returned by default when used as the `image_field` property of the `multi2vec_weaviate` vectorizer. You must request them explicitly via `return_properties` (as shown in `search_documents()` above). The returned blob is base64-encoded. The Ollama Python SDK's `images` key accepts raw `bytes` or path-like strings (not base64 strings), so decode with `base64.b64decode()` before passing to Ollama (as shown in `OllamaVLM.generate_answer()`).
308
+
309
+ #### Metadata Filtering
310
+
311
+ Add filters to narrow search scope by document properties:
312
+
313
+ ```python
314
+ import weaviate.classes.config as wc
315
+
316
+ # Example: Filter by document ID
317
+ response = collection.query.near_text(
318
+ query="query text",
319
+ limit=5,
320
+ filters=wc.Filter.by_property("document_id").equal("paper_123"),
321
+ )
322
+
323
+ # Example: Filter by page range
324
+ response = collection.query.near_text(
325
+ query="query text",
326
+ limit=5,
327
+ filters=wc.Filter.by_property("page_number").less_than(10),
328
+ )
329
+
330
+ # Example: Combine multiple filters
331
+ from weaviate.classes.query import Filter
332
+
333
+ response = collection.query.near_text(
334
+ query="query text",
335
+ limit=5,
336
+ filters=(
337
+ Filter.by_property("document_id").equal("paper_123") &
338
+ Filter.by_property("page_number").less_than(10)
339
+ ),
340
+ )
341
+ ```
342
+
343
+ #### Hybrid Search
344
+
345
+ Combine vector search with BM25 keyword search:
346
+
347
+ ```python
348
+ # Hybrid search: vector + keyword (Weaviate handles embedding)
349
+ response = collection.query.hybrid(
350
+ query="query text",
351
+ alpha=0.7, # 0.0=keyword only, 0.5=balanced, 1.0=vector only
352
+ limit=5,
353
+ )
354
+ ```
355
+
356
+ **When to use hybrid search:**
357
+ - When exact keyword matches are important (e.g., searching for specific terms, IDs)
358
+ - To combine semantic understanding with exact text matching (BM25)
359
+ - Adjust `alpha` based on whether you prioritize semantic vs. keyword matching
360
+
361
+ ### Step 6: Extend to Full RAG with a Vision Language Model
362
+
363
+ #### About Ollama
364
+
365
+ [Ollama](https://ollama.com/) makes it easy to run vision language models locally with a single command. No manual model downloads, GPU configuration, or dependency management required.
366
+
367
+ **Recommended VLM models for Ollama:**
368
+ - `qwen3-vl:4b`: ~4 GB, good for limited hardware
369
+ - `qwen3-vl:8b`: ~8 GB, better quality
370
+ - `qwen3-vl:32b`: ~32 GB, highest quality
371
+ - `gemma3`: Google's multimodal model, available in 4B/12B/27B sizes
372
+ - `llava`: LLaVA model, lightweight and fast
373
+
374
+ #### Install Ollama and Pull a Model
375
+
376
+ ```bash
377
+ # Install Ollama (macOS/Linux)
378
+ curl -fsSL https://ollama.com/install.sh -o /tmp/ollama-install.sh
379
+ less /tmp/ollama-install.sh
380
+ sh /tmp/ollama-install.sh
381
+
382
+ # Or on macOS with Homebrew
383
+ brew install ollama
384
+
385
+ # Pull a vision language model
386
+ ollama pull qwen3-vl:4b
387
+ ```
388
+
389
+ Verify the model is available:
390
+ ```bash
391
+ ollama list
392
+ ```
393
+
394
+ #### Implement Ollama VLM Wrapper
395
+
396
+ ```python
397
+ import base64
398
+ import ollama
399
+
400
+ class OllamaVLM:
401
+ def __init__(self, model_name="qwen3-vl:4b"):
402
+ """Initialize with an Ollama vision model name.
403
+
404
+ Args:
405
+ model_name: Ollama model tag (must support vision)
406
+ """
407
+ self.model_name = model_name
408
+
409
+ def generate_answer(self, query, images_base64, max_tokens=128):
410
+ """Generate text response based on query and retrieved document images.
411
+
412
+ Args:
413
+ query: String text query
414
+ images_base64: List of base64-encoded image strings (as returned by Weaviate)
415
+ max_tokens: Maximum tokens to generate (default: 128)
416
+
417
+ Returns:
418
+ Generated text answer as string
419
+ """
420
+ # The Ollama SDK "images" key accepts bytes or path-like strings,
421
+ # so decode the base64 strings from Weaviate into raw bytes
422
+ images_bytes = [base64.b64decode(img) for img in images_base64]
423
+
424
+ response = ollama.chat(
425
+ model=self.model_name,
426
+ messages=[{
427
+ "role": "user",
428
+ "content": query,
429
+ "images": images_bytes,
430
+ }],
431
+ options={"num_predict": max_tokens},
432
+ )
433
+
434
+ return response["message"]["content"]
435
+
436
+ # Instantiate the VLM
437
+ vlm = OllamaVLM(model_name="qwen3-vl:4b")
438
+ ```
439
+
440
+ #### Complete RAG Pipeline
441
+
442
+ ```python
443
+ def multimodal_rag(query, num_documents=3, max_tokens=128):
444
+ """Complete multimodal RAG pipeline using Weaviate Embeddings + Ollama VLM.
445
+
446
+ Args:
447
+ query: Natural language question
448
+ num_documents: Number of documents to retrieve (1-3 recommended)
449
+ max_tokens: Maximum tokens for VLM response
450
+
451
+ Returns:
452
+ Dict with query, answer, sources, and metadata
453
+ """
454
+ # Step 1: Retrieve relevant documents (Weaviate handles embedding)
455
+ print(f"Searching for: {query}")
456
+ retrieved_docs = search_documents(query, limit=num_documents)
457
+
458
+ # Display retrieved sources
459
+ print(f"\nRetrieved {len(retrieved_docs)} documents:")
460
+ for doc in retrieved_docs:
461
+ print(f" - {doc['title']}, Page {doc['page_number']} "
462
+ f"(Distance: {doc['distance']:.4f})")
463
+
464
+ # Step 2: Extract base64 images from results
465
+ context_images = [doc["image_base64"] for doc in retrieved_docs]
466
+
467
+ # Step 3: Generate answer using Ollama VLM
468
+ print(f"\nGenerating answer...")
469
+ answer = vlm.generate_answer(query, context_images, max_tokens=max_tokens)
470
+
471
+ # Step 4: Return structured response
472
+ return {
473
+ "query": query,
474
+ "answer": answer,
475
+ "sources": retrieved_docs,
476
+ "num_sources": len(retrieved_docs)
477
+ }
478
+
479
+ # Example usage
480
+ query = "How does DeepSeek-V2 compare against the LLaMA family of LLMs?"
481
+ result = multimodal_rag(query, num_documents=1, max_tokens=128)
482
+
483
+ print(f"\nQuery: {result['query']}")
484
+ print(f"Answer: {result['answer']}")
485
+ print(f"\nBased on {result['num_sources']} source(s)")
486
+ ```
487
+
488
+ #### Response Citation
489
+
490
+ Include source attribution in generated answers:
491
+
492
+ ```python
493
+ def generate_with_citations(query, retrieved_docs, max_tokens=256):
494
+ """Generate answer with source citations.
495
+
496
+ Args:
497
+ query: User question
498
+ retrieved_docs: List of documents from search_documents()
499
+ max_tokens: Maximum response length
500
+
501
+ Returns:
502
+ Answer string with embedded citations
503
+ """
504
+ # Build source references
505
+ sources_text = "\n".join([
506
+ f"Source {i+1}: \"{doc['title']}\", Page {doc['page_number']}"
507
+ for i, doc in enumerate(retrieved_docs)
508
+ ])
509
+
510
+ # Enhanced prompt with citation instructions
511
+ enhanced_query = f"""{query}
512
+
513
+ Available sources:
514
+ {sources_text}
515
+
516
+ Instructions: Answer the question based on the provided document images.
517
+ Cite sources in your answer using [Source N] notation."""
518
+
519
+ # Generate answer with citations
520
+ answer = vlm.generate_answer(
521
+ enhanced_query,
522
+ [doc["image_base64"] for doc in retrieved_docs],
523
+ max_tokens=max_tokens
524
+ )
525
+
526
+ return answer, retrieved_docs
527
+
528
+ # Example usage
529
+ query = "What is the architecture of GPT-4?"
530
+ answer, sources = generate_with_citations(query, search_documents(query, limit=3))
531
+ print(f"Answer: {answer}\n")
532
+ print("Sources:")
533
+ for src in sources:
534
+ print(f" - {src['title']}, Page {src['page_number']}")
535
+ ```
536
+
537
+ ## Troubleshooting
538
+
539
+ ### Missing Environment Variables
540
+ ```
541
+ Error: WEAVIATE_URL environment variable is not set
542
+ ```
543
+ **Solution:** Set `WEAVIATE_URL` and `WEAVIATE_API_KEY` environment variables. See `environment_requirements.md`.
544
+
545
+ ### Connection Errors
546
+ ```
547
+ WeaviateConnectionError: Failed to connect to Weaviate
548
+ ```
549
+ **Solution:** Verify `WEAVIATE_URL` is correct and your network can reach the Weaviate Cloud instance.
550
+
551
+ ### Ollama Connection Error
552
+ ```
553
+ ConnectionError: Failed to connect to Ollama
554
+ ```
555
+ **Solution:** Make sure Ollama is running. Start it with:
556
+ ```bash
557
+ ollama serve
558
+ ```
559
+
560
+ ### Ollama Model Not Found
561
+ ```
562
+ ollama._types.ResponseError: model 'qwen3-vl:4b' not found
563
+ ```
564
+ **Solution:** Pull the model first:
565
+ ```bash
566
+ ollama pull qwen3-vl:4b
567
+ ```
568
+
569
+ ### Out of Memory (OOM) During VLM Generation
570
+ **Symptoms:** Out of memory errors when generating answers.
571
+
572
+ **Solutions:**
573
+ - Reduce `num_documents` — retrieve fewer documents (even 1 can work well)
574
+ - Reduce `max_tokens` — shorter responses use less memory
575
+ - Use a smaller model variant (`qwen3-vl:4b` instead of `8b`)
576
+ - Use API-based VLMs (GPT-4V, Claude, Gemini) to avoid local resource requirements entirely
577
+
578
+ ### BLOB Property Not Returned in Query Results
579
+ **Symptom:** `doc_page` field is missing from query results.
580
+
581
+ **Solution:** BLOB properties used as `image_field` in `multi2vec_weaviate` are not returned by default. Specify them explicitly:
582
+ ```python
583
+ response = collection.query.near_text(
584
+ query=query_text,
585
+ limit=limit,
586
+ return_properties=["page_id", "document_id", "page_number", "title", "doc_page"],
587
+ )
588
+ ```
589
+
590
+ ### Poppler Not Installed (PDF Processing)
591
+ ```
592
+ Exception: Unable to get page count. Is poppler installed and in PATH?
593
+ ```
594
+ **Solution:** Install poppler for `pdf2image`:
595
+ ```bash
596
+ # macOS
597
+ brew install poppler
598
+
599
+ # Ubuntu/Debian
600
+ sudo apt-get install poppler-utils
601
+ ```
602
+
603
+ ### TypeError: unexpected keyword argument 'image_fields'
604
+ ```
605
+ TypeError: _MultiVectors.multi2vec_weaviate() got an unexpected keyword argument 'image_fields'
606
+ ```
607
+ **Cause:** The parameter is singular, not a list.
608
+
609
+ **Solution:** Use `image_field` (singular) instead of `image_fields`:
610
+ ```python
611
+ Configure.MultiVectors.multi2vec_weaviate(
612
+ name="doc_vector",
613
+ image_field="doc_page",
614
+ ...
615
+ )
616
+ ```
617
+
618
+ ## Done Criteria
619
+
620
+ The implementation is complete when:
621
+ - [ ] Project is initialized with `uv` and all dependencies are installed
622
+ - [ ] Document images are converted and uploaded to a Weaviate collection with `multi2vec_weaviate` vectorizer
623
+ - [ ] The collection uses `ModernVBERT/colmodernvbert` model with MUVERA encoding configured
624
+ - [ ] `search_documents()` returns ranked results with similarity scores for text queries
625
+ - [ ] Ollama with a vision language model generates natural language answers from retrieved document images
626
+ - [ ] The full `multimodal_rag()` pipeline retrieves documents and generates answers end-to-end
627
+
628
+ ## Next Steps
629
+
630
+ - **Add metadata filtering** to narrow search scope by document ID, page range, or other properties
631
+ - **Implement hybrid search** combining vector similarity with BM25 keyword matching for better precision
632
+ - **Add response citations** using `generate_with_citations()` to attribute answers to source documents
633
+ - **Scale the dataset** by processing larger document collections with batch chunking and memory management
634
+ - **Swap in API-based VLMs** (GPT, Claude, Gemini) or other Ollama vision models (`gemma3`, `llava`) as alternatives
635
+ - **Evaluate retrieval quality** by testing queries against known-relevant documents and tuning MUVERA parameters
@@ -0,0 +1,75 @@
1
+ # Project Setup Contract (All Cookbooks)
2
+
3
+ Use this reference before generating any cookbook app.
4
+
5
+ ## Goal
6
+
7
+ Set up a safe default project layout that prevents accidental secret leaks and keeps setup instructions consistent across all cookbooks.
8
+
9
+ ## Required Order
10
+
11
+ 1. Create project directory.
12
+ 2. Initialize git immediately.
13
+ 3. Create `.gitignore` before any local `.env` file.
14
+ 4. Create `.env` from [environment_requirements.md](environment_requirements.md).
15
+ 5. Ask user to fill required values (`WEAVIATE_URL`, `WEAVIATE_API_KEY`) and only the optional keys they need.
16
+
17
+ ## Required Files
18
+
19
+ ### `.gitignore`
20
+
21
+ ```gitignore
22
+ # Python
23
+ __pycache__/
24
+ *.py[cod]
25
+ .venv/
26
+
27
+ # Node
28
+ node_modules/
29
+ .next/
30
+ out/
31
+ dist/
32
+
33
+ # Local env files (never commit secrets)
34
+ .env
35
+ .env.*
36
+ secrets/
37
+
38
+ # Common local artifacts
39
+ .DS_Store
40
+ ```
41
+
42
+ ### `.env`
43
+
44
+ - Use the canonical template as provided in [environment_requirements.md](environment_requirements.md).
45
+ - Keep real `.env` values local only.
46
+
47
+ ## Git Baseline
48
+
49
+ Run these commands in every new cookbook app:
50
+
51
+ ```bash
52
+ git init
53
+ git add .gitignore
54
+ git commit -m "initialize project baseline"
55
+ ```
56
+
57
+ ## Claude Safety Baseline (Recommended)
58
+
59
+ For projects developed with Claude Code, add deny rules for local secret files:
60
+
61
+ ```json
62
+ {
63
+ "permissions": {
64
+ "deny": [
65
+ "Read(./.env)",
66
+ "Read(./.env.*)",
67
+ "Read(./**/.env)",
68
+ "Read(./**/.env.*)",
69
+ "Read(./secrets/**)"
70
+ ]
71
+ }
72
+ }
73
+ ```
74
+
75
+ Save this to `.claude/settings.json` at project root.