iints-sdk-python35 1.1.2__py3-none-any.whl → 1.1.3__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.
- iints/__init__.py +1 -1
- iints/ai/backends/ollama.py +84 -1
- iints/ai/cli.py +16 -0
- {iints_sdk_python35-1.1.2.dist-info → iints_sdk_python35-1.1.3.dist-info}/METADATA +5 -2
- {iints_sdk_python35-1.1.2.dist-info → iints_sdk_python35-1.1.3.dist-info}/RECORD +9 -9
- {iints_sdk_python35-1.1.2.dist-info → iints_sdk_python35-1.1.3.dist-info}/WHEEL +0 -0
- {iints_sdk_python35-1.1.2.dist-info → iints_sdk_python35-1.1.3.dist-info}/entry_points.txt +0 -0
- {iints_sdk_python35-1.1.2.dist-info → iints_sdk_python35-1.1.3.dist-info}/licenses/LICENSE +0 -0
- {iints_sdk_python35-1.1.2.dist-info → iints_sdk_python35-1.1.3.dist-info}/top_level.txt +0 -0
iints/__init__.py
CHANGED
|
@@ -11,7 +11,7 @@ except ImportError: # pragma: no cover - Python < 3.8 fallback
|
|
|
11
11
|
try:
|
|
12
12
|
__version__ = version("iints-sdk-python35")
|
|
13
13
|
except PackageNotFoundError: # pragma: no cover - source tree fallback
|
|
14
|
-
__version__ = "1.1.
|
|
14
|
+
__version__ = "1.1.3"
|
|
15
15
|
|
|
16
16
|
# Note to developers: this SDK is currently maintained by a single author.
|
|
17
17
|
# Please report bugs via GitHub issues and feel free to contribute fixes via PRs.
|
iints/ai/backends/ollama.py
CHANGED
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
from http.client import IncompleteRead, RemoteDisconnected
|
|
6
|
+
from time import sleep
|
|
5
7
|
from urllib import error, request
|
|
6
8
|
|
|
7
9
|
|
|
@@ -40,6 +42,20 @@ class OllamaBackend:
|
|
|
40
42
|
def _pull_hint(self) -> str:
|
|
41
43
|
return f"ollama pull {self.model_name}"
|
|
42
44
|
|
|
45
|
+
def _generation_failure_hint(self) -> str:
|
|
46
|
+
resolved = self.resolved_model_name or self.model_name
|
|
47
|
+
return (
|
|
48
|
+
"Ollama closed the generation connection before returning a response.\n"
|
|
49
|
+
f"Endpoint: {self.base_url}\n"
|
|
50
|
+
f"Model: {resolved}\n"
|
|
51
|
+
"This usually means the model crashed while loading, the daemon restarted, "
|
|
52
|
+
"or the machine ran out of memory.\n"
|
|
53
|
+
"Try one of these:\n"
|
|
54
|
+
f" 1. Run `ollama run {resolved} \"Reply with OK.\"` to confirm direct inference works.\n"
|
|
55
|
+
" 2. Run `iints ai local-check --smoke-test` to validate a real generation path.\n"
|
|
56
|
+
" 3. Switch to a smaller local model such as `ministral-3:3b` if memory is tight."
|
|
57
|
+
)
|
|
58
|
+
|
|
43
59
|
def _requires_ministral_3_runtime(self) -> bool:
|
|
44
60
|
requested = self.model_name.strip().lower()
|
|
45
61
|
return requested.startswith("ministral-3") or requested == "ministral"
|
|
@@ -68,6 +84,15 @@ class OllamaBackend:
|
|
|
68
84
|
payload: dict[str, object] | None = None,
|
|
69
85
|
*,
|
|
70
86
|
method: str = "POST",
|
|
87
|
+
) -> dict[str, object]:
|
|
88
|
+
return self._request_json_once(path, payload, method=method)
|
|
89
|
+
|
|
90
|
+
def _request_json_once(
|
|
91
|
+
self,
|
|
92
|
+
path: str,
|
|
93
|
+
payload: dict[str, object] | None = None,
|
|
94
|
+
*,
|
|
95
|
+
method: str = "POST",
|
|
71
96
|
) -> dict[str, object]:
|
|
72
97
|
url = f"{self.base_url}{path}"
|
|
73
98
|
body = None
|
|
@@ -92,6 +117,12 @@ class OllamaBackend:
|
|
|
92
117
|
f"Could not reach Ollama at {self.base_url}. "
|
|
93
118
|
"Start Ollama or set OLLAMA_HOST to the correct endpoint."
|
|
94
119
|
) from exc
|
|
120
|
+
except (RemoteDisconnected, ConnectionResetError, IncompleteRead) as exc:
|
|
121
|
+
if path == "/api/generate":
|
|
122
|
+
raise RuntimeError(self._generation_failure_hint()) from exc
|
|
123
|
+
raise RuntimeError(
|
|
124
|
+
f"Ollama connection closed unexpectedly while calling {path} at {self.base_url}."
|
|
125
|
+
) from exc
|
|
95
126
|
|
|
96
127
|
try:
|
|
97
128
|
payload_json = json.loads(text)
|
|
@@ -223,6 +254,43 @@ class OllamaBackend:
|
|
|
223
254
|
"version_ok": version_ok,
|
|
224
255
|
}
|
|
225
256
|
|
|
257
|
+
def smoke_test(self) -> dict[str, object]:
|
|
258
|
+
resolved_model = self.ensure_model_ready()
|
|
259
|
+
payload = {
|
|
260
|
+
"model": resolved_model,
|
|
261
|
+
"system": "You are a health check. Reply with exactly: OK",
|
|
262
|
+
"prompt": "Reply with exactly: OK",
|
|
263
|
+
"stream": False,
|
|
264
|
+
"options": {
|
|
265
|
+
"temperature": 0,
|
|
266
|
+
"num_predict": 8,
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
last_error: Exception | None = None
|
|
271
|
+
for attempt in range(2):
|
|
272
|
+
try:
|
|
273
|
+
response = self._request_json_once("/api/generate", payload)
|
|
274
|
+
text = response.get("response")
|
|
275
|
+
if not isinstance(text, str) or not text.strip():
|
|
276
|
+
raise RuntimeError("Ollama returned an empty smoke-test completion.")
|
|
277
|
+
return {
|
|
278
|
+
"ok": True,
|
|
279
|
+
"response": text.strip(),
|
|
280
|
+
"attempts": attempt + 1,
|
|
281
|
+
}
|
|
282
|
+
except (RuntimeError, RemoteDisconnected, ConnectionResetError, IncompleteRead) as exc:
|
|
283
|
+
if not isinstance(exc, RuntimeError):
|
|
284
|
+
exc = RuntimeError(self._generation_failure_hint())
|
|
285
|
+
last_error = exc
|
|
286
|
+
if attempt == 0:
|
|
287
|
+
sleep(1.0)
|
|
288
|
+
continue
|
|
289
|
+
break
|
|
290
|
+
|
|
291
|
+
assert last_error is not None
|
|
292
|
+
raise last_error
|
|
293
|
+
|
|
226
294
|
def complete(self, *, system_prompt: str, user_prompt: str) -> str:
|
|
227
295
|
resolved_model = self.ensure_model_ready()
|
|
228
296
|
payload = {
|
|
@@ -231,7 +299,22 @@ class OllamaBackend:
|
|
|
231
299
|
"prompt": user_prompt,
|
|
232
300
|
"stream": False,
|
|
233
301
|
}
|
|
234
|
-
|
|
302
|
+
last_error: Exception | None = None
|
|
303
|
+
for attempt in range(2):
|
|
304
|
+
try:
|
|
305
|
+
response = self._request_json_once("/api/generate", payload)
|
|
306
|
+
break
|
|
307
|
+
except (RuntimeError, RemoteDisconnected, ConnectionResetError, IncompleteRead) as exc:
|
|
308
|
+
if not isinstance(exc, RuntimeError):
|
|
309
|
+
exc = RuntimeError(self._generation_failure_hint())
|
|
310
|
+
last_error = exc
|
|
311
|
+
if attempt == 0:
|
|
312
|
+
sleep(1.0)
|
|
313
|
+
continue
|
|
314
|
+
raise exc
|
|
315
|
+
else:
|
|
316
|
+
assert last_error is not None
|
|
317
|
+
raise last_error
|
|
235
318
|
text = response.get("response")
|
|
236
319
|
if not isinstance(text, str) or not text.strip():
|
|
237
320
|
raise RuntimeError("Ollama returned an empty completion.")
|
iints/ai/cli.py
CHANGED
|
@@ -111,6 +111,7 @@ def _render_local_check(console: Console, status: dict[str, object]) -> None:
|
|
|
111
111
|
installed_text = ", ".join(str(item) for item in installed) if isinstance(installed, list) and installed else "none"
|
|
112
112
|
ready = bool(status.get("ready"))
|
|
113
113
|
resolved_model = status.get("resolved_model") or "not found"
|
|
114
|
+
smoke_text = status.get("smoke_test") or "not run"
|
|
114
115
|
console.print(
|
|
115
116
|
Panel(
|
|
116
117
|
"\n".join(
|
|
@@ -126,6 +127,7 @@ def _render_local_check(console: Console, status: dict[str, object]) -> None:
|
|
|
126
127
|
if status.get("pull_command")
|
|
127
128
|
else "Pull command: not needed"
|
|
128
129
|
),
|
|
130
|
+
f"Generate smoke-test: {smoke_text}",
|
|
129
131
|
]
|
|
130
132
|
),
|
|
131
133
|
title="IINTS AI Local Check",
|
|
@@ -239,6 +241,13 @@ def local_check(
|
|
|
239
241
|
model: Annotated[str, typer.Option(help="Ollama model name to validate locally.")] = DEFAULT_MINISTRAL_MODEL,
|
|
240
242
|
ollama_host: Annotated[Optional[str], typer.Option(help="Override the Ollama base URL.")] = None,
|
|
241
243
|
timeout_seconds: Annotated[float, typer.Option(help="HTTP timeout for Ollama health checks.")] = 120.0,
|
|
244
|
+
smoke_test: Annotated[
|
|
245
|
+
bool,
|
|
246
|
+
typer.Option(
|
|
247
|
+
"--smoke-test/--no-smoke-test",
|
|
248
|
+
help="Run a tiny generation request after health checks to prove the model can actually answer.",
|
|
249
|
+
),
|
|
250
|
+
] = True,
|
|
242
251
|
) -> None:
|
|
243
252
|
console = Console()
|
|
244
253
|
backend = OllamaBackend(model_name=model, base_url=ollama_host, timeout_seconds=timeout_seconds)
|
|
@@ -250,6 +259,13 @@ def local_check(
|
|
|
250
259
|
)
|
|
251
260
|
raise typer.Exit(code=1)
|
|
252
261
|
status = backend.healthcheck()
|
|
262
|
+
if smoke_test and bool(status.get("ready")):
|
|
263
|
+
smoke = backend.smoke_test()
|
|
264
|
+
status["smoke_test"] = f"OK ({smoke.get('response')})"
|
|
265
|
+
elif smoke_test:
|
|
266
|
+
status["smoke_test"] = "skipped (model not ready)"
|
|
267
|
+
else:
|
|
268
|
+
status["smoke_test"] = "disabled"
|
|
253
269
|
_render_local_check(console, status)
|
|
254
270
|
if not bool(status.get("ready")):
|
|
255
271
|
raise typer.Exit(code=1)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iints-sdk-python35
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
4
4
|
Summary: A pre-clinical Edge-AI SDK for diabetes management validation.
|
|
5
5
|
Author-email: Rune Bobbaers <rune.bobbaers@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/python35/IINTS-SDK
|
|
@@ -110,6 +110,8 @@ ollama pull ministral-3:8b
|
|
|
110
110
|
iints ai local-check --model ministral-3:8b
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
`local-check` now performs a tiny generation smoke-test by default, so it verifies both model presence and real inference readiness.
|
|
114
|
+
|
|
113
115
|
Recommended flow:
|
|
114
116
|
|
|
115
117
|
```bash
|
|
@@ -134,6 +136,7 @@ Notes:
|
|
|
134
136
|
- Users can choose a larger or smaller local Mistral-family model with `--model ...`.
|
|
135
137
|
- Large JSON payloads are clipped automatically before prompt generation to keep local inference stable.
|
|
136
138
|
- `iints ai prepare <run_dir>` now creates AI-ready JSON payloads and, when MDMP is installed, a local development certificate plus keypair in `<run_dir>/ai/`.
|
|
139
|
+
- If Ollama closes the connection during generation, the SDK now surfaces an explicit recovery hint and points users toward `ministral-3:3b` for lower-memory systems.
|
|
137
140
|
- After `iints ai prepare`, you can point `iints ai explain|trends|anomalies|report` directly at the run directory.
|
|
138
141
|
- Output is research-only and not medical advice.
|
|
139
142
|
|
|
@@ -144,7 +147,7 @@ Troubleshooting:
|
|
|
144
147
|
|
|
145
148
|
```bash
|
|
146
149
|
python -m pip uninstall -y iints iints-sdk-python35
|
|
147
|
-
python -m pip install -U "iints-sdk-python35[mdmp]==1.1.
|
|
150
|
+
python -m pip install -U "iints-sdk-python35[mdmp]==1.1.3"
|
|
148
151
|
hash -r
|
|
149
152
|
```
|
|
150
153
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
iints/__init__.py,sha256=
|
|
1
|
+
iints/__init__.py,sha256=F_rnQgeZZn3bQpyYAy4JwHARfQfq8fUSLvbs07Ycybw,6391
|
|
2
2
|
iints/highlevel.py,sha256=DX12LRmL6YaYY99P0c_P93xfHe4mZjqyLhTYuS6L6hI,20491
|
|
3
3
|
iints/metrics.py,sha256=O9hqOqJpUhUJDqsbfuqRMS9dkV97gzcgh3Y2jYUqHzg,907
|
|
4
4
|
iints/ai/__init__.py,sha256=nyRDcFfSHI4a3NbTvySipFc3_inqRMEsr6xIEipWuyo,575
|
|
5
5
|
iints/ai/assistant.py,sha256=0Ye1IaWEYg2rZnk3ny8f0GMoYqOWIa7U_GsV-sWrxtU,4346
|
|
6
|
-
iints/ai/cli.py,sha256=
|
|
6
|
+
iints/ai/cli.py,sha256=jR9cgLkYGQRx_AfiGGWK4oBFTQDO2fHas_3UJGq78X8,19009
|
|
7
7
|
iints/ai/mdmp_guard.py,sha256=BpFQX0oyP9WMCUZbFhhoBzomNeVKuI1HY1EFH9cG8EE,4249
|
|
8
8
|
iints/ai/model_catalog.py,sha256=gRW-i4eaXkrjX3mIKJlGzHqzU75lpIulEFKQsCX11CI,1804
|
|
9
9
|
iints/ai/prepare.py,sha256=z3y5elCAMv0p_aNq4gQfZA1uIT7_cX3FGRdzmoZoKho,12967
|
|
@@ -11,7 +11,7 @@ iints/ai/prompts.py,sha256=pGp9tC1wBZXGG5duxfktaJEF4p_cvmR0zEIxmMTEAyE,2812
|
|
|
11
11
|
iints/ai/backends/__init__.py,sha256=EAJRZS8G0DK7fffw_LHio9DkyYHwtzvz2Jo7AXk7pk4,303
|
|
12
12
|
iints/ai/backends/base.py,sha256=BLgP03X-jebYkF9D5n5crawoPBmy3RSh4q3jaT8a9XM,274
|
|
13
13
|
iints/ai/backends/mistral_api.py,sha256=dousHnzgzuik49822H8nCclYv5NoxHpTMLwtZPVj_TM,507
|
|
14
|
-
iints/ai/backends/ollama.py,sha256=
|
|
14
|
+
iints/ai/backends/ollama.py,sha256=3VWi37ueh7oeNK6tPrq3Ks8gbfQhzgNlMHHnV_6BxAY,12189
|
|
15
15
|
iints/analysis/__init__.py,sha256=Qx49KDy0deoxSnVORJB10_BsdezZXKsuoXTR0KZRcqg,411
|
|
16
16
|
iints/analysis/algorithm_xray.py,sha256=-AtXkZsgnsiFQ_K-IozjIDWkq-dDn0i0zmqWVMhINP4,15952
|
|
17
17
|
iints/analysis/baseline.py,sha256=PCFVb5vX0lYKChZvVk-8I_B5NLQQwGyx7Y6M3XjpIEY,3458
|
|
@@ -142,9 +142,9 @@ iints/validation/schemas.py,sha256=uXhiPxyfyvOgCA83ZPBIzlITOu663fWctYxOMXUyf1I,4
|
|
|
142
142
|
iints/visualization/__init__.py,sha256=OdxVHDpY-9bDt8DTWWd-dspn1p0O9T908Cck-IGFaiM,640
|
|
143
143
|
iints/visualization/cockpit.py,sha256=Y7hoJXcTEWQ8yLiU5X5abT58uqGGsQllftXJwqerG1E,25057
|
|
144
144
|
iints/visualization/uncertainty_cloud.py,sha256=I5nNzSitgai21rkul31YNtJriSEmCeTsW0GWW2HUskY,19848
|
|
145
|
-
iints_sdk_python35-1.1.
|
|
146
|
-
iints_sdk_python35-1.1.
|
|
147
|
-
iints_sdk_python35-1.1.
|
|
148
|
-
iints_sdk_python35-1.1.
|
|
149
|
-
iints_sdk_python35-1.1.
|
|
150
|
-
iints_sdk_python35-1.1.
|
|
145
|
+
iints_sdk_python35-1.1.3.dist-info/licenses/LICENSE,sha256=b1luljj2mWWDW10t_qFIqd9Z6euXAcDBmIXowWuUlm4,1417
|
|
146
|
+
iints_sdk_python35-1.1.3.dist-info/METADATA,sha256=5gDxBH6JpAOqvlisyv6aOye2tS4YRLBuybhz2-Oq7H0,9188
|
|
147
|
+
iints_sdk_python35-1.1.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
148
|
+
iints_sdk_python35-1.1.3.dist-info/entry_points.txt,sha256=aVioeLytTHG7WM7L3LIZ6XDJCKiSfqG-nVUQDVHPpQk,578
|
|
149
|
+
iints_sdk_python35-1.1.3.dist-info/top_level.txt,sha256=7Usr6NQKiC9SpNFyCis81MmgXy71lDCr5unR8BNXZ0E,6
|
|
150
|
+
iints_sdk_python35-1.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|