Azizahalq commited on
Commit
b7242c4
·
1 Parent(s): e562e41

Create app_gradio.py

Browse files
Files changed (1) hide show
  1. app_gradio.py +227 -0
app_gradio.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ MaterialMind (fixed corpus demo)
5
+ - Uses YOUR PDFs from ./sources
6
+ - Builds a tiny in-memory RAG index at startup (FastEmbed + cosine)
7
+ - Cloud LLM scores candidates 0..400 (four 0..100 subscores)
8
+ - Simple Gradio UI (no uploads)
9
+ """
10
+ import os, re, json, textwrap
11
+ from pathlib import Path
12
+ from typing import List, Tuple, Dict, Any
13
+
14
+ import gradio as gr
15
+ import requests
16
+
17
+ from rag_utils import (
18
+ build_index_from_dir, retrieve, format_context_and_cites
19
+ )
20
+
21
+ # -------------------- LLM client --------------------
22
+ PROVIDER = os.getenv("LLM_PROVIDER", "openai").lower() # "openai" | "together"
23
+ API_KEY = os.getenv("LLM_API_KEY", "")
24
+ MODEL = os.getenv("LLM_MODEL", "gpt-4o-mini") # e.g. Together: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"
25
+ TIMEOUT = int(os.getenv("LLM_TIMEOUT", "60"))
26
+
27
+ def call_llm(system: str, user: str) -> str:
28
+ if not API_KEY:
29
+ return "[Error] Missing LLM_API_KEY. Add a secret/env var."
30
+ if PROVIDER == "together":
31
+ base = "https://api.together.xyz/v1"
32
+ headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
33
+ else:
34
+ base = "https://api.openai.com/v1"
35
+ headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
36
+
37
+ payload = {
38
+ "model": MODEL,
39
+ "messages": [{"role":"system","content":system},{"role":"user","content":user}],
40
+ "temperature": 0.2,
41
+ }
42
+ r = requests.post(f"{base}/chat/completions", headers=headers, json=payload, timeout=TIMEOUT)
43
+ if r.status_code != 200:
44
+ return f"[Error] LLM HTTP {r.status_code}: {r.text[:500]}"
45
+ try:
46
+ return r.json()["choices"][0]["message"]["content"]
47
+ except Exception:
48
+ return f"[Error] Unexpected LLM response: {r.text[:500]}"
49
+
50
+ # -------------------- Prompting --------------------
51
+ SYSTEM_RULES = """You are MaterialMind, a general-purpose materials-selection assistant.
52
+ Return TWO things:
53
+
54
+ 1) A JSON block with EXACT schema:
55
+ {
56
+ "candidates": [
57
+ {
58
+ "name": "string",
59
+ "score": 0, // integer 0..400 (sum of four 0..100 subscores)
60
+ "subscores": { "performance": 0, "stability": 0, "cost": 0, "availability": 0 },
61
+ "reasons": ["string", "..."],
62
+ "tradeoffs": ["string", "..."],
63
+ "citations": ["[1]", "[4]"]
64
+ }
65
+ ]
66
+ }
67
+
68
+ SCORING (absolute, not weighted):
69
+ - performance (0..100): strength/stiffness/thermal range vs user targets
70
+ - stability (0..100): corrosion/oxidation/chem/UV/thermal/creep, environment fit
71
+ - cost (0..100): relative cost vs user budget (If budget is "Not important", set cost=100)
72
+ - availability(0..100): manufacturability, supply forms/lead time
73
+
74
+ Total score = performance + stability + cost + availability (0..400). Be conservative; do not invent data.
75
+
76
+ 2) After the JSON, add 3–6 concise bullets explaining trade-offs.
77
+
78
+ Rules:
79
+ - Use ONLY the provided context; cite like [n].
80
+ - If critical info is missing, state what to clarify.
81
+ - Keep units correct; state assumptions if needed.
82
+ """
83
+
84
+ ANSWER_TEMPLATE = """User constraints
85
+ - Application: {environment}
86
+ - Temperature: {temperature}
87
+ - Targets: UTS ≥ {min_uts} MPa, density ≤ {max_density} g/cm^3
88
+ - Budget: {budget} • Process: {process}
89
+ - Preferences: performance={pref_perf}, stability={pref_stab}, cost={pref_cost}, availability={pref_avail}
90
+
91
+ Task
92
+ Shortlist suitable materials and score them 0..400 using the four 0..100 subscores (see rules).
93
+ Explain trade-offs and include citations.
94
+
95
+ Context snippets (numbered)
96
+ {context}
97
+
98
+ Citations
99
+ {citations}
100
+
101
+ Now first output ONLY the JSON block. Then the bullet narrative.
102
+ """
103
+
104
+ def extract_json_block(text: str):
105
+ m = re.search(r"```json\s*(\{.*?\})\s*```", text, flags=re.S | re.I)
106
+ s = m.group(1) if m else None
107
+ if not s:
108
+ m2 = re.search(r"(\{(?:[^{}]|(?1))*\})", text, flags=re.S)
109
+ s = m2.group(1) if m2 else None
110
+ if not s: return None
111
+ try:
112
+ return json.loads(s)
113
+ except Exception:
114
+ last = s.rfind("}")
115
+ if last != -1:
116
+ try: return json.loads(s[:last+1])
117
+ except Exception: return None
118
+ return None
119
+
120
+ # -------------------- Build index once (your PDFs) --------------------
121
+ SOURCES_DIR = Path(os.getenv("SOURCES_DIR", "sources")).resolve()
122
+ INDEX = build_index_from_dir(SOURCES_DIR) # texts, metas, embs (L2-normalized)
123
+
124
+ # -------------------- UI callback --------------------
125
+ PREF_CHOICES = ["Very high", "High", "Medium", "Low", "Very low"]
126
+ COST_CHOICES = ["Not important", "High", "Medium", "Low", "Very low"]
127
+
128
+ def recommend(environment, temperature, min_uts, max_density, budget, process,
129
+ pref_perf, pref_stab, pref_cost, pref_avail, topk):
130
+
131
+ if INDEX["embs"].shape[0] == 0:
132
+ return "No context available. Add PDFs to ./sources and redeploy.", None, None
133
+
134
+ # Retrieval
135
+ q = (f"For {environment or 'general'} at {temperature or 'room temperature'}, shortlist materials that meet "
136
+ f"UTS ≥ {min_uts or '0'} MPa and density ≤ {max_density or '100'} g/cm^3; "
137
+ f"consider budget={budget or 'open'}, process={process or 'any'}.")
138
+ hits = retrieve(INDEX, q, k=int(topk))
139
+ if not hits:
140
+ return "No extractable context found (OCR may be needed).", None, None
141
+ ctx, cites = format_context_and_cites(hits)
142
+
143
+ # LLM
144
+ prompt = ANSWER_TEMPLATE.format(
145
+ environment=environment or "general",
146
+ temperature=temperature or "room temperature",
147
+ min_uts=min_uts or "0",
148
+ max_density=max_density or "100",
149
+ budget=budget or "open",
150
+ process=process or "any",
151
+ pref_perf=pref_perf, pref_stab=pref_stab, pref_cost=pref_cost, pref_avail=pref_avail,
152
+ context=ctx, citations=cites
153
+ )
154
+ raw = call_llm(SYSTEM_RULES, prompt)
155
+ parsed = extract_json_block(raw) if raw else None
156
+ cands = (parsed or {}).get("candidates", []) if parsed else []
157
+
158
+ # Format outputs
159
+ if not cands:
160
+ return raw, None, cites
161
+
162
+ headers = ["Rank","Material","Score","Performance","Stability","Cost","Availability","Top reasons"]
163
+ rows = []
164
+ for i, c in enumerate(sorted(cands, key=lambda x: x.get("score",0), reverse=True), 1):
165
+ ss = c.get("subscores", {})
166
+ reasons = " • ".join(c.get("reasons", [])[:3])
167
+ rows.append([i, c.get("name","?"), c.get("score",0),
168
+ ss.get("performance","—"), ss.get("stability","—"),
169
+ ss.get("cost","—"), ss.get("availability","—"), reasons])
170
+
171
+ # Markdown table
172
+ table_md = "| " + " | ".join(headers) + " |\n|" + " --- |"*len(headers) + "\n"
173
+ for r in rows:
174
+ table_md += "| " + " | ".join(str(x) for x in r) + " |\n"
175
+
176
+ # Cards
177
+ cards = []
178
+ for i, c in enumerate(sorted(cands, key=lambda x: x.get("score",0), reverse=True), 1):
179
+ ss = c.get("subscores", {})
180
+ card = f"**{i}. {c.get('name','?')}** \n"
181
+ card += f"Score {c.get('score',0)} (perf {ss.get('performance','—')}, stab {ss.get('stability','—')}, cost {ss.get('cost','—')}, avail {ss.get('availability','—')})\n\n"
182
+ if c.get("tradeoffs"):
183
+ card += "**Trade-offs:**\n- " + "\n- ".join(c["tradeoffs"]) + "\n\n"
184
+ if c.get("citations"):
185
+ card += "**Citations:** " + ", ".join(c["citations"])
186
+ cards.append(card)
187
+ cards_md = "\n---\n".join(cards)
188
+
189
+ return table_md + "\n\n" + raw, cards_md, cites
190
+
191
+ # -------------------- Gradio UI --------------------
192
+ with gr.Blocks(title="MaterialMind") as demo:
193
+ gr.Markdown("## MaterialMind — ranked materials shortlist with page-level citations")
194
+ with gr.Row():
195
+ environment = gr.Textbox(label="Application", placeholder="seawater / sour service / high-T oxidation")
196
+ temperature = gr.Textbox(label="Temperature", placeholder="e.g., 20–25 °C")
197
+ with gr.Row():
198
+ min_uts = gr.Textbox(label="Min UTS (MPa)", value="0")
199
+ max_density = gr.Textbox(label="Max density (g/cm³)", value="100")
200
+ with gr.Row():
201
+ budget = gr.Dropdown(["open","low","medium","high","Not important"], value="open", label="Budget")
202
+ process = gr.Textbox(label="Process", placeholder="wrought / casting / AM / any", value="any")
203
+
204
+ gr.Markdown("**Priorities (qualitative; scoring is absolute 0..100 each, total 0..400)**")
205
+ with gr.Row():
206
+ pref_perf = gr.Dropdown(["Very high","High","Medium","Low","Very low"], value="High", label="Performance")
207
+ pref_stab = gr.Dropdown(["Very high","High","Medium","Low","Very low"], value="High", label="Stability")
208
+ pref_cost = gr.Dropdown(["Not important","High","Medium","Low","Very low"], value="Medium", label="Cost")
209
+ pref_avail = gr.Dropdown(["Very high","High","Medium","Low","Very low"], value="Medium", label="Availability")
210
+
211
+ topk = gr.Slider(3, 10, step=1, value=5, label="Top-k context pages")
212
+
213
+ run_btn = gr.Button("Get ranked shortlist", variant="primary")
214
+ out_table = gr.Markdown(label="Shortlist & raw model output")
215
+ out_cards = gr.Markdown(label="Material cards")
216
+ out_cites = gr.Markdown(label="Citations (source mapping)")
217
+
218
+ run_btn.click(
219
+ recommend,
220
+ inputs=[environment, temperature, min_uts, max_density, budget, process,
221
+ pref_perf, pref_stab, pref_cost, pref_avail, topk],
222
+ outputs=[out_table, out_cards, out_cites],
223
+ api_name="recommend"
224
+ )
225
+
226
+ if __name__ == "__main__":
227
+ demo.launch()