sapling-py 4.1.0__tar.gz → 4.3.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.
- {sapling_py-4.1.0/sapling_py.egg-info → sapling_py-4.3.0}/PKG-INFO +17 -18
- {sapling_py-4.1.0 → sapling_py-4.3.0}/README.md +12 -3
- sapling_py-4.3.0/pyproject.toml +42 -0
- {sapling_py-4.1.0 → sapling_py-4.3.0}/sapling/client.py +363 -4
- sapling_py-4.3.0/sapling/version.py +1 -0
- {sapling_py-4.1.0 → sapling_py-4.3.0/sapling_py.egg-info}/PKG-INFO +17 -18
- {sapling_py-4.1.0 → sapling_py-4.3.0}/sapling_py.egg-info/SOURCES.txt +7 -2
- sapling_py-4.3.0/setup.py +53 -0
- sapling_py-4.3.0/test/test_classify.py +200 -0
- {sapling_py-4.1.0 → sapling_py-4.3.0}/test/test_client.py +37 -0
- sapling_py-4.3.0/test/test_extract.py +213 -0
- sapling_py-4.3.0/test/test_pii.py +66 -0
- sapling_py-4.3.0/test/test_safety.py +56 -0
- sapling_py-4.3.0/test/test_seo.py +116 -0
- sapling_py-4.1.0/sapling/version.py +0 -1
- sapling_py-4.1.0/setup.py +0 -41
- sapling_py-4.1.0/test/__init__.py +0 -0
- {sapling_py-4.1.0 → sapling_py-4.3.0}/LICENSE +0 -0
- {sapling_py-4.1.0 → sapling_py-4.3.0}/sapling/__init__.py +0 -0
- {sapling_py-4.1.0 → sapling_py-4.3.0}/sapling_py.egg-info/dependency_links.txt +0 -0
- {sapling_py-4.1.0 → sapling_py-4.3.0}/sapling_py.egg-info/requires.txt +0 -0
- {sapling_py-4.1.0 → sapling_py-4.3.0}/sapling_py.egg-info/top_level.txt +0 -0
- {sapling_py-4.1.0 → sapling_py-4.3.0}/setup.cfg +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sapling-py
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
4
4
|
Summary: Sapling Python Client
|
|
5
|
-
|
|
6
|
-
Author: Sapling Intelligence
|
|
7
|
-
Author-email: info@sapling.ai
|
|
5
|
+
Author-email: Sapling Intelligence <info@sapling.ai>
|
|
8
6
|
License: Apache License 2.0
|
|
7
|
+
Project-URL: Homepage, https://sapling.ai
|
|
8
|
+
Project-URL: Documentation, https://sapling.readthedocs.io/
|
|
9
|
+
Project-URL: Repository, https://github.com/saplingai/sapling-py
|
|
9
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
12
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -17,18 +18,7 @@ Requires-Dist: requests
|
|
|
17
18
|
Provides-Extra: test
|
|
18
19
|
Requires-Dist: pytest; extra == "test"
|
|
19
20
|
Requires-Dist: responses; extra == "test"
|
|
20
|
-
Dynamic: author
|
|
21
|
-
Dynamic: author-email
|
|
22
|
-
Dynamic: classifier
|
|
23
|
-
Dynamic: description
|
|
24
|
-
Dynamic: description-content-type
|
|
25
|
-
Dynamic: home-page
|
|
26
|
-
Dynamic: license
|
|
27
21
|
Dynamic: license-file
|
|
28
|
-
Dynamic: provides-extra
|
|
29
|
-
Dynamic: requires-dist
|
|
30
|
-
Dynamic: requires-python
|
|
31
|
-
Dynamic: summary
|
|
32
22
|
|
|
33
23
|
# Sapling Python Client
|
|
34
24
|
|
|
@@ -40,7 +30,9 @@ Compare against leading grammar checking tools and APIs like Grammarly (Grammerl
|
|
|
40
30
|
## Overview
|
|
41
31
|
|
|
42
32
|
Sapling is a toolkit for helping developers build language model-powered applications.
|
|
43
|
-
The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
|
|
33
|
+
The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
|
|
34
|
+
zero-shot text classification, structured data extraction, PII detection and redaction, content safety
|
|
35
|
+
scoring, SEO analysis, and more.
|
|
44
36
|
|
|
45
37
|
Key Features:
|
|
46
38
|
- **60% More Corrections**: Outperforms [competing solutions](https://sapling.ai/comparison/api#sapling) at similar accuracy levels using state-of-the-art generative AI models.
|
|
@@ -52,12 +44,19 @@ Key Features:
|
|
|
52
44
|
|
|
53
45
|
## Installation
|
|
54
46
|
|
|
55
|
-
Install the `sapling-py` package with
|
|
47
|
+
Install the `sapling-py` package from PyPI with
|
|
48
|
+
[pip](https://pip.pypa.io/en/stable/installation/):
|
|
56
49
|
|
|
57
|
-
```
|
|
50
|
+
```sh
|
|
58
51
|
python -m pip install sapling-py
|
|
59
52
|
```
|
|
60
53
|
|
|
54
|
+
Or add it to a [uv](https://docs.astral.sh/uv/) project:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
uv add sapling-py
|
|
58
|
+
```
|
|
59
|
+
|
|
61
60
|
## Documentation
|
|
62
61
|
Documentation for the client is available at [Read the Docs](https://sapling.readthedocs.io/) and
|
|
63
62
|
documentation for the HTTP API is available at [Sapling.ai](https://sapling.ai/docs).
|
|
@@ -8,7 +8,9 @@ Compare against leading grammar checking tools and APIs like Grammarly (Grammerl
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
10
|
Sapling is a toolkit for helping developers build language model-powered applications.
|
|
11
|
-
The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
|
|
11
|
+
The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
|
|
12
|
+
zero-shot text classification, structured data extraction, PII detection and redaction, content safety
|
|
13
|
+
scoring, SEO analysis, and more.
|
|
12
14
|
|
|
13
15
|
Key Features:
|
|
14
16
|
- **60% More Corrections**: Outperforms [competing solutions](https://sapling.ai/comparison/api#sapling) at similar accuracy levels using state-of-the-art generative AI models.
|
|
@@ -20,12 +22,19 @@ Key Features:
|
|
|
20
22
|
|
|
21
23
|
## Installation
|
|
22
24
|
|
|
23
|
-
Install the `sapling-py` package with
|
|
25
|
+
Install the `sapling-py` package from PyPI with
|
|
26
|
+
[pip](https://pip.pypa.io/en/stable/installation/):
|
|
24
27
|
|
|
25
|
-
```
|
|
28
|
+
```sh
|
|
26
29
|
python -m pip install sapling-py
|
|
27
30
|
```
|
|
28
31
|
|
|
32
|
+
Or add it to a [uv](https://docs.astral.sh/uv/) project:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
uv add sapling-py
|
|
36
|
+
```
|
|
37
|
+
|
|
29
38
|
## Documentation
|
|
30
39
|
Documentation for the client is available at [Read the Docs](https://sapling.readthedocs.io/) and
|
|
31
40
|
documentation for the HTTP API is available at [Sapling.ai](https://sapling.ai/docs).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sapling-py"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Sapling Python Client"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.7"
|
|
11
|
+
license = { text = "Apache License 2.0" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Sapling Intelligence", email = "info@sapling.ai" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 5 - Production/Stable",
|
|
17
|
+
"License :: OSI Approved :: Apache Software License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"requests",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
test = [
|
|
27
|
+
"pytest",
|
|
28
|
+
"responses",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://sapling.ai"
|
|
33
|
+
Documentation = "https://sapling.readthedocs.io/"
|
|
34
|
+
Repository = "https://github.com/saplingai/sapling-py"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.dynamic]
|
|
37
|
+
version = { attr = "sapling.version.__version__" }
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
include = ["sapling", "sapling.*"]
|
|
41
|
+
exclude = ["test", "test.*"]
|
|
42
|
+
namespaces = false
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
import uuid
|
|
3
|
+
from collections.abc import Iterable
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class SaplingError(Exception):
|
|
@@ -631,6 +632,92 @@ class SaplingClient:
|
|
|
631
632
|
}
|
|
632
633
|
return self._request(url, data)
|
|
633
634
|
|
|
635
|
+
def extract(
|
|
636
|
+
self,
|
|
637
|
+
text,
|
|
638
|
+
fields,
|
|
639
|
+
context=None,
|
|
640
|
+
):
|
|
641
|
+
'''
|
|
642
|
+
Extracts structured data from unstructured text: you name the fields you
|
|
643
|
+
want, the API returns their values pulled from the document. Every value is
|
|
644
|
+
grounded in a verbatim span of the input, so this is extraction rather than
|
|
645
|
+
summarization or inference; a field the text does not state is reported
|
|
646
|
+
missing rather than guessed.
|
|
647
|
+
|
|
648
|
+
Example::
|
|
649
|
+
|
|
650
|
+
client.extract(
|
|
651
|
+
'Invoice INV-1042 for Acme Corp, total $1,299.00, due March 5, 2026.',
|
|
652
|
+
fields=[
|
|
653
|
+
'invoice_number',
|
|
654
|
+
{'name': 'total', 'type': 'number', 'description': 'Amount due',
|
|
655
|
+
'required': True},
|
|
656
|
+
{'name': 'due_date', 'type': 'date'},
|
|
657
|
+
'purchase_order',
|
|
658
|
+
],
|
|
659
|
+
context='A vendor invoice',
|
|
660
|
+
)
|
|
661
|
+
# {'data': {'invoice_number': 'INV-1042', 'total': 1299.0,
|
|
662
|
+
# 'due_date': '2026-03-05', 'purchase_order': None},
|
|
663
|
+
# 'fields': [{'name': 'invoice_number', 'type': 'string',
|
|
664
|
+
# 'value': 'INV-1042', 'evidence': 'Invoice INV-1042',
|
|
665
|
+
# 'found': True},
|
|
666
|
+
# {'name': 'total', 'type': 'number', 'value': 1299.0,
|
|
667
|
+
# 'evidence': 'total $1,299.00', 'found': True},
|
|
668
|
+
# {'name': 'due_date', 'type': 'date', 'value': '2026-03-05',
|
|
669
|
+
# 'evidence': 'due March 5, 2026', 'found': True},
|
|
670
|
+
# {'name': 'purchase_order', 'type': 'string', 'value': None,
|
|
671
|
+
# 'evidence': '', 'found': False}],
|
|
672
|
+
# 'missing': ['purchase_order']}
|
|
673
|
+
|
|
674
|
+
:param text: Document to extract from, plain text or HTML (tags are
|
|
675
|
+
stripped), up to 10,000 characters.
|
|
676
|
+
:type text: str
|
|
677
|
+
:param fields: 1-20 fields to extract. Each entry is either a field name
|
|
678
|
+
(str) or a dict ``{'name': str, 'type': str, 'description': str,
|
|
679
|
+
'required': bool}`` where everything but ``name`` is optional. Names are
|
|
680
|
+
up to 50 characters and must be unique (case-insensitive). ``type`` is
|
|
681
|
+
one of ``'string'`` (the default), ``'number'``, ``'integer'``,
|
|
682
|
+
``'boolean'``, ``'date'`` or ``'list'``. ``description`` is up to 200
|
|
683
|
+
characters and sharpens ambiguous fields. ``required`` is a hint to the
|
|
684
|
+
model; a required field the text does not state is still reported
|
|
685
|
+
missing, never guessed.
|
|
686
|
+
:type fields: list[str | dict]
|
|
687
|
+
:raises TypeError: If ``fields`` is None, a single string or a dict rather
|
|
688
|
+
than a list/tuple of fields.
|
|
689
|
+
:param context: Up to 500 characters describing what the document is or how
|
|
690
|
+
to read ambiguous fields (e.g. ``'A vendor invoice'``).
|
|
691
|
+
:type context: str
|
|
692
|
+
:rtype: dict
|
|
693
|
+
:return:
|
|
694
|
+
- data: ``{field name: value}`` map. Every requested field is present,
|
|
695
|
+
with ``None`` where the text did not state it. ``date`` values are
|
|
696
|
+
``YYYY-MM-DD`` strings and ``list`` values are lists of strings.
|
|
697
|
+
- fields: One dict per requested field, in the order requested, with
|
|
698
|
+
``name``, ``type``, ``value``, the verbatim ``evidence`` span the value
|
|
699
|
+
came from (``''`` when not found) and ``found``.
|
|
700
|
+
- missing: Names of the fields the text did not yield, in request order.
|
|
701
|
+
Finding nothing is a normal, successful response.
|
|
702
|
+
'''
|
|
703
|
+
# A bare string would silently be split into one-character field names,
|
|
704
|
+
# a dict would be reduced to its keys, and a non-iterable (None, an int)
|
|
705
|
+
# would raise an opaque "'int' object is not iterable" from list().
|
|
706
|
+
# Fail loudly with a message that names the right shape instead.
|
|
707
|
+
if (not isinstance(fields, Iterable)
|
|
708
|
+
or isinstance(fields, (str, bytes, dict))):
|
|
709
|
+
raise TypeError('fields must be a list of field names or '
|
|
710
|
+
'{name, type, description, required} dicts')
|
|
711
|
+
url = self.url_endpoint + 'extract'
|
|
712
|
+
data = {
|
|
713
|
+
'key': self.api_key,
|
|
714
|
+
'text': text,
|
|
715
|
+
'fields': list(fields),
|
|
716
|
+
}
|
|
717
|
+
if context is not None:
|
|
718
|
+
data['context'] = context
|
|
719
|
+
return self._request(url, data)
|
|
720
|
+
|
|
634
721
|
def tone(
|
|
635
722
|
self,
|
|
636
723
|
text,
|
|
@@ -682,43 +769,79 @@ class SaplingClient:
|
|
|
682
769
|
def quality(
|
|
683
770
|
self,
|
|
684
771
|
text,
|
|
772
|
+
sentence_scores=None,
|
|
773
|
+
rubric=None,
|
|
685
774
|
):
|
|
686
775
|
'''
|
|
687
776
|
Computes a quality score for the provided text based on how "surprising"
|
|
688
777
|
the text is to the model. Lower surprisal corresponds to higher scores.
|
|
778
|
+
Optionally also returns a per-sentence breakdown and an LLM-judged rubric.
|
|
689
779
|
|
|
690
|
-
:param text: Text to compute a quality score for.
|
|
780
|
+
:param text: Text to compute a quality score for. The base score is computed
|
|
781
|
+
on the leading ~4,000 characters; the rubric evaluates the whole text
|
|
782
|
+
(up to 20,000 characters).
|
|
691
783
|
:type text: str
|
|
784
|
+
:param sentence_scores: If true, also return a 1-5 score for each sentence
|
|
785
|
+
under `sentences`.
|
|
786
|
+
:type sentence_scores: bool
|
|
787
|
+
:param rubric: If true, also return an LLM-judged rubric under `rubric`
|
|
788
|
+
(billed at the Tone rate).
|
|
789
|
+
:type rubric: bool
|
|
692
790
|
:rtype: dict
|
|
693
791
|
:return:
|
|
694
792
|
- score: A score from 1 (low quality) to 5 (high quality).
|
|
793
|
+
- sentences: If sentence_scores is set, list of {start, end, text, score} in
|
|
794
|
+
document order; start/end are character offsets into the text.
|
|
795
|
+
- rubric: If rubric is set, {overall, dimensions: {clarity, coherence,
|
|
796
|
+
correctness, concision}, summary, issues: [{quote, dimension, note,
|
|
797
|
+
suggestion, start, end}]}; scores are integers 1-5.
|
|
695
798
|
'''
|
|
696
799
|
url = self.url_endpoint + 'quality'
|
|
697
800
|
data = {
|
|
698
801
|
'key': self.api_key,
|
|
699
802
|
'text': text,
|
|
700
803
|
}
|
|
804
|
+
if sentence_scores is not None:
|
|
805
|
+
data['sentence_scores'] = sentence_scores
|
|
806
|
+
if rubric is not None:
|
|
807
|
+
data['rubric'] = rubric
|
|
701
808
|
return self._request(url, data)
|
|
702
809
|
|
|
703
810
|
def langdetect(
|
|
704
811
|
self,
|
|
705
812
|
text,
|
|
813
|
+
top_k=None,
|
|
814
|
+
segments=None,
|
|
706
815
|
):
|
|
707
816
|
'''
|
|
708
|
-
Identifies the language of the provided text
|
|
817
|
+
Identifies the language of the provided text, with optional runner-up
|
|
818
|
+
candidates and a per-segment breakdown for mixed-language text.
|
|
709
819
|
|
|
710
|
-
:param text: Text to identify the language for.
|
|
820
|
+
:param text: Text to identify the language for (up to 20,000 characters).
|
|
711
821
|
:type text: str
|
|
822
|
+
:param top_k: Number of language candidates to return, 1-10. Defaults to 3 server-side.
|
|
823
|
+
:type top_k: int
|
|
824
|
+
:param segments: If true, also detect the language of each line/sentence
|
|
825
|
+
of the text and return them under `segments`.
|
|
826
|
+
:type segments: bool
|
|
712
827
|
:rtype: dict
|
|
713
828
|
:return:
|
|
714
|
-
- lang: The detected language code.
|
|
829
|
+
- lang: The detected language code (mostly ISO 639-1).
|
|
830
|
+
- name: English name of the detected language.
|
|
715
831
|
- conf: Confidence score for the detection result.
|
|
832
|
+
- candidates: List of {lang, name, conf} for the top languages, most likely first.
|
|
833
|
+
- segments: If segments is set, list of {start, end, text, lang, name, conf, candidates}
|
|
834
|
+
in document order; start/end are character offsets into the text.
|
|
716
835
|
'''
|
|
717
836
|
url = self.url_endpoint + 'langdetect'
|
|
718
837
|
data = {
|
|
719
838
|
'key': self.api_key,
|
|
720
839
|
'text': text,
|
|
721
840
|
}
|
|
841
|
+
if top_k is not None:
|
|
842
|
+
data['top_k'] = top_k
|
|
843
|
+
if segments is not None:
|
|
844
|
+
data['segments'] = segments
|
|
722
845
|
return self._request(url, data)
|
|
723
846
|
|
|
724
847
|
def profanity(
|
|
@@ -741,3 +864,239 @@ class SaplingClient:
|
|
|
741
864
|
'text': text,
|
|
742
865
|
}
|
|
743
866
|
return self._request(url, data)
|
|
867
|
+
|
|
868
|
+
def safety(
|
|
869
|
+
self,
|
|
870
|
+
text,
|
|
871
|
+
threshold=None,
|
|
872
|
+
spans=None,
|
|
873
|
+
):
|
|
874
|
+
'''
|
|
875
|
+
Scores the provided text on seven content-safety categories: toxicity,
|
|
876
|
+
profanity, harassment, hate_speech, self_harm, sexual, and violence.
|
|
877
|
+
|
|
878
|
+
:param text: Text to score, up to 20,000 characters.
|
|
879
|
+
:type text: str
|
|
880
|
+
:param threshold: Score at or above which a category is flagged.
|
|
881
|
+
Between 0 and 1 inclusive; the API defaults to 0.5.
|
|
882
|
+
:type threshold: float
|
|
883
|
+
:param spans: When True, the response also includes the offending
|
|
884
|
+
passages as spans, each with its text, start/end character offsets
|
|
885
|
+
into the submitted text, per-passage category scores, and
|
|
886
|
+
flagged_categories. The API defaults to False.
|
|
887
|
+
:type spans: bool
|
|
888
|
+
:rtype: dict
|
|
889
|
+
:return:
|
|
890
|
+
- scores: A probability from 0 to 1 for each category.
|
|
891
|
+
- flagged: True if any category scored at or above the threshold.
|
|
892
|
+
- flagged_categories: Categories that scored at or above the threshold.
|
|
893
|
+
- threshold: The threshold that was applied.
|
|
894
|
+
- spans: Only with spans=True — the offending passages, most
|
|
895
|
+
severe first, as {text, start, end, scores, flagged_categories}.
|
|
896
|
+
'''
|
|
897
|
+
url = self.url_endpoint + 'safety'
|
|
898
|
+
data = {
|
|
899
|
+
'key': self.api_key,
|
|
900
|
+
'text': text,
|
|
901
|
+
}
|
|
902
|
+
if threshold is not None:
|
|
903
|
+
data['threshold'] = threshold
|
|
904
|
+
if spans is not None:
|
|
905
|
+
data['spans'] = spans
|
|
906
|
+
return self._request(url, data)
|
|
907
|
+
|
|
908
|
+
def pii(
|
|
909
|
+
self,
|
|
910
|
+
text,
|
|
911
|
+
types=None,
|
|
912
|
+
redact=None,
|
|
913
|
+
):
|
|
914
|
+
'''
|
|
915
|
+
Detects personally identifiable information (PII) in the provided text and
|
|
916
|
+
optionally returns a redacted copy.
|
|
917
|
+
|
|
918
|
+
Detection is deterministic (pattern matching plus checksum validation, no ML)
|
|
919
|
+
and every entity comes with exact character offsets into ``text``, so pass
|
|
920
|
+
raw text (not HTML) and apply the spans to your own copy of the document.
|
|
921
|
+
Person names and street addresses are not detected.
|
|
922
|
+
|
|
923
|
+
:param text: Text to scan for PII.
|
|
924
|
+
:type text: str
|
|
925
|
+
:param types: Optional list of PII types to detect. Defaults to all types:
|
|
926
|
+
``email``, ``phone``, ``ssn``, ``credit_card``, ``ip_address``, ``iban``,
|
|
927
|
+
``us_bank_routing``.
|
|
928
|
+
:type types: list[str]
|
|
929
|
+
:param redact: If True, the response also includes ``redacted_text`` with every
|
|
930
|
+
detected entity replaced by a placeholder such as ``[EMAIL]``.
|
|
931
|
+
:type redact: bool
|
|
932
|
+
:rtype: dict
|
|
933
|
+
:return:
|
|
934
|
+
- entities: List of ``{type, text, start, end, replacement}`` dicts, sorted
|
|
935
|
+
by ``start``; spans never overlap.
|
|
936
|
+
- flagged: True if any PII was detected.
|
|
937
|
+
- types: Sorted list of the PII types found.
|
|
938
|
+
- redacted_text: Only present when ``redact`` is True.
|
|
939
|
+
'''
|
|
940
|
+
url = self.url_endpoint + 'pii'
|
|
941
|
+
data = {
|
|
942
|
+
'key': self.api_key,
|
|
943
|
+
'text': text,
|
|
944
|
+
}
|
|
945
|
+
if types is not None:
|
|
946
|
+
if (not isinstance(types, Iterable)
|
|
947
|
+
or isinstance(types, (str, bytes, dict))):
|
|
948
|
+
raise TypeError('types must be a list of entity type names')
|
|
949
|
+
data['types'] = list(types)
|
|
950
|
+
if redact is not None:
|
|
951
|
+
data['redact'] = redact
|
|
952
|
+
return self._request(url, data)
|
|
953
|
+
|
|
954
|
+
def seo(
|
|
955
|
+
self,
|
|
956
|
+
text,
|
|
957
|
+
keywords=None,
|
|
958
|
+
suggestions=None,
|
|
959
|
+
lang=None,
|
|
960
|
+
):
|
|
961
|
+
'''
|
|
962
|
+
Analyzes the provided page or article text for SEO: deterministic content
|
|
963
|
+
statistics and readability, target-keyword usage, the most frequent terms, and
|
|
964
|
+
(optionally) LLM-generated titles, meta descriptions, a URL slug, and focus
|
|
965
|
+
keywords.
|
|
966
|
+
|
|
967
|
+
:param text: Page or article body, plain text or HTML (block tags become line
|
|
968
|
+
breaks, inline tags are removed), up to 20,000 characters.
|
|
969
|
+
:type text: str
|
|
970
|
+
:param keywords: Optional list of up to 10 target keyword phrases, most
|
|
971
|
+
important first (the first is the primary keyword). Each 1-100 characters.
|
|
972
|
+
Measured in the text and steered into the suggestions.
|
|
973
|
+
:type keywords: list[str]
|
|
974
|
+
:param suggestions: If False, only the deterministic ``stats``, ``keywords`` and
|
|
975
|
+
``top_terms`` are returned (free); no ``suggestions`` are generated. The API
|
|
976
|
+
defaults to True (billed at the Tone rate).
|
|
977
|
+
:type suggestions: bool
|
|
978
|
+
:param lang: ISO 639-1 language code of the text (a region subtag such as
|
|
979
|
+
``pt-BR`` is allowed, or ``auto``). The API defaults to ``en``. Only selects
|
|
980
|
+
the readability formulas: ``flesch_reading_ease`` and
|
|
981
|
+
``flesch_kincaid_grade`` are computed for ``en``, ``de``, ``es``, ``fr``,
|
|
982
|
+
``it``, ``nl``, ``pl`` and ``ru`` and are None for any other language.
|
|
983
|
+
:type lang: str
|
|
984
|
+
:rtype: dict
|
|
985
|
+
:return:
|
|
986
|
+
- stats: ``{chars, words, sentences, paragraphs, reading_time_min,
|
|
987
|
+
flesch_reading_ease, flesch_kincaid_grade}``; the two readability fields
|
|
988
|
+
are None for languages without readability support.
|
|
989
|
+
- keywords: One ``{keyword, count, density, in_first_100_words}`` dict per
|
|
990
|
+
submitted target keyword, in the same order; empty when none were sent.
|
|
991
|
+
- top_terms: Up to 10 most frequent content unigrams/bigrams as
|
|
992
|
+
``{term, count}`` dicts (lower-cased, English stopwords removed).
|
|
993
|
+
- suggestions: Only present when ``suggestions`` is not False:
|
|
994
|
+
``{titles, meta_descriptions, slug, keywords}`` where ``titles`` and
|
|
995
|
+
``meta_descriptions`` are lists of strings, ``slug`` is a kebab-case
|
|
996
|
+
string (or None when no ASCII slug could be derived, e.g. purely CJK
|
|
997
|
+
content) and ``keywords`` is a list of focus-keyword phrases.
|
|
998
|
+
'''
|
|
999
|
+
url = self.url_endpoint + 'seo'
|
|
1000
|
+
data = {
|
|
1001
|
+
'key': self.api_key,
|
|
1002
|
+
'text': text,
|
|
1003
|
+
}
|
|
1004
|
+
if keywords is not None:
|
|
1005
|
+
if (not isinstance(keywords, Iterable)
|
|
1006
|
+
or isinstance(keywords, (str, bytes, dict))):
|
|
1007
|
+
raise TypeError('keywords must be a list of keyword strings')
|
|
1008
|
+
data['keywords'] = list(keywords)
|
|
1009
|
+
if suggestions is not None:
|
|
1010
|
+
data['suggestions'] = suggestions
|
|
1011
|
+
if lang is not None:
|
|
1012
|
+
data['lang'] = lang
|
|
1013
|
+
return self._request(url, data)
|
|
1014
|
+
|
|
1015
|
+
def classify(
|
|
1016
|
+
self,
|
|
1017
|
+
text,
|
|
1018
|
+
labels,
|
|
1019
|
+
multi_label=None,
|
|
1020
|
+
threshold=None,
|
|
1021
|
+
context=None,
|
|
1022
|
+
):
|
|
1023
|
+
'''
|
|
1024
|
+
Classifies the provided text into one (or several) of the labels you supply.
|
|
1025
|
+
Zero-shot: no training data or setup is needed, just the label names and,
|
|
1026
|
+
optionally, a short description per label.
|
|
1027
|
+
|
|
1028
|
+
Example::
|
|
1029
|
+
|
|
1030
|
+
client.classify(
|
|
1031
|
+
'I was charged twice for my last invoice.',
|
|
1032
|
+
labels=[
|
|
1033
|
+
'billing',
|
|
1034
|
+
{'name': 'technical issue', 'description': 'Bugs, crashes, errors'},
|
|
1035
|
+
'shipping',
|
|
1036
|
+
'other',
|
|
1037
|
+
],
|
|
1038
|
+
context='Support tickets for a SaaS billing product',
|
|
1039
|
+
)
|
|
1040
|
+
# {'label': 'billing',
|
|
1041
|
+
# 'labels': ['billing'],
|
|
1042
|
+
# 'scores': [{'label': 'billing', 'score': 0.86},
|
|
1043
|
+
# {'label': 'technical issue', 'score': 0.07},
|
|
1044
|
+
# {'label': 'shipping', 'score': 0.04},
|
|
1045
|
+
# {'label': 'other', 'score': 0.03}],
|
|
1046
|
+
# 'rationale': 'The customer reports being charged twice on one invoice.',
|
|
1047
|
+
# 'multi_label': False}
|
|
1048
|
+
|
|
1049
|
+
:param text: Text to classify, plain text or HTML (tags are stripped), up to
|
|
1050
|
+
10,000 characters.
|
|
1051
|
+
:type text: str
|
|
1052
|
+
:param labels: 2-20 candidate labels. Each entry is either a label name (str) or
|
|
1053
|
+
a dict ``{'name': str, 'description': str}`` where ``description`` is
|
|
1054
|
+
optional. Names are up to 50 characters and must be unique
|
|
1055
|
+
(case-insensitive); descriptions are up to 200 characters and help the
|
|
1056
|
+
model with borderline decisions.
|
|
1057
|
+
:type labels: list[str | dict]
|
|
1058
|
+
:raises TypeError: If ``labels`` is None, a single string or a dict rather
|
|
1059
|
+
than a list/tuple of labels.
|
|
1060
|
+
:param multi_label: If False (the API default), exactly one label applies and
|
|
1061
|
+
the scores form a probability distribution over the labels. If True, zero,
|
|
1062
|
+
one or several labels may apply and each score is an independent 0-1
|
|
1063
|
+
probability.
|
|
1064
|
+
:type multi_label: bool
|
|
1065
|
+
:param threshold: Multi-label cut-off, 0-1: ``labels`` in the response contains
|
|
1066
|
+
every label with ``score >= threshold``. The API defaults to 0.5. Ignored in
|
|
1067
|
+
single-label mode.
|
|
1068
|
+
:type threshold: float
|
|
1069
|
+
:param context: Up to 500 characters describing what the texts are or how to
|
|
1070
|
+
decide (e.g. ``'Support tickets for a SaaS billing product'``). Guidance
|
|
1071
|
+
only; the model can still only answer with the labels you provide.
|
|
1072
|
+
:type context: str
|
|
1073
|
+
:rtype: dict
|
|
1074
|
+
:return:
|
|
1075
|
+
- label: Best-matching label name (always one of the input labels).
|
|
1076
|
+
- labels: Single-label mode: ``[label]``. Multi-label mode: every label
|
|
1077
|
+
with ``score >= threshold``, descending by score; may be empty when none
|
|
1078
|
+
of the labels apply.
|
|
1079
|
+
- scores: One ``{label, score}`` dict per input label, sorted descending
|
|
1080
|
+
by score (0-1, rounded to 4 decimals).
|
|
1081
|
+
- rationale: One sentence naming the evidence in the text (may be empty).
|
|
1082
|
+
- multi_label: The mode that was used.
|
|
1083
|
+
'''
|
|
1084
|
+
# A bare string would silently be split into one-character labels and
|
|
1085
|
+
# None would raise an opaque TypeError from list(); fail loudly instead.
|
|
1086
|
+
if (not isinstance(labels, Iterable)
|
|
1087
|
+
or isinstance(labels, (str, bytes, dict))):
|
|
1088
|
+
raise TypeError('labels must be a list of label names or '
|
|
1089
|
+
'{name, description} dicts')
|
|
1090
|
+
url = self.url_endpoint + 'classify'
|
|
1091
|
+
data = {
|
|
1092
|
+
'key': self.api_key,
|
|
1093
|
+
'text': text,
|
|
1094
|
+
'labels': list(labels),
|
|
1095
|
+
}
|
|
1096
|
+
if multi_label is not None:
|
|
1097
|
+
data['multi_label'] = multi_label
|
|
1098
|
+
if threshold is not None:
|
|
1099
|
+
data['threshold'] = threshold
|
|
1100
|
+
if context is not None:
|
|
1101
|
+
data['context'] = context
|
|
1102
|
+
return self._request(url, data)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '4.3.0'
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sapling-py
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
4
4
|
Summary: Sapling Python Client
|
|
5
|
-
|
|
6
|
-
Author: Sapling Intelligence
|
|
7
|
-
Author-email: info@sapling.ai
|
|
5
|
+
Author-email: Sapling Intelligence <info@sapling.ai>
|
|
8
6
|
License: Apache License 2.0
|
|
7
|
+
Project-URL: Homepage, https://sapling.ai
|
|
8
|
+
Project-URL: Documentation, https://sapling.readthedocs.io/
|
|
9
|
+
Project-URL: Repository, https://github.com/saplingai/sapling-py
|
|
9
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
12
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -17,18 +18,7 @@ Requires-Dist: requests
|
|
|
17
18
|
Provides-Extra: test
|
|
18
19
|
Requires-Dist: pytest; extra == "test"
|
|
19
20
|
Requires-Dist: responses; extra == "test"
|
|
20
|
-
Dynamic: author
|
|
21
|
-
Dynamic: author-email
|
|
22
|
-
Dynamic: classifier
|
|
23
|
-
Dynamic: description
|
|
24
|
-
Dynamic: description-content-type
|
|
25
|
-
Dynamic: home-page
|
|
26
|
-
Dynamic: license
|
|
27
21
|
Dynamic: license-file
|
|
28
|
-
Dynamic: provides-extra
|
|
29
|
-
Dynamic: requires-dist
|
|
30
|
-
Dynamic: requires-python
|
|
31
|
-
Dynamic: summary
|
|
32
22
|
|
|
33
23
|
# Sapling Python Client
|
|
34
24
|
|
|
@@ -40,7 +30,9 @@ Compare against leading grammar checking tools and APIs like Grammarly (Grammerl
|
|
|
40
30
|
## Overview
|
|
41
31
|
|
|
42
32
|
Sapling is a toolkit for helping developers build language model-powered applications.
|
|
43
|
-
The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
|
|
33
|
+
The API provides spelling and grammar checking, autocomplete, tone detection, rephrasing, AI detection,
|
|
34
|
+
zero-shot text classification, structured data extraction, PII detection and redaction, content safety
|
|
35
|
+
scoring, SEO analysis, and more.
|
|
44
36
|
|
|
45
37
|
Key Features:
|
|
46
38
|
- **60% More Corrections**: Outperforms [competing solutions](https://sapling.ai/comparison/api#sapling) at similar accuracy levels using state-of-the-art generative AI models.
|
|
@@ -52,12 +44,19 @@ Key Features:
|
|
|
52
44
|
|
|
53
45
|
## Installation
|
|
54
46
|
|
|
55
|
-
Install the `sapling-py` package with
|
|
47
|
+
Install the `sapling-py` package from PyPI with
|
|
48
|
+
[pip](https://pip.pypa.io/en/stable/installation/):
|
|
56
49
|
|
|
57
|
-
```
|
|
50
|
+
```sh
|
|
58
51
|
python -m pip install sapling-py
|
|
59
52
|
```
|
|
60
53
|
|
|
54
|
+
Or add it to a [uv](https://docs.astral.sh/uv/) project:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
uv add sapling-py
|
|
58
|
+
```
|
|
59
|
+
|
|
61
60
|
## Documentation
|
|
62
61
|
Documentation for the client is available at [Read the Docs](https://sapling.readthedocs.io/) and
|
|
63
62
|
documentation for the HTTP API is available at [Sapling.ai](https://sapling.ai/docs).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
README.md
|
|
3
|
+
pyproject.toml
|
|
3
4
|
setup.py
|
|
4
5
|
sapling/__init__.py
|
|
5
6
|
sapling/client.py
|
|
@@ -9,5 +10,9 @@ sapling_py.egg-info/SOURCES.txt
|
|
|
9
10
|
sapling_py.egg-info/dependency_links.txt
|
|
10
11
|
sapling_py.egg-info/requires.txt
|
|
11
12
|
sapling_py.egg-info/top_level.txt
|
|
12
|
-
test/
|
|
13
|
-
test/test_client.py
|
|
13
|
+
test/test_classify.py
|
|
14
|
+
test/test_client.py
|
|
15
|
+
test/test_extract.py
|
|
16
|
+
test/test_pii.py
|
|
17
|
+
test/test_safety.py
|
|
18
|
+
test/test_seo.py
|