kynth 0.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.
- kynth-0.1.0/.gitignore +6 -0
- kynth-0.1.0/PKG-INFO +71 -0
- kynth-0.1.0/README.md +53 -0
- kynth-0.1.0/pyproject.toml +29 -0
- kynth-0.1.0/src/kynth/__init__.py +199 -0
kynth-0.1.0/.gitignore
ADDED
kynth-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kynth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for Kynth Core — the AI back-end for your product.
|
|
5
|
+
Project-URL: Homepage, https://api.kynth.studio
|
|
6
|
+
Project-URL: Documentation, https://api.kynth.studio/docs
|
|
7
|
+
Project-URL: Source, https://github.com/kyisaiah47/kynth-os
|
|
8
|
+
Author: Kynth Studios
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: ai,api,chargeback,contract,document-parsing,kynth,llm,ocr,pii
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Python: >=3.8
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# kynth
|
|
20
|
+
|
|
21
|
+
Official Python SDK for **[Kynth Core](https://api.kynth.studio)** — the AI back-end for your product. Parse documents, extract fields, redact PII, analyze contracts, fight chargebacks, and enrich companies.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install kynth
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quickstart
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from kynth import Kynth
|
|
31
|
+
|
|
32
|
+
client = Kynth(api_key="ksk_live_...")
|
|
33
|
+
|
|
34
|
+
doc = client.parse(file_url="https://.../invoice.pdf")
|
|
35
|
+
print(doc["totalAmount"]) # 4820.5
|
|
36
|
+
print(doc["usage"]["balanceRemaining"]) # 490
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Get a key (and 500 free credits) at **[api.kynth.studio](https://api.kynth.studio)**. Zero dependencies — stdlib only, Python 3.8+.
|
|
40
|
+
|
|
41
|
+
## Methods
|
|
42
|
+
|
|
43
|
+
Every method returns the endpoint result as a dict, including a `usage` envelope. A non-2xx response raises a typed `KynthError` (and never burns credits).
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
client.parse(file_url=...) # documents → JSON
|
|
47
|
+
client.extract(text=..., fields=["order", "total"]) # pull named fields
|
|
48
|
+
client.classify(text=..., labels=["billing", "tech"]) # label text
|
|
49
|
+
client.summarize(text=..., length="standard") # summary + actions
|
|
50
|
+
client.redact(text=...) # strip PII/PHI
|
|
51
|
+
client.sentiment(text=..., aspects=["product"]) # sentiment + aspects
|
|
52
|
+
client.contract(file_url=...) # contract → terms + risks
|
|
53
|
+
client.chargeback(reason=..., transaction={"amount": 129}) # representment packet
|
|
54
|
+
client.enrich(email="sam@stripe.com") # company profile
|
|
55
|
+
client.account() # balance
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Error handling
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from kynth import Kynth, KynthError
|
|
62
|
+
|
|
63
|
+
client = Kynth(api_key="ksk_live_...")
|
|
64
|
+
try:
|
|
65
|
+
client.parse(file_url="https://.../invoice.pdf")
|
|
66
|
+
except KynthError as err:
|
|
67
|
+
# err.code: "insufficient_credits" | "rate_limited" | "unauthorized" | ...
|
|
68
|
+
print(err.code, err.status, err.message)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
MIT © Kynth Studios
|
kynth-0.1.0/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# kynth
|
|
2
|
+
|
|
3
|
+
Official Python SDK for **[Kynth Core](https://api.kynth.studio)** — the AI back-end for your product. Parse documents, extract fields, redact PII, analyze contracts, fight chargebacks, and enrich companies.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install kynth
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from kynth import Kynth
|
|
13
|
+
|
|
14
|
+
client = Kynth(api_key="ksk_live_...")
|
|
15
|
+
|
|
16
|
+
doc = client.parse(file_url="https://.../invoice.pdf")
|
|
17
|
+
print(doc["totalAmount"]) # 4820.5
|
|
18
|
+
print(doc["usage"]["balanceRemaining"]) # 490
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Get a key (and 500 free credits) at **[api.kynth.studio](https://api.kynth.studio)**. Zero dependencies — stdlib only, Python 3.8+.
|
|
22
|
+
|
|
23
|
+
## Methods
|
|
24
|
+
|
|
25
|
+
Every method returns the endpoint result as a dict, including a `usage` envelope. A non-2xx response raises a typed `KynthError` (and never burns credits).
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
client.parse(file_url=...) # documents → JSON
|
|
29
|
+
client.extract(text=..., fields=["order", "total"]) # pull named fields
|
|
30
|
+
client.classify(text=..., labels=["billing", "tech"]) # label text
|
|
31
|
+
client.summarize(text=..., length="standard") # summary + actions
|
|
32
|
+
client.redact(text=...) # strip PII/PHI
|
|
33
|
+
client.sentiment(text=..., aspects=["product"]) # sentiment + aspects
|
|
34
|
+
client.contract(file_url=...) # contract → terms + risks
|
|
35
|
+
client.chargeback(reason=..., transaction={"amount": 129}) # representment packet
|
|
36
|
+
client.enrich(email="sam@stripe.com") # company profile
|
|
37
|
+
client.account() # balance
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Error handling
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from kynth import Kynth, KynthError
|
|
44
|
+
|
|
45
|
+
client = Kynth(api_key="ksk_live_...")
|
|
46
|
+
try:
|
|
47
|
+
client.parse(file_url="https://.../invoice.pdf")
|
|
48
|
+
except KynthError as err:
|
|
49
|
+
# err.code: "insufficient_credits" | "rate_limited" | "unauthorized" | ...
|
|
50
|
+
print(err.code, err.status, err.message)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
MIT © Kynth Studios
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kynth"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for Kynth Core — the AI back-end for your product."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Kynth Studios" }]
|
|
13
|
+
keywords = ["kynth", "ai", "api", "document-parsing", "ocr", "pii", "contract", "chargeback", "llm"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
20
|
+
]
|
|
21
|
+
dependencies = []
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://api.kynth.studio"
|
|
25
|
+
Documentation = "https://api.kynth.studio/docs"
|
|
26
|
+
Source = "https://github.com/kyisaiah47/kynth-os"
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build.targets.wheel]
|
|
29
|
+
packages = ["src/kynth"]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Official Python SDK for Kynth Core — the AI back-end for your product.
|
|
2
|
+
|
|
3
|
+
from kynth import Kynth, KynthError
|
|
4
|
+
|
|
5
|
+
client = Kynth(api_key="ksk_live_...")
|
|
6
|
+
doc = client.parse(file_url="https://.../invoice.pdf")
|
|
7
|
+
print(doc["totalAmount"], doc["usage"]["balanceRemaining"])
|
|
8
|
+
|
|
9
|
+
Zero dependencies (stdlib only). Every method returns the endpoint result as a
|
|
10
|
+
dict including a ``usage`` envelope; failures raise a typed ``KynthError`` and
|
|
11
|
+
never burn credits. Get a key + 500 free credits at https://api.kynth.studio.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import urllib.error
|
|
18
|
+
import urllib.request
|
|
19
|
+
from typing import Any, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
__all__ = ["Kynth", "KynthError", "__version__"]
|
|
22
|
+
__version__ = "0.1.0"
|
|
23
|
+
|
|
24
|
+
_DEFAULT_BASE_URL = "https://api.kynth.studio"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class KynthError(Exception):
|
|
28
|
+
"""Raised for any non-2xx API response. Mirrors ``{"error": {"code", "message"}}``."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, code: str, message: str, status: int) -> None:
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.code = code
|
|
33
|
+
self.message = message
|
|
34
|
+
self.status = status
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str: # pragma: no cover - trivial
|
|
37
|
+
return f"KynthError(code={self.code!r}, status={self.status}, message={self.message!r})"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _document(file_url: Optional[str], text: Optional[str], file: Optional[Dict[str, str]]) -> Dict[str, Any]:
|
|
41
|
+
if file_url:
|
|
42
|
+
return {"fileUrl": file_url}
|
|
43
|
+
if file:
|
|
44
|
+
return {"file": file}
|
|
45
|
+
if text:
|
|
46
|
+
return {"text": text}
|
|
47
|
+
raise ValueError("Provide one of: file_url, text, or file={'data','mimeType'}.")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Kynth:
|
|
51
|
+
"""Synchronous client for the Kynth Core API."""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
api_key: str,
|
|
56
|
+
base_url: str = _DEFAULT_BASE_URL,
|
|
57
|
+
timeout: float = 60.0,
|
|
58
|
+
) -> None:
|
|
59
|
+
if not api_key:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
"Kynth: `api_key` is required. Mint one at https://api.kynth.studio/dashboard."
|
|
62
|
+
)
|
|
63
|
+
self.api_key = api_key
|
|
64
|
+
self.base_url = base_url.rstrip("/")
|
|
65
|
+
self.timeout = timeout
|
|
66
|
+
|
|
67
|
+
# -- transport ---------------------------------------------------------
|
|
68
|
+
def _request(self, method: str, path: str, body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
69
|
+
url = f"{self.base_url}{path}"
|
|
70
|
+
data = json.dumps(body).encode("utf-8") if body is not None else None
|
|
71
|
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
|
72
|
+
if data is not None:
|
|
73
|
+
headers["Content-Type"] = "application/json"
|
|
74
|
+
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
|
75
|
+
try:
|
|
76
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
77
|
+
payload = resp.read().decode("utf-8")
|
|
78
|
+
return json.loads(payload) if payload else {}
|
|
79
|
+
except urllib.error.HTTPError as exc:
|
|
80
|
+
raw = exc.read().decode("utf-8", errors="replace")
|
|
81
|
+
code, message = "unknown", f"Request failed with status {exc.code}."
|
|
82
|
+
try:
|
|
83
|
+
err = json.loads(raw).get("error") or {}
|
|
84
|
+
code = err.get("code", code)
|
|
85
|
+
message = err.get("message", message)
|
|
86
|
+
except (ValueError, AttributeError):
|
|
87
|
+
pass
|
|
88
|
+
raise KynthError(code, message, exc.code) from None
|
|
89
|
+
except urllib.error.URLError as exc:
|
|
90
|
+
raise KynthError("unknown", str(exc.reason), 0) from None
|
|
91
|
+
|
|
92
|
+
def _post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
|
|
93
|
+
# Drop None values so optional params are omitted.
|
|
94
|
+
clean = {k: v for k, v in body.items() if v is not None}
|
|
95
|
+
return self._request("POST", path, clean)
|
|
96
|
+
|
|
97
|
+
# -- capabilities ------------------------------------------------------
|
|
98
|
+
def parse(
|
|
99
|
+
self,
|
|
100
|
+
*,
|
|
101
|
+
file_url: Optional[str] = None,
|
|
102
|
+
text: Optional[str] = None,
|
|
103
|
+
file: Optional[Dict[str, str]] = None,
|
|
104
|
+
doc_type: Optional[str] = None,
|
|
105
|
+
) -> Dict[str, Any]:
|
|
106
|
+
"""Parse a document (PDF/image/text) into structured, validated JSON."""
|
|
107
|
+
body = _document(file_url, text, file)
|
|
108
|
+
if doc_type:
|
|
109
|
+
body["docType"] = doc_type
|
|
110
|
+
return self._post("/v1/parse", body)
|
|
111
|
+
|
|
112
|
+
def extract(
|
|
113
|
+
self, *, text: str, fields: List[str], instructions: Optional[str] = None
|
|
114
|
+
) -> Dict[str, Any]:
|
|
115
|
+
"""Pull a caller-defined set of fields out of any text."""
|
|
116
|
+
return self._post("/v1/extract", {"text": text, "fields": fields, "instructions": instructions})
|
|
117
|
+
|
|
118
|
+
def classify(
|
|
119
|
+
self,
|
|
120
|
+
*,
|
|
121
|
+
text: str,
|
|
122
|
+
labels: List[str],
|
|
123
|
+
multi: Optional[bool] = None,
|
|
124
|
+
instructions: Optional[str] = None,
|
|
125
|
+
) -> Dict[str, Any]:
|
|
126
|
+
"""Classify text against your own taxonomy."""
|
|
127
|
+
return self._post(
|
|
128
|
+
"/v1/classify",
|
|
129
|
+
{"text": text, "labels": labels, "multi": multi, "instructions": instructions},
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def summarize(
|
|
133
|
+
self,
|
|
134
|
+
*,
|
|
135
|
+
text: str,
|
|
136
|
+
length: Optional[str] = None,
|
|
137
|
+
action_items: Optional[bool] = None,
|
|
138
|
+
) -> Dict[str, Any]:
|
|
139
|
+
"""Summarize long text into a summary, key points, and action items."""
|
|
140
|
+
return self._post(
|
|
141
|
+
"/v1/summarize", {"text": text, "length": length, "actionItems": action_items}
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def redact(
|
|
145
|
+
self,
|
|
146
|
+
*,
|
|
147
|
+
text: str,
|
|
148
|
+
types: Optional[List[str]] = None,
|
|
149
|
+
placeholder: Optional[str] = None,
|
|
150
|
+
) -> Dict[str, Any]:
|
|
151
|
+
"""Detect and strip PII/PHI from text."""
|
|
152
|
+
return self._post("/v1/redact", {"text": text, "types": types, "placeholder": placeholder})
|
|
153
|
+
|
|
154
|
+
def sentiment(self, *, text: str, aspects: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
155
|
+
"""Sentiment, aspects, and themes from feedback text."""
|
|
156
|
+
return self._post("/v1/sentiment", {"text": text, "aspects": aspects})
|
|
157
|
+
|
|
158
|
+
def contract(
|
|
159
|
+
self,
|
|
160
|
+
*,
|
|
161
|
+
file_url: Optional[str] = None,
|
|
162
|
+
text: Optional[str] = None,
|
|
163
|
+
file: Optional[Dict[str, str]] = None,
|
|
164
|
+
) -> Dict[str, Any]:
|
|
165
|
+
"""Analyze a contract into parties, terms, obligations, and risk flags."""
|
|
166
|
+
return self._post("/v1/contract", _document(file_url, text, file))
|
|
167
|
+
|
|
168
|
+
def chargeback(
|
|
169
|
+
self,
|
|
170
|
+
*,
|
|
171
|
+
reason: str,
|
|
172
|
+
transaction: Dict[str, Any],
|
|
173
|
+
evidence: Optional[List[str]] = None,
|
|
174
|
+
context: Optional[str] = None,
|
|
175
|
+
network: Optional[str] = None,
|
|
176
|
+
) -> Dict[str, Any]:
|
|
177
|
+
"""Turn dispute details into a chargeback representment packet."""
|
|
178
|
+
return self._post(
|
|
179
|
+
"/v1/chargeback",
|
|
180
|
+
{
|
|
181
|
+
"reason": reason,
|
|
182
|
+
"transaction": transaction,
|
|
183
|
+
"evidence": evidence,
|
|
184
|
+
"context": context,
|
|
185
|
+
"network": network,
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def enrich(
|
|
190
|
+
self, *, domain: Optional[str] = None, email: Optional[str] = None
|
|
191
|
+
) -> Dict[str, Any]:
|
|
192
|
+
"""Enrich a domain or work email into a structured company profile."""
|
|
193
|
+
if not domain and not email:
|
|
194
|
+
raise ValueError("Provide one of: domain or email.")
|
|
195
|
+
return self._post("/v1/enrich", {"domain": domain, "email": email})
|
|
196
|
+
|
|
197
|
+
def account(self) -> Dict[str, Any]:
|
|
198
|
+
"""Fetch the authenticated account's credit balance."""
|
|
199
|
+
return self._request("GET", "/v1/account")
|