iints-sdk-python35 1.5.19__py3-none-any.whl → 1.5.22__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/analysis/reporting.py +9 -3
- iints/core/simulator.py +4 -1
- iints/highlevel.py +3 -2
- iints/research/alphafold_engine.py +91 -0
- iints/research/genomics_engine.py +24 -5
- iints/utils/plotting.py +5 -0
- iints_desktop/engine.py +68 -1
- iints_desktop/local_ai.py +22 -19
- iints_desktop/qt_app.py +598 -143
- {iints_sdk_python35-1.5.19.dist-info → iints_sdk_python35-1.5.22.dist-info}/METADATA +3 -3
- {iints_sdk_python35-1.5.19.dist-info → iints_sdk_python35-1.5.22.dist-info}/RECORD +18 -17
- {iints_sdk_python35-1.5.19.dist-info → iints_sdk_python35-1.5.22.dist-info}/WHEEL +0 -0
- {iints_sdk_python35-1.5.19.dist-info → iints_sdk_python35-1.5.22.dist-info}/entry_points.txt +0 -0
- {iints_sdk_python35-1.5.19.dist-info → iints_sdk_python35-1.5.22.dist-info}/licenses/LICENSE +0 -0
- {iints_sdk_python35-1.5.19.dist-info → iints_sdk_python35-1.5.22.dist-info}/licenses/LICENSE-MIT-IINTS-LEGACY +0 -0
- {iints_sdk_python35-1.5.19.dist-info → iints_sdk_python35-1.5.22.dist-info}/licenses/NOTICE +0 -0
- {iints_sdk_python35-1.5.19.dist-info → iints_sdk_python35-1.5.22.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.5.
|
|
14
|
+
__version__ = "1.5.22"
|
|
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/analysis/reporting.py
CHANGED
|
@@ -854,9 +854,15 @@ class ClinicalReportGenerator:
|
|
|
854
854
|
ax.set_ylabel("Sensor glucose (mg/dL)")
|
|
855
855
|
ax.grid(True, alpha=0.22)
|
|
856
856
|
ax.legend(loc="upper left", fontsize=7, frameon=True)
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
857
|
+
try:
|
|
858
|
+
fig.tight_layout()
|
|
859
|
+
fig.savefig(output_path, dpi=260, bbox_inches="tight")
|
|
860
|
+
except ValueError as exc:
|
|
861
|
+
logger.warning("Clinical validation plot layout failed; saving fallback layout: %s", exc)
|
|
862
|
+
fig.subplots_adjust(left=0.08, right=0.98, top=0.9, bottom=0.12)
|
|
863
|
+
fig.savefig(output_path, dpi=260)
|
|
864
|
+
finally:
|
|
865
|
+
plt.close(fig)
|
|
860
866
|
|
|
861
867
|
@staticmethod
|
|
862
868
|
def _metric_delta(current: float, target: float, higher_is_better: bool = True) -> str:
|
iints/core/simulator.py
CHANGED
|
@@ -724,12 +724,13 @@ class Simulator:
|
|
|
724
724
|
"""Alias for run_batch to ensure backward compatibility."""
|
|
725
725
|
return self.run_batch(duration_minutes)
|
|
726
726
|
|
|
727
|
-
def run_batch(self, duration_minutes: int) -> Tuple[pd.DataFrame, Dict[str, Any]]:
|
|
727
|
+
def run_batch(self, duration_minutes: int, step_callback: Optional[Callable[[int, int, float], None]] = None) -> Tuple[pd.DataFrame, Dict[str, Any]]:
|
|
728
728
|
"""
|
|
729
729
|
Runs the entire simulation and returns the results as a single DataFrame.
|
|
730
730
|
|
|
731
731
|
Args:
|
|
732
732
|
duration_minutes (int): Total simulation duration in minutes.
|
|
733
|
+
step_callback (Callable): Optional callback for live telemetry (time, duration, glucose).
|
|
733
734
|
|
|
734
735
|
Returns:
|
|
735
736
|
pd.DataFrame: A DataFrame containing the complete simulation results.
|
|
@@ -740,6 +741,8 @@ class Simulator:
|
|
|
740
741
|
try:
|
|
741
742
|
for record in self.run_live(duration_minutes):
|
|
742
743
|
all_records.append(record)
|
|
744
|
+
if step_callback:
|
|
745
|
+
step_callback(record["time"], duration_minutes, record["glucose"])
|
|
743
746
|
except SimulationLimitError as err:
|
|
744
747
|
logger.error("Simulation terminated early: %s", err)
|
|
745
748
|
self._termination_info = {
|
iints/highlevel.py
CHANGED
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from dataclasses import asdict
|
|
5
|
-
from typing import Any, Dict, Optional, Union
|
|
5
|
+
from typing import Any, Dict, Optional, Union, Callable
|
|
6
6
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
import yaml
|
|
@@ -296,6 +296,7 @@ def run_full(
|
|
|
296
296
|
enable_profiling: bool = True,
|
|
297
297
|
safety_config: Optional[SafetyConfig] = None,
|
|
298
298
|
predictor: Optional[object] = None,
|
|
299
|
+
step_callback: Optional[Callable[[int, int, float], None]] = None,
|
|
299
300
|
) -> Dict[str, Any]:
|
|
300
301
|
"""
|
|
301
302
|
One-line runner that always exports results + audit + PDF + baseline comparison.
|
|
@@ -341,7 +342,7 @@ def run_full(
|
|
|
341
342
|
for event in build_stress_events(stress_event_payloads):
|
|
342
343
|
simulator.add_stress_event(event)
|
|
343
344
|
|
|
344
|
-
results_df, safety_report = simulator.run_batch(duration_minutes)
|
|
345
|
+
results_df, safety_report = simulator.run_batch(duration_minutes, step_callback=step_callback)
|
|
345
346
|
|
|
346
347
|
outputs: Dict[str, Any] = {
|
|
347
348
|
"results": results_df,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import urllib.request
|
|
2
|
+
import json
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
|
|
5
|
+
class AlphaFoldGenomicsEngine:
|
|
6
|
+
"""
|
|
7
|
+
Bridges deep 3D structural AI (AlphaFold) to physiological equations.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def evaluate_plddt_impact(uniprot_id: str, residue_index: int) -> Dict[str, Any]:
|
|
12
|
+
"""
|
|
13
|
+
Fetches AlphaFold structure, finds the pLDDT for the residue,
|
|
14
|
+
and mathematically translates it into a molecular_affinity_scalar.
|
|
15
|
+
"""
|
|
16
|
+
api_url = f"https://alphafold.ebi.ac.uk/api/prediction/{uniprot_id}"
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
req = urllib.request.Request(api_url, headers={'User-Agent': 'IINTS-AF-SDK/1.0'})
|
|
20
|
+
with urllib.request.urlopen(req) as response:
|
|
21
|
+
if response.status != 200:
|
|
22
|
+
return {"error": f"Failed to query AlphaFold API for {uniprot_id} (Status {response.status})"}
|
|
23
|
+
data = json.loads(response.read().decode())
|
|
24
|
+
except Exception as e:
|
|
25
|
+
return {"error": f"AlphaFold API request failed: {e}"}
|
|
26
|
+
|
|
27
|
+
if not data:
|
|
28
|
+
return {"error": f"No AlphaFold prediction found for {uniprot_id}"}
|
|
29
|
+
|
|
30
|
+
# Find the correct fragment for large proteins
|
|
31
|
+
target_fragment = None
|
|
32
|
+
for frag in data:
|
|
33
|
+
start = frag.get("uniprotStart", 1)
|
|
34
|
+
end = frag.get("uniprotEnd", 999999)
|
|
35
|
+
if start <= residue_index <= end:
|
|
36
|
+
target_fragment = frag
|
|
37
|
+
break
|
|
38
|
+
|
|
39
|
+
if not target_fragment:
|
|
40
|
+
return {"error": f"Residue {residue_index} is out of bounds for {uniprot_id}."}
|
|
41
|
+
|
|
42
|
+
pdb_url = target_fragment.get("pdbUrl")
|
|
43
|
+
if not pdb_url:
|
|
44
|
+
return {"error": "PDB file not available in AlphaFold DB for this protein."}
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
pdb_req = urllib.request.Request(pdb_url, headers={'User-Agent': 'IINTS-AF-SDK/1.0'})
|
|
48
|
+
with urllib.request.urlopen(pdb_req) as response:
|
|
49
|
+
pdb_text = response.read().decode()
|
|
50
|
+
except Exception as e:
|
|
51
|
+
return {"error": f"Failed to download PDB file: {e}"}
|
|
52
|
+
|
|
53
|
+
# Parse PDB text to find pLDDT (B-factor is in columns 61-66)
|
|
54
|
+
plddt_values = []
|
|
55
|
+
for line in pdb_text.split("\n"):
|
|
56
|
+
if line.startswith("ATOM "):
|
|
57
|
+
try:
|
|
58
|
+
res_num = int(line[22:26].strip())
|
|
59
|
+
if res_num == residue_index:
|
|
60
|
+
b_factor = float(line[60:66].strip())
|
|
61
|
+
plddt_values.append(b_factor)
|
|
62
|
+
except ValueError:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
if not plddt_values:
|
|
66
|
+
return {"error": f"Residue {residue_index} not found in AlphaFold structure."}
|
|
67
|
+
|
|
68
|
+
avg_plddt = sum(plddt_values) / len(plddt_values)
|
|
69
|
+
|
|
70
|
+
# Mathematical translation
|
|
71
|
+
if avg_plddt >= 90:
|
|
72
|
+
scalar = 0.15
|
|
73
|
+
conclusion = "Highly structured core domain. Mutation is likely catastrophic."
|
|
74
|
+
elif avg_plddt >= 70:
|
|
75
|
+
scalar = 0.40
|
|
76
|
+
conclusion = "Structured domain. Mutation likely impairs function."
|
|
77
|
+
elif avg_plddt >= 50:
|
|
78
|
+
scalar = 0.75
|
|
79
|
+
conclusion = "Moderate flexibility. Mutation partially tolerated."
|
|
80
|
+
else:
|
|
81
|
+
scalar = 0.95
|
|
82
|
+
conclusion = "Intrinsically disordered/flexible region. Mutation is highly tolerated."
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
"uniprot_id": uniprot_id,
|
|
86
|
+
"residue_index": residue_index,
|
|
87
|
+
"plddt": round(avg_plddt, 2),
|
|
88
|
+
"scalar": scalar,
|
|
89
|
+
"conclusion": conclusion,
|
|
90
|
+
"pdb_url": pdb_url
|
|
91
|
+
}
|
|
@@ -57,14 +57,33 @@ class GenomicsEngine:
|
|
|
57
57
|
|
|
58
58
|
@staticmethod
|
|
59
59
|
def evaluate_mutation(gene: str, variant: str) -> dict[str, Any]:
|
|
60
|
-
"""Translate a variant such as ``INSR V938M`` into a functional scalar."""
|
|
60
|
+
"""Translate a variant such as ``INSR V938M`` into a functional scalar using AlphaFold."""
|
|
61
|
+
import re
|
|
62
|
+
from iints.research.alphafold_engine import AlphaFoldGenomicsEngine
|
|
61
63
|
|
|
62
64
|
variant = variant.upper().strip()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if variant in KNOWN_MUTATIONS:
|
|
65
|
+
|
|
66
|
+
# Check known mutations first for fast/offline execution
|
|
67
|
+
if gene.upper() == "INSR" and variant in KNOWN_MUTATIONS:
|
|
66
68
|
return dict(KNOWN_MUTATIONS[variant])
|
|
67
|
-
|
|
69
|
+
|
|
70
|
+
# Determine UniProt ID (INSR -> P06213)
|
|
71
|
+
uniprot_id = "P06213" if gene.upper() == "INSR" else gene.strip()
|
|
72
|
+
|
|
73
|
+
# Extract residue index from variant (e.g. V938M -> 938)
|
|
74
|
+
match = re.search(r'\d+', variant)
|
|
75
|
+
if not match:
|
|
76
|
+
return {"scalar": 0.5, "desc": "Unknown mutation format (assumed 50% loss of function)", "residue": None}
|
|
77
|
+
|
|
78
|
+
residue_idx = int(match.group())
|
|
79
|
+
|
|
80
|
+
# Query AlphaFold
|
|
81
|
+
af_result = AlphaFoldGenomicsEngine.evaluate_plddt_impact(uniprot_id, residue_idx)
|
|
82
|
+
if "error" in af_result:
|
|
83
|
+
return {"scalar": 0.5, "desc": f"AlphaFold fallback (50% loss): {af_result['error']}", "residue": residue_idx}
|
|
84
|
+
|
|
85
|
+
desc = f"AlphaFold pLDDT: {af_result['plddt']}. {af_result['conclusion']}"
|
|
86
|
+
return {"scalar": af_result['scalar'], "desc": desc, "residue": residue_idx}
|
|
68
87
|
|
|
69
88
|
@staticmethod
|
|
70
89
|
def run_multi_scale_simulation(
|
iints/utils/plotting.py
CHANGED
|
@@ -54,6 +54,11 @@ def apply_plot_style(
|
|
|
54
54
|
"ytick.labelsize": 10,
|
|
55
55
|
"axes.spines.top": False,
|
|
56
56
|
"axes.spines.right": False,
|
|
57
|
+
# Scientific styles can enable mathtext tick formatting. Some
|
|
58
|
+
# Matplotlib/macOS combinations then try to parse empty ticklabels
|
|
59
|
+
# during tight_layout(), which can abort report generation.
|
|
60
|
+
"axes.formatter.use_mathtext": False,
|
|
61
|
+
"text.usetex": False,
|
|
57
62
|
}
|
|
58
63
|
)
|
|
59
64
|
return colors
|
iints_desktop/engine.py
CHANGED
|
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
7
|
from importlib import metadata
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any, Callable
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@dataclass(frozen=True)
|
|
@@ -174,6 +174,7 @@ def run_demo_preset(
|
|
|
174
174
|
desktop_preset_key: str = DEFAULT_DESKTOP_PRESET_KEY,
|
|
175
175
|
preset_name: str | None = None,
|
|
176
176
|
seed: int = 42,
|
|
177
|
+
step_callback: Callable[[int, int, float], None] | None = None,
|
|
177
178
|
) -> DesktopRunResult:
|
|
178
179
|
"""Run one deterministic demo preset through the normal SDK engine.
|
|
179
180
|
|
|
@@ -205,6 +206,7 @@ def run_demo_preset(
|
|
|
205
206
|
time_step=int(preset["time_step_minutes"]),
|
|
206
207
|
seed=seed,
|
|
207
208
|
output_dir=target,
|
|
209
|
+
step_callback=step_callback,
|
|
208
210
|
)
|
|
209
211
|
|
|
210
212
|
results_csv = _optional_path(outputs.get("results_csv"))
|
|
@@ -243,6 +245,71 @@ def run_demo_preset(
|
|
|
243
245
|
return result
|
|
244
246
|
|
|
245
247
|
|
|
248
|
+
def run_custom_preset(
|
|
249
|
+
*,
|
|
250
|
+
output_dir: str | Path,
|
|
251
|
+
custom_preset: dict[str, Any],
|
|
252
|
+
seed: int = 42,
|
|
253
|
+
step_callback: Callable[[int, int, float], None] | None = None,
|
|
254
|
+
) -> DesktopRunResult:
|
|
255
|
+
"""Run a dynamically constructed scenario from the UI."""
|
|
256
|
+
base_output = Path(output_dir).expanduser().resolve()
|
|
257
|
+
mpl_cache = base_output / ".cache" / "matplotlib"
|
|
258
|
+
mpl_cache.mkdir(parents=True, exist_ok=True)
|
|
259
|
+
os.environ.setdefault("MPLCONFIGDIR", str(mpl_cache))
|
|
260
|
+
|
|
261
|
+
from iints.core.algorithms.clinical_baseline import ClinicalBaselineAlgorithm
|
|
262
|
+
from iints.highlevel import run_full
|
|
263
|
+
|
|
264
|
+
resolved_preset_name = custom_preset.get("name", "custom_scenario")
|
|
265
|
+
folder_name = _safe_slug(f"custom-{resolved_preset_name}-{seed}")
|
|
266
|
+
target = base_output / folder_name
|
|
267
|
+
outputs: dict[str, Any] = run_full(
|
|
268
|
+
algorithm=ClinicalBaselineAlgorithm(),
|
|
269
|
+
scenario=custom_preset.get("scenario", {}),
|
|
270
|
+
patient_config=custom_preset.get("patient_config", {}),
|
|
271
|
+
duration_minutes=int(custom_preset.get("duration_minutes", 1440)),
|
|
272
|
+
time_step=int(custom_preset.get("time_step_minutes", 5)),
|
|
273
|
+
seed=seed,
|
|
274
|
+
output_dir=target,
|
|
275
|
+
step_callback=step_callback,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
results_csv = _optional_path(outputs.get("results_csv"))
|
|
279
|
+
report_pdf = _optional_path(outputs.get("report_pdf"))
|
|
280
|
+
config_path = _optional_path(outputs.get("config_path"))
|
|
281
|
+
run_id = str(outputs.get("run_id", "unknown-run"))
|
|
282
|
+
summary = "\n".join(
|
|
283
|
+
line
|
|
284
|
+
for line in [
|
|
285
|
+
"Workflow: Custom Scenario Builder",
|
|
286
|
+
f"SDK preset: {resolved_preset_name}",
|
|
287
|
+
f"Seed: {seed}",
|
|
288
|
+
f"Run completed: {run_id}",
|
|
289
|
+
f"Output folder: {target}",
|
|
290
|
+
f"Results CSV: {results_csv}" if results_csv else "Results CSV: not generated",
|
|
291
|
+
f"Clinical report: {report_pdf}" if report_pdf else "Clinical report: not generated",
|
|
292
|
+
"Research only: not a medical device and not for treatment decisions.",
|
|
293
|
+
]
|
|
294
|
+
)
|
|
295
|
+
result = DesktopRunResult(
|
|
296
|
+
run_id=run_id,
|
|
297
|
+
workflow_title="Custom Scenario",
|
|
298
|
+
preset_name=resolved_preset_name,
|
|
299
|
+
seed=seed,
|
|
300
|
+
output_dir=target,
|
|
301
|
+
results_csv=results_csv,
|
|
302
|
+
report_pdf=report_pdf,
|
|
303
|
+
config_path=config_path,
|
|
304
|
+
summary=summary,
|
|
305
|
+
)
|
|
306
|
+
try:
|
|
307
|
+
append_run_history(base_output, result)
|
|
308
|
+
except OSError:
|
|
309
|
+
pass
|
|
310
|
+
return result
|
|
311
|
+
|
|
312
|
+
|
|
246
313
|
def _optional_path(value: object) -> Path | None:
|
|
247
314
|
if value is None:
|
|
248
315
|
return None
|
iints_desktop/local_ai.py
CHANGED
|
@@ -19,6 +19,7 @@ RECOMMENDED_OLLAMA_MODELS = (
|
|
|
19
19
|
"llama3.1:8b",
|
|
20
20
|
"qwen2.5:7b",
|
|
21
21
|
"gemma3:4b",
|
|
22
|
+
"hf.co/devanshamin/PubMedDiabetes-LLM-Predictions",
|
|
22
23
|
)
|
|
23
24
|
|
|
24
25
|
|
|
@@ -45,22 +46,19 @@ class LocalAIStartResult:
|
|
|
45
46
|
pulled_model: bool = False
|
|
46
47
|
|
|
47
48
|
|
|
48
|
-
SYSTEM_PROMPT = """You are the
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Limitations
|
|
62
|
-
Next checks
|
|
63
|
-
- Use short paragraphs or numbered points. Do not use markdown tables, decorative separators, or long raw bullet lists.
|
|
49
|
+
SYSTEM_PROMPT = """You are a highly advanced Medical Data Scientist and Computational Biologist analyzing output from the IINTS-AF closed-loop insulin simulator.
|
|
50
|
+
|
|
51
|
+
CRITICAL INSTRUCTIONS:
|
|
52
|
+
1. Speak with absolute professional authority and scientific rigor. Do NOT sound like an AI assistant. Do NOT use vague conversational filler.
|
|
53
|
+
2. Analyze the provided clinical simulation data. Extract meaningful physiological insights, glycemic control patterns (Time in Range, Coefficient of Variation), and algorithmic behaviors (e.g. basal suspension aggressiveness, insulin resistance masking).
|
|
54
|
+
3. Do not include boilerplate legal disclaimers at the beginning of your text. If necessary, include a single sentence limitation at the very end.
|
|
55
|
+
4. Structure your response explicitly using these exact headers:
|
|
56
|
+
Clinical Overview
|
|
57
|
+
Biomathematical Observations
|
|
58
|
+
Algorithmic Behavior
|
|
59
|
+
Conclusions
|
|
60
|
+
5. Use highly specific scientific terminology (e.g., exogenous insulin kinetics, hepatic glucose production, PI3K/AKT pathway attenuation).
|
|
61
|
+
6. Acknowledge that the IINTS-AF simulator is for research and education only. It is Not a medical device. Do not provide diagnosis, insulin dosing, or treatment advice.
|
|
64
62
|
"""
|
|
65
63
|
|
|
66
64
|
|
|
@@ -280,7 +278,12 @@ def format_ai_answer(text: str) -> str:
|
|
|
280
278
|
line = "• " + line[2:].strip()
|
|
281
279
|
if line.startswith(("---", "___", "***")):
|
|
282
280
|
continue
|
|
283
|
-
if line.lower().rstrip(":") in {
|
|
281
|
+
if line.lower().rstrip(":") in {
|
|
282
|
+
"clinical overview",
|
|
283
|
+
"biomathematical observations",
|
|
284
|
+
"algorithmic behavior",
|
|
285
|
+
"conclusions"
|
|
286
|
+
}:
|
|
284
287
|
line = line.rstrip(":").title()
|
|
285
288
|
lines.append(line)
|
|
286
289
|
return "\n".join(lines).strip()
|
|
@@ -309,8 +312,8 @@ def ask_local_ai(
|
|
|
309
312
|
user_prompt = (
|
|
310
313
|
f"Result context:\n{context}\n\n"
|
|
311
314
|
f"User question:\n{question.strip()}\n\n"
|
|
312
|
-
"
|
|
313
|
-
"
|
|
315
|
+
"Perform a highly technical, rigorous analysis based purely on the data. "
|
|
316
|
+
"Do not offer generic advice. Structure the response perfectly."
|
|
314
317
|
)
|
|
315
318
|
answer = backend.complete(system_prompt=SYSTEM_PROMPT, user_prompt=user_prompt)
|
|
316
319
|
resolved = backend.resolved_model_name or model
|