cortexdbai 0.4.3__tar.gz → 0.6.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.
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/.gitignore +3 -3
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/PKG-INFO +7 -1
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/README.md +6 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/__init__.py +71 -71
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/v1/client.py +333 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/pyproject.toml +186 -186
- cortexdbai-0.6.0/tests/test_conflicts.py +310 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ag2/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ag2/agent.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ag2/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/agno/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/agno/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/agno/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/autogen/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/autogen/agent.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/autogen/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/beeai/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/beeai/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/beeai/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/camel/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/camel/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/camel/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/controlflow/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/controlflow/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/crewai/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/crewai/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/crewai/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/deepinfra/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/deepinfra/config.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/deepinfra/embeddings.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/deepinfra/openai_compat.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dify/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dify/manifest.yaml +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dify/tool.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dspy/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dspy/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dspy/retriever.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/fireworks/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/fireworks/config.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/fireworks/embeddings.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/fireworks/openai_compat.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/flowise/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/flowise/node.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/google_adk/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/google_adk/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/groq/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/groq/config.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/groq/embeddings.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/groq/openai_compat.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/hermes/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/hermes/provider.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/instructor/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/instructor/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langchain/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langchain/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langchain/retriever.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langchain/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langgraph/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langgraph/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/letta/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/letta/storage.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/llamaindex/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/llamaindex/memory.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/nemo_guardrails/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/nemo_guardrails/actions.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/nemo_guardrails/knowledge_base.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ollama/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ollama/config.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ollama/embeddings.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/openai_agents/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/openai_agents/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/prefect/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/prefect/blocks.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/prefect/tasks.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/pydanticai/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/pydanticai/dependencies.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/pydanticai/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/smolagents/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/smolagents/tools.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/temporal/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/temporal/activities.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/temporal/interceptor.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/together/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/together/config.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/together/embeddings.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/together/openai_compat.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/vllm/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/vllm/config.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/vllm/embeddings.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/vllm/openai_compat.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/py.typed +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/v1/__init__.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/v1/exceptions.py +0 -0
- {cortexdbai-0.4.3 → cortexdbai-0.6.0}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cortexdbai
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: The Long-Term Memory Layer for AI Systems
|
|
5
5
|
Project-URL: Homepage, https://cortexdb.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.cortexdb.ai
|
|
@@ -173,6 +173,12 @@ print(c.recall(c.actor, query="what did I say?")["context_block"])
|
|
|
173
173
|
c.experience("ws:demo", text="Deployed payments-service v3.1 to prod.")
|
|
174
174
|
c.experience("ws:demo", text="Rolled back auth-gateway.", wait="indexed")
|
|
175
175
|
|
|
176
|
+
# Large content: wait="captured" returns at the durable accept point (~10ms);
|
|
177
|
+
# poll write_status to see indexing progress. After a client timeout,
|
|
178
|
+
# found=False or status="failed" both mean re-sending is safe (re-processes).
|
|
179
|
+
c.experience("ws:demo", text=big_transcript, idempotency_key="k1", wait="captured")
|
|
180
|
+
c.write_status(idempotency_key="k1") # {found, status, stages_completed, ...}
|
|
181
|
+
|
|
176
182
|
# Recall — returns a StratifiedPack { context_block, layers, provenance }.
|
|
177
183
|
pack = c.recall("ws:demo", query="recent deploys", view="holistic")
|
|
178
184
|
|
|
@@ -60,6 +60,12 @@ print(c.recall(c.actor, query="what did I say?")["context_block"])
|
|
|
60
60
|
c.experience("ws:demo", text="Deployed payments-service v3.1 to prod.")
|
|
61
61
|
c.experience("ws:demo", text="Rolled back auth-gateway.", wait="indexed")
|
|
62
62
|
|
|
63
|
+
# Large content: wait="captured" returns at the durable accept point (~10ms);
|
|
64
|
+
# poll write_status to see indexing progress. After a client timeout,
|
|
65
|
+
# found=False or status="failed" both mean re-sending is safe (re-processes).
|
|
66
|
+
c.experience("ws:demo", text=big_transcript, idempotency_key="k1", wait="captured")
|
|
67
|
+
c.write_status(idempotency_key="k1") # {found, status, stages_completed, ...}
|
|
68
|
+
|
|
63
69
|
# Recall — returns a StratifiedPack { context_block, layers, provenance }.
|
|
64
70
|
pack = c.recall("ws:demo", query="recent deploys", view="holistic")
|
|
65
71
|
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
"""CortexDB Python SDK — the long-term memory layer for AI systems.
|
|
2
|
-
|
|
3
|
-
The SDK is a thin HTTP client over the v1 API surface that cortexdb
|
|
4
|
-
exposes by default on port 3141. The top-level :class:`Cortex` class
|
|
5
|
-
(alias :class:`V1Client`) is what you should reach for.
|
|
6
|
-
|
|
7
|
-
Quick start::
|
|
8
|
-
|
|
9
|
-
from cortexdb import Cortex
|
|
10
|
-
|
|
11
|
-
# Local docker run with no auth (CORTEX_API_KEY unset on the server):
|
|
12
|
-
with Cortex("http://localhost:3141") as c:
|
|
13
|
-
c.experience("ws:demo", text="Priya at Acme signed for 200 seats.")
|
|
14
|
-
pack = c.recall("ws:demo", query="How many seats did Acme sign for?")
|
|
15
|
-
print(pack)
|
|
16
|
-
|
|
17
|
-
# Hosted / multi-tenant deployment with PASETO bearer + actor header:
|
|
18
|
-
with Cortex(
|
|
19
|
-
"https://api-v1.cortexdb.ai",
|
|
20
|
-
actor="user:alice",
|
|
21
|
-
bearer="eyJ...",
|
|
22
|
-
) as c:
|
|
23
|
-
out = c.answer(
|
|
24
|
-
"org:initech/user:alice",
|
|
25
|
-
question="What did Alice say about coffee?",
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
Versions ≤ 0.2.x exposed a separate ``cortexdb.Cortex`` class targeting
|
|
29
|
-
the legacy ``/v1/remember`` endpoints. That surface was retired in 0.3.0
|
|
30
|
-
because the cortexdb binary's public port now only serves the v1 API;
|
|
31
|
-
the legacy endpoints survive only as an internal bench-compat helper.
|
|
32
|
-
If you need them, pin ``cortexdbai<0.3``.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
from cortexdb.v1 import V1Client, AsyncV1Client
|
|
36
|
-
from cortexdb.v1.exceptions import (
|
|
37
|
-
V1APIError,
|
|
38
|
-
V1AuthError,
|
|
39
|
-
V1ConnectionError,
|
|
40
|
-
V1Error,
|
|
41
|
-
V1NotConfiguredError,
|
|
42
|
-
V1PolicyDeniedError,
|
|
43
|
-
V1RateLimitError,
|
|
44
|
-
V1TimeoutError,
|
|
45
|
-
)
|
|
46
|
-
from cortexdb import v1 # the explicit submodule namespace
|
|
47
|
-
|
|
48
|
-
# Ergonomic top-level aliases. New code can write either form.
|
|
49
|
-
Cortex = V1Client
|
|
50
|
-
AsyncCortex = AsyncV1Client
|
|
51
|
-
|
|
52
|
-
__version__ = "0.
|
|
53
|
-
__all__ = [
|
|
54
|
-
# Clients (preferred top-level names)
|
|
55
|
-
"Cortex",
|
|
56
|
-
"AsyncCortex",
|
|
57
|
-
# Explicit v1 names (identical classes, kept for code that pinned them)
|
|
58
|
-
"V1Client",
|
|
59
|
-
"AsyncV1Client",
|
|
60
|
-
# Submodule
|
|
61
|
-
"v1",
|
|
62
|
-
# Exceptions
|
|
63
|
-
"V1Error",
|
|
64
|
-
"V1APIError",
|
|
65
|
-
"V1AuthError",
|
|
66
|
-
"V1ConnectionError",
|
|
67
|
-
"V1TimeoutError",
|
|
68
|
-
"V1RateLimitError",
|
|
69
|
-
"V1NotConfiguredError",
|
|
70
|
-
"V1PolicyDeniedError",
|
|
71
|
-
]
|
|
1
|
+
"""CortexDB Python SDK — the long-term memory layer for AI systems.
|
|
2
|
+
|
|
3
|
+
The SDK is a thin HTTP client over the v1 API surface that cortexdb
|
|
4
|
+
exposes by default on port 3141. The top-level :class:`Cortex` class
|
|
5
|
+
(alias :class:`V1Client`) is what you should reach for.
|
|
6
|
+
|
|
7
|
+
Quick start::
|
|
8
|
+
|
|
9
|
+
from cortexdb import Cortex
|
|
10
|
+
|
|
11
|
+
# Local docker run with no auth (CORTEX_API_KEY unset on the server):
|
|
12
|
+
with Cortex("http://localhost:3141") as c:
|
|
13
|
+
c.experience("ws:demo", text="Priya at Acme signed for 200 seats.")
|
|
14
|
+
pack = c.recall("ws:demo", query="How many seats did Acme sign for?")
|
|
15
|
+
print(pack)
|
|
16
|
+
|
|
17
|
+
# Hosted / multi-tenant deployment with PASETO bearer + actor header:
|
|
18
|
+
with Cortex(
|
|
19
|
+
"https://api-v1.cortexdb.ai",
|
|
20
|
+
actor="user:alice",
|
|
21
|
+
bearer="eyJ...",
|
|
22
|
+
) as c:
|
|
23
|
+
out = c.answer(
|
|
24
|
+
"org:initech/user:alice",
|
|
25
|
+
question="What did Alice say about coffee?",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
Versions ≤ 0.2.x exposed a separate ``cortexdb.Cortex`` class targeting
|
|
29
|
+
the legacy ``/v1/remember`` endpoints. That surface was retired in 0.3.0
|
|
30
|
+
because the cortexdb binary's public port now only serves the v1 API;
|
|
31
|
+
the legacy endpoints survive only as an internal bench-compat helper.
|
|
32
|
+
If you need them, pin ``cortexdbai<0.3``.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from cortexdb.v1 import V1Client, AsyncV1Client
|
|
36
|
+
from cortexdb.v1.exceptions import (
|
|
37
|
+
V1APIError,
|
|
38
|
+
V1AuthError,
|
|
39
|
+
V1ConnectionError,
|
|
40
|
+
V1Error,
|
|
41
|
+
V1NotConfiguredError,
|
|
42
|
+
V1PolicyDeniedError,
|
|
43
|
+
V1RateLimitError,
|
|
44
|
+
V1TimeoutError,
|
|
45
|
+
)
|
|
46
|
+
from cortexdb import v1 # the explicit submodule namespace
|
|
47
|
+
|
|
48
|
+
# Ergonomic top-level aliases. New code can write either form.
|
|
49
|
+
Cortex = V1Client
|
|
50
|
+
AsyncCortex = AsyncV1Client
|
|
51
|
+
|
|
52
|
+
__version__ = "0.6.0"
|
|
53
|
+
__all__ = [
|
|
54
|
+
# Clients (preferred top-level names)
|
|
55
|
+
"Cortex",
|
|
56
|
+
"AsyncCortex",
|
|
57
|
+
# Explicit v1 names (identical classes, kept for code that pinned them)
|
|
58
|
+
"V1Client",
|
|
59
|
+
"AsyncV1Client",
|
|
60
|
+
# Submodule
|
|
61
|
+
"v1",
|
|
62
|
+
# Exceptions
|
|
63
|
+
"V1Error",
|
|
64
|
+
"V1APIError",
|
|
65
|
+
"V1AuthError",
|
|
66
|
+
"V1ConnectionError",
|
|
67
|
+
"V1TimeoutError",
|
|
68
|
+
"V1RateLimitError",
|
|
69
|
+
"V1NotConfiguredError",
|
|
70
|
+
"V1PolicyDeniedError",
|
|
71
|
+
]
|
|
@@ -148,6 +148,99 @@ def _envelope(
|
|
|
148
148
|
return env
|
|
149
149
|
|
|
150
150
|
|
|
151
|
+
# Per-action argument contract for POST /v1/conflicts/{id}/resolve. The
|
|
152
|
+
# server rejects unknown shapes with a 422; validating client-side gives the
|
|
153
|
+
# caller a `ValueError` naming the exact problem before any bytes move.
|
|
154
|
+
_RESOLVE_ACTIONS: dict = {
|
|
155
|
+
"pick": {"required": ("winner",), "optional": ("reason",)},
|
|
156
|
+
"split": {"required": ("boundary", "earlier", "later"), "optional": ()},
|
|
157
|
+
"new_information": {"required": ("record",), "optional": ()},
|
|
158
|
+
"dismiss": {"required": (), "optional": ("reason",)},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _resolve_conflict_body(action: str, kwargs: Mapping[str, Any]) -> dict:
|
|
163
|
+
"""Validate + build the resolve-conflict request body.
|
|
164
|
+
|
|
165
|
+
``kwargs`` maps every action-specific parameter name to its value (or
|
|
166
|
+
``None`` when the caller didn't pass it). Raises :class:`ValueError` on
|
|
167
|
+
an unknown action, a missing required parameter, or a parameter that
|
|
168
|
+
doesn't belong to the chosen action.
|
|
169
|
+
"""
|
|
170
|
+
spec = _RESOLVE_ACTIONS.get(action)
|
|
171
|
+
if spec is None:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"unknown resolve action {action!r}; expected one of "
|
|
174
|
+
f"{sorted(_RESOLVE_ACTIONS)}"
|
|
175
|
+
)
|
|
176
|
+
allowed = set(spec["required"]) | set(spec["optional"])
|
|
177
|
+
provided = {k for k, v in kwargs.items() if v is not None}
|
|
178
|
+
missing = [k for k in spec["required"] if k not in provided]
|
|
179
|
+
if missing:
|
|
180
|
+
raise ValueError(f"action {action!r} requires {missing}")
|
|
181
|
+
extraneous = sorted(provided - allowed)
|
|
182
|
+
if extraneous:
|
|
183
|
+
raise ValueError(
|
|
184
|
+
f"action {action!r} does not accept {extraneous} "
|
|
185
|
+
f"(allowed: {sorted(allowed)})"
|
|
186
|
+
)
|
|
187
|
+
body: dict = {"action": action}
|
|
188
|
+
for k in allowed:
|
|
189
|
+
v = kwargs.get(k)
|
|
190
|
+
if v is not None:
|
|
191
|
+
body[k] = v
|
|
192
|
+
if action in ("pick", "dismiss") and "reason" not in body:
|
|
193
|
+
body["reason"] = ""
|
|
194
|
+
return body
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _range_expr(value: Any) -> str:
|
|
198
|
+
"""Normalize a bi-temporal range to the server's ``start..end`` string.
|
|
199
|
+
|
|
200
|
+
Accepts a preformatted ``"start..end"`` string or a 2-item
|
|
201
|
+
``(start, end)`` sequence of date expressions.
|
|
202
|
+
"""
|
|
203
|
+
if isinstance(value, str):
|
|
204
|
+
return value
|
|
205
|
+
try:
|
|
206
|
+
start, end = value
|
|
207
|
+
except (TypeError, ValueError) as e:
|
|
208
|
+
raise ValueError(
|
|
209
|
+
"range must be a 'start..end' string or a (start, end) pair"
|
|
210
|
+
) from e
|
|
211
|
+
return f"{start}..{end}"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _claims_params(
|
|
215
|
+
scope: str,
|
|
216
|
+
*,
|
|
217
|
+
subject: Optional[str],
|
|
218
|
+
predicate: Optional[str],
|
|
219
|
+
valid_as_of: Optional[str],
|
|
220
|
+
known_as_of: Optional[str],
|
|
221
|
+
valid_during: Optional[Any],
|
|
222
|
+
recorded_during: Optional[Any],
|
|
223
|
+
limit: Optional[int],
|
|
224
|
+
) -> dict:
|
|
225
|
+
"""Build the query params for ``GET /v1/claims``."""
|
|
226
|
+
params: dict = {"scope": scope}
|
|
227
|
+
if subject:
|
|
228
|
+
params["subject"] = subject
|
|
229
|
+
if predicate:
|
|
230
|
+
params["predicate"] = predicate
|
|
231
|
+
if valid_as_of:
|
|
232
|
+
params["valid_as_of"] = valid_as_of
|
|
233
|
+
if known_as_of:
|
|
234
|
+
params["known_as_of"] = known_as_of
|
|
235
|
+
if valid_during is not None:
|
|
236
|
+
params["valid_during"] = _range_expr(valid_during)
|
|
237
|
+
if recorded_during is not None:
|
|
238
|
+
params["recorded_during"] = _range_expr(recorded_during)
|
|
239
|
+
if limit is not None:
|
|
240
|
+
params["limit"] = limit
|
|
241
|
+
return params
|
|
242
|
+
|
|
243
|
+
|
|
151
244
|
# ── Synchronous client ──────────────────────────────────────────────────────
|
|
152
245
|
|
|
153
246
|
|
|
@@ -291,6 +384,28 @@ class V1Client:
|
|
|
291
384
|
path = "/v1/experience" + (f"?wait={wait}" if wait else "")
|
|
292
385
|
return self._post(path, env)
|
|
293
386
|
|
|
387
|
+
def write_status(
|
|
388
|
+
self,
|
|
389
|
+
*,
|
|
390
|
+
idempotency_key: Optional[str] = None,
|
|
391
|
+
event_id: Optional[str] = None,
|
|
392
|
+
) -> dict:
|
|
393
|
+
"""``GET /v1/experience/status`` — did a write land, and how far has
|
|
394
|
+
it progressed? The safe resume path after a client-side timeout, and
|
|
395
|
+
the poll target after ``experience(..., wait="captured")``.
|
|
396
|
+
|
|
397
|
+
Returns ``{found, event_id, status, stages_completed, note}`` where
|
|
398
|
+
``status`` is ``"captured"`` / ``"indexed"`` / ``"failed"``.
|
|
399
|
+
``found: False`` (or ``status: "failed"``) means re-sending the same
|
|
400
|
+
write with the same ``idempotency_key`` is safe — it re-processes
|
|
401
|
+
rather than replaying."""
|
|
402
|
+
params: dict = {}
|
|
403
|
+
if idempotency_key is not None:
|
|
404
|
+
params["idempotency_key"] = idempotency_key
|
|
405
|
+
if event_id is not None:
|
|
406
|
+
params["event_id"] = event_id
|
|
407
|
+
return self._get("/v1/experience/status", params=params)
|
|
408
|
+
|
|
294
409
|
def upload_blob(
|
|
295
410
|
self, data: bytes, content_type: str = "application/octet-stream"
|
|
296
411
|
) -> dict:
|
|
@@ -492,6 +607,130 @@ class V1Client:
|
|
|
492
607
|
body: dict = {"scope": scope, "topics": list(topics) if topics else []}
|
|
493
608
|
return self._post("/v1/understanding/synthesize", body)
|
|
494
609
|
|
|
610
|
+
# ── conflicts + bi-temporal claims ────────────────────────────────
|
|
611
|
+
#
|
|
612
|
+
# Rendering note: RecordView boundaries carry `valid_from_basis` /
|
|
613
|
+
# `valid_to_basis` ("stated" | "observed"). An "observed" boundary is
|
|
614
|
+
# only a knowledge bound — present it as "by <date>", never
|
|
615
|
+
# "on/since <date>".
|
|
616
|
+
|
|
617
|
+
def list_conflicts(
|
|
618
|
+
self,
|
|
619
|
+
scope: str,
|
|
620
|
+
*,
|
|
621
|
+
status: str = "open",
|
|
622
|
+
limit: int = 50,
|
|
623
|
+
cursor: Optional[int] = None,
|
|
624
|
+
) -> dict:
|
|
625
|
+
"""``GET /v1/conflicts`` — the conflict queue for a scope.
|
|
626
|
+
|
|
627
|
+
``status`` is ``"open"`` (default) or ``"all"``. Returns
|
|
628
|
+
``{conflicts: [ConflictView], total, next_cursor}``; pass
|
|
629
|
+
``next_cursor`` back as ``cursor`` to page.
|
|
630
|
+
"""
|
|
631
|
+
params: dict = {"scope": scope, "status": status, "limit": limit}
|
|
632
|
+
if cursor is not None:
|
|
633
|
+
params["cursor"] = cursor
|
|
634
|
+
return self._get("/v1/conflicts", params=params)
|
|
635
|
+
|
|
636
|
+
def get_conflict(self, conflict_id: str) -> dict:
|
|
637
|
+
"""``GET /v1/conflicts/{id}`` — one conflict with full member
|
|
638
|
+
records."""
|
|
639
|
+
return self._get(f"/v1/conflicts/{conflict_id}")
|
|
640
|
+
|
|
641
|
+
def resolve_conflict(
|
|
642
|
+
self,
|
|
643
|
+
conflict_id: str,
|
|
644
|
+
action: str,
|
|
645
|
+
*,
|
|
646
|
+
winner: Optional[str] = None,
|
|
647
|
+
reason: Optional[str] = None,
|
|
648
|
+
boundary: Optional[str] = None,
|
|
649
|
+
earlier: Optional[str] = None,
|
|
650
|
+
later: Optional[str] = None,
|
|
651
|
+
record: Optional[str] = None,
|
|
652
|
+
) -> dict:
|
|
653
|
+
"""``POST /v1/conflicts/{id}/resolve`` — manually resolve a conflict.
|
|
654
|
+
|
|
655
|
+
Actions and their arguments:
|
|
656
|
+
|
|
657
|
+
* ``"pick"`` — ``winner`` (record UUID) required, ``reason`` optional.
|
|
658
|
+
* ``"split"`` — ``boundary`` (absolute date expression, e.g.
|
|
659
|
+
``"2024-03"``), ``earlier`` and ``later`` (record UUIDs) required.
|
|
660
|
+
* ``"new_information"`` — ``record`` (record UUID) required.
|
|
661
|
+
* ``"dismiss"`` — ``reason`` optional.
|
|
662
|
+
|
|
663
|
+
Raises :class:`ValueError` on a bad action/argument combination.
|
|
664
|
+
Returns the post-resolution ``ConflictView``.
|
|
665
|
+
"""
|
|
666
|
+
body = _resolve_conflict_body(
|
|
667
|
+
action,
|
|
668
|
+
{
|
|
669
|
+
"winner": winner,
|
|
670
|
+
"reason": reason,
|
|
671
|
+
"boundary": boundary,
|
|
672
|
+
"earlier": earlier,
|
|
673
|
+
"later": later,
|
|
674
|
+
"record": record,
|
|
675
|
+
},
|
|
676
|
+
)
|
|
677
|
+
return self._post(f"/v1/conflicts/{conflict_id}/resolve", body)
|
|
678
|
+
|
|
679
|
+
def query_claims(
|
|
680
|
+
self,
|
|
681
|
+
scope: str,
|
|
682
|
+
*,
|
|
683
|
+
subject: Optional[str] = None,
|
|
684
|
+
predicate: Optional[str] = None,
|
|
685
|
+
valid_as_of: Optional[str] = None,
|
|
686
|
+
known_as_of: Optional[str] = None,
|
|
687
|
+
valid_during: Optional[Any] = None,
|
|
688
|
+
recorded_during: Optional[Any] = None,
|
|
689
|
+
limit: Optional[int] = None,
|
|
690
|
+
) -> dict:
|
|
691
|
+
"""``GET /v1/claims`` — the two-axis bi-temporal record query
|
|
692
|
+
("what was true at X, as known at Y").
|
|
693
|
+
|
|
694
|
+
* ``valid_as_of`` — world-time point (``"2023"``, ``"2023-06-01"``).
|
|
695
|
+
Defaults server-side to now.
|
|
696
|
+
* ``known_as_of`` — record-time point. Defaults server-side to now.
|
|
697
|
+
* ``valid_during`` / ``recorded_during`` — ranges; either a
|
|
698
|
+
``"start..end"`` string or a ``(start, end)`` pair.
|
|
699
|
+
|
|
700
|
+
Returns ``{records: [RecordView]}``.
|
|
701
|
+
"""
|
|
702
|
+
params = _claims_params(
|
|
703
|
+
scope,
|
|
704
|
+
subject=subject,
|
|
705
|
+
predicate=predicate,
|
|
706
|
+
valid_as_of=valid_as_of,
|
|
707
|
+
known_as_of=known_as_of,
|
|
708
|
+
valid_during=valid_during,
|
|
709
|
+
recorded_during=recorded_during,
|
|
710
|
+
limit=limit,
|
|
711
|
+
)
|
|
712
|
+
return self._get("/v1/claims", params=params)
|
|
713
|
+
|
|
714
|
+
def claim_history(self, scope: str, *, subject: str, predicate: str) -> dict:
|
|
715
|
+
"""``GET /v1/claims/history`` — the full change-log of one claim:
|
|
716
|
+
every valid interval and record-time correction, plus any conflicts
|
|
717
|
+
involving the claim. Returns ``{claim, history, conflicts}``."""
|
|
718
|
+
return self._get(
|
|
719
|
+
"/v1/claims/history",
|
|
720
|
+
params={"scope": scope, "subject": subject, "predicate": predicate},
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
def belief_history(self, belief_id: str) -> dict:
|
|
724
|
+
"""``GET /v1/beliefs/history`` — current belief + every superseded
|
|
725
|
+
semantic version, oldest first. Returns
|
|
726
|
+
``{current, versions, revision_count}``."""
|
|
727
|
+
return self._get("/v1/beliefs/history", params={"belief_id": belief_id})
|
|
728
|
+
|
|
729
|
+
def concept_history(self, concept_id: str) -> dict:
|
|
730
|
+
"""``GET /v1/concepts/{id}/history`` — current concept + superseded
|
|
731
|
+
semantic versions. Returns ``{current, versions}``."""
|
|
732
|
+
return self._get(f"/v1/concepts/{concept_id}/history")
|
|
733
|
+
|
|
495
734
|
# ── forget + erasures ─────────────────────────────────────────────
|
|
496
735
|
|
|
497
736
|
def forget(
|
|
@@ -798,6 +1037,100 @@ class AsyncV1Client:
|
|
|
798
1037
|
body["question_date"] = question_date
|
|
799
1038
|
return await self._post("/v1/answer", body)
|
|
800
1039
|
|
|
1040
|
+
# ── conflicts + bi-temporal claims ────────────────────────────────
|
|
1041
|
+
# Same surface + semantics as the sync client; see V1Client for the
|
|
1042
|
+
# full docstrings (including the "observed → by <date>" rendering rule).
|
|
1043
|
+
|
|
1044
|
+
async def list_conflicts(
|
|
1045
|
+
self,
|
|
1046
|
+
scope: str,
|
|
1047
|
+
*,
|
|
1048
|
+
status: str = "open",
|
|
1049
|
+
limit: int = 50,
|
|
1050
|
+
cursor: Optional[int] = None,
|
|
1051
|
+
) -> dict:
|
|
1052
|
+
"""``GET /v1/conflicts``. See :meth:`V1Client.list_conflicts`."""
|
|
1053
|
+
params: dict = {"scope": scope, "status": status, "limit": limit}
|
|
1054
|
+
if cursor is not None:
|
|
1055
|
+
params["cursor"] = cursor
|
|
1056
|
+
return await self._get("/v1/conflicts", params=params)
|
|
1057
|
+
|
|
1058
|
+
async def get_conflict(self, conflict_id: str) -> dict:
|
|
1059
|
+
"""``GET /v1/conflicts/{id}``. See :meth:`V1Client.get_conflict`."""
|
|
1060
|
+
return await self._get(f"/v1/conflicts/{conflict_id}")
|
|
1061
|
+
|
|
1062
|
+
async def resolve_conflict(
|
|
1063
|
+
self,
|
|
1064
|
+
conflict_id: str,
|
|
1065
|
+
action: str,
|
|
1066
|
+
*,
|
|
1067
|
+
winner: Optional[str] = None,
|
|
1068
|
+
reason: Optional[str] = None,
|
|
1069
|
+
boundary: Optional[str] = None,
|
|
1070
|
+
earlier: Optional[str] = None,
|
|
1071
|
+
later: Optional[str] = None,
|
|
1072
|
+
record: Optional[str] = None,
|
|
1073
|
+
) -> dict:
|
|
1074
|
+
"""``POST /v1/conflicts/{id}/resolve``.
|
|
1075
|
+
See :meth:`V1Client.resolve_conflict` for the per-action contract."""
|
|
1076
|
+
body = _resolve_conflict_body(
|
|
1077
|
+
action,
|
|
1078
|
+
{
|
|
1079
|
+
"winner": winner,
|
|
1080
|
+
"reason": reason,
|
|
1081
|
+
"boundary": boundary,
|
|
1082
|
+
"earlier": earlier,
|
|
1083
|
+
"later": later,
|
|
1084
|
+
"record": record,
|
|
1085
|
+
},
|
|
1086
|
+
)
|
|
1087
|
+
return await self._post(f"/v1/conflicts/{conflict_id}/resolve", body)
|
|
1088
|
+
|
|
1089
|
+
async def query_claims(
|
|
1090
|
+
self,
|
|
1091
|
+
scope: str,
|
|
1092
|
+
*,
|
|
1093
|
+
subject: Optional[str] = None,
|
|
1094
|
+
predicate: Optional[str] = None,
|
|
1095
|
+
valid_as_of: Optional[str] = None,
|
|
1096
|
+
known_as_of: Optional[str] = None,
|
|
1097
|
+
valid_during: Optional[Any] = None,
|
|
1098
|
+
recorded_during: Optional[Any] = None,
|
|
1099
|
+
limit: Optional[int] = None,
|
|
1100
|
+
) -> dict:
|
|
1101
|
+
"""``GET /v1/claims``. See :meth:`V1Client.query_claims`."""
|
|
1102
|
+
params = _claims_params(
|
|
1103
|
+
scope,
|
|
1104
|
+
subject=subject,
|
|
1105
|
+
predicate=predicate,
|
|
1106
|
+
valid_as_of=valid_as_of,
|
|
1107
|
+
known_as_of=known_as_of,
|
|
1108
|
+
valid_during=valid_during,
|
|
1109
|
+
recorded_during=recorded_during,
|
|
1110
|
+
limit=limit,
|
|
1111
|
+
)
|
|
1112
|
+
return await self._get("/v1/claims", params=params)
|
|
1113
|
+
|
|
1114
|
+
async def claim_history(
|
|
1115
|
+
self, scope: str, *, subject: str, predicate: str
|
|
1116
|
+
) -> dict:
|
|
1117
|
+
"""``GET /v1/claims/history``. See :meth:`V1Client.claim_history`."""
|
|
1118
|
+
return await self._get(
|
|
1119
|
+
"/v1/claims/history",
|
|
1120
|
+
params={"scope": scope, "subject": subject, "predicate": predicate},
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
async def belief_history(self, belief_id: str) -> dict:
|
|
1124
|
+
"""``GET /v1/beliefs/history``. See :meth:`V1Client.belief_history`."""
|
|
1125
|
+
return await self._get(
|
|
1126
|
+
"/v1/beliefs/history", params={"belief_id": belief_id}
|
|
1127
|
+
)
|
|
1128
|
+
|
|
1129
|
+
async def concept_history(self, concept_id: str) -> dict:
|
|
1130
|
+
"""``GET /v1/concepts/{id}/history``.
|
|
1131
|
+
See :meth:`V1Client.concept_history`."""
|
|
1132
|
+
return await self._get(f"/v1/concepts/{concept_id}/history")
|
|
1133
|
+
|
|
801
1134
|
async def _get(
|
|
802
1135
|
self,
|
|
803
1136
|
path: str,
|