diff --git a/HISTORY.md b/HISTORY.md index d37fab09a..7e7b9fdae 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,19 @@ # Release history +### main branch + +- Infinite output for DeepSeek Coder, Mistral models in addition to Anthropic's models. +- New `--deepseek` switch to use DeepSeek Coder. +- New `--chat-mode ` switch to launch in ask/help/code modes. +- New `/code ` command request a code edit while in `ask` mode. +- Web scraper is more robust if page never idles. +- Improved token and cost reporting for infinite output. +- Improvements and bug fixes for `/read` only files. +- Bug fix to persist files added during `/ask`. +- Bug fix for chat history size in `/tokens`. + + ### Aider v0.49.1 - Bugfix to `/help`. diff --git a/aider/args.py b/aider/args.py index 00d5dd0b0..e29d37d0b 100644 --- a/aider/args.py +++ b/aider/args.py @@ -109,6 +109,14 @@ def get_parser(default_config_files, git_root): const=gpt_3_model_name, help=f"Use {gpt_3_model_name} model for the main chat", ) + deepseek_model = "deepseek/deepseek-coder" + group.add_argument( + "--deepseek", + action="store_const", + dest="model", + const=deepseek_model, + help=f"Use {deepseek_model} model for the main chat", + ) ########## group = parser.add_argument_group("Model Settings") diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 23e6ef438..eca0114fd 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -73,6 +73,9 @@ class Coder: multi_response_content = "" partial_response_content = "" commit_before_message = [] + message_cost = 0.0 + message_tokens_sent = 0 + message_tokens_received = 0 @classmethod def create( @@ -149,7 +152,10 @@ class Coder: main_model = self.main_model weak_model = main_model.weak_model prefix = "Model:" - output = f" {main_model.name} with {self.edit_format} edit format" + output = f" {main_model.name} with" + if main_model.info.get("supports_assistant_prefill"): + output += " ♾️" + output += f" {self.edit_format} edit format" if weak_model is not main_model: prefix = "Models:" output += f", weak model {weak_model.name}" @@ -993,7 +999,7 @@ class Coder: return except FinishReasonLength: # We hit the output limit! - if not self.main_model.can_prefill: + if not self.main_model.info.get("supports_assistant_prefill"): exhausted = True break @@ -1002,7 +1008,9 @@ class Coder: if messages[-1]["role"] == "assistant": messages[-1]["content"] = self.multi_response_content else: - messages.append(dict(role="assistant", content=self.multi_response_content)) + messages.append( + dict(role="assistant", content=self.multi_response_content, prefix=True) + ) except Exception as err: self.io.tool_error(f"Unexpected error: {err}") traceback.print_exc() @@ -1017,8 +1025,7 @@ class Coder: self.io.tool_output() - if self.usage_report: - self.io.tool_output(self.usage_report) + self.show_usage_report() if exhausted: self.show_exhausted_error() @@ -1241,7 +1248,6 @@ class Coder: self.io.log_llm_history("TO LLM", format_messages(messages)) - interrupted = False try: hash_object, completion = send_completion( model.name, @@ -1258,9 +1264,9 @@ class Coder: yield from self.show_send_output_stream(completion) else: self.show_send_output(completion) - except KeyboardInterrupt: + except KeyboardInterrupt as kbi: self.keyboard_interrupt() - interrupted = True + raise kbi finally: self.io.log_llm_history( "LLM RESPONSE", @@ -1275,10 +1281,7 @@ class Coder: if args: self.io.ai_output(json.dumps(args, indent=4)) - if interrupted: - raise KeyboardInterrupt - - self.calculate_and_show_tokens_and_cost(messages, completion) + self.calculate_and_show_tokens_and_cost(messages, completion) def show_send_output(self, completion): if self.verbose: @@ -1390,13 +1393,19 @@ class Coder: prompt_tokens = self.main_model.token_count(messages) completion_tokens = self.main_model.token_count(self.partial_response_content) - self.usage_report = f"Tokens: {prompt_tokens:,} sent, {completion_tokens:,} received." + self.message_tokens_sent += prompt_tokens + self.message_tokens_received += completion_tokens + + tokens_report = ( + f"Tokens: {self.message_tokens_sent:,} sent, {self.message_tokens_received:,} received." + ) if self.main_model.info.get("input_cost_per_token"): cost += prompt_tokens * self.main_model.info.get("input_cost_per_token") if self.main_model.info.get("output_cost_per_token"): cost += completion_tokens * self.main_model.info.get("output_cost_per_token") self.total_cost += cost + self.message_cost += cost def format_cost(value): if value == 0: @@ -1407,9 +1416,20 @@ class Coder: else: return f"{value:.{max(2, 2 - int(math.log10(magnitude)))}f}" - self.usage_report += ( - f" Cost: ${format_cost(cost)} request, ${format_cost(self.total_cost)} session." + cost_report = ( + f" Cost: ${format_cost(self.message_cost)} message," + f" ${format_cost(self.total_cost)} session." ) + self.usage_report = tokens_report + cost_report + else: + self.usage_report = tokens_report + + def show_usage_report(self): + if self.usage_report: + self.io.tool_output(self.usage_report) + self.message_cost = 0.0 + self.message_tokens_sent = 0 + self.message_tokens_received = 0 self.event( "message_send", diff --git a/aider/main.py b/aider/main.py index 7610d3bd9..bc553db2c 100644 --- a/aider/main.py +++ b/aider/main.py @@ -423,11 +423,11 @@ def main(argv=None, input=None, output=None, force_git_root=None, return_coder=F return main(argv, input, output, right_repo_root, return_coder=return_coder) if args.just_check_update: - update_available = check_version(io, just_check=True) + update_available = check_version(io, just_check=True, verbose=args.verbose) return 0 if not update_available else 1 if args.check_update: - check_version(io) + check_version(io, verbose=args.verbose) if args.models: models.print_matching_models(io, args.models) diff --git a/aider/models.py b/aider/models.py index 908404343..551d0b9a4 100644 --- a/aider/models.py +++ b/aider/models.py @@ -70,7 +70,6 @@ class ModelSettings: lazy: bool = False reminder_as_sys_msg: bool = False examples_as_sys_msg: bool = False - can_prefill: bool = False extra_headers: Optional[dict] = None max_tokens: Optional[int] = None @@ -248,7 +247,6 @@ MODEL_SETTINGS = [ weak_model_name="claude-3-haiku-20240307", use_repo_map=True, send_undo_reply=True, - can_prefill=True, ), ModelSettings( "openrouter/anthropic/claude-3-opus", @@ -256,13 +254,11 @@ MODEL_SETTINGS = [ weak_model_name="openrouter/anthropic/claude-3-haiku", use_repo_map=True, send_undo_reply=True, - can_prefill=True, ), ModelSettings( "claude-3-sonnet-20240229", "whole", weak_model_name="claude-3-haiku-20240307", - can_prefill=True, ), ModelSettings( "claude-3-5-sonnet-20240620", @@ -270,7 +266,6 @@ MODEL_SETTINGS = [ weak_model_name="claude-3-haiku-20240307", use_repo_map=True, examples_as_sys_msg=True, - can_prefill=True, accepts_images=True, max_tokens=8192, extra_headers={"anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15"}, @@ -281,7 +276,6 @@ MODEL_SETTINGS = [ weak_model_name="claude-3-haiku-20240307", use_repo_map=True, examples_as_sys_msg=True, - can_prefill=True, max_tokens=8192, extra_headers={ "anthropic-beta": "max-tokens-3-5-sonnet-2024-07-15", @@ -295,7 +289,6 @@ MODEL_SETTINGS = [ weak_model_name="openrouter/anthropic/claude-3-haiku-20240307", use_repo_map=True, examples_as_sys_msg=True, - can_prefill=True, accepts_images=True, max_tokens=8192, extra_headers={ @@ -312,7 +305,6 @@ MODEL_SETTINGS = [ weak_model_name="vertex_ai/claude-3-haiku@20240307", use_repo_map=True, examples_as_sys_msg=True, - can_prefill=True, accepts_images=True, ), ModelSettings( @@ -321,13 +313,11 @@ MODEL_SETTINGS = [ weak_model_name="vertex_ai/claude-3-haiku@20240307", use_repo_map=True, send_undo_reply=True, - can_prefill=True, ), ModelSettings( "vertex_ai/claude-3-sonnet@20240229", "whole", weak_model_name="vertex_ai/claude-3-haiku@20240307", - can_prefill=True, ), # Cohere ModelSettings( @@ -486,14 +476,10 @@ class Model: if "gpt-3.5" in model or "gpt-4" in model: self.reminder_as_sys_msg = True - if "anthropic" in model: - self.can_prefill = True - if "3.5-sonnet" in model or "3-5-sonnet" in model: self.edit_format = "diff" self.use_repo_map = True self.examples_as_sys_msg = True - self.can_prefill = True # use the defaults if self.edit_format == "diff": diff --git a/aider/sendchat.py b/aider/sendchat.py index e767e29ce..29ba668ce 100644 --- a/aider/sendchat.py +++ b/aider/sendchat.py @@ -57,6 +57,7 @@ def send_completion( temperature=temperature, stream=stream, ) + if functions is not None: kwargs["functions"] = functions if extra_headers is not None: diff --git a/aider/versioncheck.py b/aider/versioncheck.py index e9a5f7146..5ce90cd35 100644 --- a/aider/versioncheck.py +++ b/aider/versioncheck.py @@ -10,12 +10,15 @@ from aider import utils from aider.dump import dump # noqa: F401 -def check_version(io, just_check=False): +def check_version(io, just_check=False, verbose=False): fname = Path.home() / ".aider" / "caches" / "versioncheck" if not just_check and fname.exists(): day = 60 * 60 * 24 since = time.time() - fname.stat().st_mtime if since < day: + if verbose: + hours = since / 60 / 60 + io.tool_output(f"Too soon to check version: {hours:.1f} hours") return # To keep startup fast, avoid importing this unless needed @@ -27,7 +30,7 @@ def check_version(io, just_check=False): latest_version = data["info"]["version"] current_version = aider.__version__ - if just_check: + if just_check or verbose: io.tool_output(f"Current version: {current_version}") io.tool_output(f"Latest version: {latest_version}") @@ -41,11 +44,13 @@ def check_version(io, just_check=False): fname.parent.mkdir(parents=True, exist_ok=True) fname.touch() - if just_check: + if just_check or verbose: if is_update_available: io.tool_output("Update available") else: io.tool_output("No update available") + + if just_check: return is_update_available if not is_update_available: diff --git a/aider/website/HISTORY.md b/aider/website/HISTORY.md index 9851d605f..8eba0623f 100644 --- a/aider/website/HISTORY.md +++ b/aider/website/HISTORY.md @@ -16,6 +16,19 @@ cog.out(text) # Release history +### main branch + +- Infinite output for DeepSeek Coder, Mistral models in addition to Anthropic's models. +- New `--deepseek` switch to use DeepSeek Coder. +- New `--chat-mode ` switch to launch in ask/help/code modes. +- New `/code ` command request a code edit while in `ask` mode. +- Web scraper is more robust if page never idles. +- Improved token and cost reporting for infinite output. +- Improvements and bug fixes for `/read` only files. +- Bug fix to persist files added during `/ask`. +- Bug fix for chat history size in `/tokens`. + + ### Aider v0.49.1 - Bugfix to `/help`. diff --git a/aider/website/assets/sample.aider.conf.yml b/aider/website/assets/sample.aider.conf.yml index b771f791d..5ca6131a4 100644 --- a/aider/website/assets/sample.aider.conf.yml +++ b/aider/website/assets/sample.aider.conf.yml @@ -47,6 +47,9 @@ ## Use gpt-3.5-turbo model for the main chat #35turbo: false +## Use deepseek/deepseek-coder model for the main chat +#deepseek: false + ################# # Model Settings: diff --git a/aider/website/assets/sample.env b/aider/website/assets/sample.env index a0f305388..cd1f49c44 100644 --- a/aider/website/assets/sample.env +++ b/aider/website/assets/sample.env @@ -51,6 +51,9 @@ ## Use gpt-3.5-turbo model for the main chat #AIDER_35TURBO= +## Use deepseek/deepseek-coder model for the main chat +#AIDER_DEEPSEEK= + ################# # Model Settings: diff --git a/aider/website/docs/config/aider_conf.md b/aider/website/docs/config/aider_conf.md index e45b71f54..d3dfe6887 100644 --- a/aider/website/docs/config/aider_conf.md +++ b/aider/website/docs/config/aider_conf.md @@ -86,6 +86,9 @@ cog.outl("```") ## Use gpt-3.5-turbo model for the main chat #35turbo: false +## Use deepseek/deepseek-coder model for the main chat +#deepseek: false + ################# # Model Settings: diff --git a/aider/website/docs/config/dotenv.md b/aider/website/docs/config/dotenv.md index 380d7cabd..de1434641 100644 --- a/aider/website/docs/config/dotenv.md +++ b/aider/website/docs/config/dotenv.md @@ -93,6 +93,9 @@ cog.outl("```") ## Use gpt-3.5-turbo model for the main chat #AIDER_35TURBO= +## Use deepseek/deepseek-coder model for the main chat +#AIDER_DEEPSEEK= + ################# # Model Settings: diff --git a/aider/website/docs/config/options.md b/aider/website/docs/config/options.md index 331ec13e2..bdfadbdd4 100644 --- a/aider/website/docs/config/options.md +++ b/aider/website/docs/config/options.md @@ -27,7 +27,7 @@ cog.out(get_md_help()) ``` usage: aider [-h] [--openai-api-key] [--anthropic-api-key] [--model] [--opus] [--sonnet] [--4] [--4o] [--mini] [--4-turbo] - [--35turbo] [--models] [--openai-api-base] + [--35turbo] [--deepseek] [--models] [--openai-api-base] [--openai-api-type] [--openai-api-version] [--openai-api-deployment-id] [--openai-organization-id] [--model-settings-file] [--model-metadata-file] @@ -118,6 +118,10 @@ Aliases: - `--3` - `-3` +### `--deepseek` +Use deepseek/deepseek-coder model for the main chat +Environment variable: `AIDER_DEEPSEEK` + ## Model Settings: ### `--models MODEL` diff --git a/aider/website/docs/llms/deepseek.md b/aider/website/docs/llms/deepseek.md index df1e3b465..1c2bc33f3 100644 --- a/aider/website/docs/llms/deepseek.md +++ b/aider/website/docs/llms/deepseek.md @@ -6,7 +6,7 @@ nav_order: 500 # DeepSeek Aider can connect to the DeepSeek.com API. -The DeepSeek Coder V2 model gets the top score on aider's code editing benchmark. +The DeepSeek Coder V2 model has a top score on aider's code editing benchmark. ``` python -m pip install aider-chat @@ -15,10 +15,6 @@ export DEEPSEEK_API_KEY= # Mac/Linux setx DEEPSEEK_API_KEY # Windows, restart shell after setx # Use DeepSeek Coder V2 -aider --model deepseek/deepseek-coder +aider --deepseek ``` -See the [model warnings](warnings.html) -section for information on warnings which will occur -when working with models that aider is not familiar with. - diff --git a/requirements.txt b/requirements.txt index 84375d2af..906d45000 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ # aiohappyeyeballs==2.3.5 # via aiohttp -aiohttp==3.10.2 +aiohttp==3.10.3 # via litellm aiosignal==1.3.1 # via aiohttp @@ -92,7 +92,7 @@ jsonschema==4.23.0 # litellm jsonschema-specifications==2023.12.1 # via jsonschema -litellm==1.43.4 +litellm==1.43.9 # via -r requirements/requirements.in markdown-it-py==3.0.0 # via rich @@ -112,7 +112,7 @@ numpy==1.26.4 # via # -r requirements/requirements.in # scipy -openai==1.40.2 +openai==1.40.6 # via litellm packaging==24.1 # via @@ -125,7 +125,9 @@ pathspec==0.12.1 pillow==10.4.0 # via -r requirements/requirements.in prompt-toolkit==3.0.47 - # via -r requirements/requirements.in + # via + # -r requirements/requirements.in + # pypager pycodestyle==2.12.1 # via flake8 pycparser==2.22 @@ -139,7 +141,11 @@ pydantic-core==2.20.1 pyflakes==3.2.0 # via flake8 pygments==2.18.0 - # via rich + # via + # pypager + # rich +pypager==3.0.1 + # via -r requirements/requirements.in pypandoc==1.13 # via -r requirements/requirements.in pyperclip==1.9.0 @@ -176,7 +182,7 @@ sniffio==1.3.1 # anyio # httpx # openai -sounddevice==0.4.7 +sounddevice==0.5.0 # via -r requirements/requirements.in soundfile==0.12.1 # via -r requirements/requirements.in @@ -210,5 +216,5 @@ wcwidth==0.2.13 # via prompt-toolkit yarl==1.9.4 # via aiohttp -zipp==3.19.2 +zipp==3.20.0 # via importlib-metadata diff --git a/requirements/requirements-browser.txt b/requirements/requirements-browser.txt index 0718a7382..a2f82bdcd 100644 --- a/requirements/requirements-browser.txt +++ b/requirements/requirements-browser.txt @@ -4,7 +4,7 @@ # # pip-compile --output-file=requirements/requirements-browser.txt requirements/requirements-browser.in # -altair==5.3.0 +altair==5.4.0 # via streamlit attrs==24.2.0 # via @@ -64,10 +64,11 @@ mdurl==0.1.2 # via # -c requirements/../requirements.txt # markdown-it-py +narwhals==1.3.0 + # via altair numpy==1.26.4 # via # -c requirements/../requirements.txt - # altair # pandas # pyarrow # pydeck @@ -78,9 +79,7 @@ packaging==24.1 # altair # streamlit pandas==2.2.2 - # via - # altair - # streamlit + # via streamlit pillow==10.4.0 # via # -c requirements/../requirements.txt @@ -129,13 +128,12 @@ tenacity==8.5.0 # via streamlit toml==0.10.2 # via streamlit -toolz==0.12.1 - # via altair tornado==6.4.1 # via streamlit typing-extensions==4.12.2 # via # -c requirements/../requirements.txt + # altair # streamlit tzdata==2024.1 # via pandas @@ -143,5 +141,5 @@ urllib3==2.2.2 # via # -c requirements/../requirements.txt # requests -watchdog==4.0.1 +watchdog==4.0.2 # via -r requirements/requirements-browser.in diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 124552e9e..0580cc4ba 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -75,7 +75,7 @@ markupsafe==2.1.5 # via # -c requirements/../requirements.txt # jinja2 -matplotlib==3.9.1.post1 +matplotlib==3.9.2 # via -r requirements/requirements-dev.in mdurl==0.1.2 # via diff --git a/requirements/requirements-help.txt b/requirements/requirements-help.txt index 9213407c8..38c7b7504 100644 --- a/requirements/requirements-help.txt +++ b/requirements/requirements-help.txt @@ -8,7 +8,7 @@ aiohappyeyeballs==2.3.5 # via # -c requirements/../requirements.txt # aiohttp -aiohttp==3.10.2 +aiohttp==3.10.3 # via # -c requirements/../requirements.txt # huggingface-hub @@ -112,11 +112,11 @@ joblib==1.4.2 # via # nltk # scikit-learn -llama-index-core==0.10.63 +llama-index-core==0.10.65 # via # -r requirements/requirements-help.in # llama-index-embeddings-huggingface -llama-index-embeddings-huggingface==0.2.2 +llama-index-embeddings-huggingface==0.2.3 # via -r requirements/requirements-help.in markupsafe==2.1.5 # via @@ -142,7 +142,7 @@ networkx==3.2.1 # -c requirements/../requirements.txt # llama-index-core # torch -nltk==3.8.1 +nltk==3.8.2 # via llama-index-core numpy==1.26.4 # via @@ -153,7 +153,7 @@ numpy==1.26.4 # scipy # sentence-transformers # transformers -openai==1.40.2 +openai==1.40.6 # via # -c requirements/../requirements.txt # llama-index-core @@ -224,7 +224,7 @@ sqlalchemy[asyncio]==2.0.32 # via # llama-index-core # sqlalchemy -sympy==1.13.1 +sympy==1.13.2 # via torch tenacity==8.5.0 # via llama-index-core diff --git a/requirements/requirements-playwright.txt b/requirements/requirements-playwright.txt index 4be3d03b6..739bd2dae 100644 --- a/requirements/requirements-playwright.txt +++ b/requirements/requirements-playwright.txt @@ -6,7 +6,7 @@ # greenlet==3.0.3 # via playwright -playwright==1.45.1 +playwright==1.46.0 # via -r requirements/requirements-playwright.in pyee==11.1.0 # via playwright diff --git a/tests/basic/test_main.py b/tests/basic/test_main.py index f8322d2ef..1560894a0 100644 --- a/tests/basic/test_main.py +++ b/tests/basic/test_main.py @@ -224,6 +224,15 @@ class TestMain(TestCase): main(["--yes", fname, "--encoding", "iso-8859-15"]) + def test_main_exit_calls_version_check(self): + with GitTemporaryDirectory(): + with patch("aider.main.check_version") as mock_check_version, patch( + "aider.main.InputOutput" + ) as mock_input_output: + main(["--exit"], input=DummyInput(), output=DummyOutput()) + mock_check_version.assert_called_once() + mock_input_output.assert_called_once() + @patch("aider.main.InputOutput") @patch("aider.coders.base_coder.Coder.run") def test_main_message_adds_to_input_history(self, mock_run, MockInputOutput):