From 041f3a4a381670449011e3cfbf4d18900452855b Mon Sep 17 00:00:00 2001 From: JV Date: Tue, 15 Aug 2023 03:35:55 +1200 Subject: [PATCH 01/11] initial code for working with openrouter --- aider/coders/base_coder.py | 3 +- aider/commands.py | 3 +- aider/history.py | 2 +- aider/main.py | 4 +- aider/models/__init__.py | 7 ++++ aider/models/model.py | 60 +++++++++++++++++++++++++++ aider/{models.py => models/openai.py} | 20 ++------- aider/models/openrouter.py | 13 ++++++ aider/repo.py | 4 +- aider/repomap.py | 3 +- aider/sendchat.py | 6 +++ 11 files changed, 99 insertions(+), 26 deletions(-) create mode 100644 aider/models/__init__.py create mode 100644 aider/models/model.py rename aider/{models.py => models/openai.py} (83%) create mode 100644 aider/models/openrouter.py 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 From a0d6efc13c737d9707c0186a369170158d5c2f23 Mon Sep 17 00:00:00 2001 From: JV Date: Tue, 15 Aug 2023 04:04:02 +1200 Subject: [PATCH 02/11] dynamically get pricing and context length for openrouter models --- aider/coders/base_coder.py | 1 - aider/models/openrouter.py | 12 +++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index b3e7c4e4f..0f1e2f54b 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -901,6 +901,5 @@ 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/models/openrouter.py b/aider/models/openrouter.py index e52f9bbbc..11e0bc323 100644 --- a/aider/models/openrouter.py +++ b/aider/models/openrouter.py @@ -7,7 +7,17 @@ class OpenRouterModel(Model): 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") + + available_models = openai.Model.list().data + found = next((details for details in available_models if details.get('id') == name), None) + + if found: + self.max_context_tokens = int(found.context_length) + self.prompt_price = float(found.get('pricing').get('prompt')) * 1000 + self.completion_price = float(found.get('pricing').get('completion')) * 1000 + + else: + raise ValueError('invalid openrouter model for {name}') From 22f33498628fdd91eff22d5bd3a28c8f14d429d9 Mon Sep 17 00:00:00 2001 From: JV Date: Tue, 15 Aug 2023 04:27:53 +1200 Subject: [PATCH 03/11] Refactored the code to use a global variable to store the OpenAI instance and added a helper function to determine the edit format for a given model. --- aider/models/model.py | 17 +++++------------ aider/models/openrouter.py | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/aider/models/model.py b/aider/models/model.py index 0c0ebafaf..4b3397a50 100644 --- a/aider/models/model.py +++ b/aider/models/model.py @@ -1,6 +1,6 @@ import importlib -using_openrouter = False +saved_openai = None class Model: name = None @@ -16,22 +16,15 @@ class Model: completion_price = None def __init__(self, name, openai=None): - global using_openrouter + global saved_openai if (openai and "openrouter.ai" in openai.api_base): - using_openrouter = True + saved_openai = openai 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) + if saved_openai: + model = OpenRouterModel(name, saved_openai) else: model = OpenAIModel(name) diff --git a/aider/models/openrouter.py b/aider/models/openrouter.py index 11e0bc323..7250a521a 100644 --- a/aider/models/openrouter.py +++ b/aider/models/openrouter.py @@ -4,9 +4,16 @@ from .model import Model class OpenRouterModel(Model): def __init__(self, name, openai): + 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' + self.name = name - self.edit_format = "diff" - self.use_repo_map = True + self.edit_format = edit_format_for_model(name) + self.use_repo_map = self.edit_format == "diff" # TODO: figure out proper encodings for non openai models self.tokenizer = tiktoken.get_encoding("cl100k_base") @@ -20,4 +27,11 @@ class OpenRouterModel(Model): self.completion_price = float(found.get('pricing').get('completion')) * 1000 else: - raise ValueError('invalid openrouter model for {name}') + raise ValueError(f'invalid openrouter model: {name}') + + +def edit_format_for_model(name): + if any(str in name for str in ['gpt-4', 'claude-2']): + return "diff" + + return "whole" From a39829c5f869162b54f29b4239667fe064130290 Mon Sep 17 00:00:00 2001 From: JV Date: Tue, 15 Aug 2023 04:34:40 +1200 Subject: [PATCH 04/11] adding todo notes --- aider/models/model.py | 3 +-- aider/models/openrouter.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/aider/models/model.py b/aider/models/model.py index 4b3397a50..3781d0770 100644 --- a/aider/models/model.py +++ b/aider/models/model.py @@ -1,7 +1,6 @@ -import importlib - saved_openai = None + class Model: name = None edit_format = None diff --git a/aider/models/openrouter.py b/aider/models/openrouter.py index 7250a521a..97802702f 100644 --- a/aider/models/openrouter.py +++ b/aider/models/openrouter.py @@ -18,6 +18,7 @@ class OpenRouterModel(Model): # TODO: figure out proper encodings for non openai models self.tokenizer = tiktoken.get_encoding("cl100k_base") + # TODO cache the model list data to speed up using multiple models available_models = openai.Model.list().data found = next((details for details in available_models if details.get('id') == name), None) @@ -30,6 +31,7 @@ class OpenRouterModel(Model): raise ValueError(f'invalid openrouter model: {name}') +# TODO run benchmarks and figure out which models support which edit-formats def edit_format_for_model(name): if any(str in name for str in ['gpt-4', 'claude-2']): return "diff" From 8fca0f27eed23328e0b18e274332a70e1747afe7 Mon Sep 17 00:00:00 2001 From: JV Date: Tue, 15 Aug 2023 20:21:40 +1200 Subject: [PATCH 05/11] add caching of openrouter model details --- aider/models/openrouter.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/aider/models/openrouter.py b/aider/models/openrouter.py index 97802702f..8beb42215 100644 --- a/aider/models/openrouter.py +++ b/aider/models/openrouter.py @@ -1,6 +1,8 @@ import tiktoken from .model import Model +cached_model_details = None + class OpenRouterModel(Model): def __init__(self, name, openai): @@ -19,8 +21,10 @@ class OpenRouterModel(Model): self.tokenizer = tiktoken.get_encoding("cl100k_base") # TODO cache the model list data to speed up using multiple models - available_models = openai.Model.list().data - found = next((details for details in available_models if details.get('id') == name), None) + global cached_model_details + if cached_model_details == None: + cached_model_details = openai.Model.list().data + found = next((details for details in cached_model_details if details.get('id') == name), None) if found: self.max_context_tokens = int(found.context_length) From 1fc2abe3d0f1de5b4849e48e3253090218a3e227 Mon Sep 17 00:00:00 2001 From: JV Date: Tue, 15 Aug 2023 20:28:30 +1200 Subject: [PATCH 06/11] save model to ChatSummary --- aider/history.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aider/history.py b/aider/history.py index 4ac255f9a..44c6edea1 100644 --- a/aider/history.py +++ b/aider/history.py @@ -12,6 +12,7 @@ class ChatSummary: def __init__(self, model=models.GPT35.name, max_tokens=1024): self.tokenizer = tiktoken.encoding_for_model(model) self.max_tokens = max_tokens + self.model = model def too_big(self, messages): sized = self.tokenize(messages) From abbc93678b2d14dd6a7e34bd2861ed9ac743e4ad Mon Sep 17 00:00:00 2001 From: Joshua Vial Date: Wed, 23 Aug 2023 21:26:27 +1200 Subject: [PATCH 07/11] finishing openrouter integration --- aider/coders/base_coder.py | 2 +- aider/history.py | 8 ++++---- aider/main.py | 2 +- aider/models/__init__.py | 6 +++--- aider/models/model.py | 35 ++++++++++------------------------- aider/models/openrouter.py | 3 ++- aider/repomap.py | 2 +- aider/sendchat.py | 8 ++++---- tests/test_models.py | 12 ++++++------ 9 files changed, 32 insertions(+), 46 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 0f1e2f54b..d103396d9 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -190,7 +190,7 @@ class Coder: for fname in self.get_inchat_relative_files(): self.io.tool_output(f"Added {fname} to the chat.") - self.summarizer = ChatSummary() + self.summarizer = ChatSummary(models.Model.weak_model()) self.summarizer_thread = None self.summarized_done_messages = None diff --git a/aider/history.py b/aider/history.py index 44c6edea1..36d148344 100644 --- a/aider/history.py +++ b/aider/history.py @@ -9,8 +9,8 @@ from aider.sendchat import simple_send_with_retries class ChatSummary: - def __init__(self, model=models.GPT35.name, max_tokens=1024): - self.tokenizer = tiktoken.encoding_for_model(model) + def __init__(self, model=models.Model.weak_model(), max_tokens=1024): + self.tokenizer = model.tokenizer self.max_tokens = max_tokens self.model = model @@ -86,7 +86,7 @@ class ChatSummary: dict(role="user", content=content), ] - summary = simple_send_with_retries(model=self.model.weak_model, messages=messages) + summary = simple_send_with_retries(self.model.name, messages) summary = prompts.summary_prefix + summary return [dict(role="user", content=summary)] @@ -124,7 +124,7 @@ def main(): assistant.append(line) - summarizer = ChatSummary(models.GPT35.name) + summarizer = ChatSummary(models.Model.weak_model()) summary = summarizer.summarize(messages[-40:]) dump(summary) diff --git a/aider/main.py b/aider/main.py index 2283d6b5e..755e118bb 100644 --- a/aider/main.py +++ b/aider/main.py @@ -458,7 +458,7 @@ 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) + main_model = models.Model.create(args.model) try: coder = Coder.create( diff --git a/aider/models/__init__.py b/aider/models/__init__.py index 4ab348c29..f5a6fa7a8 100644 --- a/aider/models/__init__.py +++ b/aider/models/__init__.py @@ -2,6 +2,6 @@ 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") +GPT4 = Model.create('gpt-4') +GPT35 = Model.create('gpt-3.5-turbo') +GPT35_16k = Model.create('gpt-3.5-turbo-16k') diff --git a/aider/models/model.py b/aider/models/model.py index 3781d0770..9f48f4a79 100644 --- a/aider/models/model.py +++ b/aider/models/model.py @@ -1,6 +1,4 @@ -saved_openai = None - - +import openai class Model: name = None edit_format = None @@ -13,40 +11,27 @@ class Model: prompt_price = None completion_price = None + openai=None - def __init__(self, name, openai=None): - global saved_openai - if (openai and "openrouter.ai" in openai.api_base): - saved_openai = openai - + @classmethod + def create(cls, name, **kwargs): from .openai import OpenAIModel from .openrouter import OpenRouterModel - model = None - if saved_openai: - model = OpenRouterModel(name, saved_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 + if ("openrouter.ai" in openai.api_base): + return OpenRouterModel(name, **kwargs) + return OpenAIModel(name, **kwargs) def __str__(self): return self.name @staticmethod def strong_model(): - return Model('gpt-4') + return Model.create('gpt-4') @staticmethod def weak_model(): - return Model('gpt-3.5-turbo') + return Model.create('gpt-3.5-turbo') @staticmethod def commit_message_models(): - return [Model('gpt-3.5-turbo'), Model('gpt-3.5-turbo-16k')] + return [Model.create('gpt-3.5-turbo'), Model.create('gpt-3.5-turbo-16k')] diff --git a/aider/models/openrouter.py b/aider/models/openrouter.py index 8beb42215..4f6c9e1dc 100644 --- a/aider/models/openrouter.py +++ b/aider/models/openrouter.py @@ -1,3 +1,4 @@ +import openai import tiktoken from .model import Model @@ -5,7 +6,7 @@ cached_model_details = None class OpenRouterModel(Model): - def __init__(self, name, openai): + def __init__(self, name): if name == 'gpt-4': name = 'openai/gpt-4' elif name == 'gpt-3.5-turbo': diff --git a/aider/repomap.py b/aider/repomap.py index d22ef6ba8..1b9f8a005 100644 --- a/aider/repomap.py +++ b/aider/repomap.py @@ -80,7 +80,7 @@ class RepoMap: self, map_tokens=1024, root=None, - main_model=models.GPT4, + main_model=models.Model.strong_model(), io=None, repo_content_prefix=None, verbose=False, diff --git a/aider/sendchat.py b/aider/sendchat.py index 04bcab94f..2269e512c 100644 --- a/aider/sendchat.py +++ b/aider/sendchat.py @@ -34,9 +34,9 @@ CACHE = None f"{details.get('exception','Exception')}\nRetry in {details['wait']:.1f} seconds." ), ) -def send_with_retries(model, messages, functions, stream): +def send_with_retries(model_name, messages, functions, stream): kwargs = dict( - model=model, + model=model_name, messages=messages, temperature=0, stream=stream, @@ -72,10 +72,10 @@ def send_with_retries(model, messages, functions, stream): return hash_object, res -def simple_send_with_retries(model, messages): +def simple_send_with_retries(model_name, messages): try: _hash, response = send_with_retries( - model=model, + model_name=model_name, messages=messages, functions=None, stream=False, diff --git a/tests/test_models.py b/tests/test_models.py index af2a6f8d7..39482dee3 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -5,22 +5,22 @@ from aider.models import Model class TestModels(unittest.TestCase): def test_max_context_tokens(self): - model = Model("gpt-3.5-turbo") + model = Model.create("gpt-3.5-turbo") self.assertEqual(model.max_context_tokens, 4 * 1024) - model = Model("gpt-3.5-turbo-16k") + model = Model.create("gpt-3.5-turbo-16k") self.assertEqual(model.max_context_tokens, 16 * 1024) - model = Model("gpt-4") + model = Model.create("gpt-4") self.assertEqual(model.max_context_tokens, 8 * 1024) - model = Model("gpt-4-32k") + model = Model.create("gpt-4-32k") self.assertEqual(model.max_context_tokens, 32 * 1024) - model = Model("gpt-4-0101") + model = Model.create("gpt-4-0101") self.assertEqual(model.max_context_tokens, 8 * 1024) - model = Model("gpt-4-32k-2123") + model = Model.create("gpt-4-32k-2123") self.assertEqual(model.max_context_tokens, 32 * 1024) From d1e0a196b5a88ce8af5eff35381ab1833a10081b Mon Sep 17 00:00:00 2001 From: Joshua Vial Date: Wed, 23 Aug 2023 21:32:00 +1200 Subject: [PATCH 08/11] Add test for creating models from OpenRouter API. --- tests/test_models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_models.py b/tests/test_models.py index 39482dee3..1b826b29b 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -23,6 +23,12 @@ class TestModels(unittest.TestCase): model = Model.create("gpt-4-32k-2123") self.assertEqual(model.max_context_tokens, 32 * 1024) + def test_openrouter_models(self): + import openai + openai.api_base = 'https://openrouter.ai/api/v1' + model = Model.create("gpt-3.5-turbo") + self.assertEqual(model.name, 'openai/gpt-3.5-turbo') + if __name__ == "__main__": unittest.main() From 668a0500fffcb0ecf49989682f938fceed0bbdbe Mon Sep 17 00:00:00 2001 From: Joshua Vial Date: Wed, 23 Aug 2023 21:37:22 +1200 Subject: [PATCH 09/11] test openrouter model properties --- aider/models/openrouter.py | 6 +++--- tests/test_models.py | 28 ++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/aider/models/openrouter.py b/aider/models/openrouter.py index 4f6c9e1dc..59a099876 100644 --- a/aider/models/openrouter.py +++ b/aider/models/openrouter.py @@ -28,9 +28,9 @@ class OpenRouterModel(Model): found = next((details for details in cached_model_details if details.get('id') == name), None) if found: - self.max_context_tokens = int(found.context_length) - self.prompt_price = float(found.get('pricing').get('prompt')) * 1000 - self.completion_price = float(found.get('pricing').get('completion')) * 1000 + self.max_context_tokens = int(found.get('context_length')) + self.prompt_price = round(float(found.get('pricing').get('prompt')) * 1000,6) + self.completion_price = round(float(found.get('pricing').get('completion')) * 1000,6) else: raise ValueError(f'invalid openrouter model: {name}') diff --git a/tests/test_models.py b/tests/test_models.py index 1b826b29b..04f8bce81 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,6 +1,7 @@ import unittest +from unittest.mock import patch -from aider.models import Model +from aider.models import Model, OpenRouterModel class TestModels(unittest.TestCase): @@ -23,12 +24,31 @@ class TestModels(unittest.TestCase): model = Model.create("gpt-4-32k-2123") self.assertEqual(model.max_context_tokens, 32 * 1024) - def test_openrouter_models(self): + + @patch('openai.Model.list') + def test_openrouter_model_properties(self, mock_model_list): import openai openai.api_base = 'https://openrouter.ai/api/v1' - model = Model.create("gpt-3.5-turbo") - self.assertEqual(model.name, 'openai/gpt-3.5-turbo') + mock_model_list.return_value = { + 'data': [ + { + 'id': 'openai/gpt-4', + 'object': 'model', + 'context_length': '8192', + 'pricing': { + 'prompt': '0.00006', + 'completion': '0.00012' + } + } + ] + } + mock_model_list.return_value = type('', (), {'data': mock_model_list.return_value['data']})() + model = OpenRouterModel("gpt-4") + self.assertEqual(model.name, 'openai/gpt-4') + self.assertEqual(model.max_context_tokens, 8192) + self.assertEqual(model.prompt_price, 0.06) + self.assertEqual(model.completion_price, 0.12) if __name__ == "__main__": unittest.main() From 0826e116daa060bd67bdd9dddcef0945f62e82a8 Mon Sep 17 00:00:00 2001 From: Joshua Vial Date: Wed, 23 Aug 2023 22:03:09 +1200 Subject: [PATCH 10/11] cleaning up openrouter code --- aider/models/model.py | 7 +++---- aider/models/openrouter.py | 1 - tests/test_models.py | 2 ++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aider/models/model.py b/aider/models/model.py index 9f48f4a79..f7169557e 100644 --- a/aider/models/model.py +++ b/aider/models/model.py @@ -11,15 +11,14 @@ class Model: prompt_price = None completion_price = None - openai=None @classmethod - def create(cls, name, **kwargs): + def create(cls, name): from .openai import OpenAIModel from .openrouter import OpenRouterModel if ("openrouter.ai" in openai.api_base): - return OpenRouterModel(name, **kwargs) - return OpenAIModel(name, **kwargs) + return OpenRouterModel(name) + return OpenAIModel(name) def __str__(self): return self.name diff --git a/aider/models/openrouter.py b/aider/models/openrouter.py index 59a099876..8306c136f 100644 --- a/aider/models/openrouter.py +++ b/aider/models/openrouter.py @@ -21,7 +21,6 @@ class OpenRouterModel(Model): # TODO: figure out proper encodings for non openai models self.tokenizer = tiktoken.get_encoding("cl100k_base") - # TODO cache the model list data to speed up using multiple models global cached_model_details if cached_model_details == None: cached_model_details = openai.Model.list().data diff --git a/tests/test_models.py b/tests/test_models.py index 04f8bce81..6856a9827 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -28,6 +28,7 @@ class TestModels(unittest.TestCase): @patch('openai.Model.list') def test_openrouter_model_properties(self, mock_model_list): import openai + old_base = openai.api_base openai.api_base = 'https://openrouter.ai/api/v1' mock_model_list.return_value = { 'data': [ @@ -49,6 +50,7 @@ class TestModels(unittest.TestCase): self.assertEqual(model.max_context_tokens, 8192) self.assertEqual(model.prompt_price, 0.06) self.assertEqual(model.completion_price, 0.12) + openai.api_base = old_base if __name__ == "__main__": unittest.main() From 6405193a95075b44f79422030ca701a7407cca9a Mon Sep 17 00:00:00 2001 From: Joshua Vial Date: Wed, 23 Aug 2023 22:26:14 +1200 Subject: [PATCH 11/11] docs for openrouter --- docs/faq.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 4b0765475..94f552e05 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -4,6 +4,7 @@ - [How does aider use git?](#how-does-aider-use-git) - [GPT-4 vs GPT-3.5](#gpt-4-vs-gpt-35) - [Aider isn't editing my files?](#aider-isnt-editing-my-files) +- [Accessing other LLMs with OpenRouter](#accessing-other-llms-with-openrouter) - [Can I use aider with other LLMs, local LLMs, etc?](#can-i-use-aider-with-other-llms-local-llms-etc) - [Can I change the system prompts that aider uses?](#can-i-change-the-system-prompts-that-aider-uses) - [Can I run aider in Google Colab?](#can-i-run-aider-in-google-colab) @@ -112,6 +113,19 @@ In these cases, here are some things you might try: - Use `/drop` to remove files from the chat session which aren't needed for the task at hand. This will reduce distractions and may help GPT produce properly formatted edits. - Use `/clear` to remove the conversation history, again to help GPT focus. +## Accessing other LLMs with OpenRouter + +[OpenRouter](https://openrouter.ai) provide an interface to [many models](https://openrouter.ai/docs) which are not widely accessible, in particular gpt-4-32k and claude-2. + +To access the openrouter models simply + +- register for an account, purchase some credits and generate an api key +- set --openai-api-base to https://openrouter.ai/api/v1 +- set --openai-api-key to your openrouter key +- set --model to the model of your choice (openai/gpt-4-32k, anthropic/claude-2 etc.) + +Some of the models weren't very functional and each llm has its own quirks. The anthropic models work ok, but the llama-2 ones in particular will need more work to play friendly with aider. + ## Can I use aider with other LLMs, local LLMs, etc? Aider only has experimental support for LLMs other than OpenAI's GPT-3.5 and GPT-4. This is for two reasons: