- _history_kwargs unconditionally populates input_text/output_text for
cost tracking, regardless of save_history toggle.
- OllamaClient falls back to prompt_eval_count/eval_count before tiktoken.
- Release 0.5.2.
The Apply Update endpoint used 'git pull origin {branch}', which fails
with a merge conflict if the working tree has any local modifications.
This happened to every install that had leftover version-string edits from
a prior partial update, leaving the old code running silently.
Fix: unconditionally stash (including untracked files) and reset to the
exact remote commit before reinstalling. The update now succeeds even
if the repo is dirty.
Bumps all version strings to 0.5.1 so existing 0.4.2 → 0.5.x installs
immediately receive the new updater logic.
- __init__.py, app.py, pyproject.toml: bump to 0.5.0
- __init__.py: query guanaco-llm-proxy (not guanaco) and only override
hardcoded if metadata version >= hardcoded version
Prevents stale guanaco-0.4.2.dist-info from clobbering the version.
Fixes apply_update showing wrong version after git-pull.
- Add _fetch_usage_level_sync() to parse ollama.com/library/{model} HTML
- Handle both top-level model badges (x-test-model-cost-slot-active)
and per-tag listings (x-test-model-tag-cost + x-test-model-tag-usage-slot-active)
- Add async fetch_usage_levels() with 1h global cache + parallel fetching
- Wire usage_multiplier into /v1/models and /api/ollama/models responses
- Replace heuristic _get_model_multiplier fallback with real scraped levels
- Fixes gemma3:4b/12b showing 1.00x instead of 0.25x, qwen3-vl 0.25x→0.75x, etc.
Changes:
- Add missing UsageConfig fields (last_plan, redirect_on_full, last_session_reset, last_weekly_reset, last_checked) to fix AttributeError crashes
- Add config migration layer in load_config() for v0.4.2 to v0.4.3+ configs
- Rename package guanaco to guanaco-llm-proxy to avoid PyPI collision
- Add Ollama API key validation during install.sh setup (fixes: use real key var, fix env file write, fix grep pattern)
- Add startup version sanity check detecting repo/venv mismatch
- Fix systemd service WorkingDirectory to repo checkout
- Add GUANACO_CONFIG_DIR env var to systemd service
Adds a new experimental feature to the Status tab that compares your
total subscription cost to what you'd pay with OpenRouter's cheapest
provider pricing.
Backend:
- guanaco/roi.py: Fetches live prices from OpenRouter API, matches
Ollama model names to OpenRouter model IDs via family inference
(exact → family prefix → same parameter size → size window →
global average), multiplies tokens by $/Mt cost.
- Calculates this week's cost, extrapolates to 100% weekly usage,
then monthly. Shows ROI multiplier (monthly value / subscription).
- /dashboard/api/roi/calculate: fresh calculation
- /dashboard/api/roi/last: cached last result
- /dashboard/api/roi/reset: clear data
- /dashboard/api/roi/config: enable/disable, set price tier
Dashboard (Status tab):
- Toggle to enable/disable the feature
- Pro (0/mo) / Max (00/mo) / Custom price selector
- Shows: this-week cost, weekly @ 100%, monthly value, ROI multiplier
- Per-model table: prompt_tokens, completion_tokens, $/Mt in/out,
total cost, % of usage
- Warnings for unmatched models and stale prices
- Reset button clears cached data
ROIConfig added to config.py (persisted in config.yaml).
Previously new Ollama Cloud models showed no capability badges and
assumed 1.00x multiplier until manually added to KNOWN_CLOUD_MODELS.
Now both backend (client.py) and frontend (dashboard.html) fall
back to name-based inference for unknown models:
- Capabilities: 'vision' from vl/gemma/gemini/kimi/deepseek, 'tools'
from coder/minimax/glm/mistral/gpt-oss/devstral/nemotron/deepseek,
'thinking' from deepseek/cogito/reason/think and kimi-k* families
- Multiplier: extract :XXb|:Xt from model name for size-based tier
(<=20b=0.25, <=80b=0.50, <=400b=0.75, >400b/1t=1.00), then fall
back to name heuristics (nano/mini/small=0.25, flash/gemma=0.50,
pro/mistral-large=1.00)
Models explicitly in KNOWN_CLOUD_MODELS still use exact values.
This means new models like deepseek-v5-pro or kimi-k3 show up
correctly in the dashboard immediately without code changes.
- Add usage_multiplier column to analytics DB (auto-resolved from model name)
- Log weighted token totals (prompt*multiplier, completion*multiplier) in summary
- Show cost bars + weighted totals in dashboard analytics tab
- Add per-model multiplier column to analytics table
- Auto-compute total_tokens in analytics if not provided
- Add deepseek-v4-pro (1.0x) and deepseek-v4-flash (0.50x) to KNOWN_CLOUD_MODELS
- Add gemini-3-flash-preview to available_models and dashboard capability maps
- Add new models to config.py available_models list
- KNOWN_CLOUD_MODELS: add kimi-k2.6 with [vision, tools, thinking, cloud]
- LLMConfig defaults: reranker/scraper/summary/default all set to nemotron-3-nano:30b
- available_models: add nemotron-3-nano:30b and kimi-k2.6
- dashboard.html: add kimi-k2.6 and nemotron-3-nano capability maps
- cli.py setup wizard: update all model prompts/default placeholders
- version bump: 0.4.2 → 0.4.3 in __init__.py, app.py, pyproject.toml
Update-safe: only new model registry entries + default value changes.
Existing configs preserve their saved values (pydantic).
Zero schema or API break.
Features:
- Multi-account rotation: Add multiple Ollama Cloud accounts with API keys
and session cookies via Dashboard Accounts tab or API
- Quota-aware selection: AccountPool picker prefers least-loaded accounts;
new/untested accounts get priority so they're validated immediately
- Premium model routing: kimi-k2.6 and glm-5.1 restricted to Pro/Max
accounts only; free-tier accounts show warning badge in dashboard
- Account-level usage tracking: Per-account session/weekly percentages,
plan type, and reset timers displayed in Dashboard Accounts tab
- Analytics account_name: New DB column logs which account handled each
request; provider field also includes account suffix (ollama:ollama2)
- Dashboard Accounts tab: Full CRUD (add/remove/update-key/set-cookie/
check-usage/test), usage progress bars, plan badges, premium warnings
- Config backward compatibility: Old configs without ollama_accounts
auto-generate the primary account from legacy ollama_api_key + usage
fields; no data loss on save/restart cycles
APIs:
- GET /api/accounts - List all accounts with usage data
- POST /api/accounts/add - Add new account
- POST /api/accounts/remove - Remove account by name
- POST /api/accounts/test - Test account connectivity
- POST /api/accounts/update-key - Update API key
- POST /api/accounts/session-cookie - Set session cookie
- POST /api/accounts/check-usage - Refresh usage from Ollama settings
DB Migration:
- account_name TEXT column added to request_log (auto-migrated on startup)
All existing rows get NULL for account_name (no data loss)
Files changed:
- guanaco/accounts.py: AccountPool with model-aware filtering, PREMIUM_MODELS
- guanaco/analytics.py: account_name param in log_llm(), migration
- guanaco/app.py: AccountPool wiring into create_app()
- guanaco/config.py: ollama_accounts list, legacy sync, primary_account property
- guanaco/dashboard/dashboard.py: Account management API endpoints
- guanaco/dashboard/templates/dashboard.html: Accounts tab UI
- guanaco/router/router.py: _select_account(), provider=logging with account
- pyproject.toml: version 0.4.2
- checkForUpdate() now runs on page load (not just System tab click),
so stale 'update available' banners clear after refreshing
- checkForUpdate() retries on network errors (service may be restarting)
- saveHistoryConfig() now syncs UI state from server POST response,
preventing toggle drift on tab switch
- __init__.py version parsing uses strict semver regex, preventing
RC/pre-release versions like '0.4.0rc1' from overriding the hardcoded
fallback. Only clean X.Y.Z versions >= baseline are accepted.
- Bumped version to 0.4.1 across __init__.py, app.py, pyproject.toml
- New fallback_reason TEXT column in request_log (auto-migrated)
- log_llm now accepts fallback_reason parameter
- All fallback log_llm calls in router.py now pass a reason:
- 'Quota full (session=X%, weekly=Y%)' for quota redirects
- 'Ollama error: <description>' for error-triggered fallbacks
- Works for both streaming and non-streaming paths
- History list: fallback badge has title tooltip with reason,
plus inline orange snippet showing reason text
- History modal: fallback banner shows 'Reason: <text>'
- Also: replaced emoji ℹ️ info icon with SVG icon for token tooltip
- Remove the alert() blocker that prevented modal from opening for entries
without content (was showing 'logged before history was enabled')
- Now the modal always opens showing metadata (model, duration, tokens, etc.)
- Add orange warning banner when content is missing: explains it was either
from an older instance or pre-history code
- Remove redundant 'Input not saved' / 'Output not saved' stubs
- If content exists, input/output sections render with full text
- If content is missing, just the orange warning explains why
- List rows: red left border + error snippet on failed requests
- Detail modal: full error with monospace formatting, provider info
- Add 'Failed Only' filter option in dropdown
- Add errors_only param to get_history() API
- Add error_count to history stats display
closest('div') hits the sticky header div first, not the modal overlay.
Added .history-modal class to both modals, close with closest('.history-modal').remove()
- Fix: showTab() now includes 'history' in tab visibility list
- Fix: showTab() no longer crashes on programmatic calls (uses querySelector)
- Fix: Add has_content boolean to get_history() for badge rendering
- Fix: _extract_output_text() captures reasoning content from GLM models
- Fix: _extract_sse_content() extracts both content and reasoning from SSE
- Fix: Router streaming paths properly accumulate output for history
- Enhancement: History tab now loads config/logs when switched to
These fixes enable the History tab to display request entries with content badges,
and properly capture both input_text and output_text (including reasoning) for
both streaming and non-streaming LLM requests.
- Change 'Ollama Weekly' label to 'Fallback Rate' in analytics overview
- Calculate fallback_rate as % of main provider requests routed to fallback
- Uses 24h rolling window for rate calculation
- Add get_fallback_rate() method for dedicated fallback queries
- Add optional full history fields (opt-in): source_ip, user_agent, input/output text
The auto-update now does stop → 2s gap → start instead of restart.
This ensures the old process is fully dead before the new one starts,
preventing the common issue where the new process fails to bind
the port because the old one hasn't released it yet.
Also increased the gap between stop and start from 1s to 2s.
## Bug Fixes
- **Fallback stream payload**: _call_fallback_provider() now always injects
payload["stream"] = stream into the JSON body. Previously, stream was only
passed as a function arg for httpx routing, not included in the actual JSON.
This caused providers like Fireworks to reject requests with max_tokens > 4096
that lacked "stream": true in the body.
- **Summarize toggle persistence**: Two-part fix:
1. Backend: POST /api/config now returns the search config in the response
(was only returning llm + fallback, so the JS callback had no data to sync)
2. Frontend: saveSearchConfig() now uses the POST response to update toggle
state instead of re-fetching via GET, which could return stale/empty data
on partially-updated installs
- **Version single-source**: Removed all importlib.metadata version lookups.
app.py now hardcodes __version__ = "0.3.9". check_for_update and apply_update
import from guanaco.app. This fixes health endpoint returning stale versions
(e.g. "0.3.0" while dashboard showed "0.3.8")
- **More reliable auto-update restart**: Changed from systemctl restart to
stop + 1s gap + start, which is more reliable for killing stubborn processes
## Tests
- Added tests/test_fallback_stream.py covering streaming and non-streaming
fallback payload injection, and verifying original payloads aren't mutated
- Endpoints tab: restored search provider endpoints card with dynamic
rendering showing all endpoints per provider with HTTP method badges
- New Search tab with playground: test any search provider or get an
AI summary of results in one click
- Summarize feature (BETA): /dashboard/api/summarize endpoint that
searches the web then feeds results to the configured summary_model
for an AI-generated summary with source citations
- Handles thinking models (qwen3.5) correctly: checks both content
and reasoning fields, uses max_tokens=4096 to avoid empty responses
- Provider endpoints are now defined as class metadata (endpoints
property) and rendered dynamically instead of hardcoded in JS
- Bump version to 0.3.7
- Add endpoints class property to ProviderEmulator base + all 8 providers
listing their routes with path and HTTP method
- Dashboard backend now includes endpoint metadata in PROVIDERS JS object
- Dashboard JS renders all endpoints per provider dynamically instead of
hardcoding a single endpoint per provider
- Each endpoint shows HTTP method badge (POST, GET/POST) next to copy URL
- updateEndpoints() function uses PROVIDERS data instead of hardcoded map
- app.py: use importlib.metadata for version (no cross-module import
that breaks under systemd/uvicorn). Fallback to hardcoded 0.3.6.
- dashboard: apply_update uses BackgroundTask for restart, so the HTTP
response is sent BEFORE the service kills itself. Previously the
response was lost because systemctl restart terminated the process.
- dashboard: add import validation step before restart — tests that
create_app() and importlib.metadata.version() both work. If the
update would crash-loop, the handler returns an error instead and
does NOT restart. This prevents the v0.3.3→0.3.5 bootloop scenario.
- dashboard: remove unused subprocess import in check_for_update.
- All version sources now use importlib.metadata consistently
(app.py, __init__.py, update check, health endpoint).
- Bump version to 0.3.6.
Default behavior on main: only check stable releases (/releases/latest).
Set router.allow_prerelease: true in config to also see prereleases.
This keeps production users on stable releases while develop/test
instances can opt into cutting-edge builds.
- app.py: replace hardcoded __version__ with import from guanaco.__init__
- __init__.py: update fallback version to 0.3.5 to match pyproject.toml
- Version now lives in pyproject.toml only, propagates via importlib
- Fix: thinking models (GLM) sent 0 tokens in streaming analytics because
only 'content' was counted, not 'reasoning'/'reasoning_content' fields.
Now tracks content_chars and reasoning_chars separately, estimates tokens
at 4 chars/token
- Fix: _is_empty_stream_buffer false-positive on GLM streams — checked
'reasoning_content' but Ollama Cloud sends 'reasoning'. Now checks both
- Fix: _describe_error crashes on streaming httpx errors — catches
ResponseNotRead when accessing response.text
- Fix: fallback provider ReadTimeout on thinking models — streaming fallback
now uses 120s read timeout (was 30s)
- Fix: /health and FastAPI version hardcoded to 0.3.0 — now uses __version__
from importlib.metadata
- Fix: double [DONE] in streaming output — client uses return instead of
break in [DONE] handler
- Feat: streaming analytics captures prompt_tokens from usage data
- Feat: avg TPS/TTFT calculated over most recent 10k completion tokens
instead of all-time average
- Feat: completion_tokens prefers API usage data over per-chunk count