mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-24 22:34:59 +00:00
Merge branch 'main' into sitter-map
This commit is contained in:
commit
fa6ae80653
13 changed files with 163 additions and 42 deletions
|
@ -183,7 +183,7 @@ class Coder:
|
||||||
for fname in self.get_inchat_relative_files():
|
for fname in self.get_inchat_relative_files():
|
||||||
self.io.tool_output(f"Added {fname} to the chat.")
|
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.summarizer_thread = None
|
||||||
self.summarized_done_messages = None
|
self.summarized_done_messages = None
|
||||||
|
|
||||||
|
@ -629,7 +629,7 @@ class Coder:
|
||||||
if len(chunk.choices) == 0:
|
if len(chunk.choices) == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if chunk.choices[0].finish_reason == "length":
|
if hasattr(chunk.choices[0], "finish_reason") and chunk.choices[0].finish_reason == "length":
|
||||||
raise ExhaustedContextWindow()
|
raise ExhaustedContextWindow()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -5,7 +5,6 @@ import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import git
|
import git
|
||||||
import tiktoken
|
|
||||||
from prompt_toolkit.completion import Completion
|
from prompt_toolkit.completion import Completion
|
||||||
|
|
||||||
from aider import prompts, voice
|
from aider import prompts, voice
|
||||||
|
@ -24,7 +23,7 @@ class Commands:
|
||||||
voice_language = None
|
voice_language = None
|
||||||
|
|
||||||
self.voice_language = voice_language
|
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):
|
def is_command(self, inp):
|
||||||
if inp[0] == "/":
|
if inp[0] == "/":
|
||||||
|
|
|
@ -9,9 +9,10 @@ from aider.sendchat import simple_send_with_retries
|
||||||
|
|
||||||
|
|
||||||
class ChatSummary:
|
class ChatSummary:
|
||||||
def __init__(self, model=models.GPT35.name, max_tokens=1024):
|
def __init__(self, model=models.Model.weak_model(), max_tokens=1024):
|
||||||
self.tokenizer = tiktoken.encoding_for_model(model)
|
self.tokenizer = model.tokenizer
|
||||||
self.max_tokens = max_tokens
|
self.max_tokens = max_tokens
|
||||||
|
self.model = model
|
||||||
|
|
||||||
def too_big(self, messages):
|
def too_big(self, messages):
|
||||||
sized = self.tokenize(messages)
|
sized = self.tokenize(messages)
|
||||||
|
@ -85,7 +86,7 @@ class ChatSummary:
|
||||||
dict(role="user", content=content),
|
dict(role="user", content=content),
|
||||||
]
|
]
|
||||||
|
|
||||||
summary = simple_send_with_retries(model=models.GPT35.name, messages=messages)
|
summary = simple_send_with_retries(self.model.name, messages)
|
||||||
summary = prompts.summary_prefix + summary
|
summary = prompts.summary_prefix + summary
|
||||||
|
|
||||||
return [dict(role="user", content=summary)]
|
return [dict(role="user", content=summary)]
|
||||||
|
@ -123,7 +124,7 @@ def main():
|
||||||
|
|
||||||
assistant.append(line)
|
assistant.append(line)
|
||||||
|
|
||||||
summarizer = ChatSummary(models.GPT35.name)
|
summarizer = ChatSummary(models.Model.weak_model())
|
||||||
summary = summarizer.summarize(messages[-40:])
|
summary = summarizer.summarize(messages[-40:])
|
||||||
dump(summary)
|
dump(summary)
|
||||||
|
|
||||||
|
|
|
@ -449,8 +449,6 @@ def main(argv=None, input=None, output=None, force_git_root=None):
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
main_model = models.Model(args.model)
|
|
||||||
|
|
||||||
openai.api_key = args.openai_api_key
|
openai.api_key = args.openai_api_key
|
||||||
for attr in ("base", "type", "version", "deployment_id", "engine"):
|
for attr in ("base", "type", "version", "deployment_id", "engine"):
|
||||||
arg_key = f"openai_api_{attr}"
|
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)
|
setattr(openai, mod_key, val)
|
||||||
io.tool_output(f"Setting openai.{mod_key}={val}")
|
io.tool_output(f"Setting openai.{mod_key}={val}")
|
||||||
|
|
||||||
|
main_model = models.Model.create(args.model)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
coder = Coder.create(
|
coder = Coder.create(
|
||||||
main_model,
|
main_model,
|
||||||
|
|
7
aider/models/__init__.py
Normal file
7
aider/models/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from .openai import OpenAIModel
|
||||||
|
from .openrouter import OpenRouterModel
|
||||||
|
from .model import Model
|
||||||
|
|
||||||
|
GPT4 = Model.create('gpt-4')
|
||||||
|
GPT35 = Model.create('gpt-3.5-turbo')
|
||||||
|
GPT35_16k = Model.create('gpt-3.5-turbo-16k')
|
36
aider/models/model.py
Normal file
36
aider/models/model.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import openai
|
||||||
|
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
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, name):
|
||||||
|
from .openai import OpenAIModel
|
||||||
|
from .openrouter import OpenRouterModel
|
||||||
|
if ("openrouter.ai" in openai.api_base):
|
||||||
|
return OpenRouterModel(name)
|
||||||
|
return OpenAIModel(name)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def strong_model():
|
||||||
|
return Model.create('gpt-4')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def weak_model():
|
||||||
|
return Model.create('gpt-3.5-turbo')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def commit_message_models():
|
||||||
|
return [Model.create('gpt-3.5-turbo'), Model.create('gpt-3.5-turbo-16k')]
|
|
@ -1,4 +1,6 @@
|
||||||
|
import tiktoken
|
||||||
import re
|
import re
|
||||||
|
from .model import Model
|
||||||
|
|
||||||
known_tokens = {
|
known_tokens = {
|
||||||
"gpt-3.5-turbo": 4,
|
"gpt-3.5-turbo": 4,
|
||||||
|
@ -6,14 +8,7 @@ known_tokens = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Model:
|
class OpenAIModel(Model):
|
||||||
always_available = False
|
|
||||||
use_repo_map = False
|
|
||||||
send_undo_reply = False
|
|
||||||
|
|
||||||
prompt_price = None
|
|
||||||
completion_price = None
|
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
@ -31,6 +26,7 @@ class Model:
|
||||||
raise ValueError(f"Unknown context window size for model: {name}")
|
raise ValueError(f"Unknown context window size for model: {name}")
|
||||||
|
|
||||||
self.max_context_tokens = tokens * 1024
|
self.max_context_tokens = tokens * 1024
|
||||||
|
self.tokenizer = tiktoken.encoding_for_model(name)
|
||||||
|
|
||||||
if self.is_gpt4():
|
if self.is_gpt4():
|
||||||
self.edit_format = "diff"
|
self.edit_format = "diff"
|
||||||
|
@ -66,11 +62,3 @@ class Model:
|
||||||
|
|
||||||
def is_gpt35(self):
|
def is_gpt35(self):
|
||||||
return self.name.startswith("gpt-3.5-turbo")
|
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")
|
|
43
aider/models/openrouter.py
Normal file
43
aider/models/openrouter.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import openai
|
||||||
|
import tiktoken
|
||||||
|
from .model import Model
|
||||||
|
|
||||||
|
cached_model_details = None
|
||||||
|
|
||||||
|
|
||||||
|
class OpenRouterModel(Model):
|
||||||
|
def __init__(self, name):
|
||||||
|
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 = 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")
|
||||||
|
|
||||||
|
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.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}')
|
||||||
|
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
return "whole"
|
|
@ -109,8 +109,8 @@ class GitRepo:
|
||||||
dict(role="user", content=content),
|
dict(role="user", content=content),
|
||||||
]
|
]
|
||||||
|
|
||||||
for model in [models.GPT35.name, models.GPT35_16k.name]:
|
for model in models.Model.commit_message_models():
|
||||||
commit_message = simple_send_with_retries(model, messages)
|
commit_message = simple_send_with_retries(model.name, messages)
|
||||||
if commit_message:
|
if commit_message:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ from collections import Counter, defaultdict, namedtuple
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
import tiktoken
|
|
||||||
from diskcache import Cache
|
from diskcache import Cache
|
||||||
from grep_ast import TreeContext
|
from grep_ast import TreeContext
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
@ -87,7 +86,7 @@ class RepoMap:
|
||||||
self,
|
self,
|
||||||
map_tokens=1024,
|
map_tokens=1024,
|
||||||
root=None,
|
root=None,
|
||||||
main_model=models.GPT4,
|
main_model=models.Model.strong_model(),
|
||||||
io=None,
|
io=None,
|
||||||
repo_content_prefix=None,
|
repo_content_prefix=None,
|
||||||
verbose=False,
|
verbose=False,
|
||||||
|
@ -103,7 +102,7 @@ class RepoMap:
|
||||||
|
|
||||||
self.max_map_tokens = map_tokens
|
self.max_map_tokens = map_tokens
|
||||||
|
|
||||||
self.tokenizer = tiktoken.encoding_for_model(main_model.name)
|
self.tokenizer = main_model.tokenizer
|
||||||
self.repo_content_prefix = repo_content_prefix
|
self.repo_content_prefix = repo_content_prefix
|
||||||
|
|
||||||
def get_repo_map(self, chat_files, other_files):
|
def get_repo_map(self, chat_files, other_files):
|
||||||
|
|
|
@ -34,9 +34,9 @@ CACHE = None
|
||||||
f"{details.get('exception','Exception')}\nRetry in {details['wait']:.1f} seconds."
|
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(
|
kwargs = dict(
|
||||||
model=model,
|
model=model_name,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
temperature=0,
|
temperature=0,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
|
@ -50,6 +50,12 @@ def send_with_retries(model, messages, functions, stream):
|
||||||
if hasattr(openai, "api_engine"):
|
if hasattr(openai, "api_engine"):
|
||||||
kwargs["engine"] = 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()
|
key = json.dumps(kwargs, sort_keys=True).encode()
|
||||||
|
|
||||||
# Generate SHA1 hash of kwargs and append it to chat_completion_call_hashes
|
# Generate SHA1 hash of kwargs and append it to chat_completion_call_hashes
|
||||||
|
@ -66,10 +72,10 @@ def send_with_retries(model, messages, functions, stream):
|
||||||
return hash_object, res
|
return hash_object, res
|
||||||
|
|
||||||
|
|
||||||
def simple_send_with_retries(model, messages):
|
def simple_send_with_retries(model_name, messages):
|
||||||
try:
|
try:
|
||||||
_hash, response = send_with_retries(
|
_hash, response = send_with_retries(
|
||||||
model=model,
|
model_name=model_name,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
functions=None,
|
functions=None,
|
||||||
stream=False,
|
stream=False,
|
||||||
|
|
14
docs/faq.md
14
docs/faq.md
|
@ -4,6 +4,7 @@
|
||||||
- [How does aider use git?](#how-does-aider-use-git)
|
- [How does aider use git?](#how-does-aider-use-git)
|
||||||
- [GPT-4 vs GPT-3.5](#gpt-4-vs-gpt-35)
|
- [GPT-4 vs GPT-3.5](#gpt-4-vs-gpt-35)
|
||||||
- [Aider isn't editing my files?](#aider-isnt-editing-my-files)
|
- [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 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 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)
|
- [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 `/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.
|
- 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?
|
## Can I use aider with other LLMs, local LLMs, etc?
|
||||||
|
|
||||||
Aider provides experimental support for LLMs other than OpenAI's GPT-3.5 and GPT-4. The support is currently only experimental for two reasons:
|
Aider provides experimental support for LLMs other than OpenAI's GPT-3.5 and GPT-4. The support is currently only experimental for two reasons:
|
||||||
|
|
|
@ -1,28 +1,56 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from aider.models import Model
|
from aider.models import Model, OpenRouterModel
|
||||||
|
|
||||||
|
|
||||||
class TestModels(unittest.TestCase):
|
class TestModels(unittest.TestCase):
|
||||||
def test_max_context_tokens(self):
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
self.assertEqual(model.max_context_tokens, 32 * 1024)
|
||||||
|
|
||||||
|
|
||||||
|
@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': [
|
||||||
|
{
|
||||||
|
'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)
|
||||||
|
openai.api_base = old_base
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue