cat-stack 0.4.0__tar.gz → 1.0.0__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.
Files changed (42) hide show
  1. {cat_stack-0.4.0 → cat_stack-1.0.0}/PKG-INFO +38 -1
  2. {cat_stack-0.4.0 → cat_stack-1.0.0}/README.md +37 -0
  3. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/__about__.py +1 -1
  4. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/summarize.py +79 -1
  5. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/text_functions_ensemble.py +8 -2
  6. {cat_stack-0.4.0 → cat_stack-1.0.0}/.gitignore +0 -0
  7. {cat_stack-0.4.0 → cat_stack-1.0.0}/LICENSE +0 -0
  8. {cat_stack-0.4.0 → cat_stack-1.0.0}/pyproject.toml +0 -0
  9. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/__init__.py +0 -0
  10. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_batch.py +0 -0
  11. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_category_analysis.py +0 -0
  12. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_chunked.py +0 -0
  13. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_embeddings.py +0 -0
  14. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_formatter.py +0 -0
  15. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_pilot_test.py +0 -0
  16. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_providers.py +0 -0
  17. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_review_ui.py +0 -0
  18. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_tiebreaker.py +0 -0
  19. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_utils.py +0 -0
  20. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/_web_fetch.py +0 -0
  21. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/calls/CoVe.py +0 -0
  22. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/calls/__init__.py +0 -0
  23. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/calls/all_calls.py +0 -0
  24. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/calls/image_CoVe.py +0 -0
  25. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/calls/image_stepback.py +0 -0
  26. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/calls/pdf_CoVe.py +0 -0
  27. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/calls/pdf_stepback.py +0 -0
  28. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/calls/stepback.py +0 -0
  29. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/calls/top_n.py +0 -0
  30. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/classify.py +0 -0
  31. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/explore.py +0 -0
  32. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/extract.py +0 -0
  33. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/image_functions.py +0 -0
  34. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/images/circle.png +0 -0
  35. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/images/cube.png +0 -0
  36. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/images/diamond.png +0 -0
  37. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/images/overlapping_pentagons.png +0 -0
  38. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/images/rectangles.png +0 -0
  39. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/model_reference_list.py +0 -0
  40. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/pdf_functions.py +0 -0
  41. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/prompt_tune.py +0 -0
  42. {cat_stack-0.4.0 → cat_stack-1.0.0}/src/cat_stack/text_functions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cat-stack
3
- Version: 0.4.0
3
+ Version: 1.0.0
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
@@ -100,6 +100,41 @@ cat.classify(
100
100
  )
101
101
  ```
102
102
 
103
+ #### Inline prompt tuning
104
+
105
+ Add `prompt_tune=True` to automatically optimize the classification prompt before the full run. A browser UI opens for you to correct a small sample, then the optimized prompt is used for all remaining items.
106
+
107
+ ```python
108
+ cat.classify(
109
+ input_data=df["text"],
110
+ categories=["Cat A", "Cat B", "Cat C"],
111
+ models=[("gpt-4o", "openai", key)],
112
+ prompt_tune=15, # tune on 15 random items, then classify all
113
+ tune_iterations=3, # max attempts per category (default 3)
114
+ )
115
+ ```
116
+
117
+ ### `prompt_tune()`
118
+ Standalone automatic prompt optimization. Iteratively refines classification prompts using user feedback — classify a sample, correct mistakes in the browser, and let the LLM generate targeted per-category instructions.
119
+
120
+ ```python
121
+ result = cat.prompt_tune(
122
+ input_data=df["text"],
123
+ categories=["Cat A", "Cat B", "Cat C"],
124
+ api_key="your-key",
125
+ sample_size=15,
126
+ max_iterations=3,
127
+ )
128
+
129
+ # Use the optimized prompt for classification
130
+ cat.classify(
131
+ input_data=df["text"],
132
+ categories=["Cat A", "Cat B", "Cat C"],
133
+ api_key="your-key",
134
+ system_prompt=result["system_prompt"],
135
+ )
136
+ ```
137
+
103
138
  ### `extract()`
104
139
  Discover categories from a corpus using LLM-driven exploration.
105
140
 
@@ -141,11 +176,13 @@ All providers use the same `(model_name, provider, api_key)` tuple format. Provi
141
176
 
142
177
  ## Features
143
178
 
179
+ - **Automatic prompt optimization** (`prompt_tune`) — correct a small sample in a browser UI, and the system generates per-category instructions that improve accuracy
144
180
  - **Multi-model ensemble** with consensus voting and agreement scores
145
181
  - **Batch API support** for OpenAI, Anthropic, Google, Mistral, and xAI
146
182
  - **Prompt strategies**: Chain-of-Thought, Chain-of-Verification, step-back prompting, few-shot examples
147
183
  - **Text, image, and PDF** input auto-detection
148
184
  - **Embedding similarity** tiebreaker for ensemble consensus ties
185
+ - **Pilot test** — validate classifications on a small sample before committing to the full run
149
186
 
150
187
  ## License
151
188
 
@@ -61,6 +61,41 @@ cat.classify(
61
61
  )
62
62
  ```
