cat-stack 2.5.3__tar.gz → 2.5.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cat_stack-2.5.3 → cat_stack-2.5.4}/PKG-INFO +1 -1
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/__about__.py +1 -1
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/text_functions_ensemble.py +58 -11
- {cat_stack-2.5.3 → cat_stack-2.5.4}/.gitignore +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/LICENSE +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/README.md +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/pyproject.toml +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/cat_stack/__init__.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/__init__.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_batch.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_category_analysis.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_chunked.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_embeddings.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_formatter.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_pilot_test.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_prompts.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_providers.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_review_ui.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_tiebreaker.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_utils.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_web_fetch.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/_wrapper_helpers.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/calls/CoVe.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/calls/__init__.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/calls/image_CoVe.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/calls/image_stepback.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/calls/pdf_CoVe.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/calls/pdf_stepback.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/calls/stepback.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/calls/top_n.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/classify.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/collapse_themes.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/explore.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/extract.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/image_functions.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/images/circle.png +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/images/cube.png +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/images/diamond.png +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/images/overlapping_pentagons.png +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/images/rectangles.png +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/model_reference_list.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/pdf_functions.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/prompt_tune.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/summarize.py +0 -0
- {cat_stack-2.5.3 → cat_stack-2.5.4}/src/catstack/text_functions.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cat-stack
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.4
|
|
4
4
|
Summary: Domain-agnostic text, image, PDF, and DOCX classification engine powered by LLMs
|
|
5
5
|
Project-URL: Documentation, https://github.com/chrissoria/cat-stack#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/chrissoria/cat-stack/issues
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2025-present Christopher Soria <chrissoria@berkeley.edu>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
-
__version__ = "2.5.
|
|
4
|
+
__version__ = "2.5.4"
|
|
5
5
|
__author__ = "Chris Soria"
|
|
6
6
|
__email__ = "chrissoria@berkeley.edu"
|
|
7
7
|
__title__ = "cat-stack"
|
|
@@ -1490,6 +1490,53 @@ def _extract_json_for_summary(reply: str) -> str:
|
|
|
1490
1490
|
return '{"summary": ""}'
|
|
1491
1491
|
|
|
1492
1492
|
|
|
1493
|
+
_FENCE_RE = re.compile(r"^\s*```(?:json)?\s*|\s*```\s*$", re.IGNORECASE)
|
|
1494
|
+
_THINK_RE = re.compile(r"<think>.*?</think>", re.DOTALL)
|
|
1495
|
+
# `"summary": "` ... last `"` ... then only closing braces / a fence
|
|
1496
|
+
_LENIENT_SUMMARY_RE = re.compile(r'"summary"\s*:\s*"(.*)"\s*\}*\s*(?:```)?\s*$', re.DOTALL)
|
|
1497
|
+
|
|
1498
|
+
|
|
1499
|
+
def _parse_summary_reply(reply) -> tuple:
|
|
1500
|
+
"""Read a summary out of a model reply as tolerantly as is safe.
|
|
1501
|
+
|
|
1502
|
+
Returns ``(json_str, error)``: ``json_str`` is a ``{"summary": ...}``
|
|
1503
|
+
object and ``error`` is None, or ``error`` explains why no summary could
|
|
1504
|
+
be read (so the row is retried and the reason reaches `error_message`,
|
|
1505
|
+
instead of the row failing silently as it used to).
|
|
1506
|
+
|
|
1507
|
+
Tried in order:
|
|
1508
|
+
1. strict: the first JSON object in the reply (code fences and
|
|
1509
|
+
<think> blocks are fine);
|
|
1510
|
+
2. lenient: a `"summary": "..."` object that is not valid JSON, most
|
|
1511
|
+
often unescaped double quotes or raw newlines inside the text
|
|
1512
|
+
(common with quoted category labels or non-English text);
|
|
1513
|
+
3. prose: a reply with no JSON object at all is taken as the summary
|
|
1514
|
+
itself (the model answered in plain text).
|
|
1515
|
+
"""
|
|
1516
|
+
if reply is None or not str(reply).strip():
|
|
1517
|
+
return '{"summary": ""}', "the model returned an empty reply"
|
|
1518
|
+
text = _THINK_RE.sub("", str(reply)).strip()
|
|
1519
|
+
|
|
1520
|
+
json_str = _extract_json_for_summary(text)
|
|
1521
|
+
ok, _ = extract_summary_from_json(json_str)
|
|
1522
|
+
if ok:
|
|
1523
|
+
return json_str, None
|
|
1524
|
+
|
|
1525
|
+
m = _LENIENT_SUMMARY_RE.search(text)
|
|
1526
|
+
if m:
|
|
1527
|
+
value = m.group(1).replace('\\"', '"').replace("\\n", "\n").strip()
|
|
1528
|
+
if value:
|
|
1529
|
+
return json.dumps({"summary": value}, ensure_ascii=False), None
|
|
1530
|
+
|
|
1531
|
+
if "{" not in text:
|
|
1532
|
+
prose = _FENCE_RE.sub("", text).strip()
|
|
1533
|
+
if prose:
|
|
1534
|
+
return json.dumps({"summary": prose}, ensure_ascii=False), None
|
|
1535
|
+
|
|
1536
|
+
snippet = " ".join(text.split())[:200]
|
|
1537
|
+
return '{"summary": ""}', f"could not read a summary from the model's reply: {snippet!r}"
|
|
1538
|
+
|
|
1539
|
+
|
|
1493
1540
|
def extract_summary_from_json(json_str: str) -> tuple:
|
|
1494
1541
|
"""
|
|
1495
1542
|
Extract summary from JSON response.
|
|
@@ -4230,10 +4277,10 @@ def summarize_ensemble(
|
|
|
4230
4277
|
if error:
|
|
4231
4278
|
return (model_name, '{"summary": ""}', error)
|
|
4232
4279
|
|
|
4233
|
-
#
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
return (model_name, json_str,
|
|
4280
|
+
# Read the summary; an unreadable reply is an error (retried,
|
|
4281
|
+
# reported), never a silent empty summary
|
|
4282
|
+
json_str, parse_error = _parse_summary_reply(response)
|
|
4283
|
+
return (model_name, json_str, parse_error)
|
|
4237
4284
|
|
|
4238
4285
|
except Exception as e:
|
|
4239
4286
|
error_msg = str(e)
|
|
@@ -4294,8 +4341,8 @@ def summarize_ensemble(
|
|
|
4294
4341
|
if error:
|
|
4295
4342
|
return (model_name, '{"summary": ""}', error)
|
|
4296
4343
|
|
|
4297
|
-
json_str =
|
|
4298
|
-
return (model_name, json_str,
|
|
4344
|
+
json_str, parse_error = _parse_summary_reply(response)
|
|
4345
|
+
return (model_name, json_str, parse_error)
|
|
4299
4346
|
|
|
4300
4347
|
except Exception as e:
|
|
4301
4348
|
return (model_name, '{"summary": ""}', str(e))
|
|
@@ -4346,10 +4393,10 @@ def summarize_ensemble(
|
|
|
4346
4393
|
if error:
|
|
4347
4394
|
return (model_name, '{"summary": ""}', error)
|
|
4348
4395
|
|
|
4349
|
-
#
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
return (model_name, json_str,
|
|
4396
|
+
# Read the summary; an unreadable reply is an error (retried,
|
|
4397
|
+
# reported), never a silent empty summary
|
|
4398
|
+
json_str, parse_error = _parse_summary_reply(response)
|
|
4399
|
+
return (model_name, json_str, parse_error)
|
|
4353
4400
|
|
|
4354
4401
|
except Exception as e:
|
|
4355
4402
|
error_msg = str(e)
|
|
@@ -4735,7 +4782,7 @@ Provide your answer in JSON format: {{"summary": "your synthesized summary"}}"""
|
|
|
4735
4782
|
max_retries=max_retries,
|
|
4736
4783
|
)
|
|
4737
4784
|
|
|
4738
|
-
json_str =
|
|
4785
|
+
json_str, _ = _parse_summary_reply(response)
|
|
4739
4786
|
is_valid, summary = extract_summary_from_json(json_str)
|
|
4740
4787
|
|
|
4741
4788
|
if is_valid:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|