Add model hygiene workflow and enforce OpenRouter-only profiles
This commit is contained in:
parent
405433bb7f
commit
3bab4b3b4f
@ -153,8 +153,8 @@ Outputs:
|
||||
|
||||
## 10) Acceptance Criteria
|
||||
|
||||
- AC-1: Running `bash ./scripts/model_profile_switch.sh free` changes default to `ollama/qwen3:14b`.
|
||||
- AC-2: Running `bash ./scripts/model_profile_switch.sh paid` restores default to `openrouter/moonshotai/kimi-k2.5`.
|
||||
- AC-1: Running `bash ./scripts/model_profile_switch.sh free` changes default to `openrouter/qwen/qwen3-coder:free`.
|
||||
- AC-2: Running `bash ./scripts/model_profile_switch.sh paid` restores default to `openrouter/qwen/qwen3-coder:free`.
|
||||
- AC-3: With schedule enabled (`21`/`7`), profile changes correctly by local time window.
|
||||
- AC-4: If operator manually drifts model during scheduled window, schedule guard re-aligns on next run.
|
||||
- AC-5: Budget guard warns and reverts when high model remains active beyond thresholds.
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# OpenClaw Setup Max (Paid + Free Model Switching)
|
||||
|
||||
This workspace runs OpenClaw with:
|
||||
- A paid high-quality model profile (`openrouter/moonshotai/kimi-k2.5`)
|
||||
- A free local profile (Ollama-only)
|
||||
- A paid high-quality model profile (`openrouter/qwen/qwen3-coder:free`)
|
||||
- A free OpenRouter profile
|
||||
- One-command live switching
|
||||
- Optional automatic day/night switching (example: free from 9pm-7am)
|
||||
- Optional budget guard that warns and auto-reverts from expensive models
|
||||
@ -27,11 +27,11 @@ Everything else is grouped by function:
|
||||
## 1) What You Get
|
||||
|
||||
- `paid` profile:
|
||||
- Primary: `openrouter/moonshotai/kimi-k2.5`
|
||||
- Fallbacks: `ollama/qwen3:14b`, `ollama/llama3.2:3b`
|
||||
- Primary: `openrouter/qwen/qwen3-coder:free`
|
||||
- Fallbacks: `openrouter/qwen/qwen3-coder-next`, `openrouter/qwen/qwen3-14b`
|
||||
- `free` profile:
|
||||
- Primary: `ollama/qwen3:14b`
|
||||
- Fallbacks: `ollama/devstral:24b`, `ollama/llama3.2:3b`
|
||||
- Primary: `openrouter/qwen/qwen3-coder:free`
|
||||
- Fallbacks: `openrouter/qwen/qwen3-14b`
|
||||
|
||||
Profiles are defined in `config/model-profiles.config.json`.
|
||||
|
||||
@ -41,6 +41,7 @@ Profiles are defined in `config/model-profiles.config.json`.
|
||||
- `config/model-schedule.config.json`: schedule settings (`dayProfile`, `nightProfile`, hours)
|
||||
- `config/model-budget-guard.config.json`: high-cost warning and auto-revert settings
|
||||
- `scripts/model_profile_switch.sh`: manual live switch command
|
||||
- `scripts/model_hygiene_workflow.sh`: apply profile + prune legacy model/provider/auth config
|
||||
- `scripts/model_schedule_guard.sh`: scheduled switch worker
|
||||
- `scripts/model_budget_guard.sh`: budget guard worker
|
||||
- `scripts/install_model_schedule_guard_launchd.sh`: installs schedule LaunchAgent
|
||||
@ -83,10 +84,16 @@ PULL_LOCAL_MODELS=false bash ./setup/setup_openclaw_ollama.sh
|
||||
|
||||
## 4) Quick Start (Most Common)
|
||||
|
||||
Switch to free mode now:
|
||||
Run the hygiene workflow (recommended after any model changes):
|
||||
|
||||
```bash
|
||||
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
|
||||
bash ./scripts/model_hygiene_workflow.sh paid
|
||||
```
|
||||
|
||||
Switch to free mode now:
|
||||
|
||||
```bash
|
||||
bash ./scripts/model_profile_switch.sh free
|
||||
```
|
||||
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"sessionKey": "agent:main:main",
|
||||
"lowModel": "ollama/qwen3:14b",
|
||||
"lowModel": "openrouter/qwen/qwen3-coder:free",
|
||||
"highModels": [
|
||||
"openrouter/moonshotai/kimi-k2.5",
|
||||
"xai/grok-4-fast",
|
||||
"xai/grok-code-fast-1"
|
||||
],
|
||||
|
||||
@ -2,18 +2,17 @@
|
||||
"profiles": {
|
||||
"paid": {
|
||||
"description": "High-quality paid model for deep work",
|
||||
"primary": "openrouter/moonshotai/kimi-k2.5",
|
||||
"primary": "openrouter/qwen/qwen3-coder:free",
|
||||
"fallbacks": [
|
||||
"ollama/qwen3:14b",
|
||||
"ollama/llama3.2:3b"
|
||||
"openrouter/qwen/qwen3-coder-next",
|
||||
"openrouter/qwen/qwen3-14b"
|
||||
]
|
||||
},
|
||||
"free": {
|
||||
"description": "All-local no-cost model stack",
|
||||
"primary": "ollama/qwen3:14b",
|
||||
"description": "OpenRouter free-first stack",
|
||||
"primary": "openrouter/qwen/qwen3-coder:free",
|
||||
"fallbacks": [
|
||||
"ollama/devstral:24b",
|
||||
"ollama/llama3.2:3b"
|
||||
"openrouter/qwen/qwen3-14b"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,20 @@
|
||||
|
||||
This setup adds two model profiles:
|
||||
|
||||
- `paid` -> `openrouter/moonshotai/kimi-k2.5` with local Ollama fallbacks
|
||||
- `free` -> local Ollama-only stack
|
||||
- `paid` -> `openrouter/qwen/qwen3-coder:free` with OpenRouter fallbacks
|
||||
- `free` -> `openrouter/qwen/qwen3-coder:free` with OpenRouter fallback
|
||||
|
||||
## Recommended workflow (prevents legacy drift)
|
||||
|
||||
```bash
|
||||
cd /Volumes/Data/openclaw-setups/openclaw-setup-max
|
||||
bash ./scripts/model_hygiene_workflow.sh paid
|
||||
```
|
||||
|
||||
This command:
|
||||
- Applies the selected profile
|
||||
- Restages/restarts guard LaunchAgents
|
||||
- Prunes legacy models/providers/auth entries not referenced by current config
|
||||
|
||||
## On-demand switch (immediate)
|
||||
|
||||
|
||||
@ -38,8 +38,6 @@ cat > "$PLIST_PATH" <<PLIST
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<key>OLLAMA_API_KEY</key>
|
||||
<string>ollama-local</string>
|
||||
<key>MODEL_GUARD_CONFIG</key>
|
||||
<string>$MODEL_GUARD_CONFIG</string>
|
||||
</dict>
|
||||
|
||||
@ -40,8 +40,6 @@ cat > "$PLIST_PATH" <<PLIST
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
||||
<key>OLLAMA_API_KEY</key>
|
||||
<string>ollama-local</string>
|
||||
<key>MODEL_SCHEDULE_CONFIG</key>
|
||||
<string>$MODEL_SCHEDULE_CONFIG</string>
|
||||
<key>MODEL_PROFILES_CONFIG</key>
|
||||
|
||||
189
openclaw-setup-max/scripts/model_hygiene_workflow.sh
Executable file
189
openclaw-setup-max/scripts/model_hygiene_workflow.sh
Executable file
@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
PROFILES_CONFIG="${MODEL_PROFILES_CONFIG:-$ROOT_DIR/config/model-profiles.config.json}"
|
||||
BUDGET_CONFIG="${MODEL_GUARD_CONFIG:-$ROOT_DIR/config/model-budget-guard.config.json}"
|
||||
AUTH_STORE="${OPENCLAW_AUTH_STORE:-$HOME/.openclaw/agents/main/agent/auth-profiles.json}"
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo "[model-hygiene] jq is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v openclaw >/dev/null 2>&1; then
|
||||
echo "[model-hygiene] openclaw CLI is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage:
|
||||
bash ./scripts/model_hygiene_workflow.sh <profile> [--no-live] [--no-guards]
|
||||
|
||||
Behavior:
|
||||
1) Applies profile via model_profile_switch.sh
|
||||
2) Reinstalls guard LaunchAgents (unless --no-guards)
|
||||
3) Prunes legacy models/providers/auth not referenced by config
|
||||
4) Prints models status
|
||||
|
||||
Examples:
|
||||
bash ./scripts/model_hygiene_workflow.sh paid
|
||||
bash ./scripts/model_hygiene_workflow.sh free --no-live
|
||||
USAGE
|
||||
}
|
||||
|
||||
in_array() {
|
||||
local needle="$1"
|
||||
shift
|
||||
local item
|
||||
for item in "$@"; do
|
||||
if [[ "$item" == "$needle" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
profile="${1:-}"
|
||||
if [[ -z "$profile" ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$profile" == "-h" || "$profile" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
shift || true
|
||||
|
||||
apply_live="true"
|
||||
install_guards="true"
|
||||
while (( "$#" )); do
|
||||
case "$1" in
|
||||
--no-live) apply_live="false" ;;
|
||||
--no-guards) install_guards="false" ;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "[model-hygiene] unknown argument: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ ! -f "$PROFILES_CONFIG" ]]; then
|
||||
echo "[model-hygiene] missing config: $PROFILES_CONFIG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
profile_exists="$(jq -r --arg p "$profile" '.profiles[$p] != null' "$PROFILES_CONFIG")"
|
||||
if [[ "$profile_exists" != "true" ]]; then
|
||||
echo "[model-hygiene] unknown profile '$profile'" >&2
|
||||
jq -r '.profiles | keys[]' "$PROFILES_CONFIG" | sed 's/^/ - /'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[model-hygiene] collecting managed models/providers from config"
|
||||
keep_models=()
|
||||
while IFS= read -r model; do
|
||||
[[ -z "$model" ]] && continue
|
||||
if ! in_array "$model" "${keep_models[@]-}"; then
|
||||
keep_models+=("$model")
|
||||
fi
|
||||
done < <(jq -r '.profiles | to_entries[] | .value.primary, (.value.fallbacks[]? // empty)' "$PROFILES_CONFIG")
|
||||
|
||||
if [[ -f "$BUDGET_CONFIG" ]]; then
|
||||
while IFS= read -r model; do
|
||||
[[ -z "$model" ]] && continue
|
||||
if ! in_array "$model" "${keep_models[@]-}"; then
|
||||
keep_models+=("$model")
|
||||
fi
|
||||
done < <(jq -r '.lowModel // empty, (.highModels[]? // empty)' "$BUDGET_CONFIG")
|
||||
fi
|
||||
|
||||
if [[ ${#keep_models[@]} -eq 0 ]]; then
|
||||
echo "[model-hygiene] no models discovered in config; aborting" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
keep_providers=()
|
||||
for model in "${keep_models[@]}"; do
|
||||
provider="${model%%/*}"
|
||||
[[ -z "$provider" || "$provider" == "$model" ]] && continue
|
||||
if ! in_array "$provider" "${keep_providers[@]-}"; then
|
||||
keep_providers+=("$provider")
|
||||
fi
|
||||
done
|
||||
|
||||
echo "[model-hygiene] applying profile '$profile'"
|
||||
switch_args=("$profile" "--no-status")
|
||||
if [[ "$apply_live" != "true" ]]; then
|
||||
switch_args+=("--no-live")
|
||||
fi
|
||||
bash "$SCRIPT_DIR/model_profile_switch.sh" "${switch_args[@]}"
|
||||
|
||||
if [[ "$install_guards" == "true" ]]; then
|
||||
echo "[model-hygiene] reinstalling guard LaunchAgents"
|
||||
bash "$SCRIPT_DIR/install_local_model_guardrails.sh" >/dev/null
|
||||
fi
|
||||
|
||||
echo "[model-hygiene] pruning legacy configured models"
|
||||
current_models_json="$(openclaw config get agents.defaults.models 2>/dev/null || echo '{}')"
|
||||
while IFS= read -r model; do
|
||||
[[ -z "$model" ]] && continue
|
||||
if ! in_array "$model" "${keep_models[@]-}"; then
|
||||
openclaw config unset "agents.defaults.models[$model]" >/dev/null || true
|
||||
fi
|
||||
done < <(jq -r 'keys[]? // empty' <<<"$current_models_json")
|
||||
|
||||
for model in "${keep_models[@]}"; do
|
||||
openclaw config set --json "agents.defaults.models[$model]" '{}' >/dev/null || true
|
||||
done
|
||||
|
||||
echo "[model-hygiene] pruning legacy aliases"
|
||||
while IFS=$'\t' read -r alias target; do
|
||||
[[ -z "$alias" || -z "$target" ]] && continue
|
||||
if ! in_array "$target" "${keep_models[@]-}"; then
|
||||
openclaw models aliases remove "$alias" >/dev/null || true
|
||||
fi
|
||||
done < <(openclaw models status --json 2>/dev/null | jq -r '.aliases // {} | to_entries[] | "\(.key)\t\(.value)"')
|
||||
|
||||
echo "[model-hygiene] pruning legacy provider blocks"
|
||||
providers_json="$(openclaw config get models.providers 2>/dev/null || echo '{}')"
|
||||
while IFS= read -r provider; do
|
||||
[[ -z "$provider" ]] && continue
|
||||
if ! in_array "$provider" "${keep_providers[@]-}"; then
|
||||
openclaw config unset "models.providers.$provider" >/dev/null || true
|
||||
fi
|
||||
done < <(jq -r 'keys[]? // empty' <<<"$providers_json")
|
||||
|
||||
echo "[model-hygiene] pruning legacy auth profiles in openclaw config"
|
||||
auth_profiles_json="$(openclaw config get auth.profiles 2>/dev/null || echo '{}')"
|
||||
while IFS=$'\t' read -r profile_id provider; do
|
||||
[[ -z "$profile_id" || -z "$provider" ]] && continue
|
||||
if ! in_array "$provider" "${keep_providers[@]-}"; then
|
||||
openclaw config unset "auth.profiles[$profile_id]" >/dev/null || true
|
||||
fi
|
||||
done < <(jq -r 'to_entries[]? | "\(.key)\t\(.value.provider // empty)"' <<<"$auth_profiles_json")
|
||||
|
||||
if [[ -f "$AUTH_STORE" ]]; then
|
||||
echo "[model-hygiene] pruning legacy auth profiles in auth store"
|
||||
keep_providers_json="$(printf '%s\n' "${keep_providers[@]-}" | jq -R . | jq -s .)"
|
||||
tmp_auth="$(mktemp)"
|
||||
jq --argjson keep "$keep_providers_json" '
|
||||
.profiles |= with_entries(select((.value.provider // "") as $p | ($keep | index($p)) != null))
|
||||
| .lastGood |= with_entries(select(.key as $p | ($keep | index($p)) != null))
|
||||
| .usageStats |= with_entries(select((.key | split(":")[0]) as $p | ($keep | index($p)) != null))
|
||||
' "$AUTH_STORE" > "$tmp_auth"
|
||||
mv "$tmp_auth" "$AUTH_STORE"
|
||||
fi
|
||||
|
||||
echo "[model-hygiene] complete"
|
||||
echo "[model-hygiene] providers kept: ${keep_providers[*]}"
|
||||
echo "[model-hygiene] models kept: ${keep_models[*]}"
|
||||
echo ""
|
||||
openclaw models status
|
||||
Loading…
Reference in New Issue
Block a user