Skip to content

Commit 5b8224d

Browse files
committed
Add ability to set agent model in config and add in-chat command
1 parent 94b82eb commit 5b8224d

9 files changed

Lines changed: 210 additions & 2 deletions

File tree

cecli/args.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,12 @@ def get_parser(default_config_files, git_root):
297297
help="Specify Agent Mode configuration as a JSON string",
298298
default=None,
299299
)
300+
group.add_argument(
301+
"--agent-model",
302+
metavar="AGENT_MODEL",
303+
default=None,
304+
help="Specify the model to use for Agent mode (default depends on --model)",
305+
)
300306
group.add_argument(
301307
"--auto-save",
302308
action=argparse.BooleanOptionalAction,
@@ -1111,15 +1117,21 @@ def main():
11111117
shell = sys.argv[2]
11121118
if shell not in shtab.SUPPORTED_SHELLS:
11131119
print(f"Error: Unsupported shell '{shell}'.", file=sys.stderr)
1114-
print(f"Supported shells are: {', '.join(shtab.SUPPORTED_SHELLS)}", file=sys.stderr)
1120+
print(
1121+
f"Supported shells are: {', '.join(shtab.SUPPORTED_SHELLS)}",
1122+
file=sys.stderr,
1123+
)
11151124
sys.exit(1)
11161125
parser = get_parser([], None)
11171126
parser.prog = "cecli" # Set the program name on the parser
11181127
print(shtab.complete(parser, shell=shell))
11191128
else:
11201129
print("Error: Please specify a shell for completion.", file=sys.stderr)
11211130
print(f"Usage: python {sys.argv[0]} completion <shell_name>", file=sys.stderr)
1122-
print(f"Supported shells are: {', '.join(shtab.SUPPORTED_SHELLS)}", file=sys.stderr)
1131+
print(
1132+
f"Supported shells are: {', '.join(shtab.SUPPORTED_SHELLS)}",
1133+
file=sys.stderr,
1134+
)
11231135
sys.exit(1)
11241136
else:
11251137
# Default to YAML for any other unrecognized argument, or if 'yaml' was explicitly passed

cecli/coders/base_coder.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ def get_announcements(self):
660660
# Model
661661
main_model = self.main_model
662662
weak_model = main_model.weak_model
663+
agent_model = main_model.agent_model
663664

664665
if weak_model is not main_model:
665666
prefix = "Main model"
@@ -698,6 +699,10 @@ def get_announcements(self):
698699
output = f"Weak model: {weak_model.name}"
699700
lines.append(output)
700701

702+
if agent_model is not main_model:
703+
output = f"Agent model: {agent_model.name}"
704+
lines.append(output)
705+
701706
# Repo
702707
if self.repo:
703708
rel_repo_dir = self.repo.get_rel_repo_dir()

cecli/commands/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from .add import AddCommand
99
from .agent import AgentCommand
10+
from .agent_model import AgentModelCommand
1011
from .architect import ArchitectCommand
1112
from .ask import AskCommand
1213
from .clear import ClearCommand
@@ -77,6 +78,7 @@
7778
# Register commands
7879
CommandRegistry.register(AddCommand)
7980
CommandRegistry.register(AgentCommand)
81+
CommandRegistry.register(AgentModelCommand)
8082
CommandRegistry.register(ArchitectCommand)
8183
CommandRegistry.register(AskCommand)
8284
CommandRegistry.register(ClearCommand)
@@ -136,6 +138,7 @@
136138
__all__ = [
137139
"AddCommand",
138140
"AgentCommand",
141+
"AgentModelCommand",
139142
"ArchitectCommand",
140143
"AskCommand",
141144
"BaseCommand",

cecli/commands/agent_model.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
from typing import List
2+
3+
import cecli.models as models
4+
from cecli.commands.utils.base_command import BaseCommand
5+
from cecli.commands.utils.helpers import format_command_result
6+
from cecli.helpers.conversation import ConversationManager, MessageTag
7+
8+
9+
class AgentModelCommand(BaseCommand):
10+
NORM_NAME = "agent-model"
11+
DESCRIPTION = "Switch the Agent Model to a new LLM"
12+
13+
@classmethod
14+
async def execute(cls, io, coder, args, **kwargs):
15+
"""Execute the agent-model command with given parameters."""
16+
arg_split = args.split(" ", 1)
17+
model_name = arg_split[0].strip()
18+
if not model_name:
19+
# If no model name provided, show current agent model
20+
current_agent_model = coder.main_model.agent_model.name
21+
io.tool_output(f"Current agent model: {current_agent_model}")
22+
return format_command_result(
23+
io, "agent-model", f"Displayed current agent model: {current_agent_model}"
24+
)
25+
26+
# Create a new model with the same main model and editor model, but updated agent model
27+
model = models.Model(
28+
coder.main_model.name,
29+
editor_model=coder.main_model.editor_model.name,
30+
weak_model=coder.main_model.weak_model.name,
31+
agent_model=model_name,
32+
io=io,
33+
retries=coder.main_model.retries,
34+
debug=coder.main_model.debug,
35+
)
36+
await models.sanity_check_models(io, model)
37+
38+
if len(arg_split) > 1:
39+
# implement architect coder-like generation call for agent model
40+
message = arg_split[1].strip()
41+
42+
# Store the original model configuration
43+
original_main_model = coder.main_model
44+
original_edit_format = coder.edit_format
45+
46+
# Create a temporary coder with the new model
47+
from cecli.coders import Coder
48+
49+
kwargs = dict()
50+
kwargs["main_model"] = model
51+
kwargs["edit_format"] = coder.edit_format # Keep the same edit format
52+
kwargs["suggest_shell_commands"] = False
53+
kwargs["total_cost"] = coder.total_cost
54+
kwargs["num_cache_warming_pings"] = 0
55+
kwargs["summarize_from_coder"] = False
56+
kwargs["done_messages"] = []
57+
kwargs["cur_messages"] = []
58+
59+
new_kwargs = dict(io=io, from_coder=coder)
60+
new_kwargs.update(kwargs)
61+
62+
# Save current conversation state
63+
original_coder = coder
64+
65+
temp_coder = await Coder.create(**new_kwargs)
66+
67+
# Re-initialize ConversationManager with temp coder
68+
ConversationManager.initialize(
69+
temp_coder,
70+
reset=True,
71+
reformat=True,
72+
preserve_tags=[MessageTag.DONE, MessageTag.CUR],
73+
)
74+
75+
verbose = kwargs.get("verbose", False)
76+
if verbose:
77+
temp_coder.show_announcements()
78+
79+
try:
80+
await temp_coder.generate(user_message=message, preproc=False)
81+
coder.total_cost = temp_coder.total_cost
82+
coder.coder_commit_hashes = temp_coder.coder_commit_hashes
83+
84+
# Clear manager and restore original state
85+
ConversationManager.initialize(
86+
original_coder,
87+
reset=True,
88+
reformat=True,
89+
preserve_tags=[MessageTag.DONE, MessageTag.CUR],
90+
)
91+
92+
# Restore the original model configuration
93+
from cecli.commands import SwitchCoderSignal
94+
95+
raise SwitchCoderSignal(
96+
main_model=original_main_model, edit_format=original_edit_format
97+
)
98+
except Exception as e:
99+
# If there's an error, still restore the original model
100+
if not isinstance(e, SwitchCoderSignal):
101+
io.tool_error(str(e))
102+
raise SwitchCoderSignal(
103+
main_model=original_main_model, edit_format=original_edit_format
104+
)
105+
else:
106+
# Re-raise SwitchCoderSignal if that's what was thrown
107+
raise
108+
else:
109+
from cecli.commands import SwitchCoderSignal
110+
111+
raise SwitchCoderSignal(main_model=model, edit_format=coder.edit_format)
112+
113+
@classmethod
114+
def get_completions(cls, io, coder, args) -> List[str]:
115+
"""Get completion options for agent-model command."""
116+
return models.get_chat_model_names()
117+
118+
@classmethod
119+
def get_help(cls) -> str:
120+
"""Get help text for the agent-model command."""
121+
help_text = super().get_help()
122+
help_text += "\nUsage:\n"
123+
help_text += " /agent-model <model-name> # Switch to a new agent model\n"
124+
help_text += (
125+
" /agent-model <model-name> <prompt> # Use a specific agent model for a single"
126+
" prompt\n"
127+
)
128+
help_text += "\nExamples:\n"
129+
help_text += (
130+
" /agent-model gpt-4o-mini # Switch to GPT-4o Mini as agent model\n"
131+
)
132+
help_text += (
133+
" /agent-model claude-3-haiku # Switch to Claude 3 Haiku as agent model\n"
134+
)
135+
help_text += ' /agent-model o1-mini "review this code" # Use o1-mini to review code\n'
136+
help_text += (
137+
"\nWhen switching agent models, the main model and editor model remain unchanged.\n"
138+
)
139+
help_text += (
140+
"\nIf you provide a prompt after the model name, that agent model will be used\n"
141+
)
142+
help_text += "just for that prompt, then you'll return to your original agent model.\n"
143+
return help_text

cecli/commands/editor_model.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ async def execute(cls, io, coder, args, **kwargs):
2828
coder.main_model.name,
2929
editor_model=model_name,
3030
weak_model=coder.main_model.weak_model.name,
31+
agent_model=coder.main_model.agent_model.name,
3132
io=io,
3233
retries=coder.main_model.retries,
3334
debug=coder.main_model.debug,

cecli/commands/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ async def execute(cls, io, coder, args, **kwargs):
3030
("Main model", coder.main_model),
3131
("Editor model", getattr(coder.main_model, "editor_model", None)),
3232
("Weak model", getattr(coder.main_model, "weak_model", None)),
33+
("Agent model", getattr(coder.main_model, "agent_model", None)),
3334
]
3435
for label, model in active_models:
3536
if not model:

cecli/main.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ def apply_model_overrides(model_name):
826826
main_model_name, main_model_overrides = apply_model_overrides(args.model)
827827
weak_model_name, weak_model_overrides = apply_model_overrides(args.weak_model)
828828
editor_model_name, editor_model_overrides = apply_model_overrides(args.editor_model)
829+
agent_model_name, agent_model_overrides = apply_model_overrides(args.agent_model)
829830
weak_model_obj = None
830831
if weak_model_name:
831832
weak_model_obj = models.Model(
@@ -848,6 +849,18 @@ def apply_model_overrides(model_name):
848849
retries=args.retries,
849850
debug=args.debug,
850851
)
852+
agent_model_obj = None
853+
if agent_model_name:
854+
agent_model_obj = models.Model(
855+
agent_model_name,
856+
agent_model=False,
857+
verbose=args.verbose,
858+
io=io,
859+
override_kwargs=agent_model_overrides,
860+
retries=args.retries,
861+
debug=args.debug,
862+
)
863+
851864
if main_model_name.startswith("openrouter/") and not os.environ.get("OPENROUTER_API_KEY"):
852865
io.tool_warning(
853866
f"The specified model '{main_model_name}' requires an OpenRouter API key, which was not"
@@ -873,6 +886,7 @@ def apply_model_overrides(model_name):
873886
main_model_name,
874887
weak_model=weak_model_obj,
875888
editor_model=editor_model_obj,
889+
agent_model=agent_model_obj,
876890
editor_edit_format=args.editor_edit_format,
877891
verbose=args.verbose,
878892
io=io,

cecli/models.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class ModelSettings:
105105
name: str
106106
edit_format: str = "diff"
107107
weak_model_name: Optional[str] = None
108+
agent_model_name: Optional[str] = None
108109
use_repo_map: bool = False
109110
send_undo_reply: bool = False
110111
lazy: bool = False
@@ -314,6 +315,7 @@ def __init__(
314315
model,
315316
weak_model=None,
316317
editor_model=None,
318+
agent_model=None,
317319
editor_edit_format=None,
318320
verbose=False,
319321
io=None,
@@ -341,6 +343,7 @@ def __init__(
341343
self.max_chat_history_tokens = 1024
342344
self.weak_model = None
343345
self.editor_model = None
346+
self.agent_model = None
344347
self.extra_model_settings = next(
345348
(ms for ms in MODEL_SETTINGS if ms.name == "cecli/extra_params"), None
346349
)
@@ -354,6 +357,7 @@ def __init__(
354357
self.configure_model_settings(model)
355358
self._apply_provider_defaults()
356359
self.get_weak_model(weak_model)
360+
self.get_agent_model(agent_model)
357361
self.retries = retries
358362
self.debug = debug
359363

@@ -580,6 +584,30 @@ def get_weak_model(self, provided_weak_model):
580584
self.weak_model = Model(self.weak_model_name, weak_model=False, io=self.io)
581585
return self.weak_model
582586

587+
def get_agent_model(self, provided_weak_model):
588+
if provided_weak_model is False:
589+
self.agent_model = self
590+
self.agent_model_name = None
591+
return
592+
if self.copy_paste_transport == "clipboard":
593+
self.agent_model = self
594+
self.agent_model_name = None
595+
return
596+
if isinstance(provided_weak_model, Model):
597+
self.agent_model = provided_weak_model
598+
self.agent_model_name = provided_weak_model.name
599+
return
600+
if provided_weak_model:
601+
self.agent_model_name = provided_weak_model
602+
if not self.agent_model_name:
603+
self.agent_model = self
604+
return
605+
if self.agent_model_name == self.name:
606+
self.agent_model = self
607+
return
608+
self.agent_model = Model(self.agent_model_name, agent_model=False, io=self.io)
609+
return self.agent_model
610+
583611
def commit_message_models(self):
584612
return [self.weak_model, self]
585613

cecli/sessions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def _build_session_data(self, session_name) -> Dict:
152152
"model": self.coder.main_model.name,
153153
"weak_model": self.coder.main_model.weak_model.name,
154154
"editor_model": self.coder.main_model.editor_model.name,
155+
"agent_model": self.coder.main_model.agent_model.name,
155156
"editor_edit_format": self.coder.main_model.editor_edit_format,
156157
"edit_format": self.coder.edit_format,
157158
"chat_history": {

0 commit comments

Comments
 (0)