conductor-core is the reusable prompt-to-MIDI engine behind the Conductor
applications. It can be embedded in a CLI, notebook, backend service, test
harness, or another UI without importing Gradio, Dash, Plotly, or the evaluation
package.
Core owns:
- provider routing for OpenAI, Anthropic, Google, and Ollama
- validated four-bar loop models and provider response parsing
- prompt assembly and model capability metadata
- loop-to-MIDI and MIDI-to-loop conversion
- generation workspaces, messages, metadata, and history persistence
- optional SoundFont discovery and MIDI-to-audio rendering
- structured generation results and progress events
Core uses uv 0.11.16 or newer for local development. After cloning the repository, run this from the repository root:
uv sync --all-extrasThat creates the virtual environment and installs Core, the development tools,
and every optional provider and playback dependency. You do not need to
activate the environment. If you only need the base package and development
tools, use uv sync instead.
Run the project checks with:
uv run --locked --all-extras ruff format --check .
uv run --locked --all-extras ruff check .
uv run --locked --all-extras pytest -q
uv buildWhen intentionally updating dependencies, run uv lock --upgrade, review the
lockfile diff, and rerun the checks. Do not edit uv.lock by hand.
Pin Core to a release tag and choose only the optional features your application
needs. Use providers for all model providers, a provider name such as google
for just one, and playback for audio helpers.
# Add Core to a uv-managed project
uv add "conductor-core[providers] @ git+https://github.com/laceyp99/conductor-core.git@v0.4.0"
# Install Core in a pip-managed environment
python -m pip install "conductor-core[providers] @ git+https://github.com/laceyp99/conductor-core.git@v0.4.0"Available provider extras are openai, anthropic, google, and ollama.
Extras can be combined—for example, use [google,playback] for Gemini generation
with audio previews. To upgrade, change the pinned tag and review
CHANGELOG.md.
from conductor_core import EngineConfig, GenerationRequest, LoopGenerationEngine
engine = LoopGenerationEngine(EngineConfig.from_defaults())
result = engine.generate(
GenerationRequest(
key="C",
scale="Major",
description="warm neo-soul electric piano chords",
model="gemini-3.1-flash-lite",
temperature=0.3,
)
)
print(result.generation_id)
print(result.midi_path)
print(result.cost)generate() is synchronous. It calls the selected provider, converts the
validated loop to MIDI, and persists the resulting artifacts before returning.
For a complete editable workflow—including prompt customization, progress
events, persisted result fields, and optional audio rendering—see
scripts/generate_midi.py. Running that example
makes a real provider call and may incur usage charges.
Credentials can be injected by the calling application:
from conductor_core import EngineConfig, ProviderCredentials
config = EngineConfig.from_defaults(
artifact_root="my-output",
provider_credentials=ProviderCredentials(
openai_api_key="...",
google_api_key="...",
anthropic_api_key="...",
ollama_host="http://localhost:11434",
),
)If a credential is not injected, provider modules fall back to these environment variables:
OPENAI_API_KEY="..."
GEMINI_API_KEY="..."
ANTHROPIC_API_KEY="..."
OLLAMA_API_HOST_ADDRESS="http://localhost:11434"The provider is derived from the route actually used for model;
GenerationRequest does not accept a caller-supplied provider. To inspect
available providers, models, and capabilities without contacting a provider, run
scripts/inspect_models.py.
| Field | Purpose |
|---|---|
key, scale, description |
Musical request added to the model prompt |
model |
Packaged model identifier used for routing and response handling |
temperature |
Sampling temperature for models that support it |
use_thinking |
Toggle-style reasoning control for supported models |
effort |
Model-specific reasoning effort such as minimal, low, or high |
prompt_override |
System prompt override for only this request |
render_audio |
Request an MP3 preview after MIDI generation |
soundfont_path |
SoundFont name or path for this request |
Model capabilities differ. Consumers can inspect
conductor_core.music.get_model_info() or run
scripts/inspect_models.py instead of assuming
every model accepts temperature or the same reasoning settings.
Every packaged cloud model has a rate_limits object with the same fields:
RPM, TPM, and RPD. RPM is a positive integer conservative baseline for
the lowest generally supported account tier that Core intentionally supports;
it is not a complete representation of provider usage, loyalty, priority, or
entitlement tiers. TPM and RPD are positive integers when a matching
baseline is intentionally recorded and null when it is unknown or cannot be
represented consistently. Core exposes this metadata but does not schedule or
retry requests from it.
Core ships with a default loop-generation prompt. Set prompt_override on
EngineConfig for every request made by an engine or on GenerationRequest
for one request. The request override takes precedence over the engine override,
which takes precedence over the packaged prompt. The generation script contains
a commented prompt override ready to edit.
Pass a callback to generate(..., progress_callback=...) to adapt synchronous
Core work to logs, a progress bar, a queue, or an asynchronous UI wrapper.
Current stages include provider generation, MIDI processing, and audio
rendering. The callback reports progress but does not cancel an in-flight
provider request. The generation script prints each event as it arrives.
Set render_audio=True on a request to render an MP3 after MIDI generation.
Install the playback extra and provide FluidSynth and FFmpeg on the system
PATH. Leaving soundfont_path unset uses Core's default packaged SoundFont;
set it on the request or default_soundfont_path on EngineConfig to choose
another. Audio failure does not discard a successful MIDI generation: Core
returns the MIDI with a warning and audio_path=None.
Lower-level discovery and rendering helpers live in conductor_core.playback.
The generation script enables audio with the default SoundFont and reports both
the MIDI and audio result paths.
Core stores durable generation history under one predictable Conductor suite root. The default layout is:
~/.conductor/
core/
generations/
gen_<id>/
loop.mid
loop.mp3 # only when audio rendering succeeds
messages.json # when provider messages are available
metadata.json
On Windows, ~/.conductor/core is
%USERPROFILE%\.conductor\core. Path selection has this precedence:
CONDUCTOR_CORE_DATA_DIRselects Core's complete project data directory.CONDUCTOR_HOMEselects the shared suite root; Core appendscore.- Otherwise Core uses
Path.home() / ".conductor" / "core".
Both environment variables support ~ expansion. PowerShell examples:
# Relocate every participating Conductor project under one suite root.
$env:CONDUCTOR_HOME = "D:\ConductorData"
# Relocate only Core; this takes precedence over CONDUCTOR_HOME.
$env:CONDUCTOR_CORE_DATA_DIR = "D:\ConductorData\custom-core"An explicit EngineConfig.artifact_root or FilesystemArtifactStore root still
overrides the default generation location. Request- and engine-specific prompt
or SoundFont choices keep their existing precedence, and caller-added SoundFont
search directories remain separate from Core's packaged read-only resources.
Packaged prompts, model metadata, and the bundled SoundFont are not copied or
moved into the data directory. Core currently owns no persistent configuration
or disposable disk cache.
Resolving or importing these paths does not create directories. Core creates the selected generation-history directory only when a generation workspace is written.
Generation history can grow through MIDI, JSON, and especially optional MP3 files. Core retains the newest 20 generations by default, but custom artifact stores and manually retained files still consume space at their selected location.
Configure retention on the engine or store. Use None only when the calling
application owns its disk-usage policy:
from conductor_core import EngineConfig
from conductor_core.storage import FilesystemArtifactStore
config = EngineConfig.from_defaults(max_generations=100)
unlimited_store = FilesystemArtifactStore("my-output", max_generations=None)GenerationResult contains:
| Attribute | Contents |
|---|---|
generation_id |
Unique filesystem generation identifier |
loop |
Validated provider-independent loop object |
midi_path |
Persisted MIDI path |
audio_path |
Persisted MP3 path, when rendering succeeds |
messages |
Provider conversation/response messages |
cost |
Provider-reported estimated cost, when available |
metadata |
Persisted generation metadata |
warnings |
Non-fatal issues such as skipped audio |
Each generation workspace contains loop.mid, messages.json,
metadata.json, and optionally loop.mp3. Use FilesystemArtifactStore for
custom history roots, loading saved generations, deleting generations, and
updating saved audio metadata. By default, history retains the newest 20
generations. The generation script shows the most commonly consumed result
fields after a run.
For new engine generations, GenerationMetadata.use_thinking and
GenerationMetadata.effort record the exact reasoning settings from the
GenerationRequest. Both fields are optional for compatibility with history
written by Core 0.2.0 and earlier: None means the value was not recorded, not
that a historical default should be inferred. In particular, consumers should
distinguish an explicit False value from None.
Consumers can convert existing MIDI into Core's four-bar loop model and write it
back without a provider call. See
scripts/midi_loop_roundtrip.py for an
offline example that normalizes note starts and durations to sixteenth-note
integer positions.
Additional packaged utilities and models are available from:
conductor_core.modelsfor loop, bar, note, and timing models;conductor_core.musicfor model metadata, prompts, scales, and durations;conductor_core.routingfor lower-level provider routing;conductor_core.storagefor artifact and history management;conductor_core.playbackfor optional audio operations.
Prefer LoopGenerationEngine for complete generation workflows so persistence,
cleanup, prompt handling, and provider behavior stay consistent.
Provider, parsing, and MIDI conversion errors are raised to the caller. If an error occurs after a workspace is allocated, Core removes the unfinished workspace. Callers should catch exceptions at their application boundary and decide how to display, retry, or log them.
Lower-level audio rendering failures raise AudioRenderingError. The generation
engine treats optional audio failure as non-fatal, returning the generated MIDI
with audio_path=None and the rendering diagnostic in warnings.
Hosted providers fail before constructing an SDK client when their required API
key is missing or blank. These failures, along with credentials rejected by a
provider, raise ProviderAuthenticationError. Other provider SDK failures use
the public ProviderError hierarchy and identify the provider and operation.
Core emits log records under the conductor_core logger namespace and never
configures handlers or global logging itself (a NullHandler is attached so
unconfigured consumers see no warnings). To surface Core logs, configure
logging in the application:
import logging
logging.basicConfig(level=logging.INFO) # everything to the console
# or route only Core records somewhere specific:
logging.getLogger("conductor_core").addHandler(my_handler)uv run --locked --all-extras pytest -qThe tests are deterministic and do not make live provider calls or require the audio toolchain.
Release history, compatibility notes, and migration guidance live in
CHANGELOG.md. GitHub releases provide the automatic source
archives for each tag; this project does not attach release-specific wheels or
binary downloads.
