@@ -131,7 +131,7 @@ def validate_request(req):
131 if not isinstance(it, dict):
131 if not isinstance(it, dict):
132 raise Fail("BAD_REQUEST", f"item {n} is not an object")
132 raise Fail("BAD_REQUEST", f"item {n} is not an object")
133 clean.append({"i": n, "title": _trim(it.get("title"), 120), "url": _trim(it.get("url"), 300),
133 clean.append({"i": n, "title": _trim(it.get("title"), 120), "url": _trim(it.get("url"), 300),
134− "description": _trim(it.get("description"), 300)})
134+ "description": _trim(it.get("description"), 500)})
135 payload["items"] = clean
135 payload["items"] = clean
136 for key in ("siblingTitles", "mustDifferFrom"):
136 for key in ("siblingTitles", "mustDifferFrom"):
137 payload[key] = [_trim(t, 60) for t in payload.get(key) or [] if isinstance(t, str)][:20]
137 payload[key] = [_trim(t, 60) for t in payload.get(key) or [] if isinstance(t, str)][:20]
@@ -186,6 +186,20 @@ def schema_path(name):
186 return path if os.path.isfile(path) and os.path.getsize(path) > 0 else None
186 return path if os.path.isfile(path) and os.path.getsize(path) > 0 else None
189+# How much of each page's text to send, most first: when a batch doesn't fit the budget, the pages' text
190+# is shortened, then dropped, before the host gives up and asks for a smaller batch.
191+DESC_CAPS = (500, 200, 0)
194+def fit(build, n_items):
195+ for cap in DESC_CAPS:
197+ if prompts.size(prompt) <= CHAR_BUDGET:
199+ check_budget(prompt, n_items) # raises OVER_BUDGET with how many items would fit
189def check_budget(prompt, n_items):
203def check_budget(prompt, n_items):
190 size = prompts.size(prompt)
204 size = prompts.size(prompt)
191 if size > CHAR_BUDGET:
205 if size > CHAR_BUDGET:
@@ -324,8 +338,7 @@ def op_name(payload, opts):
324 schema = schema_path("name.json")
338 schema = schema_path("name.json")
325 if not schema:
339 if not schema:
326 raise Fail("SCHEMA_MISSING", f"fm could not write {os.path.join(SCHEMAS, 'name.json')}")
340 raise Fail("SCHEMA_MISSING", f"fm could not write {os.path.join(SCHEMAS, 'name.json')}")
327− prompt = prompts.build_name_prompt(payload, strict=opts.get("strict", False))
341+ prompt = fit(lambda cap: prompts.build_name_prompt(payload, strict=opts.get("strict", False), desc_cap=cap), len(payload["items"]))
328− check_budget(prompt, len(payload["items"]))
330 out = run_fm(prompt, schema, opts["model"], opts["timeout_s"])
343 out = run_fm(prompt, schema, opts["model"], opts["timeout_s"])
331 except Fail as f:
344 except Fail as f:
@@ -341,8 +354,7 @@ def op_organize(payload, opts):
341 schema = schema_path("topics.json")
354 schema = schema_path("topics.json")
342 if not schema:
355 if not schema:
343 raise Fail("SCHEMA_MISSING", f"fm could not write {os.path.join(SCHEMAS, 'topics.json')}")
356 raise Fail("SCHEMA_MISSING", f"fm could not write {os.path.join(SCHEMAS, 'topics.json')}")
344− prompt = prompts.build_topics_prompt(payload, strict=opts.get("strict", False))
357+ prompt = fit(lambda cap: prompts.build_topics_prompt(payload, strict=opts.get("strict", False), desc_cap=cap), len(payload["items"]))
345− check_budget(prompt, len(payload["items"]))
347 out = run_fm(prompt, schema, opts["model"], opts["timeout_s"])
359 out = run_fm(prompt, schema, opts["model"], opts["timeout_s"])
348 except Fail as f:
360 except Fail as f: