Providers¶
A provider is a remote inference service. OpenMed supports OpenAI (via an API key or a ChatGPT OAuth sign-in), Anthropic, OpenRouter, Xiaomi MiMo token plans, Doubleword, and custom endpoints for the interactive agent. Doubleword also supports async Responses and 24-hour JSONL batches. The interactive agent uses the same medical tools, workflows, and audit trail across providers.
This page covers credentials, model selection, and the behaviour specific to each provider. For runtime settings that are not provider-related, see Configuration.
Choosing a provider¶
| Provider | Auth | Use it for |
|---|---|---|
| OpenAI | ChatGPT OAuth (openmed login) or OPENAI_API_KEY |
The default path. GPT-6.1 Sol and GPT-6 models. |
| Anthropic | ANTHROPIC_API_KEY |
Claude models, with prompt caching enabled by default. |
| OpenRouter | OPENROUTER_API_KEY |
Models OpenMed does not host itself — Gemini, Kimi, GLM, Qwen, Llama and several hundred more, on one key. |
| MiMo | MIMO_API_KEY (starts with tp) |
Xiaomi MiMo token-plan subscriptions, with four native models ready to select. |
| Doubleword | DOUBLEWORD_API_KEY |
Real-time TUI, async flex Responses, and 24-hour bulk inference. |
| Custom | optional key | One or more OpenAI-compatible servers you operate — vLLM, SGLang, llama.cpp, Ollama, LM Studio, or gateways. |
Each provider resolves its own credentials. MiMo uses only its token-plan key and fixed endpoint; it never falls back to an OpenAI, Anthropic, or OpenRouter key.
All Doubleword modes use the same Doubleword key (from the environment or
provider secrets) and the fixed
https://api.doubleword.ai/v1 endpoint. See Doubleword for mode
selection, interactive use, Responses jobs, and batches.
You can configure more than one at a time. Which provider handles a request is decided by the model you select — see How a provider is selected.
Where credentials live¶
| Path | Holds |
|---|---|
~/.openmed/auth.json |
ChatGPT OAuth tokens, written by openmed login |
~/.openmed/provider_secrets.json |
Provider API keys shared by interactive, async and batch requests, OpenRouter/Doubleword model lists, and custom endpoints |
~/.openmed/settings.json |
Runtime behaviour such as permission mode — not secrets |
Store keys without hand-editing JSON:
openmed config provider-set openai --api-key "sk-..."
openmed config provider-set anthropic --api-key "sk-ant-..."
openmed config provider-set openrouter --api-key "sk-or-..."
openmed config provider-set mimo --api-key "tp-..."
openmed config provider-set doubleword --api-key "sk-..."
openmed config provider-show
provider-show reports which providers are configured without printing secret values. To remove a stored key, use --unset-api-key with the same command.
Editing the file directly¶
~/.openmed/provider_secrets.json is written with 0600 permissions. Values may be literal strings, or {env:VAR_NAME} references that resolve at load time — useful when the file is synced between machines and you would rather not store the secret in it:
{
"provider": {
"openai": {
"options": {
"apiKey": "{env:OPENAI_API_KEY}"
}
},
"anthropic": {
"options": {
"apiKey": "{env:ANTHROPIC_API_KEY}"
}
},
"openrouter": {
"options": {
"apiKey": "sk-or-v1-...",
"models": ["qwen/qwen3.8-27b", "z-ai/glm-5.2"]
}
},
"mimo": {
"options": {
"apiKey": "{env:MIMO_API_KEY}"
}
},
"doubleword": {
"options": {
"apiKey": "{env:DOUBLEWORD_API_KEY}"
}
},
"custom": {
"options": {
"endpoints": [
{"baseURL": "http://localhost:8000/v1", "model": "Qwen/Qwen3.8-27B", "apiKey": "{env:MY_SERVER_KEY}"}
]
}
}
}
}
Only the custom section carries endpoint URLs (inside its endpoints
list) — hosted provider endpoints are fixed, and a stored baseURL on them
is ignored and stripped.
One JSON object, one provider map
Every provider is a key inside the single provider object above. A
common mistake is to paste a second example after the first, which produces
two objects and makes the whole file invalid — at which point all your
keys are ignored and every provider shows as not configured. If that
happens, openmed config provider-show now names the file and the line.
A flatter shape is accepted as an alternative to the block above — use one or the other, never both in the same file:
{
"openrouter": {
"api_key": "{env:OPENROUTER_API_KEY}",
"models": ["qwen/qwen3.8-27b", "x-ai/grok-4.6"]
}
}
Check the file is valid at any time:
It prints which providers are configured, and exits non-zero with the parse error if the file is malformed.
Precedence is explicit runtime config → environment variable → this file → default. Provider config is read once per process, so restart OpenMed after editing the file by hand.
Hosted provider endpoints are fixed: OpenAI, Anthropic, OpenRouter, MiMo, and Doubleword do not take a base URL through provider-set. Use the custom provider for a server or gateway you operate.
OpenAI¶
The default provider. Two independent auth paths — OAuth, or an API key.
Sign in with ChatGPT (OAuth)¶
Uses your ChatGPT subscription rather than API billing:
Credentials are reused until they expire. If auth status reports the token is no longer valid, openmed login -f refreshes the single global auth file. openmed login --no-browser prints the authorization URL instead of opening a browser, and --timeout <seconds> extends the default five-minute wait.
An interactive sign-in can only be started by openmed login — never implicitly from inside the TUI. If you sign in from another terminal while the TUI is open, your next message picks up the new credentials without a restart.
API key¶
No openmed login step is required when using a key.
Models¶
gpt-6.1-sol (default), gpt-6-astra, gpt-6-sol, and gpt-6-luna.
GPT-5.6 models and the gpt-5.6 alias are no longer supported by OpenMed;
update saved model selections to one of these IDs. GPT-5.5 remains hidden
from the picker; explicit gpt-5.5 selections use the OpenAI API when a key
is configured. For the current picker models, OpenMed selects OAuth when
both OAuth and an API key are available; use --provider openai to select
API billing explicitly.
GPT-6.1 Sol
uses the Responses API for agent tools, has a 1,050,000-token context window,
and supports low, medium (default), high, xhigh, and max reasoning.
It requires reasoning: an existing none setting is mapped to low.
OpenAI and Codex auxiliary calls use the cheaper gpt-6-luna.
Anthropic¶
openmed config provider-set anthropic --api-key "sk-ant-..."
# or
export ANTHROPIC_API_KEY=sk-ant-...
openmed agent --provider anthropic --model claude-sonnet-4-6
Anthropic has no OAuth path — an API key is the only option.
Models¶
claude-fable-5-1, claude-opus-5-5, claude-opus-5, claude-sonnet-5, claude-opus-4-8, claude-sonnet-4-6 (the Anthropic default), and claude-haiku-4-5-20251001. Opus 4.7 is no longer listed in the picker, but existing explicit model selections remain usable. Selecting any claude-* model routes to Anthropic when Anthropic credentials are available. Fable 5.1 and Opus 5.5 always use adaptive thinking; the other 5.x models also use adaptive thinking by default.
Prompt caching¶
Enabled by default. OpenMed marks the stable tool schema block and the system prompt as 5-minute cache breakpoints, and enables Anthropic's top-level automatic cache control for the growing conversation, so long tool runs do not resend the entire prior transcript as fresh input every turn.
| Variable | Purpose | Default |
|---|---|---|
OPENMED_ANTHROPIC_PROMPT_CACHE |
Enable caching for stable tool/system prefixes | 1 |
OPENMED_ANTHROPIC_PROMPT_CACHE_TTL |
Cache TTL — 5m or 1h |
5m |
OPENMED_ANTHROPIC_AUTOMATIC_CACHE |
Top-level automatic caching for multi-turn conversations | 1 |
Rate-limit guard¶
OpenMed also guards Claude's input-token-per-minute limits locally. Cache reads are excluded from Anthropic's rate-limit accounting, but cache writes and uncached conversation growth still count. The Anthropic SDK tracks uncached input usage, paces requests near the local budget, compacts stale history when a cache miss would make a request too large, and retries transient 429 responses using Claude's retry-after header.
| Variable | Purpose | Default |
|---|---|---|
OPENMED_ANTHROPIC_RATE_LIMIT_GUARD |
Pace and compact before ITPM limits are exceeded | 1 |
OPENMED_ANTHROPIC_ITPM_LIMIT |
Override the input-token-per-minute limit | model default |
OPENMED_ANTHROPIC_ITPM_SAFETY_RATIO |
Fraction of the limit to use before pacing | 0.85 |
OPENMED_ANTHROPIC_RATE_LIMIT_RETRIES |
Automatic retries after a 429 |
2 |
OpenRouter¶
OpenRouter gives you models OpenMed does not host itself — Gemini, Kimi, GLM, Qwen, Llama and several hundred others — through one API key. It is a separate provider: OpenRouter credentials are never sent to OpenAI or Anthropic, and an OpenAI key is never sent to OpenRouter.
Choosing which models appear¶
Configure a short list rather than all several hundred. Those models appear in their own OpenRouter group in the /model picker:
openmed config provider-set openrouter \
--api-key "sk-or-..." \
--models "google/gemini-3.7-flash,qwen/qwen3.8-27b,z-ai/glm-5.2"
Or in ~/.openmed/provider_secrets.json directly (as one key inside the single provider object — see Editing the file directly):
{
"provider": {
"openrouter": {
"options": {
"apiKey": "sk-or-v1-...",
"models": [
"google/gemini-3.7-flash",
"qwen/qwen3.8-27b",
"z-ai/glm-5.2"
]
}
}
}
}
qwen/qwen3.8-27b is OpenMed's default OpenRouter model. Gemini is shown here because people ask for it, but see the warning below before using it for clinical work — Google's API terms prohibit that use.
The list is stored as models in the openrouter section of ~/.openmed/provider_secrets.json — see Editing the file directly. OPENROUTER_MODELS (comma-separated) overrides the stored list for one session, and openmed config provider-set openrouter --unset-models removes it. With nothing configured, OpenMed uses qwen/qwen3.8-27b.
Browse what OpenRouter offers:
openmed config openrouter-models --search gemini
openmed config openrouter-models --all # include models OpenMed cannot use
openmed config openrouter-models --refresh # bypass the 24h metadata cache
OpenRouter environment variables¶
| Variable | Purpose | Default |
|---|---|---|
OPENROUTER_API_KEY |
The OpenRouter key. Required — OpenMed never falls back to another provider's key. | unset |
OPENROUTER_MODELS |
Comma-separated model ids offered in /model; overrides the stored list |
stored list, else qwen/qwen3.8-27b |
OPENMED_OPENROUTER_MODELS_TTL_SECONDS |
Lifetime of cached model metadata under ~/.openmed/cache/ |
86400 (24h) |
The OpenRouter base URL is fixed to https://openrouter.ai/api/v1. For a self-hosted or gateway server, use the custom provider.
Recommended models¶
These are the models OpenMed has actually run through its own agent loop with the full 93-tool schema, cross-checked against OpenRouter's τ²-Bench Airline (multi-turn tool use) and GPQA Diamond (reasoning) leaderboards.
Open-weight — self-hostable, and the starting point for most users:
openmed config provider-set openrouter --models "\
qwen/qwen3.8-27b,\
z-ai/glm-5.2,\
qwen/qwen3.5-397b-a17b,\
nvidia/nemotron-3-ultra-550b-a55b,\
xiaomi/mimo-v2.5-pro,\
qwen/qwen3.5-122b-a10b,\
minimax/minimax-m3,\
moonshotai/kimi-k3,\
qwen/qwen3.8-2.4t-a95b,\
deepseek/deepseek-v4-flash-0731,\
deepseek/deepseek-v4-pro-0813"
| Model | Licence | Notes |
|---|---|---|
qwen/qwen3.8-27b |
Apache-2.0 | Best value: τ²-Bench #3 overall, 1M context, vision |
z-ai/glm-5.2 |
MIT | Best all-round balance; low hallucination rate |
qwen/qwen3.5-397b-a17b |
open | Strong on both tool use and reasoning |
nvidia/nemotron-3-ultra-550b-a55b |
permissive | Fastest model tested |
xiaomi/mimo-v2.5-pro |
open | Very well calibrated; 1M context |
qwen/qwen3.5-122b-a10b |
open | Outstanding cost/capability, under a cent per turn |
minimax/minimax-m3 |
open | Best-calibrated model evaluated; GPQA top-10 |
moonshotai/kimi-k3 |
custom | Strongest reasoning of any open model |
qwen/qwen3.8-2.4t-a95b |
bespoke | Highest agentic score; licence is not permissive |
deepseek/deepseek-v4-flash-0731 |
MIT | Cheapest model listed ($0.0018/turn), 1M context — but see the DeepSeek warning below |
deepseek/deepseek-v4-pro-0813 |
MIT | τ²-Bench #8, GPQA #21 — verify every code it emits; see the DeepSeek warning below |
Closed, hosted-only:
| Model | Notes |
|---|---|
x-ai/grok-4.6 |
Best-placed non-OpenAI/Anthropic model on healthcare benchmarks |
qwen/qwen3.8-max |
Top agentic tier; rarely refuses clinical content |
amazon/nova-pro-v1 |
Fastest closed model tested |
qwen/qwen3.7-max |
Best published clinical benchmark results of the closed set |
x-ai/grok-4.5 |
Same posture as 4.6, roughly twice as fast |
Budget tiers that also passed cleanly: upstage/solar-pro4, amazon/nova-lite-v1, google/gemma-4-26b-a4b-it, qwen/qwen3.6-35b-a3b, nvidia/nemotron-3.5-lightning.
Fast, well-calibrated additions from a volume campaign (2026-08-31). These are the base models, worth running free or paid:
| Model | Notes |
|---|---|
cohere/north-mini-code |
Fastest clean probe of any model tested (~10s), every clean run perfect |
inclusionai/ling-3.0-flash-fin |
Every clean run perfect, ~11s. Not ling-3.0-flash, which is excluded for hallucination — this is the -fin variant |
minimax/minimax-m2.7 |
The reliable long-horizon pick. Slower (~21s) |
Trying OpenMed for free¶
Several of the models above are also served on a :free tier, so you can drive
the whole 93-tool agent without spending anything. The numbers below come from
5,500+ live runs of OpenMed's own agent loop against the free tiers, not
from a leaderboard.
Start with one line:
openmed config provider-set openrouter --api-key "sk-or-..." \
--models "minimax/minimax-m2.7:free,cohere/north-mini-code:free,inclusionai/ling-3.0-flash-fin:free"
openmed agent --provider openrouter --model "minimax/minimax-m2.7:free"
| Free model | Clean runs | Perfect | Honest | Median | Long tasks |
|---|---|---|---|---|---|
minimax/minimax-m2.7:free |
2320/2327 | 92% | 100% | 21s | 281/310 |
minimax/minimax-m3:free |
1580/1620 | 72% | 100% | 18s | 154/163 |
cohere/north-mini-code:free |
273/273 | 100% | 100% | 10s | see note |
inclusionai/ling-3.0-flash-fin:free |
271/271 | 100% | 100% | 11s | see note |
minimax/minimax-m2.7:free is the one to start with. It is the only free
model that reliably finishes long, multi-step clinical work — 281 of 310
sustained 10-case runs — while staying honest on every single clean run.
cohere/north-mini-code:free and inclusionai/ling-3.0-flash-fin:free are the
fastest and were flawless on every clean short probe. Their long-horizon column
is marked "see note" deliberately: their sustained runs did not complete in our
testing, but they were cut short by the daily request cap, not by any
observed failure of the model. Treat them as proven for short tasks and
unproven for long ones.
Not honest, not calibrated — the same thing across every one of these: no free model fabricated a file's contents or a clinical fact in any clean run. That property is why these are listed at all; see the warning below about choosing on benchmark rank.
nvidia/nemotron-3-ultra-550b-a55b:free — use the paid tier
The free tier of this model was usable in only 23 of 386 attempts, with a median of 63s, because its free capacity routes through a single congested upstream. The paid variant of the identical model scored 80/80 perfect and completed 12/12 long runs at 20s. This is a serving problem, not a model problem — but on the free tier it will waste your daily allowance.
What the free tier actually gives you
OpenRouter allows 1,000 free-model requests per day once an account has purchased at least $10 in credits, and 50 per day before that. Free models cost nothing to run; the credit only raises the request ceiling.
The allowance is a single pool shared across every free model, not a
per-model quota — measured directly: once it is spent, models the account
has never called return remaining=0 with the same reset timestamp as the
one that drained it. Adding more free models to your config therefore
diversifies what you can run, but does not increase your daily budget.
It resets at 00:00 UTC.
A long agent run can use 50-80 requests, so long-horizon work exhausts the pool far faster than short tasks.
One exception worth knowing: a few :free models are served by
providers with their own free capacity and do not draw on that shared pool
— minimax/minimax-m2.7:free and minimax/minimax-m3:free both keep
serving after it is exhausted. If you only configure one free model, make
it one of those.
Read before choosing on benchmark rank alone
Some of the highest-ranked and most-used models on OpenRouter are not suitable for clinical work, and OpenMed's own testing is why. A high leaderboard rank measures tool mechanics and reasoning, not calibration — a model can drive the whole 93-tool loop flawlessly and still state a confident, wrong clinical fact. Several providers' terms also forbid medical use outright — Google's Gemini API terms prohibit use "in clinical practice, to provide medical advice"; Z.AI's and Tencent's terms carry comparable restrictions, and Tencent's licence excludes the EU, UK and South Korea entirely. Moonshot's hosted API forbids processing HIPAA-defined PHI, so Kimi models should be self-hosted for patient data. A BAA with an upstream provider does not survive routing through OpenRouter.
DeepSeek V4: listed, but check its output
deepseek/deepseek-v4-flash-0731 and deepseek/deepseek-v4-pro-0813 are listed above for their licence (MIT), their cost and their leaderboard placings — pro-0813 is τ²-Bench #8 and GPQA #21, and flash-0731 is the cheapest entry on the list at roughly $0.0018 per turn.
They are listed despite the DeepSeek family's published AA-Omniscience hallucination rates of 91.7–94.1%, the highest of any model OpenMed recommends. Both pass our tool-discipline probe — that is the point: the probe proves a model can hold the loop and not invent a file's contents, and it does not prove calibration.
In OpenMed's own repeated testing, pro-0813 returned ICD-10 R03.0 instead of I10 on four consecutive runs for a straightforward hypertension presentation, once describing the reading as "stage 2 hypertension" while coding elevated blood pressure without a diagnosis of hypertension — a self-contradiction. flash-0731 answered correctly every time.
If you need a DeepSeek model, prefer flash-0731. With either, treat generated codes, doses and clinical claims as drafts requiring review — which is what OpenMed's reviewer-gated workflow is for.
Mistral was evaluated on request and not added: mistral-medium-3-5 scores
100/100 on our live agent probes but carries an 81.6% published hallucination
rate — roughly double the range of every model on this list. A clean probe
proves tool mechanics, not calibration.
The full evidence table, including per-model probe results, hallucination rates and licence verification, is in PLANS/openrouter-recommended-models.md.
Not every OpenRouter model can run the agent¶
OpenRouter hosts image generators, audio models, and chat models without tool calling. OpenMed's tools are the product, so a model that cannot call tools cannot drive it. OpenMed reads each model's capabilities from OpenRouter's model metadata and refuses an incompatible model at startup with the reason, rather than failing part-way through a case:
OpenRouter model 'google/lyria-3-pro-preview' cannot drive the agent:
no tool-calling support (required by the agent).
Capabilities are also enforced per request:
| Capability | Behavior |
|---|---|
| Tool calling | Required. Models without it are refused at startup. |
| Text output | Required. Image-only and audio-only models are refused. |
| Image input | Scanned documents and screenshots are sent only to vision models. On a text-only model the attachment is omitted and the message says so. |
| Context window | Taken from OpenRouter metadata, so compaction triggers at the model's real limit. |
| Reasoning effort | Clamped to the levels the model advertises, so /model never sends an effort the model rejects. |
| Max output tokens | Clamped to the model's provider cap. |
The /model picker shows these as chips per model (vision · 1M ctx · reasoning), and marks a model incompatible before you select it.
Model quality is your call¶
OpenMed evaluates the curated OpenAI and Anthropic models it ships with. It does not evaluate the OpenRouter catalog. A model that passes the compatibility check can still be a poor fit for clinical work — weaker instruction-following, worse tool discipline, or a smaller effective context than its advertised window. Treat an OpenRouter model as unvalidated until you have evaluated it on your own cases. Every PHI, de-identification, and finalization gate behaves identically regardless of provider.
Prompts you send — including any PHI in them — go to OpenRouter and on to whichever upstream provider serves that model. Check OpenRouter's privacy settings for how your data is handled and whether prompt logging is enabled on your account.
Doubleword¶
OpenMed Agent 0.5.0 supports three Doubleword inference modes with one key.
The built-in default interactive model is DeepSeek V4.1 Flash
(deepseek-ai/DeepSeek-V4.1-Flash); a configured model list uses its first entry.
Choose the mode based on when you need the
answer and whether requests depend on earlier results.
| Mode | OpenMed entry point | Suitable work |
|---|---|---|
| Real-time | openmed agent --provider doubleword (streaming TUI), or openmed responses submit request.json --service-tier priority (direct CLI) |
Interactive questions and work where latency matters; the TUI executes agent tools |
Async Responses (flex) |
openmed responses submit request.json |
Background handoffs, document extraction, dependent review steps that tolerate a delay |
| 24-hour Batch | openmed batch submit requests.jsonl |
Independent overnight chart reviews, research screening, indexing and other backlogs |
Doubleword describes flex as lower cost than real-time, and batch as lower cost
than flex. background: true changes how you collect a response; it does not
make a priority request cheaper. The 24-hour batch window is a completion window,
not a required wait. Each dependent batch stage gets its own window. See the
async,
real-time, and
batch service descriptions
and current model pricing before choosing a tier.
Doubleword credentials¶
export DOUBLEWORD_API_KEY="sk-your-doubleword-key"
# Or, instead of exporting the variable, store a per-user secret:
openmed config provider-set doubleword --api-key "sk-your-doubleword-key"
openmed config provider-show
Alternatively, put this entry in ~/.openmed/provider_secrets.json, merging it
inside the existing provider object if other providers are configured:
The top-level key is provider (singular). Replace the placeholder with your
key, or use "{env:DOUBLEWORD_API_KEY}" and export that variable. Keep the file
private (chmod 600 ~/.openmed/provider_secrets.json) and restart OpenMed after
manual edits. provider-set applies those permissions automatically.
An exported environment key takes precedence. All three modes use
https://api.doubleword.ai/v1; no base URL or OpenAI key is needed. openmed login
is unrelated. Export or store the key for consistent behavior. In a development
checkout, the CLI also loads .env when the optional python-dotenv dependency
is installed; the source demo runner loads the repository .env explicitly.
Try all three Doubleword modes¶
After configuring your key above, check openmed --version reports 0.5.0 or
later. These examples work with the installed CLI and need no repository files
or native medical services. Each submission uses credits; the examples use
synthetic text and cap each output at 4,096 tokens, including reasoning.
Paste: “A synthetic document queue has six items: four reviewed and two
awaiting review. Summarize that status in one sentence. No tools needed.”
The answer streams in the TUI. /model lets you change models; select the
Doubleword provider group.
Save this JSON object as request.json:
{
"model": "deepseek-ai/DeepSeek-V4.1-Flash",
"input": "A synthetic document queue has six items: four reviewed and two awaiting review. Summarize that status in one sentence.",
"max_output_tokens": 4096
}
openmed responses submit request.json --service-tier flex
# Replace <response-id> with the ID printed above. You can close the terminal.
openmed responses wait <response-id> --timeout 300
openmed responses results <response-id>
Submission prints a durable ID. results prints the Responses JSON object;
the answer is in its output items. If waiting times out, run wait again
with the same ID. To try on-demand priority through the CLI, submit a new
request with --service-tier priority; polling does not make it flex-priced.
Save these two lines as requests.jsonl (one complete JSON object per line):
{"custom_id":"queue-a","method":"POST","url":"/v1/chat/completions","body":{"model":"deepseek-ai/DeepSeek-V4.1-Flash","messages":[{"role":"user","content":"Synthetic queue A has six items, four reviewed. State how many await review."}],"max_tokens":4096}}
{"custom_id":"queue-b","method":"POST","url":"/v1/chat/completions","body":{"model":"deepseek-ai/DeepSeek-V4.1-Flash","messages":[{"role":"user","content":"Synthetic queue B has ten items, seven reviewed. State how many await review."}],"max_tokens":4096}}
openmed batch validate requests.jsonl # offline; no credits spent
openmed batch submit requests.jsonl
# Replace <job-id> with the ID printed above; collect later.
openmed batch wait <job-id> --timeout 300
openmed batch results <job-id> --json
results prints the paths to output.jsonl and errors.jsonl. Open those
files and match each row by custom_id; successful answers are under
response.body.choices[].message.content. Check errors and finish_reason
too. A five-minute local timeout is normal for a batch with a 24-hour window:
resume wait or results using the same ID. Never resubmit to check progress.
Use openmed responses list or openmed batch list if you lose an ID. All
three examples use the same DeepSeek model. Only the TUI runs the interactive
agent loop; direct Responses and batch requests do not execute tools for you.
The mode guide explains when to use
each path, and batch inference covers overnight NLP/HCC
evidence review and result handling.
Doubleword models and picker configuration¶
With just a key, the picker offers these generation models in this order. Use the exact API IDs, including capitalization, in configuration and request bodies. The display names are not API aliases.
| Display name | API model ID |
|---|---|
| DeepSeek V4.1 Flash | deepseek-ai/DeepSeek-V4.1-Flash |
| GLM 5.3 Flash | zai-org/GLM-5.3-Flash |
| GLM 5.3 | zai-org/GLM-5.3 |
| DeepSeek V4 Pro 0813 | deepseek-ai/DeepSeek-V4-Pro-0813 |
| DeepSeek V4 Flash 0731 | deepseek-ai/DeepSeek-V4-Flash-0731 |
| Kimi K3 | moonshotai/kimi-k3 |
| Inkling | thinkingmachines/Inkling-NVFP4 |
| GLM 5.2 | zai-org/GLM-5.2-FP8 |
| Nemotron 3 Ultra 550B A55B | nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4 |
| Kimi K2.6 | moonshotai/Kimi-K2.6 |
| Nemotron 3 Super 120B A12B | nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4 |
These are selectable defaults, not a quality ranking. Check Doubleword's catalog for current availability and pricing. Shared real-time access can be rate limited.
Automatic compaction uses Doubleword's published serving windows for all 11
models: 1M for the DeepSeek, GLM, Kimi K3 and Inkling entries; 262K for
Kimi K2.6 and both Nemotron entries. OpenMed uses conservative decimal values
from the Models dashboard, with a default
80% compaction threshold and space reserved for output. For example,
DeepSeek V4.1 Flash
has a published 1M window; 28K tokens is about 3% of it.
The /v1/models API currently omits context lengths, so OpenMed carries these
limits locally. Dedicated aliases are not assumed to share the same window.
See context and compaction for details.
Like OpenRouter, Doubleword accepts a models list under its provider options:
{
"provider": {
"doubleword": {
"options": {
"apiKey": "{env:DOUBLEWORD_API_KEY}",
"models": [
"deepseek-ai/DeepSeek-V4.1-Flash",
"zai-org/GLM-5.3-Flash",
"moonshotai/kimi-k3"
]
}
}
}
}
Export DOUBLEWORD_API_KEY for this example, or replace its environment
reference with a literal key. Merge the entry into an existing provider
object instead of replacing other providers. You can also update only the list
without changing the saved key:
openmed config provider-set doubleword --models "deepseek-ai/DeepSeek-V4.1-Flash,zai-org/GLM-5.3-Flash,moonshotai/kimi-k3"
openmed config provider-show
openmed --models
The list replaces the built-in picker list; its first entry is the default
for openmed agent --provider doubleword. Duplicates and blank entries are
removed while order and ID capitalization are preserved. Dedicated aliases
are accepted. Explicit --model selections still work outside the picker list.
Restart after manual edits, or reopen /model to reload provider configuration.
DOUBLEWORD_MODELS (comma-separated IDs) overrides the file list. An empty or
missing effective list falls back to the 11 built-ins. To restore the defaults:
The list controls interactive selection only. Responses JSON and batch JSONL
still require an explicit model; their request bodies are not rewritten from
the picker configuration. The models list does not select an execution tier.
Choosing the Doubleword execution mode¶
Async is selected per request, not in provider_secrets.json. Doubleword
does not read an async, service_tier, or mode setting from that file, or from
settings.json. The same stored key works for all modes:
| What you set | Where it goes |
|---|---|
| API key | DOUBLEWORD_API_KEY or provider.doubleword.options.apiKey in provider secrets |
| Model, input and output-token limit | The Responses JSON object or each batch JSONL row; --model for the TUI |
| Async flex or on-demand priority | openmed responses submit ... --service-tier flex (default) or --service-tier priority |
| Bulk processing with a 24-hour completion window | openmed batch submit requests.jsonl |
| Wait in this terminal | --wait on submission, or responses wait / batch wait later |
For example, an overnight backlog of independent, closed-encounter reviews fits a batch: gather NLP/HCC service evidence first, then ask the model to review each record. A single report or the next step of a dependent workflow can use flex when a delay is acceptable. Interactive work belongs in the real-time TUI. See when a 24-hour batch helps.
Doubleword real-time TUI¶
openmed agent --provider doubleword
openmed agent --provider doubleword --model deepseek-ai/DeepSeek-V4.1-Flash
# Account-specific dedicated aliases work too:
openmed agent --provider doubleword --model yourorg-zai/GLM-5.3
Open /model and select Doubleword → DeepSeek V4.1 Flash. Provider headings
remain visible while searching. This is real-time streaming, including tool
calls through OpenMed's existing agent loop. Helpers use Doubleword too.
Selecting Doubleword in the TUI does not enable flex or batch processing.
There is no async mode toggle or Responses/batch job screen in the TUI in 0.5.0;
use the CLI commands below for those modes.
The interactive adapter currently uses the model's default reasoning behavior;
--reasoning-effort does not override it. For explicit model-supported effort
settings, use reasoning.effort in a Responses request below.
Always pass --provider doubleword for an explicit model when several provider
keys are configured: a namespaced model ID alone can otherwise select OpenRouter.
The built-in or configured picker is a starting point; --model accepts other
generation models and dedicated aliases your account can serve. Model capabilities and tier
availability vary. Embedding models cannot run the interactive agent.
Doubleword's shared self-serve real-time endpoint is intended for development and testing and is rate limited. Production real-time capacity is arranged separately with Doubleword; dedicated deployments use their assigned model aliases on the same API. See dedicated real-time.
Doubleword async and on-demand CLI¶
Save this JSON object, not JSONL, as request.json:
{
"model": "deepseek-ai/DeepSeek-V4.1-Flash",
"input": "Synthetic review backlog: four records have supporting evidence and two have conflicting mappings. Write a short handoff preserving the unresolved items for human review. Do not invent diagnoses or calculate risk scores.",
"max_output_tokens": 4096
}
# Async flex: return local and remote IDs once Doubleword accepts the request.
openmed responses submit request.json --service-tier flex
# Read status or resume from another terminal, even after restarting OpenMed.
openmed responses list
openmed responses status <response-id>
openmed responses wait <response-id> --timeout 300
openmed responses results <response-id>
# On-demand priority: same lifecycle, different price/latency tier.
openmed responses submit request.json --service-tier priority --wait --timeout 120
Replace <response-id> with the local or remote ID printed by submit or list.
Omitting --service-tier selects flex.
What happens after submission:
- OpenMed saves the request locally and sends one
POST /v1/responsesto Doubleword withservice_tier: "flex",background: true, andstream: false. - Doubleword returns a remote response ID and processes the work on its servers. You can close the terminal after submission; no local worker needs to stay up.
status,wait, andresultsretrieve that response withGET /v1/responses/{id}. They reuse the saved ID and do not create another paid inference request.- When complete,
resultsprints the output. It stays pending while the remote work is unfinished; failures and incomplete responses must be handled separately.
Flex is a scheduling and pricing tier. background: true lets a request return
an ID while work continues. This CLI also uses background polling for priority
requests, which still receive priority scheduling and pricing. Async flex uses
/v1/responses; bulk batches use the separate /v1/files and /v1/batches
lifecycle. Flex has no 24-hour completion window. Doubleword documents a target
of starting flex work within a minute, with exceptions; this is not a completion
guarantee. See Doubleword's async contract.
--wait controls local polling, not provider scheduling or price.
--poll-interval defaults to
five seconds and must be at least one second. Omit --timeout to keep polling;
a timeout applies to the polling loop and can overrun by one HTTP request.
Use --json for machine-readable status. results always prints the complete
Responses object, including output items, tool calls, usage, errors and
incomplete_details. A terminal incomplete response is not a successful
answer, even if it contains partial text. Exit codes are 0 for success/status,
1 for terminal failure or incomplete results, 2 for errors, 3 for a local
wait timeout, and 130 for interruption.
Closing the terminal or pressing Ctrl-C does not cancel remote work. Resume the
saved ID; do not submit the same request again to check progress. If a creation
request loses its connection, the local state is submission_unknown (or
submitting after a hard interruption). Inspect Doubleword's Responses dashboard
and attach the existing remote ID with:
There is no Responses cancellation command in this release. Batch cancellation
remains available through openmed batch cancel. An HTTP 402/403/429 indicates a
credits, access, or rate-limit issue; automatic paid retries are disabled.
Doubleword structured input and dependent steps¶
Responses request JSON passes through instructions, message-array input,
previous_response_id, tools, tool_choice, text.format, reasoning, and
other provider-supported fields. Always set an explicit max_output_tokens.
The default model may consume part of that budget for reasoning. Use only the
reasoning levels your model supports.
Direct Responses and Batch commands do not execute OpenMed tools. If output
contains a function_call, your orchestrator must execute the tool, then submit
its result in the next request. A completed inference response is not evidence
that the whole workflow finished. For automatic interactive tool execution,
use the TUI. For overnight NLP/HCC work, prepare independent service evidence
first, then submit one batch of model reviews; see the
retrospective backlog example.
Model-supported image input, structured output and tool definitions can be
included in request JSON. Use an embedding model only with the embeddings API
or embedding batches. For a direct on-demand embedding
call using the optional openai dependency:
from openai import OpenAI
from openmed.agent.sdk.provider_secrets import get_provider_config
from openmed.agent.sdk.doubleword import resolve_doubleword_base_url
key = get_provider_config("doubleword").api_key
client = OpenAI(
api_key=key,
base_url=resolve_doubleword_base_url(key),
max_retries=0,
)
client.organization = client.project = None
result = client.embeddings.create(
model="Qwen/Qwen3-Embedding-8B",
input=["Synthetic research abstract for indexing"],
)
print(len(result.data[0].embedding))
client.close()
The same explicit client can call responses.create with service_tier="flex"
or "priority" and background=False for blocking operation. The durable CLI
is preferable when the process may restart or the connection may time out.
Doubleword storage and data boundary¶
Individual Responses snapshots, submitted request bodies and collected output
are stored with owner-only permissions under ~/.openmed/responses/. Override
with OPENMED_RESPONSES_DIR or relocate the whole home with OPENMED_HOME.
status --no-refresh, results --no-refresh, and list work offline.
Batch files remain under ~/.openmed/batches/; TUI sessions use normal session
storage. Credentials are not written into job snapshots.
These are hosted inference calls. Direct JSON/JSONL input is sent as supplied
and is not transformed by the TUI's PHI setting. Review your inputs and the
provider's storage policies before submitting sensitive material. Background
Responses retain state remotely for retrieval; store:false is rejected by
the resumable CLI. Local deletion does not delete provider-side data.
Doubleword source demos¶
The demo library entry PLANS/DEMOS/14-doubleword-inference.md covers streaming
tool execution and restartable flex/priority requests. Run from a development
checkout with agent dependencies and python-dotenv installed:
python3 scripts/demo/run_doubleword_inference_demo.py
python3 scripts/demo/run_doubleword_inference_demo.py --run --mode real-time
python3 scripts/demo/run_doubleword_inference_demo.py --run --mode flex
python3 scripts/demo/run_doubleword_inference_demo.py --run --mode priority
python3 scripts/demo/run_doubleword_inference_demo.py --resume artifacts/doubleword-inference-demo/<run>
The default invocation is offline. Each --run spends credits on synthetic
inputs, with a 4,096-token output limit per model turn. Real-time allows up to
three agent turns; flex/priority submits one request. Actual native NLP/HCC calls
are covered by the separate overnight and stress demos.
To compare the 11 default models, use the source probe:
# Offline: inspect the task and request limits.
python3 scripts/benchmarks/doubleword_model_probe.py
# Live: one synthetic tool workflow per model.
python3 scripts/benchmarks/doubleword_model_probe.py --run
It uses the real Doubleword adapter and production tool schemas, with tool
execution restricted to synthetic in-memory fixtures. It checks planning,
missing-file recovery, conflicting evidence, structured output and read-back
verification, and records latency and token usage under
artifacts/doubleword-model-probe/. One run can reveal compatibility failures;
it does not establish clinical quality or prove a model is consistently better.
Xiaomi MiMo token plan¶
For a Xiaomi MiMo token-plan subscription, store your key once. The commands below use a placeholder; replace it with your own tp key:
Or export the key for the current shell:
Neither method requires openmed login. An exported MIMO_API_KEY takes
precedence over the stored key. If MiMo is your only configured provider,
openmed also selects the default MiMo model.
Only the API key is required. All four models are preconfigured in Ctrl+M / /model:
mimo-v2.5-pro— default; text input.mimo-v2.5— text and image input.mimo-v2.6-pro— text input.mimo-v2.6-flash— text input.
Select a model at launch with:
openmed agent --provider mimo --model mimo-v2.6-pro
openmed agent --provider mimo --model mimo-v2.6-flash
Keys starting with tp use https://token-plan-ams.xiaomimimo.com/v1.
This provider currently supports token-plan subscriptions only. Pay-as-you-go
keys starting with sk are rejected with an explanatory message; they are never
sent to the token-plan endpoint. No base URL or model list needs to be configured.
MiMo provider secrets example¶
For a MiMo-only setup, the complete ~/.openmed/provider_secrets.json file is:
Replace tp-... with your token-plan key. If the file already contains other
providers, add only the mimo entry inside its existing provider object;
keep the other entries. The combined example
shows all providers together. Do not add a second top-level JSON object.
To keep the actual key in the environment, use this alternative complete file:
Export MIMO_API_KEY before launching OpenMed. A reference resolves only when
that variable is set in the OpenMed process. When editing the file manually,
keep its permissions private (chmod 600 ~/.openmed/provider_secrets.json)
and restart OpenMed to reload it. provider-set applies those permissions
automatically. Keys belong in this per-user file or the environment, never
in project settings or a repository.
MiMo configuration reference¶
| Setting | Behavior |
|---|---|
| Provider ID | mimo |
| Environment variable | MIMO_API_KEY; overrides the stored key |
| Stored key | provider.mimo.options.apiKey |
| Key type | Starts with tp; sk pay-as-you-go keys are unsupported |
| Base URL | https://token-plan-ams.xiaomimimo.com/v1; selected automatically |
| Default model | mimo-v2.5-pro |
| Built-in models | mimo-v2.5-pro, mimo-v2.5, mimo-v2.6-pro, mimo-v2.6-flash |
| Model list | Built in; no --models configuration needed |
| Transport | Streaming OpenAI-compatible Chat Completions |
| Inference boundary | Hosted by Xiaomi; the token plan is not local or offline inference |
Thinking and attachments¶
Thinking is enabled by default. Choose Thinking off in the reasoning picker,
or pass --reasoning-effort none, to disable it. Other effort values enable
thinking without selecting a strength. OpenMed preserves the provider's returned
reasoning_content across tool calls and follow-up turns as required by
MiMo's conversation protocol.
Workflow helper calls use the same token-plan key and selected model, with thinking
disabled for their short outputs. OpenMed currently forwards images only to
mimo-v2.5, whose image support is established. Images attached with any other
native MiMo model are omitted with a notice.
openmed agent --provider mimo --reasoning-effort none # thinking off
openmed agent --provider mimo --reasoning-effort medium # thinking on
Startup latency and TUI status¶
The direct token-plan route and OpenRouter's namespaced MiMo route are separate services. They use different credentials, endpoints, and serving infrastructure, so their time to first output can differ substantially even for the same model family. Fast generation after output begins does not imply a fast first token.
The TUI distinguishes the stages:
| Status | Meaning |
|---|---|
| Preparing request… | OpenMed is applying request/privacy routing and may run a short same-provider workflow-intent helper call. The primary model stream has not started yet. |
| Waiting for model response… | The primary request has been dispatched, but OpenMed has not received a non-empty reasoning or answer delta. |
| Model is reasoning… | The provider has emitted reasoning_content; OpenMed is rendering it live. |
Choosing --reasoning-effort none disables MiMo's generated thinking, but it
does not control provider queueing, connection setup, or time to first output.
If Preparing request… is slow, the delay is before the primary stream. If
Waiting for model response… is slow, the request has reached the provider
path and OpenMed is waiting for model output. OpenMed forwards the first
non-empty reasoning_content or answer delta to the TUI as soon as it arrives.
Verify, replace, or remove the key¶
openmed config provider-show
openmed --models
openmed config provider-set mimo --api-key "tp-..." # replace the stored key
openmed config provider-set mimo --unset-api-key # remove the stored key
provider-show reports set without printing the key. After removing a stored
key, an exported MIMO_API_KEY still takes precedence; use unset MIMO_API_KEY
to remove it from the current shell too.
| Symptom | What to check |
|---|---|
| MiMo asks for a key | Set MIMO_API_KEY or store provider.mimo.options.apiKey; check provider-show for malformed JSON or an unresolved environment reference. |
| Pay-as-you-go key rejected | Use the tp key from your token-plan subscription. sk keys are not supported by this provider yet. |
| Unsupported model | Use mimo-v2.5-pro, mimo-v2.5, mimo-v2.6-pro, or mimo-v2.6-flash. Namespaced xiaomi/... IDs belong to the separate OpenRouter route. |
| Slow before reasoning appears | Check the current TUI status. Preparing request… is pre-stream work; Waiting for model response… means the primary request was dispatched but the provider has not emitted model output. Compare the direct token-plan route with OpenRouter only as separate serving paths. |
--base-url or --models rejected |
MiMo's endpoint and model list are built in. Configure only the key, then select a model with --model at launch. |
| Replacing the stored key has no effect | Check whether MIMO_API_KEY is still exported, then restart OpenMed. |
Custom OpenAI-compatible endpoints¶
The custom provider points OpenMed at servers you operate — vLLM,
SGLang, llama.cpp, Ollama, LM Studio, or a company gateway. Serving engines
launch one model per endpoint, so the configuration is a list of
endpoints, each with its own base URL, optional credential, and the one model
it serves. Selecting a model routes the session to the endpoint that declares
it. The hosted providers (OpenAI, Anthropic, OpenRouter, MiMo) deliberately have
no base-URL configuration — a provider means one thing, and any stored
base URL for them is ignored and stripped.
Add one endpoint per model:
openmed config provider-set custom --base-url http://host1:8000/v1 --model "nvidia/Nemotron-3-Super-120B"
openmed config provider-set custom --base-url http://host2:8000/v1 --model "Qwen/Qwen3-235B-A22B" --api-key sk-host2
openmed config provider-set custom --base-url http://host4:8000/v1 --model "poolside/Laguna"
openmed config endpoint-doctor http://host1:8000/v1 # validate each FIRST
openmed agent --provider custom --model "poolside/Laguna"
Re-running with the same --model updates that endpoint (a stored key is
kept unless you pass a new one); --remove-model <id> deletes it.
Or in ~/.openmed/provider_secrets.json directly — an endpoints list under
the custom section:
{
"provider": {
"custom": {
"options": {
"endpoints": [
{"baseURL": "http://host1:8000/v1", "model": "nvidia/Nemotron-3-Super-120B"},
{"baseURL": "http://host2:8000/v1", "model": "Qwen/Qwen3-235B-A22B", "apiKey": "{env:HOST2_KEY}"},
{"baseURL": "http://host4:8000/v1", "model": "poolside/Laguna"}
]
}
}
}
}
apiKey is optional per endpoint (local servers are commonly keyless) and
supports literal or {env:VAR} forms. The same one-object-one-provider-map
rule from Editing the file directly applies.
For a quick test without touching the file, environment variables declare one
additional endpoint: OPENMED_CUSTOM_BASE_URL, OPENMED_CUSTOM_MODEL, and
optionally OPENMED_CUSTOM_API_KEY. They win over a stored endpoint that
declares the same model.
What the provider does for you at startup:
- Routes by model. Each session talks only to the endpoint declaring its
model, with that endpoint's own credential — keys never cross hosts, and
your
OPENAI_API_KEYcan never silently travel to your own server. - Consults the endpoint's
/modelslisting. A model the server does not actually serve fails immediately, naming what it does serve. - Reads the served context window (vLLM/SGLang
max_model_len) for automatic compaction. A server that reports none pins the window to unknown — automatic compaction is off for that session (see Context And Compaction) and a hosted catalog namesake can never supply a fabricated number. - Sends the safe request shape. Chat Completions only,
temperatureomitted, streamed usage requested, and the runtime guard refuses a turn whose "text" is really an unparsed tool call. - Refuses cleartext keys to remote hosts — plain
http://with a stored key is allowed only for localhost and private networks, enforced both in the CLI and again at resolution so a hand-edited file cannot bypass it.
Configured models appear as a Custom endpoint group in the /model
picker, each row naming the host that serves it.
What custom models are not
OpenMed cannot vouch for a model it has never evaluated. Tool-calling
discipline, clinical quality and safety behaviour are yours to validate —
endpoint-doctor proves the plumbing, not the medicine. Per-model launch
flags for the common engines, including the exact --tool-call-parser
value (which fails silently when wrong), are in
PLANS/model-serving-matrix.md.
Checking an endpoint before you trust it¶
openmed config endpoint-doctor tells you whether an OpenAI-compatible server —
your own vLLM, SGLang, llama.cpp or Ollama, or a gateway — can actually drive the
agent. It makes no changes and is safe to run against anything.
A healthy endpoint:
Endpoint: http://localhost:8000/v1
Model: openmed-local
[PASS] reachable: Connected to http://localhost:8000/v1.
[PASS] auth: Credentials accepted.
[PASS] models: 1 model(s) served: openmed-local
[PASS] context: Context window 262,144 tokens.
[PASS] streaming: Server streamed a response (SSE).
[PASS] tools: Model emitted a streamed tool call.
[PASS] tool_call_index: Tool-call deltas carry `index`.
[PASS] temperature: Model accepts `temperature`.
Result: usable by OpenMed (2.4s)
Exit codes are 0 usable, 1 not usable, 2 bad input — so it works in CI.
--json emits the same report as JSON. --model picks a specific model, and
--quick sends one tool instead of OpenMed's full 93-tool schema (faster, but it
will not surface large-tool-array failures).
What it checks, and why¶
| Check | Catches |
|---|---|
reachable |
Wrong port, wrong scheme, DNS typo, TLS mismatch — each reported distinctly |
auth |
Missing or wrong API key (self-hosted servers 401; public catalogs do not) |
models |
Server up but serving nothing, or a listing that is not OpenAI-shaped |
context |
A window too small for OpenMed's ~25,300-token per-turn overhead |
streaming |
Gateways that buffer and break SSE — OpenMed only streams |
tools |
Tool calling disabled, unsupported, or rejected at 93 tools |
leaked_tool_call |
The safety check — a wrong tool-call parser returning tool syntax as prose |
tool_call_index |
Streamed deltas missing index, which breaks parallel calls |
finish_reason |
Shapes the runtime rejects, which would crash on the first tool turn |
temperature |
Reasoning models that reject the parameter |
The failure that matters most¶
If the server's tool-call parser is missing or wrong, it returns the tool call as
plain text with tool_calls empty and finish_reason: "stop". There is no
error, and in a clinical agent that text is indistinguishable from real content.
The doctor fails the endpoint and names the fix:
[FAIL] leaked_tool_call: The model returned tool syntax as PLAIN TEXT
(XML <tool_call> (Qwen 3.5+, GLM, MiniMax)) with no tool_calls
(finish_reason='stop'). The server is not parsing tool calls.
-> The tool-call parser is missing or wrong. vLLM needs BOTH
-> --enable-auto-tool-choice AND --tool-call-parser <name> (there is no
-> 'auto'); SGLang needs --tool-call-parser. Use the value for this model
-> family — e.g. Qwen 3.5+ is `qwen3_coder`, NOT `hermes`.
It also catches the harder version of this, where a parser matches the first tool call and leaks the rest as text — the case that otherwise looks healthy.
Context: the quiet killer¶
OpenMed sends ~93 tool schemas (~21,000 tokens) plus a ~4,500-token system prompt on every turn. A window below that truncates every request:
[FAIL] context: Context window is 16,384 tokens; OpenMed's tool schema alone
is 20,791. Every request would be truncated.
-> Raise the served context to at least 65,536: vLLM --max-model-len 65536,
-> SGLang --context-length 65536, llama.cpp --ctx-size 65536 (its default is
-> 4096), Ollama OLLAMA_CONTEXT_LENGTH=65536 before `ollama serve`.
llama.cpp defaults to 4096, Ollama auto-scales and can land at 4k, and Google's own vLLM recipe for Gemma 4 ships 16384 — all below the floor.
Serving your own model¶
Per-model launch flags, including the exact --tool-call-parser value for each
family, are in PLANS/model-serving-matrix.md. The short version for the
recommended single-GPU model:
vllm serve Qwen/Qwen3.8-27B \
--served-model-name openmed-local \
--enable-auto-tool-choice --tool-call-parser qwen3_coder \
--reasoning-parser qwen3 \
--max-model-len 65536 \
--host 0.0.0.0 --port 8000 --api-key sk-openmed-CHANGEME
openmed config endpoint-doctor http://localhost:8000/v1
Credentials
Prefer OPENMED_ENDPOINT_API_KEY over --api-key: arguments are visible in
ps and shell history. The doctor refuses to send a key over cleartext
http:// to a non-local host, strips any user:pass@ from the URL before
printing anything, and never echoes the key into its output.
Web search¶
Web search is off by default on every provider, including OpenAI and ChatGPT OAuth, and is not a provider capability — it runs client-side so it behaves identically on Anthropic, OpenRouter, MiMo, Doubleword, or a local model. Enabling it sends search queries to a third-party vendor, so it is an explicit operator decision. See Web Search.
How a provider is selected¶
The TUI defaults to gpt-6.1-sol with medium reasoning. When multiple providers are configured, the model you select determines the provider:
claude-*models use Anthropic when Anthropic credentials are available.- GPT-6.1 Sol and GPT-6 models use ChatGPT OAuth first when
~/.openmed/auth.jsonexists, then fall back to the OpenAI key. Explicit GPT-5.5 selections prefer an API key when available. - Bare
mimo-v2.5-pro,mimo-v2.5,mimo-v2.6-pro, andmimo-v2.6-flashIDs use MiMo token-plan credentials. If MiMo is the only configured provider, an ordinary launch selectsmimo-v2.5-pro. - Namespaced model ids (
vendor/model, such asgoogle/gemini-3.7-flash) use OpenRouter when its key is configured. Without an OpenRouter key, a configured Doubleword key can select Doubleword for these IDs. Use--provider doublewordexplicitly to select Doubleword when both keys are present. - Selecting the Doubleword group in
/model, or passing--provider doubleword, selects its real-time adapter. Its default model isdeepseek-ai/DeepSeek-V4.1-Flash; async and batch jobs use their own CLI commands. - With no credentials at all, the native fallback remains available.
Choose the model at launch, or at runtime with Ctrl+M or /model:
openmed agent --model gpt-6.1-sol --reasoning-effort medium
openmed agent --provider openrouter --model qwen/qwen3.8-27b
openmed agent --provider doubleword --model deepseek-ai/DeepSeek-V4.1-Flash
--provider forces a specific provider; omit it to let the model decide.
Reasoning effort¶
The picker exposes model-specific values. GPT-6.1 Sol and GPT-6 Astra offer low, medium, high, xhigh, and max; GPT-6 Sol and Luna also offer none. GPT-5.5 stops at xhigh. Supported Anthropic models have their own effort levels. extra_high remains accepted as a compatibility alias and resolves to xhigh on OpenAI models.
For OpenRouter models the effort is clamped to the levels that model actually advertises, so a selection is never rejected by the provider.
Provider keys vs native medical services
OpenAI, Anthropic, OpenRouter, MiMo, and Doubleword keys are model-provider credentials. OpenMed's native medical services — extraction, PII, de-identification, terminology, coding — are a separate service plane with their own configuration. See Native Medical Services.