knowledge2 0.5.0__tar.gz → 0.7.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 (139) hide show
  1. {knowledge2-0.5.0 → knowledge2-0.7.0}/CHANGELOG.md +67 -2
  2. {knowledge2-0.5.0/knowledge2.egg-info → knowledge2-0.7.0}/PKG-INFO +115 -14
  3. knowledge2-0.7.0/README.md +289 -0
  4. {knowledge2-0.5.0 → knowledge2-0.7.0}/_async_base.py +48 -2
  5. {knowledge2-0.5.0 → knowledge2-0.7.0}/_base.py +48 -2
  6. {knowledge2-0.5.0 → knowledge2-0.7.0}/_validation_response.py +2 -2
  7. {knowledge2-0.5.0 → knowledge2-0.7.0}/_version.py +1 -1
  8. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_client.py +10 -2
  9. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/agents.py +44 -3
  10. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/documents.py +354 -9
  11. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/feeds.py +138 -0
  12. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/indexes.py +11 -2
  13. {knowledge2-0.5.0 → knowledge2-0.7.0}/client.py +10 -3
  14. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/batch_operations.py +4 -2
  15. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/document_upload.py +3 -2
  16. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/e2e_lifecycle.py +6 -32
  17. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/retrieval_quickstart.py +2 -2
  18. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/__init__.py +6 -6
  19. {knowledge2-0.5.0 → knowledge2-0.7.0/knowledge2.egg-info}/PKG-INFO +115 -14
  20. {knowledge2-0.5.0 → knowledge2-0.7.0}/knowledge2.egg-info/requires.txt +2 -1
  21. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/__init__.py +4 -6
  22. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/_registry.py +8 -0
  23. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/agents.py +4 -0
  24. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/documents.py +26 -0
  25. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/feeds.py +31 -3
  26. {knowledge2-0.5.0 → knowledge2-0.7.0}/namespaces.py +25 -1
  27. {knowledge2-0.5.0 → knowledge2-0.7.0}/pyproject.toml +4 -3
  28. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/agents.py +44 -3
  29. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/documents.py +356 -9
  30. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/feeds.py +193 -0
  31. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/indexes.py +11 -2
  32. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/__init__.py +10 -0
  33. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/agents.py +10 -1
  34. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/documents.py +26 -0
  35. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/feeds.py +27 -0
  36. knowledge2-0.5.0/README.md +0 -189
  37. {knowledge2-0.5.0 → knowledge2-0.7.0}/.github/workflows/pypi-release.yml +0 -0
  38. {knowledge2-0.5.0 → knowledge2-0.7.0}/MANIFEST.in +0 -0
  39. {knowledge2-0.5.0 → knowledge2-0.7.0}/__init__.py +0 -0
  40. {knowledge2-0.5.0 → knowledge2-0.7.0}/_async_paging.py +0 -0
  41. {knowledge2-0.5.0 → knowledge2-0.7.0}/_logging.py +0 -0
  42. {knowledge2-0.5.0 → knowledge2-0.7.0}/_paging.py +0 -0
  43. {knowledge2-0.5.0 → knowledge2-0.7.0}/_preview.py +0 -0
  44. {knowledge2-0.5.0 → knowledge2-0.7.0}/_raw_response.py +0 -0
  45. {knowledge2-0.5.0 → knowledge2-0.7.0}/_request_options.py +0 -0
  46. {knowledge2-0.5.0 → knowledge2-0.7.0}/_transport.py +0 -0
  47. {knowledge2-0.5.0 → knowledge2-0.7.0}/_validation.py +0 -0
  48. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/__init__.py +0 -0
  49. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/_mixin_base.py +0 -0
  50. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/a2a.py +0 -0
  51. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/audit.py +0 -0
  52. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/auth.py +0 -0
  53. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/console.py +0 -0
  54. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/corpora.py +0 -0
  55. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/generation_models.py +0 -0
  56. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/jobs.py +0 -0
  57. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/metadata.py +0 -0
  58. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/onboarding.py +0 -0
  59. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/orgs.py +0 -0
  60. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/pipelines.py +0 -0
  61. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/projects.py +0 -0
  62. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/search.py +0 -0
  63. {knowledge2-0.5.0 → knowledge2-0.7.0}/async_resources/usage.py +0 -0
  64. {knowledge2-0.5.0 → knowledge2-0.7.0}/config.py +0 -0
  65. {knowledge2-0.5.0 → knowledge2-0.7.0}/errors.py +0 -0
  66. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/auth_factory.py +0 -0
  67. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/error_handling.py +0 -0
  68. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/pagination.py +0 -0
  69. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/quickstart.py +0 -0
  70. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/request_options.py +0 -0
  71. {knowledge2-0.5.0 → knowledge2-0.7.0}/examples/search.py +0 -0
  72. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/_client.py +0 -0
  73. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/langchain/__init__.py +0 -0
  74. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/langchain/retriever.py +0 -0
  75. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/langchain/tools.py +0 -0
  76. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/llamaindex/__init__.py +0 -0
  77. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/llamaindex/filters.py +0 -0
  78. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/llamaindex/retriever.py +0 -0
  79. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/llamaindex/tools.py +0 -0
  80. {knowledge2-0.5.0 → knowledge2-0.7.0}/integrations/llamaindex/vector_store.py +0 -0
  81. {knowledge2-0.5.0 → knowledge2-0.7.0}/knowledge2.egg-info/SOURCES.txt +0 -0
  82. {knowledge2-0.5.0 → knowledge2-0.7.0}/knowledge2.egg-info/dependency_links.txt +0 -0
  83. {knowledge2-0.5.0 → knowledge2-0.7.0}/knowledge2.egg-info/top_level.txt +0 -0
  84. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/_base.py +0 -0
  85. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/a2a.py +0 -0
  86. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/audit.py +0 -0
  87. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/auth.py +0 -0
  88. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/chunks.py +0 -0
  89. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/common.py +0 -0
  90. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/console.py +0 -0
  91. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/corpora.py +0 -0
  92. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/embeddings.py +0 -0
  93. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/feedback.py +0 -0
  94. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/generation_models.py +0 -0
  95. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/indexes.py +0 -0
  96. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/jobs.py +0 -0
  97. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/onboarding.py +0 -0
  98. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/orgs.py +0 -0
  99. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/pipelines.py +0 -0
  100. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/projects.py +0 -0
  101. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/search.py +0 -0
  102. {knowledge2-0.5.0 → knowledge2-0.7.0}/models/usage.py +0 -0
  103. {knowledge2-0.5.0 → knowledge2-0.7.0}/py.typed +0 -0
  104. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/__init__.py +0 -0
  105. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/_mixin_base.py +0 -0
  106. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/a2a.py +0 -0
  107. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/audit.py +0 -0
  108. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/auth.py +0 -0
  109. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/console.py +0 -0
  110. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/corpora.py +0 -0
  111. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/generation_models.py +0 -0
  112. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/jobs.py +0 -0
  113. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/metadata.py +0 -0
  114. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/onboarding.py +0 -0
  115. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/orgs.py +0 -0
  116. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/pipeline_builder.py +0 -0
  117. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/pipelines.py +0 -0
  118. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/projects.py +0 -0
  119. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/search.py +0 -0
  120. {knowledge2-0.5.0 → knowledge2-0.7.0}/resources/usage.py +0 -0
  121. {knowledge2-0.5.0 → knowledge2-0.7.0}/setup.cfg +0 -0
  122. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/a2a.py +0 -0
  123. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/audit.py +0 -0
  124. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/auth.py +0 -0
  125. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/chunks.py +0 -0
  126. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/common.py +0 -0
  127. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/console.py +0 -0
  128. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/corpora.py +0 -0
  129. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/embeddings.py +0 -0
  130. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/feedback.py +0 -0
  131. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/generation_models.py +0 -0
  132. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/indexes.py +0 -0
  133. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/jobs.py +0 -0
  134. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/onboarding.py +0 -0
  135. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/orgs.py +0 -0
  136. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/pipelines.py +0 -0
  137. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/projects.py +0 -0
  138. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/search.py +0 -0
  139. {knowledge2-0.5.0 → knowledge2-0.7.0}/types/usage.py +0 -0
