**Prepared sessions.** Most of this existed: unanswered first, weakest topic next, wrong-before-right after that, all scaled by what share of the real paper each topic carries. What it could not do was change with time, say anything about itself, or be reached without filling in a form. Evidence now decays on a thirty-day half-life. Exponential rather than a fixed window because memory has a slope, not a cliff — under a window, 29 days counts fully and 31 counts for nothing — and because it is memoryless, so an answer's weight does not shift when unrelated questions are answered, which is what lets the preview stay a valid forecast. Spring is worth an eighth of last week. Two things decay: a question's recall probability, drifting towards even rather than past it, so an old right answer becomes eligible rather than wrong; and a topic's accuracy, against a prior of two "no idea" answers, which fixes "right once, known forever". Strict unanswered-first meant that on a bank of 2,900 nothing was ever recycled — spaced repetition existed and was unreachable. Review now takes up to two fifths of a session. And the damping that spread the picks across topics was applied only to seen material, so a learner with no history was handed the heaviest domain entire instead of a spread; that was live. The plan is the product. It is computed, shown, and then the session is built from that plan's own ids and the plan returned with it, so the two cannot differ; every figure in it is a tally over the chosen questions rather than a forecast. No model touches the ranking — a learner asking "why these twenty" has to get the same answer twice. **Vision.** The proxy's own `/model/info` says which models can see, so nothing is hard-coded: 77 report yes, 11 no, and 328 say nothing at all, which means absent rather than incapable — so those are asked once with an 8px PNG and the refusal cached. The deployment's main model turns out not to see, and questions carry figures the learner is looking at, so the tutor was answering about an image it had never been shown. It routes to a configured tool model now, folds the description back in as text saying plainly where it came from, and caches on the bytes because the same figure is re-sent every turn. Also fixed on the way: `article` was missing from the admin's task list, so article drafting always ran on the fallback model whatever an administrator chose; and `.jpx` stem images were sent as JPEG because `mimetypes` guesses that from the name, so the provider rejected them two hops later. An administrator must pick a tool model in Settings → AI models. Until then the tutor says a figure exists that nothing could read, rather than describing one it cannot see. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
34 lines
864 B
Python
34 lines
864 B
Python
from datetime import datetime
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class AIModelConfigCreate(BaseModel):
|
|
model_config = {"protected_namespaces": ()}
|
|
name: str
|
|
model_id: str
|
|
task: str # extraction, tts, stt, teach, keyword, flashcard, article, tool
|
|
api_key: str | None = None
|
|
is_active: bool = True
|
|
is_default: bool = False
|
|
|
|
|
|
class AIModelConfigResponse(BaseModel):
|
|
model_config = {"protected_namespaces": (), "from_attributes": True}
|
|
id: int
|
|
name: str
|
|
model_id: str
|
|
task: str
|
|
is_active: bool
|
|
is_default: bool
|
|
created_at: datetime
|
|
|
|
|
|
class AIModelConfigUpdate(BaseModel):
|
|
model_config = {"protected_namespaces": ()}
|
|
name: str | None = None
|
|
model_id: str | None = None
|
|
task: str | None = None
|
|
api_key: str | None = None
|
|
is_active: bool | None = None
|
|
is_default: bool | None = None
|