63
63
 
64
+ #### Inline prompt tuning
65
+
66
+ Add `prompt_tune=True` to automatically optimize the classification prompt before the full run. A browser UI opens for you to correct a small sample, then the optimized prompt is used for all remaining items.
67
+
68
+ ```python
69
+ cat.classify(
70
+ input_data=df["text"],
71
+ categories=["Cat A", "Cat B", "Cat C"],
72
+ models=[("gpt-4o", "openai", key)],
73
+ prompt_tune=15, # tune on 15 random items, then classify all
74
+ tune_iterations=3, # max attempts per category (default 3)
75
+ )
76
+ ```
77
+
78
+ ### `prompt_tune()`
79
+ Standalone automatic prompt optimization. Iteratively refines classification prompts using user feedback — classify a sample, correct mistakes in the browser, and let the LLM generate targeted per-category instructions.
80
+
81
+ ```python
82
+ result = cat.prompt_tune(
83
+ input_data=df["text"],
84
+ categories=["Cat A", "Cat B", "Cat C"],
85
+ api_key="your-key",
86
+ sample_size=15,
87
+ max_iterations=3,
88
+ )
89
+
90
+ # Use the optimized prompt for classification
91
+ cat.classify(
92
+ input_data=df["text"],
93
+ categories=["Cat A", "Cat B", "Cat C"],
94
+ api_key="your-key",
95
+ system_prompt=result["system_prompt"],
96
+ )
97
+ ```
98
+
64
99
  ### `extract()`
65
100
  Discover categories from a corpus using LLM-driven exploration.
66
101
 
@@ -102,11 +137,13 @@ All providers use the same `(model_name, provider, api_key)` tuple format. Provi
102
137
 
103
138
  ## Features
104
139
 
140
+ - **Automatic prompt optimization** (`prompt_tune`) — correct a small sample in a browser UI, and the system generates per-category instructions that improve accuracy
105
141
  - **Multi-model ensemble** with consensus voting and agreement scores
106
142
  - **Batch API support** for OpenAI, Anthropic, Google, Mistral, and xAI
107
143
  - **Prompt strategies**: Chain-of-Thought, Chain-of-Verification, step-back prompting, few-shot examples
108
144
  - **Text, image, and PDF** input auto-detection
109
145
  - **Embedding similarity** tiebreaker for ensemble consensus ties
146
+ - **Pilot test** — validate classifications on a small sample before committing to the full run
110
147
 
111
148
  ## License
112
149
 
@@ -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__ = "0.4.0"
4
+ __version__ = "1.0.0"
5
5
  __author__ = "Chris Soria"
6
6
  __email__ = "chrissoria@berkeley.edu"
7
7
  __title__ = "cat-stack"
