rigidhat commited on
Commit
3909b7f
·
verified ·
1 Parent(s): 5587ab4

v3: swap to Llama 4 Scout via Together-hosted endpoint (82% task, 72% Governance)

Browse files
Files changed (1) hide show
  1. app.py +98 -54
app.py CHANGED
@@ -1,9 +1,15 @@
1
- """HF Space demo for construction-code-cite (v1.1 LoRA).
2
-
3
- Uses transformers + peft (not mlx-lm) so it runs on Space Linux CPU.
4
- Fetches the OSHA 1926 corpus at cold boot from the public HF dataset,
5
- loads the LoRA adapter on top of Qwen 2.5 1.5B-Instruct, and serves
6
- strict-JSON hazard + citation predictions through Gradio.
 
 
 
 
 
 
7
  """
8
  from __future__ import annotations
9
 
@@ -15,8 +21,14 @@ from pathlib import Path
15
 
16
  import gradio as gr
17
 
18
- BASE_MODEL = os.environ.get("BASE_MODEL", "meta-llama/Llama-3.2-3B-Instruct")
19
- ADAPTER_REPO = os.environ.get("ADAPTER_REPO", "rigidhat/llama-3.2-3b-construction-codecite-v2")
 
 
 
 
 
 
20
  DATASET_REPO = os.environ.get("DATASET_REPO", "rigidhat/construction-code-corpus-v1")
21
  MAX_NEW_TOKENS = 384
22
  RAG_K = 5
@@ -153,59 +165,64 @@ def parse_json(raw: str) -> dict:
153
  return {}
154
 
155
 
156
- _PIPELINE = None
157
 
158
 
159
- def get_pipeline():
160
- global _PIPELINE
161
- if _PIPELINE is not None:
162
- return _PIPELINE
163
  corpus = ensure_corpus()
164
- verify = build_verifier(corpus)
165
- search = build_bm25(corpus)
 
 
166
 
167
- print(f"Loading base model: {BASE_MODEL}")
 
 
 
168
  import torch
169
  from peft import PeftModel
170
  from transformers import AutoModelForCausalLM, AutoTokenizer
171
 
172
- tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, trust_remote_code=True)
 
173
  base = AutoModelForCausalLM.from_pretrained(
174
- BASE_MODEL, torch_dtype=torch.float32, trust_remote_code=True
175
  )
176
- print(f"Loading LoRA adapter: {ADAPTER_REPO}")
177
- model = PeftModel.from_pretrained(base, ADAPTER_REPO)
178
-
179
- _PIPELINE = {
180
- "tokenizer": tokenizer,
181
- "model": model,
182
- "search": search,
183
- "verify": verify,
184
- "torch": torch,
185
- }
186
- return _PIPELINE
187
-
188
 
189
- def format_candidates(hits) -> str:
190
- if not hits:
191
- return "(no high-confidence candidates)"
192
- return "\n".join(f"- {h['section']}: {h['heading'][:80]}" for h in hits)
193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
- def predict(narrative: str):
196
- if not (narrative or "").strip():
197
- return "{}", "(paste an incident narrative first)", "—"
198
- t0 = time.time()
199
- pipe = get_pipeline()
200
- hits = pipe["search"](narrative, k=RAG_K)
201
- prompt = PROMPT.format(candidates=format_candidates(hits), narrative=narrative[:1800])
202
 
 
 
 
203
  messages = [{"role": "user", "content": prompt}]
204
  chat = pipe["tokenizer"].apply_chat_template(
205
  messages, add_generation_prompt=True, tokenize=False
206
  )
207
  inputs = pipe["tokenizer"](chat, return_tensors="pt")
208
-
209
  with pipe["torch"].no_grad():
210
  out = pipe["model"].generate(
211
  **inputs,
@@ -215,17 +232,41 @@ def predict(narrative: str):
215
  )
216
  generated = out[0][inputs["input_ids"].shape[1]:]