@@ -1,10 +1,75 @@
1
1
  # Changelog
2
2
 
3
- All notable changes to the Knowledge2 Python SDK will be documented in this file.
3
+ All notable changes to the Knowledge² Python SDK will be documented in this file.
4
4
 
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.7.0] - 2026-04-23
9
+
10
+ ### Added
11
+
12
+ - Feed draft lifecycle on both `Knowledge2` and `AsyncKnowledge2`:
13
+ `create_feed_draft`, `get_feed_draft`, `activate_feed_draft`,
14
+ `discard_feed_draft`. Activation returns the updated **parent** feed;
15
+ the draft is deleted server-side.
16
+ - `list_feed_subscriptions(feed_id)` — read-only view of the subscriptions
17
+ embedded on the feed record. Handles validated and raw-response modes,
18
+ passing `with_raw_response` errors through unchanged and re-wrapping
19
+ successful responses so status/headers survive.
20
+ - Feed feedback helpers: `submit_feed_feedback(feed_id, *, rating,
21
+ chunk_id, feed_run_id)` and `get_feed_feedback_stats(feed_id, *,
22
+ feed_run_id=None)`. `rating` is validated client-side to be `0` or `1`.
23
+ - New response types `FeedFeedbackSubmitResponse` and
24
+ `FeedFeedbackStatsResponse` exported from `sdk.types`, with matching
25
+ strict Pydantic models registered for `validate_responses=True`.
26
+ - `FeedSubscriptionResponse` (and its Pydantic model) now include
27
+ `match_spec` and `match_spec_description` — the routing metadata
28
+ returned by the feed-side subscription schema for `explicit` and
29
+ `nl_semantic` subscriptions.
30
+
31
+ ### Changed
32
+
33
+ - `FeedResponseModel.subscriptions` now always validates to a list
34
+ (previously `list | None = None`). The server contract guarantees a
35
+ list — empty by default — so validated clients can iterate without a
36
+ None check. A legacy response containing `null` for `subscriptions`
37
+ will now raise `ValidationError` instead of producing a `None` field.
38
+ - `FeedSubscriptionResponseModel.agent_id` and `role` are now required
39
+ (previously `str | None = None`) to match the server schema and catch
40
+ malformed subscription payloads in validated clients.
41
+
42
+ ## [0.6.1] - 2026-04-17
43
+
44
+ ### Added
45
+
46
+ - `upload_documents_batch_and_wait(...)` as the canonical blocking helper for raw-text batch ingestion
47
+ - `wait_for_document_batch(...)` for callers that intentionally enqueue first and resolve the batch later
48
+
49
+ ### Changed
50
+
51
+ - base installs now include `pydantic`, so `pip install knowledge2` is import-clean and supports validated response models without an extra
52
+ - `knowledge2[pydantic]` remains accepted as a compatibility alias for older install commands
53
+ - batch wait helpers now enforce a single end-to-end timeout budget and support `with_raw_response` correctly
54
+ - `get_document_batch(...)` now surfaces stable `doc_ids` and live counters once a batch is visible, instead of waiting for terminal aggregation
55
+ - refreshed the primary onboarding docs and examples to prefer the blocking batch helper and the released public contract
56
+ - clarified early auth and quota guidance for the supported retrieval workflow
57
+
58
+ ## [0.6.0] - 2026-04-16
59
+
60
+ ### Changed
61
+
62
+ - `upload_documents_batch(..., wait=False)` returns an enqueue handle with `job_id`, `batch_id`, and `count`
63
+ - `get_document_batch(...)` is the supported batch-status lookup for raw-text uploads, including final `doc_ids` and per-item errors
64
+ - refreshed public SDK release notes to align with the current batch-upload surface
65
+
66
+ ## [0.5.0] - 2026-04-13
67
+
68
+ ### Changed
69
+
70
+ - aligned the published Python SDK support matrix with the public customer-facing surface
71
+ - refreshed README guidance, examples, and optimize-related test coverage for the current SDK capabilities
72
+
8
73
  ## [0.4.1] - 2026-04-03