@@ -31,6 +31,7 @@ def summarize(
31
31
  api_key: str = None,
32
32
  description: str = "",
33
33
  instructions: str = "",
34
+ format: str = "paragraph",
34
35
  max_length: int = None,
35
36
  focus: str = None,
36
37
  user_model: str = "gpt-4o",
@@ -76,7 +77,15 @@ def summarize(
76
77
  - PDF: directory path, single PDF path, or list of PDF paths
77
78
  api_key (str): API key for the model provider (single-model mode)
78
79
  description (str): Description of what the content contains (provides context)
79
- instructions (str): Specific summarization instructions (e.g., "bullet points")
80
+ instructions (str): Specific summarization instructions. When used with
81
+ format, these are appended as additional instructions. Default "".
82
+ format (str): Output format for the summary. Default "paragraph".
83
+ - "paragraph": Flowing prose summary (default)
84
+ - "bullets": Bullet-point list of key points
85
+ - "one-liner": Single-sentence summary
86
+ - "structured": Labeled sections (What, Who, Why, Impact)
87
+ - "report": Comprehensive full-page report with Overview, Background,
88
+ Key Provisions, Stakeholders/Impact, and Implementation sections
80
89
  max_length (int): Maximum summary length in words
81
90
  focus (str): What to focus on (e.g., "main arguments", "emotional content")
82
91
  user_model (str): Model to use (default "gpt-4o")
@@ -179,6 +188,75 @@ def summarize(
179
188
  ... ],
180
189
  ... )
181
190
  """
191
+ # =========================================================================
192
+ # Resolve format → instructions + max_length defaults
193
+ # =========================================================================
194
+ _FORMAT_PRESETS = {
195
+ "paragraph": {
196
+ "instructions": "Write a concise summary in paragraph form.",
197
+ "max_length": None,
198
+ },
199
+ "bullets": {
200
+ "instructions": (
201
+ "Summarize as a bullet-point list. Each bullet should capture "
202
+ "one key point. Use '- ' prefix for each bullet."
203
+ ),
204
+ "max_length": None,
205
+ },
206
+ "one-liner": {
207
+ "instructions": "Summarize in a single sentence.",
208
+ "max_length": 40,
209
+ },
210
+ "structured": {
211
+ "instructions": (
212
+ "Summarize using these labeled sections:\n"
213
+ "- What: What does this do or say?\n"
214
+ "- Who: Who is affected or involved?\n"
215
+ "- Why: What is the motivation or purpose?\n"
216
+ "- Impact: What are the key consequences or effects?"
217
+ ),
218
+ "max_length": None,
219
+ },
220
+ "report": {
221
+ "instructions": (
222
+ "Write a comprehensive full-page report covering the following sections. "
223
+ "Use clear headings and be thorough.\n\n"
224
+ "## Overview\n"
225
+ "A brief executive summary (2-3 sentences).\n\n"
226
+ "## Background and Context\n"
227
+ "What is the background? What problem or situation prompted this? "
228
+ "Include relevant history and prior actions.\n\n"
229
+ "## Key Provisions\n"
230
+ "Detail the main provisions, requirements, or arguments. "
231
+ "Be specific about numbers, dates, names, and conditions.\n\n"
232
+ "## Stakeholders and Impact\n"
233
+ "Who is affected? What are the expected consequences? "
234
+ "Include both intended effects and potential concerns.\n\n"
235
+ "## Implementation\n"
236
+ "How will this be implemented? What is the timeline? "
237
+ "Are there enforcement mechanisms or milestones?"
238
+ ),
239
+ "max_length": 800,
240
+ },
241
+ }
242
+
243
+ format_lower = format.lower() if format else "paragraph"
244
+ if format_lower not in _FORMAT_PRESETS:
245
+ valid = ", ".join(f'"{k}"' for k in _FORMAT_PRESETS)
246
+ raise ValueError(f"format must be one of {valid}, got '{format}'")
247
+
248
+ preset = _FORMAT_PRESETS[format_lower]
249
+
250
+ # Format instructions are prepended to any user-provided instructions
251
+ if not instructions:
252
+ instructions = preset["instructions"]
253
+ else:
254
+ instructions = f"{preset['instructions']}\n\nAdditional instructions: {instructions}"
255
+
256
+ # Use format's max_length as default only if user didn't specify one
257
+ if max_length is None and preset["max_length"] is not None:
258
+ max_length = preset["max_length"]
259
+
182
260
  # Map mode to pdf_mode
183
261
  pdf_mode = mode if mode in ("image", "text", "both") else "image"
184
262
 
@@ -3756,7 +3756,7 @@ def summarize_ensemble(
3756
3756
  max_retries=max_retries,
3757
3757
  )
3758
3758
  else:
3759
- response, _err = client.complete(
3759
+ response, error = client.complete(
3760
3760
  messages=messages,
3761
3761
  json_schema=json_schema,
3762
3762
  creativity=creativity,
@@ -3764,6 +3764,9 @@ def summarize_ensemble(
3764
3764
  max_retries=max_retries,
3765
3765
  )
3766
3766
 
3767
+ if error:
3768
+ return (model_name, '{"summary": ""}', error)
3769
+
3767
3770
  # Extract JSON from response
3768
3771
  json_str = extract_json(response)
3769
3772
 
@@ -3806,7 +3809,7 @@ def summarize_ensemble(
3806
3809
  # Resolve thinking_budget for this provider
3807
3810
  effective_thinking = thinking_budget if cfg["provider"] in ("google", "openai", "anthropic", "huggingface", "huggingface-together") else None
3808
3811
 
3809
- response, _err = client.complete(
3812
+ response, error = client.complete(
3810
3813
  messages=messages,
3811
3814
  json_schema=json_schema,
3812
3815
  creativity=creativity,
@@ -3814,6 +3817,9 @@ def summarize_ensemble(
3814
3817
  max_retries=max_retries,
3815
3818
  )
3816
3819
 
3820
+ if error:
3821
+ return (model_name, '{"summary": ""}', error)
3822
+
3817
3823
  # Extract JSON from response
3818
3824
  json_str = extract_json(response)
3819
3825
 
File without changes
File without changes
File without changes