minima-cli 0.4.9__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.
- minima/__init__.py +5 -0
- minima/api/__init__.py +1 -0
- minima/api/auth.py +39 -0
- minima/api/errors.py +40 -0
- minima/api/routers/__init__.py +1 -0
- minima/api/routers/calibration.py +50 -0
- minima/api/routers/feedback.py +279 -0
- minima/api/routers/health.py +50 -0
- minima/api/routers/models.py +42 -0
- minima/api/routers/recommend.py +66 -0
- minima/api/routers/savings.py +55 -0
- minima/api/routers/strategies.py +33 -0
- minima/catalog/__init__.py +1 -0
- minima/catalog/data/capability_priors.json +210 -0
- minima/catalog/data/model_aliases.json +12 -0
- minima/catalog/merge.py +69 -0
- minima/catalog/refresh.py +54 -0
- minima/catalog/sources/__init__.py +1 -0
- minima/catalog/sources/litellm.py +19 -0
- minima/catalog/sources/openrouter.py +25 -0
- minima/catalog/store.py +86 -0
- minima/config.py +288 -0
- minima/deps.py +35 -0
- minima/llm/__init__.py +1 -0
- minima/llm/anthropic.py +106 -0
- minima/llm/base.py +196 -0
- minima/llm/gemini.py +124 -0
- minima/llm/registry.py +54 -0
- minima/logging.py +28 -0
- minima/main.py +109 -0
- minima/memory/__init__.py +1 -0
- minima/memory/adapter.py +572 -0
- minima/memory/keys.py +83 -0
- minima/memory/records.py +190 -0
- minima/memory/threadpool.py +41 -0
- minima/metrics/__init__.py +1 -0
- minima/metrics/calibration.py +415 -0
- minima/metrics/report.py +116 -0
- minima/metrics/savings.py +98 -0
- minima/recommender/__init__.py +1 -0
- minima/recommender/_pg_pool.py +38 -0
- minima/recommender/_redis_client.py +32 -0
- minima/recommender/aggregate.py +157 -0
- minima/recommender/classify.py +165 -0
- minima/recommender/decisionlog.py +505 -0
- minima/recommender/durablerefs.py +312 -0
- minima/recommender/engine.py +997 -0
- minima/recommender/escalation.py +83 -0
- minima/recommender/propensity.py +189 -0
- minima/recommender/recstore.py +368 -0
- minima/recommender/score.py +318 -0
- minima/recommender/types.py +166 -0
- minima/schemas/__init__.py +1 -0
- minima/schemas/common.py +73 -0
- minima/schemas/feedback.py +34 -0
- minima/schemas/models_catalog.py +36 -0
- minima/schemas/recommend.py +104 -0
- minima/schemas/savings.py +39 -0
- minima/schemas/strategies.py +57 -0
- minima/schemas/workflow.py +43 -0
- minima/seeding/__init__.py +1 -0
- minima/seeding/items.py +42 -0
- minima/seeding/llmrouterbench.py +232 -0
- minima/seeding/routerbench.py +141 -0
- minima/seeding/run_seed.py +56 -0
- minima/seeding/synthetic.py +70 -0
- minima/tenancy/__init__.py +8 -0
- minima/tenancy/context.py +37 -0
- minima/tenancy/passthrough.py +110 -0
- minima/version.py +3 -0
- minima_cli-0.4.9.dist-info/METADATA +275 -0
- minima_cli-0.4.9.dist-info/RECORD +161 -0
- minima_cli-0.4.9.dist-info/WHEEL +4 -0
- minima_cli-0.4.9.dist-info/entry_points.txt +5 -0
- minima_cli-0.4.9.dist-info/licenses/LICENSE +295 -0
- minima_client/__init__.py +19 -0
- minima_client/autocapture.py +101 -0
- minima_client/client.py +301 -0
- minima_client/errors.py +23 -0
- minima_harness/LICENSE_PI +32 -0
- minima_harness/__init__.py +16 -0
- minima_harness/agent/__init__.py +72 -0
- minima_harness/agent/agent.py +276 -0
- minima_harness/agent/events.py +124 -0
- minima_harness/agent/loop.py +311 -0
- minima_harness/agent/state.py +79 -0
- minima_harness/agent/tools.py +97 -0
- minima_harness/ai/__init__.py +66 -0
- minima_harness/ai/compat.py +71 -0
- minima_harness/ai/errors.py +96 -0
- minima_harness/ai/events.py +117 -0
- minima_harness/ai/openrouter_catalog.py +153 -0
- minima_harness/ai/provider_catalog.py +299 -0
- minima_harness/ai/provider_quirks.py +37 -0
- minima_harness/ai/providers/__init__.py +75 -0
- minima_harness/ai/providers/_common.py +48 -0
- minima_harness/ai/providers/anthropic.py +290 -0
- minima_harness/ai/providers/base.py +65 -0
- minima_harness/ai/providers/faux.py +173 -0
- minima_harness/ai/providers/google.py +221 -0
- minima_harness/ai/providers/openai_compat.py +278 -0
- minima_harness/ai/registry.py +184 -0
- minima_harness/ai/stream.py +82 -0
- minima_harness/ai/tools.py +51 -0
- minima_harness/ai/types.py +204 -0
- minima_harness/ai/usage.py +41 -0
- minima_harness/minima/__init__.py +40 -0
- minima_harness/minima/cache.py +102 -0
- minima_harness/minima/config.py +85 -0
- minima_harness/minima/goals.py +226 -0
- minima_harness/minima/judge.py +144 -0
- minima_harness/minima/mapping.py +147 -0
- minima_harness/minima/meter.py +143 -0
- minima_harness/minima/router.py +220 -0
- minima_harness/minima/runtime.py +544 -0
- minima_harness/minima/signals.py +195 -0
- minima_harness/session/__init__.py +14 -0
- minima_harness/session/format.py +35 -0
- minima_harness/session/store.py +236 -0
- minima_harness/tasks/__init__.py +17 -0
- minima_harness/tasks/task_set.py +78 -0
- minima_harness/tools/__init__.py +7 -0
- minima_harness/tools/_io.py +34 -0
- minima_harness/tools/bash.py +70 -0
- minima_harness/tools/builtin.py +23 -0
- minima_harness/tools/edit.py +50 -0
- minima_harness/tools/find.py +38 -0
- minima_harness/tools/grep.py +73 -0
- minima_harness/tools/ls.py +35 -0
- minima_harness/tools/read.py +38 -0
- minima_harness/tools/tasks.py +75 -0
- minima_harness/tools/write.py +36 -0
- minima_harness/tui/__init__.py +3 -0
- minima_harness/tui/analytics.py +111 -0
- minima_harness/tui/app.py +1927 -0
- minima_harness/tui/bridge.py +103 -0
- minima_harness/tui/cli.py +227 -0
- minima_harness/tui/clipboard.py +60 -0
- minima_harness/tui/commands.py +49 -0
- minima_harness/tui/compaction.py +17 -0
- minima_harness/tui/config_cli.py +141 -0
- minima_harness/tui/config_store.py +237 -0
- minima_harness/tui/context.py +93 -0
- minima_harness/tui/customize.py +95 -0
- minima_harness/tui/diff.py +53 -0
- minima_harness/tui/editor.py +43 -0
- minima_harness/tui/extensions.py +84 -0
- minima_harness/tui/extra_models.py +52 -0
- minima_harness/tui/history.py +71 -0
- minima_harness/tui/mubit.py +295 -0
- minima_harness/tui/overlays.py +593 -0
- minima_harness/tui/packages.py +59 -0
- minima_harness/tui/run_modes.py +66 -0
- minima_harness/tui/theme.py +77 -0
- minima_harness/tui/welcome.py +83 -0
- minima_harness/tui/widgets/__init__.py +3 -0
- minima_harness/tui/widgets/banner.py +38 -0
- minima_harness/tui/widgets/editor.py +83 -0
- minima_harness/tui/widgets/footer.py +73 -0
- minima_harness/tui/widgets/messages.py +151 -0
- minima_harness/tui/widgets/status.py +57 -0
minima/llm/gemini.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Gemini-backed reasoner (best-effort, secondary provider).
|
|
2
|
+
|
|
3
|
+
Uses google-genai structured output. Any error degrades gracefully to None.
|
|
4
|
+
|
|
5
|
+
Gemini's ``response_schema`` is a constrained dialect of JSON Schema (a single ``type``
|
|
6
|
+
per field plus a ``nullable`` flag; no ``type: [...]`` unions and no
|
|
7
|
+
``additionalProperties``), so the Anthropic/strict-JSON schemas in ``llm.base`` can't be
|
|
8
|
+
reused verbatim — these Gemini-native equivalents below are what the API accepts.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from minima.llm.base import (
|
|
18
|
+
CLASSIFY_SYSTEM,
|
|
19
|
+
RANK_SYSTEM,
|
|
20
|
+
CandidateView,
|
|
21
|
+
ReasonerResult,
|
|
22
|
+
build_rank_user,
|
|
23
|
+
parse_classification,
|
|
24
|
+
parse_ranking,
|
|
25
|
+
)
|
|
26
|
+
from minima.logging import get_logger
|
|
27
|
+
from minima.schemas.common import Difficulty, TaskType
|
|
28
|
+
|
|
29
|
+
log = get_logger("minima.llm.gemini")
|
|
30
|
+
|
|
31
|
+
DEFAULT_MODEL = "gemini-2.5-flash"
|
|
32
|
+
|
|
33
|
+
_GEMINI_RANKING_SCHEMA: dict[str, Any] = {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"recommended": {"type": "string"},
|
|
37
|
+
"fallback": {"type": "string", "nullable": True},
|
|
38
|
+
"ranking": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"items": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"model_id": {"type": "string"},
|
|
44
|
+
"predicted_success": {"type": "number"},
|
|
45
|
+
"rationale": {"type": "string"},
|
|
46
|
+
},
|
|
47
|
+
"required": ["model_id", "predicted_success", "rationale"],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
"required": ["recommended", "ranking"],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_GEMINI_CLASSIFY_SCHEMA: dict[str, Any] = {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"task_type": {"type": "string", "enum": [t.value for t in TaskType]},
|
|
58
|
+
"difficulty": {"type": "string", "enum": [d.value for d in Difficulty]},
|
|
59
|
+
},
|
|
60
|
+
"required": ["task_type", "difficulty"],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class GeminiReasoner:
|
|
65
|
+
def __init__(self, *, model: str, api_key: str, timeout_ms: int, max_tokens: int):
|
|
66
|
+
import google.genai as genai # lazy; optional extra
|
|
67
|
+
|
|
68
|
+
self._genai = genai
|
|
69
|
+
self._model = model
|
|
70
|
+
self._max_tokens = max_tokens
|
|
71
|
+
# Bound the call so a hung provider can't stall a recommendation (genai timeout
|
|
72
|
+
# is in milliseconds). Without this the Gemini client would wait indefinitely.
|
|
73
|
+
self._client = genai.Client(api_key=api_key, http_options={"timeout": timeout_ms})
|
|
74
|
+
|
|
75
|
+
async def _json_call(self, *, system: str, user: str, schema: dict) -> Any | None:
|
|
76
|
+
try:
|
|
77
|
+
resp = await self._client.aio.models.generate_content(
|
|
78
|
+
model=self._model,
|
|
79
|
+
contents=user,
|
|
80
|
+
config={
|
|
81
|
+
"system_instruction": system,
|
|
82
|
+
"response_mime_type": "application/json",
|
|
83
|
+
"response_schema": schema,
|
|
84
|
+
"max_output_tokens": self._max_tokens,
|
|
85
|
+
},
|
|
86
|
+
)
|
|
87
|
+
text = getattr(resp, "text", None)
|
|
88
|
+
if not text:
|
|
89
|
+
return None
|
|
90
|
+
return json.loads(text)
|
|
91
|
+
except Exception as exc: # noqa: BLE001 — reasoner must never break a recommendation
|
|
92
|
+
log.warning("reasoner_call_failed", model=self._model, error=str(exc))
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
async def rank(
|
|
96
|
+
self,
|
|
97
|
+
*,
|
|
98
|
+
task: str,
|
|
99
|
+
task_type: str,
|
|
100
|
+
difficulty: str,
|
|
101
|
+
candidates: Sequence[CandidateView],
|
|
102
|
+
memory_block: str,
|
|
103
|
+
cost_quality_tradeoff: float,
|
|
104
|
+
) -> ReasonerResult | None:
|
|
105
|
+
user = build_rank_user(
|
|
106
|
+
task=task,
|
|
107
|
+
task_type=task_type,
|
|
108
|
+
difficulty=difficulty,
|
|
109
|
+
candidates=candidates,
|
|
110
|
+
memory_block=memory_block,
|
|
111
|
+
cost_quality_tradeoff=cost_quality_tradeoff,
|
|
112
|
+
)
|
|
113
|
+
data = await self._json_call(system=RANK_SYSTEM, user=user, schema=_GEMINI_RANKING_SCHEMA)
|
|
114
|
+
if data is None:
|
|
115
|
+
return None
|
|
116
|
+
return parse_ranking(data, {c.model_id for c in candidates})
|
|
117
|
+
|
|
118
|
+
async def classify(self, *, task: str) -> tuple[TaskType, Difficulty] | None:
|
|
119
|
+
data = await self._json_call(
|
|
120
|
+
system=CLASSIFY_SYSTEM, user=task[:2000], schema=_GEMINI_CLASSIFY_SCHEMA
|
|
121
|
+
)
|
|
122
|
+
if data is None:
|
|
123
|
+
return None
|
|
124
|
+
return parse_classification(data)
|
minima/llm/registry.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Build a reasoner from settings, or None when disabled/unconfigured."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from minima.config import Settings
|
|
6
|
+
from minima.llm.base import Reasoner
|
|
7
|
+
from minima.logging import get_logger
|
|
8
|
+
|
|
9
|
+
log = get_logger("minima.llm")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_reasoner(settings: Settings) -> Reasoner | None:
|
|
13
|
+
provider = settings.minima_reasoner_provider.lower().strip()
|
|
14
|
+
if provider in ("", "none"):
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
if provider == "anthropic":
|
|
18
|
+
if not settings.anthropic_api_key:
|
|
19
|
+
log.warning("reasoner_disabled_no_key", provider="anthropic")
|
|
20
|
+
return None
|
|
21
|
+
# The provider SDK is imported lazily (here and inside the constructor), so
|
|
22
|
+
# catch ImportError across both: a missing extra must degrade, not crash startup.
|
|
23
|
+
try:
|
|
24
|
+
from minima.llm.anthropic import DEFAULT_MODEL, AnthropicReasoner
|
|
25
|
+
|
|
26
|
+
return AnthropicReasoner(
|
|
27
|
+
model=settings.minima_reasoner_model or DEFAULT_MODEL,
|
|
28
|
+
api_key=settings.anthropic_api_key,
|
|
29
|
+
timeout_ms=settings.minima_reasoner_timeout_ms,
|
|
30
|
+
max_tokens=settings.minima_reasoner_max_tokens,
|
|
31
|
+
)
|
|
32
|
+
except ImportError:
|
|
33
|
+
log.warning("reasoner_extra_missing", provider="anthropic", extra="reasoner-anthropic")
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
if provider == "gemini":
|
|
37
|
+
if not settings.gemini_api_key:
|
|
38
|
+
log.warning("reasoner_disabled_no_key", provider="gemini")
|
|
39
|
+
return None
|
|
40
|
+
try:
|
|
41
|
+
from minima.llm.gemini import DEFAULT_MODEL, GeminiReasoner
|
|
42
|
+
|
|
43
|
+
return GeminiReasoner(
|
|
44
|
+
model=settings.minima_reasoner_model or DEFAULT_MODEL,
|
|
45
|
+
api_key=settings.gemini_api_key,
|
|
46
|
+
timeout_ms=settings.minima_reasoner_timeout_ms,
|
|
47
|
+
max_tokens=settings.minima_reasoner_max_tokens,
|
|
48
|
+
)
|
|
49
|
+
except ImportError:
|
|
50
|
+
log.warning("reasoner_extra_missing", provider="gemini", extra="reasoner-gemini")
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
log.warning("reasoner_unknown_provider", provider=provider)
|
|
54
|
+
return None
|
minima/logging.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Structured logging setup."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import structlog
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def configure_logging(level: str = "info") -> None:
|
|
11
|
+
log_level = getattr(logging, level.upper(), logging.INFO)
|
|
12
|
+
logging.basicConfig(format="%(message)s", level=log_level)
|
|
13
|
+
structlog.configure(
|
|
14
|
+
wrapper_class=structlog.make_filtering_bound_logger(log_level),
|
|
15
|
+
processors=[
|
|
16
|
+
structlog.contextvars.merge_contextvars,
|
|
17
|
+
structlog.processors.add_log_level,
|
|
18
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
19
|
+
structlog.processors.StackInfoRenderer(),
|
|
20
|
+
structlog.processors.format_exc_info,
|
|
21
|
+
structlog.processors.JSONRenderer(),
|
|
22
|
+
],
|
|
23
|
+
cache_logger_on_first_use=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_logger(name: str = "minima") -> structlog.stdlib.BoundLogger:
|
|
28
|
+
return structlog.get_logger(name)
|
minima/main.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""FastAPI application factory and lifespan wiring."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import contextlib
|
|
7
|
+
from collections.abc import AsyncIterator
|
|
8
|
+
|
|
9
|
+
from fastapi import FastAPI
|
|
10
|
+
|
|
11
|
+
from minima.api.errors import register_error_handlers
|
|
12
|
+
from minima.api.routers import (
|
|
13
|
+
calibration,
|
|
14
|
+
feedback,
|
|
15
|
+
health,
|
|
16
|
+
models,
|
|
17
|
+
recommend,
|
|
18
|
+
savings,
|
|
19
|
+
strategies,
|
|
20
|
+
)
|
|
21
|
+
from minima.catalog.refresh import refresh_loop
|
|
22
|
+
from minima.catalog.store import CatalogStore
|
|
23
|
+
from minima.config import Settings, get_settings
|
|
24
|
+
from minima.llm.registry import build_reasoner
|
|
25
|
+
from minima.logging import configure_logging
|
|
26
|
+
from minima.memory.adapter import Memory
|
|
27
|
+
from minima.recommender.decisionlog import build_decision_log
|
|
28
|
+
from minima.recommender.durablerefs import build_durable_refs
|
|
29
|
+
from minima.recommender.engine import Recommender
|
|
30
|
+
from minima.recommender.propensity import build_propensity
|
|
31
|
+
from minima.recommender.recstore import LaneCounter, RecStore, build_recstore
|
|
32
|
+
from minima.tenancy.passthrough import PassthroughRuntime
|
|
33
|
+
from minima.version import __version__
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@contextlib.asynccontextmanager
|
|
37
|
+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
38
|
+
settings: Settings = app.state.settings
|
|
39
|
+
configure_logging(settings.minima_log_level)
|
|
40
|
+
|
|
41
|
+
injected: dict = getattr(app.state, "_injected", {})
|
|
42
|
+
catalog_store: CatalogStore = injected.get("catalog_store") or CatalogStore(settings)
|
|
43
|
+
recstore_backend: RecStore = injected.get("recstore") or build_recstore(settings)
|
|
44
|
+
propensity_backend = build_propensity(settings)
|
|
45
|
+
decision_log_backend = build_decision_log(settings)
|
|
46
|
+
reasoner = build_reasoner(settings)
|
|
47
|
+
lane_counter = LaneCounter()
|
|
48
|
+
|
|
49
|
+
app.state.catalog_store = catalog_store
|
|
50
|
+
app.state.lane_counter = lane_counter
|
|
51
|
+
injected_memory: Memory | None = injected.get("memory")
|
|
52
|
+
app.state.passthrough_runtime = injected.get("passthrough_runtime") or PassthroughRuntime(
|
|
53
|
+
settings=settings,
|
|
54
|
+
catalog_store=catalog_store,
|
|
55
|
+
reasoner=reasoner,
|
|
56
|
+
recstore_backend=recstore_backend,
|
|
57
|
+
propensity_backend=propensity_backend,
|
|
58
|
+
lane_counter=lane_counter,
|
|
59
|
+
memory_factory=(lambda _key: injected_memory) if injected_memory is not None else None,
|
|
60
|
+
decision_log_backend=decision_log_backend,
|
|
61
|
+
durable_refs_backend=build_durable_refs(settings),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
refresh_task: asyncio.Task | None = None
|
|
65
|
+
if getattr(app.state, "_start_refresh", True):
|
|
66
|
+
refresh_task = asyncio.create_task(refresh_loop(settings, catalog_store))
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
yield
|
|
70
|
+
finally:
|
|
71
|
+
if refresh_task is not None:
|
|
72
|
+
refresh_task.cancel()
|
|
73
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
74
|
+
await refresh_task
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def create_app(
|
|
78
|
+
*,
|
|
79
|
+
settings: Settings | None = None,
|
|
80
|
+
memory: Memory | None = None,
|
|
81
|
+
catalog_store: CatalogStore | None = None,
|
|
82
|
+
recstore: RecStore | None = None,
|
|
83
|
+
recommender: Recommender | None = None,
|
|
84
|
+
passthrough_runtime: PassthroughRuntime | None = None,
|
|
85
|
+
start_refresh: bool = True,
|
|
86
|
+
) -> FastAPI:
|
|
87
|
+
app = FastAPI(title="Minima", version=__version__, lifespan=lifespan)
|
|
88
|
+
app.state.settings = settings or get_settings()
|
|
89
|
+
app.state._injected = {
|
|
90
|
+
"memory": memory,
|
|
91
|
+
"catalog_store": catalog_store,
|
|
92
|
+
"recstore": recstore,
|
|
93
|
+
"recommender": recommender,
|
|
94
|
+
"passthrough_runtime": passthrough_runtime,
|
|
95
|
+
}
|
|
96
|
+
app.state._start_refresh = start_refresh
|
|
97
|
+
|
|
98
|
+
register_error_handlers(app)
|
|
99
|
+
app.include_router(recommend.router)
|
|
100
|
+
app.include_router(feedback.router)
|
|
101
|
+
app.include_router(models.router)
|
|
102
|
+
app.include_router(strategies.router)
|
|
103
|
+
app.include_router(savings.router)
|
|
104
|
+
app.include_router(calibration.router)
|
|
105
|
+
app.include_router(health.router)
|
|
106
|
+
return app
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
app = create_app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Mubit memory integration — the only place the Mubit SDK is touched."""
|