AYI-NEDJIMI commited on
Commit
30984ac
·
verified ·
1 Parent(s): 526fb20

Initial RAG & LangChain Explorer Space with Gradio 5.50.0

Browse files
Files changed (3) hide show
  1. README.md +30 -6
  2. app.py +338 -0
  3. requirements.txt +4 -0
README.md CHANGED
@@ -1,12 +1,36 @@
1
  ---
2
- title: Rag Langchain Explorer
3
- emoji: 🏃
4
- colorFrom: green
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 6.5.1
8
  app_file: app.py
9
  pinned: false
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: RAG & LangChain Explorer
3
+ emoji: 🔍
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: "5.50.0"
8
  app_file: app.py
9
  pinned: false
10
+ datasets:
11
+ - AYI-NEDJIMI/rag-langchain-fr
12
+ - AYI-NEDJIMI/rag-langchain-en
13
  ---
14
 
15
+ # RAG & LangChain Explorer
16
+
17
+ An interactive explorer for RAG (Retrieval-Augmented Generation) and LangChain components.
18
+
19
+ Browse, search, and compare document loaders, text splitters, embedding models, vector stores, retrievers, and chains — in both French and English.
20
+
21
+ ## Features
22
+
23
+ - **Explorer**: Searchable table with category filtering
24
+ - **Details**: View full details for any component
25
+ - **Q&A**: Quick answers about RAG & LangChain concepts
26
+ - **Statistics**: Interactive Plotly charts by category and type
27
+ - **Bilingual**: Toggle between French and English
28
+
29
+ ## Datasets
30
+
31
+ - [AYI-NEDJIMI/rag-langchain-fr](https://huggingface.co/datasets/AYI-NEDJIMI/rag-langchain-fr)
32
+ - [AYI-NEDJIMI/rag-langchain-en](https://huggingface.co/datasets/AYI-NEDJIMI/rag-langchain-en)
33
+
34
+ ---
35
+
36
+ Built by [AYI-NEDJIMI Consultants](https://ayinedjimi-consultants.fr)
app.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import plotly.graph_objects as go
5
+ from datasets import load_dataset
6
+
7
+ # ---------------------------------------------------------------------------
8
+ # Data loading
9
+ # ---------------------------------------------------------------------------
10
+
11
+ COLUMNS = [
12
+ "id", "type", "category", "name", "content",
13
+ "details", "pros", "cons", "tools", "source_url", "language",
14
+ ]
15
+
16
+ def load_data():
17
+ """Load both FR and EN datasets and return as DataFrames."""
18
+ try:
19
+ ds_fr = load_dataset("AYI-NEDJIMI/rag-langchain-fr", split="train")
20
+ df_fr = ds_fr.to_pandas()
21
+ except Exception:
22
+ df_fr = pd.DataFrame(columns=COLUMNS)
23
+
24
+ try:
25
+ ds_en = load_dataset("AYI-NEDJIMI/rag-langchain-en", split="train")
26
+ df_en = ds_en.to_pandas()
27
+ except Exception:
28
+ df_en = pd.DataFrame(columns=COLUMNS)
29
+
30
+ return df_fr, df_en
31
+
32
+
33
+ DF_FR, DF_EN = load_data()
34
+
35
+ CATEGORIES = [
36
+ "document_loader", "text_splitter", "embedding_model",
37
+ "vector_store", "retriever", "chain",
38
+ ]
39
+
40
+ LABELS = {
41
+ "FR": {
42
+ "title": "RAG & LangChain Explorer",
43
+ "explorer": "Explorateur",
44
+ "details": "Détails",
45
+ "qna": "Q&R",
46
+ "statistics": "Statistiques",
47
+ "search": "Rechercher…",
48
+ "category": "Catégorie",
49
+ "all": "Toutes",
50
+ "select_item": "Sélectionner un élément",
51
+ "no_results": "Aucun résultat.",
52
+ "by_category": "Répartition par catégorie",
53
+ "by_type": "Répartition par type",
54
+ "items_per_cat": "Nombre d'éléments par catégorie",
55
+ "ask": "Posez votre question sur RAG / LangChain",
56
+ "answer": "Réponse",
57
+ "qa_placeholder": "Ex : Qu'est-ce qu'un Text Splitter ?",
58
+ "no_match": "Aucune correspondance trouvée. Essayez un autre terme.",
59
+ "name_col": "Nom",
60
+ "type_col": "Type",
61
+ "category_col": "Catégorie",
62
+ "content_lbl": "Contenu",
63
+ "details_lbl": "Détails",
64
+ "pros_lbl": "Avantages",
65
+ "cons_lbl": "Inconvénients",
66
+ "tools_lbl": "Outils",
67
+ "source_lbl": "Source",
68
+ },
69
+ "EN": {
70
+ "title": "RAG & LangChain Explorer",
71
+ "explorer": "Explorer",
72
+ "details": "Details",
73
+ "qna": "Q&A",
74
+ "statistics": "Statistics",
75
+ "search": "Search…",
76
+ "category": "Category",
77
+ "all": "All",
78
+ "select_item": "Select an item",
79
+ "no_results": "No results.",
80
+ "by_category": "Distribution by category",
81
+ "by_type": "Distribution by type",
82
+ "items_per_cat": "Number of items per category",
83
+ "ask": "Ask a question about RAG / LangChain",
84
+ "answer": "Answer",
85
+ "qa_placeholder": "E.g. What is a Text Splitter?",
86
+ "no_match": "No match found. Try another term.",
87
+ "name_col": "Name",
88
+ "type_col": "Type",
89
+ "category_col": "Category",
90
+ "content_lbl": "Content",
91
+ "details_lbl": "Details",
92
+ "pros_lbl": "Pros",
93
+ "cons_lbl": "Cons",
94
+ "tools_lbl": "Tools",
95
+ "source_lbl": "Source",
96
+ },
97
+ }
98
+
99
+ # ---------------------------------------------------------------------------
100
+ # Helpers
101
+ # ---------------------------------------------------------------------------
102
+
103
+ def get_df(lang: str) -> pd.DataFrame:
104
+ return DF_FR.copy() if lang == "FR" else DF_EN.copy()
105
+
106
+
107
+ def filter_table(search: str, category: str, lang: str):
108
+ df = get_df(lang)
109
+ if df.empty:
110
+ return pd.DataFrame()
111
+ if category and category not in ("Toutes", "All"):
112
+ df = df[df["category"] == category]
113
+ if search:
114
+ mask = df.apply(
115
+ lambda r: search.lower() in " ".join(r.astype(str)).lower(), axis=1
116
+ )
117
+ df = df[mask]
118
+ display_cols = ["name", "type", "category"]
119
+ available = [c for c in display_cols if c in df.columns]
120
+ return df[available].reset_index(drop=True)
121
+
122
+
123
+ def get_item_names(lang: str):
124
+ df = get_df(lang)
125
+ if df.empty:
126
+ return []
127
+ return sorted(df["name"].dropna().unique().tolist())
128
+
129
+
130
+ def get_item_details(name: str, lang: str):
131
+ L = LABELS[lang]
132
+ df = get_df(lang)
133
+ if df.empty or not name:
134
+ return L["no_results"]
135
+ row = df[df["name"] == name]
136
+ if row.empty:
137
+ return L["no_results"]
138
+ r = row.iloc[0]
139
+ parts = []
140
+ parts.append(f"## {r.get('name', '')}")
141
+ parts.append(f"**{L['category_col']}**: {r.get('category', '')}")
142
+ parts.append(f"**{L['type_col']}**: {r.get('type', '')}")
143
+ if pd.notna(r.get("content")):
144
+ parts.append(f"\n### {L['content_lbl']}\n{r['content']}")
145
+ if pd.notna(r.get("details")):
146
+ parts.append(f"\n### {L['details_lbl']}\n{r['details']}")
147
+ if pd.notna(r.get("pros")):
148
+ parts.append(f"\n### {L['pros_lbl']}\n{r['pros']}")
149
+ if pd.notna(r.get("cons")):
150
+ parts.append(f"\n### {L['cons_lbl']}\n{r['cons']}")
151
+ if pd.notna(r.get("tools")):
152
+ parts.append(f"\n### {L['tools_lbl']}\n{r['tools']}")
153
+ if pd.notna(r.get("source_url")):
154
+ parts.append(f"\n### {L['source_lbl']}\n[Link]({r['source_url']})")
155
+ return "\n".join(parts)
156
+
157
+
158
+ def answer_question(question: str, lang: str):
159
+ L = LABELS[lang]
160
+ if not question or not question.strip():
161
+ return ""
162
+ df = get_df(lang)
163
+ if df.empty:
164
+ return L["no_match"]
165
+ q = question.lower()
166
+ # Search across content-heavy columns
167
+ search_cols = ["name", "content", "details", "category", "type"]
168
+ scores = []
169
+ for _, row in df.iterrows():
170
+ text = " ".join(str(row.get(c, "")) for c in search_cols).lower()
171
+ score = sum(1 for w in q.split() if w in text)
172
+ scores.append(score)
173
+ df = df.copy()
174
+ df["_score"] = scores
175
+ best = df.sort_values("_score", ascending=False).head(3)
176
+ best = best[best["_score"] > 0]
177
+ if best.empty:
178
+ return L["no_match"]
179
+ parts = []
180
+ for _, r in best.iterrows():
181
+ parts.append(f"### {r.get('name', '')}")
182
+ parts.append(f"**{L['category_col']}**: {r.get('category', '')}")
183
+ if pd.notna(r.get("content")):
184
+ parts.append(f"{r['content'][:500]}")
185
+ parts.append("---")
186
+ return "\n".join(parts)
187
+
188
+
189
+ def make_category_chart(lang: str):
190
+ L = LABELS[lang]
191
+ df = get_df(lang)
192
+ if df.empty:
193
+ return go.Figure()
194
+ counts = df["category"].value_counts().reset_index()
195
+ counts.columns = ["category", "count"]
196
+ fig = px.bar(
197
+ counts, x="category", y="count",
198
+ title=L["items_per_cat"],
199
+ color="category",
200
+ color_discrete_sequence=px.colors.qualitative.Set2,
201
+ )
202
+ fig.update_layout(showlegend=False, xaxis_title="", yaxis_title="")
203
+ return fig
204
+
205
+
206
+ def make_type_chart(lang: str):
207
+ L = LABELS[lang]
208
+ df = get_df(lang)
209
+ if df.empty:
210
+ return go.Figure()
211
+ counts = df["type"].value_counts().reset_index()
212
+ counts.columns = ["type", "count"]
213
+ fig = px.pie(
214
+ counts, names="type", values="count",
215
+ title=L["by_type"],
216
+ color_discrete_sequence=px.colors.qualitative.Pastel,
217
+ )
218
+ return fig
219
+
220
+
221
+ def make_category_pie(lang: str):
222
+ L = LABELS[lang]
223
+ df = get_df(lang)
224
+ if df.empty:
225
+ return go.Figure()
226
+ counts = df["category"].value_counts().reset_index()
227
+ counts.columns = ["category", "count"]
228
+ fig = px.pie(
229
+ counts, names="category", values="count",
230
+ title=L["by_category"],
231
+ color_discrete_sequence=px.colors.qualitative.Set2,
232
+ )
233
+ return fig
234
+
235
+
236
+ # ---------------------------------------------------------------------------
237
+ # UI
238
+ # ---------------------------------------------------------------------------
239
+
240
+ FOOTER_HTML = """
241
+ <div style="text-align:center; padding:20px; margin-top:30px; border-top:1px solid #444; color:#888; font-size:0.9em;">
242
+ Built by <a href="https://ayinedjimi-consultants.fr" target="_blank"
243
+ style="color:#7c8aff; text-decoration:none;">AYI-NEDJIMI Consultants</a>
244
+ </div>
245
+ """
246
+
247
+ with gr.Blocks(
248
+ title="RAG & LangChain Explorer",
249
+ theme=gr.themes.Soft(primary_hue="blue", secondary_hue="purple"),
250
+ ) as demo:
251
+
252
+ gr.Markdown("# RAG & LangChain Explorer")
253
+
254
+ lang_toggle = gr.Radio(
255
+ choices=["FR", "EN"], value="FR", label="Language / Langue",
256
+ interactive=True,
257
+ )
258
+
259
+ with gr.Tabs():
260
+ # ---- Explorer tab ----
261
+ with gr.Tab("Explorer / Explorateur"):
262
+ with gr.Row():
263
+ search_box = gr.Textbox(
264
+ label="Search…", placeholder="Search…", scale=3,
265
+ )
266
+ cat_filter = gr.Dropdown(
267
+ choices=["Toutes"] + CATEGORIES,
268
+ value="Toutes",
269
+ label="Category",
270
+ scale=1,
271
+ )
272
+ table_output = gr.Dataframe(
273
+ value=filter_table("", "Toutes", "FR"),
274
+ interactive=False,
275
+ )
276
+
277
+ search_box.change(
278
+ filter_table, [search_box, cat_filter, lang_toggle], table_output,
279
+ )
280
+ cat_filter.change(
281
+ filter_table, [search_box, cat_filter, lang_toggle], table_output,
282
+ )
283
+ lang_toggle.change(
284
+ filter_table, [search_box, cat_filter, lang_toggle], table_output,
285
+ )
286
+
287
+ # ---- Details tab ----
288
+ with gr.Tab("Details / Détails"):
289
+ item_dropdown = gr.Dropdown(
290
+ choices=get_item_names("FR"),
291
+ label="Select an item / Sélectionner un élément",
292
+ interactive=True,
293
+ )
294
+ detail_output = gr.Markdown()
295
+
296
+ item_dropdown.change(
297
+ get_item_details, [item_dropdown, lang_toggle], detail_output,
298
+ )
299
+
300
+ def refresh_names(lang):
301
+ return gr.update(choices=get_item_names(lang), value=None)
302
+
303
+ lang_toggle.change(refresh_names, lang_toggle, item_dropdown)
304
+
305
+ # ---- Q&A tab ----
306
+ with gr.Tab("Q&A / Q&R"):
307
+ qa_input = gr.Textbox(
308
+ label="Ask a question / Posez votre question",
309
+ placeholder="E.g. What is a Text Splitter?",
310
+ lines=2,
311
+ )
312
+ qa_btn = gr.Button("Search / Rechercher")
313
+ qa_output = gr.Markdown()
314
+
315
+ qa_btn.click(answer_question, [qa_input, lang_toggle], qa_output)
316
+ qa_input.submit(answer_question, [qa_input, lang_toggle], qa_output)
317
+
318
+ # ---- Statistics tab ----
319
+ with gr.Tab("Statistics / Statistiques"):
320
+ with gr.Row():
321
+ cat_bar = gr.Plot(value=make_category_chart("FR"))
322
+ cat_pie = gr.Plot(value=make_category_pie("FR"))
323
+ with gr.Row():
324
+ type_pie = gr.Plot(value=make_type_chart("FR"))
325
+
326
+ def refresh_stats(lang):
327
+ return (
328
+ make_category_chart(lang),
329
+ make_category_pie(lang),
330
+ make_type_chart(lang),
331
+ )
332
+
333
+ lang_toggle.change(refresh_stats, lang_toggle, [cat_bar, cat_pie, type_pie])
334
+
335
+ gr.HTML(FOOTER_HTML)
336
+
337
+ if __name__ == "__main__":
338
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio==5.50.0
2
+ plotly
3
+ pandas
4
+ datasets