9
74
 
10
75
  ### Changed
@@ -43,7 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
43
108
  - **`K2Config` frozen config** (#720): Pydantic-settings based configuration from environment variables (`K2_*` prefix), JSON/YAML files, or named profiles.
44
109
  - **Sub-client namespaces** (#721): `client.documents.*`, `client.corpora.*`, `client.search_ns.*`, etc. Lightweight cached proxies — flat methods still work.
45
110
  - **Parallel uploads** (#722): `upload_documents_parallel()` using `ThreadPoolExecutor` for concurrent document ingestion.
46
- - **Pydantic response validation** (#723): Optional `validate_responses=True` or `Knowledge2Validated` class. Requires `pip install knowledge2[pydantic]`.
111
+ - **Pydantic response validation** (#723): Optional `validate_responses=True` or `Knowledge2Validated` class. Base installs now include the Pydantic model dependency.
47
112
  - **`AsyncKnowledge2` client** (#724): Native async client using `httpx.AsyncClient`. `AsyncPager[T]` for async pagination. Factory methods: `create()`, `from_env()`, `from_file()`, `from_profile()`.
48
113
  - **`RequestOptions`** (#732): Per-call overrides for timeout, retries, and passthrough headers. `request_options` parameter on all public methods.
49
114
  - **`with_raw_response`** (#733): `client.with_raw_response.<method>(...)` returns `RawResponse[T]` with `status_code`, `headers`, and `parsed` body. Thread-safe (sync) and task-safe (async).
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: knowledge2
3
- Version: 0.5.0
4
- Summary: Python SDK for the Knowledge2 retrieval platform
3
+ Version: 0.7.0
4
+ Summary: Python SDK for the Knowledge² retrieval platform
5
5
  Author-email: Knowledge2 <contact@knowledge2.ai>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://knowledge2.ai
@@ -19,20 +19,21 @@ Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.11
20
20
  Description-Content-Type: text/markdown
21
21
  Requires-Dist: httpx>=0.27
22
+ Requires-Dist: pydantic<3,>=2
22
23
  Provides-Extra: config
23
24
  Requires-Dist: pydantic-settings>=2.0; extra == "config"
24
25
  Provides-Extra: pydantic
25
- Requires-Dist: pydantic>=2.0; extra == "pydantic"
26
+ Requires-Dist: pydantic<3,>=2; extra == "pydantic"
26
27
  Provides-Extra: yaml
27
28
  Requires-Dist: pyyaml>=6.0; extra == "yaml"
28
29
 
29
- # Knowledge2 Python SDK
30
+ # Knowledge² Python SDK
30
31
 
31
32
  [![PyPI version](https://img.shields.io/pypi/v/knowledge2.svg)](https://pypi.org/project/knowledge2/)
32
33
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
33
34
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
34
35
 
35
- Official Python client for the Knowledge2 retrieval platform. The supported customer journey is:
36
+ Official Python client for the Knowledge² retrieval platform. The supported customer journey is:
36
37
 
37
38
  `create corpus -> ingest documents -> build indexes -> search -> optimize retrieval`
38
39
 
@@ -43,7 +44,6 @@ From PyPI:
43
44
  ```bash
44
45
  pip install knowledge2
45
46
  pip install "knowledge2[config]"
46
- pip install "knowledge2[pydantic]"
47
47
  pip install "knowledge2[yaml]"
48
48
  ```
49
49
 
@@ -52,10 +52,21 @@ From source:
52
52
  ```bash
53
53
  pip install -e .
54
54
  pip install -e ".[config]"
55
- pip install -e ".[pydantic]"
56
55
  pip install -e ".[yaml]"
57
56
  ```
58
57
 
58
+ `pip install knowledge2` now includes the typed response model dependency
59
+ (`pydantic`) out of the box. Install `knowledge2[config]` only if you want
60
+ `K2Config` environment/file loading via `pydantic-settings`.
61
+
62
+ ## Before You Start
63
+
64
+ - Use a normal org-scoped API key for the standard retrieval workflow:
65
+ projects, corpora, documents, indexes, search, and optimize.
66
+ - `optimize_indexes()` and some enterprise/preview surfaces can return
67
+ feature-flag or quota errors (`403`, `409`, `429`) even when the payload is
68
+ correct. Check environment entitlements early.
69
+
59
70
  ## Surface Categories
60
71
 
61
72
  | Category | Surface |
@@ -75,12 +86,12 @@ client = Knowledge2(api_key="k2_...")
75
86
  project = client.create_project("My Project")
76
87
  corpus = client.create_corpus(project["id"], "My Corpus")
77
88
 
78
- client.upload_documents_batch(
89
+ batch = client.upload_documents_batch_and_wait(
79
90
  corpus["id"],
80
91
  [
81
92
  {
82
93
  "source_uri": "doc://overview",
83
- "raw_text": "Knowledge2 builds dense and sparse indexes for hybrid retrieval.",
94
+ "raw_text": "Knowledge² builds dense and sparse indexes for hybrid retrieval.",
84
95
  "metadata": {"topic": "overview"},
85
96
  },
86
97
  {
@@ -89,7 +100,6 @@ client.upload_documents_batch(
89
100
  "metadata": {"topic": "search"},
90
101
  },
91
102
  ],
92
- wait=True,
93
103
  auto_index=False,
94
104
  )
95
105
  client.sync_indexes(corpus["id"], wait=True)
@@ -105,6 +115,33 @@ for hit in results["results"]:
105
115
  print(hit["score"], hit.get("text", "")[:80])
106
116
  ```
107
117
 
118
+ `upload_documents_batch_and_wait(...)` is the canonical onboarding helper for
119
+ raw-text JSON batch ingestion. It blocks until the batch finishes and returns
120
+ the final batch payload, including `doc_ids`.
121
+
122
+ If you intentionally want enqueue-first control, use `wait=False` and then
123
+ resolve the batch with `wait_for_document_batch(...)`:
124
+
125
+ ```python
126
+ docs = [
127
+ {
128
+ "source_uri": "doc://overview",
129
+ "raw_text": "Knowledge² builds dense and sparse indexes for hybrid retrieval.",
130
+ },
131
+ ]
132
+
133
+ enqueue = client.upload_documents_batch(corpus["id"], docs, wait=False)
134
+ batch = client.wait_for_document_batch(corpus["id"], enqueue["batch_id"])
135
+ print(batch["status"], batch["doc_ids"])
136
+ ```
137
+
138
+ For large in-flight imports, `get_document_batch(...)` and
139
+ `wait_for_document_batch(...)` are the canonical batch APIs. Once the batch is
140
+ visible they return stable `doc_ids`, terminal resolution, and live batch
141
+ counters that track admitted documents as processing advances. For broader
142
+ operational context during a large import, you can still pair them with
143
+ `get_corpus_status(...)`, `get_job(...)`, or document-level status checks.
144
+
108
145
  ## Improve Retrieval Quality
109
146
 
110
147
  ```python
@@ -121,7 +158,7 @@ job = client.optimize_indexes(
121
158
  query_count=25,
122
159
  top_k=10,
123
160
  metric="ndcg",
124
- wait=False,
161
+ wait=True,
125
162
  )
126
163
  print(job["job_id"], job["job_type"])
127
164
  ```
@@ -144,9 +181,8 @@ python sdk/examples/e2e_lifecycle.py
144
181
 
145
182
  | Method | Header | Typical use |
146
183
  |---|---|---|
147
- | API key | `X-API-Key` | primary programmatic access |
184
+ | API key | `X-API-Key` | primary programmatic access for retrieval workflows |
148
185
  | Bearer token | `Authorization: Bearer <token>` | console / Auth0 session |
149
- | Admin token | `X-Admin-Token` | bootstrap and admin operations |
150
186
 
151
187
  ```python
152
188
  client = Knowledge2(api_key="k2_...")
@@ -178,14 +214,19 @@ client = Knowledge2(
178
214
 
179
215
  ## Namespaces
180
216
 
181
- The flat client API is canonical. Namespace helpers group the same methods without changing behavior:
217
+ The flat client API is canonical. The sync client also exposes namespace helpers
218
+ that group the same methods without changing behavior:
182
219
 
183
220
  - `client.documents.*`
221
+ - `client.documents.upload_batch_and_wait(...)`
222
+ - `client.documents.wait_for_batch(...)`
184
223
  - `client.corpora.*`
185
224
  - `client.search_ns.*`
186
225
  - `client.jobs.*`
187
226
  - `client.auth.*`
188
227
 
228
+ `AsyncKnowledge2` currently stays flat-only.
229
+
189
230
  ## Framework Integrations
190
231
 
191
232
  The SDK ships LangChain and LlamaIndex integration modules in-package. Install the framework dependency separately, then import the adapter:
@@ -199,6 +240,66 @@ from sdk.integrations.llamaindex import K2LlamaIndexRetriever
199
240
 
200
241
  Agents, feeds, pipelines, and A2A are available for enterprise deployments. Keep the primary examples focused on the core retrieval flow.
201
242
 
243
+ ### Subscription Modes (Preview)
244
+
245
+ Agent-feed subscriptions support three authoring modes on `create_subscription`, gated behind the `knowledge_agents_enabled` feature flag:
246
+
247
+ | Mode | Use | Required fields |
248
+ |------|-----|-----------------|
249
+ | `always` | Route every envelope from the feed | `feed_id`, `role` |
250
+ | `explicit` | Evaluate a predicate DSL against the envelope | `feed_id`, `role`, `match_spec` |
251
+ | `nl_semantic` | Describe the match in plain English; compiled server-side into a `semantic_like` predicate against `content` | `feed_id`, `role`, `match_spec_description` (10-500 chars); optional `threshold` (default 0.75) |
252
+
253
+ The create response echoes the compiled `match_spec` and the raw `match_spec_description`, so no separate `/preview` endpoint is required:
254
+
255
+ ```python
256
+ sub = client.create_subscription(
257
+ agent_id,
258
+ feed_id=feed_id,
259
+ role="input",
260
+ mode="nl_semantic",
261
+ match_spec_description="documents about security incidents",
262
+ )
263
+ print(sub["match_spec"]) # compiled semantic_like predicate
264
+ print(sub["match_spec_description"]) # raw NL description (echoed)
265
+ ```
266
+
267
+ ### Feed Drafts, Subscriptions, and Feedback (Preview)
268
+
269
+ In addition to CRUD and `run_feed`, the `Knowledge2` client exposes the full
270
+ editing and feedback surface of the Feeds API as flat methods on `client`
271
+ (the same mixin-based pattern used by every other resource).
272
+
273
+ | Method | Endpoint | Notes |
274
+ |--------|----------|-------|
275
+ | `create_feed_draft(feed_id)` | `POST /v1/feeds/{id}/draft` | Returns a draft feed with `parent_feed_id` set |
276
+ | `get_feed_draft(feed_id)` | `GET /v1/feeds/{id}/draft` | 404 when no draft exists |
277
+ | `activate_feed_draft(feed_id)` | `POST /v1/feeds/{id}/draft/activate` | Returns the updated **parent** feed (draft is deleted) |
278
+ | `discard_feed_draft(feed_id)` | `DELETE /v1/feeds/{id}/draft` | Returns `None` |
279
+ | `list_feed_subscriptions(feed_id)` | Read-only view | Returns subscriptions embedded on the feed record; use `create_subscription` on the Agents mixin to attach new ones |
280
+ | `submit_feed_feedback(feed_id, *, rating, chunk_id, feed_run_id)` | `POST /v1/feeds/{id}/feedback` | `rating` is `1` (thumbs up) or `0` (thumbs down) |
281
+ | `get_feed_feedback_stats(feed_id, *, feed_run_id=None)` | `GET /v1/feeds/{id}/feedback` | Optional `feed_run_id` scopes stats to a single run |
282
+
283
+ ```python
284
+ draft = client.create_feed_draft(feed_id)
285
+ client.update_feed(draft["id"], name="new name")
286
+ client.activate_feed_draft(feed_id) # applies the draft; returns the parent
287
+
288
+ run = client.run_feed(feed_id, return_results=True)
289
+ # `results` is only populated for non-persistent feeds run with
290
+ # `return_results=True`; guard the example for safe use.
291
+ if run.get("results"):
292
+ client.submit_feed_feedback(
293
+ feed_id,
294
+ rating=1,
295
+ chunk_id=run["results"][0]["chunk_id"],
296
+ feed_run_id=run["feed_run_id"],
297
+ )
298
+ stats = client.get_feed_feedback_stats(feed_id) # org-wide for this feed
299
+ ```
300
+
301
+ All three areas are fully mirrored on `AsyncKnowledge2` under the same names.
302
+
202
303
  ## Error Handling
203
304
 
204
305
  All SDK exceptions inherit from `Knowledge2Error`.
@@ -0,0 +1,289 @@
1
+ # Knowledge² 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 Knowledge² 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[yaml]"
19
+ ```
20
+
21
+ From source:
22
+
23
+ ```bash
24
+ pip install -e .
25
+ pip install -e ".[config]"
26
+ pip install -e ".[yaml]"
27
+ ```
28
+
29
+ `pip install knowledge2` now includes the typed response model dependency
30
+ (`pydantic`) out of the box. Install `knowledge2[config]` only if you want
31
+ `K2Config` environment/file loading via `pydantic-settings`.
32
+
33
+ ## Before You Start
34
+
35
+ - Use a normal org-scoped API key for the standard retrieval workflow:
36
+ projects, corpora, documents, indexes, search, and optimize.
37
+ - `optimize_indexes()` and some enterprise/preview surfaces can return
38
+ feature-flag or quota errors (`403`, `409`, `429`) even when the payload is
39
+ correct. Check environment entitlements early.
40
+
41
+ ## Surface Categories
42
+
43
+ | Category | Surface |
44
+ |---|---|
45
+ | Core retrieval workflow | orgs, auth, projects, corpora, documents, indexes, search, jobs, metadata, onboarding, audit, usage, console, generation models |
46
+ | Enterprise capabilities | agents, feeds, pipelines, A2A |
47
+
48
+ The main docs and examples below focus on the core retrieval workflow.
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from sdk import Knowledge2
54
+
55
+ client = Knowledge2(api_key="k2_...")
56
+
57
+ project = client.create_project("My Project")
58
+ corpus = client.create_corpus(project["id"], "My Corpus")
59
+
60
+ batch = client.upload_documents_batch_and_wait(
61
+ corpus["id"],
62
+ [
63
+ {
64
+ "source_uri": "doc://overview",
65
+ "raw_text": "Knowledge² builds dense and sparse indexes for hybrid retrieval.",
66
+ "metadata": {"topic": "overview"},
67
+ },
68
+ {
69
+ "source_uri": "doc://search",
70
+ "raw_text": "Hybrid retrieval combines semantic similarity with exact keyword matching.",
71
+ "metadata": {"topic": "search"},
72
+ },
73
+ ],
74
+ auto_index=False,
75
+ )
76
+ client.sync_indexes(corpus["id"], wait=True)
77
+
78
+ results = client.search(
79
+ corpus["id"],
80
+ "what is hybrid retrieval",
81
+ top_k=3,
82
+ return_config={"include_text": True, "include_scores": True},
83
+ )
84
+
85
+ for hit in results["results"]:
86
+ print(hit["score"], hit.get("text", "")[:80])
87
+ ```
88
+
89
+ `upload_documents_batch_and_wait(...)` is the canonical onboarding helper for
90
+ raw-text JSON batch ingestion. It blocks until the batch finishes and returns
91
+ the final batch payload, including `doc_ids`.
92
+
93
+ If you intentionally want enqueue-first control, use `wait=False` and then
94
+ resolve the batch with `wait_for_document_batch(...)`:
95
+
96
+ ```python
97
+ docs = [
98
+ {
99
+ "source_uri": "doc://overview",
100
+ "raw_text": "Knowledge² builds dense and sparse indexes for hybrid retrieval.",
101
+ },
102
+ ]
103
+
104
+ enqueue = client.upload_documents_batch(corpus["id"], docs, wait=False)
105
+ batch = client.wait_for_document_batch(corpus["id"], enqueue["batch_id"])
106
+ print(batch["status"], batch["doc_ids"])
107
+ ```
108
+
109
+ For large in-flight imports, `get_document_batch(...)` and
110
+ `wait_for_document_batch(...)` are the canonical batch APIs. Once the batch is
111
+ visible they return stable `doc_ids`, terminal resolution, and live batch
112
+ counters that track admitted documents as processing advances. For broader
113
+ operational context during a large import, you can still pair them with
114
+ `get_corpus_status(...)`, `get_job(...)`, or document-level status checks.
115
+
116
+ ## Improve Retrieval Quality
117
+
118
+ ```python
119
+ profile = client.get_query_profile(corpus["id"])
120
+ print(profile["example_queries"])
121
+
122
+ job = client.optimize_indexes(
123
+ corpus["id"],
124
+ example_queries=[
125
+ "how does hybrid retrieval work",
126
+ "what is bm25 tuning",
127
+ "how does rrf combine dense and sparse search",
128
+ ],
129
+ query_count=25,
130
+ top_k=10,
131
+ metric="ndcg",
132
+ wait=True,
133
+ )
134
+ print(job["job_id"], job["job_type"])
135
+ ```
136
+
137
+ ## Examples
138
+
139
+ - `sdk/examples/retrieval_quickstart.py`: minimal happy path from empty corpus to working hybrid search
140
+ - `sdk/examples/e2e_lifecycle.py`: full retrieval-quality workflow with query profile inspection and `indexes:optimize`
141
+
142
+ Run either example with:
143
+
144
+ ```bash
145
+ export K2_BASE_URL=https://api.knowledge2.ai
146
+ export K2_API_KEY=<api-key>
147
+ python sdk/examples/retrieval_quickstart.py
148
+ python sdk/examples/e2e_lifecycle.py
149
+ ```
150
+
151
+ ## Authentication
152
+
153
+ | Method | Header | Typical use |
154
+ |---|---|---|
155
+ | API key | `X-API-Key` | primary programmatic access for retrieval workflows |
156
+ | Bearer token | `Authorization: Bearer <token>` | console / Auth0 session |
157
+
158
+ ```python
159
+ client = Knowledge2(api_key="k2_...")
160
+ client = Knowledge2.from_env()
161
+ client = Knowledge2(bearer_token="...")
162
+ ```
163
+
164
+ ## Configuration
165
+
166
+ Important constructor knobs:
167
+
168
+ - `api_host`: defaults to `https://api.knowledge2.ai`
169
+ - `api_key`: API key for programmatic access
170
+ - `org_id`: auto-detected from `GET /v1/auth/whoami` when omitted
171
+ - `timeout`: float or `ClientTimeouts`
172
+ - `limits`: connection-pool settings via `ClientLimits`
173
+ - `max_retries`: transient retry budget
174
+ - `validate_responses`: enable Pydantic response validation
175
+ - `http_client`: bring your own `httpx.Client`
176
+
177
+ ```python
178
+ from sdk import ClientTimeouts, Knowledge2
179
+
180
+ client = Knowledge2(
181
+ api_key="k2_...",
182
+ timeout=ClientTimeouts(connect=5, read=120, write=30, pool=10),
183
+ )
184
+ ```
185
+
186
+ ## Namespaces
187
+
188
+ The flat client API is canonical. The sync client also exposes namespace helpers
189
+ that group the same methods without changing behavior:
190
+
191
+ - `client.documents.*`
192
+ - `client.documents.upload_batch_and_wait(...)`
193
+ - `client.documents.wait_for_batch(...)`
194
+ - `client.corpora.*`
195
+ - `client.search_ns.*`
196
+ - `client.jobs.*`
197
+ - `client.auth.*`
198
+
199
+ `AsyncKnowledge2` currently stays flat-only.
200
+
201
+ ## Framework Integrations
202
+
203
+ The SDK ships LangChain and LlamaIndex integration modules in-package. Install the framework dependency separately, then import the adapter:
204
+
205
+ ```python
206
+ from sdk.integrations.langchain import K2LangChainRetriever
207
+ from sdk.integrations.llamaindex import K2LlamaIndexRetriever
208
+ ```
209
+
210
+ ## Enterprise Capabilities
211
+
212
+ Agents, feeds, pipelines, and A2A are available for enterprise deployments. Keep the primary examples focused on the core retrieval flow.
213
+
214
+ ### Subscription Modes (Preview)
215
+
216
+ Agent-feed subscriptions support three authoring modes on `create_subscription`, gated behind the `knowledge_agents_enabled` feature flag:
217
+
218
+ | Mode | Use | Required fields |
219
+ |------|-----|-----------------|
220
+ | `always` | Route every envelope from the feed | `feed_id`, `role` |
221
+ | `explicit` | Evaluate a predicate DSL against the envelope | `feed_id`, `role`, `match_spec` |
222
+ | `nl_semantic` | Describe the match in plain English; compiled server-side into a `semantic_like` predicate against `content` | `feed_id`, `role`, `match_spec_description` (10-500 chars); optional `threshold` (default 0.75) |
223
+
224
+ The create response echoes the compiled `match_spec` and the raw `match_spec_description`, so no separate `/preview` endpoint is required:
225
+
226
+ ```python
227
+ sub = client.create_subscription(
228
+ agent_id,
229
+ feed_id=feed_id,
230
+ role="input",
231
+ mode="nl_semantic",
232
+ match_spec_description="documents about security incidents",
233
+ )
234
+ print(sub["match_spec"]) # compiled semantic_like predicate
235
+ print(sub["match_spec_description"]) # raw NL description (echoed)
236
+ ```
237
+
238
+ ### Feed Drafts, Subscriptions, and Feedback (Preview)
239
+
240
+ In addition to CRUD and `run_feed`, the `Knowledge2` client exposes the full
241
+ editing and feedback surface of the Feeds API as flat methods on `client`
242
+ (the same mixin-based pattern used by every other resource).
243
+
244
+ | Method | Endpoint | Notes |
245
+ |--------|----------|-------|
246
+ | `create_feed_draft(feed_id)` | `POST /v1/feeds/{id}/draft` | Returns a draft feed with `parent_feed_id` set |
247
+ | `get_feed_draft(feed_id)` | `GET /v1/feeds/{id}/draft` | 404 when no draft exists |
248
+ | `activate_feed_draft(feed_id)` | `POST /v1/feeds/{id}/draft/activate` | Returns the updated **parent** feed (draft is deleted) |
249
+ | `discard_feed_draft(feed_id)` | `DELETE /v1/feeds/{id}/draft` | Returns `None` |
250
+ | `list_feed_subscriptions(feed_id)` | Read-only view | Returns subscriptions embedded on the feed record; use `create_subscription` on the Agents mixin to attach new ones |
251
+ | `submit_feed_feedback(feed_id, *, rating, chunk_id, feed_run_id)` | `POST /v1/feeds/{id}/feedback` | `rating` is `1` (thumbs up) or `0` (thumbs down) |
252
+ | `get_feed_feedback_stats(feed_id, *, feed_run_id=None)` | `GET /v1/feeds/{id}/feedback` | Optional `feed_run_id` scopes stats to a single run |
253
+
254
+ ```python
255
+ draft = client.create_feed_draft(feed_id)
256
+ client.update_feed(draft["id"], name="new name")
257
+ client.activate_feed_draft(feed_id) # applies the draft; returns the parent
258
+
259
+ run = client.run_feed(feed_id, return_results=True)
260
+ # `results` is only populated for non-persistent feeds run with
261
+ # `return_results=True`; guard the example for safe use.
262
+ if run.get("results"):
263
+ client.submit_feed_feedback(
264
+ feed_id,
265
+ rating=1,
266
+ chunk_id=run["results"][0]["chunk_id"],
267
+ feed_run_id=run["feed_run_id"],
268
+ )
269
+ stats = client.get_feed_feedback_stats(feed_id) # org-wide for this feed
270
+ ```
271
+
272
+ All three areas are fully mirrored on `AsyncKnowledge2` under the same names.
273
+
274
+ ## Error Handling
275
+
276
+ All SDK exceptions inherit from `Knowledge2Error`.
277
+
278
+ ```python
279
+ from sdk.errors import Knowledge2Error, NotFoundError, RateLimitError
280
+
281
+ try:
282
+ client.get_corpus("missing")
283
+ except NotFoundError:
284
+ ...
285
+ except RateLimitError as exc:
286
+ print(exc.retry_after)
287
+ except Knowledge2Error as exc:
288
+ print(exc)
289
+ ```
@@ -41,6 +41,34 @@ except ImportError: # pragma: no cover - Python < 3.11
41
41
 
42
42
 
43
43
  class AsyncBaseClient:
44
+ @staticmethod
45
+ def _tightest_timeout(current: float | None, budget: float | None) -> float | None:
46
+ if budget is None:
47
+ return current
48
+ if current is None:
49
+ return budget
50
+ if current <= budget:
51
+ return current
52
+ return budget
53
+
54
+ def _job_poll_timeout(self, budget_s: float | None) -> httpx.Timeout | None:
55
+ if budget_s is None:
56
+ return None
57
+ client_timeout = getattr(self._client, "timeout", None)
58
+ if isinstance(client_timeout, httpx.Timeout):
59
+ return httpx.Timeout(
60
+ connect=self._tightest_timeout(client_timeout.connect, budget_s),
61
+ read=self._tightest_timeout(client_timeout.read, budget_s),
62
+ write=self._tightest_timeout(client_timeout.write, budget_s),
63
+ pool=self._tightest_timeout(client_timeout.pool, budget_s),
64
+ )
65
+ return httpx.Timeout(
66
+ connect=budget_s,
67
+ read=budget_s,
68
+ write=budget_s,
69
+ pool=budget_s,
70
+ )
71
+
44
72
  @staticmethod
45
73
  def _normalize_base_url(base_url: str) -> str:
46
74
  """Normalize and validate base URL input before constructing httpx.AsyncClient."""
@@ -421,7 +449,19 @@ class AsyncBaseClient:
421
449
  ) -> dict[str, Any]:
422
450
  start = time.monotonic()
423
451
  while True:
424
- job = await self._request("GET", f"/v1/jobs/{job_id}")
452
+ remaining_timeout = (
453
+ max(0.0, timeout_s - (time.monotonic() - start)) if timeout_s is not None else None
454
+ )
455
+ if timeout_s is not None and remaining_timeout == 0:
456
+ raise TimeoutError(f"Timed out waiting for job {job_id}")
457
+ try:
458
+ job = await self._request(
459
+ "GET",
460
+ f"/v1/jobs/{job_id}",
461
+ timeout=self._job_poll_timeout(remaining_timeout),
462
+ )
463
+ except APITimeoutError as exc:
464
+ raise TimeoutError(f"Timed out waiting for job {job_id}") from exc
425
465
  if not isinstance(job, dict):
426
466
  raise RuntimeError(
427
467
  f"Unexpected response polling job {job_id}: {type(job).__name__}"
@@ -434,7 +474,13 @@ class AsyncBaseClient:
434
474
  return job
435
475
  if timeout_s is not None and (time.monotonic() - start) > timeout_s:
436
476
  raise TimeoutError(f"Timed out waiting for job {job_id}")
437
- await asyncio.sleep(poll_s)
477
+ if timeout_s is not None:
478
+ remaining_timeout = max(0.0, timeout_s - (time.monotonic() - start))
479
+ if remaining_timeout == 0:
480
+ raise TimeoutError(f"Timed out waiting for job {job_id}")
481
+ await asyncio.sleep(min(poll_s, remaining_timeout))
482
+ else:
483
+ await asyncio.sleep(poll_s)
438
484
 
439
485
  # ------------------------------------------------------------------
440
486
  # Pagination