Repository navigation
Claude blocks: configurable endpoint (ANTHROPIC_BASE_URL) and timeout - #26
Conversation
The Messages API URL was hardcoded to api.anthropic.com, so the Claude blocks could not use any compatible local or self-hosted endpoint (oMLX, Ollama, vLLM, LM Studio, ...). - ANTHROPIC_BASE_URL (same convention as the Claude Code CLI) points the API path at another Messages API endpoint; /v1/messages is appended when absent, the key is still sent as x-api-key - PRINTR_CLAUDE_TIMEOUT_SECS replaces the hardcoded 120 s call timeout (both API and CLI paths); slow local models need more - unit tests for URL/timeout resolution - docs: guide (en/fr), CONTRIBUTING (en/fr), CHANGELOG
|
Validation against a local inference server (oMLX, Messages API on
The ticket, rendered dot-for-dot by the emulator (512 dots, 180 dpi): Terminal capture ( (No physical printer was used: One honest note on model quality: with this local model, structured output is occasionally malformed — 2 of 5 horoscope attempts returned prose (or a |
XNinety9
left a comment
There was a problem hiding this comment.
Thanks a lot for this, it's a great fit for Printr: keeping family content on the home network is exactly the spirit of the project, and the PR is small, clear and tested. I'd like to ship it in 1.11.1, after three small changes:
1. ANTHROPIC_BASE_URL is ignored when there is no ANTHROPIC_API_KEY. Claude::from_env only picks the API backend when a key is set; otherwise it falls back to the Claude CLI. Many local servers (Ollama, LM Studio…) don't need a key, so someone who only sets ANTHROPIC_BASE_URL would silently get the CLI (or "Claude unavailable") without understanding why. Could you pick the API backend whenever ANTHROPIC_BASE_URL is set, and send x-api-key only when a key exists? If you prefer to keep it simple, a line in the docs saying "set ANTHROPIC_API_KEY to any value for a server that needs no key" would do too.
2. PRINTR_CLAUDE_TIMEOUT_SECS=0 gives a zero timeout, so every call fails right away. Ignoring 0 (or any value below a few seconds) and keeping the default would avoid that, e.g. .filter(|&s| s > 0).
3. The tests change the process environment (set_var/remove_var) while other tests run in parallel. That can make the suite flaky (we already had to fix a test like that). Could you split the parsing into pure functions that take the value as a parameter, e.g. fn api_url_from(value: Option<&str>) -> String and fn timeout_from(value: Option<&str>) -> Duration, test those, and have api_url() / api_timeout() read the environment and call them? The PC850 code page patch (#31) does it that way in cp858.rs.
One sentence I'd add to the docs: with ANTHROPIC_BASE_URL, the API key is sent to that server, so it should only point to a server you trust (or use a dummy key for a local one).
Small detail, no need to change anything: api_timeout() is read on every loop of the CLI wait (every 20 ms); reading it once before the loop would be enough.
Thanks again, looking forward to it!
…rd, pure parse functions - Pick the API backend whenever ANTHROPIC_BASE_URL is set; send x-api-key only when a key exists (local servers need no key) - PRINTR_CLAUDE_TIMEOUT_SECS=0 (or garbage) now falls back to the 120 s default instead of a zero timeout - Split URL/timeout parsing into pure functions (api_url_from / timeout_from) and test those, so no test touches the process environment anymore - Read the CLI timeout once before the wait loop - Docs: trust note for ANTHROPIC_BASE_URL, keyless note (en + fr) Requested in review of PR XNinety9#26 by XNinety9.
48be53b to
3efaa0e
Compare
…rd, pure parse functions - Pick the API backend whenever ANTHROPIC_BASE_URL is set; send x-api-key only when a key exists (local servers need no key) - PRINTR_CLAUDE_TIMEOUT_SECS=0 (or garbage) now falls back to the 120 s default instead of a zero timeout - Split URL/timeout parsing into pure functions (api_url_from / timeout_from) and test those, so no test touches the process environment anymore - Read the CLI timeout once before the wait loop - Docs: trust note for ANTHROPIC_BASE_URL, keyless note (en + fr) Requested in review of PR XNinety9#26 by XNinety9. (cherry picked from commit 3efaa0e)
3efaa0e to
3185e84
Compare
…nctions - Pick the API backend whenever ANTHROPIC_BASE_URL is set; send x-api-key only when a key exists (local servers need no key) - PRINTR_CLAUDE_TIMEOUT_SECS=0 (or garbage) now falls back to the 120 s default instead of a zero timeout - Split URL/timeout parsing into pure functions (api_url_from / timeout_from) and test those, so no test touches the process environment anymore - Read the CLI timeout once before the wait loop - Docs: trust note for ANTHROPIC_BASE_URL, keyless note (en + fr) Requested in review of PR XNinety9#26 by XNinety9. (cherry picked from commit 3efaa0e)
3185e84 to
1cd81cf
Compare
XNinety9
left a comment
There was a problem hiding this comment.
Thanks a lot! All three points are addressed, and the keyless run against a local server is exactly what I hoped for. fmt, clippy and the tests pass locally. Merging: it will ship in 2.0 'Mergenthaler'. Glad your kids enjoy it
PRINTR_CLAUDE_TIMEOUT_SECS (from #26) can be set in the app. ANTHROPIC_BASE_URL stays out on purpose: the Claude key is sent to that address, and the app never hands the key out.


Why
The Claude blocks are the only part of Printr that must call a specific third-party endpoint — everything else is local or uses free, keyless APIs ("frugal by default" is the project's own line). With the URL hardcoded to
api.anthropic.com:ANTHROPIC_BASE_URL— the standard convention of the Claude Code ecosystem. The API path was the one of the two that couldn't be steered; one variable keeps one convention in the same program.Printr's audience — Pi, systemd, MQTT/Node-RED, Docker, self-hosting — is exactly the crowd already running local LLMs; one environment variable lets Printr join that setup.
What changes
ANTHROPIC_BASE_URLpoints the API path at any Messages-API-compatible endpoint./v1/messagesis appended when absent (trailing slash tolerated); the key is still sent asx-api-key, no client change.PRINTR_CLAUDE_TIMEOUT_SECSreplaces the hardcoded 120 s timeout (API and CLI paths); default unchanged.Verified
Against a local MLX inference server exposing the Messages API: the exact request Printr sends (system prompt +
output_configwitheffortand JSON schema) is accepted, and a horoscope returns schema-matching JSON in seconds.cargo fmt --checkpasses with no diff and the aarch64 build produces no warnings.Happy to rename the variable or split the timeout into a separate PR if preferred.