cat-stack 1.6.5__tar.gz → 1.6.6__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 (44) hide show
  1. {cat_stack-1.6.5 → cat_stack-1.6.6}/PKG-INFO +1 -1
  2. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/__about__.py +1 -1
  3. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_providers.py +77 -0
  4. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/text_functions_ensemble.py +3 -3
  5. {cat_stack-1.6.5 → cat_stack-1.6.6}/.gitignore +0 -0
  6. {cat_stack-1.6.5 → cat_stack-1.6.6}/LICENSE +0 -0
  7. {cat_stack-1.6.5 → cat_stack-1.6.6}/README.md +0 -0
  8. {cat_stack-1.6.5 → cat_stack-1.6.6}/pyproject.toml +0 -0
  9. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/cat_stack/__init__.py +0 -0
  10. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/__init__.py +0 -0
  11. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_batch.py +0 -0
  12. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_category_analysis.py +0 -0
  13. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_chunked.py +0 -0
  14. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_embeddings.py +0 -0
  15. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_formatter.py +0 -0
  16. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_pilot_test.py +0 -0
  17. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_prompts.py +0 -0
  18. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_review_ui.py +0 -0
  19. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_tiebreaker.py +0 -0
  20. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_utils.py +0 -0
  21. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_web_fetch.py +0 -0
  22. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/_wrapper_helpers.py +0 -0
  23. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/calls/CoVe.py +0 -0
  24. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/calls/__init__.py +0 -0
  25. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/calls/image_CoVe.py +0 -0
  26. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/calls/image_stepback.py +0 -0
  27. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/calls/pdf_CoVe.py +0 -0
  28. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/calls/pdf_stepback.py +0 -0
  29. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/calls/stepback.py +0 -0
  30. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/calls/top_n.py +0 -0
  31. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/classify.py +0 -0
  32. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/explore.py +0 -0
  33. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/extract.py +0 -0
  34. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/image_functions.py +0 -0
  35. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/images/circle.png +0 -0
  36. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/images/cube.png +0 -0
  37. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/images/diamond.png +0 -0
  38. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/images/overlapping_pentagons.png +0 -0
  39. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/images/rectangles.png +0 -0
  40. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/model_reference_list.py +0 -0
  41. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/pdf_functions.py +0 -0
  42. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/prompt_tune.py +0 -0
  43. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/summarize.py +0 -0
  44. {cat_stack-1.6.5 → cat_stack-1.6.6}/src/catstack/text_functions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cat-stack
3
- Version: 1.6.5
3
+ Version: 1.6.6
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__ = "1.6.5"
4
+ __version__ = "1.6.6"
5
5
  __author__ = "Chris Soria"
6
6
  __email__ = "chrissoria@berkeley.edu"
7
7
  __title__ = "cat-stack"
