szwendaczjakomaj Claude Sonnet 4.6 commited on
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>

Files changed (1) hide show
  1. app.py +16 -27
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
- # "Output one per line, exactly as `- CODE: NAME`, and nothing else. "
 
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
- content = []
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
- return [e for e in entries if is_paint_name(e.get("name"))]
 
 
 
 
 
 
 
 
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.", "<p>(timeout)</p>"
340
  except requests.RequestException as e:
341
- return f"Server error: {e}", f"<p>(error)</p>"
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):