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.
Files changed (162) hide show
  1. {knowledge2-0.4.0 → knowledge2-0.5.0}/CHANGELOG.md +6 -0
  2. knowledge2-0.5.0/MANIFEST.in +5 -0
  3. knowledge2-0.5.0/PKG-INFO +217 -0
  4. knowledge2-0.5.0/README.md +189 -0
  5. {knowledge2-0.4.0 → knowledge2-0.5.0}/__init__.py +4 -0
  6. {knowledge2-0.4.0 → knowledge2-0.5.0}/_async_base.py +6 -0
  7. {knowledge2-0.4.0 → knowledge2-0.5.0}/_async_paging.py +32 -4
  8. {knowledge2-0.4.0 → knowledge2-0.5.0}/_base.py +6 -0
  9. {knowledge2-0.4.0 → knowledge2-0.5.0}/_paging.py +34 -5
  10. {knowledge2-0.4.0 → knowledge2-0.5.0}/_preview.py +6 -2
  11. {knowledge2-0.4.0 → knowledge2-0.5.0}/_transport.py +31 -2
  12. {knowledge2-0.4.0 → knowledge2-0.5.0}/_validation.py +22 -0
  13. {knowledge2-0.4.0 → knowledge2-0.5.0}/_version.py +1 -1
  14. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_client.py +2 -6
  15. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/__init__.py +2 -6
  16. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/agents.py +5 -3
  17. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/console.py +0 -4
  18. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/corpora.py +2 -63
  19. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/documents.py +26 -0
  20. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/feeds.py +12 -27
  21. knowledge2-0.5.0/async_resources/generation_models.py +29 -0
  22. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/indexes.py +46 -2
  23. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/onboarding.py +16 -11
  24. knowledge2-0.5.0/async_resources/projects.py +305 -0
  25. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/search.py +2 -1
  26. {knowledge2-0.4.0 → knowledge2-0.5.0}/client.py +2 -34
  27. {knowledge2-0.4.0 → knowledge2-0.5.0}/errors.py +64 -0
  28. knowledge2-0.5.0/examples/e2e_lifecycle.py +178 -0
  29. knowledge2-0.5.0/examples/retrieval_quickstart.py +58 -0
  30. knowledge2-0.5.0/knowledge2.egg-info/PKG-INFO +217 -0
  31. {knowledge2-0.4.0 → knowledge2-0.5.0}/knowledge2.egg-info/SOURCES.txt +11 -30
  32. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/_registry.py +35 -48
  33. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/agents.py +34 -0
  34. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/console.py +0 -3
  35. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/corpora.py +1 -6
  36. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/documents.py +20 -0
  37. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/feeds.py +2 -4
  38. knowledge2-0.5.0/models/generation_models.py +16 -0
  39. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/indexes.py +6 -2
  40. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/onboarding.py +13 -5
  41. knowledge2-0.5.0/models/projects.py +46 -0
  42. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/search.py +2 -34
  43. {knowledge2-0.4.0 → knowledge2-0.5.0}/namespaces.py +4 -109
  44. {knowledge2-0.4.0 → knowledge2-0.5.0}/pyproject.toml +1 -1
  45. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/__init__.py +2 -6
  46. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/agents.py +5 -3
  47. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/corpora.py +2 -58
  48. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/documents.py +26 -0
  49. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/feeds.py +12 -27
  50. knowledge2-0.5.0/resources/generation_models.py +29 -0
  51. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/indexes.py +51 -2
  52. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/onboarding.py +16 -12
  53. knowledge2-0.5.0/resources/projects.py +300 -0
  54. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/search.py +2 -1
  55. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/__init__.py +32 -33
  56. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/agents.py +48 -1
  57. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/corpora.py +0 -1
  58. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/documents.py +22 -1
  59. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/feeds.py +2 -4
  60. knowledge2-0.5.0/types/generation_models.py +14 -0
  61. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/indexes.py +7 -1
  62. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/onboarding.py +13 -5
  63. knowledge2-0.5.0/types/projects.py +52 -0
  64. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/search.py +1 -0
  65. knowledge2-0.4.0/AGENTS.md +0 -31
  66. knowledge2-0.4.0/PKG-INFO +0 -556
  67. knowledge2-0.4.0/README.md +0 -528
  68. knowledge2-0.4.0/async_resources/deployments.py +0 -106
  69. knowledge2-0.4.0/async_resources/models.py +0 -102
  70. knowledge2-0.4.0/async_resources/projects.py +0 -90
  71. knowledge2-0.4.0/async_resources/training.py +0 -357
  72. knowledge2-0.4.0/examples/e2e_lifecycle.py +0 -213
  73. knowledge2-0.4.0/knowledge2.egg-info/PKG-INFO +0 -556
  74. knowledge2-0.4.0/models/deployments.py +0 -13
  75. knowledge2-0.4.0/models/evaluation.py +0 -17
  76. knowledge2-0.4.0/models/models.py +0 -26
  77. knowledge2-0.4.0/models/projects.py +0 -19
  78. knowledge2-0.4.0/models/training.py +0 -57
  79. knowledge2-0.4.0/resources/deployments.py +0 -105
  80. knowledge2-0.4.0/resources/models.py +0 -99
  81. knowledge2-0.4.0/resources/projects.py +0 -87
  82. knowledge2-0.4.0/resources/training.py +0 -358
  83. knowledge2-0.4.0/specification.md +0 -129
  84. knowledge2-0.4.0/types/deployments.py +0 -11
  85. knowledge2-0.4.0/types/evaluation.py +0 -15
  86. knowledge2-0.4.0/types/models.py +0 -22
  87. knowledge2-0.4.0/types/projects.py +0 -14
  88. knowledge2-0.4.0/types/training.py +0 -55
  89. {knowledge2-0.4.0 → knowledge2-0.5.0}/.github/workflows/pypi-release.yml +0 -0
  90. {knowledge2-0.4.0 → knowledge2-0.5.0}/_logging.py +0 -0
  91. {knowledge2-0.4.0 → knowledge2-0.5.0}/_raw_response.py +0 -0
  92. {knowledge2-0.4.0 → knowledge2-0.5.0}/_request_options.py +0 -0
  93. {knowledge2-0.4.0 → knowledge2-0.5.0}/_validation_response.py +0 -0
  94. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/_mixin_base.py +0 -0
  95. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/a2a.py +0 -0
  96. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/audit.py +0 -0
  97. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/auth.py +0 -0
  98. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/jobs.py +0 -0
  99. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/metadata.py +0 -0
  100. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/orgs.py +0 -0
  101. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/pipelines.py +0 -0
  102. {knowledge2-0.4.0 → knowledge2-0.5.0}/async_resources/usage.py +0 -0
  103. {knowledge2-0.4.0 → knowledge2-0.5.0}/config.py +0 -0
  104. {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/auth_factory.py +0 -0
  105. {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/batch_operations.py +0 -0
  106. {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/document_upload.py +0 -0
  107. {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/error_handling.py +0 -0
  108. {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/pagination.py +0 -0
  109. {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/quickstart.py +0 -0
  110. {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/request_options.py +0 -0
  111. {knowledge2-0.4.0 → knowledge2-0.5.0}/examples/search.py +0 -0
  112. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/__init__.py +0 -0
  113. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/_client.py +0 -0
  114. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/langchain/__init__.py +0 -0
  115. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/langchain/retriever.py +0 -0
  116. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/langchain/tools.py +0 -0
  117. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/__init__.py +0 -0
  118. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/filters.py +0 -0
  119. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/retriever.py +0 -0
  120. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/tools.py +0 -0
  121. {knowledge2-0.4.0 → knowledge2-0.5.0}/integrations/llamaindex/vector_store.py +0 -0
  122. {knowledge2-0.4.0 → knowledge2-0.5.0}/knowledge2.egg-info/dependency_links.txt +0 -0
  123. {knowledge2-0.4.0 → knowledge2-0.5.0}/knowledge2.egg-info/requires.txt +0 -0
  124. {knowledge2-0.4.0 → knowledge2-0.5.0}/knowledge2.egg-info/top_level.txt +0 -0
  125. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/__init__.py +0 -0
  126. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/_base.py +0 -0
  127. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/a2a.py +0 -0
  128. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/audit.py +0 -0
  129. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/auth.py +0 -0
  130. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/chunks.py +0 -0
  131. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/common.py +0 -0
  132. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/embeddings.py +0 -0
  133. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/feedback.py +0 -0
  134. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/jobs.py +0 -0
  135. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/orgs.py +0 -0
  136. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/pipelines.py +0 -0
  137. {knowledge2-0.4.0 → knowledge2-0.5.0}/models/usage.py +0 -0
  138. {knowledge2-0.4.0 → knowledge2-0.5.0}/py.typed +0 -0
  139. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/_mixin_base.py +0 -0
  140. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/a2a.py +0 -0
  141. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/audit.py +0 -0
  142. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/auth.py +0 -0
  143. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/console.py +0 -0
  144. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/jobs.py +0 -0
  145. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/metadata.py +0 -0
  146. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/orgs.py +0 -0
  147. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/pipeline_builder.py +0 -0
  148. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/pipelines.py +0 -0
  149. {knowledge2-0.4.0 → knowledge2-0.5.0}/resources/usage.py +0 -0
  150. {knowledge2-0.4.0 → knowledge2-0.5.0}/setup.cfg +0 -0
  151. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/a2a.py +0 -0
  152. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/audit.py +0 -0
  153. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/auth.py +0 -0
  154. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/chunks.py +0 -0
  155. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/common.py +0 -0
  156. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/console.py +0 -0
  157. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/embeddings.py +0 -0
  158. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/feedback.py +0 -0
  159. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/jobs.py +0 -0
  160. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/orgs.py +0 -0
  161. {knowledge2-0.4.0 → knowledge2-0.5.0}/types/pipelines.py +0 -0
  162. {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,5 @@
1
+ exclude AGENTS.md
2
+ exclude specification.md
3
+ recursive-exclude .claude *
4
+ recursive-exclude .codex *
5
+ recursive-exclude .skills *
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/knowledge2.svg)](https://pypi.org/project/knowledge2/)
32
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ [![PyPI version](https://img.shields.io/pypi/v/knowledge2.svg)](https://pypi.org/project/knowledge2/)
4
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 len(items) < self._limit or (total > len(items) and self._offset + self._limit >= total):
35
- self._exhausted = True
36
- else:
37
- self._offset += self._limit
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 Any, Callable, Generic, Iterator, TypeVar
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 len(items) < self._limit or (total > len(items) and self._offset + self._limit >= total):
55
- self._exhausted = True
56
- else:
57
- self._offset += self._limit
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
- if error_cls is RateLimitError:
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 RateLimitError(
111
+ return error_cls(
83
112
  message,
84
113
  status_code=status,
85
114
  retry_after=retry_after,