Request And Output
AbstractCore still supports the familiar prompt-first forms:
Table of Contents
Request shape Output shape Defaults and explicit overrides Capability default routes: one store, two entry points Route inspection Relatedresp = llm.generate("Summarize this.")
resp = llm.generate("Red cube", output="image")
resp = llm.generate(media={"type": "audio", "path": "meeting.wav"}, output="text")
The lower-level canonical shape is now the keyword form:
resp = llm.generate(
request={
"text": "Turn this into a calm synth loop.",
"media": [],
},
output={"modality": "music", "format": "wav"},
)
A sound effect uses the sound output and takes its length in seconds (or duration_s); the
route's model and the task reach the music plugin, and a sound effect without a length is 5
seconds:
resp = llm.generate(
text="laser gunshot",
output={"modality": "sound", "task": "text_to_audio", "seconds": 3, "format": "wav"},
)
clip = resp.outputs["sound"][0]
request= and the legacy prompt / text / messages / media kwargs normalize to the same
internal contract. This keeps the public API compatible while giving Core one stable semantic
request shape under the hood.
Request shape
The stable request payload is intentionally small:
text: plain request textmessages: optional chat historymedia: optional image/audio/video/document inputs
Routing fields do not live inside request. Provider/model/base URL selection still comes from:
- explicit call arguments and output-spec fields when you intentionally pin a route;
- capability defaults (
input.*,output.*) when you do not; or - the provider instance you created with
create_llm(...).
Output shape
output= stays separate from request and remains the public generated-output selector:
output="text"
output={"modality": "image"}
output={"modality": "video", "task": "image_to_video"}
output={"task": "tts"}
Core infers the concrete task structurally when it can:
- text request +
output=image-> text-to-image - one source image +
output=image-> image edit - one source image +
output=video-> image-to-video - audio media +
output=textwith no text prompt -> transcription - audio media +
output=voicewith no explicit voice id -> voice clone
Defaults and explicit overrides
The request/output contract does not remove explicit control. Manual routing still works when you need it:
resp = llm.generate(
request={"text": "Slow dolly shot over a misty valley."},
output={
"modality": "video",
"provider": "mlx-gen",
"model": "Wan-AI/Wan2.2-TI2V-5B-Diffusers",
"num_frames": 41,
},
)
When you do not pin a route, capability defaults fill in provider/model/base URL and, for the text-generation route, a default reasoning effort.
The reasoning effort
The text-generation route carries an optional reasoning field alongside its provider and model.
It is the execution host's default reasoning effort, and it follows the same precedence as the
provider and the model:
- an explicit
thinking=on the call, includingthinking=False, which turns reasoning off for that call, andthinking="auto", which asks for no control at all; - a provider-native reasoning field on the call (
reasoning_effort=,reasoning=,think=), sent as given; - the effort configured on the text route, applied when the call names none of the above and the route describes the instance serving the call: same provider (or endpoint profile) and same model. A route that names only a reasoning effort applies to every instance. A route configured for another provider or model does not set this instance's reasoning;
- nothing — no reasoning parameter is sent and the model behaves as it does without one.
The full rule, with the metadata that reports which one applied, is in Reasoning Control.
abstractcore config set-default output.text --provider lmstudio --model qwen3-30b --reasoning high
The same field is editable from the AbstractGateway console, and both entry points write the same row. See Centralized Config for the full route grid.
Capability default routes: one store, two entry points
There are two entry points to the framework — AbstractCore (low level) and AbstractGateway (high
level) — and one store of per-modality defaults. AbstractCore owns it
(~/.abstractcore/config/abstractcore.json, key capability_defaults.routes). AbstractGateway is a
full CRUD surface over that store, not a second copy: every gateway read hits Core's manager and
every gateway write goes through Core's setter. Setting a default in the Gateway console configures
Core's default, and both entry points then read the same value.
Route keys are a kind.modality[.task] grid. kind is the direction of the media the route
handles, not the direction of your intent — that is the only reading under which every modality
lands in exactly one cell (speech-to-text provisions the audio side, so it is an input route).
| Generation task | Route key | Broad fallback |
|---|---|---|
text_generation |
output.text (derived, read-only — canonical storage is input.text) |
— |
image_generation / text_to_image |
output.image.text_to_image |
output.image |
image_to_image / image_edit |
output.image.image_to_image |
output.image |
image_upscale |
output.image.image_upscale |
output.image |
text_to_video |
output.video.text_to_video |
output.video |
image_to_video |
output.video.image_to_video |
output.video |
tts |
output.voice |
— |
voice_clone (voice output + reference audio) |
output.voice |
— |
stt / transcription |
input.voice |
— |
music_generation |
output.music |
— |
sound_generation |
output.sound |
— |
text_to_scene3d |
output.scene3d.text_to_scene3d |
output.scene3d |
image_to_scene3d |
output.scene3d.image_to_scene3d |
output.scene3d |
| image / video / sound / music understanding | input.image / input.video / input.sound / input.music |
covered by input.text when that model is multimodal |
This mapping is stated once in code, in
abstractcore/config/capability_defaults.py::_OUTPUT_ROUTE_TABLE
(capability_route_key_for_output). Core's execution path and AbstractRuntime's media path both
delegate to it.
Two rules follow from the table:
- A
.tasksuffix is only legal for the tasks the store persists — the seven inCAPABILITY_ROUTE_TASKS.tts,stt,music_generationandsound_generationhave no sub-route; they resolve at the modality cell. Writingoutput.voice.ttsis rejected byset_capability_defaultand dropped on load. - A bare request with a source image attached resolves to the edit variant
(
image_to_image/image_to_video/image_to_scene3d) rather than the text-to-X one.
A pin always wins. A default only ever fills a field the request left absent. Naming
provider, model or base_url on an output spec makes that spec the author's: the route
contributes nothing further to it (an explicit provider that contradicts the configured row also
drops the row's options — see _route_row_contribution). So a default is safe to set: it changes
what unpinned calls do, never what pinned ones do.
Setting one from the Core entry point
abstractcore config set-default output.voice --provider supertonic --model supertonic-3
abstractcore config set-default output.image.text_to_image --provider mlx-gen --model <model-id>
abstractcore config set-default output.text # shows the row
abstractcore config clear-default output.music
AbstractCore's console-TUI (abstractcore --config) drives the same command. In Python:
get_capability_default / set_capability_default / clear_capability_default on the
configuration manager; list_capability_defaults() returns the whole grid, each row naming its
source (abstractcore.capability_defaults, not_configured, or a derived/covered marker).
Either spelling writes the one store, so a running AbstractGateway picks it up too: the Gateway host
fingerprints the config file and re-publishes the defaults to its live runtime on the next run (see
abstractgateway/docs/configuration.md).
Route inspection
For debugging and replay-oriented integrations, responses carry a bounded normalized route summary in metadata:
resp = llm.generate(request={"text": "hello"}, output="text")
route = resp.metadata.get("_resolved_generate_route")
That summary is meant for inspection and integration, not for ordinary application logic. It
records the normalized request, normalized outputs, resolved text/input/output routes, and the
effective reasoning default when one applied. When a configured effort was not applied,
reasoning is None and reasoning_source says why (not_applied:explicit_reasoning_effort,
not_applied:route_provider_differs, not_applied:route_model_differs).