- Context-overflow self-heal. Strict backends (vLLM) reject
prompt + max_tokens > contextwith a 400 instead of clamping. houtini-lm sizes its output budget from the context reported by/v1/models— but that number is wrong when a proxy sits in front (e.g. a LiteLLM router advertising a generic 100k window over a model actually loaded at 64k), producing a spurious 400 (max_completion_tokens=99002 … max_model_len=65536). houtini-lm now parses the real limit from the backend's own error and retries once with a corrected budget — robust even when the advertised context is wrong, and self-correcting across vLLM / OpenAI / llama.cpp error shapes. Newsrc/context-overflow.ts(unit-tested vianpm run test:overflow). The parser also tolerates thousands separators in the limit (max_model_len=65,536).
Docs-only release (no code change). Ships the README addition documenting
HOUTINI_LM_THINKING; the substantive backend notes below live in
docs/VLLM-BACKEND.md on GitHub (docs/ is not part of the npm tarball).
- Backend setup guides: new step-by-step SETUP-VLLM.md and SETUP-LMSTUDIO.md, each carrying the traps that cause silent failures (no-think toggle, tool-call parser, cold-start timeouts, the Ada block-FP8 kernel trap). Linked from the README quick start and endpoints table.
- README: the
HOUTINI_LM_THINKINGenv var now appears in the configuration table on npm. - LiteLLM router topology — houtini-lm can sit behind a LiteLLM router that fronts local vLLM + DeepSeek V4 behind one endpoint, selecting tier per call via the
modelparam. The router must keepdrop_params: falseso the nestedchat_template_kwargsno-think toggle reaches vLLM. - Reasoning-model token budgets generalised from Qwen to every reasoning model (Qwen, DeepSeek V4): the
max_tokenscap counts reasoning + answer together, so a low cap returns empty content; a generous ceiling is not consumption. (Kimi/Moonshot evaluated and dropped for hanging on large non-streamed requests.)
HOUTINI_LM_THINKING(auto|off|on, defaultauto) — forces the no-think path regardless of model detection. Required when vLLM serves a thinking model under an alias (e.g.coder-next) that Hugging Face detection can't identify, so the toggle would otherwise never fire.offis the right default when an orchestrator (Claude) does the reasoning and the local model only executes.
- vLLM thinking models returned empty content — two causes. (1)
enable_thinking:falsewas sent only as a top-level param, which vLLM silently ignores (it reads the toggle fromchat_template_kwargs); now sent in both shapes. (2) HF-metadata detection can't see vLLM's arbitrary served-names, so a real thinking model looked non-thinking and the toggle-branch never ran — addressed byHOUTINI_LM_THINKING=off. Together the answer now lands incontent, notreasoning_content. Regression-guarded bytest-vllm-thinking.mjs; verified end-to-end against live Qwen3-Coder-Next.
max_tokensfloor (HOUTINI_LM_MIN_TOKENS, default 4096) — caller-supplied budgets below the floor are ignored and the dynamic 25%-of-context budget applies instead. MCP clients habitually pass tiny caps like 256 that strangle reasoning models. Set to0to honour any value (deliberate micro-chunking on slow hardware). Tool schema descriptions rewritten to match.- vLLM backend documentation (docs/VLLM-BACKEND.md).
- Output budget capped to context — requested/inflated
max_tokensis clamped tocontext − estimated prompt; strict backends (vLLM) previously rejected the ×4-inflated thinking-model budget with a 400 when it exceeded the context window. - Prefill estimator regime changes — the linear fit is recency-weighted (half-life 6 samples) so a backend restart with different performance settings stops poisoning the estimate; a low-confidence fit (R² < 0.5) can no longer refuse a call.
- Dependencies updated in-range (
@modelcontextprotocol/sdk1.29.0).
- Per-request sampling controls on
chat,custom_prompt,code_task,code_task_files:seed,stop,top_p,top_k,repeat_penalty,frequency_penalty,presence_penalty. All are range-validated server-side and forwarded only when set (unknown fields are ignored by backends that don't support them).seedgives reproducible output for testing. - Prefix-cache telemetry —
usage.prompt_tokens_details.cached_tokens(KV-reuse hits) is now captured and surfaced in the footer, a strong "this delegation was nearly free" signal when re-sending shared context. content_filterfinish-reason is flagged distinctly in the quality line — a refusal, not a length truncation, so the orchestrator handles it differently.max_tokensfloor (HOUTINI_LM_MIN_TOKENS, default 4096) — caller-supplied budgets below the floor are ignored and the dynamic 25%-of-context budget applies instead. MCP clients habitually pass tiny caps like 256 that strangle reasoning models (hidden thinking burns the budget before any visible output). SetHOUTINI_LM_MIN_TOKENS=0to honour any value, e.g. deliberate micro-chunking on slow hardware. Tool schema descriptions rewritten to match.
json_schemanow accepts both the documented wrapper{name, schema, strict}and a bare JSON Schema; a bare schema previously producedundefinedname/schema and silently unconstrained output.max_tokens/temperatureare range-validated before reaching the upstream request.- Prefill estimator derives its ratio fallback from the same per-call
(prompt_tokens, ttft)samples as the linear fit, instead of mixing populations (which skewed the rate and could mis-fire the pre-flight refusal). - Prefill estimator regime changes — the linear fit is now recency-weighted (half-life 6 samples), so a backend restart with different performance settings stops poisoning the estimate within a few calls; and a low-confidence fit (R² < 0.5) no longer refuses the call — the keepalive/timeout machinery handles a false-ok, whereas a false refusal blocked valid tiny inputs outright.
- Output budget capped to context — requested/inflated
max_tokensis clamped tocontext − estimated prompt, fixing a 400 from strict backends (vLLM) when the dynamic 25% budget was then ×4-inflated for thinking models to the full context window. Previously masked by callers passing tiny caps.
Breaking: minimum Node is now >=22.5 (recommended >=22.13), for node:sqlite.
- Model cache migrated from sql.js to
node:sqlite(Node's built-in SQLite) in WAL mode. Multiple houtini-lm processes sharing one cache file now get real cross-process concurrency — per-row writes and proper locking instead of whole-file snapshots — which fixes stats being clobbered under multi-agent fan-out. Still no third-party native dependency, no build step. Existing sql.js databases open unchanged. Ifnode:sqliteis unavailable (e.g. Node 22.5–22.12 without--experimental-sqlite), the cache is disabled and the server runs without persistence rather than crashing.
- Cross-process inference lock — an advisory file lock serialises inference across processes on the same machine (opt out with
HOUTINI_LM_CROSS_PROCESS_LOCK=0), so multiple agents don't hammer one loaded model in parallel. Fail-open: never blocks a call indefinitely. code_task_filesread guards — per-file size cap (HOUTINI_LM_MAX_FILE_MB, default 10) and optional root confinement (HOUTINI_LM_FILE_ROOTS, symlink-resolved).
- Mid-stream backend
errorevents are surfaced instead of returned as a silent empty success; per-token progress notifications are time-throttled so a fast model can't flood stdio;tok/sis measured over the decode window (excludes prefill);discoverreports "no model loaded" instead of presenting an unloaded model as active; thestatstool no longer crashes when called with no arguments; endpoint URLs are redacted before display. See docs/CODE-REVIEW-2026-07.md and docs/AUDIT-2026-07.md for the full list.
- Pre-fetch progress heartbeat — streaming keepalive used to start only after the upstream LLM returned HTTP response headers. On slow backends (big prompt, heavy prefill, cold model) the POST to
/v1/chat/completionscan sit open for 30–60+ seconds before headers flush, and that window was silent — tripping MCP clients with the default 60s request timeout before any progress notification could fire.chatCompletionStreamingInnernow sends a progress notification immediately on tool-call receipt (resetting the client clock as soon as the call is acknowledged) plus a 10s heartbeat while awaiting upstream response headers. The existing post-fetch prefill keepalive is preserved. Clients that honourresetTimeoutOnProgress(Claude Desktop and similar) will now survive multi-minute prefills cleanly.
HOUTINI_LM_MODELis now a genuine override, not just a routing fallback. When set, every tool call uses that model id without running through the scoring path. Previously the env var was only consulted when routing failed entirely, which meant that on OpenRouter (where all 343 models report as "loaded") the router would pick essentially at random regardless of the user's configured preference. Backwards-compatible: if the env var is unset, routing behaves exactly as before.
- Optional
modelparameter onchat,custom_prompt,code_task,code_task_files— lets the caller pin a specific model id per-tool-call, overriding both routing andHOUTINI_LM_MODEL. Especially useful on OpenRouter: passmodel: "nvidia/nemotron-3-nano-30b-a3b:free"ormodel: "moonshotai/kimi-k2.6"directly from the MCP client.embedalready had amodelparameter.
HOUTINI_LM_*env var namespace — now that multiple providers are supported theLM_STUDIO_*prefix is misleading. New preferred names:HOUTINI_LM_ENDPOINT_URL,HOUTINI_LM_API_KEY,HOUTINI_LM_MODEL,HOUTINI_LM_PROVIDER,HOUTINI_LM_CONTEXT_WINDOW. LegacyLM_STUDIO_URL/LM_STUDIO_MODEL/LM_STUDIO_PASSWORD/LM_PASSWORD/OPENROUTER_API_KEY/LM_CONTEXT_WINDOWremain accepted indefinitely — existing configs keep working untouched.- OpenRouter support — detected automatically when
LM_STUDIO_URLcontainsopenrouter.ai, or forced viaHOUTINI_LM_PROVIDER=openrouter. Attribution headers (HTTP-Referer,X-Title) are sent per request. Auth viaLM_STUDIO_PASSWORD/LM_PASSWORD/OPENROUTER_API_KEY(all three names accepted). - Provider-profile layer — small central registry (
getProviderProfile()) gates per-backend behaviour: extra request headers, inference serialisation, 429/5xx retry policy, and reasoning-model output handling. Keeps per-provider divergence in one place so adding Groq/Together/Fireworks later is a config change, not a scavenger hunt across the file. - Jittered retry-with-backoff on 429/5xx for remote providers (
fetchWithRetry). HonoursRetry-Afterheader (seconds or HTTP-date) with a 10s ceiling. Local providers are unchanged — still a single-shot fetch. - End-to-end MCP smoke test (
test-mcp-e2e.mjs) — spawns the built server over stdio and drives real tool calls. Used to validate provider paths against both LM Studio and OpenRouter without mocking.
- Inference semaphore is now provider-gated — only serialises requests for local/LM-Studio/Ollama backends (where a single GPU is being contended for). Remote providers bypass the lock and can serve parallel calls, which OpenRouter and similar multi-host backends handle natively.
- Reasoning-model handling branches on provider. Local thinking models still get
enable_thinking:false+reasoning_effort+ max_tokens inflation. OpenRouter getsreasoning: { exclude: true }(which the provider normalises across Nemotron, DeepSeek R1, Qwen3, Claude thinking etc.) plus the same max_tokens inflation to defend against providers that bill reasoning tokens against the cap before exclude filtering. - Model listing short-circuits for OpenRouter — skips LM Studio
/api/v0/modelsand Ollama/api/tagsprobes and goes straight to/v1/models, which on OpenRouter already returnscontext_lengthandarchitecture.input_modalitiesat the richness our routing needs. test.mjshonoursLM_PASSWORD/OPENROUTER_API_KEYfor bearer auth, so the same suite can run against remote providers for smoke testing.
- Ollama
delta.reasoningcapture — Ollama's OpenAI-compatible streaming emits reasoning ondelta.reasoning, notdelta.reasoning_content(which LM Studio uses). The field was silently dropped, so Ollama thinking models likeqwen3:4bproduced emptycontentwithfinish_reason=lengthat defaultmax_tokens. Now captured identically to LM Studio's channel — routing, safety-net fallback, and thereasoning-onlyquality flag all fire for Ollama. - Qwen3 base models detected as thinking-capable —
qwen3:4b,qwen3-8b,qwen3-14b-instructetc. ship withenable_thinking=truehardcoded in their Jinja template (Ollama ignores the API flag).detectThinkingSupportFromArch()now flags anyqwen3*model except coder / VL / embedding variants, somax_tokensinflation fires on first call. Previously onlyqwen3-thinking-tagged variants were recognised. - Auto-inject
modelfield — Ollama returns HTTP 400 ("model is required") when the field is absent; LM Studio accepted it and picked the loaded default. Inference path now resolves the active model from the backend before sending, so either backend behaves identically when the caller omits the model.
- Pre-flight estimator now uses ordinary-least-squares linear regression over the most recent 100
(prompt_tokens, TTFT_ms)samples per model. FitsTTFT ≈ α + β·prompt_tokensso fixed per-request overhead (α) is separated from genuine per-token prefill cost (β). The previous ratio-of-averages estimator (totalPromptTokens / totalTtftMs) systematically under-predicted for inputs much larger than the historical mean because small-prompt TTFT is dominated by the α term. Verified against real data: a mixed-size sample set containing one 6,955-token call and six ≤230-token calls fits α=668ms, β=1.49ms/token (R²=0.999); predicts 7,000-token TTFT as 11.1s, matching the observed 11.0s within 1%. Falls back to the ratio estimator when fewer than 5 samples exist, then to a conservative default for unknown models.
model_prefill_samplesSQLite table — stores individual(prompt_tokens, ttft_ms)observations per model. Capped at 100 samples per model with oldest-first pruning on each insert. Written fire-and-forget alongsidemodel_performanceso a DB hiccup never stalls a tool response.fitPrefillLinear()inmodel-cache.ts— reusable OLS implementation exposed so theshakedown.mjsscript and any future consumers can reason about prefill characteristics of the workstation.- Richer refuse diagnostic — when
code_task_filesrefuses a too-large input, the error message now shows whether the estimate came from the linear fit (with α, β, R², n) or the ratio fallback, so the caller can judge confidence in the refusal.
statsMCP tool — compact markdown dump of session + lifetime totals, per-model performance, and reasoning-token overhead. Optionalmodelfilter. Cheap to call repeatedly to watch the 💰 counter climb.- Lifetime performance persistence — a new
model_performanceSQLite table accumulates calls, TTFT, tok/s, prompt tokens, completion tokens, and reasoning tokens across sessions. Footer now readsthis session: X · lifetime: Y.discovershows both session and lifetime speed lines, with alast useddate. Data lives in~/.houtini-lm/model-cache.dbalongside the existing profile cache. - Backend detection — startup probe distinguishes LM Studio (
/api/v0/models), Ollama (/api/tags), and generic OpenAI-compatible (/v1/models). Surfaced indiscoveroutput. Inference stays on the portable/v1/chat/completionspath regardless — detection only steers enrichment and the per-backendreasoning_effortmapping. delta.reasoning_contentcapture — LM Studio's "Separate reasoning_content" dev toggle, DeepSeek R1, and Nemotron stream reasoning via this vendor-extension field. Previously discarded → silent empty bodies when the model exhausted its output budget on reasoning. Now captured into a buffer and returned as a last-ditch fallback with areasoning-onlyquality flag so the caller sees something.- Prefill keep-alive — a timer fires
notifications/progressevery 10s while waiting for the first chunk, preventing the MCP client's ~60s request timeout from firing during long prompt processing on slow hardware with big inputs. - Split prefill vs mid-stream timeouts —
PREFILL_TIMEOUT_MS(180s) applies until the first chunk arrives;READ_CHUNK_TIMEOUT_MS(30s) takes over afterwards. NewPREFILL-STALLquality flag when truncation happens before any chunk. - Pre-flight token estimator for
code_task_files— uses measured per-model prefill rate from SQLite to refuse obviously-over-budget inputs early with a concrete diagnostic (estimated prefill seconds, tokens, sample count) instead of letting them silently hang. Only fires after ≥2 measured samples — first-time callers are never refused. - Reasoning-token split in footer — when
usage.completion_tokens_details.reasoning_tokensarrives, the token block readsprompt→total (reasoning / visible). Diagnoses "why is the body empty despite hit-max-tokens?" instantly. reasoning_effort+max_completion_tokensin request body — sent alongsideenable_thinking: falseandmax_tokensfor broader compatibility.reasoning_effortvalue is backend-mapped:'none'on LM Studio + Ollama (hardest off-switch),'low'on generic OpenAI-compatible.shakedown.mjsend-to-end self-test — runs all seven tools in sequence, prints a markdown summary with real TTFT / tok/s / token counts / reasoning-token split. Wired asnpm run shakedown. Not shipped in the npm tarball.SHAKEDOWN.md— canonical natural-language test prompt for conversational test runs via Claude.DEVELOPER.md— internals guide: streaming pipeline, reasoning-model handling, backend detection, SQLite schema, pre-flight estimator, adding tools/backends, release process, quality-flag reference.
reasoning_effort: 'low'caused HTTP 400 on Nemotron via LM Studio — the LM Studio adapter acceptsnone | minimal | low | medium | high | xhigh, and Nemotron's narrower set would reject'low'with a silent fallback to'on'(maximum reasoning — opposite of intent). Backend-mapped value ('none'on LM Studio) is accepted by every model variant and is the hardest off-switch available.- Silent empty bodies on reasoning models (Nemotron, DeepSeek R1, LM Studio with "Separate reasoning_content") — previously
delta.reasoning_contentwas tracked for progress notifications but never accumulated into a content buffer, so when the model exhaustedmax_tokenson reasoning before emitting anydelta.content, the response was empty and no safety flag fired. Now captured and returned viareasoningFallbackwith a clear preamble and flag. - Thinking-model detection was Gemma-4-only — now covers Nemotron, DeepSeek R1, GLM-4, gpt-oss, and
qwen3-thinking/*-thinkingpatterns. Arch + id + HF chat_template signals are OR'd. - Stale cache masked new thinking detection —
getThinkingSupportnow re-applies the arch/id fallback at read time, so entries cached before the detection list was broadened still pick up flags without a manual cache flush. max_tokensinflation used wrong base — wasDEFAULT_MAX_TOKENS(16k), noweffectiveMaxTokens(context-aware 25%), so inflation sizes correctly on big-context models.- README
code_task_filesparameter name — was documented asfile_paths, actual parameter ispaths.
- Footer format updated to show
this session: X · lifetime: Yandtokens (reasoning / visible)splits where applicable. discoverper-model speed line now shows session and/or lifetime variants; the "not yet benchmarked" fallback only appears for models with no prior use on the workstation.code_task_filestool description mentions the size ceiling and pre-flight estimator behaviour..gitignore/.npmignoreadd explicitmemory/andMEMORY.mdentries.
- Tool descriptions reframed from pitch to peer — removed "FREE, parallel worker", "delegate generously — it costs nothing", and similar salesy framing from
chat,custom_prompt,code_task, andcode_task_files. Descriptions now state the honest trade (local inference is typically 3-30× slower than frontier models, but doesn't bill against the user's Claude quota) and let the caller decide task-by-task. Positions houtini-lm as a sidekick — a capable peer for bounded work — rather than a blanket offload target. - Session savings line promoted — the cumulative offloaded-token line now appears on its own line below the response footer with a 💰 prefix and "Claude quota saved this session" framing, instead of being pipe-separated among six other fields. Reads as value rather than accounting.
discoverconnection latency relabelled — the ms number in discover is now labelled "Connection latency (does not reflect inference speed)" to avoid the prior misreading that it measured how fast the model generates tokens.
- Session-level sidekick instructions — the MCP
Servernow sends aninstructionsstring at initialisation that frames houtini-lm as a local LLM sidekick, states when to delegate vs when not to, and directs Claude todiscoverfor model speed. Surfaced once per session by the MCP client, so it sets baseline expectations rather than relying on per-tool descriptions being re-read. - First-call speed benchmark — on the first measured call per model per session, the response footer adds a prominent line (
📊 First measured call on <model>: X tok/s, Yms to first token). No synthetic warmup — the number reflects a real task. Gives Claude honest speed data for calibrating subsequent delegation decisions. discoversurfaces measured speed — the active model's measured tok/s and TTFT appear prominently near the top of the discover output, averaged over the session. Shows "not yet benchmarked" when no real call has run, rather than inventing a number from an artificial probe.
- Streaming reader cleanup on abrupt disconnect — the
finallyblock now racesreader.cancel()against a 500ms timer beforereleaseLock(), so abrupt client disconnects free the upstream socket sooner without blocking the tool response path. PreviouslyreleaseLock()alone could leave a wedged upstream connection until the per-chunk timeout fired. - Average TTFT denominator — per-model
avgTtftMs(indiscover, thehoutini://metrics/sessionresource, and the session performance block) now dividestotalTtftMsby a dedicatedttftCallscounter rather thancalls. Previously, calls that reported no TTFT still counted toward the divisor, under-reporting the average. Surfaced by the new prominent speed line indiscover. - Footer no longer swallows session line when
partsis empty — if every other footer field is absent but the session savings line or first-call benchmark exists, the footer now renders those instead of returning an empty string.
code_task_filestool (#4) — accepts absolute file paths; the server reads and concatenates them server-side so source never passes through the MCP client's context window. UsesPromise.allSettledso one unreadable file doesn't sink the call; failures are surfaced inline.- Dynamic
max_tokens(#5) — derived from the active model's loaded context window (25%, e.g. 262K ctx → 65K output) when the caller doesn't pass an explicit budget. Falls back to 16384 when context is unknown. - Progress notifications during reasoning (#5) — each streamed chunk sends a progress notification during the thinking phase too, resetting the client's 60s clock so big-input + slow-TTFT calls don't time out.
- Thinking-model detection for gated HuggingFace repos — including Gemma 4.
- Empty response body from thinking models (#6) — two-layer fix:
- For models that support thinking toggle, inflate
max_tokensby 4× (minimum +2000) so reasoning doesn't starve content generation. Gemma 4 hardcodesenable_thinking=truein its Jinja template and ignores the API flag, so this inflation is the real fix. - Safety net: if the
<think>stripper still ends up with an emptycleanContent(MLX/GGUF quants that ignore the flag entirely), return the raw output with athink-strip-emptyquality flag instead of an empty body + lone footer.
- For models that support thinking toggle, inflate
- Default soft timeout raised to 5 minutes — progress notifications reset the MCP client's 60s clock, so the soft timeout is now a safety net rather than the primary limit.
- Quality metadata — every response includes structured quality signals (truncation, think-block detection, token estimation, finish reason) so Claude can make informed trust decisions about local LLM output
- Session metrics resource —
houtini://metrics/sessionMCP resource exposes cumulative offload stats and per-model performance as JSON, enabling proactive routing feedback - Request semaphore — inference calls are serialised to prevent stacked timeouts when parallel requests hit a single-model server
- SQLite statement leak in
getCachedProfile— statement was not freed ifgetAsObject()threw (now wrapped in try/finally) - Unflushed SSE buffer — the final streaming chunk (often containing usage data) could be stranded in the buffer after loop exit, causing missing token counts on truncated responses
- Session stats on truncated responses — token counts now estimated from content length (~4 chars/token) when the usage chunk is lost, instead of silently showing zero
- Model routing — automatically picks the best loaded model for each task type (code, chat, analysis, embedding)
- Per-model prompt hints — temperature, output constraints, and think-block flags tuned per model family (GLM, Qwen, LLaMA, Nemotron, Granite, GPT-OSS)
stream_options: { include_usage: true }— enables accurate tok/s measurement from SSE streams- Model routing suggestions when a better model is downloaded but not loaded
code_tasktemperature now set by routing hints (e.g. 0.1 for Qwen Coder) instead of hardcoded 0.2chatandcustom_promptinject output constraints into system prompts for models that need them- Perf averaging now divides by calls with actual data, not all calls
profileModelsAtStartupbatches DB writes (single flush instead of per-model)- Removed unused
dirnameimport from model-cache.ts - Test suite auto-detects loaded model instead of hardcoding
- tok/s was always
?becausestream_optionswasn't set - Perf averages inflated by calls without usage data
- Model discovery — loaded vs available models, context window reporting, capability profiles
- SQLite cache (sql.js, pure WASM) — auto-profiles models via HuggingFace API, 7-day TTL
- Performance stats — TTFT and tok/s measured from SSE stream timing
- Structured output —
json_schemaparameter for grammar-constrained JSON - Embeddings tool —
/v1/embeddingsendpoint support - Think-block stripping — removes
<think>blocks from GLM, Nemotron, Qwen3 - 12 static model profiles — Nemotron, Granite, Qwen3, LLaMA, GLM-4, GPT-OSS, and more
- Session-level token accounting across all calls
- Rewrote README — clearer install instructions, use cases, and tool docs
- Complete rewrite — stripped the bloated plugin/prompt architecture down to a clean ~190-line MCP server
- Replaced
@lmstudio/sdkwith plainfetch()to the OpenAI-compatible API - Removed
puppeteer,css-tree,jest, and all unused dependencies - Updated MCP SDK from
^1.17.3to^1.26.0 - Enabled TypeScript strict mode
- Plugin system, prompt library, caching layer, security module, template engine
- All "lite" variants and their build scripts
- Diagnostic tools, test files, development docs
chat— send a message and get a responsecustom_prompt— structured prompt with system message, context, and instructionlist_models— list models loaded in LM Studiohealth_check— verify connectivity