knowledge2 0.4.0__tar.gz → 0.5.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.
- {knowledge2-0.4.0 → knowledge2-0.5.0}/CHANGELOG.md +6 -0
- knowledge2-0.5.0/MANIFEST.in +5 -0
- knowledge2-0.5.0/PKG-INFO +217 -0
- knowledge2-0.5.0/README.md +189 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/__init__.py +4 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_async_base.py +6 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_async_paging.py +32 -4
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_base.py +6 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_paging.py +34 -5
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_preview.py +6 -2
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_transport.py +31 -2
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_validation.py +22 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_version.py +1 -1
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_client.py +2 -6
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/__init__.py +2 -6
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/agents.py +5 -3
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/console.py +0 -4
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/corpora.py +2 -63
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/documents.py +26 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/feeds.py +12 -27
- knowledge2-0.5.0/async_resources/generation_models.py +29 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/indexes.py +46 -2
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/onboarding.py +16 -11
- knowledge2-0.5.0/async_resources/projects.py +305 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/search.py +2 -1
- {knowledge2-0.4.0 → knowledge2-0.5.0}/client.py +2 -34
- {knowledge2-0.4.0 → knowledge2-0.5.0}/errors.py +64 -0
- knowledge2-0.5.0/examples/e2e_lifecycle.py +178 -0
- knowledge2-0.5.0/examples/retrieval_quickstart.py +58 -0
- knowledge2-0.5.0/knowledge2.egg-info/PKG-INFO +217 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/knowledge2.egg-info/SOURCES.txt +11 -30
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/_registry.py +35 -48
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/agents.py +34 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/console.py +0 -3
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/corpora.py +1 -6
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/documents.py +20 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/feeds.py +2 -4
- knowledge2-0.5.0/models/generation_models.py +16 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/indexes.py +6 -2
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/onboarding.py +13 -5
- knowledge2-0.5.0/models/projects.py +46 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/search.py +2 -34
- {knowledge2-0.4.0 → knowledge2-0.5.0}/namespaces.py +4 -109
- {knowledge2-0.4.0 → knowledge2-0.5.0}/pyproject.toml +1 -1
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/__init__.py +2 -6
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/agents.py +5 -3
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/corpora.py +2 -58
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/documents.py +26 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/feeds.py +12 -27
- knowledge2-0.5.0/resources/generation_models.py +29 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/indexes.py +51 -2
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/onboarding.py +16 -12
- knowledge2-0.5.0/resources/projects.py +300 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/search.py +2 -1
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/__init__.py +32 -33
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/agents.py +48 -1
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/corpora.py +0 -1
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/documents.py +22 -1
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/feeds.py +2 -4
- knowledge2-0.5.0/types/generation_models.py +14 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/indexes.py +7 -1
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/onboarding.py +13 -5
- knowledge2-0.5.0/types/projects.py +52 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/search.py +1 -0
- knowledge2-0.4.0/AGENTS.md +0 -31
- knowledge2-0.4.0/PKG-INFO +0 -556
- knowledge2-0.4.0/README.md +0 -528
- knowledge2-0.4.0/async_resources/deployments.py +0 -106
- knowledge2-0.4.0/async_resources/models.py +0 -102
- knowledge2-0.4.0/async_resources/projects.py +0 -90
- knowledge2-0.4.0/async_resources/training.py +0 -357
- knowledge2-0.4.0/examples/e2e_lifecycle.py +0 -213
- knowledge2-0.4.0/knowledge2.egg-info/PKG-INFO +0 -556
- knowledge2-0.4.0/models/deployments.py +0 -13
- knowledge2-0.4.0/models/evaluation.py +0 -17
- knowledge2-0.4.0/models/models.py +0 -26
- knowledge2-0.4.0/models/projects.py +0 -19
- knowledge2-0.4.0/models/training.py +0 -57
- knowledge2-0.4.0/resources/deployments.py +0 -105
- knowledge2-0.4.0/resources/models.py +0 -99
- knowledge2-0.4.0/resources/projects.py +0 -87
- knowledge2-0.4.0/resources/training.py +0 -358
- knowledge2-0.4.0/specification.md +0 -129
- knowledge2-0.4.0/types/deployments.py +0 -11
- knowledge2-0.4.0/types/evaluation.py +0 -15
- knowledge2-0.4.0/types/models.py +0 -22
- knowledge2-0.4.0/types/projects.py +0 -14
- knowledge2-0.4.0/types/training.py +0 -55
- {knowledge2-0.4.0 → knowledge2-0.5.0}/.github/workflows/pypi-release.yml +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_logging.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_raw_response.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_request_options.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/_validation_response.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/_mixin_base.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/a2a.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/audit.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/auth.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/jobs.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/metadata.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/orgs.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/pipelines.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/usage.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/config.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/auth_factory.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/batch_operations.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/document_upload.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/error_handling.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/pagination.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/quickstart.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/request_options.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/search.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/__init__.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/_client.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/langchain/__init__.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/langchain/retriever.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/langchain/tools.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/__init__.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/filters.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/retriever.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/tools.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/vector_store.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/knowledge2.egg-info/dependency_links.txt +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/knowledge2.egg-info/requires.txt +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/knowledge2.egg-info/top_level.txt +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/__init__.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/_base.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/a2a.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/audit.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/auth.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/chunks.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/common.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/embeddings.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/feedback.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/jobs.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/orgs.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/pipelines.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/models/usage.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/py.typed +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/_mixin_base.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/a2a.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/audit.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/auth.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/console.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/jobs.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/metadata.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/orgs.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/pipeline_builder.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/pipelines.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/usage.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/setup.cfg +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/a2a.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/audit.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/auth.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/chunks.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/common.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/console.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/embeddings.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/feedback.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/jobs.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/orgs.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/pipelines.py +0 -0
- {knowledge2-0.4.0 → knowledge2-0.5.0}/types/usage.py +0 -0
|
@@ -5,6 +5,12 @@ All notable changes to the Knowledge2 Python SDK will be documented in this file
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.1] - 2026-04-03
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- Release automation hardening for the public SDK sync/tag/PyPI path. No SDK API surface changes.
|
|
13
|
+
|
|
8
14
|
## [0.3.0] - 2026-03-30
|
|
9
15
|
|
|
10
16
|
### Added
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: knowledge2
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Python SDK for the Knowledge2 retrieval platform
|
|
5
|
+
Author-email: Knowledge2 <contact@knowledge2.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://knowledge2.ai
|
|
8
|
+
Project-URL: Documentation, https://knowledge2.ai/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/knowledge2-ai/knowledge2-python-sdk
|
|
10
|
+
Project-URL: Changelog, https://github.com/knowledge2-ai/knowledge2-python-sdk/blob/main/CHANGELOG.md
|
|
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.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Provides-Extra: config
|
|
23
|
+
Requires-Dist: pydantic-settings>=2.0; extra == "config"
|
|
24
|
+
Provides-Extra: pydantic
|
|
25
|
+
Requires-Dist: pydantic>=2.0; extra == "pydantic"
|
|
26
|
+
Provides-Extra: yaml
|
|
27
|
+
Requires-Dist: pyyaml>=6.0; extra == "yaml"
|
|
28
|
+
|
|
29
|
+
# Knowledge2 Python SDK
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/knowledge2/)
|
|
32
|
+
[](https://www.python.org/downloads/)
|
|
33
|
+
[](https://opensource.org/licenses/MIT)
|
|
34
|
+
|
|
35
|
+
Official Python client for the Knowledge2 retrieval platform. The supported customer journey is:
|
|
36
|
+
|
|
37
|
+
`create corpus -> ingest documents -> build indexes -> search -> optimize retrieval`
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
From PyPI:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install knowledge2
|
|
45
|
+
pip install "knowledge2[config]"
|
|
46
|
+
pip install "knowledge2[pydantic]"
|
|
47
|
+
pip install "knowledge2[yaml]"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
From source:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install -e .
|
|
54
|
+
pip install -e ".[config]"
|
|
55
|
+
pip install -e ".[pydantic]"
|
|
56
|
+
pip install -e ".[yaml]"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Surface Categories
|
|
60
|
+
|
|
61
|
+
| Category | Surface |
|
|
62
|
+
|---|---|
|
|
63
|
+
| Core retrieval workflow | orgs, auth, projects, corpora, documents, indexes, search, jobs, metadata, onboarding, audit, usage, console, generation models |
|
|
64
|
+
| Enterprise capabilities | agents, feeds, pipelines, A2A |
|
|
65
|
+
|
|
66
|
+
The main docs and examples below focus on the core retrieval workflow.
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from sdk import Knowledge2
|
|
72
|
+
|
|
73
|
+
client = Knowledge2(api_key="k2_...")
|
|
74
|
+
|
|
75
|
+
project = client.create_project("My Project")
|
|
76
|
+
corpus = client.create_corpus(project["id"], "My Corpus")
|
|
77
|
+
|
|
78
|
+
client.upload_documents_batch(
|
|
79
|
+
corpus["id"],
|
|
80
|
+
[
|
|
81
|
+
{
|
|
82
|
+
"source_uri": "doc://overview",
|
|
83
|
+
"raw_text": "Knowledge2 builds dense and sparse indexes for hybrid retrieval.",
|
|
84
|
+
"metadata": {"topic": "overview"},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"source_uri": "doc://search",
|
|
88
|
+
"raw_text": "Hybrid retrieval combines semantic similarity with exact keyword matching.",
|
|
89
|
+
"metadata": {"topic": "search"},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
wait=True,
|
|
93
|
+
auto_index=False,
|
|
94
|
+
)
|
|
95
|
+
client.sync_indexes(corpus["id"], wait=True)
|
|
96
|
+
|
|
97
|
+
results = client.search(
|
|
98
|
+
corpus["id"],
|
|
99
|
+
"what is hybrid retrieval",
|
|
100
|
+
top_k=3,
|
|
101
|
+
return_config={"include_text": True, "include_scores": True},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
for hit in results["results"]:
|
|
105
|
+
print(hit["score"], hit.get("text", "")[:80])
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Improve Retrieval Quality
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
profile = client.get_query_profile(corpus["id"])
|
|
112
|
+
print(profile["example_queries"])
|
|
113
|
+
|
|
114
|
+
job = client.optimize_indexes(
|
|
115
|
+
corpus["id"],
|
|
116
|
+
example_queries=[
|
|
117
|
+
"how does hybrid retrieval work",
|
|
118
|
+
"what is bm25 tuning",
|
|
119
|
+
"how does rrf combine dense and sparse search",
|
|
120
|
+
],
|
|
121
|
+
query_count=25,
|
|
122
|
+
top_k=10,
|
|
123
|
+
metric="ndcg",
|
|
124
|
+
wait=False,
|
|
125
|
+
)
|
|
126
|
+
print(job["job_id"], job["job_type"])
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Examples
|
|
130
|
+
|
|
131
|
+
- `sdk/examples/retrieval_quickstart.py`: minimal happy path from empty corpus to working hybrid search
|
|
132
|
+
- `sdk/examples/e2e_lifecycle.py`: full retrieval-quality workflow with query profile inspection and `indexes:optimize`
|
|
133
|
+
|
|
134
|
+
Run either example with:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
export K2_BASE_URL=https://api.knowledge2.ai
|
|
138
|
+
export K2_API_KEY=<api-key>
|
|
139
|
+
python sdk/examples/retrieval_quickstart.py
|
|
140
|
+
python sdk/examples/e2e_lifecycle.py
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Authentication
|
|
144
|
+
|
|
145
|
+
| Method | Header | Typical use |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| API key | `X-API-Key` | primary programmatic access |
|
|
148
|
+
| Bearer token | `Authorization: Bearer <token>` | console / Auth0 session |
|
|
149
|
+
| Admin token | `X-Admin-Token` | bootstrap and admin operations |
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
client = Knowledge2(api_key="k2_...")
|
|
153
|
+
client = Knowledge2.from_env()
|
|
154
|
+
client = Knowledge2(bearer_token="...")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
Important constructor knobs:
|
|
160
|
+
|
|
161
|
+
- `api_host`: defaults to `https://api.knowledge2.ai`
|
|
162
|
+
- `api_key`: API key for programmatic access
|
|
163
|
+
- `org_id`: auto-detected from `GET /v1/auth/whoami` when omitted
|
|
164
|
+
- `timeout`: float or `ClientTimeouts`
|
|
165
|
+
- `limits`: connection-pool settings via `ClientLimits`
|
|
166
|
+
- `max_retries`: transient retry budget
|
|
167
|
+
- `validate_responses`: enable Pydantic response validation
|
|
168
|
+
- `http_client`: bring your own `httpx.Client`
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from sdk import ClientTimeouts, Knowledge2
|
|
172
|
+
|
|
173
|
+
client = Knowledge2(
|
|
174
|
+
api_key="k2_...",
|
|
175
|
+
timeout=ClientTimeouts(connect=5, read=120, write=30, pool=10),
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Namespaces
|
|
180
|
+
|
|
181
|
+
The flat client API is canonical. Namespace helpers group the same methods without changing behavior:
|
|
182
|
+
|
|
183
|
+
- `client.documents.*`
|
|
184
|
+
- `client.corpora.*`
|
|
185
|
+
- `client.search_ns.*`
|
|
186
|
+
- `client.jobs.*`
|
|
187
|
+
- `client.auth.*`
|
|
188
|
+
|
|
189
|
+
## Framework Integrations
|
|
190
|
+
|
|
191
|
+
The SDK ships LangChain and LlamaIndex integration modules in-package. Install the framework dependency separately, then import the adapter:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from sdk.integrations.langchain import K2LangChainRetriever
|
|
195
|
+
from sdk.integrations.llamaindex import K2LlamaIndexRetriever
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Enterprise Capabilities
|
|
199
|
+
|
|
200
|
+
Agents, feeds, pipelines, and A2A are available for enterprise deployments. Keep the primary examples focused on the core retrieval flow.
|
|
201
|
+
|
|
202
|
+
## Error Handling
|
|
203
|
+
|
|
204
|
+
All SDK exceptions inherit from `Knowledge2Error`.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from sdk.errors import Knowledge2Error, NotFoundError, RateLimitError
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
client.get_corpus("missing")
|
|
211
|
+
except NotFoundError:
|
|
212
|
+
...
|
|
213
|
+
except RateLimitError as exc:
|
|
214
|
+
print(exc.retry_after)
|
|
215
|
+
except Knowledge2Error as exc:
|
|
216
|
+
print(exc)
|
|
217
|
+
```
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Knowledge2 Python SDK
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/knowledge2/)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
Official Python client for the Knowledge2 retrieval platform. The supported customer journey is:
|
|
8
|
+
|
|
9
|
+
`create corpus -> ingest documents -> build indexes -> search -> optimize retrieval`
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
From PyPI:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install knowledge2
|
|
17
|
+
pip install "knowledge2[config]"
|
|
18
|
+
pip install "knowledge2[pydantic]"
|
|
19
|
+
pip install "knowledge2[yaml]"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
From source:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install -e .
|
|
26
|
+
pip install -e ".[config]"
|
|
27
|
+
pip install -e ".[pydantic]"
|
|
28
|
+
pip install -e ".[yaml]"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Surface Categories
|
|
32
|
+
|
|
33
|
+
| Category | Surface |
|
|
34
|
+
|---|---|
|
|
35
|
+
| Core retrieval workflow | orgs, auth, projects, corpora, documents, indexes, search, jobs, metadata, onboarding, audit, usage, console, generation models |
|
|
36
|
+
| Enterprise capabilities | agents, feeds, pipelines, A2A |
|
|
37
|
+
|
|
38
|
+
The main docs and examples below focus on the core retrieval workflow.
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from sdk import Knowledge2
|
|
44
|
+
|
|
45
|
+
client = Knowledge2(api_key="k2_...")
|
|
46
|
+
|
|
47
|
+
project = client.create_project("My Project")
|
|
48
|
+
corpus = client.create_corpus(project["id"], "My Corpus")
|
|
49
|
+
|
|
50
|
+
client.upload_documents_batch(
|
|
51
|
+
corpus["id"],
|
|
52
|
+
[
|
|
53
|
+
{
|
|
54
|
+
"source_uri": "doc://overview",
|
|
55
|
+
"raw_text": "Knowledge2 builds dense and sparse indexes for hybrid retrieval.",
|
|
56
|
+
"metadata": {"topic": "overview"},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"source_uri": "doc://search",
|
|
60
|
+
"raw_text": "Hybrid retrieval combines semantic similarity with exact keyword matching.",
|
|
61
|
+
"metadata": {"topic": "search"},
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
wait=True,
|
|
65
|
+
auto_index=False,
|
|
66
|
+
)
|
|
67
|
+
client.sync_indexes(corpus["id"], wait=True)
|
|
68
|
+
|
|
69
|
+
results = client.search(
|
|
70
|
+
corpus["id"],
|
|
71
|
+
"what is hybrid retrieval",
|
|
72
|
+
top_k=3,
|
|
73
|
+
return_config={"include_text": True, "include_scores": True},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
for hit in results["results"]:
|
|
77
|
+
print(hit["score"], hit.get("text", "")[:80])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Improve Retrieval Quality
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
profile = client.get_query_profile(corpus["id"])
|
|
84
|
+
print(profile["example_queries"])
|
|
85
|
+
|
|
86
|
+
job = client.optimize_indexes(
|
|
87
|
+
corpus["id"],
|
|
88
|
+
example_queries=[
|
|
89
|
+
"how does hybrid retrieval work",
|
|
90
|
+
"what is bm25 tuning",
|
|
91
|
+
"how does rrf combine dense and sparse search",
|
|
92
|
+
],
|
|
93
|
+
query_count=25,
|
|
94
|
+
top_k=10,
|
|
95
|
+
metric="ndcg",
|
|
96
|
+
wait=False,
|
|
97
|
+
)
|
|
98
|
+
print(job["job_id"], job["job_type"])
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Examples
|
|
102
|
+
|
|
103
|
+
- `sdk/examples/retrieval_quickstart.py`: minimal happy path from empty corpus to working hybrid search
|
|
104
|
+
- `sdk/examples/e2e_lifecycle.py`: full retrieval-quality workflow with query profile inspection and `indexes:optimize`
|
|
105
|
+
|
|
106
|
+
Run either example with:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
export K2_BASE_URL=https://api.knowledge2.ai
|
|
110
|
+
export K2_API_KEY=<api-key>
|
|
111
|
+
python sdk/examples/retrieval_quickstart.py
|
|
112
|
+
python sdk/examples/e2e_lifecycle.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Authentication
|
|
116
|
+
|
|
117
|
+
| Method | Header | Typical use |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| API key | `X-API-Key` | primary programmatic access |
|
|
120
|
+
| Bearer token | `Authorization: Bearer <token>` | console / Auth0 session |
|
|
121
|
+
| Admin token | `X-Admin-Token` | bootstrap and admin operations |
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
client = Knowledge2(api_key="k2_...")
|
|
125
|
+
client = Knowledge2.from_env()
|
|
126
|
+
client = Knowledge2(bearer_token="...")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Configuration
|
|
130
|
+
|
|
131
|
+
Important constructor knobs:
|
|
132
|
+
|
|
133
|
+
- `api_host`: defaults to `https://api.knowledge2.ai`
|
|
134
|
+
- `api_key`: API key for programmatic access
|
|
135
|
+
- `org_id`: auto-detected from `GET /v1/auth/whoami` when omitted
|
|
136
|
+
- `timeout`: float or `ClientTimeouts`
|
|
137
|
+
- `limits`: connection-pool settings via `ClientLimits`
|
|
138
|
+
- `max_retries`: transient retry budget
|
|
139
|
+
- `validate_responses`: enable Pydantic response validation
|
|
140
|
+
- `http_client`: bring your own `httpx.Client`
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from sdk import ClientTimeouts, Knowledge2
|
|
144
|
+
|
|
145
|
+
client = Knowledge2(
|
|
146
|
+
api_key="k2_...",
|
|
147
|
+
timeout=ClientTimeouts(connect=5, read=120, write=30, pool=10),
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Namespaces
|
|
152
|
+
|
|
153
|
+
The flat client API is canonical. Namespace helpers group the same methods without changing behavior:
|
|
154
|
+
|
|
155
|
+
- `client.documents.*`
|
|
156
|
+
- `client.corpora.*`
|
|
157
|
+
- `client.search_ns.*`
|
|
158
|
+
- `client.jobs.*`
|
|
159
|
+
- `client.auth.*`
|
|
160
|
+
|
|
161
|
+
## Framework Integrations
|
|
162
|
+
|
|
163
|
+
The SDK ships LangChain and LlamaIndex integration modules in-package. Install the framework dependency separately, then import the adapter:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from sdk.integrations.langchain import K2LangChainRetriever
|
|
167
|
+
from sdk.integrations.llamaindex import K2LlamaIndexRetriever
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Enterprise Capabilities
|
|
171
|
+
|
|
172
|
+
Agents, feeds, pipelines, and A2A are available for enterprise deployments. Keep the primary examples focused on the core retrieval flow.
|
|
173
|
+
|
|
174
|
+
## Error Handling
|
|
175
|
+
|
|
176
|
+
All SDK exceptions inherit from `Knowledge2Error`.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from sdk.errors import Knowledge2Error, NotFoundError, RateLimitError
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
client.get_corpus("missing")
|
|
183
|
+
except NotFoundError:
|
|
184
|
+
...
|
|
185
|
+
except RateLimitError as exc:
|
|
186
|
+
print(exc.retry_after)
|
|
187
|
+
except Knowledge2Error as exc:
|
|
188
|
+
print(exc)
|
|
189
|
+
```
|
|
@@ -18,9 +18,11 @@ from .errors import (
|
|
|
18
18
|
BadRequestError,
|
|
19
19
|
ConfirmationRequiredError,
|
|
20
20
|
ConflictError,
|
|
21
|
+
FeatureNotEnabledError,
|
|
21
22
|
Knowledge2Error,
|
|
22
23
|
NotFoundError,
|
|
23
24
|
PermissionDeniedError,
|
|
25
|
+
QuotaExceededError,
|
|
24
26
|
RateLimitError,
|
|
25
27
|
ServerError,
|
|
26
28
|
ValidationError,
|
|
@@ -51,6 +53,7 @@ __all__ = [
|
|
|
51
53
|
"ClientTimeouts",
|
|
52
54
|
"ConfirmationRequiredError",
|
|
53
55
|
"ConflictError",
|
|
56
|
+
"FeatureNotEnabledError",
|
|
54
57
|
"K2Config",
|
|
55
58
|
"Knowledge2",
|
|
56
59
|
"Knowledge2Error",
|
|
@@ -59,6 +62,7 @@ __all__ = [
|
|
|
59
62
|
"Page",
|
|
60
63
|
"PermissionDeniedError",
|
|
61
64
|
"PipelineBuilder",
|
|
65
|
+
"QuotaExceededError",
|
|
62
66
|
"RateLimitError",
|
|
63
67
|
"RawResponse",
|
|
64
68
|
"RequestOptions",
|
|
@@ -285,6 +285,12 @@ class AsyncBaseClient:
|
|
|
285
285
|
response = await self._client.request(
|
|
286
286
|
method, path, headers=merged_headers, **kwargs
|
|
287
287
|
)
|
|
288
|
+
except RuntimeError as exc:
|
|
289
|
+
if "closed" in str(exc).lower():
|
|
290
|
+
raise APIConnectionError(
|
|
291
|
+
"Client has been closed. Create a new client instance."
|
|
292
|
+
) from exc
|
|
293
|
+
raise
|
|
288
294
|
except asyncio.CancelledError:
|
|
289
295
|
raise
|
|
290
296
|
except httpx.ConnectError as exc:
|
|
@@ -24,17 +24,45 @@ class AsyncPager(Generic[T]):
|
|
|
24
24
|
self._limit = limit
|
|
25
25
|
self._offset = offset
|
|
26
26
|
self._exhausted = False
|
|
27
|
+
self._first_page: Page[T] | None = None
|
|
28
|
+
self._first_page_consumed = False
|
|
29
|
+
|
|
30
|
+
async def get_total(self) -> int:
|
|
31
|
+
"""Total number of items across all pages.
|
|
32
|
+
|
|
33
|
+
Lazily fetches the first page if not yet fetched.
|
|
34
|
+
"""
|
|
35
|
+
if self._first_page is None:
|
|
36
|
+
items, total = await self._fetch_page(self._offset, self._limit)
|
|
37
|
+
self._first_page = Page(
|
|
38
|
+
items=items, total=total, offset=self._offset, limit=self._limit
|
|
39
|
+
)
|
|
40
|
+
return self._first_page.total
|
|
41
|
+
|
|
42
|
+
def _advance(self, page: Page[T]) -> None:
|
|
43
|
+
"""Update offset or mark exhausted after consuming a page."""
|
|
44
|
+
if len(page.items) < self._limit or (
|
|
45
|
+
page.total > len(page.items) and self._offset + self._limit >= page.total
|
|
46
|
+
):
|
|
47
|
+
self._exhausted = True
|
|
48
|
+
else:
|
|
49
|
+
self._offset += self._limit
|
|
27
50
|
|
|
28
51
|
async def next_page(self) -> Page[T] | None:
|
|
29
52
|
"""Fetch the next page. Returns None when exhausted."""
|
|
30
53
|
if self._exhausted:
|
|
31
54
|
return None
|
|
55
|
+
# Return cached first page if it hasn't been consumed yet
|
|
56
|
+
if self._first_page is not None and not self._first_page_consumed:
|
|
57
|
+
self._first_page_consumed = True
|
|
58
|
+
self._advance(self._first_page)
|
|
59
|
+
return self._first_page
|
|
32
60
|
items, total = await self._fetch_page(self._offset, self._limit)
|
|
33
61
|
page = Page(items=items, total=total, offset=self._offset, limit=self._limit)
|
|
34
|
-
if
|
|
35
|
-
self.
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
if self._first_page is None:
|
|
63
|
+
self._first_page = page
|
|
64
|
+
self._first_page_consumed = True
|
|
65
|
+
self._advance(page)
|
|
38
66
|
return page
|
|
39
67
|
|
|
40
68
|
async def iter_pages(self) -> AsyncIterator[Page[T]]:
|
|
@@ -302,6 +302,12 @@ class BaseClient:
|
|
|
302
302
|
_redact_headers(merged_headers),
|
|
303
303
|
)
|
|
304
304
|
response = self._client.request(method, path, headers=merged_headers, **kwargs)
|
|
305
|
+
except RuntimeError as exc:
|
|
306
|
+
if "closed" in str(exc).lower():
|
|
307
|
+
raise APIConnectionError(
|
|
308
|
+
"Client has been closed. Create a new client instance."
|
|
309
|
+
) from exc
|
|
310
|
+
raise
|
|
305
311
|
except httpx.ConnectError as exc:
|
|
306
312
|
last_error = APIConnectionError(f"Connection error: {exc}")
|
|
307
313
|
last_error.__cause__ = exc
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Callable, Generic, Iterator, TypeVar
|
|
7
7
|
|
|
8
8
|
T = TypeVar("T")
|
|
9
9
|
|
|
@@ -44,17 +44,46 @@ class SyncPager(Generic[T]):
|
|
|
44
44
|
self._limit = limit
|
|
45
45
|
self._offset = offset
|
|
46
46
|
self._exhausted = False
|
|
47
|
+
self._first_page: Page[T] | None = None
|
|
48
|
+
self._first_page_consumed = False
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def total(self) -> int:
|
|
52
|
+
"""Total number of items across all pages.
|
|
53
|
+
|
|
54
|
+
Lazily fetches the first page if not yet fetched.
|
|
55
|
+
"""
|
|
56
|
+
if self._first_page is None:
|
|
57
|
+
items, total = self._fetch_page(self._offset, self._limit)
|
|
58
|
+
self._first_page = Page(
|
|
59
|
+
items=items, total=total, offset=self._offset, limit=self._limit
|
|
60
|
+
)
|
|
61
|
+
return self._first_page.total
|
|
62
|
+
|
|
63
|
+
def _advance(self, page: Page[T]) -> None:
|
|
64
|
+
"""Update offset or mark exhausted after consuming a page."""
|
|
65
|
+
if len(page.items) < self._limit or (
|
|
66
|
+
page.total > len(page.items) and self._offset + self._limit >= page.total
|
|
67
|
+
):
|
|
68
|
+
self._exhausted = True
|
|
69
|
+
else:
|
|
70
|
+
self._offset += self._limit
|
|
47
71
|
|
|
48
72
|
def next_page(self) -> Page[T] | None:
|
|
49
73
|
"""Fetch the next page. Returns None when exhausted."""
|
|
50
74
|
if self._exhausted:
|
|
51
75
|
return None
|
|
76
|
+
# Return cached first page if it hasn't been consumed yet
|
|
77
|
+
if self._first_page is not None and not self._first_page_consumed:
|
|
78
|
+
self._first_page_consumed = True
|
|
79
|
+
self._advance(self._first_page)
|
|
80
|
+
return self._first_page
|
|
52
81
|
items, total = self._fetch_page(self._offset, self._limit)
|
|
53
82
|
page = Page(items=items, total=total, offset=self._offset, limit=self._limit)
|
|
54
|
-
if
|
|
55
|
-
self.
|
|
56
|
-
|
|
57
|
-
|
|
83
|
+
if self._first_page is None:
|
|
84
|
+
self._first_page = page
|
|
85
|
+
self._first_page_consumed = True
|
|
86
|
+
self._advance(page)
|
|
58
87
|
return page
|
|
59
88
|
|
|
60
89
|
def iter_pages(self) -> Iterator[Page[T]]:
|
|
@@ -42,7 +42,9 @@ def preview_resource(cls: _T) -> _T:
|
|
|
42
42
|
warnings.warn(
|
|
43
43
|
f"{_name}() is a preview feature and may not be available "
|
|
44
44
|
f"in all environments. The underlying API requires a "
|
|
45
|
-
f"feature flag to be enabled."
|
|
45
|
+
f"feature flag to be enabled."
|
|
46
|
+
f" Visit https://console.knowledge2.ai/settings/support"
|
|
47
|
+
f" to request access.",
|
|
46
48
|
RuntimeWarning,
|
|
47
49
|
stacklevel=2,
|
|
48
50
|
)
|
|
@@ -59,7 +61,9 @@ def preview_resource(cls: _T) -> _T:
|
|
|
59
61
|
warnings.warn(
|
|
60
62
|
f"{_name}() is a preview feature and may not be available "
|
|
61
63
|
f"in all environments. The underlying API requires a "
|
|
62
|
-
f"feature flag to be enabled."
|
|
64
|
+
f"feature flag to be enabled."
|
|
65
|
+
f" Visit https://console.knowledge2.ai/settings/support"
|
|
66
|
+
f" to request access.",
|
|
63
67
|
RuntimeWarning,
|
|
64
68
|
stacklevel=2,
|
|
65
69
|
)
|
|
@@ -18,9 +18,11 @@ from sdk.errors import (
|
|
|
18
18
|
AuthenticationError,
|
|
19
19
|
BadRequestError,
|
|
20
20
|
ConflictError,
|
|
21
|
+
FeatureNotEnabledError,
|
|
21
22
|
Knowledge2Error,
|
|
22
23
|
NotFoundError,
|
|
23
24
|
PermissionDeniedError,
|
|
25
|
+
QuotaExceededError,
|
|
24
26
|
RateLimitError,
|
|
25
27
|
ServerError,
|
|
26
28
|
ValidationError,
|
|
@@ -41,6 +43,12 @@ _STATUS_ERROR_MAP: dict[int, type[APIError]] = {
|
|
|
41
43
|
504: ServerError,
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
# Code-based overrides: (status_code, error_code) -> more specific error class.
|
|
47
|
+
_CODE_ERROR_OVERRIDE: dict[tuple[int, str], type[APIError]] = {
|
|
48
|
+
(403, "feature_not_enabled"): FeatureNotEnabledError,
|
|
49
|
+
(429, "quota_exceeded"): QuotaExceededError,
|
|
50
|
+
}
|
|
51
|
+
|
|
44
52
|
|
|
45
53
|
def error_from_response(response: httpx.Response) -> APIError:
|
|
46
54
|
"""Parse an error response into the appropriate APIError subclass."""
|
|
@@ -69,17 +77,38 @@ def error_from_response(response: httpx.Response) -> APIError:
|
|
|
69
77
|
message = f"{message} (request_id={request_id})"
|
|
70
78
|
|
|
71
79
|
status = response.status_code
|
|
80
|
+
|
|
81
|
+
# Build human-readable message from Pydantic validation details for 422
|
|
82
|
+
if status == 422 and isinstance(details, list) and details:
|
|
83
|
+
parts = []
|
|
84
|
+
for item in details:
|
|
85
|
+
if isinstance(item, dict):
|
|
86
|
+
loc = item.get("loc", [])
|
|
87
|
+
field = " → ".join(str(s) for s in loc if s != "body") or "unknown"
|
|
88
|
+
msg = item.get("msg", "invalid")
|
|
89
|
+
parts.append(f"{field} — {msg}")
|
|
90
|
+
if parts:
|
|
91
|
+
message = "Validation failed: " + "; ".join(parts)
|
|
92
|
+
if request_id:
|
|
93
|
+
message = f"{message} (request_id={request_id})"
|
|
94
|
+
|
|
72
95
|
error_cls = _STATUS_ERROR_MAP.get(status)
|
|
73
96
|
if error_cls is None:
|
|
74
97
|
error_cls = ServerError if 500 <= status < 600 else APIError
|
|
75
98
|
|
|
76
|
-
|
|
99
|
+
# Narrow to a more specific subclass when the error code matches.
|
|
100
|
+
if code:
|
|
101
|
+
override_key = (status, code)
|
|
102
|
+
if override_key in _CODE_ERROR_OVERRIDE:
|
|
103
|
+
error_cls = _CODE_ERROR_OVERRIDE[override_key]
|
|
104
|
+
|
|
105
|
+
if error_cls is not None and issubclass(error_cls, RateLimitError):
|
|
77
106
|
retry_after_raw = response.headers.get("Retry-After")
|
|
78
107
|
retry_after: float | None = None
|
|
79
108
|
if retry_after_raw is not None:
|
|
80
109
|
with contextlib.suppress(ValueError, TypeError):
|
|
81
110
|
retry_after = float(retry_after_raw)
|
|
82
|
-
return
|
|
111
|
+
return error_cls(
|
|
83
112
|
message,
|
|
84
113
|
status_code=status,
|
|
85
114
|
retry_after=retry_after,
|