From 050d35790e0533a2cbc574a8633cfcde68e8ec7c Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 30 Apr 2024 16:02:38 -0700 Subject: [PATCH 1/5] added /models cmd --- aider/commands.py | 12 +++++++++++- aider/main.py | 12 +----------- aider/models.py | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/aider/commands.py b/aider/commands.py index 3b6f359d5..ed81c54ca 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -8,7 +8,7 @@ import git import openai from prompt_toolkit.completion import Completion -from aider import prompts, voice +from aider import models, prompts, voice from aider.scrape import Scraper from aider.utils import is_image_file @@ -28,6 +28,16 @@ class Commands: self.voice_language = voice_language + def cmd_model(self, args): + "Search the list of available models" + + args = args.strip() + + if args: + models.print_matching_models(self.io, args) + else: + self.io.tool_output("Use `/model ` to show models which match") + def cmd_web(self, args): "Use headless selenium to scrape a webpage and add the content to the chat" url = args.strip() diff --git a/aider/main.py b/aider/main.py index 193f29f89..11e0143cb 100644 --- a/aider/main.py +++ b/aider/main.py @@ -270,17 +270,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F return 0 if not update_available else 1 if args.models: - matches = models.fuzzy_match_models(args.models) - if matches: - io.tool_output(f'Models which match "{args.models}":') - for model in matches: - fq, m = model - if fq == m: - io.tool_output(f"- {m}") - else: - io.tool_output(f"- {m} ({fq})") - else: - io.tool_output(f'No models match "{args.models}".') + models.print_matching_models(io, args.models) return 0 if args.git: diff --git a/aider/models.py b/aider/models.py index 59976f2b1..c7f127705 100644 --- a/aider/models.py +++ b/aider/models.py @@ -409,6 +409,20 @@ def fuzzy_match_models(name): return list(zip(matching_models, matching_models)) +def print_matching_models(io, search): + matches = fuzzy_match_models(search) + if matches: + io.tool_output(f'Models which match "{search}":') + for model in matches: + fq, m = model + if fq == m: + io.tool_output(f"- {m}") + else: + io.tool_output(f"- {m} ({fq})") + else: + io.tool_output(f'No models match "{search}".') + + def main(): if len(sys.argv) != 2: print("Usage: python models.py ") From 304856fc600d3774528481b6f588a7a6bc334440 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 30 Apr 2024 16:22:13 -0700 Subject: [PATCH 2/5] roughed in model switch --- aider/commands.py | 17 ++++++++++++- aider/main.py | 61 ++++++++++++++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/aider/commands.py b/aider/commands.py index ed81c54ca..958902977 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -15,6 +15,11 @@ from aider.utils import is_image_file from .dump import dump # noqa: F401 +class SwitchModel(Exception): + def __init__(self, model): + self.model = model + + class Commands: voice = None scraper = None @@ -29,6 +34,14 @@ class Commands: self.voice_language = voice_language def cmd_model(self, args): + "Switch to a new LLM" + + model_name = args.strip() + model = models.Model(model_name) + models.sanity_check_models(self.io, model) + raise SwitchModel(model) + + def cmd_models(self, args): "Search the list of available models" args = args.strip() @@ -36,7 +49,7 @@ class Commands: if args: models.print_matching_models(self.io, args) else: - self.io.tool_output("Use `/model ` to show models which match") + self.io.tool_output("Please provide a partial model name to search for.") def cmd_web(self, args): "Use headless selenium to scrape a webpage and add the content to the chat" @@ -109,6 +122,8 @@ class Commands: matching_commands, first_word, rest_inp = res if len(matching_commands) == 1: return self.do_run(matching_commands[0][1:], rest_inp) + elif first_word in matching_commands: + return self.do_run(first_word[1:], rest_inp) elif len(matching_commands) > 1: self.io.tool_error(f"Ambiguous command: {', '.join(matching_commands)}") else: diff --git a/aider/main.py b/aider/main.py index 11e0143cb..fd6195f5b 100644 --- a/aider/main.py +++ b/aider/main.py @@ -10,6 +10,7 @@ from streamlit.web import cli from aider import __version__, models from aider.args import get_parser from aider.coders import Coder +from aider.commands import SwitchModel from aider.io import InputOutput from aider.repo import GitRepo from aider.versioncheck import check_version @@ -305,28 +306,31 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F if args.show_model_warnings: models.sanity_check_models(io, main_model) + coder_kwargs = dict( + main_model=main_model, + edit_format=args.edit_format, + io=io, + ## + fnames=fnames, + git_dname=git_dname, + pretty=args.pretty, + show_diffs=args.show_diffs, + auto_commits=args.auto_commits, + dirty_commits=args.dirty_commits, + dry_run=args.dry_run, + map_tokens=args.map_tokens, + verbose=args.verbose, + assistant_output_color=args.assistant_output_color, + code_theme=args.code_theme, + stream=args.stream, + use_git=args.git, + voice_language=args.voice_language, + aider_ignore_file=args.aiderignore, + ) + try: - coder = Coder.create( - main_model=main_model, - edit_format=args.edit_format, - io=io, - ## - fnames=fnames, - git_dname=git_dname, - pretty=args.pretty, - show_diffs=args.show_diffs, - auto_commits=args.auto_commits, - dirty_commits=args.dirty_commits, - dry_run=args.dry_run, - map_tokens=args.map_tokens, - verbose=args.verbose, - assistant_output_color=args.assistant_output_color, - code_theme=args.code_theme, - stream=args.stream, - use_git=args.git, - voice_language=args.voice_language, - aider_ignore_file=args.aiderignore, - ) + coder = Coder.create(**coder_kwargs) + except ValueError as err: io.tool_error(str(err)) return 1 @@ -388,7 +392,20 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F return 1 return - coder.run() + while True: + try: + coder.run() + return + except SwitchModel as switch: + kwargs = dict( + main_model=switch.model, + edit_format=None, + fnames=coder.get_inchat_relative_files(), + ) + coder_kwargs.update(kwargs) + + coder = Coder.create(**coder_kwargs) + coder.show_announcements() if __name__ == "__main__": From 8e9a00006b32acfa41a350a1138b6f075f0b4562 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 30 Apr 2024 16:25:47 -0700 Subject: [PATCH 3/5] carry conversation history to switched model --- aider/coders/base_coder.py | 14 ++++++++++++-- aider/main.py | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 007f03347..75884c9d2 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -153,6 +153,8 @@ class Coder: use_git=True, voice_language=None, aider_ignore_file=None, + cur_messages=None, + done_messages=None, ): if not fnames: fnames = [] @@ -166,8 +168,16 @@ class Coder: self.verbose = verbose self.abs_fnames = set() - self.cur_messages = [] - self.done_messages = [] + + if cur_messages: + self.cur_messages = cur_messages + else: + self.cur_messages = [] + + if done_messages: + self.done_messages = done_messages + else: + self.done_messages = [] self.io = io self.stream = stream diff --git a/aider/main.py b/aider/main.py index fd6195f5b..bc9c0b732 100644 --- a/aider/main.py +++ b/aider/main.py @@ -401,6 +401,8 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F main_model=switch.model, edit_format=None, fnames=coder.get_inchat_relative_files(), + done_messages=coder.done_messages, + cur_messages=coder.cur_messages, ) coder_kwargs.update(kwargs) From 665b9044c82b6345c4ac64617d4215e9e4190f91 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 30 Apr 2024 16:29:10 -0700 Subject: [PATCH 4/5] added autocomplete for model names --- aider/commands.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/aider/commands.py b/aider/commands.py index 958902977..3cbf7967c 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -5,6 +5,7 @@ import sys from pathlib import Path import git +import litellm import openai from prompt_toolkit.completion import Completion @@ -14,6 +15,8 @@ from aider.utils import is_image_file from .dump import dump # noqa: F401 +litellm.suppress_debug_info = True + class SwitchModel(Exception): def __init__(self, model): @@ -41,6 +44,12 @@ class Commands: models.sanity_check_models(self.io, model) raise SwitchModel(model) + def completions_model(self, partial): + models = litellm.model_cost.keys() + for model in models: + if partial.lower() in model.lower(): + yield Completion(model, start_position=-len(partial)) + def cmd_models(self, args): "Search the list of available models" From 256a9a454ccef7116d011bac6ea7b31f67cdbe83 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 30 Apr 2024 17:31:58 -0700 Subject: [PATCH 5/5] move cloning into Coder.create, summarize chat history if edit format changes --- aider/coders/base_coder.py | 34 +++++++++++++++++-- aider/coders/wholefile_prompts.py | 4 +-- aider/main.py | 55 +++++++++++++------------------ 3 files changed, 55 insertions(+), 38 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 75884c9d2..7e1bc74c8 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -66,6 +66,7 @@ class Coder: main_model=None, edit_format=None, io=None, + from_coder=None, **kwargs, ): from . import EditBlockCoder, UnifiedDiffCoder, WholeFileCoder @@ -76,15 +77,42 @@ class Coder: if edit_format is None: edit_format = main_model.edit_format + if from_coder: + use_kwargs = dict(from_coder.original_kwargs) # copy orig kwargs + + # If the edit format changes, we can't leave old ASSISTANT + # messages in the chat history. The old edit format will + # confused the new LLM. It may try and imitate it, disobeying + # the system prompt. + done_messages = from_coder.done_messages + if edit_format != from_coder.edit_format and done_messages: + done_messages = from_coder.summarizer.summarize_all(done_messages) + + # Bring along context from the old Coder + update = dict( + fnames=from_coder.get_inchat_relative_files(), + done_messages=done_messages, + cur_messages=from_coder.cur_messages, + ) + + use_kwargs.update(update) # override to complete the switch + use_kwargs.update(kwargs) # override passed kwargs + + kwargs = use_kwargs + if edit_format == "diff": - return EditBlockCoder(main_model, io, **kwargs) + res = EditBlockCoder(main_model, io, **kwargs) elif edit_format == "whole": - return WholeFileCoder(main_model, io, **kwargs) + res = WholeFileCoder(main_model, io, **kwargs) elif edit_format == "udiff": - return UnifiedDiffCoder(main_model, io, **kwargs) + res = UnifiedDiffCoder(main_model, io, **kwargs) else: raise ValueError(f"Unknown edit format {edit_format}") + res.original_kwargs = dict(kwargs) + + return res + def get_announcements(self): lines = [] lines.append(f"Aider v{__version__}") diff --git a/aider/coders/wholefile_prompts.py b/aider/coders/wholefile_prompts.py index dda10fe3c..e410fdd9c 100644 --- a/aider/coders/wholefile_prompts.py +++ b/aider/coders/wholefile_prompts.py @@ -18,14 +18,14 @@ Once you understand the request you MUST: You MUST use this *file listing* format: path/to/filename.js -{fence[0]}javascript +{fence[0]} // entire file content ... // ... goes in between {fence[1]} Every *file listing* MUST use this format: - First line: the filename with any originally provided path -- Second line: opening {fence[0]} including the code language +- Second line: opening {fence[0]} - ... entire content of the file ... - Final line: closing {fence[1]} diff --git a/aider/main.py b/aider/main.py index bc9c0b732..85fa08a0a 100644 --- a/aider/main.py +++ b/aider/main.py @@ -306,30 +306,28 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F if args.show_model_warnings: models.sanity_check_models(io, main_model) - coder_kwargs = dict( - main_model=main_model, - edit_format=args.edit_format, - io=io, - ## - fnames=fnames, - git_dname=git_dname, - pretty=args.pretty, - show_diffs=args.show_diffs, - auto_commits=args.auto_commits, - dirty_commits=args.dirty_commits, - dry_run=args.dry_run, - map_tokens=args.map_tokens, - verbose=args.verbose, - assistant_output_color=args.assistant_output_color, - code_theme=args.code_theme, - stream=args.stream, - use_git=args.git, - voice_language=args.voice_language, - aider_ignore_file=args.aiderignore, - ) - try: - coder = Coder.create(**coder_kwargs) + coder = Coder.create( + main_model=main_model, + edit_format=args.edit_format, + io=io, + ## + fnames=fnames, + git_dname=git_dname, + pretty=args.pretty, + show_diffs=args.show_diffs, + auto_commits=args.auto_commits, + dirty_commits=args.dirty_commits, + dry_run=args.dry_run, + map_tokens=args.map_tokens, + verbose=args.verbose, + assistant_output_color=args.assistant_output_color, + code_theme=args.code_theme, + stream=args.stream, + use_git=args.git, + voice_language=args.voice_language, + aider_ignore_file=args.aiderignore, + ) except ValueError as err: io.tool_error(str(err)) @@ -397,16 +395,7 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F coder.run() return except SwitchModel as switch: - kwargs = dict( - main_model=switch.model, - edit_format=None, - fnames=coder.get_inchat_relative_files(), - done_messages=coder.done_messages, - cur_messages=coder.cur_messages, - ) - coder_kwargs.update(kwargs) - - coder = Coder.create(**coder_kwargs) + coder = Coder.create(main_model=switch.model, io=io, from_coder=coder) coder.show_announcements()