nullprotocol 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,268 @@
1
+ Metadata-Version: 2.5
2
+ Name: nullprotocol
3
+ Version: 0.2.0
4
+ Summary: Focused AI tasks with managed context, Knowledge and observable results
5
+ Project-URL: Homepage, https://nullprotocol.ai
6
+ Project-URL: Documentation, https://nullprotocol.ai/docs/python
7
+ Project-URL: Repository, https://github.com/stuseek/nullprotocol-python
8
+ Project-URL: Issues, https://github.com/stuseek/nullprotocol-python/issues
9
+ Author: NullProtocol
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: agents,ai,context,llm,structured-output
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: httpx<1,>=0.28
23
+ Requires-Dist: jsonschema<5,>=4.23
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy<2,>=1.15; extra == 'dev'
26
+ Requires-Dist: pytest<10,>=8; extra == 'dev'
27
+ Requires-Dist: ruff<1,>=0.11; extra == 'dev'
28
+ Requires-Dist: types-jsonschema>=4.23; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # NullProtocol for Python
32
+
33
+ Run focused AI tasks with saved instructions, context and Knowledge. Create an agent in the [dashboard](https://app.nullprotocol.ai), then use it from your Python backend.
34
+
35
+ Python 3.10+. Both synchronous and asynchronous clients are included. This SDK calls NullProtocol-hosted agents; its model operations and History are handled by the existing HTTP API.
36
+
37
+ Install the Python package:
38
+
39
+ ```sh
40
+ python -m pip install nullprotocol
41
+ ```
42
+
43
+ ## First request
44
+
45
+ 1. Create an agent in the dashboard. Choose a model and write its instructions.
46
+ 2. An Owner creates a Space SDK key under **Connect**. Keep it on your backend.
47
+ 3. Set `NULLPROTOCOL_KEY` and replace `support` below with your agent ID.
48
+
49
+ ```python
50
+ from nullprotocol import NullProtocol
51
+
52
+ with NullProtocol() as client:
53
+ agent = client.load("support")
54
+ result = agent.extract(
55
+ "Order 42: two blue mugs.",
56
+ {"orderId": "number", "quantity": "number", "item": "string"},
57
+ )
58
+ if result.success:
59
+ print(result.data)
60
+ print(result.usage)
61
+ print(result.operation_id)
62
+ else:
63
+ print(result.error_code, result.error)
64
+ ```
65
+
66
+ The same Space key loads the agent and authorizes its hosted model. The next call picks up saved instruction/model/context changes. The operation appears in History. A team needs enabled model access and available credit.
67
+
68
+ You can also pass `key=...` and `endpoint=...` explicitly. Environment defaults are `NULLPROTOCOL_KEY` and `NULLPROTOCOL_API_URL`. The production endpoint is `https://api.nullprotocol.ai`. `timeout=30.0` is in **seconds**. Use the context manager or call `close()` to release the HTTP pool.
69
+
70
+ ## Four operations
71
+
72
+ ```python
73
+ fields = agent.extract(text, {"orderId": "number"})
74
+ summary = agent.summarize(text, max_length=300, focus="next step")
75
+ review = agent.validate("Every claim must follow the policy.", draft, reference=policy)
76
+ decision = agent.decide(
77
+ text,
78
+ [
79
+ {"action": "request_photo", "description": "Ask for a photo of the damage."},
80
+ {"action": "human_review", "description": "Ask a person to review the case."},
81
+ ],
82
+ )
83
+ ```
84
+
85
+ These snippets use an agent loaded as above and your own `text`, `draft` and `policy` variables. A successful extraction exposes `data`; summarization exposes `summary` and `key_points`; validation exposes `recommendation`, `score`, `reasoning`; a decision exposes `action`, `parameters`, `reasoning`.
86
+
87
+ Always check `result.success`. A completed validation with recommendation `fail` still has `success=True`: the model operation succeeded, the claim did not pass. Keep business rules in code. The schema checks output structure, not truth.
88
+
89
+ The Python summary limit defaults to 200 characters to match the JavaScript SDK; it is sent explicitly to the HTTP API. Invalid output gets the server's normal repair attempt. This client does not add its own generation or repair loop.
90
+
91
+ ## Async applications
92
+
93
+ ```python
94
+ import asyncio
95
+ from nullprotocol import AsyncNullProtocol
96
+
97
+
98
+ async def main():
99
+ async with AsyncNullProtocol() as client:
100
+ agent = await client.load("support")
101
+ result = await agent.summarize("The buyer received a broken mug.")
102
+ print(result.summary if result.success else result.error)
103
+
104
+
105
+ asyncio.run(main())
106
+ ```
107
+
108
+ Methods that perform network I/O are awaited. `register_action()` and `with_scope()` are local and do not need `await`. Async handlers and guards are supported by `AsyncNullProtocol`. A sync client requires sync handlers. Use the async client in an async web server; sync callbacks in an async action must not block its event loop.
109
+
110
+ ## Context, notes and scopes
111
+
112
+ ```python
113
+ order = agent.with_scope("order:42")
114
+ order.context.set("delivery", {"delivered": True}, inclusion="selected")
115
+ entry = order.context.get("delivery")
116
+ result = order.extract(
117
+ "Read delivery status.",
118
+ {"delivered": "boolean"},
119
+ context_keys=["delivery"],
120
+ )
121
+ note = order.memory.add("The buyer supplied a packaging photo.")
122
+ notes = order.memory.list()
123
+ order.memory.delete(note["id"])
124
+ order.context.delete("delivery")
125
+ ```
126
+
127
+ `scope` is mapped to the API's `conversation` field. It can identify an order, user, project or job. IDs are flat: `user:42:session:a` does not inherit `user:42`. A scope selects data, not permissions: authorize the end user in your backend. Without a scope, notes are agent-wide; with one, notes belong to that ID and include agent-wide facts. Nothing is remembered automatically.
128
+
129
+ `agent.space.set/get/list/delete` manages shared Space context; `agent.context` selects the current agent/scope. `always` is the default inclusion. A narrower value replaces the whole value under the same key; fields are not merged. `context.set(..., if_version=None)` creates only, `if_version=entry["version"]` guards replacement, and omitting the parameter overwrites.
130
+
131
+ State-management results such as notes, settings and context entries are dictionaries preserving the server's JSON field names (`agentId`, `disabledActions`, etc.). Model-operation result attributes use Python `snake_case`; keys inside extracted `data` and action `parameters` remain exactly as specified by your application.
132
+
133
+ ## Knowledge
134
+
135
+ ```python
136
+ result = agent.extract(
137
+ "How many days is the return window?",
138
+ {"days": "number"},
139
+ knowledge=True,
140
+ )
141
+ result = agent.validate(
142
+ "The statement must agree with the policy.",
143
+ "Personalised goods can always be returned.",
144
+ knowledge={"query": "Return exceptions for personalised goods"},
145
+ )
146
+ ```
147
+
148
+ Add/prepare documents and connect Space sources in the dashboard or through the SDK. All four operations accept `knowledge=True` or `{"query": "..."}`. Omitted/False means no search. Scope IDs do not narrow Knowledge connections. `result.knowledge` contains the actual source selection, and `result.knowledge_usage` tracks search embeddings separately from generation `result.usage`.
149
+
150
+ Reference texts under Context are included automatically and in full. Python uses the HTTP API's default 6000-character reference-text budget; the run endpoint does not accept the JavaScript SDK's `documentBudget` option. Longer sources belong in Knowledge. The complete request must still fit the model's window.
151
+
152
+ ## Manage Knowledge sources
153
+
154
+ ```python
155
+ shared = agent.space.knowledge.add(
156
+ name="Returns policy",
157
+ text="# Returns\nUnused goods may be returned within 30 days.",
158
+ )
159
+ source = agent.space.knowledge.get(shared["id"])
160
+ if source["state"] == "ready":
161
+ agent.knowledge.connect(shared["id"])
162
+
163
+ own = agent.knowledge.add(name="FAQ", text="We ship on weekdays.")
164
+ changed = agent.knowledge.update(
165
+ own["id"],
166
+ text="We ship Monday to Thursday.",
167
+ if_version=own["version"],
168
+ )
169
+ agent.knowledge.delete(own["id"], if_version=changed["version"])
170
+ ```
171
+
172
+ Use these snippets inside the client context above. `add`/`update` return `id`, `revision` and `version`; preparation is asynchronous. Inspect `get(id)["state"]`: preparing, ready or failed, plus `activeRevision`, `error` and `retryScheduled`. For a live agent, stage a new document in the Space library and connect it after it is ready. An unprepared new agent-owned/connected source refuses Knowledge calls; calls without Knowledge continue. A replacement keeps the previous prepared revision in use until the new one is ready.
173
+
174
+ Both stores provide `add`, `list`, `get`, `update`, `delete` and `retry`. `agent.knowledge.list()` lists only agent-owned documents. `agent.space.knowledge.list()` lists the shared library, and `agent.knowledge.connections()` lists connected Space sources. Read/update/delete through the owning store; connecting does not copy a document. Use `connect(id)`/`disconnect(id)` on `agent.knowledge`. Scope IDs do not change Knowledge ownership or connections.
175
+
176
+ Updates and deletes require `if_version`; a competing edit raises `APIError` with code `version_conflict`. The SDK never silently substitutes a newer version. A rename changes the version but not the text revision. `retry(id)` explicitly restarts failed preparation and returns a `retrying` flag; it does not wait or reindex a ready document. Preparation and searches can use embedding tokens. `agent.space.knowledge.usage()` returns 30-day Space totals: `preparation`, `search`, `uncounted` and `requests`, preserving unknown sums as None and reported sums as decimal strings.
177
+
178
+ All these methods are awaited on an async agent. Sources accept nonempty UTF-8 text up to 1 MiB without NUL; names hold up to 120 UTF-16 characters. A TXT/Markdown file can be read with `Path(path).read_text(encoding="utf-8")` and passed to `add`; PDF/DOCX parsing is not included. Adding or connecting sources does not automatically turn on search: pass `knowledge=True` or an explicit query per operation.
179
+
180
+ ## Local actions
181
+
182
+ ```python
183
+ from nullprotocol import ActionContext
184
+
185
+
186
+ def send_message(parameters, context: ActionContext):
187
+ print(parameters["text"]) # Replace with your delivery API.
188
+ return {"sent": True}
189
+
190
+
191
+ agent.register_action(
192
+ "send_message",
193
+ send_message,
194
+ description="Send a helpful reply to the customer.",
195
+ input_schema={
196
+ "type": "object",
197
+ "properties": {"text": {"type": "string", "minLength": 1}},
198
+ "required": ["text"],
199
+ "additionalProperties": False,
200
+ },
201
+ guard=lambda parameters, decision: len(parameters["text"]) <= 1000,
202
+ )
203
+ decision = agent.decide("The customer says: Good morning!", ["send_message"])
204
+ if decision.success:
205
+ execution = agent.execute(decision)
206
+ print(execution.success, execution.outcome, execution.history_error)
207
+ ```
208
+
209
+ Handlers receive `(parameters, ActionContext)`. `context.input` is the original task; `context.decision` is a copy of its decision snapshot. The SDK supplies registered parameter schemas as structured data in the decision's `context` because the HTTP endpoint only accepts action names and descriptions. The original task and schema contract are visible in History. Automatic Knowledge queries still use the original task, not that schema wrapper.
210
+
211
+ Before a handler runs, `execute` rereads pause/disabled settings, validates its parameters against the registered JSON Schema and checks the optional guard. A guard must return exactly `True`. Changing the returned result or values in a guard does not change the checked execution snapshot. Only an original successful decision from that agent object can be executed; a JSON/HTTP response loaded elsewhere is not executable. There is no implicit `chat` action or automatic tool loop.
212
+
213
+ Decisions are consumed once by this object, including failed/refused executions. Handlers are never automatically retried. Cancellation can leave an external effect’s outcome unknown; it does not make the consumed decision safe to execute again. HTTP model-call idempotency is **not** durable idempotency for your external action: use your own stable business-operation ID/transaction before sending messages, issuing refunds or making other writes, including across processes/restarts. `execute` records its outcome on the decision's History operation. Recording failure is exposed as `execution.history_error` and does not undo an action that already ran. A local handler makes no second model call; this first Python release does not include the JavaScript handler's `reply()` helper.
214
+
215
+ Action schemas use JSON Schema 2020-12, local references and the formats available to the installed `jsonschema` FormatChecker. Unsupported formats/remote references are rejected at registration. `decide` without a registered handler is supported when you only want a payload; validate that payload before using it yourself.
216
+
217
+ ## Errors, usage and retries
218
+
219
+ - `result.success=False`: the operation failed or was refused. Read `error_code` and `error`.
220
+ - `APIError`: the HTTP API refused the request. Read `status_code`, `code`, optional `retry_after`, and `request_id` for a run.
221
+ - `TransportError`: no response was received; the operation may already have completed. Keep `request_id`.
222
+ - `ProtocolError`: an unexpected response shape, including malformed usage. Do not treat it as a successful task.
223
+ - `ValueError`: invalid local configuration or input detected before dispatch.
224
+ - `result.history_error`: a usable result may exist even if its History record failed. Preserve the result.
225
+
226
+ `usage` is `None` when generation was not reported; it is not fabricated as zero. Known failure usage is retained. `usage.uncounted_requests` indicates partial reporting. Search usage stays separate in `knowledge_usage`.
227
+
228
+ The client generates one idempotency key per operation and preserves the exact body/key across safe transport retries. `max_retries=2` is the default. You can provide your own stable event ID:
229
+
230
+ ```python
231
+ result = agent.extract(text, schema, idempotency_key="event-42-extract-v1")
232
+ ```
233
+
234
+ Run timeouts, `429 rate_limited`, `409 in_progress` and `503 busy` are retried within the configured limit. Numeric Retry-After delays up to 30 seconds are honored; a longer or unsupported delay is returned to your application rather than ignored. Stored configuration failures, 500 errors, and unknown outcomes are not retried automatically. GETs may retry safely. Agent creation, context writes, note creation, settings writes and action recording are not automatically replayed after an uncertain write.
235
+
236
+ A completed HTTP response is cached **even if it is a failure**. After fixing a definite refusal (for example paused agent), deliberately choose a new attempt ID. After an unknown outcome, inspect History first. Do not generate a fresh ID automatically after a timeout.
237
+
238
+ ## Create or edit an agent in Python
239
+
240
+ ```python
241
+ agent = client.create(
242
+ agent_id="invoice-reader",
243
+ name="Invoice reader",
244
+ model="your-enabled-model-alias",
245
+ instructions="Read only supplied facts. Do not invent missing values.",
246
+ )
247
+ settings = agent.settings()
248
+ agent.update(instructions="Use null for missing values.", if_revision=settings["revision"])
249
+ agent.update(paused=True)
250
+ ```
251
+
252
+ Use a model alias enabled for the team. `with_scope()` creates a new binding with copied action registrations, not the previous object's executable decisions. The bound objects share their client's HTTP pool; closing the client closes SDK access for all of them. When you supply your own `httpx.Client`/`AsyncClient` as `http_client`, you own and close that HTTP client yourself.
253
+
254
+ Standalone Ollama/OpenAI/Anthropic providers are available in the JavaScript SDK; this Python release is for saved hosted agents. It has no browser key storage, automatic memory extraction, payment flow or hosted workflow engine.
255
+
256
+ ## Examples and development
257
+
258
+ Examples in `examples/` use your backend environment key and an existing `support` agent. They make real hosted requests when run. The scoped-data example explicitly removes only its own note/key; it uses a fixed demonstration scope, so use a disposable test agent when trying it.
259
+
260
+ ```sh
261
+ python -m pip install -e '.[dev]'
262
+ pytest
263
+ ruff check .
264
+ ruff format --check .
265
+ mypy src
266
+ ```
267
+
268
+ Tests use a scripted HTTP fixture, not paid providers. The workspace integration proof also runs this client against the actual API/gateway and an isolated PostgreSQL database with scripted generation/embeddings. These checks verify integration, not model accuracy.
@@ -0,0 +1,14 @@
1
+ nullprotocol/__init__.py,sha256=wpmLCS8SMXMgeJXlt35eP03ly6QnA1b1Ib8ia46eHnw,841
2
+ nullprotocol/_agent.py,sha256=yOukGCU2yqIDC80lB7I4oRQO1Z3am1icOziVuBJS--s,18057
3
+ nullprotocol/_client.py,sha256=JJB9PtMPhtZJs0oAmS2TtnaDkat-tWX7l0RUOt6Qpts,3387
4
+ nullprotocol/_common.py,sha256=wLi-dunnz2EEfoLVO7DhWHzDXf_AGXC6jc4rMt9328s,11166
5
+ nullprotocol/_errors.py,sha256=iBeCYsTBJN_36gAOqiuKDyhuthwvrnN42oCVcxWjYt8,1323
6
+ nullprotocol/_knowledge.py,sha256=Ok1I2VEmA7j_6xAEBGwq_6x_rqVh79HJpT93xyxFajo,6921
7
+ nullprotocol/_stores.py,sha256=7q14e5EBZKNUCs6B-0dC_RjHj0HZvIRu3243hevqL1A,6063
8
+ nullprotocol/_transport.py,sha256=dPGZyBtN-l5Zm15QYYcmDwV4P4DF9m9iz6Y754RplYQ,10299
9
+ nullprotocol/_types.py,sha256=MRo0ekKtRLTt2spq9e95WCtS2yz1B1KpxsRrl9CzPMg,6428
10
+ nullprotocol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ nullprotocol-0.2.0.dist-info/METADATA,sha256=4hRYRmZbZWz-kLk6ds4DyrWdmAWSDcQpqMeTCJBFISg,16441
12
+ nullprotocol-0.2.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
13
+ nullprotocol-0.2.0.dist-info/licenses/LICENSE,sha256=zB73BbeHv6aEdBBt2SslxJKZ4pKnXdSH5Py8RV5Q1BQ,1066
14
+ nullprotocol-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 AISEC Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.