cat-stack 2.0.1__tar.gz → 2.1.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.
- {cat_stack-2.0.1 → cat_stack-2.1.0}/PKG-INFO +3 -1
- {cat_stack-2.0.1 → cat_stack-2.1.0}/pyproject.toml +1 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/__about__.py +1 -1
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_providers.py +60 -1
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/text_functions.py +3 -3
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/text_functions_ensemble.py +13 -1
- {cat_stack-2.0.1 → cat_stack-2.1.0}/.gitignore +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/LICENSE +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/README.md +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/cat_stack/__init__.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/__init__.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_batch.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_category_analysis.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_chunked.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_embeddings.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_formatter.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_pilot_test.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_prompts.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_review_ui.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_tiebreaker.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_utils.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_web_fetch.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_wrapper_helpers.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/CoVe.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/__init__.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/image_CoVe.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/image_stepback.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/pdf_CoVe.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/pdf_stepback.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/stepback.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/top_n.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/classify.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/collapse_themes.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/explore.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/extract.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/image_functions.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/circle.png +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/cube.png +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/diamond.png +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/overlapping_pentagons.png +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/rectangles.png +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/model_reference_list.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/pdf_functions.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/prompt_tune.py +0 -0
- {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/summarize.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cat-stack
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.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
|
|
@@ -22,6 +22,8 @@ Requires-Python: >=3.8
|
|
|
22
22
|
Requires-Dist: pandas
|
|
23
23
|
Requires-Dist: requests
|
|
24
24
|
Requires-Dist: tqdm
|
|
25
|
+
Provides-Extra: agent
|
|
26
|
+
Requires-Dist: cat-claws>=0.1.0; extra == 'agent'
|
|
25
27
|
Provides-Extra: docx
|
|
26
28
|
Requires-Dist: python-docx>=1.0.0; extra == 'docx'
|
|
27
29
|
Provides-Extra: embeddings
|
|
@@ -35,6 +35,7 @@ pdf = ["PyMuPDF>=1.23.0"]
|
|
|
35
35
|
docx = ["python-docx>=1.0.0"]
|
|
36
36
|
formatter = ["torch>=2.0.0", "transformers>=4.40.0", "accelerate>=0.27.0"]
|
|
37
37
|
embeddings = ["sentence-transformers>=2.2.0"]
|
|
38
|
+
agent = ["cat-claws>=0.1.0"]
|
|
38
39
|
|
|
39
40
|
[project.urls]
|
|
40
41
|
Documentation = "https://github.com/chrissoria/cat-stack#readme"
|
|
@@ -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.0
|
|
4
|
+
__version__ = "2.1.0"
|
|
5
5
|
__author__ = "Chris Soria"
|
|
6
6
|
__email__ = "chrissoria@berkeley.edu"
|
|
7
7
|
__title__ = "cat-stack"
|
|
@@ -721,6 +721,11 @@ PROVIDER_CONFIG = {
|
|
|
721
721
|
"auth_header": None,
|
|
722
722
|
"auth_prefix": "",
|
|
723
723
|
},
|
|
724
|
+
"claude-agent": {
|
|
725
|
+
"endpoint": None, # Uses the cat-claws SDK adapter, not HTTP
|
|
726
|
+
"auth_header": None,
|
|
727
|
+
"auth_prefix": "",
|
|
728
|
+
},
|
|
724
729
|
}
|
|
725
730
|
|
|
726
731
|
|
|
@@ -1195,7 +1200,7 @@ class UnifiedLLMClient:
|
|
|
1195
1200
|
except FileNotFoundError:
|
|
1196
1201
|
return None, (
|
|
1197
1202
|
"Claude CLI not found. Install it: "
|
|
1198
|
-
"https://
|
|
1203
|
+
"https://code.claude.com/docs"
|
|
1199
1204
|
)
|
|
1200
1205
|
|
|
1201
1206
|
return None, "Max retries exceeded"
|
|
@@ -1206,6 +1211,55 @@ class UnifiedLLMClient:
|
|
|
1206
1211
|
# contract that callers depend on.
|
|
1207
1212
|
return None, f"Claude CLI subprocess failed: {e} (prompt may be too large for argv)"
|
|
1208
1213
|
|
|
1214
|
+
def _call_claude_agent(
|
|
1215
|
+
self,
|
|
1216
|
+
messages: list,
|
|
1217
|
+
thinking_budget: int = None,
|
|
1218
|
+
) -> tuple[str, str | None]:
|
|
1219
|
+
"""Route one completion through the cat-claws SDK adapter.
|
|
1220
|
+
|
|
1221
|
+
Like `_call_claude_cli`, this runs on the user's Claude subscription
|
|
1222
|
+
(no API key) and returns the same (text, error) contract. cat-claws is
|
|
1223
|
+
an optional dependency (the `[agent]` extra); a missing install
|
|
1224
|
+
degrades to a clear install hint rather than an ImportError traceback.
|
|
1225
|
+
|
|
1226
|
+
The adapter is async. complete() is sync and may run inside ensemble
|
|
1227
|
+
worker threads, so we drive one sealed call per invocation with
|
|
1228
|
+
asyncio.run (a fresh loop per call) - never a shared/module-global
|
|
1229
|
+
loop. Message flattening mirrors _call_claude_cli exactly.
|
|
1230
|
+
"""
|
|
1231
|
+
try:
|
|
1232
|
+
from catclaws._adapters import get_adapter
|
|
1233
|
+
except ImportError:
|
|
1234
|
+
return None, (
|
|
1235
|
+
"cat-claws is not installed. Install it to use "
|
|
1236
|
+
"model_source='claude-agent': pip install cat-stack[agent]"
|
|
1237
|
+
)
|
|
1238
|
+
import asyncio
|
|
1239
|
+
|
|
1240
|
+
system_parts = []
|
|
1241
|
+
user_parts = []
|
|
1242
|
+
for msg in messages:
|
|
1243
|
+
if msg["role"] == "system":
|
|
1244
|
+
system_parts.append(msg["content"])
|
|
1245
|
+
elif msg["role"] in ("user", "assistant"):
|
|
1246
|
+
user_parts.append(msg["content"])
|
|
1247
|
+
system_prompt = "\n\n".join(system_parts) if system_parts else None
|
|
1248
|
+
user_prompt = "\n\n".join(user_parts)
|
|
1249
|
+
|
|
1250
|
+
adapter = get_adapter("claude")
|
|
1251
|
+
try:
|
|
1252
|
+
return asyncio.run(
|
|
1253
|
+
adapter.one_shot(
|
|
1254
|
+
user_prompt,
|
|
1255
|
+
system_prompt=system_prompt,
|
|
1256
|
+
model=self.model,
|
|
1257
|
+
thinking_budget=thinking_budget or 0,
|
|
1258
|
+
)
|
|
1259
|
+
)
|
|
1260
|
+
except Exception as e:
|
|
1261
|
+
return None, f"cat-claws call failed: {e}"
|
|
1262
|
+
|
|
1209
1263
|
def complete(
|
|
1210
1264
|
self,
|
|
1211
1265
|
messages: list,
|
|
@@ -1249,6 +1303,9 @@ class UnifiedLLMClient:
|
|
|
1249
1303
|
if self.provider == "claude-code":
|
|
1250
1304
|
return self._call_claude_cli(messages, max_retries=max_retries, initial_delay=initial_delay)
|
|
1251
1305
|
|
|
1306
|
+
if self.provider == "claude-agent":
|
|
1307
|
+
return self._call_claude_agent(messages, thinking_budget=thinking_budget)
|
|
1308
|
+
|
|
1252
1309
|
headers = self._get_headers()
|
|
1253
1310
|
payload = self._build_payload(messages, json_schema, creativity, thinking_budget=thinking_budget, force_json=force_json)
|
|
1254
1311
|
|
|
@@ -1741,6 +1798,8 @@ def _detect_model_source(user_model, model_source):
|
|
|
1741
1798
|
still use this name. Will be inlined in a future cleanup."""
|
|
1742
1799
|
if model_source and model_source.lower() == "claude-code":
|
|
1743
1800
|
return "claude-code"
|
|
1801
|
+
if model_source and model_source.lower() == "claude-agent":
|
|
1802
|
+
return "claude-agent"
|
|
1744
1803
|
return detect_provider(user_model, provider=model_source)
|
|
1745
1804
|
|
|
1746
1805
|
|
|
@@ -410,7 +410,7 @@ def explore_corpus(
|
|
|
410
410
|
provider = detect_provider(model, provider)
|
|
411
411
|
|
|
412
412
|
# Validate api_key
|
|
413
|
-
if provider not in ("ollama", "claude-code") and not api_key:
|
|
413
|
+
if provider not in ("ollama", "claude-code", "claude-agent") and not api_key:
|
|
414
414
|
raise ValueError(f"api_key is required for provider '{provider}'")
|
|
415
415
|
|
|
416
416
|
print(f"Exploring categories for question: '{survey_question}'")
|
|
@@ -596,7 +596,7 @@ def explore_common_categories(
|
|
|
596
596
|
provider = detect_provider(model, provider)
|
|
597
597
|
|
|
598
598
|
# Validate api_key
|
|
599
|
-
if provider not in ("ollama", "claude-code") and not api_key:
|
|
599
|
+
if provider not in ("ollama", "claude-code", "claude-agent") and not api_key:
|
|
600
600
|
raise ValueError(f"api_key is required for provider '{provider}'")
|
|
601
601
|
|
|
602
602
|
# Ollama-specific checks
|
|
@@ -1062,7 +1062,7 @@ def multi_class(
|
|
|
1062
1062
|
provider = detect_provider(model, provider)
|
|
1063
1063
|
|
|
1064
1064
|
# Validate api_key requirement
|
|
1065
|
-
if provider not in ("ollama", "claude-code") and not api_key:
|
|
1065
|
+
if provider not in ("ollama", "claude-code", "claude-agent") and not api_key:
|
|
1066
1066
|
raise ValueError(f"api_key is required for provider '{provider}'")
|
|
1067
1067
|
|
|
1068
1068
|
# Handle categories="auto" - auto-detect categories from the data
|
|
@@ -660,6 +660,18 @@ def prepare_model_configs(
|
|
|
660
660
|
"Install: https://docs.anthropic.com/en/docs/claude-code\n"
|
|
661
661
|
+ "="*60
|
|
662
662
|
)
|
|
663
|
+
elif detected_provider == "claude-agent":
|
|
664
|
+
try:
|
|
665
|
+
import catclaws # noqa: F401
|
|
666
|
+
except ImportError:
|
|
667
|
+
raise ConnectionError(
|
|
668
|
+
"\n" + "="*60 + "\n"
|
|
669
|
+
" CAT-AGENT NOT INSTALLED\n"
|
|
670
|
+
"="*60 + "\n\n"
|
|
671
|
+
"The cat-claws package is required to use claude-agent as a provider.\n"
|
|
672
|
+
"Install: pip install cat-stack[agent]\n"
|
|
673
|
+
+ "="*60
|
|
674
|
+
)
|
|
663
675
|
else:
|
|
664
676
|
# Validate API key exists for cloud providers
|
|
665
677
|
if not api_key:
|
|
@@ -670,7 +682,7 @@ def prepare_model_configs(
|
|
|
670
682
|
# Preflight probe: test the model with a minimal JSON call to catch
|
|
671
683
|
# issues (model not found, structured output not supported) before
|
|
672
684
|
# processing thousands of rows.
|
|
673
|
-
if detected_provider not in ("ollama", "claude-code"):
|
|
685
|
+
if detected_provider not in ("ollama", "claude-code", "claude-agent"):
|
|
674
686
|
try:
|
|
675
687
|
probe_client = UnifiedLLMClient(
|
|
676
688
|
provider=detected_provider,
|
|
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
|