Commit graph

18 commits

Author SHA1 Message Date
Evan Rice
87ff832ea3 fix(updater): stash + hard-reset instead of merge-pull; release 0.5.1
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.
2026-06-10 01:38:17 +00:00
Evan Rice
aadb45d026 hotfix(v0.5.0): correct version strings to 0.5.0
The squash-merge from develop included all v0.5.0 feature commits but
excluded the version-bump commit (b4ed4fa), which was local-only and
never pushed to origin/develop before the PR merge. This left main
reporting v0.4.3 despite having all v0.5.0 code.

Fixes: __init__.py, app.py, pyproject.toml all now report 0.5.0.
Update-safe: only version string changes, zero functional diff.
2026-06-10 01:16:50 +00:00
evangit2
4686af1a4f
release: v0.5.0 — usage tracking, ROI dashboard, multi-account, live usage-level scraping
* v0.4.3 — add kimi-k2.6 vision model, default all roles to nemotron-3-nano:30b

- 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.

* feat: cost-weighted analytics (usage_multiplier) + add deepseek-v4 models

- 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

* feat: auto-infer capabilities and multiplier for unknown models

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.

* feat: experimental Subscription Value Calculator (ROI) vs OpenRouter

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).

* fix(config): v0.4.3 backward compat and install robustness

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

* feat(v0.4.3): token estimation fallback, ROI dashboard, per-model usage breakdown

Token tracking:
- analytics.py: fall back to skimtoken estimation when API omits usage
  (~15% error, better than zero). Proper total_tokens = prompt + completion.
  Removed broken usage_multiplier column.
- router.py: unchanged — already passes usage → analytics correctly

Dashboard / ROI:
- roi.py: OpenRouter price fetcher with cache-hit discount logic
- dashboard.py: expose ROI config with cache_hit_pct slider
- dashboard.html: cost breakdown UI, per-model value scoring

Models:
- client.py: added minimax-m3, nemotron-3-ultra, kimi-k2.6 200k context
- client.py: per-model Ollama usage breakdown scraping from /settings

Config:
- config.py: backward compat for installs missing SearchConfig

* feat(usage-levels): scrape real per-tag usage tiers from ollama.com library pages

- 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.

---------

Co-authored-by: evangit2 <evangit2@users.norereply.github.com>
Co-authored-by: Evan Rice <evanrice@vt.edu>
2026-06-09 21:06:39 -04:00
evangit2
c6741f92f3 v0.4.2: Multi-account Ollama rotation with quota-aware load balancing
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
2026-04-21 02:05:56 +00:00
evangit2
44f132b4a7 v0.4.1: fix update banner after applying, fix history toggle reverting, fix version reporting
- 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
2026-04-20 22:07:53 +00:00
evangit2
ff99d56e9b release: v0.4.0 2026-04-20 21:32:54 +00:00
evangit2
3bc114204d Bump version to 0.4.0-rc.1 2026-04-20 21:01:43 +00:00
evangit2
88a97a1770 v0.3.9: fallback stream fix, summarize toggle fix, version single-source
## 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
2026-04-10 20:49:00 +00:00
evangit2
2f133097dd v0.3.8: Endpoints hotfix, search settings with summarize toggle, remove playground 2026-04-10 19:53:22 +00:00
evangit2
331117c4dd v0.3.8-dev: Fix mutable endpoints bug, replace search playground with search settings + summarize toggle 2026-04-10 19:37:53 +00:00
evangit2
1b6eb603c2 v0.3.7 - Search tab, summarize feature, enhanced endpoints
- 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
2026-04-10 18:55:50 +00:00
evangit2
982a4f8270 v0.3.6 - Fix auto-update system
- 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.
2026-04-10 17:27:46 +00:00
evangit2
5497ee9868 Bump version to 0.3.5 for develop prerelease 2026-04-10 16:14:21 +00:00
evangit2
a461b3b915 fix: dashboard update uses correct venv path for install.sh users
The dashboard update handler assumed venv was at project_dir/venv (repo-local),
but install.sh creates venv at ~/.guanaco/venv while cloning the repo to
~/.guanaco/repo. This caused 'No such file or directory' errors when applying
updates from the dashboard on systems installed via install.sh.

Now checks ~/.guanaco/venv first (install.sh default), then falls back to
repo-local venv. Bumps to v0.3.3.
2026-04-10 03:22:48 +00:00
evangit2
299aa04947 bump version to 0.3.2 2026-04-10 02:41:00 +00:00
evangit2
286adcd99c fix: strip :cloud/:local routing suffixes in _resolve_model
Hermes sends models like 'glm-5.1:cloud' but _resolve_model only
handled '-cloud' (hyphen). The ':cloud' suffix passed through to
Ollama which didn't recognize it, returning empty responses and
causing 2+ minute timeouts in Hermes.

Now strips :cloud, :local, and -cloud suffixes before matching
against available_models. Bumps version to 0.3.1.
2026-04-10 00:25:54 +00:00
evangit2
da567c07ad 🦙 Polish install.sh: auto-install prereqs, interactive config, security warnings, branding fixes 2026-04-09 20:59:31 +00:00
evangit2
bbb2cc4903 🦙 Guanaco v0.3.0 — initial OSS release
OpenAI-compatible LLM proxy that maximizes your Ollama Cloud subscription.

Features:
- /v1/chat/completions router with token tracking
- /v1/messages Anthropic proxy
- 8 search/scrape API emulators (Tavily, Exa, SearXNG, Firecrawl, Serper, Jina, Cohere, Brave)
- Automatic fallback to secondary providers with configurable timeouts
- Streaming support with first-chunk fast failover
- Web dashboard with analytics, config, and usage monitoring
- Caching layer (beta)
- CLI for setup, status, analytics, key management
- Docker and systemd deployment support
- Backward compatible with OCT (ollama-cloud-tools) installations
2026-04-09 20:49:59 +00:00