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.
Files changed (45) hide show
  1. {cat_stack-2.0.1 → cat_stack-2.1.0}/PKG-INFO +3 -1
  2. {cat_stack-2.0.1 → cat_stack-2.1.0}/pyproject.toml +1 -0
  3. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/__about__.py +1 -1
  4. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_providers.py +60 -1
  5. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/text_functions.py +3 -3
  6. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/text_functions_ensemble.py +13 -1
  7. {cat_stack-2.0.1 → cat_stack-2.1.0}/.gitignore +0 -0
  8. {cat_stack-2.0.1 → cat_stack-2.1.0}/LICENSE +0 -0
  9. {cat_stack-2.0.1 → cat_stack-2.1.0}/README.md +0 -0
  10. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/cat_stack/__init__.py +0 -0
  11. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/__init__.py +0 -0
  12. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_batch.py +0 -0
  13. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_category_analysis.py +0 -0
  14. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_chunked.py +0 -0
  15. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_embeddings.py +0 -0
  16. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_formatter.py +0 -0
  17. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_pilot_test.py +0 -0
  18. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_prompts.py +0 -0
  19. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_review_ui.py +0 -0
  20. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_tiebreaker.py +0 -0
  21. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_utils.py +0 -0
  22. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_web_fetch.py +0 -0
  23. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/_wrapper_helpers.py +0 -0
  24. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/CoVe.py +0 -0
  25. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/__init__.py +0 -0
  26. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/image_CoVe.py +0 -0
  27. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/image_stepback.py +0 -0
  28. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/pdf_CoVe.py +0 -0
  29. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/pdf_stepback.py +0 -0
  30. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/stepback.py +0 -0
  31. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/calls/top_n.py +0 -0
  32. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/classify.py +0 -0
  33. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/collapse_themes.py +0 -0
  34. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/explore.py +0 -0
  35. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/extract.py +0 -0
  36. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/image_functions.py +0 -0
  37. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/circle.png +0 -0
  38. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/cube.png +0 -0
  39. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/diamond.png +0 -0
  40. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/overlapping_pentagons.png +0 -0
  41. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/images/rectangles.png +0 -0
  42. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/model_reference_list.py +0 -0
  43. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/pdf_functions.py +0 -0
  44. {cat_stack-2.0.1 → cat_stack-2.1.0}/src/catstack/prompt_tune.py +0 -0
  45. {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.1
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.1"
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://docs.anthropic.com/en/docs/claude-code"
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