Spaces:
Sleeping
Sleeping
Commit Β·
5bc1cf3
1
Parent(s): c33194e
feat: switch to MiniCPM finetune prompt + fix name==code filter
Browse files- Update LIST_PROMPT for MiniCPM finetune output format
- Fix extract_entries: treat name==code as unnamed (finetune echoes code when no colour name known)
- Remove streaming from call_llama (heartbeat proxy handles CF timeout; MiniCPM ~64s < 100s limit)
- Set LLAMA_SERVER default to inference.llmops.pl tunnel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -12,7 +12,7 @@ from html import escape
|
|
| 12 |
from PIL import Image, ImageOps
|
| 13 |
import inventory
|
| 14 |
|
| 15 |
-
LLAMA_SERVER = os.environ.get("LLAMA_SERVER", "")
|
| 16 |
TIMEOUT = 300 # seconds; CPU inference is slow
|
| 17 |
|
| 18 |
# ---------- Humbrol β Tamiya lookup ----------
|
|
@@ -37,8 +37,9 @@ HUMBROL_TO_TAMIYA = _load_humbrol_tamiya()
|
|
| 37 |
# `- CODE: NAME` list regardless of source layout, so a single tolerant regex parses
|
| 38 |
# all of them β no per-format coupling to break when the model varies its output.
|
| 39 |
LIST_PROMPT = (
|
| 40 |
-
"List every paint code visible in this image."
|
| 41 |
-
|
|
|
|
| 42 |
# "For a paint given as a mix ratio, join the codes with + (for example 118+34)."
|
| 43 |
)
|
| 44 |
|
|
@@ -149,33 +150,13 @@ def call_llama(img_b64: str, prompt: str) -> str:
|
|
| 149 |
}
|
| 150 |
elif OUTPUT_MODE == "line":
|
| 151 |
payload["grammar"] = LINE_GRAMMAR
|
| 152 |
-
# stream=True keeps the Cloudflare connection alive via SSE tokens;
|
| 153 |
-
# without it a 100s+ inference triggers Cloudflare 524 before the response arrives.
|
| 154 |
-
payload["stream"] = True
|
| 155 |
r = requests.post(
|
| 156 |
f"{LLAMA_SERVER}/v1/chat/completions",
|
| 157 |
json=payload,
|
| 158 |
timeout=TIMEOUT,
|
| 159 |
-
stream=True,
|
| 160 |
)
|
| 161 |
r.raise_for_status()
|
| 162 |
-
|
| 163 |
-
for line in r.iter_lines():
|
| 164 |
-
if not line:
|
| 165 |
-
continue
|
| 166 |
-
line = line.decode("utf-8") if isinstance(line, bytes) else line
|
| 167 |
-
if line.startswith("data: "):
|
| 168 |
-
data = line[6:]
|
| 169 |
-
if data.strip() == "[DONE]":
|
| 170 |
-
break
|
| 171 |
-
try:
|
| 172 |
-
chunk = json.loads(data)
|
| 173 |
-
delta = chunk["choices"][0]["delta"].get("content", "")
|
| 174 |
-
if delta:
|
| 175 |
-
content.append(delta)
|
| 176 |
-
except (json.JSONDecodeError, KeyError, IndexError):
|
| 177 |
-
pass
|
| 178 |
-
return "".join(content)
|
| 179 |
|
| 180 |
# ---------- Parsing ----------
|
| 181 |
|
|
@@ -255,7 +236,15 @@ def extract_entries(raw: str, pattern: re.Pattern) -> list[dict]:
|
|
| 255 |
entries = parse_json_entries(raw)
|
| 256 |
else:
|
| 257 |
entries = parse_entries(strip_parens(raw), pattern)
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
|
| 261 |
def expand_codes(entries: list[dict]) -> list[dict]:
|
|
@@ -336,9 +325,9 @@ def _run_analysis(image: Image.Image, format_id: str) -> tuple[str, str]:
|
|
| 336 |
codes_str = "<p>(none parsed)</p>"
|
| 337 |
return raw, codes_str
|
| 338 |
except requests.Timeout:
|
| 339 |
-
return "Timeout β model took too long.", "
|
| 340 |
except requests.RequestException as e:
|
| 341 |
-
return f"Server error: {e}",
|
| 342 |
|
| 343 |
|
| 344 |
def analyze(image: Image.Image, format_id: str):
|
|
|
|
| 12 |
from PIL import Image, ImageOps
|
| 13 |
import inventory
|
| 14 |
|
| 15 |
+
LLAMA_SERVER = os.environ.get("LLAMA_SERVER", "https://inference.llmops.pl")
|
| 16 |
TIMEOUT = 300 # seconds; CPU inference is slow
|
| 17 |
|
| 18 |
# ---------- Humbrol β Tamiya lookup ----------
|
|
|
|
| 37 |
# `- CODE: NAME` list regardless of source layout, so a single tolerant regex parses
|
| 38 |
# all of them β no per-format coupling to break when the model varies its output.
|
| 39 |
LIST_PROMPT = (
|
| 40 |
+
# "List every paint code visible in this image."
|
| 41 |
+
"List all paint codes shown. 'code' = the numeric identifier (e.g. '33'). 'name' = colour name (e.g. 'Matt Black')"
|
| 42 |
+
# "Output one per line, exactly as `- CODE: NAME`, and nothing else."
|
| 43 |
# "For a paint given as a mix ratio, join the codes with + (for example 118+34)."
|
| 44 |
)
|
| 45 |
|
|
|
|
| 150 |
}
|
| 151 |
elif OUTPUT_MODE == "line":
|
| 152 |
payload["grammar"] = LINE_GRAMMAR
|
|
|
|
|
|
|
|
|
|
| 153 |
r = requests.post(
|
| 154 |
f"{LLAMA_SERVER}/v1/chat/completions",
|
| 155 |
json=payload,
|
| 156 |
timeout=TIMEOUT,
|
|
|
|
| 157 |
)
|
| 158 |
r.raise_for_status()
|
| 159 |
+
return r.json()["choices"][0]["message"]["content"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
# ---------- Parsing ----------
|
| 162 |
|
|
|
|
| 236 |
entries = parse_json_entries(raw)
|
| 237 |
else:
|
| 238 |
entries = parse_entries(strip_parens(raw), pattern)
|
| 239 |
+
result = []
|
| 240 |
+
for e in entries:
|
| 241 |
+
name = e.get("name")
|
| 242 |
+
# finetune model echoes the code as name when it has no colour name β treat as unnamed
|
| 243 |
+
if isinstance(name, str) and name.strip() == e.get("code", "").strip():
|
| 244 |
+
e = {**e, "name": None}
|
| 245 |
+
if is_paint_name(e.get("name")):
|
| 246 |
+
result.append(e)
|
| 247 |
+
return result
|
| 248 |
|
| 249 |
|
| 250 |
def expand_codes(entries: list[dict]) -> list[dict]:
|
|
|
|
| 325 |
codes_str = "<p>(none parsed)</p>"
|
| 326 |
return raw, codes_str
|
| 327 |
except requests.Timeout:
|
| 328 |
+
return "Timeout β model took too long.", ""
|
| 329 |
except requests.RequestException as e:
|
| 330 |
+
return f"Server error: {e}", ""
|
| 331 |
|
| 332 |
|
| 333 |
def analyze(image: Image.Image, format_id: str):
|