@@ -125,6 +125,64 @@ _HF_NEEDS_ENABLE_THINKING_OFF = (
125
125
  def _hf_model_needs_enable_thinking_off(model: str) -> bool:
126
126
  return any(model.startswith(p) for p in _HF_NEEDS_ENABLE_THINKING_OFF)
127
127
 
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # Ollama reasoning control: per-model-family parameter format for the
131
+ # top-level `think` field on chat / generate requests.
132
+ #
133
+ # Ollama standardized on a single API field name (`think`) but the value
134
+ # type differs per model family — gpt-oss takes an enum, most others take
135
+ # a boolean. See https://docs.ollama.com/capabilities/thinking.
136
+ #
137
+ # Coverage philosophy: list every Ollama reasoning model family we know of
138
+ # AND that uses the `think` field. Reasoning models that gate via other
139
+ # mechanisms (system prompts, chat-template flags) are explicitly noted in
140
+ # the "NOT in registry" comment below and handled elsewhere — adding them
141
+ # here would silently inject a no-op `think` field, which Ollama may
142
+ # accept but won't honor, leading to surprising behavior.
143
+ #
144
+ # Entries are checked longest-prefix-first by `_ollama_think_value()`, so
145
+ # put more-specific prefixes earlier when adding (e.g. `qwen3-coder` before
146
+ # `qwen3` if they differ).
147
+ #
148
+ # Registry tuple: (model prefix, value-format, low_value, high_value)
149
+ #
150
+ # Models in registry — `think` field works:
151
+ # gpt-oss — enum: "low" / "medium" / "high" (cannot fully disable)
152
+ # qwen3 / qwen3.* — bool: True / False (covers -thinking variants too)
153
+ # qwq — bool: True / False (Qwen QwQ — preceded Qwen3)
154
+ # deepseek-r1 — bool: True / False (covers -distill variants)
155
+ #
156
+ # Models NOT in registry — different mechanism, do NOT add here:
157
+ # magistral — controlled via system prompt (Mistral Magistral)
158
+ # exaone-deep — uses Modelfile-baked reasoning, no API toggle exposed
159
+ # marco-o1 — uses chat-template wrappers, not `think` field
160
+ #
161
+ # Models with NO reasoning (so `think` should not appear at all):
162
+ # gemma2/3, llama3.x/4.x, mistral, mistral-nemo, qwen2.5 (non-QwQ),
163
+ # phi3/4, granite, olmo, codestral, …
164
+ # These are NOT added; the registry's None-return for unmatched prefixes
165
+ # correctly omits the `think` field for them.
166
+ # ---------------------------------------------------------------------------
167
+ _OLLAMA_REASONING_MODELS = (
168
+ ("gpt-oss", "enum", "low", "high"),
169
+ ("qwen3", "bool", False, True), # covers qwen3.*, qwen3-*, -thinking-* variants
170
+ ("qwq", "bool", False, True),
171
+ ("deepseek-r1", "bool", False, True), # covers -distill-qwen, -distill-llama, etc.
172
+ )
173
+
174
+
175
+ def _ollama_think_value(model: str, thinking_budget):
176
+ """Map cat-stack's thinking_budget to the right Ollama `think` value for
177
+ this model family. Returns None if the model isn't in the
178
+ reasoning-capable registry (no `think` field should be set)."""
179
+ if thinking_budget is None:
180
+ return None
181
+ for prefix, fmt, low_val, high_val in _OLLAMA_REASONING_MODELS:
182
+ if model.startswith(prefix):
183
+ return low_val if thinking_budget == 0 else high_val
184
+ return None
185
+
128
186
  __all__ = [
129
187
  # Main client
130
188
  "UnifiedLLMClient",
@@ -457,6 +515,12 @@ class UnifiedLLMClient:
457
515
  elif self.provider in ("huggingface", "huggingface-together"):
458
516
  # HuggingFace needs thinking_budget to disable thinking on models that reason by default
459
517
  return self._build_openai_payload(messages, json_schema, creativity, force_json, thinking_budget)
518
+ elif self.provider == "ollama":
519
+ # Ollama threads thinking_budget to its top-level `think` field for
520
+ # reasoning-capable models (gpt-oss accepts low/medium/high; others
521
+ # accept booleans). Without this, gpt-oss family models emit long
522
+ # <think> blocks by default that bloat per-row generation 3-5x.
523
+ return self._build_openai_payload(messages, json_schema, creativity, force_json, thinking_budget)
460
524
  else:
461
525
  # Other OpenAI-compatible providers (xai, mistral, etc.)
462
526
  return self._build_openai_payload(messages, json_schema, creativity, force_json)
@@ -532,6 +596,19 @@ class UnifiedLLMClient:
532
596
  elif creativity is not None:
533
597
  payload["temperature"] = creativity
534
598
 
599
+ # Ollama: per-model-family reasoning control via the top-level
600
+ # `think` field. gpt-oss expects an enum ("low"/"medium"/"high");
601
+ # qwen3/deepseek-r1 expect a boolean. Models not in the
602
+ # `_OLLAMA_REASONING_MODELS` registry don't support reasoning and
603
+ # get no `think` field (would be a no-op at best, validator-
604
+ # confusing at worst). Without this, Ollama-served gpt-oss
605
+ # produces long `<think>` blocks by default that bloat per-row
606
+ # generation 3-5x.
607
+ if self.provider == "ollama":
608
+ think_value = _ollama_think_value(self.model, thinking_budget)
609
+ if think_value is not None:
610
+ payload["think"] = think_value
611
+
535
612
  # HuggingFace: disable thinking on model families whose chat
536
613
  # template honors `enable_thinking` (Qwen3-family). Other HF-routed
537
614
  # models don't need the kwarg, and strict-validator backends
@@ -3043,7 +3043,7 @@ Categorize text responses {cove_categorize}:
3043
3043
  messages=messages,
3044
3044
  json_schema=json_schemas[cfg["model"]],
3045
3045
  creativity=effective_creativity,
3046
- thinking_budget=thinking_budget if cfg["provider"] in ("google", "openai", "anthropic", "huggingface", "huggingface-together") else None,
3046
+ thinking_budget=thinking_budget if cfg["provider"] in ("google", "openai", "anthropic", "huggingface", "huggingface-together", "ollama") else None,
3047
3047
  max_retries=max_retries,
3048
3048
  )
3049
3049
 
@@ -3100,7 +3100,7 @@ Categorize text responses {cove_categorize}:
3100
3100
  messages=messages,
3101
3101
  json_schema=json_schemas[cfg["model"]],
3102
3102
  creativity=effective_creativity,
3103
- thinking_budget=thinking_budget if cfg["provider"] in ("google", "openai", "anthropic", "huggingface", "huggingface-together") else None,
3103
+ thinking_budget=thinking_budget if cfg["provider"] in ("google", "openai", "anthropic", "huggingface", "huggingface-together", "ollama") else None,
3104
3104
  max_retries=max_retries,
3105
3105
  )
3106
3106
 
@@ -3184,7 +3184,7 @@ Categorize text responses {cove_categorize}:
3184
3184
  messages=_retry_messages,
3185
3185
  json_schema=json_schemas[cfg["model"]],
3186
3186
  creativity=effective_creativity,
3187
- thinking_budget=thinking_budget if cfg["provider"] in ("google", "openai", "anthropic", "huggingface", "huggingface-together") else None,
3187
+ thinking_budget=thinking_budget if cfg["provider"] in ("google", "openai", "anthropic", "huggingface", "huggingface-together", "ollama") else None,
3188
3188
  max_retries=max_retries,
3189
3189
  )
3190
3190
 
File without changes
File without changes
File without changes
File without changes