classer 0.0.3__py3-none-any.whl

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.
classer/__init__.py ADDED
@@ -0,0 +1,69 @@
1
+ """Classer SDK - Low-cost, fast AI classification API."""
2
+
3
+ from typing import Optional
4
+
5
+ from .client import ClasserClient
6
+ from .exceptions import ClasserError
7
+ from .types import (
8
+ ClassifyResponse,
9
+ TagLabel,
10
+ TagResponse,
11
+ )
12
+
13
+ __all__ = [
14
+ "ClasserClient",
15
+ "ClasserError",
16
+ "ClassifyResponse",
17
+ "TagLabel",
18
+ "TagResponse",
19
+ "classify",
20
+ "tag",
21
+ ]
22
+
23
+ # Default client instance
24
+ _default_client: ClasserClient | None = None
25
+
26
+
27
+ def _get_default_client() -> ClasserClient:
28
+ global _default_client
29
+ if _default_client is None:
30
+ _default_client = ClasserClient()
31
+ return _default_client
32
+
33
+
34
+ def classify(
35
+ text: str,
36
+ labels: Optional[list[str]] = None,
37
+ classifier: Optional[str] = None,
38
+ descriptions: Optional[dict[str, str]] = None,
39
+ speed: Optional[str] = None,
40
+ cache: Optional[bool] = None,
41
+ ) -> ClassifyResponse:
42
+ """Classify text into one of the provided labels (single-label).
43
+
44
+ See ClasserClient.classify for full parameter documentation.
45
+ """
46
+ return _get_default_client().classify(
47
+ text, labels=labels, classifier=classifier,
48
+ descriptions=descriptions, speed=speed, cache=cache,
49
+ )
50
+
51
+
52
+ def tag(
53
+ text: str,
54
+ labels: Optional[list[str]] = None,
55
+ classifier: Optional[str] = None,
56
+ descriptions: Optional[dict[str, str]] = None,
57
+ threshold: Optional[float] = None,
58
+ speed: Optional[str] = None,
59
+ cache: Optional[bool] = None,
60
+ ) -> TagResponse:
61
+ """Tag text with multiple labels (multi-label).
62
+
63
+ See ClasserClient.tag for full parameter documentation.
64
+ """
65
+ return _get_default_client().tag(
66
+ text, labels=labels, classifier=classifier,
67
+ descriptions=descriptions, threshold=threshold,
68
+ speed=speed, cache=cache,
69
+ )
classer/client.py ADDED
@@ -0,0 +1,186 @@
1
+ """Classer client implementation."""
2
+
3
+ import os
4
+ from typing import Optional
5
+
6
+ import httpx
7
+
8
+ from .exceptions import ClasserError
9
+ from .types import (
10
+ ClassifyResponse,
11
+ TagLabel,
12
+ TagResponse,
13
+ )
14
+
15
+
16
+ class ClasserClient:
17
+ """Client for the Classer API."""
18
+
19
+ BASE_URL = "https://api.classer.ai"
20
+
21
+ def __init__(
22
+ self,
23
+ api_key: Optional[str] = None,
24
+ timeout: float = 30.0,
25
+ ):
26
+ """
27
+ Initialize the Classer client.
28
+
29
+ Args:
30
+ api_key: API key for authentication. Falls back to CLASSER_API_KEY env var.
31
+ timeout: Request timeout in seconds.
32
+ """
33
+ self.api_key = api_key or os.environ.get("CLASSER_API_KEY", "")
34
+ self.timeout = timeout
35
+
36
+ def _request(self, endpoint: str, body: dict) -> dict:
37
+ """Make a POST request to the API."""
38
+ url = f"{self.BASE_URL}{endpoint}"
39
+
40
+ headers = {"Content-Type": "application/json"}
41
+ if self.api_key:
42
+ headers["Authorization"] = f"Bearer {self.api_key}"
43
+
44
+ response = httpx.post(url, json=body, headers=headers, timeout=self.timeout)
45
+
46
+ if not response.is_success:
47
+ detail = None
48
+ try:
49
+ error_data = response.json()
50
+ detail = error_data.get("detail") if isinstance(error_data, dict) else None
51
+ except (ValueError, TypeError):
52
+ detail = response.text[:200] if response.text else None
53
+ raise ClasserError(
54
+ f"Request failed with status {response.status_code}",
55
+ status=response.status_code,
56
+ detail=detail,
57
+ )
58
+
59
+ return response.json()
60
+
61
+ @staticmethod
62
+ def _build_body(
63
+ text: str,
64
+ labels: Optional[list[str]] = None,
65
+ classifier: Optional[str] = None,
66
+ descriptions: Optional[dict[str, str]] = None,
67
+ speed: Optional[str] = None,
68
+ cache: Optional[bool] = None,
69
+ threshold: Optional[float] = None,
70
+ ) -> dict:
71
+ """Build a request body dict, omitting None/empty fields."""
72
+ body: dict = {"text": text}
73
+ if classifier:
74
+ body["classifier"] = classifier
75
+ if labels:
76
+ body["labels"] = labels
77
+ if descriptions:
78
+ body["descriptions"] = descriptions
79
+ if threshold is not None:
80
+ body["threshold"] = threshold
81
+ if speed:
82
+ body["speed"] = speed
83
+ if cache is not None:
84
+ body["cache"] = cache
85
+ return body
86
+
87
+ def classify(
88
+ self,
89
+ text: str,
90
+ labels: Optional[list[str]] = None,
91
+ classifier: Optional[str] = None,
92
+ descriptions: Optional[dict[str, str]] = None,
93
+ speed: Optional[str] = None,
94
+ cache: Optional[bool] = None,
95
+ ) -> ClassifyResponse:
96
+ """
97
+ Classify text into one of the provided labels (single-label).
98
+
99
+ Args:
100
+ text: Text to classify.
101
+ labels: List of possible labels (1-200).
102
+ classifier: Saved classifier name or "name@vN" reference.
103
+ descriptions: Maps label name to description for better accuracy.
104
+ speed: Speed tier — "standard" (default, <1s) or "fast" (<200ms).
105
+ cache: Set to False to bypass cache. Default: True.
106
+
107
+ Returns:
108
+ ClassifyResponse with label and confidence.
109
+
110
+ Example:
111
+ >>> result = classer.classify(
112
+ ... text="I can't log in",
113
+ ... labels=["billing", "technical_support", "sales"]
114
+ ... )
115
+ >>> print(result.label) # "technical_support"
116
+ """
117
+ body = self._build_body(
118
+ text, labels=labels, classifier=classifier,
119
+ descriptions=descriptions, speed=speed, cache=cache,
120
+ )
121
+
122
+ data = self._request("/v1/classify", body)
123
+
124
+ return ClassifyResponse(
125
+ label=data.get("label"),
126
+ confidence=data.get("confidence"),
127
+ tokens=data.get("tokens", 0),
128
+ latency_ms=data.get("latency_ms", 0),
129
+ cached=data.get("cached", False),
130
+ public=data.get("public"),
131
+ )
132
+
133
+ def tag(
134
+ self,
135
+ text: str,
136
+ labels: Optional[list[str]] = None,
137
+ classifier: Optional[str] = None,
138
+ descriptions: Optional[dict[str, str]] = None,
139
+ threshold: Optional[float] = None,
140
+ speed: Optional[str] = None,
141
+ cache: Optional[bool] = None,
142
+ ) -> TagResponse:
143
+ """
144
+ Tag text with multiple labels that exceed a confidence threshold.
145
+
146
+ Args:
147
+ text: Text to tag.
148
+ labels: List of possible labels (1-200).
149
+ classifier: Saved classifier name or "name@vN" reference.
150
+ descriptions: Maps label name to description for better accuracy.
151
+ threshold: Confidence threshold (0-1). Default: 0.3.
152
+ speed: Speed tier — "standard" (default, <1s) or "fast" (<200ms).
153
+ cache: Set to False to bypass cache. Default: True.
154
+
155
+ Returns:
156
+ TagResponse with labels list (each has label and confidence).
157
+
158
+ Example:
159
+ >>> result = classer.tag(
160
+ ... text="Breaking: Tech stocks surge amid AI boom",
161
+ ... labels=["politics", "technology", "finance", "sports"],
162
+ ... threshold=0.3
163
+ ... )
164
+ >>> for tag in result.labels:
165
+ ... print(f"{tag.label}: {tag.confidence}")
166
+ """
167
+ body = self._build_body(
168
+ text, labels=labels, classifier=classifier,
169
+ descriptions=descriptions, threshold=threshold,
170
+ speed=speed, cache=cache,
171
+ )
172
+
173
+ data = self._request("/v1/tag", body)
174
+
175
+ tag_labels = [
176
+ TagLabel(label=item["label"], confidence=item["confidence"])
177
+ for item in data.get("labels") or []
178
+ ]
179
+
180
+ return TagResponse(
181
+ labels=tag_labels,
182
+ tokens=data.get("tokens", 0),
183
+ latency_ms=data.get("latency_ms", 0),
184
+ cached=data.get("cached", False),
185
+ public=data.get("public"),
186
+ )
classer/exceptions.py ADDED
@@ -0,0 +1,26 @@
1
+ """Exception classes for the Classer SDK."""
2
+
3
+ from typing import Optional
4
+
5
+
6
+ class ClasserError(Exception):
7
+ """Base exception for Classer SDK errors."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ status: Optional[int] = None,
13
+ detail: Optional[str] = None,
14
+ ):
15
+ super().__init__(message)
16
+ self.message = message
17
+ self.status = status
18
+ self.detail = detail
19
+
20
+ def __str__(self) -> str:
21
+ parts = [self.message]
22
+ if self.status:
23
+ parts.append(f"(status: {self.status})")
24
+ if self.detail:
25
+ parts.append(f"- {self.detail}")
26
+ return " ".join(parts)
classer/types.py ADDED
@@ -0,0 +1,35 @@
1
+ """Type definitions for the Classer SDK."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Optional
5
+
6
+
7
+ @dataclass
8
+ class ClassifyResponse:
9
+ """Response from single-label classification."""
10
+
11
+ label: Optional[str] = None
12
+ confidence: Optional[float] = None
13
+ tokens: int = 0
14
+ latency_ms: float = 0.0
15
+ cached: bool = False
16
+ public: Optional[bool] = None
17
+
18
+
19
+ @dataclass
20
+ class TagLabel:
21
+ """Single label with confidence in a tag response."""
22
+
23
+ label: str
24
+ confidence: float
25
+
26
+
27
+ @dataclass
28
+ class TagResponse:
29
+ """Response from multi-label tagging."""
30
+
31
+ labels: list[TagLabel] = field(default_factory=list)
32
+ tokens: int = 0
33
+ latency_ms: float = 0.0
34
+ cached: bool = False
35
+ public: Optional[bool] = None
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: classer
3
+ Version: 0.0.3
4
+ Summary: High-performance AI classification — <200ms latency, up to 100x cheaper, beats GPT-5 mini accuracy
5
+ Project-URL: Homepage, https://classer.ai
6
+ Project-URL: Documentation, https://docs.classer.ai
7
+ Project-URL: Repository, https://github.com/classer-ai/classer-python
8
+ Author: Classer.ai
9
+ License-Expression: MIT
10
+ Keywords: ai,classification,llm,machine-learning,nlp,text-classification
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: httpx>=0.25.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
24
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
25
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ exit
29
+ # [classer](https://classer.ai)
30
+
31
+ High-performance AI classification
32
+
33
+ <200ms latency · up to 100x cheaper · beats GPT-5 mini accuracy · self-calibrating accuracy · no prompt engineering
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install classer
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```python
44
+ import classer
45
+
46
+ # Single-label classification
47
+ result = classer.classify(
48
+ text="I can't log in and need a password reset.",
49
+ labels=["billing", "technical_support", "sales", "spam"]
50
+ )
51
+ print(result.label) # "technical_support"
52
+ print(result.confidence) # 0.94
53
+
54
+ # With descriptions for better accuracy
55
+ lead = classer.classify(
56
+ text="We need a solution for 500 users, what's your enterprise pricing?",
57
+ labels=["hot", "warm", "cold"],
58
+ descriptions={
59
+ "hot": "Ready to buy, asking for pricing or demo",
60
+ "warm": "Interested but exploring options",
61
+ "cold": "Just browsing, no clear intent"
62
+ }
63
+ )
64
+ print(lead.label) # "hot"
65
+
66
+ # Multi-label tagging
67
+ result = classer.tag(
68
+ text="Breaking: Tech stocks surge amid AI boom",
69
+ labels=["politics", "technology", "finance", "sports"],
70
+ threshold=0.3
71
+ )
72
+ for t in result.labels:
73
+ print(f"{t.label}: {t.confidence}")
74
+ # technology: 0.92
75
+ # finance: 0.78
76
+ ```
77
+
78
+ ## Configuration
79
+
80
+ No API key is needed to get started. To unlock higher rate limits, get an API key from [classer.ai/api-keys](https://classer.ai/api-keys).
81
+
82
+ ```bash
83
+ export CLASSER_API_KEY=your-api-key
84
+ ```
85
+
86
+ Or configure programmatically:
87
+
88
+ ```python
89
+ from classer import ClasserClient
90
+
91
+ client = ClasserClient(
92
+ api_key="your-api-key"
93
+ )
94
+ ```
95
+
96
+ ## API Reference
97
+
98
+ ### `classify(text, labels=None, classifier=None, descriptions=None, speed=None, cache=None)`
99
+
100
+ Classify text into exactly one of the provided labels.
101
+
102
+ ```python
103
+ result = classer.classify(
104
+ text="Text to classify",
105
+ labels=["label1", "label2"], # 1-200 possible labels
106
+ descriptions={"label1": "Description for better accuracy"},
107
+ speed="standard", # "standard" (default, <1s) or "fast" (<200ms)
108
+ cache=True # Set to False to bypass cache. Default: True
109
+ )
110
+
111
+ result.label # Selected label
112
+ result.confidence # 0-1 confidence score
113
+ result.tokens # Total tokens used
114
+ result.latency_ms # Processing time in ms
115
+ result.cached # Whether served from cache
116
+ ```
117
+
118
+ ### `tag(text, labels=None, classifier=None, descriptions=None, threshold=None, speed=None, cache=None)`
119
+
120
+ Multi-label tagging — returns all labels above a confidence threshold.
121
+
122
+ ```python
123
+ result = classer.tag(
124
+ text="Text to tag",
125
+ labels=["label1", "label2"], # 1-200 possible labels
126
+ descriptions={"label1": "Description"},
127
+ threshold=0.3, # Default: 0.3
128
+ speed="standard", # "standard" (default, <1s) or "fast" (<200ms)
129
+ cache=True # Set to False to bypass cache. Default: True
130
+ )
131
+
132
+ for t in result.labels:
133
+ print(f"{t.label}: {t.confidence}")
134
+
135
+ result.tokens # Total tokens used
136
+ result.latency_ms # Processing time in ms
137
+ result.cached # Whether served from cache
138
+ ```
139
+
140
+ ## Error Handling
141
+
142
+ ```python
143
+ from classer import ClasserError
144
+
145
+ try:
146
+ result = classer.classify(text="hello", labels=["a", "b"])
147
+ except ClasserError as e:
148
+ print(e.status) # HTTP status code
149
+ print(e.detail) # Error detail from API
150
+ ```
151
+
152
+ ## Documentation
153
+
154
+ Full API reference and guides at [docs.classer.ai](https://docs.classer.ai).
155
+
156
+ ## GitHub
157
+
158
+ [github.com/classer-ai/classer-python](https://github.com/classer-ai/classer-python)
159
+
160
+ ## License
161
+
162
+ MIT
@@ -0,0 +1,7 @@
1
+ classer/__init__.py,sha256=CMP550ePsx_AEofILAY_6q72FySK-q0NBQyQNTLG7Lo,1764
2
+ classer/client.py,sha256=j0QT1DGQN5Xy-skxfkAZGgnGBqpfPDsftuzZQc9D2fs,6122
3
+ classer/exceptions.py,sha256=98CxeqMJ1OW8VzSLIPu3dWzEPR6VHGQ-vzihOz_Rlk0,656
4
+ classer/types.py,sha256=Bctr4OCDiRPAe0uyhDf5pgBZbHVKG_-x1oX7FVdssc4,750
5
+ classer-0.0.3.dist-info/METADATA,sha256=W41-9xJCOnHg9novtJXpUkmrcr7AHMwYvBtBOPa1YcI,4603
6
+ classer-0.0.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ classer-0.0.3.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any