knowledge2 0.7.0__tar.gz → 0.8.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.7.0 → knowledge2-0.8.0}/CHANGELOG.md +148 -0
- {knowledge2-0.7.0/knowledge2.egg-info → knowledge2-0.8.0}/PKG-INFO +171 -4
- {knowledge2-0.7.0 → knowledge2-0.8.0}/README.md +170 -3
- {knowledge2-0.7.0 → knowledge2-0.8.0}/__init__.py +3 -0
- knowledge2-0.8.0/_embedding_limits.py +13 -0
- knowledge2-0.8.0/_sentinels.py +33 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_version.py +1 -1
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_client.py +6 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/__init__.py +6 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/agents.py +191 -28
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/corpora.py +61 -1
- knowledge2-0.8.0/async_resources/destinations.py +249 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/documents.py +8 -0
- knowledge2-0.8.0/async_resources/extraction_templates.py +196 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/feeds.py +101 -2
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/indexes.py +29 -12
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/pipelines.py +181 -4
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/projects.py +29 -0
- knowledge2-0.8.0/async_resources/quality.py +216 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/search.py +30 -2
- {knowledge2-0.7.0 → knowledge2-0.8.0}/client.py +6 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0/knowledge2.egg-info}/PKG-INFO +171 -4
- {knowledge2-0.7.0 → knowledge2-0.8.0}/knowledge2.egg-info/SOURCES.txt +28 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/_registry.py +57 -0
- knowledge2-0.8.0/models/agents.py +376 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/console.py +3 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/corpora.py +3 -0
- knowledge2-0.8.0/models/destinations.py +31 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/embeddings.py +1 -0
- knowledge2-0.8.0/models/extraction_templates.py +35 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/feeds.py +30 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/pipelines.py +83 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/projects.py +1 -0
- knowledge2-0.8.0/models/quality.py +70 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/namespaces.py +43 -6
- {knowledge2-0.7.0 → knowledge2-0.8.0}/pyproject.toml +1 -1
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/__init__.py +6 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/agents.py +243 -28
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/corpora.py +61 -1
- knowledge2-0.8.0/resources/destinations.py +269 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/documents.py +8 -0
- knowledge2-0.8.0/resources/extraction_templates.py +194 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/feeds.py +112 -2
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/indexes.py +33 -12
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/pipeline_builder.py +2 -1
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/pipelines.py +243 -2
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/projects.py +45 -0
- knowledge2-0.8.0/resources/quality.py +216 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/search.py +30 -2
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/__init__.py +57 -0
- knowledge2-0.8.0/types/agents.py +395 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/console.py +3 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/corpora.py +3 -0
- knowledge2-0.8.0/types/destinations.py +31 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/embeddings.py +1 -0
- knowledge2-0.8.0/types/extraction_templates.py +39 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/feeds.py +42 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/pipelines.py +100 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/projects.py +1 -0
- knowledge2-0.8.0/types/quality.py +80 -0
- knowledge2-0.7.0/models/agents.py +0 -147
- knowledge2-0.7.0/types/agents.py +0 -189
- {knowledge2-0.7.0 → knowledge2-0.8.0}/.github/workflows/pypi-release.yml +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/MANIFEST.in +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_async_base.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_async_paging.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_base.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_logging.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_paging.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_preview.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_raw_response.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_request_options.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_transport.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_validation.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/_validation_response.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/_mixin_base.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/a2a.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/audit.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/auth.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/console.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/generation_models.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/jobs.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/metadata.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/onboarding.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/orgs.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/async_resources/usage.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/config.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/errors.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/auth_factory.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/batch_operations.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/document_upload.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/e2e_lifecycle.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/error_handling.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/pagination.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/quickstart.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/request_options.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/retrieval_quickstart.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/examples/search.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/__init__.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/_client.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/langchain/__init__.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/langchain/retriever.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/langchain/tools.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/llamaindex/__init__.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/llamaindex/filters.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/llamaindex/retriever.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/llamaindex/tools.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/integrations/llamaindex/vector_store.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/knowledge2.egg-info/dependency_links.txt +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/knowledge2.egg-info/requires.txt +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/knowledge2.egg-info/top_level.txt +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/__init__.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/_base.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/a2a.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/audit.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/auth.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/chunks.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/common.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/documents.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/feedback.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/generation_models.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/indexes.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/jobs.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/onboarding.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/orgs.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/search.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/models/usage.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/py.typed +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/_mixin_base.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/a2a.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/audit.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/auth.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/console.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/generation_models.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/jobs.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/metadata.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/onboarding.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/orgs.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/resources/usage.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/setup.cfg +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/a2a.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/audit.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/auth.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/chunks.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/common.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/documents.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/feedback.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/generation_models.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/indexes.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/jobs.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/onboarding.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/orgs.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/search.py +0 -0
- {knowledge2-0.7.0 → knowledge2-0.8.0}/types/usage.py +0 -0
|
@@ -5,6 +5,154 @@ All notable changes to the Knowledge² Python SDK will be documented in this fil
|
|
|
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
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **`validate_responses=True` now returns typed run envelopes.** Under response
|
|
13
|
+
validation, `run_agent(..., wait=True)`'s `result` and each `list_agent_runs()`
|
|
14
|
+
item's `result` are now `AgentRunEnvelope` models (attribute access:
|
|
15
|
+
`.content`, `.fields.inferred`, `.metadata.inferred`, `.metadata.provenance`)
|
|
16
|
+
rather than raw dicts. A malformed envelope raises `ValidationError` (as with
|
|
17
|
+
every other validated model), consistent across both read paths. The default
|
|
18
|
+
(`validate_responses=False`) is unchanged — raw dicts. Also: `Provenance` now
|
|
19
|
+
types `source_refs`, and `TaskTypeInfo.unavailable_reason` is narrowed to
|
|
20
|
+
`Literal["env", "org"] | None`. (#2594)
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `client.backfill_feed(feed_id, *, start_from)` (sync) and
|
|
25
|
+
`await client.backfill_feed(...)` (async) — re-enqueue a backfill
|
|
26
|
+
on an existing feed without delete-and-recreate. Resets the feed
|
|
27
|
+
cursor and updates `Feed.start_from`; the worker re-sweeps the
|
|
28
|
+
corpus `CorpusChangeEvent` history from the supplied cutoff.
|
|
29
|
+
Returns `{job_id, start_from, previous_last_checked_seq}` (`202
|
|
30
|
+
Accepted`). Raises `ConflictError` (`code="feed_backfill_in_flight"`)
|
|
31
|
+
when a `run_feed` job for this feed is already queued or running;
|
|
32
|
+
raises `ValidationError` for future `start_from`. Per-feed-run
|
|
33
|
+
feedback lineage (#969) survives — every backfill produces a new
|
|
34
|
+
`feed_run_id`. (#2328)
|
|
35
|
+
- `schedule_cron` kwarg on `create_feed(...)` / `update_feed(...)`
|
|
36
|
+
(sync + async) — UTC five-field cron expression for calendar-aware
|
|
37
|
+
feed schedules (e.g. `"0 9 * * MON"`). Mutually exclusive with
|
|
38
|
+
`schedule_interval`; requires `persistent=True`; rejected when
|
|
39
|
+
combined with `reactive=True`. Invalid expressions surface as
|
|
40
|
+
`422 ValidationError` with the underlying croniter message. New
|
|
41
|
+
field also surfaces on `FeedResponse`. (#2305)
|
|
42
|
+
- `FeedRunResponse.generated_answer` (`dict | None`) — when
|
|
43
|
+
`execution_mode="answer"` and the retrieval step returns results,
|
|
44
|
+
the API now runs the full agent LLM via `execute_agent_task` over
|
|
45
|
+
the chunks and returns the envelope on the run response. Generation
|
|
46
|
+
failures are logged and downgrade gracefully (the response is still
|
|
47
|
+
returned without `generated_answer`). Previously the API raised
|
|
48
|
+
`501` for `answer` mode. (#2303)
|
|
49
|
+
- `client.trigger_pipeline_spec(pipeline_spec_id)` (sync) and
|
|
50
|
+
`await client.trigger_pipeline_spec(...)` (async) — manually fire a
|
|
51
|
+
pipeline run against the spec's bound feeds. Returns
|
|
52
|
+
`{pipeline_run_id, child_run_ids}`; the freshly minted UUID is
|
|
53
|
+
stamped on every follow-on job spawned during the trigger's unwind so
|
|
54
|
+
the entire causal tree shares one correlation id (queryable via the
|
|
55
|
+
forthcoming FR-16e history endpoint). New `PipelineTriggerResponse`
|
|
56
|
+
TypedDict + Pydantic model registered for `validate_responses=True`.
|
|
57
|
+
(#2298)
|
|
58
|
+
- `client.list_feed_runs(feed_id, *, limit, offset)` (sync) and
|
|
59
|
+
`await client.list_feed_runs(...)` (async) — paginated feed-run
|
|
60
|
+
history mirroring `list_agent_runs`. Returns the `run_feed` job
|
|
61
|
+
rows for the feed; the worker-internal `ingest_feed_results`
|
|
62
|
+
follow-on jobs are excluded server-side. (#2296)
|
|
63
|
+
- `client.list_project_subscriptions(project_id, *, agent_id, feed_id,
|
|
64
|
+
limit, offset)` (sync) and `await client.list_project_subscriptions(...)`
|
|
65
|
+
(async) — paginated project-scoped subscriptions admin list. Returns
|
|
66
|
+
per-row admin summary fields (derived `mode`, `destination_count`,
|
|
67
|
+
`last_delivery_status`, `last_delivery_at`) so callers can build a
|
|
68
|
+
cross-agent overview without N+1 fetches. Powers the
|
|
69
|
+
`/subscriptions` page in the console. (#2297)
|
|
70
|
+
- `client.run_agent(agent_id, *, input_chunks, wait, poll_s, timeout_s)`
|
|
71
|
+
(sync) and `await client.run_agent(...)` (async) gain
|
|
72
|
+
`wait=True` synchronous semantics. The default remains `wait=False`
|
|
73
|
+
(preserves today's 202-and-return behavior); opting in returns the
|
|
74
|
+
full job dict (`job_id` / `status` / `result` / `error_message`) once
|
|
75
|
+
the run reaches a terminal state. Raises `TimeoutError` past
|
|
76
|
+
`timeout_s` and `RuntimeError` on `failed` / `canceled` —
|
|
77
|
+
matching the exception contract of `upload_documents_batch(...,
|
|
78
|
+
wait=True)` and `optimize_indexes(..., wait=True)`. (#2343)
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
- **`task_type` and `intent_fallback` are now narrowed to `Literal` aliases.**
|
|
83
|
+
Pre-#2291 the Python SDK declared both as bare `str`, so typos like
|
|
84
|
+
`task_type="exract"` only failed at the server's 422. Post-#2291 the new
|
|
85
|
+
`TaskType` and `IntentFallback` `Literal` aliases at `sdk.types.agents`
|
|
86
|
+
(re-exported from `sdk.types`) catch typos at static-analysis time. The
|
|
87
|
+
Pydantic response models (`sdk.models.agents`) and the resource method
|
|
88
|
+
signatures (`create_agent`, `update_agent` on both sync and async clients)
|
|
89
|
+
are narrowed in lockstep. The TS SDK gets symmetric `TaskType` and
|
|
90
|
+
`IntentFallback` literal unions exported from
|
|
91
|
+
`sdk-ts/src/resources/agents.ts` and re-exported from the package root
|
|
92
|
+
(`@knowledge2/sdk`) so npm consumers can import them directly. Note: the
|
|
93
|
+
TS SDK additionally corrected a pre-existing phantom-field bug on its
|
|
94
|
+
`AgentRun` interface — the Python `AgentRunListItem` TypedDict was
|
|
95
|
+
already correct and is unchanged by this PR. (#2291)
|
|
96
|
+
- **`tool_config` is now a kind-discriminated tagged union.** Pre-#2307,
|
|
97
|
+
`tool_config` was a flat dict treated as webhook-only. Post-#2307 the
|
|
98
|
+
canonical shape is `{"kind": "webhook" | "mcp", "webhook": {...} | "mcp": {...}}`.
|
|
99
|
+
Two TypedDict envelopes are added (`ToolConfigWebhook`, `ToolConfigMcp`) plus
|
|
100
|
+
the helper alias `ToolConfigRequest` in `sdk.types.agents`. The legacy flat
|
|
101
|
+
shape (`{"webhook_url": "..."}`) is still accepted on write — the server
|
|
102
|
+
coerces it to the kinded shape via `_coerce_legacy_tool_config`. Response
|
|
103
|
+
shape (`ToolConfigResponseModel`) now carries `kind`, `webhook`, and `mcp`
|
|
104
|
+
fields; pre-#2307 top-level fields (`webhook_url`/`webhook_secret`/
|
|
105
|
+
`webhook_retries`) are tolerated for one minor version for SDK consumers
|
|
106
|
+
reading older API responses. The MCP kind is a stub — the server rejects
|
|
107
|
+
it at entitlement check with HTTP 403 + `feature_not_enabled` until the
|
|
108
|
+
enterprise rollout ships. Follow-up #2313 covers dropping the back-compat
|
|
109
|
+
paths once the engine is confirmed tightened. (#2307)
|
|
110
|
+
- **`HarvestPolicy` enum narrowed from 5 values to 3.** Legacy aliases
|
|
111
|
+
`"always"` (equivalent to `"declared-only"`) and `"declared_and_inferred"`
|
|
112
|
+
(equivalent to `"inferred"`) are no longer accepted by the API or SDK.
|
|
113
|
+
The canonical values are now `"off"` / `"inferred"` / `"declared-only"`.
|
|
114
|
+
Existing stored values are normalised by Alembic
|
|
115
|
+
`0100_harvest_policy_dedupe`. The Python `Literal` type at
|
|
116
|
+
`sdk.types.agents.HarvestPolicy` and the TS union at
|
|
117
|
+
`sdk-ts/src/resources/agents.ts::HarvestPolicy` are narrowed in lockstep.
|
|
118
|
+
Callers using the legacy aliases will see a 422 from `create_agent` /
|
|
119
|
+
`update_agent`; switch to the canonical equivalent. (#2286)
|
|
120
|
+
- `create_agent` / `async_create_agent` docstrings now recommend the
|
|
121
|
+
structured-destination flow for `task_type="notify"`: create the agent
|
|
122
|
+
without a `tool_config`, then attach destinations via
|
|
123
|
+
`create_destination(...)` against the agent's output subscription
|
|
124
|
+
(look up the subscription id via `list_subscriptions(agent_id)` first).
|
|
125
|
+
`tool_config.webhook_url` remains accepted as a generic-HTTPS shortcut
|
|
126
|
+
for backwards compat; the dispatcher fans out to both paths when both
|
|
127
|
+
are configured, so prefer one to avoid duplicate deliveries (#2241,
|
|
128
|
+
#2244).
|
|
129
|
+
|
|
130
|
+
## [0.8.0] - 2026-05-04
|
|
131
|
+
|
|
132
|
+
### Added
|
|
133
|
+
|
|
134
|
+
- `create_corpus` and `async_create_corpus` now accept `extraction_template_id`
|
|
135
|
+
and `re_extraction_policy` keyword arguments so callers can bind an extraction
|
|
136
|
+
template at create time without a follow-up PATCH.
|
|
137
|
+
- `update_corpus` and `async_update_corpus` now accept `extraction_template_id`
|
|
138
|
+
(pass `None` to unbind) and `re_extraction_policy` (`None` resets to server
|
|
139
|
+
default `"lazy"`). Both use the `NOT_GIVEN` sentinel to distinguish omit from
|
|
140
|
+
explicit `null`, preventing unintended field resets.
|
|
141
|
+
- `create_agent` and `async_create_agent` accept `declared_schema`
|
|
142
|
+
(`AgentDeclaredSchema | None`) and `harvest_policy` (`HarvestPolicy | None`).
|
|
143
|
+
- `update_agent` and `async_update_agent` accept `declared_schema` and
|
|
144
|
+
`harvest_policy` with `NOT_GIVEN` sentinel semantics.
|
|
145
|
+
- New types exported from `sdk.types`: `AgentDeclaredSchema`, `HarvestPolicy`,
|
|
146
|
+
`NotGivenOr` — use `NOT_GIVEN` from `sdk` for update-only sentinel kwargs.
|
|
147
|
+
- `NOT_GIVEN` sentinel and `_NotGiven` class are now exported from the top-level
|
|
148
|
+
`sdk` package for callers that build update payloads programmatically.
|
|
149
|
+
|
|
150
|
+
### Changed
|
|
151
|
+
|
|
152
|
+
- `CorpusResponse` (and the underlying Pydantic model) now includes
|
|
153
|
+
`extraction_template_id` and `re_extraction_policy` fields, matching the
|
|
154
|
+
server-side schema added in API v0.8 (#1988).
|
|
155
|
+
|
|
8
156
|
## [0.7.0] - 2026-04-23
|
|
9
157
|
|
|
10
158
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: knowledge2
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Python SDK for the Knowledge² retrieval platform
|
|
5
5
|
Author-email: Knowledge2 <contact@knowledge2.ai>
|
|
6
6
|
License: MIT
|
|
@@ -72,7 +72,8 @@ pip install -e ".[yaml]"
|
|
|
72
72
|
| Category | Surface |
|
|
73
73
|
|---|---|
|
|
74
74
|
| Core retrieval workflow | orgs, auth, projects, corpora, documents, indexes, search, jobs, metadata, onboarding, audit, usage, console, generation models |
|
|
75
|
-
| Enterprise capabilities | agents, feeds, pipelines, A2A |
|
|
75
|
+
| Enterprise capabilities | agents, feeds, destinations, pipelines, A2A, extraction templates |
|
|
76
|
+
| Quality & schema evolution (preview) | quality (engagement, metrics, retrieval outcomes, proposals) — proposals require the `schema_evolution_enabled` org flag; metrics and retrieval-outcomes reads are ungated |
|
|
76
77
|
|
|
77
78
|
The main docs and examples below focus on the core retrieval workflow.
|
|
78
79
|
|
|
@@ -142,6 +143,41 @@ counters that track admitted documents as processing advances. For broader
|
|
|
142
143
|
operational context during a large import, you can still pair them with
|
|
143
144
|
`get_corpus_status(...)`, `get_job(...)`, or document-level status checks.
|
|
144
145
|
|
|
146
|
+
### Indexing semantics
|
|
147
|
+
|
|
148
|
+
Uploads run in two stages and `get_corpus_status(...)` exposes both:
|
|
149
|
+
|
|
150
|
+
1. **Ingestion** — chunking + embedding writes. Tracked by `ingesting`. Once
|
|
151
|
+
`ingesting=false`, all chunks for the upload are persisted.
|
|
152
|
+
2. **Indexing** — the dense + sparse retrieval indexes are rebuilt. Tracked by
|
|
153
|
+
`indexing` and `search_status`. Search and chat reflect the new chunks once
|
|
154
|
+
`search_status="ready"` (or `retrieval_ready=true`).
|
|
155
|
+
|
|
156
|
+
When `auto_index=True` (the default), uploads automatically queue an
|
|
157
|
+
incremental index build after ingestion completes. Search keeps serving the
|
|
158
|
+
previous ready index until the new one finishes — so a doc you just uploaded
|
|
159
|
+
will not appear in search/chat between `ingesting=false` and
|
|
160
|
+
`search_status="ready"`. Poll `get_corpus_status` for `search_status="ready"`
|
|
161
|
+
(or `retrieval_ready=true`) before relying on the new chunks:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
import time
|
|
165
|
+
|
|
166
|
+
deadline = time.monotonic() + 300 # cap the wait — adjust to your SLA
|
|
167
|
+
while time.monotonic() < deadline:
|
|
168
|
+
status = client.get_corpus_status(corpus["id"])
|
|
169
|
+
if status.get("retrieval_ready") or status.get("search_status") == "ready":
|
|
170
|
+
break
|
|
171
|
+
if status.get("search_status") in ("failed", "error"):
|
|
172
|
+
raise RuntimeError(f"Index build failed: {status}")
|
|
173
|
+
time.sleep(3)
|
|
174
|
+
else:
|
|
175
|
+
raise TimeoutError("Corpus did not reach search-ready before deadline")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
If you set `auto_index=False`, call `client.sync_indexes(corpus["id"], wait=True)`
|
|
179
|
+
yourself when you are ready to publish the new chunks to search.
|
|
180
|
+
|
|
145
181
|
## Improve Retrieval Quality
|
|
146
182
|
|
|
147
183
|
```python
|
|
@@ -240,19 +276,118 @@ from sdk.integrations.llamaindex import K2LlamaIndexRetriever
|
|
|
240
276
|
|
|
241
277
|
Agents, feeds, pipelines, and A2A are available for enterprise deployments. Keep the primary examples focused on the core retrieval flow.
|
|
242
278
|
|
|
279
|
+
### Agent Declared Schema and Harvest Policy (Preview)
|
|
280
|
+
|
|
281
|
+
`create_agent` and `update_agent` accept a typed `declared_schema` envelope and a `harvest_policy` literal. The server requires `declared_schema` to wrap the JSON Schema under a `fields_schema` key — bare schemas are rejected with HTTP 422.
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
agent = client.create_agent(
|
|
285
|
+
name="Case Brief Extractor",
|
|
286
|
+
corpus_id=corpus["id"],
|
|
287
|
+
task_type="extract",
|
|
288
|
+
declared_schema={
|
|
289
|
+
"fields_schema": {
|
|
290
|
+
"type": "object",
|
|
291
|
+
"properties": {
|
|
292
|
+
"court": {"type": "string"},
|
|
293
|
+
"decision_date": {"type": "string", "format": "date"},
|
|
294
|
+
},
|
|
295
|
+
"required": ["court"],
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
harvest_policy="declared-only",
|
|
299
|
+
)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`harvest_policy` is one of `"off"`, `"inferred"`, or `"declared-only"`:
|
|
303
|
+
|
|
304
|
+
- `"off"` (default) — agent runs do not harvest any output back into the corpus.
|
|
305
|
+
- `"inferred"` — harvest the LLM-inferred fields (and, if `declared_schema` is set, the declared fields too) into the corpus on each run.
|
|
306
|
+
- `"declared-only"` — harvest only the declared fields from `declared_schema` (no inferred fields). **Requires `declared_schema` to be set**; if it's not, no structured output is requested and the agent behaves as if `harvest_policy="off"`.
|
|
307
|
+
|
|
308
|
+
The legacy aliases `"always"` (→ `"declared-only"`) and `"declared_and_inferred"` (→ `"inferred"`) were normalised by Alembic 0100_harvest_policy_dedupe and are no longer accepted as of #2286.
|
|
309
|
+
|
|
310
|
+
### Running an Agent (Preview)
|
|
311
|
+
|
|
312
|
+
`run_agent(...)` enqueues a background job and by default returns the 202 handle
|
|
313
|
+
immediately. Pass `wait=True` to block until the run reaches a terminal state
|
|
314
|
+
and receive the full job dict (including `result`):
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
# Fire-and-forget (default) — returns {"job_id": ..., "status": "queued"}.
|
|
318
|
+
run = client.run_agent(agent_id, input_chunks=[{"text": "..."}])
|
|
319
|
+
|
|
320
|
+
# Synchronous — blocks until the run reaches a terminal state.
|
|
321
|
+
job = client.run_agent(
|
|
322
|
+
agent_id,
|
|
323
|
+
input_chunks=[{"text": "..."}],
|
|
324
|
+
wait=True,
|
|
325
|
+
poll_s=2,
|
|
326
|
+
timeout_s=120,
|
|
327
|
+
)
|
|
328
|
+
envelope = job.get("result") # AgentRunEnvelope | None: content / fields / metadata
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
`timeout_s` bounds the **full** wall-clock budget from enqueue through polling
|
|
332
|
+
(a slow POST eats into the polling budget; once `timeout_s` elapses the call
|
|
333
|
+
raises `TimeoutError` regardless of which phase is in flight). `wait=True`
|
|
334
|
+
raises `RuntimeError` (carrying the job's `error_message` when present,
|
|
335
|
+
otherwise a synthesized `"Job <id> ended with status=<status>"` string) if the
|
|
336
|
+
run ends `failed` or `canceled` — same exception contract as
|
|
337
|
+
`upload_documents_batch(..., wait=True)` and `optimize_indexes(...,
|
|
338
|
+
wait=True)`. If the server returns a 202 without a `job_id` (contract
|
|
339
|
+
violation), `wait=True` raises `RuntimeError` rather than silently returning
|
|
340
|
+
the enqueue handle.
|
|
341
|
+
|
|
342
|
+
Under `client.with_raw_response.run_agent(..., wait=True)` the return value is
|
|
343
|
+
a `RawResponse` wrapping the terminal job (`status_code`, `headers`, and
|
|
344
|
+
`parsed` dict) — the trailing `GET /v1/jobs/{id}` honors raw mode so callers
|
|
345
|
+
who opt into raw responses keep getting envelopes on the wait path.
|
|
346
|
+
|
|
347
|
+
### Corpus Extraction Template (Preview)
|
|
348
|
+
|
|
349
|
+
> **API path convention:** All v1 endpoints use hyphens in the path. Use `/v1/extraction-templates`, not `/v1/extraction_templates`. The underscore variant returns 404.
|
|
350
|
+
|
|
351
|
+
`create_corpus` and `update_corpus` accept `extraction_template_id` and `re_extraction_policy` (`"lazy"` or `"eager"`). The template must be a seed template or owned by the **target corpus/project's organization** — for ordinary org-scoped keys this is the caller's org, but global-scope/admin keys can bind templates owned by the cross-org project they're targeting.
|
|
352
|
+
|
|
353
|
+
> **Typed-contract note for `declared_schema`:** `AgentDeclaredSchema` is a Python `TypedDict` with `total=False`. The server requires at least one of `fields_schema` / `metadata_schema` (an empty envelope `{}` is rejected with `422`), but `TypedDict` cannot express that constraint statically — so static type checkers will accept `declared_schema={}` even though it will fail at runtime. The TS SDK models the same constraint as a discriminated union, which **does** catch `{}` at compile time. If you need stricter Python validation, build the request through the Pydantic model layer (`sdk.models.agents.AgentDeclaredSchemaModel`) before passing it through.
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
corpus = client.create_corpus(
|
|
357
|
+
project["id"],
|
|
358
|
+
"Legal Decisions",
|
|
359
|
+
extraction_template_id="tpl_legal_v1",
|
|
360
|
+
re_extraction_policy="eager",
|
|
361
|
+
)
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`re_extraction_policy` has no effect at create time (a fresh corpus has no documents) but is persisted so that it governs re-extraction behavior when the template binding is later changed on a non-empty corpus.
|
|
365
|
+
|
|
366
|
+
To unbind a previously-bound template, pass `extraction_template_id=None` to `update_corpus(...)` — the SDK distinguishes "omit" (leave unchanged) from "explicitly null" (clear the binding) via a sentinel default. The same pattern applies to `update_agent(declared_schema=None, ...)` and `update_agent(harvest_policy=None, ...)`.
|
|
367
|
+
|
|
368
|
+
### Per-corpus intent-routing kill-switch
|
|
369
|
+
|
|
370
|
+
`create_corpus` and `update_corpus` accept `intent_routing_enabled` (`bool`), echoed back on `get_corpus`. It defaults to `True` (routing on — opt-out). Set it to `False` to disable the whole intent pre-pass + tiered-filter pipeline for the corpus, so chat/agent-run against it skips query classification and tier assignment entirely — useful for incident response and eval baselines. Omit the kwarg (leave `None`) on `update_corpus` to leave the current value unchanged. This is broader than a per-field hard-threshold disable, which only turns off the hard tier.
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
client.update_corpus(corpus["id"], intent_routing_enabled=False) # routing off
|
|
374
|
+
```
|
|
375
|
+
|
|
243
376
|
### Subscription Modes (Preview)
|
|
244
377
|
|
|
245
|
-
Agent-feed subscriptions support
|
|
378
|
+
Agent-feed subscriptions support four authoring modes on `create_subscription`, gated behind the `knowledge_agents_enabled` feature flag:
|
|
246
379
|
|
|
247
380
|
| Mode | Use | Required fields |
|
|
248
381
|
|------|-----|-----------------|
|
|
249
382
|
| `always` | Route every envelope from the feed | `feed_id`, `role` |
|
|
250
383
|
| `explicit` | Evaluate a predicate DSL against the envelope | `feed_id`, `role`, `match_spec` |
|
|
251
384
|
| `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) |
|
|
385
|
+
| `structured` | Compose filter clauses without writing raw DSL; compiled server-side into an explicit MatchSpec | `feed_id`, `role`, `structured_filters` (list of filter dicts); optional `structured_logic` (`"and"` default or `"or"`) |
|
|
252
386
|
|
|
253
387
|
The create response echoes the compiled `match_spec` and the raw `match_spec_description`, so no separate `/preview` endpoint is required:
|
|
254
388
|
|
|
255
389
|
```python
|
|
390
|
+
# Natural-language mode
|
|
256
391
|
sub = client.create_subscription(
|
|
257
392
|
agent_id,
|
|
258
393
|
feed_id=feed_id,
|
|
@@ -262,6 +397,19 @@ sub = client.create_subscription(
|
|
|
262
397
|
)
|
|
263
398
|
print(sub["match_spec"]) # compiled semantic_like predicate
|
|
264
399
|
print(sub["match_spec_description"]) # raw NL description (echoed)
|
|
400
|
+
|
|
401
|
+
# Structured filter mode
|
|
402
|
+
sub = client.create_subscription(
|
|
403
|
+
agent_id,
|
|
404
|
+
feed_id=feed_id,
|
|
405
|
+
role="output",
|
|
406
|
+
mode="structured",
|
|
407
|
+
structured_filters=[
|
|
408
|
+
{"path": "metadata.declared.claim_type", "op": "==", "value": "theft"},
|
|
409
|
+
],
|
|
410
|
+
structured_logic="and",
|
|
411
|
+
)
|
|
412
|
+
print(sub["match_spec"]) # compiled explicit predicate
|
|
265
413
|
```
|
|
266
414
|
|
|
267
415
|
### Feed Drafts, Subscriptions, and Feedback (Preview)
|
|
@@ -279,13 +427,32 @@ editing and feedback surface of the Feeds API as flat methods on `client`
|
|
|
279
427
|
| `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
428
|
| `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
429
|
| `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 |
|
|
430
|
+
| `backfill_feed(feed_id, *, start_from)` | `POST /v1/feeds/{id}/backfill` | Re-enqueue a backfill on an existing feed without delete-and-recreate. Resets the feed cursor and updates `start_from`. Returns `202` with `{job_id, start_from, previous_last_checked_seq}`; raises `409 feed_backfill_in_flight` if a run_feed job is already queued or running (#2328) |
|
|
282
431
|
|
|
283
432
|
```python
|
|
284
433
|
draft = client.create_feed_draft(feed_id)
|
|
285
434
|
client.update_feed(draft["id"], name="new name")
|
|
286
435
|
client.activate_feed_draft(feed_id) # applies the draft; returns the parent
|
|
287
436
|
|
|
437
|
+
# Cron-scheduled feed (UTC; mutually exclusive with schedule_interval) — #2305
|
|
438
|
+
client.create_feed(
|
|
439
|
+
project_id=project_id,
|
|
440
|
+
source_agent_id=agent_id,
|
|
441
|
+
name="weekly digest",
|
|
442
|
+
persistent=True,
|
|
443
|
+
target_corpus={"existing": corpus_id},
|
|
444
|
+
schedule_cron="0 9 * * MON", # Mondays at 09:00 UTC
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
# execution_mode='answer' returns an LLM-generated answer over the chunks (#2303)
|
|
288
448
|
run = client.run_feed(feed_id, return_results=True)
|
|
449
|
+
if run.get("generated_answer"):
|
|
450
|
+
answer_text = run["generated_answer"]["content"] # envelope shape
|
|
451
|
+
|
|
452
|
+
# Re-backfill in place (#2328); the response carries the previous cursor for
|
|
453
|
+
# forensic correlation in audit logs.
|
|
454
|
+
client.backfill_feed(feed_id, start_from="2026-01-01T00:00:00Z")
|
|
455
|
+
|
|
289
456
|
# `results` is only populated for non-persistent feeds run with
|
|
290
457
|
# `return_results=True`; guard the example for safe use.
|
|
291
458
|
if run.get("results"):
|
|
@@ -298,7 +465,7 @@ if run.get("results"):
|
|
|
298
465
|
stats = client.get_feed_feedback_stats(feed_id) # org-wide for this feed
|
|
299
466
|
```
|
|
300
467
|
|
|
301
|
-
All
|
|
468
|
+
All methods above are fully mirrored on `AsyncKnowledge2` under the same names.
|
|
302
469
|
|
|
303
470
|
## Error Handling
|
|
304
471
|
|
|
@@ -43,7 +43,8 @@ pip install -e ".[yaml]"
|
|
|
43
43
|
| Category | Surface |
|
|
44
44
|
|---|---|
|
|
45
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 |
|
|
46
|
+
| Enterprise capabilities | agents, feeds, destinations, pipelines, A2A, extraction templates |
|
|
47
|
+
| Quality & schema evolution (preview) | quality (engagement, metrics, retrieval outcomes, proposals) — proposals require the `schema_evolution_enabled` org flag; metrics and retrieval-outcomes reads are ungated |
|
|
47
48
|
|
|
48
49
|
The main docs and examples below focus on the core retrieval workflow.
|
|
49
50
|
|
|
@@ -113,6 +114,41 @@ counters that track admitted documents as processing advances. For broader
|
|
|
113
114
|
operational context during a large import, you can still pair them with
|
|
114
115
|
`get_corpus_status(...)`, `get_job(...)`, or document-level status checks.
|
|
115
116
|
|
|
117
|
+
### Indexing semantics
|
|
118
|
+
|
|
119
|
+
Uploads run in two stages and `get_corpus_status(...)` exposes both:
|
|
120
|
+
|
|
121
|
+
1. **Ingestion** — chunking + embedding writes. Tracked by `ingesting`. Once
|
|
122
|
+
`ingesting=false`, all chunks for the upload are persisted.
|
|
123
|
+
2. **Indexing** — the dense + sparse retrieval indexes are rebuilt. Tracked by
|
|
124
|
+
`indexing` and `search_status`. Search and chat reflect the new chunks once
|
|
125
|
+
`search_status="ready"` (or `retrieval_ready=true`).
|
|
126
|
+
|
|
127
|
+
When `auto_index=True` (the default), uploads automatically queue an
|
|
128
|
+
incremental index build after ingestion completes. Search keeps serving the
|
|
129
|
+
previous ready index until the new one finishes — so a doc you just uploaded
|
|
130
|
+
will not appear in search/chat between `ingesting=false` and
|
|
131
|
+
`search_status="ready"`. Poll `get_corpus_status` for `search_status="ready"`
|
|
132
|
+
(or `retrieval_ready=true`) before relying on the new chunks:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import time
|
|
136
|
+
|
|
137
|
+
deadline = time.monotonic() + 300 # cap the wait — adjust to your SLA
|
|
138
|
+
while time.monotonic() < deadline:
|
|
139
|
+
status = client.get_corpus_status(corpus["id"])
|
|
140
|
+
if status.get("retrieval_ready") or status.get("search_status") == "ready":
|
|
141
|
+
break
|
|
142
|
+
if status.get("search_status") in ("failed", "error"):
|
|
143
|
+
raise RuntimeError(f"Index build failed: {status}")
|
|
144
|
+
time.sleep(3)
|
|
145
|
+
else:
|
|
146
|
+
raise TimeoutError("Corpus did not reach search-ready before deadline")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
If you set `auto_index=False`, call `client.sync_indexes(corpus["id"], wait=True)`
|
|
150
|
+
yourself when you are ready to publish the new chunks to search.
|
|
151
|
+
|
|
116
152
|
## Improve Retrieval Quality
|
|
117
153
|
|
|
118
154
|
```python
|
|
@@ -211,19 +247,118 @@ from sdk.integrations.llamaindex import K2LlamaIndexRetriever
|
|
|
211
247
|
|
|
212
248
|
Agents, feeds, pipelines, and A2A are available for enterprise deployments. Keep the primary examples focused on the core retrieval flow.
|
|
213
249
|
|
|
250
|
+
### Agent Declared Schema and Harvest Policy (Preview)
|
|
251
|
+
|
|
252
|
+
`create_agent` and `update_agent` accept a typed `declared_schema` envelope and a `harvest_policy` literal. The server requires `declared_schema` to wrap the JSON Schema under a `fields_schema` key — bare schemas are rejected with HTTP 422.
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
agent = client.create_agent(
|
|
256
|
+
name="Case Brief Extractor",
|
|
257
|
+
corpus_id=corpus["id"],
|
|
258
|
+
task_type="extract",
|
|
259
|
+
declared_schema={
|
|
260
|
+
"fields_schema": {
|
|
261
|
+
"type": "object",
|
|
262
|
+
"properties": {
|
|
263
|
+
"court": {"type": "string"},
|
|
264
|
+
"decision_date": {"type": "string", "format": "date"},
|
|
265
|
+
},
|
|
266
|
+
"required": ["court"],
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
harvest_policy="declared-only",
|
|
270
|
+
)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`harvest_policy` is one of `"off"`, `"inferred"`, or `"declared-only"`:
|
|
274
|
+
|
|
275
|
+
- `"off"` (default) — agent runs do not harvest any output back into the corpus.
|
|
276
|
+
- `"inferred"` — harvest the LLM-inferred fields (and, if `declared_schema` is set, the declared fields too) into the corpus on each run.
|
|
277
|
+
- `"declared-only"` — harvest only the declared fields from `declared_schema` (no inferred fields). **Requires `declared_schema` to be set**; if it's not, no structured output is requested and the agent behaves as if `harvest_policy="off"`.
|
|
278
|
+
|
|
279
|
+
The legacy aliases `"always"` (→ `"declared-only"`) and `"declared_and_inferred"` (→ `"inferred"`) were normalised by Alembic 0100_harvest_policy_dedupe and are no longer accepted as of #2286.
|
|
280
|
+
|
|
281
|
+
### Running an Agent (Preview)
|
|
282
|
+
|
|
283
|
+
`run_agent(...)` enqueues a background job and by default returns the 202 handle
|
|
284
|
+
immediately. Pass `wait=True` to block until the run reaches a terminal state
|
|
285
|
+
and receive the full job dict (including `result`):
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
# Fire-and-forget (default) — returns {"job_id": ..., "status": "queued"}.
|
|
289
|
+
run = client.run_agent(agent_id, input_chunks=[{"text": "..."}])
|
|
290
|
+
|
|
291
|
+
# Synchronous — blocks until the run reaches a terminal state.
|
|
292
|
+
job = client.run_agent(
|
|
293
|
+
agent_id,
|
|
294
|
+
input_chunks=[{"text": "..."}],
|
|
295
|
+
wait=True,
|
|
296
|
+
poll_s=2,
|
|
297
|
+
timeout_s=120,
|
|
298
|
+
)
|
|
299
|
+
envelope = job.get("result") # AgentRunEnvelope | None: content / fields / metadata
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`timeout_s` bounds the **full** wall-clock budget from enqueue through polling
|
|
303
|
+
(a slow POST eats into the polling budget; once `timeout_s` elapses the call
|
|
304
|
+
raises `TimeoutError` regardless of which phase is in flight). `wait=True`
|
|
305
|
+
raises `RuntimeError` (carrying the job's `error_message` when present,
|
|
306
|
+
otherwise a synthesized `"Job <id> ended with status=<status>"` string) if the
|
|
307
|
+
run ends `failed` or `canceled` — same exception contract as
|
|
308
|
+
`upload_documents_batch(..., wait=True)` and `optimize_indexes(...,
|
|
309
|
+
wait=True)`. If the server returns a 202 without a `job_id` (contract
|
|
310
|
+
violation), `wait=True` raises `RuntimeError` rather than silently returning
|
|
311
|
+
the enqueue handle.
|
|
312
|
+
|
|
313
|
+
Under `client.with_raw_response.run_agent(..., wait=True)` the return value is
|
|
314
|
+
a `RawResponse` wrapping the terminal job (`status_code`, `headers`, and
|
|
315
|
+
`parsed` dict) — the trailing `GET /v1/jobs/{id}` honors raw mode so callers
|
|
316
|
+
who opt into raw responses keep getting envelopes on the wait path.
|
|
317
|
+
|
|
318
|
+
### Corpus Extraction Template (Preview)
|
|
319
|
+
|
|
320
|
+
> **API path convention:** All v1 endpoints use hyphens in the path. Use `/v1/extraction-templates`, not `/v1/extraction_templates`. The underscore variant returns 404.
|
|
321
|
+
|
|
322
|
+
`create_corpus` and `update_corpus` accept `extraction_template_id` and `re_extraction_policy` (`"lazy"` or `"eager"`). The template must be a seed template or owned by the **target corpus/project's organization** — for ordinary org-scoped keys this is the caller's org, but global-scope/admin keys can bind templates owned by the cross-org project they're targeting.
|
|
323
|
+
|
|
324
|
+
> **Typed-contract note for `declared_schema`:** `AgentDeclaredSchema` is a Python `TypedDict` with `total=False`. The server requires at least one of `fields_schema` / `metadata_schema` (an empty envelope `{}` is rejected with `422`), but `TypedDict` cannot express that constraint statically — so static type checkers will accept `declared_schema={}` even though it will fail at runtime. The TS SDK models the same constraint as a discriminated union, which **does** catch `{}` at compile time. If you need stricter Python validation, build the request through the Pydantic model layer (`sdk.models.agents.AgentDeclaredSchemaModel`) before passing it through.
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
corpus = client.create_corpus(
|
|
328
|
+
project["id"],
|
|
329
|
+
"Legal Decisions",
|
|
330
|
+
extraction_template_id="tpl_legal_v1",
|
|
331
|
+
re_extraction_policy="eager",
|
|
332
|
+
)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
`re_extraction_policy` has no effect at create time (a fresh corpus has no documents) but is persisted so that it governs re-extraction behavior when the template binding is later changed on a non-empty corpus.
|
|
336
|
+
|
|
337
|
+
To unbind a previously-bound template, pass `extraction_template_id=None` to `update_corpus(...)` — the SDK distinguishes "omit" (leave unchanged) from "explicitly null" (clear the binding) via a sentinel default. The same pattern applies to `update_agent(declared_schema=None, ...)` and `update_agent(harvest_policy=None, ...)`.
|
|
338
|
+
|
|
339
|
+
### Per-corpus intent-routing kill-switch
|
|
340
|
+
|
|
341
|
+
`create_corpus` and `update_corpus` accept `intent_routing_enabled` (`bool`), echoed back on `get_corpus`. It defaults to `True` (routing on — opt-out). Set it to `False` to disable the whole intent pre-pass + tiered-filter pipeline for the corpus, so chat/agent-run against it skips query classification and tier assignment entirely — useful for incident response and eval baselines. Omit the kwarg (leave `None`) on `update_corpus` to leave the current value unchanged. This is broader than a per-field hard-threshold disable, which only turns off the hard tier.
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
client.update_corpus(corpus["id"], intent_routing_enabled=False) # routing off
|
|
345
|
+
```
|
|
346
|
+
|
|
214
347
|
### Subscription Modes (Preview)
|
|
215
348
|
|
|
216
|
-
Agent-feed subscriptions support
|
|
349
|
+
Agent-feed subscriptions support four authoring modes on `create_subscription`, gated behind the `knowledge_agents_enabled` feature flag:
|
|
217
350
|
|
|
218
351
|
| Mode | Use | Required fields |
|
|
219
352
|
|------|-----|-----------------|
|
|
220
353
|
| `always` | Route every envelope from the feed | `feed_id`, `role` |
|
|
221
354
|
| `explicit` | Evaluate a predicate DSL against the envelope | `feed_id`, `role`, `match_spec` |
|
|
222
355
|
| `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) |
|
|
356
|
+
| `structured` | Compose filter clauses without writing raw DSL; compiled server-side into an explicit MatchSpec | `feed_id`, `role`, `structured_filters` (list of filter dicts); optional `structured_logic` (`"and"` default or `"or"`) |
|
|
223
357
|
|
|
224
358
|
The create response echoes the compiled `match_spec` and the raw `match_spec_description`, so no separate `/preview` endpoint is required:
|
|
225
359
|
|
|
226
360
|
```python
|
|
361
|
+
# Natural-language mode
|
|
227
362
|
sub = client.create_subscription(
|
|
228
363
|
agent_id,
|
|
229
364
|
feed_id=feed_id,
|
|
@@ -233,6 +368,19 @@ sub = client.create_subscription(
|
|
|
233
368
|
)
|
|
234
369
|
print(sub["match_spec"]) # compiled semantic_like predicate
|
|
235
370
|
print(sub["match_spec_description"]) # raw NL description (echoed)
|
|
371
|
+
|
|
372
|
+
# Structured filter mode
|
|
373
|
+
sub = client.create_subscription(
|
|
374
|
+
agent_id,
|
|
375
|
+
feed_id=feed_id,
|
|
376
|
+
role="output",
|
|
377
|
+
mode="structured",
|
|
378
|
+
structured_filters=[
|
|
379
|
+
{"path": "metadata.declared.claim_type", "op": "==", "value": "theft"},
|
|
380
|
+
],
|
|
381
|
+
structured_logic="and",
|
|
382
|
+
)
|
|
383
|
+
print(sub["match_spec"]) # compiled explicit predicate
|
|
236
384
|
```
|
|
237
385
|
|
|
238
386
|
### Feed Drafts, Subscriptions, and Feedback (Preview)
|
|
@@ -250,13 +398,32 @@ editing and feedback surface of the Feeds API as flat methods on `client`
|
|
|
250
398
|
| `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
399
|
| `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
400
|
| `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 |
|
|
401
|
+
| `backfill_feed(feed_id, *, start_from)` | `POST /v1/feeds/{id}/backfill` | Re-enqueue a backfill on an existing feed without delete-and-recreate. Resets the feed cursor and updates `start_from`. Returns `202` with `{job_id, start_from, previous_last_checked_seq}`; raises `409 feed_backfill_in_flight` if a run_feed job is already queued or running (#2328) |
|
|
253
402
|
|
|
254
403
|
```python
|
|
255
404
|
draft = client.create_feed_draft(feed_id)
|
|
256
405
|
client.update_feed(draft["id"], name="new name")
|
|
257
406
|
client.activate_feed_draft(feed_id) # applies the draft; returns the parent
|
|
258
407
|
|
|
408
|
+
# Cron-scheduled feed (UTC; mutually exclusive with schedule_interval) — #2305
|
|
409
|
+
client.create_feed(
|
|
410
|
+
project_id=project_id,
|
|
411
|
+
source_agent_id=agent_id,
|
|
412
|
+
name="weekly digest",
|
|
413
|
+
persistent=True,
|
|
414
|
+
target_corpus={"existing": corpus_id},
|
|
415
|
+
schedule_cron="0 9 * * MON", # Mondays at 09:00 UTC
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# execution_mode='answer' returns an LLM-generated answer over the chunks (#2303)
|
|
259
419
|
run = client.run_feed(feed_id, return_results=True)
|
|
420
|
+
if run.get("generated_answer"):
|
|
421
|
+
answer_text = run["generated_answer"]["content"] # envelope shape
|
|
422
|
+
|
|
423
|
+
# Re-backfill in place (#2328); the response carries the previous cursor for
|
|
424
|
+
# forensic correlation in audit logs.
|
|
425
|
+
client.backfill_feed(feed_id, start_from="2026-01-01T00:00:00Z")
|
|
426
|
+
|
|
260
427
|
# `results` is only populated for non-persistent feeds run with
|
|
261
428
|
# `return_results=True`; guard the example for safe use.
|
|
262
429
|
if run.get("results"):
|
|
@@ -269,7 +436,7 @@ if run.get("results"):
|
|
|
269
436
|
stats = client.get_feed_feedback_stats(feed_id) # org-wide for this feed
|
|
270
437
|
```
|
|
271
438
|
|
|
272
|
-
All
|
|
439
|
+
All methods above are fully mirrored on `AsyncKnowledge2` under the same names.
|
|
273
440
|
|
|
274
441
|
## Error Handling
|
|
275
442
|
|
|
@@ -8,6 +8,7 @@ from ._logging import set_debug
|
|
|
8
8
|
from ._paging import Page, SyncPager
|
|
9
9
|
from ._raw_response import RawResponse
|
|
10
10
|
from ._request_options import RequestOptions
|
|
11
|
+
from ._sentinels import NOT_GIVEN, NotGivenOr
|
|
11
12
|
from .async_client import AsyncKnowledge2
|
|
12
13
|
from .client import Knowledge2, Knowledge2Validated
|
|
13
14
|
from .errors import (
|
|
@@ -42,6 +43,7 @@ def __getattr__(name: str):
|
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
__all__ = [
|
|
46
|
+
"NOT_GIVEN",
|
|
45
47
|
"APIConnectionError",
|
|
46
48
|
"APIError",
|
|
47
49
|
"APITimeoutError",
|
|
@@ -59,6 +61,7 @@ __all__ = [
|
|
|
59
61
|
"Knowledge2Error",
|
|
60
62
|
"Knowledge2Validated",
|
|
61
63
|
"NotFoundError",
|
|
64
|
+
"NotGivenOr",
|
|
62
65
|
"Page",
|
|
63
66
|
"PermissionDeniedError",
|
|
64
67
|
"PipelineBuilder",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def validate_chunking_config(config: Mapping[str, Any] | None) -> None:
|
|
8
|
+
if not config:
|
|
9
|
+
return
|
|
10
|
+
chunk_size = config.get("chunk_size")
|
|
11
|
+
overlap = config.get("overlap")
|
|
12
|
+
if chunk_size is not None and overlap is not None and int(overlap) >= int(chunk_size):
|
|
13
|
+
raise ValueError("chunking overlap must be smaller than chunk_size")
|