Mohamed284 commited on
Commit
a763f2e
·
verified ·
1 Parent(s): 4749eaf

Delete main.ipynb

Browse files
Files changed (1) hide show
  1. main.ipynb +0 -1650
main.ipynb DELETED
@@ -1,1650 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": null,
6
- "metadata": {},
7
- "outputs": [],
8
- "source": [
9
- "# Approach 1: Local Llama2 via Ollama\n",
10
- "\n",
11
- "questions = [\n",
12
- " \"How do coral proteins help make eco-friendly fabrics without dyes?\",\n",
13
- " \"What environmental problems do coral-inspired textiles solve?\",\n",
14
- " \"What is industrial symbiosis and how does the Kalundborg example work?\",\n",
15
- " \"How do Metavision sensors work like human eyes to save energy?\",\n",
16
- " \"How does TISSIUM copy skin proteins for medical adhesives?\",\n",
17
- " \"How does DNA-level design create better fibers inspired by nature?\",\n",
18
- " \"Why is industrial symbiosis hard to implement despite benefits?\",\n",
19
- " \"How can biological systems inspire sustainable manufacturing?\",\n",
20
- " \"What other industries can use protein-based materials like Werewool?\",\n",
21
- " \"How could event-based cameras improve security systems?\",\n",
22
- " \"Design a factory network that works like coral reef partnerships - what features would it need?\"\n",
23
- "]\n",
24
- "\n",
25
- "\n",
26
- "import json\n",
27
- "import pandas as pd\n",
28
- "from langchain_ollama import OllamaLLM, OllamaEmbeddings\n",
29
- "from langchain_community.vectorstores import FAISS\n",
30
- "from langchain_core.prompts import PromptTemplate\n",
31
- "from langchain_core.output_parsers import StrOutputParser\n",
32
- "from operator import itemgetter\n",
33
- "import gradio as gr\n",
34
- "\n",
35
- "# Load and process data\n",
36
- "with open('mini_data.json', 'r', encoding='utf-8') as f:\n",
37
- " data = json.load(f)\n",
38
- "documents = [f\"Source: {item['Source']}\\nApplication: {item['Application']}\\nFunction1: {item['Function1']}\\nStrategy: {item['Strategy']}\" for item in data]\n",
39
- "\n",
40
- "# Local Llama2 setup\n",
41
- "local_model = OllamaLLM(model=\"llama2\")\n",
42
- "local_embeddings = OllamaEmbeddings(model=\"llama2\")\n",
43
- "vectorstore = FAISS.from_texts(documents, local_embeddings)\n",
44
- "retriever = vectorstore.as_retriever()\n",
45
- "\n",
46
- "# RAG pipeline\n",
47
- "template = \"\"\"Answer the question based on the context below. If unsure, reply \"I don't know\".\n",
48
- "Context: {context}\n",
49
- "Question: {question}\"\"\"\n",
50
- "prompt = PromptTemplate.from_template(template)\n",
51
- "local_chain = ({\"context\": itemgetter(\"question\") | retriever, \"question\": itemgetter(\"question\")} \n",
52
- " | prompt | local_model | StrOutputParser())\n",
53
- "\n",
54
- "# Chat interface\n",
55
- "def local_rag(question, history):\n",
56
- " response = local_chain.invoke({\"question\": question})\n",
57
- " history.append((question, response))\n",
58
- " return \"\", history\n",
59
- "\n",
60
- "with gr.Blocks() as local_demo:\n",
61
- " gr.Markdown(\"# Local Llama2 RAG Chatbot\")\n",
62
- " chatbot = gr.Chatbot()\n",
63
- " question = gr.Textbox(label=\"Ask about biomimicry:\")\n",
64
- " question.submit(local_rag, [question, chatbot], [question, chatbot])\n",
65
- " \n",
66
- "local_demo.launch()"
67
- ]
68
- },
69
- {
70
- "cell_type": "code",
71
- "execution_count": null,
72
- "metadata": {},
73
- "outputs": [],
74
- "source": [
75
- "# Approach 2: Llama3.3 via API\n",
76
- "import json\n",
77
- "import gradio as gr\n",
78
- "from openai import OpenAI\n",
79
- "from operator import itemgetter\n",
80
- "\n",
81
- "# API configuration\n",
82
- "api_key = 'd9960fad1d2aaa16167902b0d26e369f'\n",
83
- "base_url = \"https://chat-ai.academiccloud.de/v1\"\n",
84
- "model = \"llama-3.3-70b-instruct\"\n",
85
- "\n",
86
- "# Initialize OpenAI client\n",
87
- "client = OpenAI(api_key=api_key, base_url=base_url)\n",
88
- "\n",
89
- "# Load and process data\n",
90
- "with open('mini_data.json', 'r', encoding='utf-8') as f:\n",
91
- " data = json.load(f)\n",
92
- "documents = [f\"Source: {item['Source']}\\nApplication: {item['Application']}\\nFunction1: {item['Function1']}\\nStrategy: {item['Strategy']}\" for item in data]\n",
93
- "\n",
94
- "def retrieve_context(question):\n",
95
- " \"\"\"Simple keyword-based retrieval since embeddings aren't available\"\"\"\n",
96
- " keywords = set(question.lower().split())\n",
97
- " relevant = []\n",
98
- " for doc in documents:\n",
99
- " if any(keyword in doc.lower() for keyword in keywords):\n",
100
- " relevant.append(doc)\n",
101
- " return \"\\n\\n\".join(relevant[:3]) # Return top 3 matches\n",
102
- "\n",
103
- "def generate_response(question):\n",
104
- " context = retrieve_context(question)\n",
105
- " response = client.chat.completions.create(\n",
106
- " messages=[\n",
107
- " {\"role\": \"system\", \"content\": f\"Answer based on context. If unsure, say 'I don't know'.\\nContext: {context}\"},\n",
108
- " {\"role\": \"user\", \"content\": question}\n",
109
- " ],\n",
110
- " model=model\n",
111
- " )\n",
112
- " return response.choices[0].message.content\n",
113
- "\n",
114
- "# Chat interface\n",
115
- "def cloud_rag(question, history):\n",
116
- " response = generate_response(question)\n",
117
- " history.append((question, response))\n",
118
- " return \"\", history\n",
119
- "\n",
120
- "with gr.Blocks() as demo:\n",
121
- " gr.Markdown(\"# AskNature RAG-based Chatbot\")\n",
122
- " chatbot = gr.Chatbot()\n",
123
- " question = gr.Textbox(label=\"Ask about biomimicry:\")\n",
124
- " question.submit(cloud_rag, [question, chatbot], [question, chatbot])\n",
125
- " \n",
126
- "demo.launch()"
127
- ]
128
- },
129
- {
130
- "cell_type": "code",
131
- "execution_count": null,
132
- "metadata": {},
133
- "outputs": [],
134
- "source": []
135
- },
136
- {
137
- "cell_type": "code",
138
- "execution_count": null,
139
- "metadata": {},
140
- "outputs": [],
141
- "source": [
142
- "# Enhanced Metadata Generation with Rate Control and Incremental Processing\n",
143
- "import json\n",
144
- "import time\n",
145
- "import random\n",
146
- "from typing import Dict, List\n",
147
- "from openai import OpenAI\n",
148
- "from tenacity import retry, stop_after_attempt, wait_random_exponential\n",
149
- "import os\n",
150
- "\n",
151
- "# Initialize OpenAI client\n",
152
- "client = OpenAI(\n",
153
- " api_key= 'd9960fad1d2aaa16167902b0d26e369f', # 'd1c9ed1ca70b9721dee1087d93f9662a',\n",
154
- " base_url=\"https://chat-ai.academiccloud.de/v1\"\n",
155
- ")\n",
156
- "\n",
157
- "@retry(stop=stop_after_attempt(5), wait=wait_random_exponential(min=2, max=60))\n",
158
- "def generate_metadata_tags(strategy_text: str) -> Dict:\n",
159
- " \"\"\"Generate structured metadata with enhanced error handling\"\"\"\n",
160
- " system_prompt = \"\"\"Analyze the technical text and generate structured metadata:\n",
161
- "1. **Technical Concepts** (array, max 5 items): Specific technical terms/methods\n",
162
- "2. **Biological Mechanisms** (array, max 3): Biological processes observed in nature\n",
163
- "3. **Industry Applications** (array, max 3): Practical commercial uses\n",
164
- "4. **Sustainability Impacts** (array, max 2): Environmental benefits\n",
165
- "\n",
166
- "Example Response:\n",
167
- "{\n",
168
- " \"technical_concepts\": [\"protein-based pigmentation\", \"DNA-level fiber design\"],\n",
169
- " \"biological_mechanisms\": [\"coral-algae symbiosis\"],\n",
170
- " \"industry_applications\": [\"textile manufacturing\"],\n",
171
- " \"sustainability_impacts\": [\"reduces chemical waste\"]\n",
172
- "}\"\"\"\n",
173
- "\n",
174
- " response = client.chat.completions.create(\n",
175
- " messages=[\n",
176
- " {\"role\": \"system\", \"content\": system_prompt},\n",
177
- " {\"role\": \"user\", \"content\": strategy_text}\n",
178
- " ],\n",
179
- " model=\"llama-3.3-70b-instruct\",\n",
180
- " temperature=0.1,\n",
181
- " response_format={\"type\": \"json_object\"}\n",
182
- " )\n",
183
- " \n",
184
- " return validate_metadata(json.loads(response.choices[0].message.content))\n",
185
- "\n",
186
- "def validate_metadata(metadata: Dict) -> Dict:\n",
187
- " \"\"\"Ensure metadata structure quality\"\"\"\n",
188
- " required_keys = {\n",
189
- " \"technical_concepts\": list,\n",
190
- " \"biological_mechanisms\": list,\n",
191
- " \"industry_applications\": list,\n",
192
- " \"sustainability_impacts\": list\n",
193
- " }\n",
194
- " \n",
195
- " for key, type_ in required_keys.items():\n",
196
- " if key not in metadata or not isinstance(metadata[key], type_):\n",
197
- " raise ValueError(f\"Invalid metadata format for {key}\")\n",
198
- " \n",
199
- " return metadata\n",
200
- "\n",
201
- "def enhance_dataset(input_file: str, output_file: str):\n",
202
- " \"\"\"Robust incremental metadata enhancement with rate control\"\"\"\n",
203
- " # Load existing enhanced data\n",
204
- " existing_data = []\n",
205
- " existing_hyperlinks = set()\n",
206
- " \n",
207
- " if os.path.exists(output_file):\n",
208
- " with open(output_file, 'r') as f:\n",
209
- " existing_data = json.load(f)\n",
210
- " existing_hyperlinks = {item[\"Hyperlink\"] for item in existing_data if \"Hyperlink\" in item}\n",
211
- " \n",
212
- " # Load input data and filter unprocessed items\n",
213
- " with open(input_file, 'r') as f:\n",
214
- " input_data = json.load(f)\n",
215
- " \n",
216
- " new_items = [item for item in input_data if item.get(\"Hyperlink\") not in existing_hyperlinks]\n",
217
- " \n",
218
- " if not new_items:\n",
219
- " print(\"All items already processed in the enhanced file.\")\n",
220
- " return\n",
221
- " else:\n",
222
- " output_length = len(existing_data)\n",
223
- " input_length = len(input_data)\n",
224
- " print(f\"Processing {len(new_items)} new items... out of {input_length} total\")\n",
225
- " \n",
226
- " results = existing_data.copy()\n",
227
- " error_count = 0\n",
228
- " total_items = len(new_items)\n",
229
- " \n",
230
- " for idx, item in enumerate(new_items):\n",
231
- " try:\n",
232
- " # Enhanced rate control with progressive backoff\n",
233
- " if idx > 0:\n",
234
- " base_delay = min(5 + (idx // 10), 30) # Progressive delay up to 30s\n",
235
- " delay = random.uniform(base_delay, base_delay + 5)\n",
236
- " time.sleep(delay)\n",
237
- " \n",
238
- " # Process item\n",
239
- " metadata = generate_metadata_tags(item[\"Strategy\"])\n",
240
- " enhanced_item = {**item, **metadata}\n",
241
- " results.append(enhanced_item)\n",
242
- " \n",
243
- " # Checkpoint saving\n",
244
- " if (idx + 1) % 5 == 0 or (idx + 1) == total_items:\n",
245
- " with open(output_file, 'w') as f:\n",
246
- " json.dump(results, f, indent=2)\n",
247
- " print(f\"Progress: {idx+1+output_length}/{input_length} items processed\")\n",
248
- " \n",
249
- " except Exception as e:\n",
250
- " error_count += 1\n",
251
- " print(f\"Error processing {item.get('Source', 'Unknown')}: {str(e)}\")\n",
252
- " # results.append(item) # Preserve original data\n",
253
- " \n",
254
- " print(f\"Processing complete. Success rate: {total_items-error_count}/{input_length}\")\n",
255
- "\n",
256
- "# Execute enhancement\n",
257
- "enhance_dataset(\"AskNatureNet_data.json\", \"AskNatureNet_data_enhanced.json\")"
258
- ]
259
- },
260
- {
261
- "cell_type": "code",
262
- "execution_count": null,
263
- "metadata": {},
264
- "outputs": [],
265
- "source": [
266
- "# Optimized RAG System with E5-Mistral Embeddings and Llama3-70B Generation\n",
267
- " \n",
268
- "import json\n",
269
- "import logging\n",
270
- "import re\n",
271
- "import os\n",
272
- "import pickle\n",
273
- "from typing import List, Tuple, Optional\n",
274
- "import gradio as gr\n",
275
- "from openai import OpenAI\n",
276
- "from functools import lru_cache\n",
277
- "from tenacity import retry, stop_after_attempt, wait_exponential\n",
278
- "from langchain_community.retrievers import BM25Retriever\n",
279
- "from langchain_community.vectorstores import FAISS\n",
280
- "from langchain_core.embeddings import Embeddings\n",
281
- "from langchain_core.documents import Document\n",
282
- "from collections import defaultdict\n",
283
- "import hashlib\n",
284
- "from tqdm import tqdm # For progress tracking\n",
285
- "from dotenv import load_dotenv\n",
286
- "load_dotenv()\n",
287
- "\n",
288
- "# --- Configuration ---\n",
289
- "FAISS_INDEX_PATH = \"faiss_index\"\n",
290
- "BM25_INDEX_PATH = \"bm25_index.pkl\"\n",
291
- "CACHE_VERSION = \"v1\" # Increment when data format changes\n",
292
- "embedding_model = \"e5-mistral-7b-instruct\"\n",
293
- "generation_model = \"meta-llama-3-70b-instruct\"\n",
294
- "data_file_name = \"AskNatureNet_data_enhanced.json\"\n",
295
- "API_CONFIG = {\n",
296
- " \"api_key\": os.getenv(\"OPENAI_API_KEY\"),\n",
297
- " \"base_url\": \"https://chat-ai.academiccloud.de/v1\"\n",
298
- "}\n",
299
- "CHUNK_SIZE = 800\n",
300
- "OVERLAP = 200\n",
301
- "EMBEDDING_BATCH_SIZE = 32 # Batch size for embedding API calls\n",
302
- "\n",
303
- "# Initialize clients\n",
304
- "client = OpenAI(**API_CONFIG)\n",
305
- "logging.basicConfig(level=logging.INFO)\n",
306
- "logger = logging.getLogger(__name__)\n",
307
- "\n",
308
- "# --- Helper Functions ---\n",
309
- "def get_data_hash(file_path: str) -> str:\n",
310
- " \"\"\"Generate hash of data file for cache validation\"\"\"\n",
311
- " with open(file_path, \"rb\") as f:\n",
312
- " return hashlib.md5(f.read()).hexdigest()\n",
313
- "\n",
314
- "# --- Custom Embedding Handler with Progress Tracking ---\n",
315
- "class MistralEmbeddings(Embeddings):\n",
316
- " \"\"\"E5-Mistral-7B embedding adapter with error handling and progress tracking\"\"\"\n",
317
- " def embed_documents(self, texts: List[str]) -> List[List[float]]:\n",
318
- " embeddings = []\n",
319
- " try:\n",
320
- " # Process in batches with progress tracking\n",
321
- " for i in tqdm(range(0, len(texts), EMBEDDING_BATCH_SIZE), desc=\"Embedding Progress\"):\n",
322
- " batch = texts[i:i + EMBEDDING_BATCH_SIZE]\n",
323
- " response = client.embeddings.create(\n",
324
- " input=batch,\n",
325
- " model=embedding_model,\n",
326
- " encoding_format=\"float\"\n",
327
- " )\n",
328
- " embeddings.extend([e.embedding for e in response.data])\n",
329
- " return embeddings\n",
330
- " except Exception as e:\n",
331
- " logger.error(f\"Embedding Error: {str(e)}\")\n",
332
- " return [[] for _ in texts]\n",
333
- "\n",
334
- " def embed_query(self, text: str) -> List[float]:\n",
335
- " return self.embed_documents([text])[0]\n",
336
- "\n",
337
- "# --- Data Processing with Cache Validation ---\n",
338
- "def load_and_chunk_data(file_path: str) -> List[Document]:\n",
339
- " \"\"\"Enhanced chunking with metadata preservation\"\"\"\n",
340
- " current_hash = get_data_hash(file_path)\n",
341
- " cache_file = f\"documents_{CACHE_VERSION}_{current_hash}.pkl\"\n",
342
- " \n",
343
- " if os.path.exists(cache_file):\n",
344
- " logger.info(\"Loading cached documents\")\n",
345
- " with open(cache_file, \"rb\") as f:\n",
346
- " return pickle.load(f)\n",
347
- " \n",
348
- " with open(file_path, 'r', encoding='utf-8') as f:\n",
349
- " data = json.load(f)\n",
350
- " \n",
351
- " documents = []\n",
352
- " for item in tqdm(data, desc=\"Chunking Progress\"):\n",
353
- " base_content = f\"\"\"Source: {item['Source']}\n",
354
- "Application: {item['Application']}\n",
355
- "Functions: {', '.join(filter(None, [item.get('Function1'), item.get('Function2')]))}\n",
356
- "Technical Concepts: {', '.join(item['technical_concepts'])}\n",
357
- "Biological Mechanisms: {', '.join(item['biological_mechanisms'])}\"\"\"\n",
358
- " \n",
359
- " strategy = item['Strategy']\n",
360
- " for i in range(0, len(strategy), CHUNK_SIZE - OVERLAP):\n",
361
- " chunk = strategy[i:i + CHUNK_SIZE]\n",
362
- " documents.append(Document(\n",
363
- " page_content=f\"{base_content}\\nStrategy Excerpt:\\n{chunk}\",\n",
364
- " metadata={\n",
365
- " \"source\": item[\"Source\"],\n",
366
- " \"application\": item[\"Application\"],\n",
367
- " \"technical_concepts\": item[\"technical_concepts\"],\n",
368
- " \"sustainability_impacts\": item[\"sustainability_impacts\"],\n",
369
- " \"hyperlink\": item[\"Hyperlink\"],\n",
370
- " \"chunk_id\": f\"{item['Source']}-{len(documents)+1}\"\n",
371
- " }\n",
372
- " ))\n",
373
- " \n",
374
- " with open(cache_file, \"wb\") as f:\n",
375
- " pickle.dump(documents, f)\n",
376
- " return documents\n",
377
- "\n",
378
- "# --- Optimized Retrieval System ---\n",
379
- "class EnhancedRetriever:\n",
380
- " \"\"\"Hybrid retriever with persistent caching\"\"\"\n",
381
- " def __init__(self, documents: List[Document]):\n",
382
- " self.documents = documents\n",
383
- " self.bm25 = self._init_bm25()\n",
384
- " self.vector_store = self._init_faiss()\n",
385
- " self.vector_retriever = self.vector_store.as_retriever(search_kwargs={\"k\": 3})\n",
386
- "\n",
387
- " def _init_bm25(self) -> BM25Retriever:\n",
388
- " cache_key = f\"{BM25_INDEX_PATH}_{get_data_hash(data_file_name)}\"\n",
389
- " if os.path.exists(cache_key):\n",
390
- " logger.info(\"Loading cached BM25 index\")\n",
391
- " with open(cache_key, \"rb\") as f:\n",
392
- " return pickle.load(f)\n",
393
- " \n",
394
- " logger.info(\"Building new BM25 index\")\n",
395
- " retriever = BM25Retriever.from_documents(self.documents)\n",
396
- " retriever.k = 5\n",
397
- " with open(cache_key, \"wb\") as f:\n",
398
- " pickle.dump(retriever, f)\n",
399
- " return retriever\n",
400
- "\n",
401
- " def _init_faiss(self) -> FAISS:\n",
402
- " cache_key = f\"{FAISS_INDEX_PATH}_{get_data_hash(data_file_name)}\"\n",
403
- " if os.path.exists(cache_key):\n",
404
- " logger.info(\"Loading cached FAISS index\")\n",
405
- " return FAISS.load_local(\n",
406
- " cache_key,\n",
407
- " MistralEmbeddings(),\n",
408
- " allow_dangerous_deserialization=True\n",
409
- " )\n",
410
- " \n",
411
- " logger.info(\"Building new FAISS index\")\n",
412
- " vector_store = FAISS.from_documents(self.documents, MistralEmbeddings())\n",
413
- " vector_store.save_local(cache_key)\n",
414
- " return vector_store\n",
415
- "\n",
416
- " @lru_cache(maxsize=500)\n",
417
- " def retrieve(self, query: str) -> str:\n",
418
- " try:\n",
419
- " processed_query = self._preprocess_query(query)\n",
420
- " expanded_query = self._hyde_expansion(processed_query)\n",
421
- " \n",
422
- " bm25_results = self.bm25.invoke(processed_query)\n",
423
- " vector_results = self.vector_retriever.invoke(processed_query)\n",
424
- " expanded_results = self.bm25.invoke(expanded_query)\n",
425
- " \n",
426
- " fused_results = self._fuse_results([bm25_results, vector_results, expanded_results])\n",
427
- " return self._format_context(fused_results[:5])\n",
428
- " except Exception as e:\n",
429
- " logger.error(f\"Retrieval Error: {str(e)}\")\n",
430
- " return \"\"\n",
431
- "\n",
432
- " def _preprocess_query(self, query: str) -> str:\n",
433
- " return query.lower().strip()\n",
434
- "\n",
435
- " @lru_cache(maxsize=500)\n",
436
- " def _hyde_expansion(self, query: str) -> str:\n",
437
- " try:\n",
438
- " response = client.chat.completions.create(\n",
439
- " model=generation_model,\n",
440
- " messages=[{\n",
441
- " \"role\": \"user\",\n",
442
- " \"content\": f\"Generate a technical draft about biomimicry for: {query}\\nInclude domain-specific terms.\"\n",
443
- " }],\n",
444
- " temperature=0.5,\n",
445
- " max_tokens=200\n",
446
- " )\n",
447
- " return response.choices[0].message.content\n",
448
- " except Exception as e:\n",
449
- " logger.error(f\"HyDE Error: {str(e)}\")\n",
450
- " return query\n",
451
- "\n",
452
- " def _fuse_results(self, result_sets: List[List[Document]]) -> List[Document]:\n",
453
- " fused_scores = defaultdict(float)\n",
454
- " for docs in result_sets:\n",
455
- " for rank, doc in enumerate(docs, 1):\n",
456
- " fused_scores[doc.metadata[\"chunk_id\"]] += 1 / (rank + 60)\n",
457
- " \n",
458
- " seen = set()\n",
459
- " return [\n",
460
- " doc for doc in sorted(\n",
461
- " (doc for docs in result_sets for doc in docs),\n",
462
- " key=lambda x: fused_scores[x.metadata[\"chunk_id\"]],\n",
463
- " reverse=True\n",
464
- " ) if not (doc.metadata[\"chunk_id\"] in seen or seen.add(doc.metadata[\"chunk_id\"]))\n",
465
- " ]\n",
466
- "\n",
467
- " def _format_context(self, docs: List[Document]) -> str:\n",
468
- " context = []\n",
469
- " for doc in docs:\n",
470
- " context_str = f\"\"\"**Source**: [{doc.metadata['source']}]({doc.metadata['hyperlink']})\n",
471
- " **Application**: {doc.metadata['application']}\n",
472
- " **Key Concepts**: {', '.join(doc.metadata['technical_concepts'])}\n",
473
- " **Strategy Excerpt**:\\n{doc.page_content.split('Strategy Excerpt:')[-1].strip()}\"\"\"\n",
474
- " context.append(context_str)\n",
475
- " return \"\\n\\n---\\n\\n\".join(context)\n",
476
- "\n",
477
- "# --- Generation System ---\n",
478
- "SYSTEM_PROMPT = \"\"\"**Biomimicry Expert Guidelines**\n",
479
- "1. Base answers strictly on context\n",
480
- "2. Cite sources as [Source]\n",
481
- "3. **Bold** technical terms\n",
482
- "4. Include reference links\n",
483
- "\n",
484
- "Context: {context}\"\"\"\n",
485
- "\n",
486
- "@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=20))\n",
487
- "def get_ai_response(query: str, context: str) -> str:\n",
488
- " try:\n",
489
- " response = client.chat.completions.create(\n",
490
- " model=generation_model,\n",
491
- " messages=[\n",
492
- " {\"role\": \"system\", \"content\": SYSTEM_PROMPT.format(context=context)},\n",
493
- " {\"role\": \"user\", \"content\": f\"Question: {query}\\nProvide a detailed technical answer:\"}\n",
494
- " ],\n",
495
- " temperature=0.4,\n",
496
- " max_tokens=2000 # Increased max_tokens\n",
497
- " )\n",
498
- " logger.info(f\"Raw Response: {response.choices[0].message.content}\") # Log raw response\n",
499
- " return _postprocess_response(response.choices[0].message.content)\n",
500
- " except Exception as e:\n",
501
- " logger.error(f\"Generation Error: {str(e)}\")\n",
502
- " return \"I'm unable to generate a response right now. Please try again later.\"\n",
503
- "\n",
504
- "def _postprocess_response(response: str) -> str:\n",
505
- " response = re.sub(r\"\\[(.*?)\\]\", r\"[\\1](#)\", response)\n",
506
- " response = re.sub(r\"\\*\\*([\\w-]+)\\*\\*\", r\"**\\1**\", response)\n",
507
- " return response\n",
508
- "\n",
509
- "# --- Optimized Pipeline ---\n",
510
- "documents = load_and_chunk_data(data_file_name)\n",
511
- "retriever = EnhancedRetriever(documents)\n",
512
- "\n",
513
- "def generate_response(question: str) -> str:\n",
514
- " try:\n",
515
- " context = retriever.retrieve(question)\n",
516
- " return get_ai_response(question, context) if context else \"No relevant information found.\"\n",
517
- " except Exception as e:\n",
518
- " logger.error(f\"Pipeline Error: {str(e)}\")\n",
519
- " return \"An error occurred processing your request.\"\n",
520
- "\n",
521
- "# --- Gradio Interface ---\n",
522
- "def chat_interface(question: str, history: List[Tuple[str, str]]):\n",
523
- " response = generate_response(question)\n",
524
- " return \"\", history + [(question, response)]\n",
525
- "\n",
526
- "with gr.Blocks(title=\"AskNature BioRAG Expert\", theme=gr.themes.Soft()) as demo:\n",
527
- " gr.Markdown(\"# 🌿 AskNature RAG-based Chatbot \")\n",
528
- " with gr.Row():\n",
529
- " chatbot = gr.Chatbot(label=\"Dialogue History\", height=500)\n",
530
- " with gr.Row():\n",
531
- " question = gr.Textbox(placeholder=\"Ask about biomimicry (e.g. 'How does Werewool use coral proteins to make fibers?')\",\n",
532
- " label=\"Inquiry\", scale=4)\n",
533
- " clear_btn = gr.Button(\"Clear History\", variant=\"secondary\")\n",
534
- " \n",
535
- " gr.Markdown(\"\"\"\n",
536
- " <div style=\"text-align: center; color: #4a7c59;\">\n",
537
- " <small>Powered by AskNature's Database | \n",
538
- " Explore nature's blueprints at <a href=\"https://asknature.org\">asknature.org</a></small>\n",
539
- " </div>\"\"\")\n",
540
- " question.submit(chat_interface, [question, chatbot], [question, chatbot])\n",
541
- " clear_btn.click(lambda: [], None, chatbot)\n",
542
- "\n",
543
- "if __name__ == \"__main__\":\n",
544
- " demo.launch(show_error=True)"
545
- ]
546
- },
547
- {
548
- "cell_type": "code",
549
- "execution_count": null,
550
- "metadata": {},
551
- "outputs": [],
552
- "source": [
553
- "from dotenv import load_dotenv\n",
554
- "import os\n",
555
- "load_dotenv()\n",
556
- "print(os.getenv(\"API_KEY\"))"
557
- ]
558
- },
559
- {
560
- "cell_type": "code",
561
- "execution_count": null,
562
- "metadata": {},
563
- "outputs": [],
564
- "source": []
565
- },
566
- {
567
- "cell_type": "code",
568
- "execution_count": null,
569
- "metadata": {},
570
- "outputs": [],
571
- "source": []
572
- },
573
- {
574
- "cell_type": "code",
575
- "execution_count": null,
576
- "metadata": {},
577
- "outputs": [],
578
- "source": []
579
- },
580
- {
581
- "cell_type": "code",
582
- "execution_count": null,
583
- "metadata": {},
584
- "outputs": [],
585
- "source": []
586
- },
587
- {
588
- "cell_type": "code",
589
- "execution_count": null,
590
- "metadata": {},
591
- "outputs": [],
592
- "source": []
593
- },
594
- {
595
- "cell_type": "code",
596
- "execution_count": null,
597
- "metadata": {},
598
- "outputs": [],
599
- "source": [
600
- "# Optimized RAG System with E5-Mistral Embeddings and Gemini Flash Generation\n",
601
- "\n",
602
- "import json\n",
603
- "import logging\n",
604
- "import re\n",
605
- "import os\n",
606
- "import pickle\n",
607
- "from typing import List, Tuple, Optional\n",
608
- "import gradio as gr\n",
609
- "from openai import OpenAI\n",
610
- "import google.generativeai as genai\n",
611
- "from functools import lru_cache\n",
612
- "from tenacity import retry, stop_after_attempt, wait_exponential\n",
613
- "from langchain_community.retrievers import BM25Retriever\n",
614
- "from langchain_community.vectorstores import FAISS\n",
615
- "from langchain_core.embeddings import Embeddings\n",
616
- "from langchain_core.documents import Document\n",
617
- "from collections import defaultdict\n",
618
- "import hashlib\n",
619
- "from tqdm import tqdm\n",
620
- "\n",
621
- "from dotenv import load_dotenv\n",
622
- "load_dotenv()\n",
623
- "\n",
624
- "# --- Configuration ---\n",
625
- "FAISS_INDEX_PATH = \"faiss_index\"\n",
626
- "BM25_INDEX_PATH = \"bm25_index.pkl\"\n",
627
- "CACHE_VERSION = \"v1\"\n",
628
- "embedding_model = \"e5-mistral-7b-instruct\"\n",
629
- "generation_model = \"gemini-2.0-flash\"\n",
630
- "data_file_name = \"AskNatureNet_data_enhanced.json\"\n",
631
- "\n",
632
- "# Initialize clients\n",
633
- "OPENAI_API_CONFIG = {\n",
634
- " \"api_key\": os.getenv(\"OPENAI_API_KEY\"),\n",
635
- " \"base_url\": \"https://chat-ai.academiccloud.de/v1\"\n",
636
- "}\n",
637
- "client = OpenAI(**OPENAI_API_CONFIG)\n",
638
- "\n",
639
- "# Configure Gemini\n",
640
- "genai.configure(api_key=os.getenv(\"GEMINI_API_KEY\"))\n",
641
- "gemini_model = genai.GenerativeModel(generation_model)\n",
642
- "\n",
643
- "logging.basicConfig(level=logging.INFO)\n",
644
- "logger = logging.getLogger(__name__)\n",
645
- "\n",
646
- "# --- Helper Functions ---\n",
647
- "def get_data_hash(file_path: str) -> str:\n",
648
- " \"\"\"Generate hash of data file for cache validation\"\"\"\n",
649
- " with open(file_path, \"rb\") as f:\n",
650
- " return hashlib.md5(f.read()).hexdigest()\n",
651
- "\n",
652
- "# --- Custom Embedding Handler ---\n",
653
- "class MistralEmbeddings(Embeddings):\n",
654
- " \"\"\"E5-Mistral-7B embedding adapter\"\"\"\n",
655
- " def embed_documents(self, texts: List[str]) -> List[List[float]]:\n",
656
- " embeddings = []\n",
657
- " try:\n",
658
- " for i in tqdm(range(0, len(texts), EMBEDDING_BATCH_SIZE), desc=\"Embedding Progress\"):\n",
659
- " batch = texts[i:i + EMBEDDING_BATCH_SIZE]\n",
660
- " response = client.embeddings.create(\n",
661
- " input=batch,\n",
662
- " model=embedding_model,\n",
663
- " encoding_format=\"float\"\n",
664
- " )\n",
665
- " embeddings.extend([e.embedding for e in response.data])\n",
666
- " return embeddings\n",
667
- " except Exception as e:\n",
668
- " logger.error(f\"Embedding Error: {str(e)}\")\n",
669
- " return [[] for _ in texts]\n",
670
- " \n",
671
- " def embed_query(self, text: str) -> List[float]:\n",
672
- " return self.embed_documents([text])[0]\n",
673
- "\n",
674
- "# --- Data Processing ---\n",
675
- "def load_and_chunk_data(file_path: str) -> List[Document]:\n",
676
- " \"\"\"Enhanced chunking with metadata preservation\"\"\"\n",
677
- " current_hash = get_data_hash(file_path)\n",
678
- " cache_file = f\"documents_{CACHE_VERSION}_{current_hash}.pkl\"\n",
679
- " \n",
680
- " if os.path.exists(cache_file):\n",
681
- " logger.info(\"Loading cached documents\")\n",
682
- " with open(cache_file, \"rb\") as f:\n",
683
- " return pickle.load(f)\n",
684
- " \n",
685
- " with open(file_path, 'r', encoding='utf-8') as f:\n",
686
- " data = json.load(f)\n",
687
- " \n",
688
- " documents = []\n",
689
- " for item in tqdm(data, desc=\"Chunking Progress\"):\n",
690
- " base_content = f\"\"\"Source: {item['Source']}\n",
691
- "Application: {item['Application']}\n",
692
- "Functions: {', '.join(filter(None, [item.get('Function1'), item.get('Function2')]))}\n",
693
- "Technical Concepts: {', '.join(item['technical_concepts'])}\n",
694
- "Biological Mechanisms: {', '.join(item['biological_mechanisms'])}\"\"\"\n",
695
- " \n",
696
- " strategy = item['Strategy']\n",
697
- " for i in range(0, len(strategy), CHUNK_SIZE - OVERLAP):\n",
698
- " chunk = strategy[i:i + CHUNK_SIZE]\n",
699
- " documents.append(Document(\n",
700
- " page_content=f\"{base_content}\\nStrategy Excerpt:\\n{chunk}\",\n",
701
- " metadata={\n",
702
- " \"source\": item[\"Source\"],\n",
703
- " \"application\": item[\"Application\"],\n",
704
- " \"technical_concepts\": item[\"technical_concepts\"],\n",
705
- " \"sustainability_impacts\": item[\"sustainability_impacts\"],\n",
706
- " \"hyperlink\": item[\"Hyperlink\"],\n",
707
- " \"chunk_id\": f\"{item['Source']}-{len(documents)+1}\"\n",
708
- " }\n",
709
- " ))\n",
710
- " \n",
711
- " with open(cache_file, \"wb\") as f:\n",
712
- " pickle.dump(documents, f)\n",
713
- " return documents\n",
714
- "\n",
715
- "# --- Optimized Retrieval System ---\n",
716
- "class EnhancedRetriever:\n",
717
- " \"\"\"Hybrid retriever with persistent caching\"\"\"\n",
718
- " def __init__(self, documents: List[Document]):\n",
719
- " self.documents = documents\n",
720
- " self.bm25 = self._init_bm25()\n",
721
- " self.vector_store = self._init_faiss()\n",
722
- " self.vector_retriever = self.vector_store.as_retriever(search_kwargs={\"k\": 3})\n",
723
- "\n",
724
- " def _init_bm25(self) -> BM25Retriever:\n",
725
- " cache_key = f\"{BM25_INDEX_PATH}_{get_data_hash(data_file_name)}\"\n",
726
- " if os.path.exists(cache_key):\n",
727
- " logger.info(\"Loading cached BM25 index\")\n",
728
- " with open(cache_key, \"rb\") as f:\n",
729
- " return pickle.load(f)\n",
730
- " \n",
731
- " logger.info(\"Building new BM25 index\")\n",
732
- " retriever = BM25Retriever.from_documents(self.documents)\n",
733
- " retriever.k = 5\n",
734
- " with open(cache_key, \"wb\") as f:\n",
735
- " pickle.dump(retriever, f)\n",
736
- " return retriever\n",
737
- "\n",
738
- " def _init_faiss(self) -> FAISS:\n",
739
- " cache_key = f\"{FAISS_INDEX_PATH}_{get_data_hash(data_file_name)}\"\n",
740
- " if os.path.exists(cache_key):\n",
741
- " logger.info(\"Loading cached FAISS index\")\n",
742
- " return FAISS.load_local(\n",
743
- " cache_key,\n",
744
- " MistralEmbeddings(),\n",
745
- " allow_dangerous_deserialization=True\n",
746
- " )\n",
747
- " \n",
748
- " logger.info(\"Building new FAISS index\")\n",
749
- " vector_store = FAISS.from_documents(self.documents, MistralEmbeddings())\n",
750
- " vector_store.save_local(cache_key)\n",
751
- " return vector_store\n",
752
- "\n",
753
- " @lru_cache(maxsize=500)\n",
754
- " def retrieve(self, query: str) -> str:\n",
755
- " try:\n",
756
- " processed_query = self._preprocess_query(query)\n",
757
- " expanded_query = self._hyde_expansion(processed_query)\n",
758
- " \n",
759
- " bm25_results = self.bm25.invoke(processed_query)\n",
760
- " vector_results = self.vector_retriever.invoke(processed_query)\n",
761
- " expanded_results = self.bm25.invoke(expanded_query)\n",
762
- " \n",
763
- " fused_results = self._fuse_results([bm25_results, vector_results, expanded_results])\n",
764
- " return self._format_context(fused_results[:5])\n",
765
- " except Exception as e:\n",
766
- " logger.error(f\"Retrieval Error: {str(e)}\")\n",
767
- " return \"\"\n",
768
- "\n",
769
- " def _preprocess_query(self, query: str) -> str:\n",
770
- " return query.lower().strip()\n",
771
- "\n",
772
- " @lru_cache(maxsize=500)\n",
773
- " def _hyde_expansion(self, query: str) -> str:\n",
774
- " try:\n",
775
- " response = gemini_model.generate_content(\n",
776
- " f\"Generate a technical draft about biomimicry for: {query}\\nInclude domain-specific terms.\"\n",
777
- " )\n",
778
- " return response.text\n",
779
- " except Exception as e:\n",
780
- " logger.error(f\"HyDE Error: {str(e)}\")\n",
781
- " return query\n",
782
- "\n",
783
- " def _fuse_results(self, result_sets: List[List[Document]]) -> List[Document]:\n",
784
- " fused_scores = defaultdict(float)\n",
785
- " for docs in result_sets:\n",
786
- " for rank, doc in enumerate(docs, 1):\n",
787
- " fused_scores[doc.metadata[\"chunk_id\"]] += 1 / (rank + 60)\n",
788
- " \n",
789
- " seen = set()\n",
790
- " return [\n",
791
- " doc for doc in sorted(\n",
792
- " (doc for docs in result_sets for doc in docs),\n",
793
- " key=lambda x: fused_scores[x.metadata[\"chunk_id\"]],\n",
794
- " reverse=True\n",
795
- " ) if not (doc.metadata[\"chunk_id\"] in seen or seen.add(doc.metadata[\"chunk_id\"]))\n",
796
- " ]\n",
797
- "\n",
798
- " def _format_context(self, docs: List[Document]) -> str:\n",
799
- " context = []\n",
800
- " for doc in docs:\n",
801
- " context_str = f\"\"\"**Source**: [{doc.metadata['source']}]({doc.metadata['hyperlink']})\n",
802
- " **Application**: {doc.metadata['application']}\n",
803
- " **Key Concepts**: {', '.join(doc.metadata['technical_concepts'])}\n",
804
- " **Strategy Excerpt**:\\n{doc.page_content.split('Strategy Excerpt:')[-1].strip()}\"\"\"\n",
805
- " context.append(context_str)\n",
806
- " return \"\\n\\n---\\n\\n\".join(context)\n",
807
- "\n",
808
- "# --- Generation System ---\n",
809
- "SYSTEM_PROMPT = \"\"\"**Biomimicry Expert Guidelines**\n",
810
- "1. Firstly Base answers strictly on context and if there is not context answer by your own.\n",
811
- "2. **Bold** technical terms\n",
812
- "3. Must Include reference links at the end of the response\n",
813
- "\n",
814
- "Context: {context}\"\"\"\n",
815
- "\n",
816
- "@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=20))\n",
817
- "def get_ai_response(query: str, context: str) -> str:\n",
818
- " try:\n",
819
- " response = gemini_model.generate_content(\n",
820
- " f\"{SYSTEM_PROMPT.format(context=context)}\\nQuestion: {query}\\nProvide a detailed technical answer:\"\n",
821
- " )\n",
822
- " logger.info(f\"Raw Response: {response.text}\")\n",
823
- " return _postprocess_response(response.text)\n",
824
- " except Exception as e:\n",
825
- " logger.error(f\"Generation Error: {str(e)}\")\n",
826
- " return \"I'm unable to generate a response right now. Please try again later.\"\n",
827
- "\n",
828
- "def _postprocess_response(response: str) -> str:\n",
829
- " response = re.sub(r\"\\[(.*?)\\]\", r\"[\\1](#)\", response)\n",
830
- " response = re.sub(r\"\\*\\*([\\w-]+)\\*\\*\", r\"**\\1**\", response)\n",
831
- " return response\n",
832
- "\n",
833
- "# --- Pipeline ---\n",
834
- "documents = load_and_chunk_data(data_file_name)\n",
835
- "retriever = EnhancedRetriever(documents)\n",
836
- "\n",
837
- "def generate_response(question: str) -> str:\n",
838
- " try:\n",
839
- " context = retriever.retrieve(question)\n",
840
- " return get_ai_response(question, context) if context else \"No relevant information found.\"\n",
841
- " except Exception as e:\n",
842
- " logger.error(f\"Pipeline Error: {str(e)}\")\n",
843
- " return \"An error occurred processing your request.\"\n",
844
- "\n",
845
- "# --- Gradio Interface ---\n",
846
- "def chat_interface(question: str, history: List[Tuple[str, str]]):\n",
847
- " response = generate_response(question)\n",
848
- " return \"\", history + [(question, response)]\n",
849
- "\n",
850
- "with gr.Blocks(title=\"AskNature BioRAG Expert\", theme=gr.themes.Soft()) as demo:\n",
851
- " gr.Markdown(\"# 🌿 AskNature RAG-based Chatbot \")\n",
852
- " with gr.Row():\n",
853
- " chatbot = gr.Chatbot(label=\"Dialogue History\", height=500)\n",
854
- " with gr.Row():\n",
855
- " question = gr.Textbox(placeholder=\"Ask about biomimicry (e.g. 'How does Werewool use coral proteins to make fibers?')\",\n",
856
- " label=\"Inquiry\", scale=4)\n",
857
- " clear_btn = gr.Button(\"Clear History\", variant=\"secondary\")\n",
858
- " \n",
859
- " gr.Markdown(\"\"\"<div style=\"text-align: center; color: #4a7c59;\">\n",
860
- " <small>Powered by AskNature's Database | \n",
861
- " Explore nature's blueprints at <a href=\"https://asknature.org\">asknature.org</a></small></div>\"\"\")\n",
862
- " question.submit(chat_interface, [question, chatbot], [question, chatbot])\n",
863
- " clear_btn.click(lambda: [], None, chatbot)\n",
864
- "\n",
865
- "if __name__ == \"__main__\":\n",
866
- " demo.launch(show_error=True)"
867
- ]
868
- },
869
- {
870
- "cell_type": "code",
871
- "execution_count": null,
872
- "metadata": {},
873
- "outputs": [],
874
- "source": []
875
- },
876
- {
877
- "cell_type": "code",
878
- "execution_count": null,
879
- "metadata": {},
880
- "outputs": [],
881
- "source": [
882
- "# Combined Llama 3 and Gemini Flash Chatbot\n",
883
- "import json\n",
884
- "import logging\n",
885
- "import re\n",
886
- "import os\n",
887
- "import pickle\n",
888
- "from typing import List, Tuple, Optional\n",
889
- "import gradio as gr\n",
890
- "from openai import OpenAI\n",
891
- "import google.generativeai as genai\n",
892
- "from functools import lru_cache\n",
893
- "from tenacity import retry, stop_after_attempt, wait_exponential\n",
894
- "from langchain_community.retrievers import BM25Retriever\n",
895
- "from langchain_community.vectorstores import FAISS\n",
896
- "from langchain_core.embeddings import Embeddings\n",
897
- "from langchain_core.documents import Document\n",
898
- "from collections import defaultdict\n",
899
- "import hashlib\n",
900
- "from tqdm import tqdm\n",
901
- "from dotenv import load_dotenv\n",
902
- "\n",
903
- "load_dotenv()\n",
904
- "\n",
905
- "# --- Configuration ---\n",
906
- "FAISS_INDEX_PATH = \"faiss_index\"\n",
907
- "BM25_INDEX_PATH = \"bm25_index.pkl\"\n",
908
- "CACHE_VERSION = \"v1\"\n",
909
- "embedding_model = \"e5-mistral-7b-instruct\"\n",
910
- "data_file_name = \"AskNatureNet_data_enhanced.json\"\n",
911
- "CHUNK_SIZE = 800\n",
912
- "OVERLAP = 200\n",
913
- "EMBEDDING_BATCH_SIZE = 32\n",
914
- "\n",
915
- "# Initialize clients\n",
916
- "OPENAI_API_CONFIG = {\n",
917
- " \"api_key\": os.getenv(\"OPENAI_API_KEY\"),\n",
918
- " \"base_url\": \"https://chat-ai.academiccloud.de/v1\"\n",
919
- "}\n",
920
- "client = OpenAI(**OPENAI_API_CONFIG)\n",
921
- "genai.configure(api_key=os.getenv(\"GEMINI_API_KEY\"))\n",
922
- "\n",
923
- "logging.basicConfig(level=logging.INFO)\n",
924
- "logger = logging.getLogger(__name__)\n",
925
- "\n",
926
- "# --- Helper Functions ---\n",
927
- "def get_data_hash(file_path: str) -> str:\n",
928
- " \"\"\"Generate hash of data file for cache validation\"\"\"\n",
929
- " with open(file_path, \"rb\") as f:\n",
930
- " return hashlib.md5(f.read()).hexdigest()\n",
931
- "\n",
932
- "# --- Custom Embedding Handler ---\n",
933
- "class MistralEmbeddings(Embeddings):\n",
934
- " \"\"\"E5-Mistral-7B embedding adapter\"\"\"\n",
935
- " def embed_documents(self, texts: List[str]) -> List[List[float]]:\n",
936
- " embeddings = []\n",
937
- " try:\n",
938
- " for i in tqdm(range(0, len(texts), EMBEDDING_BATCH_SIZE), desc=\"Embedding Progress\"):\n",
939
- " batch = texts[i:i + EMBEDDING_BATCH_SIZE]\n",
940
- " response = client.embeddings.create(\n",
941
- " input=batch,\n",
942
- " model=embedding_model,\n",
943
- " encoding_format=\"float\"\n",
944
- " )\n",
945
- " embeddings.extend([e.embedding for e in response.data])\n",
946
- " return embeddings\n",
947
- " except Exception as e:\n",
948
- " logger.error(f\"Embedding Error: {str(e)}\")\n",
949
- " return [[] for _ in texts]\n",
950
- " \n",
951
- " def embed_query(self, text: str) -> List[float]:\n",
952
- " return self.embed_documents([text])[0]\n",
953
- "\n",
954
- "# --- Data Processing ---\n",
955
- "def load_and_chunk_data(file_path: str) -> List[Document]:\n",
956
- " \"\"\"Enhanced chunking with metadata preservation\"\"\"\n",
957
- " current_hash = get_data_hash(file_path)\n",
958
- " cache_file = f\"documents_{CACHE_VERSION}_{current_hash}.pkl\"\n",
959
- " \n",
960
- " if os.path.exists(cache_file):\n",
961
- " logger.info(\"Loading cached documents\")\n",
962
- " with open(cache_file, \"rb\") as f:\n",
963
- " return pickle.load(f)\n",
964
- " \n",
965
- " with open(file_path, 'r', encoding='utf-8') as f:\n",
966
- " data = json.load(f)\n",
967
- " \n",
968
- " documents = []\n",
969
- " for item in tqdm(data, desc=\"Chunking Progress\"):\n",
970
- " base_content = f\"\"\"Source: {item['Source']}\n",
971
- "Application: {item['Application']}\n",
972
- "Functions: {', '.join(filter(None, [item.get('Function1'), item.get('Function2')]))}\n",
973
- "Technical Concepts: {', '.join(item['technical_concepts'])}\n",
974
- "Biological Mechanisms: {', '.join(item['biological_mechanisms'])}\"\"\"\n",
975
- " \n",
976
- " strategy = item['Strategy']\n",
977
- " for i in range(0, len(strategy), CHUNK_SIZE - OVERLAP):\n",
978
- " chunk = strategy[i:i + CHUNK_SIZE]\n",
979
- " documents.append(Document(\n",
980
- " page_content=f\"{base_content}\\nStrategy Excerpt:\\n{chunk}\",\n",
981
- " metadata={\n",
982
- " \"source\": item[\"Source\"],\n",
983
- " \"application\": item[\"Application\"],\n",
984
- " \"technical_concepts\": item[\"technical_concepts\"],\n",
985
- " \"sustainability_impacts\": item[\"sustainability_impacts\"],\n",
986
- " \"hyperlink\": item[\"Hyperlink\"],\n",
987
- " \"chunk_id\": f\"{item['Source']}-{len(documents)+1}\"\n",
988
- " }\n",
989
- " ))\n",
990
- " \n",
991
- " with open(cache_file, \"wb\") as f:\n",
992
- " pickle.dump(documents, f)\n",
993
- " return documents\n",
994
- "\n",
995
- "# --- Optimized Retrieval System ---\n",
996
- "class EnhancedRetriever:\n",
997
- " \"\"\"Hybrid retriever with persistent caching\"\"\"\n",
998
- " def __init__(self, documents: List[Document]):\n",
999
- " self.documents = documents\n",
1000
- " self.bm25 = self._init_bm25()\n",
1001
- " self.vector_store = self._init_faiss()\n",
1002
- " self.vector_retriever = self.vector_store.as_retriever(search_kwargs={\"k\": 3})\n",
1003
- "\n",
1004
- " def _init_bm25(self) -> BM25Retriever:\n",
1005
- " cache_key = f\"{BM25_INDEX_PATH}_{get_data_hash(data_file_name)}\"\n",
1006
- " if os.path.exists(cache_key):\n",
1007
- " logger.info(\"Loading cached BM25 index\")\n",
1008
- " with open(cache_key, \"rb\") as f:\n",
1009
- " return pickle.load(f)\n",
1010
- " \n",
1011
- " logger.info(\"Building new BM25 index\")\n",
1012
- " retriever = BM25Retriever.from_documents(self.documents)\n",
1013
- " retriever.k = 5\n",
1014
- " with open(cache_key, \"wb\") as f:\n",
1015
- " pickle.dump(retriever, f)\n",
1016
- " return retriever\n",
1017
- "\n",
1018
- " def _init_faiss(self) -> FAISS:\n",
1019
- " cache_key = f\"{FAISS_INDEX_PATH}_{get_data_hash(data_file_name)}\"\n",
1020
- " if os.path.exists(cache_key):\n",
1021
- " logger.info(\"Loading cached FAISS index\")\n",
1022
- " return FAISS.load_local(\n",
1023
- " cache_key,\n",
1024
- " MistralEmbeddings(),\n",
1025
- " allow_dangerous_deserialization=True\n",
1026
- " )\n",
1027
- " \n",
1028
- " logger.info(\"Building new FAISS index\")\n",
1029
- " vector_store = FAISS.from_documents(self.documents, MistralEmbeddings())\n",
1030
- " vector_store.save_local(cache_key)\n",
1031
- " return vector_store\n",
1032
- "\n",
1033
- " @lru_cache(maxsize=500)\n",
1034
- " def retrieve(self, query: str) -> str:\n",
1035
- " try:\n",
1036
- " processed_query = self._preprocess_query(query)\n",
1037
- " expanded_query = self._hyde_expansion(processed_query)\n",
1038
- " \n",
1039
- " bm25_results = self.bm25.invoke(processed_query)\n",
1040
- " vector_results = self.vector_retriever.invoke(processed_query)\n",
1041
- " expanded_results = self.bm25.invoke(expanded_query)\n",
1042
- " \n",
1043
- " fused_results = self._fuse_results([bm25_results, vector_results, expanded_results])\n",
1044
- " return self._format_context(fused_results[:5])\n",
1045
- " except Exception as e:\n",
1046
- " logger.error(f\"Retrieval Error: {str(e)}\")\n",
1047
- " return \"\"\n",
1048
- "\n",
1049
- " def _preprocess_query(self, query: str) -> str:\n",
1050
- " return query.lower().strip()\n",
1051
- "\n",
1052
- " @lru_cache(maxsize=500)\n",
1053
- " def _hyde_expansion(self, query: str) -> str:\n",
1054
- " try:\n",
1055
- " response = client.chat.completions.create(\n",
1056
- " model=\"meta-llama-3-70b-instruct\",\n",
1057
- " messages=[{\n",
1058
- " \"role\": \"user\",\n",
1059
- " \"content\": f\"Generate a technical draft about biomimicry for: {query}\\nInclude domain-specific terms.\"\n",
1060
- " }],\n",
1061
- " temperature=0.5,\n",
1062
- " max_tokens=200\n",
1063
- " )\n",
1064
- " return response.choices[0].message.content\n",
1065
- " except Exception as e:\n",
1066
- " logger.error(f\"HyDE Error: {str(e)}\")\n",
1067
- " return query\n",
1068
- "\n",
1069
- " def _fuse_results(self, result_sets: List[List[Document]]) -> List[Document]:\n",
1070
- " fused_scores = defaultdict(float)\n",
1071
- " for docs in result_sets:\n",
1072
- " for rank, doc in enumerate(docs, 1):\n",
1073
- " fused_scores[doc.metadata[\"chunk_id\"]] += 1 / (rank + 60)\n",
1074
- " \n",
1075
- " seen = set()\n",
1076
- " return [\n",
1077
- " doc for doc in sorted(\n",
1078
- " (doc for docs in result_sets for doc in docs),\n",
1079
- " key=lambda x: fused_scores[x.metadata[\"chunk_id\"]],\n",
1080
- " reverse=True\n",
1081
- " ) if not (doc.metadata[\"chunk_id\"] in seen or seen.add(doc.metadata[\"chunk_id\"]))\n",
1082
- " ]\n",
1083
- "\n",
1084
- " def _format_context(self, docs: List[Document]) -> str:\n",
1085
- " context = []\n",
1086
- " for doc in docs:\n",
1087
- " context_str = f\"\"\"**Source**: [{doc.metadata['source']}]({doc.metadata['hyperlink']})\n",
1088
- " **Application**: {doc.metadata['application']}\n",
1089
- " **Key Concepts**: {', '.join(doc.metadata['technical_concepts'])}\n",
1090
- " **Strategy Excerpt**:\\n{doc.page_content.split('Strategy Excerpt:')[-1].strip()}\"\"\"\n",
1091
- " context.append(context_str)\n",
1092
- " return \"\\n\\n---\\n\\n\".join(context)\n",
1093
- "\n",
1094
- "# --- Generation System ---\n",
1095
- "SYSTEM_PROMPT = \"\"\"**Biomimicry Expert Guidelines**\n",
1096
- "1. Firstly Base answers strictly on context and if there is not context answer by your own.\n",
1097
- "2. Cite sources as [Source] witht the hyperlink\n",
1098
- "3. **Bold** technical terms\n",
1099
- "4. Include reference links at the end of the response\n",
1100
- "\n",
1101
- "Context: {context}\"\"\"\n",
1102
- "\n",
1103
- "@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=20))\n",
1104
- "def get_ai_response(query: str, context: str, model: str) -> str:\n",
1105
- " try:\n",
1106
- " if model == \"gemini-2.0-flash\":\n",
1107
- " gemini_model = genai.GenerativeModel(model)\n",
1108
- " response = gemini_model.generate_content(\n",
1109
- " f\"{SYSTEM_PROMPT.format(context=context)}\\nQuestion: {query}\\nProvide a detailed technical answer:\"\n",
1110
- " )\n",
1111
- " return _postprocess_response(response.text)\n",
1112
- " elif model == \"meta-llama-3-70b-instruct\":\n",
1113
- " response = client.chat.completions.create(\n",
1114
- " model=model,\n",
1115
- " messages=[\n",
1116
- " {\"role\": \"system\", \"content\": SYSTEM_PROMPT.format(context=context)},\n",
1117
- " {\"role\": \"user\", \"content\": f\"Question: {query}\\nProvide a detailed technical answer:\"}\n",
1118
- " ],\n",
1119
- " temperature=0.4,\n",
1120
- " max_tokens=2000\n",
1121
- " )\n",
1122
- " return _postprocess_response(response.choices[0].message.content)\n",
1123
- " except Exception as e:\n",
1124
- " logger.error(f\"Generation Error: {str(e)}\")\n",
1125
- " return \"I'm unable to generate a response right now. Please try again later.\"\n",
1126
- "\n",
1127
- "def _postprocess_response(response: str) -> str:\n",
1128
- " response = re.sub(r\"\\[(.*?)\\]\", r\"[\\1](#)\", response)\n",
1129
- " response = re.sub(r\"\\*\\*([\\w-]+)\\*\\*\", r\"**\\1**\", response)\n",
1130
- " return response\n",
1131
- "\n",
1132
- "# --- Pipeline ---\n",
1133
- "documents = load_and_chunk_data(data_file_name)\n",
1134
- "retriever = EnhancedRetriever(documents)\n",
1135
- "\n",
1136
- "def generate_response(question: str, model: str) -> str:\n",
1137
- " try:\n",
1138
- " context = retriever.retrieve(question)\n",
1139
- " return get_ai_response(question, context, model) if context else \"No relevant information found.\"\n",
1140
- " except Exception as e:\n",
1141
- " logger.error(f\"Pipeline Error: {str(e)}\")\n",
1142
- " return \"An error occurred processing your request.\"\n",
1143
- "\n",
1144
- "# --- Gradio Interface ---\n",
1145
- "def chat_interface(question: str, history: List[Tuple[str, str]], model: str):\n",
1146
- " response = generate_response(question, model)\n",
1147
- " return \"\", history + [(question, response)]\n",
1148
- "\n",
1149
- "with gr.Blocks(title=\"AskNature BioRAG Expert\", theme=gr.themes.Soft()) as demo:\n",
1150
- " gr.Markdown(\"# 🌿 AskNature RAG-based Chatbot \")\n",
1151
- " with gr.Row():\n",
1152
- " chatbot = gr.Chatbot(label=\"Dialogue History\", height=500)\n",
1153
- " with gr.Row():\n",
1154
- " question = gr.Textbox(placeholder=\"Ask about biomimicry (e.g. 'How does Werewool use coral proteins to make fibers?')\",\n",
1155
- " label=\"Inquiry\", scale=4)\n",
1156
- " model_selector = gr.Dropdown(choices=[\"gemini-2.0-flash\", \"meta-llama-3-70b-instruct\"], label=\"Generation Model\", value=\"gemini-2.0-flash\")\n",
1157
- " clear_btn = gr.Button(\"Clear History\", variant=\"secondary\")\n",
1158
- " \n",
1159
- " gr.Markdown(\"\"\"\n",
1160
- " <div style=\"text-align: center; color: #4a7c59;\">\n",
1161
- " <small>Powered by AskNature's Database | \n",
1162
- " Explore nature's blueprints at <a href=\"https://asknature.org\">asknature.org</a></small>\n",
1163
- " </div>\"\"\")\n",
1164
- " question.submit(chat_interface, [question, chatbot, model_selector], [question, chatbot])\n",
1165
- " clear_btn.click(lambda: [], None, chatbot)\n",
1166
- "\n",
1167
- "if __name__ == \"__main__\":\n",
1168
- " demo.launch(show_error=True)"
1169
- ]
1170
- },
1171
- {
1172
- "cell_type": "code",
1173
- "execution_count": null,
1174
- "metadata": {},
1175
- "outputs": [],
1176
- "source": []
1177
- },
1178
- {
1179
- "cell_type": "code",
1180
- "execution_count": null,
1181
- "metadata": {},
1182
- "outputs": [],
1183
- "source": []
1184
- },
1185
- {
1186
- "cell_type": "code",
1187
- "execution_count": null,
1188
- "metadata": {},
1189
- "outputs": [
1190
- {
1191
- "name": "stderr",
1192
- "output_type": "stream",
1193
- "text": [
1194
- "INFO:__main__:Loading cached documents\n",
1195
- "INFO:__main__:Loading cached BM25 index\n",
1196
- "INFO:__main__:Loading cached FAISS index\n",
1197
- "INFO:faiss.loader:Loading faiss with AVX2 support.\n",
1198
- "INFO:faiss.loader:Successfully loaded faiss with AVX2 support.\n",
1199
- "c:\\Users\\Mohamed Elsafty\\.conda\\envs\\rag\\Lib\\site-packages\\gradio\\components\\chatbot.py:273: UserWarning: You have not specified a value for the `type` parameter. Defaulting to the 'tuples' format for chatbot messages, but this is deprecated and will be removed in a future version of Gradio. Please set type='messages' instead, which uses openai-style dictionaries with 'role' and 'content' keys.\n",
1200
- " warnings.warn(\n"
1201
- ]
1202
- },
1203
- {
1204
- "name": "stdout",
1205
- "output_type": "stream",
1206
- "text": [
1207
- "* Running on local URL: http://127.0.0.1:7860\n"
1208
- ]
1209
- },
1210
- {
1211
- "name": "stderr",
1212
- "output_type": "stream",
1213
- "text": [
1214
- "INFO:httpx:HTTP Request: GET https://api.gradio.app/pkg-version \"HTTP/1.1 200 OK\"\n",
1215
- "INFO:httpx:HTTP Request: GET http://127.0.0.1:7860/gradio_api/startup-events \"HTTP/1.1 200 OK\"\n",
1216
- "INFO:httpx:HTTP Request: HEAD http://127.0.0.1:7860/ \"HTTP/1.1 200 OK\"\n"
1217
- ]
1218
- },
1219
- {
1220
- "name": "stdout",
1221
- "output_type": "stream",
1222
- "text": [
1223
- "\n",
1224
- "To create a public link, set `share=True` in `launch()`.\n"
1225
- ]
1226
- },
1227
- {
1228
- "data": {
1229
- "text/html": [
1230
- "<div><iframe src=\"http://127.0.0.1:7860/\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
1231
- ],
1232
- "text/plain": [
1233
- "<IPython.core.display.HTML object>"
1234
- ]
1235
- },
1236
- "metadata": {},
1237
- "output_type": "display_data"
1238
- },
1239
- {
1240
- "name": "stderr",
1241
- "output_type": "stream",
1242
- "text": [
1243
- "INFO:httpx:HTTP Request: POST https://chat-ai.academiccloud.de/v1/chat/completions \"HTTP/1.1 200 OK\"\n",
1244
- "Embedding Progress: 0%| | 0/1 [00:00<?, ?it/s]INFO:httpx:HTTP Request: POST https://chat-ai.academiccloud.de/v1/embeddings \"HTTP/1.1 200 OK\"\n",
1245
- "Embedding Progress: 100%|██████████| 1/1 [00:00<00:00, 4.23it/s]\n",
1246
- "INFO:httpx:HTTP Request: POST https://chat-ai.academiccloud.de/v1/chat/completions \"HTTP/1.1 200 OK\"\n",
1247
- "INFO:__main__:Response from meta-llama-3-70b-instruct: ChatCompletion(id='chat-86b881962781487d8d9067b08011c115', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content=\"Hello. I'm excited to explore the fascinating realm of biomimicry with you. Biomimicry is an interdisciplinary field that involves the study of nature and its processes to develop innovative solutions for human challenges. By emulating the strategies and mechanisms employed by living organisms, we can create more sustainable, efficient, and effective technologies.\\n\\nIn the context of the provided sources, we can see how nature has evolved remarkable solutions to various problems. For instance, the Discosoma coral's protein-based pigmentation [1] has inspired the development of biodegradable fibers with tailored aesthetic and performance properties. This innovation has the potential to revolutionize the textile industry, which is currently responsible for significant environmental impacts, including greenhouse gas emissions and microplastic pollution.\\n\\nAnother example is the pummelo peel's hierarchical organization [2], which confers impact resistance due to its unique structural composition. This natural material has excellent damping properties, making it an attractive model for the development of composite materials with enhanced mechanical properties.\\n\\nThe concept of geometric laws and allometry [3] is also relevant to biomimicry, as it highlights the importance of scaling theory in understanding the relationships between an organism's size and its physical properties. This knowledge can be applied to the design of more efficient and sustainable systems.\\n\\nFurthermore, the anatomy of mammals' external ear-flaps [4] has evolved to concentrate sound waves, allowing for more effective hearing. This natural design can inspire the development of novel sound collection and concentration technologies.\\n\\nLastly, the behavior of starlings [5] has been studied in the context of immune cell communication and response scaling. This research can inform the design of response guidelines for various threatening situations, such as emergency evacuations.\\n\\nIn conclusion, biomimicry offers a wealth of opportunities for innovation and sustainability. By studying nature's strategies and mechanisms, we can develop more efficient, effective, and environmentally friendly solutions to various human challenges.\\n\\nReferences:\\n\\n[1] https://asknature.org/innovation/colorful-fibers-inspired-by-proteins-found-in-discosoma-coral/\\n[2] https://asknature.org/strategy/hierarchical-organization-of-peel-confers-impact-resistance/\\n[3] https://asknature.org/strategy/environment-tailors-growth/\\n[4] https://asknature.org/strategy/ear-flaps-concentrate-sound-waves/\\n[5] https://asknature.org/strategy/starlings-coordinate-movements-within-a-flock/\", refusal=None, role='assistant', audio=None, function_call=None, tool_calls=[]), stop_reason=None)], created=1739304894, model='meta-llama-3.1-70b-instruct', object='chat.completion', service_tier=None, system_fingerprint=None, usage=CompletionUsage(completion_tokens=503, prompt_tokens=1036, total_tokens=1539, completion_tokens_details=None, prompt_tokens_details=None), prompt_logprobs=None)\n"
1248
- ]
1249
- }
1250
- ],
1251
- "source": [
1252
- "# Combined Gemini Flash and Meta-LLAMA 3 GWDG and Groq Chatbot\n",
1253
- "# For Gemini Flash rate limit is 15 requests per minute\n",
1254
- "# For Groq rate 30 RPM , 14400 RPD, 6K TPM and 500K TPD\n",
1255
- "# For GWDG Llama3 60 /min 3000 /h 75000 /day 2000000 /month\n",
1256
- "import os\n",
1257
- "import json\n",
1258
- "import logging\n",
1259
- "import re\n",
1260
- "from typing import List, Tuple\n",
1261
- "import gradio as gr\n",
1262
- "from openai import OpenAI\n",
1263
- "import google.generativeai as genai\n",
1264
- "import requests\n",
1265
- "from functools import lru_cache\n",
1266
- "from tenacity import retry, stop_after_attempt, wait_exponential\n",
1267
- "from langchain_community.retrievers import BM25Retriever\n",
1268
- "from langchain_community.vectorstores import FAISS\n",
1269
- "from langchain_core.embeddings import Embeddings\n",
1270
- "from langchain_core.documents import Document\n",
1271
- "from collections import defaultdict\n",
1272
- "import hashlib\n",
1273
- "from tqdm import tqdm\n",
1274
- "from dotenv import load_dotenv\n",
1275
- "import pickle\n",
1276
- "load_dotenv()\n",
1277
- "\n",
1278
- "# --- Configuration ---\n",
1279
- "FAISS_INDEX_PATH = \"faiss_index\"\n",
1280
- "BM25_INDEX_PATH = \"bm25_index.pkl\"\n",
1281
- "CACHE_VERSION = \"v1\"\n",
1282
- "embedding_model = \"e5-mistral-7b-instruct\"\n",
1283
- "data_file_name = \"AskNatureNet_data_enhanced.json\"\n",
1284
- "CHUNK_SIZE = 800\n",
1285
- "OVERLAP = 200\n",
1286
- "EMBEDDING_BATCH_SIZE = 32\n",
1287
- "\n",
1288
- "# Initialize clients\n",
1289
- "OPENAI_API_CONFIG = {\n",
1290
- " \"api_key\": os.getenv(\"OPENAI_API_KEY\"),\n",
1291
- " \"base_url\": \"https://chat-ai.academiccloud.de/v1\"\n",
1292
- "}\n",
1293
- "client = OpenAI(**OPENAI_API_CONFIG)\n",
1294
- "genai.configure(api_key=os.getenv(\"GEMINI_API_KEY\"))\n",
1295
- "\n",
1296
- "logging.basicConfig(level=logging.INFO)\n",
1297
- "logger = logging.getLogger(__name__)\n",
1298
- "\n",
1299
- "# --- Helper Functions ---\n",
1300
- "def get_data_hash(file_path: str) -> str:\n",
1301
- " \"\"\"Generate hash of data file for cache validation\"\"\"\n",
1302
- " with open(file_path, \"rb\") as f:\n",
1303
- " return hashlib.md5(f.read()).hexdigest()\n",
1304
- "\n",
1305
- "# --- Custom Embedding Handler ---\n",
1306
- "class MistralEmbeddings(Embeddings):\n",
1307
- " \"\"\"E5-Mistral-7B embedding adapter\"\"\"\n",
1308
- " def embed_documents(self, texts: List[str]) -> List[List[float]]:\n",
1309
- " embeddings = []\n",
1310
- " try:\n",
1311
- " for i in tqdm(range(0, len(texts), EMBEDDING_BATCH_SIZE), desc=\"Embedding Progress\"):\n",
1312
- " batch = texts[i:i + EMBEDDING_BATCH_SIZE]\n",
1313
- " response = client.embeddings.create(\n",
1314
- " input=batch,\n",
1315
- " model=embedding_model,\n",
1316
- " encoding_format=\"float\"\n",
1317
- " )\n",
1318
- " embeddings.extend([e.embedding for e in response.data])\n",
1319
- " return embeddings\n",
1320
- " except Exception as e:\n",
1321
- " logger.error(f\"Embedding Error: {str(e)}\")\n",
1322
- " return [[] for _ in texts]\n",
1323
- " \n",
1324
- " def embed_query(self, text: str) -> List[float]:\n",
1325
- " return self.embed_documents([text])[0]\n",
1326
- "\n",
1327
- "# --- Data Processing ---\n",
1328
- "def load_and_chunk_data(file_path: str) -> List[Document]:\n",
1329
- " \"\"\"Enhanced chunking with metadata preservation\"\"\"\n",
1330
- " current_hash = get_data_hash(file_path)\n",
1331
- " cache_file = f\"documents_{CACHE_VERSION}_{current_hash}.pkl\"\n",
1332
- " \n",
1333
- " if os.path.exists(cache_file):\n",
1334
- " logger.info(\"Loading cached documents\")\n",
1335
- " with open(cache_file, \"rb\") as f:\n",
1336
- " return pickle.load(f)\n",
1337
- " \n",
1338
- " with open(file_path, 'r', encoding='utf-8') as f:\n",
1339
- " data = json.load(f)\n",
1340
- " \n",
1341
- " documents = []\n",
1342
- " for item in tqdm(data, desc=\"Chunking Progress\"):\n",
1343
- " base_content = f\"\"\"Source: {item['Source']}\n",
1344
- "Application: {item['Application']}\n",
1345
- "Functions: {', '.join(filter(None, [item.get('Function1'), item.get('Function2')]))}\n",
1346
- "Technical Concepts: {', '.join(item['technical_concepts'])}\n",
1347
- "Biological Mechanisms: {', '.join(item['biological_mechanisms'])}\"\"\"\n",
1348
- " \n",
1349
- " strategy = item['Strategy']\n",
1350
- " for i in range(0, len(strategy), CHUNK_SIZE - OVERLAP):\n",
1351
- " chunk = strategy[i:i + CHUNK_SIZE]\n",
1352
- " documents.append(Document(\n",
1353
- " page_content=f\"{base_content}\\nStrategy Excerpt:\\n{chunk}\",\n",
1354
- " metadata={\n",
1355
- " \"source\": item[\"Source\"],\n",
1356
- " \"application\": item[\"Application\"],\n",
1357
- " \"technical_concepts\": item[\"technical_concepts\"],\n",
1358
- " \"sustainability_impacts\": item[\"sustainability_impacts\"],\n",
1359
- " \"hyperlink\": item[\"Hyperlink\"],\n",
1360
- " \"chunk_id\": f\"{item['Source']}-{len(documents)+1}\"\n",
1361
- " }\n",
1362
- " ))\n",
1363
- " \n",
1364
- " with open(cache_file, \"wb\") as f:\n",
1365
- " pickle.dump(documents, f)\n",
1366
- " return documents\n",
1367
- "\n",
1368
- "# --- Optimized Retrieval System ---\n",
1369
- "class EnhancedRetriever:\n",
1370
- " \"\"\"Hybrid retriever with persistent caching\"\"\"\n",
1371
- " def __init__(self, documents: List[Document]):\n",
1372
- " self.documents = documents\n",
1373
- " self.bm25 = self._init_bm25()\n",
1374
- " self.vector_store = self._init_faiss()\n",
1375
- " self.vector_retriever = self.vector_store.as_retriever(search_kwargs={\"k\": 3})\n",
1376
- "\n",
1377
- " def _init_bm25(self) -> BM25Retriever:\n",
1378
- " cache_key = f\"{BM25_INDEX_PATH}_{get_data_hash(data_file_name)}\"\n",
1379
- " if os.path.exists(cache_key):\n",
1380
- " logger.info(\"Loading cached BM25 index\")\n",
1381
- " with open(cache_key, \"rb\") as f:\n",
1382
- " return pickle.load(f)\n",
1383
- " \n",
1384
- " logger.info(\"Building new BM25 index\")\n",
1385
- " retriever = BM25Retriever.from_documents(self.documents)\n",
1386
- " retriever.k = 5\n",
1387
- " with open(cache_key, \"wb\") as f:\n",
1388
- " pickle.dump(retriever, f)\n",
1389
- " return retriever\n",
1390
- "\n",
1391
- " def _init_faiss(self) -> FAISS:\n",
1392
- " cache_key = f\"{FAISS_INDEX_PATH}_{get_data_hash(data_file_name)}\"\n",
1393
- " if os.path.exists(cache_key):\n",
1394
- " logger.info(\"Loading cached FAISS index\")\n",
1395
- " return FAISS.load_local(\n",
1396
- " cache_key,\n",
1397
- " MistralEmbeddings(),\n",
1398
- " allow_dangerous_deserialization=True\n",
1399
- " )\n",
1400
- " \n",
1401
- " logger.info(\"Building new FAISS index\")\n",
1402
- " vector_store = FAISS.from_documents(self.documents, MistralEmbeddings())\n",
1403
- " vector_store.save_local(cache_key)\n",
1404
- " return vector_store\n",
1405
- "\n",
1406
- " @lru_cache(maxsize=500)\n",
1407
- " def retrieve(self, query: str) -> str:\n",
1408
- " try:\n",
1409
- " processed_query = self._preprocess_query(query)\n",
1410
- " expanded_query = self._hyde_expansion(processed_query)\n",
1411
- " \n",
1412
- " bm25_results = self.bm25.invoke(processed_query)\n",
1413
- " vector_results = self.vector_retriever.invoke(processed_query)\n",
1414
- " expanded_results = self.bm25.invoke(expanded_query)\n",
1415
- " \n",
1416
- " fused_results = self._fuse_results([bm25_results, vector_results, expanded_results])\n",
1417
- " return self._format_context(fused_results[:5])\n",
1418
- " except Exception as e:\n",
1419
- " logger.error(f\"Retrieval Error: {str(e)}\")\n",
1420
- " return \"\"\n",
1421
- "\n",
1422
- " def _preprocess_query(self, query: str) -> str:\n",
1423
- " return query.lower().strip()\n",
1424
- "\n",
1425
- " @lru_cache(maxsize=500)\n",
1426
- " def _hyde_expansion(self, query: str) -> str:\n",
1427
- " try:\n",
1428
- " response = client.chat.completions.create(\n",
1429
- " model=\"meta-llama-3-70b-instruct\",\n",
1430
- " messages=[{\n",
1431
- " \"role\": \"user\",\n",
1432
- " \"content\": f\"Generate a technical draft about biomimicry for: {query}\\nInclude domain-specific terms.\"\n",
1433
- " }],\n",
1434
- " temperature=0.5,\n",
1435
- " max_tokens=200\n",
1436
- " )\n",
1437
- " return response.choices[0].message.content\n",
1438
- " except Exception as e:\n",
1439
- " logger.error(f\"HyDE Error: {str(e)}\")\n",
1440
- " return query\n",
1441
- "\n",
1442
- " def _fuse_results(self, result_sets: List[List[Document]]) -> List[Document]:\n",
1443
- " fused_scores = defaultdict(float)\n",
1444
- " for docs in result_sets:\n",
1445
- " for rank, doc in enumerate(docs, 1):\n",
1446
- " fused_scores[doc.metadata[\"chunk_id\"]] += 1 / (rank + 60)\n",
1447
- " \n",
1448
- " seen = set()\n",
1449
- " return [\n",
1450
- " doc for doc in sorted(\n",
1451
- " (doc for docs in result_sets for doc in docs),\n",
1452
- " key=lambda x: fused_scores[x.metadata[\"chunk_id\"]],\n",
1453
- " reverse=True\n",
1454
- " ) if not (doc.metadata[\"chunk_id\"] in seen or seen.add(doc.metadata[\"chunk_id\"]))\n",
1455
- " ]\n",
1456
- "\n",
1457
- " def _format_context(self, docs: List[Document]) -> str:\n",
1458
- " context = []\n",
1459
- " for doc in docs:\n",
1460
- " context_str = f\"\"\"**Source**: [{doc.metadata['source']}]({doc.metadata['hyperlink']})\n",
1461
- "**Application**: {doc.metadata['application']}\n",
1462
- "**Key Concepts**: {', '.join(doc.metadata['technical_concepts'])}\n",
1463
- "**Strategy Excerpt**:\n",
1464
- "{doc.page_content.split('Strategy Excerpt:')[-1].strip()}\"\"\"\n",
1465
- " context.append(context_str)\n",
1466
- " return \"\\n\\n---\\n\\n\".join(context)\n",
1467
- "\n",
1468
- "# --- Generation System ---\n",
1469
- "SYSTEM_PROMPT = \"\"\"\n",
1470
- "**Expert Biomimicry Advisor**\n",
1471
- "\n",
1472
- "- **Objective**: Your role is to provide expert-level insights on biomimicry by using the provided AskNature context. When context is unavailable, rely on general knowledge.\n",
1473
- "- **Answer Precision**: Always use precise technical language and structure your response logically, emphasizing the relationship between biological concepts and innovation.\n",
1474
- "- **References**: Use numeric citations (e.g., [1]) when referencing data points or studies, corresponding to the URLs or sources provided in the context.\n",
1475
- "- **Content Formatting**: Bold technical terms for emphasis (e.g., **protein synthesis**, **ecosystem mimicry**).\n",
1476
- "- **Conclusion**: Summarize the sustainability impacts of the discussed technologies or ideas. Highlight innovative aspects and benefits.\n",
1477
- "- **References Section**: At the end of your response, list all cited sources with their corresponding numbers.\n",
1478
- "\n",
1479
- "Context: {context}\n",
1480
- "\"\"\"\n",
1481
- "\n",
1482
- "\n",
1483
- "@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=20))\n",
1484
- "def get_ai_response(query: str, context: str, model: str) -> str:\n",
1485
- " result = \"\" # Initialize the result variable\n",
1486
- " try:\n",
1487
- " if model == \"gemini-2.0-flash\":\n",
1488
- " gemini_model = genai.GenerativeModel(model)\n",
1489
- " response = gemini_model.generate_content(\n",
1490
- " f\"{SYSTEM_PROMPT.format(context=context)}\\nQuestion: {query}\\nProvide a detailed technical answer:\"\n",
1491
- " )\n",
1492
- " logger.info(f\"Response from gemini-2.0-flash: {response.text}\")\n",
1493
- " result = _postprocess_response(response.text)\n",
1494
- " elif model == \"meta-llama-3-70b-instruct\":\n",
1495
- " response = client.chat.completions.create(\n",
1496
- " model=model,\n",
1497
- " messages=[\n",
1498
- " {\"role\": \"system\", \"content\": SYSTEM_PROMPT.format(context=context)},\n",
1499
- " {\"role\": \"user\", \"content\": f\"Question: {query}\\nProvide a detailed technical answer:\"}\n",
1500
- " ],\n",
1501
- " temperature=0.4,\n",
1502
- " max_tokens=2000\n",
1503
- " )\n",
1504
- " logger.info(f\"Response from meta-llama-3-70b-instruct: {response}\")\n",
1505
- " try:\n",
1506
- " result = response.choices[0].message.content\n",
1507
- " except Exception as e:\n",
1508
- " logger.error(f\"Error processing meta-llama-3-70b-instruct response: {str(e)}\")\n",
1509
- " result = \"Failed to process response from meta-llama-3-70b-instruct\"\n",
1510
- " elif model == \"llama3-70b-8192\":\n",
1511
- " result = get_groq_llama3_response(query)\n",
1512
- " logger.info(f\"Response from llama3-70b-8192: {result}\")\n",
1513
- " if result is None:\n",
1514
- " result = \"Failed to get response from llama3-70b-8192\"\n",
1515
- " # Append the model name to the response for clarity\n",
1516
- " # get the key name model from model mapping\n",
1517
- " for key, value in model_mapping.items():\n",
1518
- " if value == model:\n",
1519
- " model = key\n",
1520
- " result += f\"\\n\\n**Model:** {model}\"\n",
1521
- " return result\n",
1522
- " except Exception as e:\n",
1523
- " logger.error(f\"Generation Error: {str(e)}\")\n",
1524
- " return \"I'm unable to generate a response right now. Please try again later.\"\n",
1525
- "\n",
1526
- "def _postprocess_response(response: str) -> str:\n",
1527
- " response = re.sub(r\"\\[(.*?)\\]\", r\"[\\1](#)\", response)\n",
1528
- " response = re.sub(r\"\\*\\*([\\w-]+)\\*\\*\", r\"**\\1**\", response)\n",
1529
- " return response\n",
1530
- "\n",
1531
- "def get_groq_llama3_response(query: str) -> str:\n",
1532
- " \"\"\"Get response from Llama 3 on Groq Cloud.\"\"\"\n",
1533
- " api_key = os.getenv(\"GROQ_API_KEY\")\n",
1534
- " url = \"https://api.groq.com/openai/v1/chat/completions\"\n",
1535
- " \n",
1536
- " headers = {\n",
1537
- " \"Content-Type\": \"application/json\",\n",
1538
- " \"Authorization\": f\"Bearer {api_key}\"\n",
1539
- " }\n",
1540
- " \n",
1541
- " payload = {\n",
1542
- " \"model\": \"llama3-70b-8192\",\n",
1543
- " \"messages\": [\n",
1544
- " {\n",
1545
- " \"role\": \"user\",\n",
1546
- " \"content\": query\n",
1547
- " }\n",
1548
- " ]\n",
1549
- " }\n",
1550
- " \n",
1551
- " try:\n",
1552
- " response = requests.post(url, headers=headers, json=payload)\n",
1553
- " response.raise_for_status()\n",
1554
- " result = response.json()\n",
1555
- " logger.info(f\"Groq API Response: {result}\")\n",
1556
- " return result[\"choices\"][0][\"message\"][\"content\"]\n",
1557
- " except requests.exceptions.RequestException as e:\n",
1558
- " logger.error(f\"Groq API Error: {str(e)}\")\n",
1559
- " return \"An error occurred while contacting Groq's Llama 3 model.\"\n",
1560
- "# --- Pipeline ---\n",
1561
- "documents = load_and_chunk_data(data_file_name)\n",
1562
- "retriever = EnhancedRetriever(documents)\n",
1563
- "\n",
1564
- "def generate_response(question: str, model: str) -> str:\n",
1565
- " try:\n",
1566
- " context = retriever.retrieve(question)\n",
1567
- " return get_ai_response(question, context, model) if context else \"No relevant information found.\"\n",
1568
- " except Exception as e:\n",
1569
- " logger.error(f\"Pipeline Error: {str(e)}\")\n",
1570
- " return \"An error occurred processing your request.\"\n",
1571
- "\n",
1572
- "# --- Gradio Interface ---\n",
1573
- "# Define the mapping from display names to actual model identifiers\n",
1574
- "model_mapping = {\n",
1575
- " \"Gemini-2.0-Flash\": \"gemini-2.0-flash\",\n",
1576
- " \"Meta-llama-3-70b-instruct(GWDG)\": \"meta-llama-3-70b-instruct\",\n",
1577
- " \"llama3-70b-8192(Groq)\": \"llama3-70b-8192\"\n",
1578
- "}\n",
1579
- "\n",
1580
- "def chat_interface(question: str, history: List[Tuple[str, str]], display_model: str):\n",
1581
- " model = model_mapping.get(display_model, \"gemini-2.0-flash\") # Default to Gemini if not found\n",
1582
- " response = generate_response(question, model)\n",
1583
- " return \"\", history + [(question, response)]\n",
1584
- "\n",
1585
- "with gr.Blocks(title=\"AskNature BioRAG Expert\", theme=gr.themes.Soft()) as demo:\n",
1586
- " gr.Markdown(\"# 🌿 AskNature RAG-based Chatbot\")\n",
1587
- " with gr.Row():\n",
1588
- " chatbot = gr.Chatbot(label=\"Dialogue History\", height=500)\n",
1589
- " with gr.Row():\n",
1590
- " question = gr.Textbox(placeholder=\"Ask about biomimicry (e.g. 'How does Werewool use coral proteins to make fibers?')\", label=\"Inquiry\", scale=4)\n",
1591
- " model_selector = gr.Dropdown(choices=list(model_mapping.keys()), label=\"Generation Model\", value=\"Meta-llama-3-70b-instruct(GWDG)\")\n",
1592
- " clear_btn = gr.Button(\"Clear History\", variant=\"secondary\")\n",
1593
- "\n",
1594
- " gr.Markdown(\"\"\"\n",
1595
- " <div style=\"text-align: center; color: #4a7c59;\">\n",
1596
- " <small>Powered by AskNature's Database | \n",
1597
- " Explore nature's blueprints at <a href=\"https://asknature.org\">asknature.org</a></small>\n",
1598
- " </div>\"\"\")\n",
1599
- " \n",
1600
- " question.submit(chat_interface, [question, chatbot, model_selector], [question, chatbot])\n",
1601
- " clear_btn.click(lambda: [], None, chatbot)\n",
1602
- "\n",
1603
- "if __name__ == \"__main__\":\n",
1604
- " demo.launch(show_error=True)\n"
1605
- ]
1606
- },
1607
- {
1608
- "cell_type": "code",
1609
- "execution_count": null,
1610
- "metadata": {},
1611
- "outputs": [],
1612
- "source": []
1613
- },
1614
- {
1615
- "cell_type": "code",
1616
- "execution_count": null,
1617
- "metadata": {},
1618
- "outputs": [],
1619
- "source": []
1620
- },
1621
- {
1622
- "cell_type": "code",
1623
- "execution_count": null,
1624
- "metadata": {},
1625
- "outputs": [],
1626
- "source": []
1627
- }
1628
- ],
1629
- "metadata": {
1630
- "kernelspec": {
1631
- "display_name": "rag",
1632
- "language": "python",
1633
- "name": "python3"
1634
- },
1635
- "language_info": {
1636
- "codemirror_mode": {
1637
- "name": "ipython",
1638
- "version": 3
1639
- },
1640
- "file_extension": ".py",
1641
- "mimetype": "text/x-python",
1642
- "name": "python",
1643
- "nbconvert_exporter": "python",
1644
- "pygments_lexer": "ipython3",
1645
- "version": "3.12.8"
1646
- }
1647
- },
1648
- "nbformat": 4,
1649
- "nbformat_minor": 2
1650
- }