Migrate saved Venice model config

Repair saved _model_config user config and presets so Venice model slots use chat completions with the Venice system prompt disabled. Leave a0_venice slots untouched and add regression coverage for parsing and slot preservation.

Alessandro committed Jul 9, 2026 at 17:19 UTC 1bd741ff7f3fef0cc5e0a0bd91b470588d9d44da
2 files changed +159 -2
plugins/_model_config/extensions/python/startup_migration/_10_migrate_model_config.py
+76 -2
@@ -1,7 +1,9 @@
1 import json
2 import os
3 +from copy import deepcopy
4 +
5 from helpers.extension import Extension
4 -from helpers import settings as settings_helper, files, plugins
6 +from helpers import files, plugins, yaml as yaml_helper
7 from helpers.print_style import PrintStyle
8
9
@@ -27,10 +29,20 @@ class MigrateModelConfig(Extension):
29 "browser_model_vision", "browser_model_rl_requests", "browser_model_rl_input",
30 "browser_model_rl_output", "browser_model_kwargs", "browser_http_headers",
31 ]
32 + CONFIG_SECTIONS = ("chat_model", "utility_model", "embedding_model")
33 + PRESET_SECTIONS = ("chat", "utility", "embedding")
34 + VENICE_KWARGS = {
35 + "a0_api_mode": "chat",
36 + "venice_parameters": {"include_venice_system_prompt": False},
37 + }
38
39 def execute(self, **kwargs):
40 + self._repair_saved_venice_config()
41 +
42 # Check if global plugin config already exists
33 - global_config_path = files.get_abs_path("plugins/_model_config/config.json")
43 + global_config_path = files.get_abs_path(
44 + files.USER_DIR, files.PLUGINS_DIR, "_model_config", plugins.CONFIG_FILE_NAME
45 + )
46 if os.path.exists(global_config_path):
47 return # already migrated or manually configured
48
@@ -91,8 +103,70 @@ class MigrateModelConfig(Extension):
103 if isinstance(kw, str):
104 plugin_config[section]["kwargs"] = {}
105
106 + self._repair_venice_config_slots(plugin_config)
107 +
108 # Save as global plugin config
109 plugins.save_plugin_config("_model_config", "", "", plugin_config)
110 PrintStyle(background_color="#6734C3", font_color="white", padding=True).print(
111 "Migrated legacy model settings to _model_config plugin config."
112 )
113 +
114 + def _repair_saved_venice_config(self):
115 + changed = False
116 + config_path = files.get_abs_path(
117 + files.USER_DIR, files.PLUGINS_DIR, "_model_config", plugins.CONFIG_FILE_NAME
118 + )
119 + presets_path = files.get_abs_path(
120 + files.USER_DIR, files.PLUGINS_DIR, "_model_config", "presets.yaml"
121 + )
122 +
123 + if os.path.exists(config_path):
124 + try:
125 + config = json.loads(files.read_file(config_path))
126 + except Exception:
127 + config = None
128 + if isinstance(config, dict) and self._repair_venice_config_slots(config):
129 + files.write_file(config_path, json.dumps(config))
130 + changed = True
131 +
132 + if os.path.exists(presets_path):
133 + try:
134 + presets = yaml_helper.loads(files.read_file(presets_path))
135 + except Exception:
136 + presets = None
137 + if isinstance(presets, list) and self._repair_venice_presets(presets):
138 + files.write_file(presets_path, yaml_helper.dumps(presets))
139 + changed = True
140 +
141 + if changed:
142 + PrintStyle(background_color="#6734C3", font_color="white", padding=True).print(
143 + "Updated saved Venice model settings for chat completions."
144 + )
145 +
146 + def _repair_venice_config_slots(self, config: dict) -> bool:
147 + changed = False
148 + for section in self.CONFIG_SECTIONS:
149 + changed = self._repair_venice_slot(config.get(section)) or changed
150 + return changed
151 +
152 + def _repair_venice_presets(self, presets: list) -> bool:
153 + changed = False
154 + for preset in presets:
155 + if not isinstance(preset, dict):
156 + continue
157 + for section in self.PRESET_SECTIONS:
158 + changed = self._repair_venice_slot(preset.get(section)) or changed
159 + if not any(section in preset for section in self.PRESET_SECTIONS):
160 + changed = self._repair_venice_slot(preset) or changed
161 + return changed
162 +
163 + def _repair_venice_slot(self, slot) -> bool:
164 + if not isinstance(slot, dict):
165 + return False
166 + provider = str(slot.get("provider") or "").strip().lower()
167 + if provider != "venice":
168 + return False
169 + if slot.get("kwargs") == self.VENICE_KWARGS:
170 + return False
171 + slot["kwargs"] = deepcopy(self.VENICE_KWARGS)
172 + return True
tests/test_model_config_api_keys.py
+83
@@ -1,3 +1,4 @@
1 +import json
2 import sys
3 import threading
4 import types
@@ -299,6 +300,88 @@ def test_direct_venice_chat_provider_defaults_to_chat_completions(monkeypatch):
300 assert custom.kwargs["a0_api_mode"] == "responses"
301
302
303 +def test_model_config_migration_repairs_saved_venice_user_slots(monkeypatch, tmp_path):
304 + import yaml
305 +
306 + from helpers import files
307 + from plugins._model_config.extensions.python.startup_migration._10_migrate_model_config import (
308 + MigrateModelConfig,
309 + )
310 +
311 + monkeypatch.setattr(files, "_base_dir", str(tmp_path))
312 + plugin_dir = tmp_path / "usr" / "plugins" / "_model_config"
313 + plugin_dir.mkdir(parents=True)
314 + expected = {
315 + "a0_api_mode": "chat",
316 + "venice_parameters": {"include_venice_system_prompt": False},
317 + }
318 +
319 + config_path = plugin_dir / "config.json"
320 + config_path.write_text(
321 + json.dumps(
322 + {
323 + "chat_model": {
324 + "provider": "venice",
325 + "name": "llama-3.3-70b",
326 + "kwargs": {"a0_api_mode": "responses"},
327 + },
328 + "utility_model": {
329 + "provider": "a0_venice",
330 + "name": "venice-proxy",
331 + "kwargs": {"a0_api_mode": "responses"},
332 + },
333 + "embedding_model": {
334 + "provider": "venice",
335 + "name": "embed",
336 + "kwargs": {},
337 + },
338 + }
339 + ),
340 + encoding="utf-8",
341 + )
342 +
343 + presets_path = plugin_dir / "presets.yaml"
344 + presets_path.write_text(
345 + yaml.safe_dump(
346 + [
347 + {
348 + "name": "Venice",
349 + "chat": {
350 + "provider": "venice",
351 + "name": "llama-3.3-70b",
352 + "kwargs": {"venice_parameters": {"include_venice_system_prompt": True}},
353 + },
354 + "utility": {
355 + "provider": "a0_venice",
356 + "name": "proxy",
357 + "kwargs": {"keep": True},
358 + },
359 + },
360 + {
361 + "name": "Legacy raw preset",
362 + "provider": "venice",
363 + "name": "raw",
364 + "kwargs": {"a0_api_mode": "responses"},
365 + },
366 + ],
367 + sort_keys=False,
368 + ),
369 + encoding="utf-8",
370 + )
371 +
372 + MigrateModelConfig(agent=None).execute()
373 +
374 + config = json.loads(config_path.read_text(encoding="utf-8"))
375 + presets = yaml.safe_load(presets_path.read_text(encoding="utf-8"))
376 +
377 + assert config["chat_model"]["kwargs"] == expected
378 + assert config["embedding_model"]["kwargs"] == expected
379 + assert config["utility_model"]["kwargs"] == {"a0_api_mode": "responses"}
380 + assert presets[0]["chat"]["kwargs"] == expected
381 + assert presets[0]["utility"]["kwargs"] == {"keep": True}
382 + assert presets[1]["kwargs"] == expected
383 +
384 +
385 def test_local_chat_providers_default_to_chat_completions():
386 import yaml
387