Fix my tool model preset switching
This commit is contained in:
+11
-3
@@ -57,6 +57,9 @@ my(action="check", key="_last_usage.prompt_tokens")
|
||||
my(action="check", key="model")
|
||||
# → What model I'm currently running on
|
||||
|
||||
my(action="check", key="model_preset")
|
||||
# → Which named model preset is active, or None/default
|
||||
|
||||
my(action="check", key="web_config.enable")
|
||||
# → Whether web search is enabled
|
||||
```
|
||||
@@ -66,6 +69,7 @@ my(action="check", key="web_config.enable")
|
||||
| Scenario | How |
|
||||
|----------|-----|
|
||||
| "What model are you using?" | `check("model")` |
|
||||
| "Which model preset is active?" | `check("model_preset")` |
|
||||
| "How many more tool calls can you make?" | `check("max_iterations")` minus `check("_current_iteration")` |
|
||||
| "How many tokens has this conversation used?" | `check("_last_usage")` — cumulative across all turns |
|
||||
| "Where is your working directory?" | `check("workspace")` |
|
||||
@@ -82,8 +86,11 @@ Changes take effect immediately, no restart required.
|
||||
my(action="set", key="max_iterations", value=80)
|
||||
# → Bump iteration limit from 40 to 80
|
||||
|
||||
my(action="set", key="model_preset", value="fast")
|
||||
# → Switch to a configured model preset
|
||||
|
||||
my(action="set", key="model", value="fast-model")
|
||||
# → Switch to a faster model
|
||||
# → Switch to a raw model and clear the active preset
|
||||
|
||||
my(action="set", key="context_window_tokens", value=131072)
|
||||
# → Expand context window for long documents
|
||||
@@ -107,6 +114,7 @@ These parameters have type and range validation — invalid values are rejected:
|
||||
| `max_iterations` | int | 1–100 | Max tool calls per conversation turn |
|
||||
| `context_window_tokens` | int | 4,096–1,000,000 | Context window size |
|
||||
| `model` | str | non-empty | LLM model to use |
|
||||
| `model_preset` | str | configured preset name | Named preset to use |
|
||||
|
||||
Other parameters (e.g. `workspace`, `provider_retry_mode`, `max_tool_result_chars`) can be set freely, as long as the value is JSON-safe.
|
||||
|
||||
@@ -124,8 +132,8 @@ Agent: This codebase is large, let me expand my context window to handle it.
|
||||
### "Simple question, don't waste compute"
|
||||
|
||||
```text
|
||||
Agent: This is a straightforward question, let me switch to a faster model.
|
||||
→ my(action="set", key="model", value="fast-model")
|
||||
Agent: This is a straightforward question, let me switch to the fast preset.
|
||||
→ my(action="set", key="model_preset", value="fast")
|
||||
```
|
||||
|
||||
### "Remember user preferences across turns"
|
||||
|
||||
@@ -148,6 +148,7 @@ class MyTool(Tool, ContextAware):
|
||||
"\n"
|
||||
"When to use:\n"
|
||||
"- User asks about your model, settings, or token usage → check that key.\n"
|
||||
"- User asks to switch to a named model preset → set model_preset to that preset name.\n"
|
||||
"- A tool fails or behaves unexpectedly → check the related config to diagnose.\n"
|
||||
"- User asks you to remember a preference for this session → set to store it in your scratchpad.\n"
|
||||
"- About to start a large task → check context_window_tokens and max_iterations first."
|
||||
@@ -175,9 +176,9 @@ class MyTool(Tool, ContextAware):
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Dot-path for check/set. Examples: 'max_iterations', 'workspace', 'provider_retry_mode'. "
|
||||
"For check without key, shows all config values.",
|
||||
"Use 'model_preset' to switch named model presets. For check without key, shows all config values.",
|
||||
},
|
||||
"value": {"description": "New value (for set). Type must match target (int for max_iterations/context_window_tokens, str for model)."},
|
||||
"value": {"description": "New value (for set). Type must match target (int for max_iterations/context_window_tokens, str for model/model_preset)."},
|
||||
},
|
||||
"required": ["action"],
|
||||
}
|
||||
@@ -399,10 +400,28 @@ class MyTool(Tool, ContextAware):
|
||||
setattr(parent, leaf, value)
|
||||
self._audit("modify", f"{key} = {value!r}")
|
||||
return f"Set {key} = {value!r}"
|
||||
if key == "model_preset":
|
||||
return self._modify_model_preset(value)
|
||||
if key in self.RESTRICTED:
|
||||
return self._modify_restricted(key, value)
|
||||
return self._modify_free(key, value)
|
||||
|
||||
def _modify_model_preset(self, value: Any) -> str:
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
return "Error: 'model_preset' must be a non-empty string"
|
||||
name = value.strip()
|
||||
result = self._modify_free("model_preset", name)
|
||||
if result.startswith("Error:"):
|
||||
return result if result.endswith((".", "!", "?")) else f"{result}."
|
||||
model = getattr(self._runtime_state, "model", None)
|
||||
context_window = getattr(self._runtime_state, "context_window_tokens", None)
|
||||
details = [result]
|
||||
if model is not None:
|
||||
details.append(f"model is now {model!r}")
|
||||
if context_window is not None:
|
||||
details.append(f"context_window_tokens is now {context_window!r}")
|
||||
return "; ".join(details)
|
||||
|
||||
def _modify_restricted(self, key: str, value: Any) -> str:
|
||||
spec = self.RESTRICTED[key]
|
||||
expected = spec["type"]
|
||||
@@ -444,8 +463,9 @@ class MyTool(Tool, ContextAware):
|
||||
try:
|
||||
setattr(self._runtime_state, key, value)
|
||||
except (ValueError, KeyError) as e:
|
||||
self._audit("modify", f"REJECTED {key}: {e}")
|
||||
return f"Error: {e}"
|
||||
message = self._exception_message(e)
|
||||
self._audit("modify", f"REJECTED {key}: {message}")
|
||||
return f"Error: {message}"
|
||||
self._audit("modify", f"{key}: {old!r} -> {value!r}")
|
||||
return f"Set {key} = {value!r} (was {old!r})"
|
||||
if callable(value):
|
||||
@@ -463,6 +483,10 @@ class MyTool(Tool, ContextAware):
|
||||
self._audit("modify", f"scratchpad.{key}: {old!r} -> {value!r}")
|
||||
return f"Set scratchpad.{key} = {value!r}"
|
||||
|
||||
@staticmethod
|
||||
def _exception_message(exc: Exception) -> str:
|
||||
return str(exc.args[0] if isinstance(exc, KeyError) and exc.args else exc).strip('"')
|
||||
|
||||
@classmethod
|
||||
def _validate_json_safe(cls, value: Any, depth: int = 0) -> str | None:
|
||||
if depth > 10:
|
||||
|
||||
@@ -36,7 +36,8 @@ always: true
|
||||
| Situation | Command |
|
||||
|-----------|---------|
|
||||
| Large codebase analysis | `my(action="set", key="context_window_tokens", value=131072)` |
|
||||
| Repetitive simple tasks | `my(action="set", key="model", value="<fast-model>")` |
|
||||
| Switch to a named model preset | `my(action="set", key="model_preset", value="<preset-name>")` |
|
||||
| Repetitive simple tasks without a preset | `my(action="set", key="model", value="<fast-model>")` |
|
||||
| Long multi-step task | `my(action="set", key="max_iterations", value=80)` |
|
||||
|
||||
**Tradeoff:** Bias toward stability. Only set when defaults are genuinely insufficient.
|
||||
@@ -58,6 +59,7 @@ always: true
|
||||
## Constraints
|
||||
|
||||
- All modifications in-memory only — restart resets everything
|
||||
- Prefer `model_preset` for configured model choices. Direct `model` changes clear the active preset and should only be used when no preset exists.
|
||||
- Protected params have type/range validation: `max_iterations` (1–100), `context_window_tokens` (4096–1M), `model` (non-empty str)
|
||||
- If `tools.my.allow_set` is false, check only
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ Concrete scenarios showing when and how to use the my tool effectively.
|
||||
```
|
||||
→ my(action="check", key="model")
|
||||
→ 'anthropic/claude-sonnet-4-20250514'
|
||||
→ my(action="check", key="model_preset")
|
||||
→ 'deep'
|
||||
```
|
||||
|
||||
## Adaptive Behavior
|
||||
@@ -37,7 +39,14 @@ Concrete scenarios showing when and how to use the my tool effectively.
|
||||
→ "I've expanded my context window to handle this large codebase."
|
||||
```
|
||||
|
||||
### Switching to a faster model for repetitive tasks
|
||||
### Switching to a configured model preset
|
||||
```
|
||||
→ my(action="set", key="model_preset", value="fast")
|
||||
→ "Set model_preset = 'fast' (was 'deep'); model is now 'openai/gpt-4.1-mini'"
|
||||
→ "Switched to the fast preset for these batch tasks."
|
||||
```
|
||||
|
||||
### Switching to a raw model when no preset exists
|
||||
```
|
||||
→ my(action="set", key="model", value="anthropic/claude-haiku-4-5-20251001")
|
||||
→ "Set model = 'anthropic/claude-haiku-4-5-20251001' (was 'anthropic/claude-sonnet-4-20250514')"
|
||||
|
||||
@@ -247,6 +247,38 @@ def test_self_tool_set_model_preset_via_modify(tmp_path) -> None:
|
||||
assert loop.model == "openai/gpt-4.1"
|
||||
|
||||
|
||||
def test_self_tool_set_model_preset_switches_back_to_default(tmp_path) -> None:
|
||||
presets = {
|
||||
"default": ModelPresetConfig(model="base-model", context_window_tokens=1000),
|
||||
"fast": ModelPresetConfig(model="openai/gpt-4.1", context_window_tokens=32_768),
|
||||
}
|
||||
loop = _make_loop(tmp_path, presets=presets, active_preset="fast")
|
||||
tool = MyTool(runtime_state=loop, modify_allowed=True)
|
||||
|
||||
result = tool._modify("model_preset", "default")
|
||||
|
||||
assert "Error" not in result
|
||||
assert "model is now 'base-model'" in result
|
||||
assert loop.model_preset == "default"
|
||||
assert loop.model == "base-model"
|
||||
assert loop.context_window_tokens == 1000
|
||||
|
||||
|
||||
def test_self_tool_set_model_preset_unknown_lists_available(tmp_path) -> None:
|
||||
presets = {
|
||||
"default": ModelPresetConfig(model="base-model"),
|
||||
"fast": ModelPresetConfig(model="openai/gpt-4.1"),
|
||||
}
|
||||
loop = _make_loop(tmp_path, presets=presets)
|
||||
tool = MyTool(runtime_state=loop, modify_allowed=True)
|
||||
|
||||
result = tool._modify("model_preset", "missing")
|
||||
|
||||
assert result == "Error: model_preset 'missing' not found. Available: default, fast."
|
||||
assert loop.model_preset is None
|
||||
assert loop.model == "base-model"
|
||||
|
||||
|
||||
def test_self_tool_set_model_clears_active_preset(tmp_path) -> None:
|
||||
presets = {
|
||||
"fast": ModelPresetConfig(model="openai/gpt-4.1"),
|
||||
|
||||
Reference in New Issue
Block a user