feat: connect NVIDIA Nemotron evidence reviewer
Browse filesBuilt with OpenAI Codex: connect the verified private Nemotron Mini 4B ZeroGPU reviewer and publish the GitHub link.
- FIELD_NOTES.md +14 -0
- README.md +10 -4
- app.py +32 -2
- frontend/app.js +6 -0
- frontend/index.html +1 -0
- frontend/styles.css +1 -0
- src/packetcourt/models.py +9 -0
- src/packetcourt/remote_nemotron.py +24 -0
- tests/test_audit.py +1 -0
FIELD_NOTES.md
CHANGED
|
@@ -35,6 +35,10 @@ The system deliberately separates three responsibilities:
|
|
| 35 |
The models can read and route an investigation. They cannot silently invent a
|
| 36 |
nutrition value or override the evidence standard.
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
## What the investigation agent does
|
| 39 |
|
| 40 |
Each packet creates a claim-dependent investigation plan. A `NO ADDED SUGAR`
|
|
@@ -62,6 +66,15 @@ a stratified held-out split. PacketCourt only enables the router after its
|
|
| 62 |
measured result is recorded in the model card and its suggestions remain
|
| 63 |
bounded by deterministic policy fallbacks.
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
## Persuasion Gap
|
| 66 |
|
| 67 |
Claim verification alone was not enough. A `HIGH PROTEIN` claim can be
|
|
@@ -79,6 +92,7 @@ the decision with the user.
|
|
| 79 |
- `10` transparent investigation traces are exported.
|
| 80 |
- The vision model has `1.30B` parameters.
|
| 81 |
- The fine-tuned evidence router has approximately `4.4M` parameters.
|
|
|
|
| 82 |
- The complete product interface is responsive and built on Gradio.
|
| 83 |
|
| 84 |
## What PacketCourt refuses to claim
|
|
|
|
| 35 |
The models can read and route an investigation. They cannot silently invent a
|
| 36 |
nutrition value or override the evidence standard.
|
| 37 |
|
| 38 |
+
NVIDIA `Nemotron-Mini-4B-Instruct` performs a second, independent review after
|
| 39 |
+
the investigation plan completes. It can identify the highest-priority missing
|
| 40 |
+
evidence, but it cannot alter PacketCourt's deterministic verdict.
|
| 41 |
+
|
| 42 |
## What the investigation agent does
|
| 43 |
|
| 44 |
Each packet creates a claim-dependent investigation plan. A `NO ADDED SUGAR`
|
|
|
|
| 66 |
measured result is recorded in the model card and its suggestions remain
|
| 67 |
bounded by deterministic policy fallbacks.
|
| 68 |
|
| 69 |
+
## A failed Nemotron deployment
|
| 70 |
+
|
| 71 |
+
The first NVIDIA reviewer used `NVIDIA-Nemotron-3-Nano-4B-BF16`. A real
|
| 72 |
+
ZeroGPU probe failed because the hybrid Mamba runtime required a specialized
|
| 73 |
+
CUDA build unavailable in the standard Gradio image. Rather than claim a model
|
| 74 |
+
that did not run, PacketCourt switched to `Nemotron-Mini-4B-Instruct`. The
|
| 75 |
+
replacement completed a real ZeroGPU evidence-gap review before it was
|
| 76 |
+
connected to the product.
|
| 77 |
+
|
| 78 |
## Persuasion Gap
|
| 79 |
|
| 80 |
Claim verification alone was not enough. A `HIGH PROTEIN` claim can be
|
|
|
|
| 92 |
- `10` transparent investigation traces are exported.
|
| 93 |
- The vision model has `1.30B` parameters.
|
| 94 |
- The fine-tuned evidence router has approximately `4.4M` parameters.
|
| 95 |
+
- The independent NVIDIA Nemotron reviewer has approximately `4B` parameters.
|
| 96 |
- The complete product interface is responsive and built on Gradio.
|
| 97 |
|
| 98 |
## What PacketCourt refuses to claim
|
README.md
CHANGED
|
@@ -12,6 +12,7 @@ tags:
|
|
| 12 |
- track:backyard
|
| 13 |
- sponsor:openbmb
|
| 14 |
- sponsor:openai
|
|
|
|
| 15 |
- achievement:offbrand
|
| 16 |
- achievement:tiny
|
| 17 |
- achievement:sharing
|
|
@@ -55,6 +56,8 @@ flowchart LR
|
|
| 55 |
M --> A["Investigation agent"]
|
| 56 |
A --> FR["Fine-tuned evidence router<br/>4.4M parameters"]
|
| 57 |
FR --> A
|
|
|
|
|
|
|
| 58 |
A --> P["Deterministic evidence parser<br/>CPU"]
|
| 59 |
P --> C["Claim-to-evidence audit"]
|
| 60 |
P --> N["Whole-packet nutrition math"]
|
|
@@ -75,10 +78,11 @@ flowchart LR
|
|
| 75 |
|
| 76 |
Photo transcription uses the 1.30B-parameter OpenBMB `MiniCPM-V-4.6` through
|
| 77 |
a private ZeroGPU companion. A fine-tuned 4.4M-parameter evidence router
|
| 78 |
-
selects the investigation tools required by each claim.
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
| 82 |
|
| 83 |
## What It Audits
|
| 84 |
|
|
@@ -150,11 +154,13 @@ Current deterministic evaluation result:
|
|
| 150 |
|
| 151 |
- Main private product: https://huggingface.co/spaces/build-small-hackathon/packetcourt
|
| 152 |
- Private OpenBMB ZeroGPU vision companion: https://huggingface.co/spaces/build-small-hackathon/packetcourt-vision
|
|
|
|
| 153 |
- Private golden evaluation dataset: https://huggingface.co/datasets/build-small-hackathon/packetcourt-golden-cases
|
| 154 |
- Public transparent agent traces: https://huggingface.co/datasets/build-small-hackathon/packetcourt-traces
|
| 155 |
- Fine-tuned evidence router: https://huggingface.co/build-small-hackathon/packetcourt-evidence-router
|
| 156 |
- Public router training set: https://huggingface.co/datasets/build-small-hackathon/packetcourt-router-training
|
| 157 |
- Public Field Notes report: https://huggingface.co/datasets/build-small-hackathon/packetcourt-field-notes
|
|
|
|
| 158 |
|
| 159 |
## Safety Boundary
|
| 160 |
|
|
|
|
| 12 |
- track:backyard
|
| 13 |
- sponsor:openbmb
|
| 14 |
- sponsor:openai
|
| 15 |
+
- sponsor:nvidia
|
| 16 |
- achievement:offbrand
|
| 17 |
- achievement:tiny
|
| 18 |
- achievement:sharing
|
|
|
|
| 56 |
M --> A["Investigation agent"]
|
| 57 |
A --> FR["Fine-tuned evidence router<br/>4.4M parameters"]
|
| 58 |
FR --> A
|
| 59 |
+
A --> NV["NVIDIA Nemotron Mini 4B<br/>Independent evidence-gap reviewer"]
|
| 60 |
+
NV --> A
|
| 61 |
A --> P["Deterministic evidence parser<br/>CPU"]
|
| 62 |
P --> C["Claim-to-evidence audit"]
|
| 63 |
P --> N["Whole-packet nutrition math"]
|
|
|
|
| 78 |
|
| 79 |
Photo transcription uses the 1.30B-parameter OpenBMB `MiniCPM-V-4.6` through
|
| 80 |
a private ZeroGPU companion. A fine-tuned 4.4M-parameter evidence router
|
| 81 |
+
selects the investigation tools required by each claim. NVIDIA
|
| 82 |
+
`Nemotron-Mini-4B-Instruct` independently reviews the completed investigation
|
| 83 |
+
for evidence gaps. The main CPU Space performs deterministic evidence auditing,
|
| 84 |
+
whole-packet calculations, persuasion-gap analysis, and refusals. ZeroGPU is
|
| 85 |
+
requested only while running the vision witness or Nemotron reviewer.
|
| 86 |
|
| 87 |
## What It Audits
|
| 88 |
|
|
|
|
| 154 |
|
| 155 |
- Main private product: https://huggingface.co/spaces/build-small-hackathon/packetcourt
|
| 156 |
- Private OpenBMB ZeroGPU vision companion: https://huggingface.co/spaces/build-small-hackathon/packetcourt-vision
|
| 157 |
+
- Private NVIDIA Nemotron reviewer: https://huggingface.co/spaces/build-small-hackathon/packetcourt-nemotron
|
| 158 |
- Private golden evaluation dataset: https://huggingface.co/datasets/build-small-hackathon/packetcourt-golden-cases
|
| 159 |
- Public transparent agent traces: https://huggingface.co/datasets/build-small-hackathon/packetcourt-traces
|
| 160 |
- Fine-tuned evidence router: https://huggingface.co/build-small-hackathon/packetcourt-evidence-router
|
| 161 |
- Public router training set: https://huggingface.co/datasets/build-small-hackathon/packetcourt-router-training
|
| 162 |
- Public Field Notes report: https://huggingface.co/datasets/build-small-hackathon/packetcourt-field-notes
|
| 163 |
+
- Public Codex-attributed GitHub repository: https://github.com/N-45div/PacketCourt
|
| 164 |
|
| 165 |
## Safety Boundary
|
| 166 |
|
app.py
CHANGED
|
@@ -17,6 +17,8 @@ sys.path.insert(0, str(ROOT / "src"))
|
|
| 17 |
from packetcourt import audit_packet
|
| 18 |
from packetcourt.ocr import extract_text
|
| 19 |
from packetcourt.remote_vision import extract_remote, is_configured
|
|
|
|
|
|
|
| 20 |
from packetcourt.samples import SAMPLES
|
| 21 |
from packetcourt.vlm import model_status
|
| 22 |
|
|
@@ -26,6 +28,29 @@ class AuditRequest(BaseModel):
|
|
| 26 |
back_text: str
|
| 27 |
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
def build_gradio_engine() -> gr.Blocks:
|
| 30 |
with gr.Blocks(title="PacketCourt Engine") as engine:
|
| 31 |
gr.Markdown(
|
|
@@ -37,7 +62,7 @@ def build_gradio_engine() -> gr.Blocks:
|
|
| 37 |
back = gr.Textbox(label="Back-label evidence")
|
| 38 |
output = gr.JSON(label="Evidence case")
|
| 39 |
gr.Button("Audit").click(
|
| 40 |
-
lambda front_text, back_text:
|
| 41 |
[front, back],
|
| 42 |
output,
|
| 43 |
)
|
|
@@ -68,6 +93,11 @@ def model() -> dict:
|
|
| 68 |
if os.getenv("PACKETCOURT_ROUTER", "0") == "1"
|
| 69 |
else "deterministic fallback"
|
| 70 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
if is_configured():
|
| 72 |
status.update(
|
| 73 |
enabled=True,
|
|
@@ -79,7 +109,7 @@ def model() -> dict:
|
|
| 79 |
|
| 80 |
@app.post("/api/audit")
|
| 81 |
def audit(request: AuditRequest) -> dict:
|
| 82 |
-
return
|
| 83 |
|
| 84 |
|
| 85 |
@app.post("/api/ocr")
|
|
|
|
| 17 |
from packetcourt import audit_packet
|
| 18 |
from packetcourt.ocr import extract_text
|
| 19 |
from packetcourt.remote_vision import extract_remote, is_configured
|
| 20 |
+
from packetcourt.remote_nemotron import is_configured as nemotron_is_configured
|
| 21 |
+
from packetcourt.remote_nemotron import review as nemotron_review
|
| 22 |
from packetcourt.samples import SAMPLES
|
| 23 |
from packetcourt.vlm import model_status
|
| 24 |
|
|
|
|
| 28 |
back_text: str
|
| 29 |
|
| 30 |
|
| 31 |
+
def run_audit(front_text: str, back_text: str):
|
| 32 |
+
result = audit_packet(front_text, back_text)
|
| 33 |
+
if not nemotron_is_configured():
|
| 34 |
+
return result
|
| 35 |
+
snapshot = {
|
| 36 |
+
"claims": [claim.model_dump(mode="json") for claim in result.claims],
|
| 37 |
+
"investigation": result.investigation.model_dump(),
|
| 38 |
+
"nutrition": result.nutrition.model_dump(),
|
| 39 |
+
"ingredients_found": bool(result.ingredients),
|
| 40 |
+
"expiry": result.expiry.model_dump(),
|
| 41 |
+
"limitations": result.limitations,
|
| 42 |
+
}
|
| 43 |
+
try:
|
| 44 |
+
result.agent_review = nemotron_review(snapshot)
|
| 45 |
+
except Exception as exc:
|
| 46 |
+
result.agent_review = {
|
| 47 |
+
"status": "UNAVAILABLE",
|
| 48 |
+
"rationale": f"Nemotron review unavailable: {type(exc).__name__}",
|
| 49 |
+
"model": "nvidia/Nemotron-Mini-4B-Instruct",
|
| 50 |
+
}
|
| 51 |
+
return result
|
| 52 |
+
|
| 53 |
+
|
| 54 |
def build_gradio_engine() -> gr.Blocks:
|
| 55 |
with gr.Blocks(title="PacketCourt Engine") as engine:
|
| 56 |
gr.Markdown(
|
|
|
|
| 62 |
back = gr.Textbox(label="Back-label evidence")
|
| 63 |
output = gr.JSON(label="Evidence case")
|
| 64 |
gr.Button("Audit").click(
|
| 65 |
+
lambda front_text, back_text: run_audit(front_text, back_text).model_dump(mode="json"),
|
| 66 |
[front, back],
|
| 67 |
output,
|
| 68 |
)
|
|
|
|
| 93 |
if os.getenv("PACKETCOURT_ROUTER", "0") == "1"
|
| 94 |
else "deterministic fallback"
|
| 95 |
)
|
| 96 |
+
status["nemotron_reviewer"] = (
|
| 97 |
+
"nvidia/Nemotron-Mini-4B-Instruct"
|
| 98 |
+
if nemotron_is_configured()
|
| 99 |
+
else "not configured"
|
| 100 |
+
)
|
| 101 |
if is_configured():
|
| 102 |
status.update(
|
| 103 |
enabled=True,
|
|
|
|
| 109 |
|
| 110 |
@app.post("/api/audit")
|
| 111 |
def audit(request: AuditRequest) -> dict:
|
| 112 |
+
return run_audit(request.front_text, request.back_text).model_dump(mode="json")
|
| 113 |
|
| 114 |
|
| 115 |
@app.post("/api/ocr")
|
frontend/app.js
CHANGED
|
@@ -59,6 +59,12 @@ function render(data) {
|
|
| 59 |
$("#missing-evidence").textContent = data.investigation.missing_evidence.length
|
| 60 |
? data.investigation.missing_evidence.join(" · ")
|
| 61 |
: "None. The required evidence path completed.";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
$("#claim-grid").innerHTML = data.claims.length
|
| 63 |
? data.claims.map((claim) => `
|
| 64 |
<article class="claim-card ${verdictClass[claim.verdict]}">
|
|
|
|
| 59 |
$("#missing-evidence").textContent = data.investigation.missing_evidence.length
|
| 60 |
? data.investigation.missing_evidence.join(" · ")
|
| 61 |
: "None. The required evidence path completed.";
|
| 62 |
+
const review = data.agent_review;
|
| 63 |
+
$("#nemotron-review").innerHTML = review.status === "NOT_REQUESTED"
|
| 64 |
+
? ""
|
| 65 |
+
: `<b>NVIDIA NEMOTRON INDEPENDENT REVIEW · ${escapeHtml(review.status)}</b>
|
| 66 |
+
<strong>${escapeHtml(review.priority || "No additional action requested.")}</strong>
|
| 67 |
+
<span>${escapeHtml(review.evidence_request || review.rationale)}</span>`;
|
| 68 |
$("#claim-grid").innerHTML = data.claims.length
|
| 69 |
? data.claims.map((claim) => `
|
| 70 |
<article class="claim-card ${verdictClass[claim.verdict]}">
|
frontend/index.html
CHANGED
|
@@ -104,6 +104,7 @@
|
|
| 104 |
<p><b>STOP REASON</b><span id="stop-reason"></span></p>
|
| 105 |
<p><b>MISSING EVIDENCE</b><span id="missing-evidence"></span></p>
|
| 106 |
</div>
|
|
|
|
| 107 |
</section>
|
| 108 |
<section class="gap-section">
|
| 109 |
<div class="gap-heading"><p class="kicker">PERSUASION GAP</p><h3>Material context the front leaves quiet.</h3></div>
|
|
|
|
| 104 |
<p><b>STOP REASON</b><span id="stop-reason"></span></p>
|
| 105 |
<p><b>MISSING EVIDENCE</b><span id="missing-evidence"></span></p>
|
| 106 |
</div>
|
| 107 |
+
<div class="nemotron-review" id="nemotron-review"></div>
|
| 108 |
</section>
|
| 109 |
<section class="gap-section">
|
| 110 |
<div class="gap-heading"><p class="kicker">PERSUASION GAP</p><h3>Material context the front leaves quiet.</h3></div>
|
frontend/styles.css
CHANGED
|
@@ -15,6 +15,7 @@ main{max-width:1320px;margin:auto;padding:0 4vw}.hero{min-height:670px;display:g
|
|
| 15 |
.sample-card{padding:24px;background:var(--cream);border:1px solid var(--line);border-radius:16px;cursor:pointer;text-align:left;transition:.2s}.sample-card:hover{border-color:var(--red);transform:translateY(-3px)}.sample-card b{display:block;font-size:19px;margin-bottom:8px}.sample-card span{font-size:13px;color:var(--muted)}
|
| 16 |
.results{border-top:1px solid var(--line)}.hidden{display:none}.case-score{width:120px;height:120px;border:1px solid var(--ink);border-radius:50%;display:flex;align-items:center;justify-content:center;gap:8px}.case-score span{font:800 45px "Playfair Display"}.case-score small{font:500 8px/1.4 "DM Mono"}
|
| 17 |
.agent-section{margin-bottom:28px;padding:26px;border:1px solid var(--line);background:var(--cream);border-radius:18px}.agent-heading{display:flex;justify-content:space-between;align-items:end;gap:20px;margin-bottom:18px}.agent-heading h3{font:700 clamp(27px,4vw,46px)/1 Georgia,serif;margin:0}.agent-heading>span{font:500 9px ui-monospace,SFMono-Regular,Menlo,monospace;padding:8px 11px;border:1px solid var(--line);border-radius:99px;color:var(--green)}.agent-steps{display:grid;grid-template-columns:repeat(2,1fr);gap:9px}.agent-steps article{display:grid;grid-template-columns:auto 1fr;gap:10px;padding:15px;border:1px solid var(--line);border-radius:11px;background:#f8f3e9}.agent-steps article>span{font:500 10px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--red)}.agent-steps b{font-size:13px;text-transform:capitalize}.agent-steps p{font-size:11px;line-height:1.4;color:var(--muted);margin:5px 0}.agent-steps small{grid-column:2;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;text-transform:uppercase;color:var(--green)}.agent-stop{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:9px}.agent-stop p{margin:0;padding:14px;border-top:1px solid var(--line);font-size:11px;line-height:1.5}.agent-stop b,.agent-stop span{display:block}.agent-stop b{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted);margin-bottom:5px}
|
|
|
|
| 18 |
.gap-section{margin-bottom:28px;padding:26px;border:1px solid var(--ink);background:#1b1b17;color:var(--cream);border-radius:18px}.gap-heading{display:flex;justify-content:space-between;gap:20px;align-items:end;margin-bottom:18px}.gap-heading .kicker{color:#bdb4a6}.gap-heading h3{font:700 clamp(27px,4vw,46px)/1 Georgia,serif;max-width:700px;margin:0}.gap-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.gap-card,.gap-empty{padding:19px;border:1px solid #4a483f;border-radius:13px;background:#26251f}.gap-card.high{border-color:var(--red)}.gap-card.medium{border-color:var(--amber)}.gap-severity{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.13em;text-transform:uppercase;color:#d9a65e}.gap-card h4{font-size:20px;margin:10px 0 16px}.gap-compare{display:grid;grid-template-columns:1fr 1fr;gap:10px}.gap-compare p{margin:0;padding:11px;background:#313029;border-radius:8px;font-size:12px;line-height:1.45}.gap-compare b{display:block;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;color:#bdb4a6;margin-bottom:5px}.gap-card .evidence{border-top-color:#4a483f}.gap-empty{display:grid;gap:5px;color:#bdb4a6}
|
| 19 |
.claim-grid{grid-template-columns:repeat(2,1fr)}.claim-card{background:var(--cream);border:1px solid var(--line);border-top:6px solid var(--muted);border-radius:16px;padding:23px}.claim-card.supported{border-top-color:var(--green)}.claim-card.contradicted{border-top-color:var(--red)}.claim-card.context{border-top-color:var(--amber)}.claim-top{display:flex;justify-content:space-between;gap:10px;align-items:start}.claim-name{font-size:21px;font-weight:800}.verdict{font:500 8px/1.3 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.08em;border:1px solid var(--line);border-radius:99px;padding:7px 9px;text-align:right}.confidence{display:block;margin-top:8px;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}.summary{min-height:45px;color:#534d43;line-height:1.5}.evidence{padding:11px 0;border-top:1px solid var(--line)}.evidence b,.evidence span{display:block}.evidence b{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.12em;color:var(--muted);text-transform:uppercase}.evidence span{font-size:13px;margin-top:4px}.caveat{font-size:11px;color:var(--muted);margin-top:15px}
|
| 20 |
.evidence-summary{margin-top:16px}.evidence-summary article{padding:25px;border:1px solid var(--line);border-radius:16px;background:var(--cream)}#nutrition-grid div{display:flex;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--line);font-size:13px}.date-card{background:var(--ink)!important;color:var(--cream)}.date-card .kicker{color:#cfc5b6}.date-card h3{font:700 28px/1.15 "Playfair Display"}details{margin-top:16px;border:1px solid var(--line);border-radius:14px;padding:17px;background:var(--cream)}summary{cursor:pointer;font-weight:700}pre{white-space:pre-wrap;font:11px/1.5 "DM Mono";overflow:auto}
|
|
|
|
| 15 |
.sample-card{padding:24px;background:var(--cream);border:1px solid var(--line);border-radius:16px;cursor:pointer;text-align:left;transition:.2s}.sample-card:hover{border-color:var(--red);transform:translateY(-3px)}.sample-card b{display:block;font-size:19px;margin-bottom:8px}.sample-card span{font-size:13px;color:var(--muted)}
|
| 16 |
.results{border-top:1px solid var(--line)}.hidden{display:none}.case-score{width:120px;height:120px;border:1px solid var(--ink);border-radius:50%;display:flex;align-items:center;justify-content:center;gap:8px}.case-score span{font:800 45px "Playfair Display"}.case-score small{font:500 8px/1.4 "DM Mono"}
|
| 17 |
.agent-section{margin-bottom:28px;padding:26px;border:1px solid var(--line);background:var(--cream);border-radius:18px}.agent-heading{display:flex;justify-content:space-between;align-items:end;gap:20px;margin-bottom:18px}.agent-heading h3{font:700 clamp(27px,4vw,46px)/1 Georgia,serif;margin:0}.agent-heading>span{font:500 9px ui-monospace,SFMono-Regular,Menlo,monospace;padding:8px 11px;border:1px solid var(--line);border-radius:99px;color:var(--green)}.agent-steps{display:grid;grid-template-columns:repeat(2,1fr);gap:9px}.agent-steps article{display:grid;grid-template-columns:auto 1fr;gap:10px;padding:15px;border:1px solid var(--line);border-radius:11px;background:#f8f3e9}.agent-steps article>span{font:500 10px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--red)}.agent-steps b{font-size:13px;text-transform:capitalize}.agent-steps p{font-size:11px;line-height:1.4;color:var(--muted);margin:5px 0}.agent-steps small{grid-column:2;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;text-transform:uppercase;color:var(--green)}.agent-stop{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:9px}.agent-stop p{margin:0;padding:14px;border-top:1px solid var(--line);font-size:11px;line-height:1.5}.agent-stop b,.agent-stop span{display:block}.agent-stop b{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted);margin-bottom:5px}
|
| 18 |
+
.nemotron-review{display:grid;gap:6px;margin-top:12px;padding:16px;border:1px solid #9fc2a9;border-radius:11px;background:#edf5eb}.nemotron-review:empty{display:none}.nemotron-review b{font:600 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.1em;color:var(--green)}.nemotron-review strong{font-size:14px}.nemotron-review span{font-size:11px;line-height:1.5;color:var(--muted)}
|
| 19 |
.gap-section{margin-bottom:28px;padding:26px;border:1px solid var(--ink);background:#1b1b17;color:var(--cream);border-radius:18px}.gap-heading{display:flex;justify-content:space-between;gap:20px;align-items:end;margin-bottom:18px}.gap-heading .kicker{color:#bdb4a6}.gap-heading h3{font:700 clamp(27px,4vw,46px)/1 Georgia,serif;max-width:700px;margin:0}.gap-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}.gap-card,.gap-empty{padding:19px;border:1px solid #4a483f;border-radius:13px;background:#26251f}.gap-card.high{border-color:var(--red)}.gap-card.medium{border-color:var(--amber)}.gap-severity{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.13em;text-transform:uppercase;color:#d9a65e}.gap-card h4{font-size:20px;margin:10px 0 16px}.gap-compare{display:grid;grid-template-columns:1fr 1fr;gap:10px}.gap-compare p{margin:0;padding:11px;background:#313029;border-radius:8px;font-size:12px;line-height:1.45}.gap-compare b{display:block;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;color:#bdb4a6;margin-bottom:5px}.gap-card .evidence{border-top-color:#4a483f}.gap-empty{display:grid;gap:5px;color:#bdb4a6}
|
| 20 |
.claim-grid{grid-template-columns:repeat(2,1fr)}.claim-card{background:var(--cream);border:1px solid var(--line);border-top:6px solid var(--muted);border-radius:16px;padding:23px}.claim-card.supported{border-top-color:var(--green)}.claim-card.contradicted{border-top-color:var(--red)}.claim-card.context{border-top-color:var(--amber)}.claim-top{display:flex;justify-content:space-between;gap:10px;align-items:start}.claim-name{font-size:21px;font-weight:800}.verdict{font:500 8px/1.3 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.08em;border:1px solid var(--line);border-radius:99px;padding:7px 9px;text-align:right}.confidence{display:block;margin-top:8px;font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.1em;text-transform:uppercase;color:var(--muted)}.summary{min-height:45px;color:#534d43;line-height:1.5}.evidence{padding:11px 0;border-top:1px solid var(--line)}.evidence b,.evidence span{display:block}.evidence b{font:500 8px ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.12em;color:var(--muted);text-transform:uppercase}.evidence span{font-size:13px;margin-top:4px}.caveat{font-size:11px;color:var(--muted);margin-top:15px}
|
| 21 |
.evidence-summary{margin-top:16px}.evidence-summary article{padding:25px;border:1px solid var(--line);border-radius:16px;background:var(--cream)}#nutrition-grid div{display:flex;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--line);font-size:13px}.date-card{background:var(--ink)!important;color:var(--cream)}.date-card .kicker{color:#cfc5b6}.date-card h3{font:700 28px/1.15 "Playfair Display"}details{margin-top:16px;border:1px solid var(--line);border-radius:14px;padding:17px;background:var(--cream)}summary{cursor:pointer;font-weight:700}pre{white-space:pre-wrap;font:11px/1.5 "DM Mono";overflow:auto}
|
src/packetcourt/models.py
CHANGED
|
@@ -80,6 +80,14 @@ class InvestigationPlan(BaseModel):
|
|
| 80 |
router_model: str = "deterministic fallback"
|
| 81 |
|
| 82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
class PacketAudit(BaseModel):
|
| 84 |
claims: list[ClaimAudit]
|
| 85 |
nutrition: NutritionFacts
|
|
@@ -91,3 +99,4 @@ class PacketAudit(BaseModel):
|
|
| 91 |
back_text: str
|
| 92 |
limitations: list[str]
|
| 93 |
investigation: InvestigationPlan
|
|
|
|
|
|
| 80 |
router_model: str = "deterministic fallback"
|
| 81 |
|
| 82 |
|
| 83 |
+
class AgentReview(BaseModel):
|
| 84 |
+
status: str = "NOT_REQUESTED"
|
| 85 |
+
priority: str = ""
|
| 86 |
+
evidence_request: str = ""
|
| 87 |
+
rationale: str = ""
|
| 88 |
+
model: str = ""
|
| 89 |
+
|
| 90 |
+
|
| 91 |
class PacketAudit(BaseModel):
|
| 92 |
claims: list[ClaimAudit]
|
| 93 |
nutrition: NutritionFacts
|
|
|
|
| 99 |
back_text: str
|
| 100 |
limitations: list[str]
|
| 101 |
investigation: InvestigationPlan
|
| 102 |
+
agent_review: AgentReview = Field(default_factory=AgentReview)
|
src/packetcourt/remote_nemotron.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
from functools import lru_cache
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def is_configured() -> bool:
|
| 9 |
+
return bool(os.getenv("PACKETCOURT_NEMOTRON_SPACE"))
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@lru_cache(maxsize=1)
|
| 13 |
+
def _client():
|
| 14 |
+
from gradio_client import Client
|
| 15 |
+
|
| 16 |
+
try:
|
| 17 |
+
return Client(os.environ["PACKETCOURT_NEMOTRON_SPACE"], hf_token=os.getenv("HF_TOKEN"))
|
| 18 |
+
except TypeError:
|
| 19 |
+
return Client(os.environ["PACKETCOURT_NEMOTRON_SPACE"], token=os.getenv("HF_TOKEN"))
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def review(snapshot: dict) -> dict:
|
| 23 |
+
result = _client().predict(json.dumps(snapshot), api_name="/predict")
|
| 24 |
+
return json.loads(result)
|
tests/test_audit.py
CHANGED
|
@@ -87,3 +87,4 @@ def test_investigation_requests_missing_evidence_and_stops_explicitly():
|
|
| 87 |
assert any(step.tool == "inspect_nutrition" for step in result.investigation.steps)
|
| 88 |
assert any("nutrition panel" in item.lower() for item in result.investigation.missing_evidence)
|
| 89 |
assert "missing-evidence" in result.investigation.stop_reason
|
|
|
|
|
|
| 87 |
assert any(step.tool == "inspect_nutrition" for step in result.investigation.steps)
|
| 88 |
assert any("nutrition panel" in item.lower() for item in result.investigation.missing_evidence)
|
| 89 |
assert "missing-evidence" in result.investigation.stop_reason
|
| 90 |
+
assert result.agent_review.status == "NOT_REQUESTED"
|