217
  raw = pipe["tokenizer"].decode(generated, skip_special_tokens=True)
218
- parsed = parse_json(raw)
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  if parsed and "citations" in parsed:
221
  for c in parsed["citations"]:
222
- is_valid, heading = pipe["verify"](c.get("standard", ""))
223
  c["verified"] = is_valid
224
  if heading and not c.get("section_heading"):
225
  c["section_heading"] = heading
226
 
227
  rag_view = "\n".join(f"- {h['section']}: {h['heading'][:80]}" for h in hits)
228
- return json.dumps(parsed, indent=2), rag_view, f"{time.time() - t0:.2f}s"
229
 
230
 
231
  EXAMPLES = [
@@ -236,14 +277,14 @@ EXAMPLES = [
236
 
237
 
238
  with gr.Blocks(title="Construction Code-Citation") as demo:
239
- gr.Markdown("# Construction Code-Citation Model (v2 · AutoScientist)")
240
  gr.Markdown(
241
- "Llama 3.2 3B fine-tuned by **AutoScientist** on OSHA Severe Injury Reports "
242
- "for the [Adaption Labs AutoScientist Challenge](https://adaptionlabs.ai/auto-scientist) "
243
- "\"All Other Domains\" category. **77% win rate** vs base Llama 3.2 3B on our test set. "
244
- "Given a construction-site incident narrative, returns strict JSON with OIICS hazard "
245
- "codes plus OSHA 29 CFR 1926 citations, verifier-grounded against the corpus. "
246
- "First request downloads the base model (~6 GB, one-time)."
247
  )
248
  with gr.Row():
249
  with gr.Column():
@@ -257,13 +298,16 @@ with gr.Blocks(title="Construction Code-Citation") as demo:
257
  with gr.Column():
258
  output_json = gr.Code(label="Hazards + Citations (JSON)", language="json")
259
  rag_view = gr.Textbox(label="OSHA 1926 RAG candidates (BM25)", lines=6)
260
- elapsed = gr.Textbox(label="Latency", lines=1)
 
 
261
 
262
- submit.click(predict, inputs=[narrative], outputs=[output_json, rag_view, elapsed])
263
 
264
  gr.Markdown(
265
  "**Artifacts:** "
266
  "[Dataset](https://huggingface.co/datasets/rigidhat/construction-code-corpus-v1) · "
 
267
  "[v2 Model (Llama 3.2 3B · AutoScientist)](https://huggingface.co/rigidhat/llama-3.2-3b-construction-codecite-v2) · "
268
  "[v1 Baseline (Qwen 2.5 1.5B)](https://huggingface.co/rigidhat/qwen-2.5-construction-codecite-v1) · "
269
  "[Source](https://github.com/snakezilla/construction-code-llm)"
 
1
+ """HF Space demo for construction-code-cite (v3 · Llama 4 Scout 17B-16E).
2
+
3
+ The v3 model is 109B total params (17B active MoE) and does not fit in-Space.
4
+ Inference goes to Together AI's hosted endpoint; the Space runs the RAG
5
+ pipeline + verifier + Gradio UI only. If TOGETHER_API_KEY is missing or the
6
+ endpoint returns an error, we fall back to the v2 (Llama 3.2 3B) in-Space
7
+ adapter so the demo never goes dark.
8
+
9
+ Set the following secrets in the Space:
10
+ - TOGETHER_API_KEY (required for v3)
11
+ - V3_MODEL_ID (default: rigidhat/llama-4-scout-17b-construction-codecite-v3)
12
+ - FALLBACK_ADAPTER (default: rigidhat/llama-3.2-3b-construction-codecite-v2)
13
  """
14
  from __future__ import annotations
15
 
 
21
 
22
  import gradio as gr
23
 
24
+ V3_MODEL_ID = os.environ.get(
25
+ "V3_MODEL_ID", "rigidhat/llama-4-scout-17b-construction-codecite-v3"
26
+ )
27
+ TOGETHER_API_KEY = os.environ.get("TOGETHER_API_KEY", "")
28
+ FALLBACK_BASE = os.environ.get("FALLBACK_BASE", "meta-llama/Llama-3.2-3B-Instruct")
29
+ FALLBACK_ADAPTER = os.environ.get(
30
+ "FALLBACK_ADAPTER", "rigidhat/llama-3.2-3b-construction-codecite-v2"
31
+ )
32
  DATASET_REPO = os.environ.get("DATASET_REPO", "rigidhat/construction-code-corpus-v1")
33
  MAX_NEW_TOKENS = 384
34
  RAG_K = 5
 
165
  return {}
166
 
167
 
168
+ _STATE = {"search": None, "verify": None, "fallback": None}
169
 
170
 
171
+ def get_search_verify():
172
+ if _STATE["search"] is not None:
173
+ return _STATE["search"], _STATE["verify"]
 
174
  corpus = ensure_corpus()
175
+ _STATE["search"] = build_bm25(corpus)
176
+ _STATE["verify"] = build_verifier(corpus)
177
+ return _STATE["search"], _STATE["verify"]
178
+
179
 
180
+ def get_fallback():
181
+ """Lazy-load v2 in-Space adapter as the fallback path."""
182
+ if _STATE["fallback"] is not None:
183
+ return _STATE["fallback"]
184
  import torch
185
  from peft import PeftModel
186
  from transformers import AutoModelForCausalLM, AutoTokenizer
187
 
188
+ print(f"Loading fallback base: {FALLBACK_BASE}")
189
+ tokenizer = AutoTokenizer.from_pretrained(FALLBACK_BASE, trust_remote_code=True)
190
  base = AutoModelForCausalLM.from_pretrained(
191
+ FALLBACK_BASE, torch_dtype=torch.float32, trust_remote_code=True
192
  )
193
+ print(f"Loading fallback adapter: {FALLBACK_ADAPTER}")
194
+ model = PeftModel.from_pretrained(base, FALLBACK_ADAPTER)
195
+ _STATE["fallback"] = {"tokenizer": tokenizer, "model": model, "torch": torch}
196
+ return _STATE["fallback"]
 
 
 
 
 
 
 
 
197
 
 
 
 
 
198
 
199
+ def generate_together(prompt: str) -> tuple[str, str]:
200
+ """Call Together AI hosted endpoint. Returns (text, path_label)."""
201
+ if not TOGETHER_API_KEY:
202
+ raise RuntimeError("TOGETHER_API_KEY not set")
203
+ try:
204
+ from together import Together
205
+ except ImportError as e:
206
+ raise RuntimeError(f"together package not installed: {e}")
207
+
208
+ client = Together(api_key=TOGETHER_API_KEY)
209
+ response = client.chat.completions.create(
210
+ model=V3_MODEL_ID,
211
+ messages=[{"role": "user", "content": prompt}],
212
+ max_tokens=MAX_NEW_TOKENS,
213
+ temperature=0.0,
214
+ )
215
+ return response.choices[0].message.content, "v3 · Llama 4 Scout 17B-16E (Together)"
216
 
 
 
 
 
 
 
 
217
 
218
+ def generate_fallback(prompt: str) -> tuple[str, str]:
219
+ """Fall back to v2 in-Space."""
220
+ pipe = get_fallback()
221
  messages = [{"role": "user", "content": prompt}]
222
  chat = pipe["tokenizer"].apply_chat_template(
223
  messages, add_generation_prompt=True, tokenize=False
224
  )
225
  inputs = pipe["tokenizer"](chat, return_tensors="pt")
 
226
  with pipe["torch"].no_grad():
227
  out = pipe["model"].generate(
228
  **inputs,
 
232
  )
233
  generated = out[0][inputs["input_ids"].shape[1]:]
234
  raw = pipe["tokenizer"].decode(generated, skip_special_tokens=True)
235
+ return raw, "v2 · Llama 3.2 3B (in-Space fallback)"
236
 
237
+
238
+ def format_candidates(hits) -> str:
239
+ if not hits:
240
+ return "(no high-confidence candidates)"
241
+ return "\n".join(f"- {h['section']}: {h['heading'][:80]}" for h in hits)
242
+
243
+
244
+ def predict(narrative: str):
245
+ if not (narrative or "").strip():
246
+ return "{}", "(paste an incident narrative first)", "—", "—"
247
+ t0 = time.time()
248
+ search, verify = get_search_verify()
249
+ hits = search(narrative, k=RAG_K)
250
+ prompt = PROMPT.format(candidates=format_candidates(hits), narrative=narrative[:1800])
251
+
252
+ path_label = ""
253
+ raw = ""
254
+ try:
255
+ raw, path_label = generate_together(prompt)
256
+ except Exception as e:
257
+ print(f"Together path failed: {e}. Falling back to v2 in-Space.")
258
+ raw, path_label = generate_fallback(prompt)
259
+
260
+ parsed = parse_json(raw)
261
  if parsed and "citations" in parsed:
262
  for c in parsed["citations"]:
263
+ is_valid, heading = verify(c.get("standard", ""))
264
  c["verified"] = is_valid
265
  if heading and not c.get("section_heading"):
266
  c["section_heading"] = heading
267
 
268
  rag_view = "\n".join(f"- {h['section']}: {h['heading'][:80]}" for h in hits)
269
+ return json.dumps(parsed, indent=2), rag_view, f"{time.time() - t0:.2f}s", path_label
270
 
271
 
272
  EXAMPLES = [
 
277
 
278
 
279
  with gr.Blocks(title="Construction Code-Citation") as demo:
280
+ gr.Markdown("# Construction Code-Citation Model (v3 · Llama 4 Scout 17B-16E · AutoScientist)")
281
  gr.Markdown(
282
+ "Llama 4 Scout 17B-16E (MoE) fine-tuned by **AutoScientist** on OSHA Severe "
283
+ "Injury Reports for the [Adaption Labs AutoScientist Challenge](https://adaptionlabs.ai/auto-scientist) "
284
+ "\"All Other Domains\" category. Given a construction-site incident narrative, "
285
+ "returns strict JSON with OIICS hazard codes plus OSHA 29 CFR 1926 citations, "
286
+ "verifier-grounded against the corpus. **Inference via Together AI hosted "
287
+ "endpoint** v2 (Llama 3.2 3B) auto-falls-back if the endpoint is unavailable."
288
  )
289
  with gr.Row():
290
  with gr.Column():
 
298
  with gr.Column():
299
  output_json = gr.Code(label="Hazards + Citations (JSON)", language="json")
300
  rag_view = gr.Textbox(label="OSHA 1926 RAG candidates (BM25)", lines=6)
301
+ with gr.Row():
302
+ elapsed = gr.Textbox(label="Latency", lines=1)
303
+ path = gr.Textbox(label="Model path", lines=1)
304
 
305
+ submit.click(predict, inputs=[narrative], outputs=[output_json, rag_view, elapsed, path])
306
 
307
  gr.Markdown(
308
  "**Artifacts:** "
309
  "[Dataset](https://huggingface.co/datasets/rigidhat/construction-code-corpus-v1) · "
310
+ "[v3 Model (Llama 4 Scout 17B-16E · AutoScientist)](https://huggingface.co/rigidhat/llama-4-scout-17b-construction-codecite-v3) · "
311
  "[v2 Model (Llama 3.2 3B · AutoScientist)](https://huggingface.co/rigidhat/llama-3.2-3b-construction-codecite-v2) · "
312
  "[v1 Baseline (Qwen 2.5 1.5B)](https://huggingface.co/rigidhat/qwen-2.5-construction-codecite-v1) · "
313
  "[Source](https://github.com/snakezilla/construction-code-llm)"