File size: 3,380 Bytes
57d4e72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from copy import deepcopy

from helpers.api import ApiHandler, Request, Response
from helpers import defer, dotenv
from helpers.extension import call_extensions_async
from plugins._model_config.helpers import model_config

API_KEY_PLACEHOLDER = "************"


class ModelConfigSet(ApiHandler):
    async def process(self, input: dict, request: Request) -> dict | Response:
        project_name = input.get("project_name", "")
        agent_profile = input.get("agent_profile", "")
        config = input.get("config")

        if not config or not isinstance(config, dict):
            return Response(status=400, response="Missing or invalid config")

        config_to_save = deepcopy(config)
        for section_name in ("chat_model", "utility_model", "embedding_model"):
            section = config_to_save.get(section_name, {})
            if not isinstance(section, dict):
                continue
            provider = str(section.get("provider", "")).strip()
            api_key = section.get("api_key", "")
            if (
                provider
                and isinstance(api_key, str)
                and api_key.strip()
                and api_key != API_KEY_PLACEHOLDER
            ):
                dotenv.save_dotenv_value(f"API_KEY_{provider.upper()}", api_key)
            section.pop("api_key", None)

        preset_name = str(
            input.get("preset_name")
            or config_to_save.get(model_config.MODEL_PRESET_CONFIG_KEY)
            or model_config.get_configured_preset_name(
                project_name=project_name or None,
                agent_profile=agent_profile or None,
            )
        ).strip()
        preset = model_config.resolve_preset(preset_name)
        if not preset:
            return Response(status=404, response=f"Preset '{preset_name}' not found")
        preset_name = str(preset.get("name") or model_config.DEFAULT_PRESET_NAME)

        # Read the preset before saving so embedding changes can still trigger
        # the established re-index notification.
        prev_config = model_config.resolve_config_settings(
            {model_config.MODEL_PRESET_CONFIG_KEY: preset_name}
        )

        try:
            model_config.update_preset_from_config(preset_name, config_to_save)
        except ValueError as exc:
            return Response(status=400, response=str(exc))

        # Keep the requested scope pointed at the preset being edited. This is
        # selection-only persistence; model dictionaries live in presets.yaml.
        from helpers import plugins

        plugins.save_plugin_config(
            "_model_config",
            project_name=project_name or None,
            agent_profile=agent_profile or None,
            settings={model_config.MODEL_PRESET_CONFIG_KEY: preset_name},
        )

        # Check if embedding model changed and notify
        prev_embed = prev_config.get("embedding_model", {})
        new_embed = config_to_save.get("embedding_model", {})
        if (
            prev_embed.get("provider") != new_embed.get("provider")
            or prev_embed.get("name") != new_embed.get("name")
            or prev_embed.get("kwargs") != new_embed.get("kwargs")
        ):
            defer.DeferredTask().start_task(
                call_extensions_async, "embedding_model_changed"
            )

        return {"ok": True, "preset_name": preset_name}