diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index a2ccde829..b3e7c4e4f 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -636,7 +636,7 @@ class Coder: if len(chunk.choices) == 0: continue - if chunk.choices[0].finish_reason == "length": + if hasattr(chunk.choices[0], "finish_reason") and chunk.choices[0].finish_reason == "length": raise ExhaustedContextWindow() try: @@ -901,5 +901,6 @@ class Coder: def check_model_availability(main_model): available_models = openai.Model.list() + print(available_models) model_ids = [model.id for model in available_models["data"]] return main_model.name in model_ids diff --git a/aider/commands.py b/aider/commands.py index c05bd1204..a4caf7142 100644 --- a/aider/commands.py +++ b/aider/commands.py @@ -5,7 +5,6 @@ import sys from pathlib import Path import git -import tiktoken from prompt_toolkit.completion import Completion from aider import prompts, voice @@ -24,7 +23,7 @@ class Commands: voice_language = None self.voice_language = voice_language - self.tokenizer = tiktoken.encoding_for_model(coder.main_model.name) + self.tokenizer = coder.main_model.tokenizer def is_command(self, inp): if inp[0] == "/": diff --git a/aider/history.py b/aider/history.py index 912a19108..4ac255f9a 100644 --- a/aider/history.py +++ b/aider/history.py @@ -85,7 +85,7 @@ class ChatSummary: dict(role="user", content=content), ] - summary = simple_send_with_retries(model=models.GPT35.name, messages=messages) + summary = simple_send_with_retries(model=self.model.weak_model, messages=messages) summary = prompts.summary_prefix + summary return [dict(role="user", content=summary)] diff --git a/aider/main.py b/aider/main.py index 8d9b384b0..2283d6b5e 100644 --- a/aider/main.py +++ b/aider/main.py @@ -449,8 +449,6 @@ def main(argv=None, input=None, output=None, force_git_root=None): ) return 1 - main_model = models.Model(args.model) - openai.api_key = args.openai_api_key for attr in ("base", "type", "version", "deployment_id", "engine"): arg_key = f"openai_api_{attr}" @@ -460,6 +458,8 @@ def main(argv=None, input=None, output=None, force_git_root=None): setattr(openai, mod_key, val) io.tool_output(f"Setting openai.{mod_key}={val}") + main_model = models.Model(args.model, openai) + try: coder = Coder.create( main_model, diff --git a/aider/models/__init__.py b/aider/models/__init__.py new file mode 100644 index 000000000..4ab348c29 --- /dev/null +++ b/aider/models/__init__.py @@ -0,0 +1,7 @@ +from .openai import OpenAIModel +from .openrouter import OpenRouterModel +from .model import Model + +GPT4 = Model("gpt-4") +GPT35 = Model("gpt-3.5-turbo") +GPT35_16k = Model("gpt-3.5-turbo-16k") diff --git a/aider/models/model.py b/aider/models/model.py new file mode 100644 index 000000000..0c0ebafaf --- /dev/null +++ b/aider/models/model.py @@ -0,0 +1,60 @@ +import importlib + +using_openrouter = False + +class Model: + name = None + edit_format = None + max_context_tokens = 0 + tokenizer = None + + always_available = False + use_repo_map = False + send_undo_reply = False + + prompt_price = None + completion_price = None + + def __init__(self, name, openai=None): + global using_openrouter + if (openai and "openrouter.ai" in openai.api_base): + using_openrouter = True + + from .openai import OpenAIModel + from .openrouter import OpenRouterModel + model = None + if using_openrouter: + if name == 'gpt-4': + name = 'openai/gpt-4' + elif name == 'gpt-3.5-turbo': + name = 'openai/gpt-3.5-turbo' + elif name == 'gpt-3.5.turbo-16k': + name = 'openai/gpt-3.5-turbo-16k' + + model = OpenRouterModel(name, openai) + else: + model = OpenAIModel(name) + + self.name = model.name + self.edit_format = model.edit_format + self.max_context_tokens = model.max_context_tokens + self.tokenizer = model.tokenizer + self.prompt_price = model.prompt_price + self.completion_price = model.completion_price + self.always_available = model.always_available + self.use_repo_map = model.use_repo_map + + def __str__(self): + return self.name + + @staticmethod + def strong_model(): + return Model('gpt-4') + + @staticmethod + def weak_model(): + return Model('gpt-3.5-turbo') + + @staticmethod + def commit_message_models(): + return [Model('gpt-3.5-turbo'), Model('gpt-3.5-turbo-16k')] diff --git a/aider/models.py b/aider/models/openai.py similarity index 83% rename from aider/models.py rename to aider/models/openai.py index fdbb2d152..d25156758 100644 --- a/aider/models.py +++ b/aider/models/openai.py @@ -1,4 +1,6 @@ +import tiktoken import re +from .model import Model known_tokens = { "gpt-3.5-turbo": 4, @@ -6,14 +8,7 @@ known_tokens = { } -class Model: - always_available = False - use_repo_map = False - send_undo_reply = False - - prompt_price = None - completion_price = None - +class OpenAIModel(Model): def __init__(self, name): self.name = name @@ -31,6 +26,7 @@ class Model: raise ValueError(f"Unknown context window size for model: {name}") self.max_context_tokens = tokens * 1024 + self.tokenizer = tiktoken.encoding_for_model(name) if self.is_gpt4(): self.edit_format = "diff" @@ -66,11 +62,3 @@ class Model: def is_gpt35(self): return self.name.startswith("gpt-3.5-turbo") - - def __str__(self): - return self.name - - -GPT4 = Model("gpt-4") -GPT35 = Model("gpt-3.5-turbo") -GPT35_16k = Model("gpt-3.5-turbo-16k") diff --git a/aider/models/openrouter.py b/aider/models/openrouter.py new file mode 100644 index 000000000..e52f9bbbc --- /dev/null +++ b/aider/models/openrouter.py @@ -0,0 +1,13 @@ +import tiktoken +from .model import Model + + +class OpenRouterModel(Model): + def __init__(self, name, openai): + self.name = name + self.edit_format = "diff" + self.use_repo_map = True + self.max_context_tokens = 1024 * 8 + + # TODO: figure out proper encodings for non openai models + self.tokenizer = tiktoken.get_encoding("cl100k_base") diff --git a/aider/repo.py b/aider/repo.py index 6b0c0b089..a44f235b0 100644 --- a/aider/repo.py +++ b/aider/repo.py @@ -109,8 +109,8 @@ class GitRepo: dict(role="user", content=content), ] - for model in [models.GPT35.name, models.GPT35_16k.name]: - commit_message = simple_send_with_retries(model, messages) + for model in models.Model.commit_message_models(): + commit_message = simple_send_with_retries(model.name, messages) if commit_message: break diff --git a/aider/repomap.py b/aider/repomap.py index 5f9ae3a8a..d22ef6ba8 100644 --- a/aider/repomap.py +++ b/aider/repomap.py @@ -9,7 +9,6 @@ from collections import Counter, defaultdict from pathlib import Path import networkx as nx -import tiktoken from diskcache import Cache from pygments.lexers import guess_lexer_for_filename from pygments.token import Token @@ -104,7 +103,7 @@ class RepoMap: else: self.use_ctags = False - self.tokenizer = tiktoken.encoding_for_model(main_model.name) + self.tokenizer = main_model.tokenizer self.repo_content_prefix = repo_content_prefix def get_repo_map(self, chat_files, other_files): diff --git a/aider/sendchat.py b/aider/sendchat.py index 5025d42dd..04bcab94f 100644 --- a/aider/sendchat.py +++ b/aider/sendchat.py @@ -50,6 +50,12 @@ def send_with_retries(model, messages, functions, stream): if hasattr(openai, "api_engine"): kwargs["engine"] = openai.api_engine + if "openrouter.ai" in openai.api_base: + kwargs["headers"] = { + "HTTP-Referer": "http://aider.chat", + "X-Title": "Aider" + } + key = json.dumps(kwargs, sort_keys=True).encode() # Generate SHA1 hash of kwargs and append it to chat_completion_call_hashes