mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-24 22:34:59 +00:00
merge in openai upgrade
This commit is contained in:
commit
fe9423d7b8
23 changed files with 304 additions and 201 deletions
|
@ -42,6 +42,7 @@ def wrap_fence(name):
|
||||||
IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.webp'}
|
IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.webp'}
|
||||||
|
|
||||||
class Coder:
|
class Coder:
|
||||||
|
client = None
|
||||||
abs_fnames = None
|
abs_fnames = None
|
||||||
repo = None
|
repo = None
|
||||||
last_aider_commit_hash = None
|
last_aider_commit_hash = None
|
||||||
|
@ -58,6 +59,7 @@ class Coder:
|
||||||
main_model=None,
|
main_model=None,
|
||||||
edit_format=None,
|
edit_format=None,
|
||||||
io=None,
|
io=None,
|
||||||
|
client=None,
|
||||||
skip_model_availabily_check=False,
|
skip_model_availabily_check=False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
@ -67,26 +69,28 @@ class Coder:
|
||||||
main_model = models.GPT4
|
main_model = models.GPT4
|
||||||
|
|
||||||
if not skip_model_availabily_check and not main_model.always_available:
|
if not skip_model_availabily_check and not main_model.always_available:
|
||||||
if not check_model_availability(io, main_model):
|
if not check_model_availability(io, client, main_model):
|
||||||
|
fallback_model = models.GPT35_1106
|
||||||
if main_model != models.GPT4:
|
if main_model != models.GPT4:
|
||||||
io.tool_error(
|
io.tool_error(
|
||||||
f"API key does not support {main_model.name}, falling back to"
|
f"API key does not support {main_model.name}, falling back to"
|
||||||
f" {models.GPT35_16k.name}"
|
f" {fallback_model.name}"
|
||||||
)
|
)
|
||||||
main_model = models.GPT35_16k
|
main_model = fallback_model
|
||||||
|
|
||||||
if edit_format is None:
|
if edit_format is None:
|
||||||
edit_format = main_model.edit_format
|
edit_format = main_model.edit_format
|
||||||
|
|
||||||
if edit_format == "diff":
|
if edit_format == "diff":
|
||||||
return EditBlockCoder(main_model, io, **kwargs)
|
return EditBlockCoder(client, main_model, io, **kwargs)
|
||||||
elif edit_format == "whole":
|
elif edit_format == "whole":
|
||||||
return WholeFileCoder(main_model, io, **kwargs)
|
return WholeFileCoder(client, main_model, io, **kwargs)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown edit format {edit_format}")
|
raise ValueError(f"Unknown edit format {edit_format}")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
client,
|
||||||
main_model,
|
main_model,
|
||||||
io,
|
io,
|
||||||
fnames=None,
|
fnames=None,
|
||||||
|
@ -105,6 +109,8 @@ class Coder:
|
||||||
voice_language=None,
|
voice_language=None,
|
||||||
aider_ignore_file=None,
|
aider_ignore_file=None,
|
||||||
):
|
):
|
||||||
|
self.client = client
|
||||||
|
|
||||||
if not fnames:
|
if not fnames:
|
||||||
fnames = []
|
fnames = []
|
||||||
|
|
||||||
|
@ -161,7 +167,9 @@ class Coder:
|
||||||
|
|
||||||
if use_git:
|
if use_git:
|
||||||
try:
|
try:
|
||||||
self.repo = GitRepo(self.io, fnames, git_dname, aider_ignore_file)
|
self.repo = GitRepo(
|
||||||
|
self.io, fnames, git_dname, aider_ignore_file, client=self.client
|
||||||
|
)
|
||||||
self.root = self.repo.root
|
self.root = self.repo.root
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
self.repo = None
|
self.repo = None
|
||||||
|
@ -192,6 +200,7 @@ class Coder:
|
||||||
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(
|
||||||
|
self.client,
|
||||||
models.Model.weak_model(),
|
models.Model.weak_model(),
|
||||||
self.main_model.max_chat_history_tokens,
|
self.main_model.max_chat_history_tokens,
|
||||||
)
|
)
|
||||||
|
@ -305,6 +314,13 @@ class Coder:
|
||||||
|
|
||||||
def get_files_messages(self):
|
def get_files_messages(self):
|
||||||
all_content = ""
|
all_content = ""
|
||||||
|
|
||||||
|
repo_content = self.get_repo_map()
|
||||||
|
if repo_content:
|
||||||
|
if all_content:
|
||||||
|
all_content += "\n"
|
||||||
|
all_content += repo_content
|
||||||
|
|
||||||
if self.abs_fnames:
|
if self.abs_fnames:
|
||||||
files_content = self.gpt_prompts.files_content_prefix
|
files_content = self.gpt_prompts.files_content_prefix
|
||||||
files_content += self.get_files_content()
|
files_content += self.get_files_content()
|
||||||
|
@ -313,12 +329,6 @@ class Coder:
|
||||||
|
|
||||||
all_content += files_content
|
all_content += files_content
|
||||||
|
|
||||||
repo_content = self.get_repo_map()
|
|
||||||
if repo_content:
|
|
||||||
if all_content:
|
|
||||||
all_content += "\n"
|
|
||||||
all_content += repo_content
|
|
||||||
|
|
||||||
files_messages = [
|
files_messages = [
|
||||||
dict(role="user", content=all_content),
|
dict(role="user", content=all_content),
|
||||||
dict(role="assistant", content="Ok."),
|
dict(role="assistant", content="Ok."),
|
||||||
|
@ -500,7 +510,7 @@ class Coder:
|
||||||
interrupted = self.send(messages, functions=self.functions)
|
interrupted = self.send(messages, functions=self.functions)
|
||||||
except ExhaustedContextWindow:
|
except ExhaustedContextWindow:
|
||||||
exhausted = True
|
exhausted = True
|
||||||
except openai.error.InvalidRequestError as err:
|
except openai.BadRequestError as err:
|
||||||
if "maximum context length" in str(err):
|
if "maximum context length" in str(err):
|
||||||
exhausted = True
|
exhausted = True
|
||||||
else:
|
else:
|
||||||
|
@ -617,7 +627,9 @@ class Coder:
|
||||||
|
|
||||||
interrupted = False
|
interrupted = False
|
||||||
try:
|
try:
|
||||||
hash_object, completion = send_with_retries(model, messages, functions, self.stream)
|
hash_object, completion = send_with_retries(
|
||||||
|
self.client, model, messages, functions, self.stream
|
||||||
|
)
|
||||||
self.chat_completion_call_hashes.append(hash_object.hexdigest())
|
self.chat_completion_call_hashes.append(hash_object.hexdigest())
|
||||||
|
|
||||||
if self.stream:
|
if self.stream:
|
||||||
|
@ -971,9 +983,16 @@ class Coder:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def check_model_availability(io, main_model):
|
def check_model_availability(io, client, main_model):
|
||||||
available_models = openai.Model.list()
|
try:
|
||||||
model_ids = sorted(model.id for model in available_models["data"])
|
available_models = client.models.list()
|
||||||
|
except openai.NotFoundError:
|
||||||
|
# Azure sometimes returns 404?
|
||||||
|
# https://discord.com/channels/1131200896827654144/1182327371232186459
|
||||||
|
io.tool_error("Unable to list available models, proceeding with {main_model.name}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
model_ids = sorted(model.id for model in available_models)
|
||||||
if main_model.name in model_ids:
|
if main_model.name in model_ids:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -182,7 +182,7 @@ If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
||||||
|
|
||||||
files_no_full_files = "I am not sharing any *read-write* files yet."
|
files_no_full_files = "I am not sharing any *read-write* files yet."
|
||||||
|
|
||||||
repo_content_prefix = """Below here are summaries of other files present in this git repository.
|
repo_content_prefix = """Below here are summaries of files present in the user's git repository.
|
||||||
Do not propose changes to these files, they are *read-only*.
|
Do not propose changes to these files, they are *read-only*.
|
||||||
To make a file *read-write*, ask the user to *add it to the chat*.
|
To make a file *read-write*, ask the user to *add it to the chat*.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -471,7 +471,7 @@ class Commands:
|
||||||
|
|
||||||
if not self.voice:
|
if not self.voice:
|
||||||
try:
|
try:
|
||||||
self.voice = voice.Voice()
|
self.voice = voice.Voice(self.coder.client)
|
||||||
except voice.SoundDeviceError:
|
except voice.SoundDeviceError:
|
||||||
self.io.tool_error(
|
self.io.tool_error(
|
||||||
"Unable to import `sounddevice` and/or `soundfile`, is portaudio installed?"
|
"Unable to import `sounddevice` and/or `soundfile`, is portaudio installed?"
|
||||||
|
|
|
@ -7,7 +7,8 @@ from aider.sendchat import simple_send_with_retries
|
||||||
|
|
||||||
|
|
||||||
class ChatSummary:
|
class ChatSummary:
|
||||||
def __init__(self, model=models.Model.weak_model(), max_tokens=1024):
|
def __init__(self, client, model=models.Model.weak_model(), max_tokens=1024):
|
||||||
|
self.client = client
|
||||||
self.tokenizer = model.tokenizer
|
self.tokenizer = model.tokenizer
|
||||||
self.max_tokens = max_tokens
|
self.max_tokens = max_tokens
|
||||||
self.model = model
|
self.model = model
|
||||||
|
@ -84,7 +85,7 @@ class ChatSummary:
|
||||||
dict(role="user", content=content),
|
dict(role="user", content=content),
|
||||||
]
|
]
|
||||||
|
|
||||||
summary = simple_send_with_retries(self.model.name, messages)
|
summary = simple_send_with_retries(self.client, self.model.name, messages)
|
||||||
if summary is None:
|
if summary is None:
|
||||||
raise ValueError(f"summarizer unexpectedly failed for {self.model.name}")
|
raise ValueError(f"summarizer unexpectedly failed for {self.model.name}")
|
||||||
summary = prompts.summary_prefix + summary
|
summary = prompts.summary_prefix + summary
|
||||||
|
|
|
@ -157,12 +157,13 @@ def main(argv=None, input=None, output=None, force_git_root=None):
|
||||||
default=False,
|
default=False,
|
||||||
help="Override to skip model availability check (default: False)",
|
help="Override to skip model availability check (default: False)",
|
||||||
)
|
)
|
||||||
|
default_3_model = models.GPT35_1106
|
||||||
core_group.add_argument(
|
core_group.add_argument(
|
||||||
"-3",
|
"-3",
|
||||||
action="store_const",
|
action="store_const",
|
||||||
dest="model",
|
dest="model",
|
||||||
const=models.GPT35_16k.name,
|
const=default_3_model.name,
|
||||||
help=f"Use {models.GPT35_16k.name} model for the main chat (gpt-4 is better)",
|
help=f"Use {default_3_model.name} model for the main chat (gpt-4 is better)",
|
||||||
)
|
)
|
||||||
core_group.add_argument(
|
core_group.add_argument(
|
||||||
"--voice-language",
|
"--voice-language",
|
||||||
|
@ -176,27 +177,22 @@ def main(argv=None, input=None, output=None, force_git_root=None):
|
||||||
model_group.add_argument(
|
model_group.add_argument(
|
||||||
"--openai-api-base",
|
"--openai-api-base",
|
||||||
metavar="OPENAI_API_BASE",
|
metavar="OPENAI_API_BASE",
|
||||||
help="Specify the openai.api_base (default: https://api.openai.com/v1)",
|
help="Specify the api base url",
|
||||||
)
|
)
|
||||||
model_group.add_argument(
|
model_group.add_argument(
|
||||||
"--openai-api-type",
|
"--openai-api-type",
|
||||||
metavar="OPENAI_API_TYPE",
|
metavar="OPENAI_API_TYPE",
|
||||||
help="Specify the openai.api_type",
|
help="Specify the api_type",
|
||||||
)
|
)
|
||||||
model_group.add_argument(
|
model_group.add_argument(
|
||||||
"--openai-api-version",
|
"--openai-api-version",
|
||||||
metavar="OPENAI_API_VERSION",
|
metavar="OPENAI_API_VERSION",
|
||||||
help="Specify the openai.api_version",
|
help="Specify the api_version",
|
||||||
)
|
)
|
||||||
model_group.add_argument(
|
model_group.add_argument(
|
||||||
"--openai-api-deployment-id",
|
"--openai-api-deployment-id",
|
||||||
metavar="OPENAI_API_DEPLOYMENT_ID",
|
metavar="OPENAI_API_DEPLOYMENT_ID",
|
||||||
help="Specify the deployment_id arg to be passed to openai.ChatCompletion.create()",
|
help="Specify the deployment_id",
|
||||||
)
|
|
||||||
model_group.add_argument(
|
|
||||||
"--openai-api-engine",
|
|
||||||
metavar="OPENAI_API_ENGINE",
|
|
||||||
help="Specify the engine arg to be passed to openai.ChatCompletion.create()",
|
|
||||||
)
|
)
|
||||||
model_group.add_argument(
|
model_group.add_argument(
|
||||||
"--edit-format",
|
"--edit-format",
|
||||||
|
@ -380,6 +376,12 @@ def main(argv=None, input=None, output=None, force_git_root=None):
|
||||||
metavar="COMMAND",
|
metavar="COMMAND",
|
||||||
help="Specify a single message to send GPT, process reply then exit (disables chat mode)",
|
help="Specify a single message to send GPT, process reply then exit (disables chat mode)",
|
||||||
)
|
)
|
||||||
|
other_group.add_argument(
|
||||||
|
"--message-file",
|
||||||
|
"-f",
|
||||||
|
metavar="MESSAGE_FILE",
|
||||||
|
help="Specify a file containing the message to send GPT, process reply, then exit (disables chat mode)",
|
||||||
|
)
|
||||||
other_group.add_argument(
|
other_group.add_argument(
|
||||||
"--encoding",
|
"--encoding",
|
||||||
default="utf-8",
|
default="utf-8",
|
||||||
|
@ -492,23 +494,34 @@ def main(argv=None, input=None, output=None, force_git_root=None):
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
openai.api_key = args.openai_api_key
|
if args.openai_api_type == "azure":
|
||||||
for attr in ("base", "type", "version", "deployment_id", "engine"):
|
client = openai.AzureOpenAI(
|
||||||
arg_key = f"openai_api_{attr}"
|
api_key=args.openai_api_key,
|
||||||
val = getattr(args, arg_key)
|
azure_endpoint=args.openai_api_base,
|
||||||
if val is not None:
|
api_version=args.openai_api_version,
|
||||||
mod_key = f"api_{attr}"
|
azure_deployment=args.openai_api_deployment_id,
|
||||||
setattr(openai, mod_key, val)
|
)
|
||||||
io.tool_output(f"Setting openai.{mod_key}={val}")
|
else:
|
||||||
|
kwargs = dict()
|
||||||
|
if args.openai_api_base:
|
||||||
|
kwargs["base_url"] = args.openai_api_base
|
||||||
|
if "openrouter.ai" in args.openai_api_base:
|
||||||
|
kwargs["default_headers"] = {
|
||||||
|
"HTTP-Referer": "http://aider.chat",
|
||||||
|
"X-Title": "Aider",
|
||||||
|
}
|
||||||
|
|
||||||
main_model = models.Model.create(args.model)
|
client = openai.OpenAI(api_key=args.openai_api_key, **kwargs)
|
||||||
|
|
||||||
|
main_model = models.Model.create(args.model, client)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
coder = Coder.create(
|
coder = Coder.create(
|
||||||
main_model,
|
main_model=main_model,
|
||||||
args.edit_format,
|
edit_format=args.edit_format,
|
||||||
io,
|
io=io,
|
||||||
args.skip_model_availability_check,
|
skip_model_availabily_check=args.skip_model_availability_check,
|
||||||
|
client=client,
|
||||||
##
|
##
|
||||||
fnames=fnames,
|
fnames=fnames,
|
||||||
git_dname=git_dname,
|
git_dname=git_dname,
|
||||||
|
@ -560,8 +573,20 @@ def main(argv=None, input=None, output=None, force_git_root=None):
|
||||||
io.tool_error(f"Git working dir: {git_root}")
|
io.tool_error(f"Git working dir: {git_root}")
|
||||||
|
|
||||||
if args.message:
|
if args.message:
|
||||||
|
io.add_to_input_history(args.message)
|
||||||
io.tool_output()
|
io.tool_output()
|
||||||
coder.run(with_message=args.message)
|
coder.run(with_message=args.message)
|
||||||
|
elif args.message_file:
|
||||||
|
try:
|
||||||
|
message_from_file = io.read_text(args.message_file)
|
||||||
|
io.tool_output()
|
||||||
|
coder.run(with_message=message_from_file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
io.tool_error(f"Message file not found: {args.message_file}")
|
||||||
|
return 1
|
||||||
|
except IOError as e:
|
||||||
|
io.tool_error(f"Error reading message file: {e}")
|
||||||
|
return 1
|
||||||
else:
|
else:
|
||||||
coder.run()
|
coder.run()
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ from .openrouter import OpenRouterModel
|
||||||
|
|
||||||
GPT4 = Model.create("gpt-4")
|
GPT4 = Model.create("gpt-4")
|
||||||
GPT35 = Model.create("gpt-3.5-turbo")
|
GPT35 = Model.create("gpt-3.5-turbo")
|
||||||
|
GPT35_1106 = Model.create("gpt-3.5-turbo-1106")
|
||||||
GPT35_16k = Model.create("gpt-3.5-turbo-16k")
|
GPT35_16k = Model.create("gpt-3.5-turbo-16k")
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
|
|
||||||
import openai
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
class Model:
|
class Model:
|
||||||
name = None
|
name = None
|
||||||
edit_format = None
|
edit_format = None
|
||||||
|
@ -20,12 +18,12 @@ class Model:
|
||||||
completion_price = None
|
completion_price = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, name):
|
def create(cls, name, client=None):
|
||||||
from .openai import OpenAIModel
|
from .openai import OpenAIModel
|
||||||
from .openrouter import OpenRouterModel
|
from .openrouter import OpenRouterModel
|
||||||
|
|
||||||
if "openrouter.ai" in openai.api_base:
|
if client and client.base_url.host == "openrouter.ai":
|
||||||
return OpenRouterModel(name)
|
return OpenRouterModel(client, name)
|
||||||
return OpenAIModel(name)
|
return OpenAIModel(name)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -37,11 +35,11 @@ class Model:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def weak_model():
|
def weak_model():
|
||||||
return Model.create("gpt-3.5-turbo")
|
return Model.create("gpt-3.5-turbo-1106")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def commit_message_models():
|
def commit_message_models():
|
||||||
return [Model.create("gpt-3.5-turbo"), Model.create("gpt-3.5-turbo-16k")]
|
return [Model.weak_model()]
|
||||||
|
|
||||||
def token_count(self, messages):
|
def token_count(self, messages):
|
||||||
if not self.tokenizer:
|
if not self.tokenizer:
|
||||||
|
@ -61,8 +59,6 @@ class Model:
|
||||||
:param fname: The filename of the image.
|
:param fname: The filename of the image.
|
||||||
:return: The token cost for the image.
|
:return: The token cost for the image.
|
||||||
"""
|
"""
|
||||||
# Placeholder for image size retrieval logic
|
|
||||||
# TODO: Implement the logic to retrieve the image size from the file
|
|
||||||
width, height = self.get_image_size(fname)
|
width, height = self.get_image_size(fname)
|
||||||
|
|
||||||
# If the image is larger than 2048 in any dimension, scale it down to fit within 2048x2048
|
# If the image is larger than 2048 in any dimension, scale it down to fit within 2048x2048
|
||||||
|
|
|
@ -55,6 +55,7 @@ class OpenAIModel(Model):
|
||||||
if self.is_gpt35():
|
if self.is_gpt35():
|
||||||
self.edit_format = "whole"
|
self.edit_format = "whole"
|
||||||
self.always_available = True
|
self.always_available = True
|
||||||
|
self.send_undo_reply = False
|
||||||
|
|
||||||
if self.name == "gpt-3.5-turbo-1106":
|
if self.name == "gpt-3.5-turbo-1106":
|
||||||
self.prompt_price = 0.001
|
self.prompt_price = 0.001
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import openai
|
|
||||||
import tiktoken
|
import tiktoken
|
||||||
|
|
||||||
from .model import Model
|
from .model import Model
|
||||||
|
@ -7,13 +6,9 @@ cached_model_details = None
|
||||||
|
|
||||||
|
|
||||||
class OpenRouterModel(Model):
|
class OpenRouterModel(Model):
|
||||||
def __init__(self, name):
|
def __init__(self, client, name):
|
||||||
if name == "gpt-4":
|
if name.startswith("gpt-4") or name.startswith("gpt-3.5-turbo"):
|
||||||
name = "openai/gpt-4"
|
name = "openai/" + name
|
||||||
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.name = name
|
||||||
self.edit_format = edit_format_for_model(name)
|
self.edit_format = edit_format_for_model(name)
|
||||||
|
@ -24,7 +19,7 @@ class OpenRouterModel(Model):
|
||||||
|
|
||||||
global cached_model_details
|
global cached_model_details
|
||||||
if cached_model_details is None:
|
if cached_model_details is None:
|
||||||
cached_model_details = openai.Model.list().data
|
cached_model_details = client.models.list().data
|
||||||
found = next(
|
found = next(
|
||||||
(details for details in cached_model_details if details.get("id") == name), None
|
(details for details in cached_model_details if details.get("id") == name), None
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,7 +16,8 @@ class GitRepo:
|
||||||
aider_ignore_spec = None
|
aider_ignore_spec = None
|
||||||
aider_ignore_ts = 0
|
aider_ignore_ts = 0
|
||||||
|
|
||||||
def __init__(self, io, fnames, git_dname, aider_ignore_file=None):
|
def __init__(self, io, fnames, git_dname, aider_ignore_file=None, client=None):
|
||||||
|
self.client = client
|
||||||
self.io = io
|
self.io = io
|
||||||
|
|
||||||
if git_dname:
|
if git_dname:
|
||||||
|
@ -102,9 +103,7 @@ class GitRepo:
|
||||||
|
|
||||||
def get_commit_message(self, diffs, context):
|
def get_commit_message(self, diffs, context):
|
||||||
if len(diffs) >= 4 * 1024 * 4:
|
if len(diffs) >= 4 * 1024 * 4:
|
||||||
self.io.tool_error(
|
self.io.tool_error("Diff is too large to generate a commit message.")
|
||||||
f"Diff is too large for {models.GPT35.name} to generate a commit message."
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
diffs = "# Diffs:\n" + diffs
|
diffs = "# Diffs:\n" + diffs
|
||||||
|
@ -120,7 +119,7 @@ class GitRepo:
|
||||||
]
|
]
|
||||||
|
|
||||||
for model in models.Model.commit_message_models():
|
for model in models.Model.commit_message_models():
|
||||||
commit_message = simple_send_with_retries(model.name, messages)
|
commit_message = simple_send_with_retries(self.client, model.name, messages)
|
||||||
if commit_message:
|
if commit_message:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,13 @@ import hashlib
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import backoff
|
import backoff
|
||||||
|
import httpx
|
||||||
import openai
|
import openai
|
||||||
import requests
|
|
||||||
|
|
||||||
# from diskcache import Cache
|
# from diskcache import Cache
|
||||||
from openai.error import (
|
from openai import APIConnectionError, InternalServerError, RateLimitError
|
||||||
APIConnectionError,
|
|
||||||
APIError,
|
from aider.dump import dump # noqa: F401
|
||||||
RateLimitError,
|
|
||||||
ServiceUnavailableError,
|
|
||||||
Timeout,
|
|
||||||
)
|
|
||||||
|
|
||||||
CACHE_PATH = "~/.aider.send.cache.v1"
|
CACHE_PATH = "~/.aider.send.cache.v1"
|
||||||
CACHE = None
|
CACHE = None
|
||||||
|
@ -22,19 +18,20 @@ CACHE = None
|
||||||
@backoff.on_exception(
|
@backoff.on_exception(
|
||||||
backoff.expo,
|
backoff.expo,
|
||||||
(
|
(
|
||||||
Timeout,
|
InternalServerError,
|
||||||
APIError,
|
|
||||||
ServiceUnavailableError,
|
|
||||||
RateLimitError,
|
RateLimitError,
|
||||||
APIConnectionError,
|
APIConnectionError,
|
||||||
requests.exceptions.ConnectionError,
|
httpx.ConnectError,
|
||||||
),
|
),
|
||||||
max_tries=10,
|
max_tries=10,
|
||||||
on_backoff=lambda details: print(
|
on_backoff=lambda details: print(
|
||||||
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_name, messages, functions, stream):
|
def send_with_retries(client, model_name, messages, functions, stream):
|
||||||
|
if not client:
|
||||||
|
raise ValueError("No openai client provided")
|
||||||
|
|
||||||
kwargs = dict(
|
kwargs = dict(
|
||||||
model=model_name,
|
model=model_name,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
|
@ -44,17 +41,8 @@ def send_with_retries(model_name, messages, functions, stream):
|
||||||
if functions is not None:
|
if functions is not None:
|
||||||
kwargs["functions"] = functions
|
kwargs["functions"] = functions
|
||||||
|
|
||||||
# we are abusing the openai object to stash these values
|
|
||||||
if hasattr(openai, "api_deployment_id"):
|
|
||||||
kwargs["deployment_id"] = openai.api_deployment_id
|
|
||||||
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"}
|
|
||||||
|
|
||||||
# Check conditions to switch to gpt-4-vision-preview
|
# Check conditions to switch to gpt-4-vision-preview
|
||||||
if "openrouter.ai" not in openai.api_base and model_name.startswith("gpt-4"):
|
if client and client.base_url.host != "openrouter.ai" and model_name.startswith("gpt-4"):
|
||||||
if any(isinstance(msg.get("content"), list) and any("image_url" in item for item in msg.get("content") if isinstance(item, dict)) for msg in messages):
|
if any(isinstance(msg.get("content"), list) and any("image_url" in item for item in msg.get("content") if isinstance(item, dict)) for msg in messages):
|
||||||
kwargs['model'] = "gpt-4-vision-preview"
|
kwargs['model'] = "gpt-4-vision-preview"
|
||||||
# looks like gpt-4-vision is limited to max tokens of 4096
|
# looks like gpt-4-vision is limited to max tokens of 4096
|
||||||
|
@ -68,7 +56,7 @@ def send_with_retries(model_name, messages, functions, stream):
|
||||||
if not stream and CACHE is not None and key in CACHE:
|
if not stream and CACHE is not None and key in CACHE:
|
||||||
return hash_object, CACHE[key]
|
return hash_object, CACHE[key]
|
||||||
|
|
||||||
res = openai.ChatCompletion.create(**kwargs)
|
res = client.chat.completions.create(**kwargs)
|
||||||
|
|
||||||
if not stream and CACHE is not None:
|
if not stream and CACHE is not None:
|
||||||
CACHE[key] = res
|
CACHE[key] = res
|
||||||
|
@ -76,14 +64,15 @@ def send_with_retries(model_name, messages, functions, stream):
|
||||||
return hash_object, res
|
return hash_object, res
|
||||||
|
|
||||||
|
|
||||||
def simple_send_with_retries(model_name, messages):
|
def simple_send_with_retries(client, model_name, messages):
|
||||||
try:
|
try:
|
||||||
_hash, response = send_with_retries(
|
_hash, response = send_with_retries(
|
||||||
|
client=client,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
functions=None,
|
functions=None,
|
||||||
stream=False,
|
stream=False,
|
||||||
)
|
)
|
||||||
return response.choices[0].message.content
|
return response.choices[0].message.content
|
||||||
except (AttributeError, openai.error.InvalidRequestError):
|
except (AttributeError, openai.BadRequestError):
|
||||||
return
|
return
|
||||||
|
|
|
@ -4,7 +4,6 @@ import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import openai
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import soundfile as sf
|
import soundfile as sf
|
||||||
|
@ -27,7 +26,7 @@ class Voice:
|
||||||
|
|
||||||
threshold = 0.15
|
threshold = 0.15
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, client):
|
||||||
if sf is None:
|
if sf is None:
|
||||||
raise SoundDeviceError
|
raise SoundDeviceError
|
||||||
try:
|
try:
|
||||||
|
@ -38,6 +37,8 @@ class Voice:
|
||||||
except (OSError, ModuleNotFoundError):
|
except (OSError, ModuleNotFoundError):
|
||||||
raise SoundDeviceError
|
raise SoundDeviceError
|
||||||
|
|
||||||
|
self.client = client
|
||||||
|
|
||||||
def callback(self, indata, frames, time, status):
|
def callback(self, indata, frames, time, status):
|
||||||
"""This is called (from a separate thread) for each audio block."""
|
"""This is called (from a separate thread) for each audio block."""
|
||||||
rms = np.sqrt(np.mean(indata**2))
|
rms = np.sqrt(np.mean(indata**2))
|
||||||
|
@ -88,9 +89,11 @@ class Voice:
|
||||||
file.write(self.q.get())
|
file.write(self.q.get())
|
||||||
|
|
||||||
with open(filename, "rb") as fh:
|
with open(filename, "rb") as fh:
|
||||||
transcript = openai.Audio.transcribe("whisper-1", fh, prompt=history, language=language)
|
transcript = self.client.audio.transcriptions.create(
|
||||||
|
model="whisper-1", file=fh, prompt=history, language=language
|
||||||
|
)
|
||||||
|
|
||||||
text = transcript["text"]
|
text = transcript.text
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ from aider.coders import Coder
|
||||||
from aider.dump import dump # noqa: F401
|
from aider.dump import dump # noqa: F401
|
||||||
from aider.io import InputOutput
|
from aider.io import InputOutput
|
||||||
|
|
||||||
BENCHMARK_DNAME = Path(os.environ["AIDER_BENCHMARK_DIR"])
|
BENCHMARK_DNAME = Path(os.environ.get("AIDER_BENCHMARK_DIR", "tmp.benchmarks"))
|
||||||
|
|
||||||
ORIGINAL_DNAME = BENCHMARK_DNAME / "exercism-python"
|
ORIGINAL_DNAME = BENCHMARK_DNAME / "exercism-python"
|
||||||
|
|
||||||
|
@ -631,12 +631,13 @@ def run_test(
|
||||||
show_fnames = ",".join(map(str, fnames))
|
show_fnames = ",".join(map(str, fnames))
|
||||||
print("fnames:", show_fnames)
|
print("fnames:", show_fnames)
|
||||||
|
|
||||||
openai.api_key = os.environ["OPENAI_API_KEY"]
|
client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
|
||||||
|
|
||||||
coder = Coder.create(
|
coder = Coder.create(
|
||||||
main_model,
|
main_model,
|
||||||
edit_format,
|
edit_format,
|
||||||
io,
|
io,
|
||||||
|
client=client,
|
||||||
fnames=fnames,
|
fnames=fnames,
|
||||||
use_git=False,
|
use_git=False,
|
||||||
stream=False,
|
stream=False,
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#
|
||||||
|
# pip-compile --output-file=dev-requirements.txt dev-requirements.in
|
||||||
|
#
|
||||||
pytest
|
pytest
|
||||||
pip-tools
|
pip-tools
|
||||||
lox
|
lox
|
||||||
|
@ -5,3 +8,4 @@ matplotlib
|
||||||
pandas
|
pandas
|
||||||
typer
|
typer
|
||||||
imgcat
|
imgcat
|
||||||
|
pre-commit
|
|
@ -10,8 +10,10 @@ babel==2.13.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
build==1.0.3
|
build==1.0.3
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
certifi==2023.7.22
|
certifi==2023.11.17
|
||||||
# via requests
|
# via requests
|
||||||
|
cfgv==3.4.0
|
||||||
|
# via pre-commit
|
||||||
charset-normalizer==3.3.2
|
charset-normalizer==3.3.2
|
||||||
# via requests
|
# via requests
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
|
@ -26,13 +28,19 @@ dill==0.3.7
|
||||||
# via
|
# via
|
||||||
# multiprocess
|
# multiprocess
|
||||||
# pathos
|
# pathos
|
||||||
docutils==0.18.1
|
distlib==0.3.7
|
||||||
|
# via virtualenv
|
||||||
|
docutils==0.20.1
|
||||||
# via
|
# via
|
||||||
# sphinx
|
# sphinx
|
||||||
# sphinx-rtd-theme
|
# sphinx-rtd-theme
|
||||||
fonttools==4.44.0
|
filelock==3.13.1
|
||||||
|
# via virtualenv
|
||||||
|
fonttools==4.46.0
|
||||||
# via matplotlib
|
# via matplotlib
|
||||||
idna==3.4
|
identify==2.5.32
|
||||||
|
# via pre-commit
|
||||||
|
idna==3.6
|
||||||
# via requests
|
# via requests
|
||||||
imagesize==1.4.1
|
imagesize==1.4.1
|
||||||
# via sphinx
|
# via sphinx
|
||||||
|
@ -48,11 +56,13 @@ lox==0.11.0
|
||||||
# via -r dev-requirements.in
|
# via -r dev-requirements.in
|
||||||
markupsafe==2.1.3
|
markupsafe==2.1.3
|
||||||
# via jinja2
|
# via jinja2
|
||||||
matplotlib==3.8.1
|
matplotlib==3.8.2
|
||||||
# via -r dev-requirements.in
|
# via -r dev-requirements.in
|
||||||
multiprocess==0.70.15
|
multiprocess==0.70.15
|
||||||
# via pathos
|
# via pathos
|
||||||
numpy==1.26.1
|
nodeenv==1.8.0
|
||||||
|
# via pre-commit
|
||||||
|
numpy==1.26.2
|
||||||
# via
|
# via
|
||||||
# contourpy
|
# contourpy
|
||||||
# matplotlib
|
# matplotlib
|
||||||
|
@ -63,7 +73,7 @@ packaging==23.2
|
||||||
# matplotlib
|
# matplotlib
|
||||||
# pytest
|
# pytest
|
||||||
# sphinx
|
# sphinx
|
||||||
pandas==2.1.2
|
pandas==2.1.3
|
||||||
# via -r dev-requirements.in
|
# via -r dev-requirements.in
|
||||||
pathos==0.3.1
|
pathos==0.3.1
|
||||||
# via lox
|
# via lox
|
||||||
|
@ -71,13 +81,17 @@ pillow==10.1.0
|
||||||
# via matplotlib
|
# via matplotlib
|
||||||
pip-tools==7.3.0
|
pip-tools==7.3.0
|
||||||
# via -r dev-requirements.in
|
# via -r dev-requirements.in
|
||||||
|
platformdirs==4.1.0
|
||||||
|
# via virtualenv
|
||||||
pluggy==1.3.0
|
pluggy==1.3.0
|
||||||
# via pytest
|
# via pytest
|
||||||
pox==0.3.3
|
pox==0.3.3
|
||||||
# via pathos
|
# via pathos
|
||||||
ppft==1.7.6.7
|
ppft==1.7.6.7
|
||||||
# via pathos
|
# via pathos
|
||||||
pygments==2.16.1
|
pre-commit==3.5.0
|
||||||
|
# via -r dev-requirements.in
|
||||||
|
pygments==2.17.2
|
||||||
# via sphinx
|
# via sphinx
|
||||||
pyparsing==3.1.1
|
pyparsing==3.1.1
|
||||||
# via matplotlib
|
# via matplotlib
|
||||||
|
@ -91,6 +105,8 @@ python-dateutil==2.8.2
|
||||||
# pandas
|
# pandas
|
||||||
pytz==2023.3.post1
|
pytz==2023.3.post1
|
||||||
# via pandas
|
# via pandas
|
||||||
|
pyyaml==6.0.1
|
||||||
|
# via pre-commit
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
# via sphinx
|
# via sphinx
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
|
@ -106,7 +122,7 @@ sphinx==7.2.6
|
||||||
# sphinxcontrib-jquery
|
# sphinxcontrib-jquery
|
||||||
# sphinxcontrib-qthelp
|
# sphinxcontrib-qthelp
|
||||||
# sphinxcontrib-serializinghtml
|
# sphinxcontrib-serializinghtml
|
||||||
sphinx-rtd-theme==1.3.0
|
sphinx-rtd-theme==2.0.0
|
||||||
# via lox
|
# via lox
|
||||||
sphinxcontrib-applehelp==1.0.7
|
sphinxcontrib-applehelp==1.0.7
|
||||||
# via sphinx
|
# via sphinx
|
||||||
|
@ -128,9 +144,11 @@ typing-extensions==4.8.0
|
||||||
# via typer
|
# via typer
|
||||||
tzdata==2023.3
|
tzdata==2023.3
|
||||||
# via pandas
|
# via pandas
|
||||||
urllib3==2.0.7
|
urllib3==2.1.0
|
||||||
# via requests
|
# via requests
|
||||||
wheel==0.41.3
|
virtualenv==20.25.0
|
||||||
|
# via pre-commit
|
||||||
|
wheel==0.42.0
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
|
|
||||||
# The following packages are considered to be unsafe in a requirements file:
|
# The following packages are considered to be unsafe in a requirements file:
|
||||||
|
|
|
@ -274,13 +274,17 @@ done
|
||||||
You can also script aider from python:
|
You can also script aider from python:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import openai
|
||||||
from aider.coders import Coder
|
from aider.coders import Coder
|
||||||
|
|
||||||
|
# Make an openai client
|
||||||
|
client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
|
||||||
|
|
||||||
# This is a list of files to add to the chat
|
# This is a list of files to add to the chat
|
||||||
fnames = ["foo.py"]
|
fnames = ["foo.py"]
|
||||||
|
|
||||||
# Create a coder object
|
# Create a coder object
|
||||||
coder = Coder.create(fnames=fnames)
|
coder = Coder.create(client=client, fnames=fnames)
|
||||||
|
|
||||||
# This will execute one instruction on those files and then return
|
# This will execute one instruction on those files and then return
|
||||||
coder.run("make a script that prints hello world")
|
coder.run("make a script that prints hello world")
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
#
|
||||||
|
# pip-compile requirements.in
|
||||||
|
#
|
||||||
configargparse
|
configargparse
|
||||||
GitPython
|
GitPython
|
||||||
openai
|
openai
|
||||||
|
|
|
@ -4,66 +4,67 @@
|
||||||
#
|
#
|
||||||
# pip-compile requirements.in
|
# pip-compile requirements.in
|
||||||
#
|
#
|
||||||
aiohttp==3.8.6
|
annotated-types==0.6.0
|
||||||
# via openai
|
# via pydantic
|
||||||
aiosignal==1.3.1
|
anyio==3.7.1
|
||||||
# via aiohttp
|
# via
|
||||||
async-timeout==4.0.3
|
# httpx
|
||||||
# via aiohttp
|
# openai
|
||||||
attrs==23.1.0
|
attrs==23.1.0
|
||||||
# via
|
# via
|
||||||
# aiohttp
|
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
backoff==2.2.1
|
backoff==2.2.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
certifi==2023.7.22
|
certifi==2023.11.17
|
||||||
# via requests
|
# via
|
||||||
|
# httpcore
|
||||||
|
# httpx
|
||||||
|
# requests
|
||||||
cffi==1.16.0
|
cffi==1.16.0
|
||||||
# via
|
# via
|
||||||
# sounddevice
|
# sounddevice
|
||||||
# soundfile
|
# soundfile
|
||||||
charset-normalizer==3.3.2
|
charset-normalizer==3.3.2
|
||||||
# via
|
# via requests
|
||||||
# aiohttp
|
|
||||||
# requests
|
|
||||||
configargparse==1.7
|
configargparse==1.7
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
diskcache==5.6.3
|
diskcache==5.6.3
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
frozenlist==1.4.0
|
distro==1.8.0
|
||||||
# via
|
# via openai
|
||||||
# aiohttp
|
|
||||||
# aiosignal
|
|
||||||
gitdb==4.0.11
|
gitdb==4.0.11
|
||||||
# via gitpython
|
# via gitpython
|
||||||
gitpython==3.1.40
|
gitpython==3.1.40
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
grep-ast==0.2.4
|
grep-ast==0.2.4
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
idna==3.4
|
h11==0.14.0
|
||||||
|
# via httpcore
|
||||||
|
httpcore==1.0.2
|
||||||
|
# via httpx
|
||||||
|
httpx==0.25.2
|
||||||
|
# via openai
|
||||||
|
idna==3.6
|
||||||
# via
|
# via
|
||||||
|
# anyio
|
||||||
|
# httpx
|
||||||
# requests
|
# requests
|
||||||
# yarl
|
jsonschema==4.20.0
|
||||||
jsonschema==4.19.2
|
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
jsonschema-specifications==2023.7.1
|
jsonschema-specifications==2023.11.2
|
||||||
# via jsonschema
|
# via jsonschema
|
||||||
markdown-it-py==3.0.0
|
markdown-it-py==3.0.0
|
||||||
# via rich
|
# via rich
|
||||||
mdurl==0.1.2
|
mdurl==0.1.2
|
||||||
# via markdown-it-py
|
# via markdown-it-py
|
||||||
multidict==6.0.4
|
|
||||||
# via
|
|
||||||
# aiohttp
|
|
||||||
# yarl
|
|
||||||
networkx==3.2.1
|
networkx==3.2.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
numpy==1.26.1
|
numpy==1.26.2
|
||||||
# via
|
# via
|
||||||
# -r requirements.in
|
# -r requirements.in
|
||||||
# scipy
|
# scipy
|
||||||
openai==0.28.1
|
openai==1.3.7
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
packaging==23.2
|
packaging==23.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
|
@ -73,49 +74,59 @@ pathspec==0.11.2
|
||||||
# grep-ast
|
# grep-ast
|
||||||
pillow==10.1.0
|
pillow==10.1.0
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
prompt-toolkit==3.0.39
|
prompt-toolkit==3.0.41
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
pycparser==2.21
|
pycparser==2.21
|
||||||
# via cffi
|
# via cffi
|
||||||
pygments==2.16.1
|
pydantic==2.5.2
|
||||||
|
# via openai
|
||||||
|
pydantic-core==2.14.5
|
||||||
|
# via pydantic
|
||||||
|
pygments==2.17.2
|
||||||
# via rich
|
# via rich
|
||||||
pyyaml==6.0.1
|
pyyaml==6.0.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
referencing==0.30.2
|
referencing==0.31.1
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# jsonschema-specifications
|
# jsonschema-specifications
|
||||||
regex==2023.10.3
|
regex==2023.10.3
|
||||||
# via tiktoken
|
# via tiktoken
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
# via
|
# via tiktoken
|
||||||
# openai
|
rich==13.7.0
|
||||||
# tiktoken
|
|
||||||
rich==13.6.0
|
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
rpds-py==0.10.6
|
rpds-py==0.13.2
|
||||||
# via
|
# via
|
||||||
# jsonschema
|
# jsonschema
|
||||||
# referencing
|
# referencing
|
||||||
scipy==1.11.3
|
scipy==1.11.4
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
smmap==5.0.1
|
smmap==5.0.1
|
||||||
# via gitdb
|
# via gitdb
|
||||||
|
sniffio==1.3.0
|
||||||
|
# via
|
||||||
|
# anyio
|
||||||
|
# httpx
|
||||||
|
# openai
|
||||||
sounddevice==0.4.6
|
sounddevice==0.4.6
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
soundfile==0.12.1
|
soundfile==0.12.1
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
tiktoken==0.5.1
|
tiktoken==0.5.2
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
tqdm==4.66.1
|
tqdm==4.66.1
|
||||||
# via openai
|
# via openai
|
||||||
tree-sitter==0.20.2
|
tree-sitter==0.20.4
|
||||||
# via tree-sitter-languages
|
# via tree-sitter-languages
|
||||||
tree-sitter-languages==1.8.0
|
tree-sitter-languages==1.8.0
|
||||||
# via grep-ast
|
# via grep-ast
|
||||||
urllib3==2.0.7
|
typing-extensions==4.8.0
|
||||||
|
# via
|
||||||
|
# openai
|
||||||
|
# pydantic
|
||||||
|
# pydantic-core
|
||||||
|
urllib3==2.1.0
|
||||||
# via requests
|
# via requests
|
||||||
wcwidth==0.2.9
|
wcwidth==0.2.12
|
||||||
# via prompt-toolkit
|
# via prompt-toolkit
|
||||||
yarl==1.9.2
|
|
||||||
# via aiohttp
|
|
||||||
|
|
|
@ -331,22 +331,25 @@ class TestCoder(unittest.TestCase):
|
||||||
# both files should still be here
|
# both files should still be here
|
||||||
self.assertEqual(len(coder.abs_fnames), 2)
|
self.assertEqual(len(coder.abs_fnames), 2)
|
||||||
|
|
||||||
@patch("aider.coders.base_coder.openai.ChatCompletion.create")
|
def test_run_with_invalid_request_error(self):
|
||||||
def test_run_with_invalid_request_error(self, mock_chat_completion_create):
|
|
||||||
with ChdirTemporaryDirectory():
|
with ChdirTemporaryDirectory():
|
||||||
# Mock the IO object
|
# Mock the IO object
|
||||||
mock_io = MagicMock()
|
mock_io = MagicMock()
|
||||||
|
|
||||||
# Initialize the Coder object with the mocked IO and mocked repo
|
mock_client = MagicMock()
|
||||||
coder = Coder.create(models.GPT4, None, mock_io)
|
|
||||||
|
|
||||||
# Set up the mock to raise InvalidRequestError
|
# Initialize the Coder object with the mocked IO and mocked repo
|
||||||
mock_chat_completion_create.side_effect = openai.error.InvalidRequestError(
|
coder = Coder.create(models.GPT4, None, mock_io, client=mock_client)
|
||||||
"Invalid request", "param"
|
|
||||||
|
# Set up the mock to raise
|
||||||
|
mock_client.chat.completions.create.side_effect = openai.BadRequestError(
|
||||||
|
message="Invalid request",
|
||||||
|
response=MagicMock(),
|
||||||
|
body=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Call the run method and assert that InvalidRequestError is raised
|
# Call the run method and assert that InvalidRequestError is raised
|
||||||
with self.assertRaises(openai.error.InvalidRequestError):
|
with self.assertRaises(openai.BadRequestError):
|
||||||
coder.run(with_message="hi")
|
coder.run(with_message="hi")
|
||||||
|
|
||||||
def test_new_file_edit_one_commit(self):
|
def test_new_file_edit_one_commit(self):
|
||||||
|
|
|
@ -182,6 +182,23 @@ class TestMain(TestCase):
|
||||||
_, kwargs = MockCoder.call_args
|
_, kwargs = MockCoder.call_args
|
||||||
assert kwargs["dirty_commits"] is True
|
assert kwargs["dirty_commits"] is True
|
||||||
|
|
||||||
|
def test_message_file_flag(self):
|
||||||
|
message_file_content = "This is a test message from a file."
|
||||||
|
message_file_path = tempfile.mktemp()
|
||||||
|
with open(message_file_path, "w", encoding="utf-8") as message_file:
|
||||||
|
message_file.write(message_file_content)
|
||||||
|
|
||||||
|
with patch("aider.main.Coder.create") as MockCoder:
|
||||||
|
MockCoder.return_value.run = MagicMock()
|
||||||
|
main(
|
||||||
|
["--yes", "--message-file", message_file_path],
|
||||||
|
input=DummyInput(),
|
||||||
|
output=DummyOutput(),
|
||||||
|
)
|
||||||
|
MockCoder.return_value.run.assert_called_once_with(with_message=message_file_content)
|
||||||
|
|
||||||
|
os.remove(message_file_path)
|
||||||
|
|
||||||
def test_encodings_arg(self):
|
def test_encodings_arg(self):
|
||||||
fname = "foo.py"
|
fname = "foo.py"
|
||||||
|
|
||||||
|
@ -196,3 +213,13 @@ class TestMain(TestCase):
|
||||||
MockSend.side_effect = side_effect
|
MockSend.side_effect = side_effect
|
||||||
|
|
||||||
main(["--yes", fname, "--encoding", "iso-8859-15"])
|
main(["--yes", fname, "--encoding", "iso-8859-15"])
|
||||||
|
|
||||||
|
@patch("aider.main.InputOutput")
|
||||||
|
@patch("aider.coders.base_coder.Coder.run")
|
||||||
|
def test_main_message_adds_to_input_history(self, mock_run, MockInputOutput):
|
||||||
|
test_message = "test message"
|
||||||
|
mock_io_instance = MockInputOutput.return_value
|
||||||
|
|
||||||
|
main(["--message", test_message], input=DummyInput(), output=DummyOutput())
|
||||||
|
|
||||||
|
mock_io_instance.add_to_input_history.assert_called_once_with(test_message)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from aider.models import Model, OpenRouterModel
|
from aider.models import Model, OpenRouterModel
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ class TestModels(unittest.TestCase):
|
||||||
model = Model.create("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.create("gpt-3.5-turbo-1106")
|
||||||
|
self.assertEqual(model.max_context_tokens, 16 * 1024)
|
||||||
|
|
||||||
model = Model.create("gpt-4")
|
model = Model.create("gpt-4")
|
||||||
self.assertEqual(model.max_context_tokens, 8 * 1024)
|
self.assertEqual(model.max_context_tokens, 8 * 1024)
|
||||||
|
|
||||||
|
@ -24,13 +27,9 @@ class TestModels(unittest.TestCase):
|
||||||
model = Model.create("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):
|
||||||
def test_openrouter_model_properties(self, mock_model_list):
|
client = MagicMock()
|
||||||
import openai
|
client.models.list.return_value = {
|
||||||
|
|
||||||
old_base = openai.api_base
|
|
||||||
openai.api_base = "https://openrouter.ai/api/v1"
|
|
||||||
mock_model_list.return_value = {
|
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"id": "openai/gpt-4",
|
"id": "openai/gpt-4",
|
||||||
|
@ -40,16 +39,15 @@ class TestModels(unittest.TestCase):
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
mock_model_list.return_value = type(
|
client.models.list.return_value = type(
|
||||||
"", (), {"data": mock_model_list.return_value["data"]}
|
"", (), {"data": client.models.list.return_value["data"]}
|
||||||
)()
|
)()
|
||||||
|
|
||||||
model = OpenRouterModel("gpt-4")
|
model = OpenRouterModel(client, "gpt-4")
|
||||||
self.assertEqual(model.name, "openai/gpt-4")
|
self.assertEqual(model.name, "openai/gpt-4")
|
||||||
self.assertEqual(model.max_context_tokens, 8192)
|
self.assertEqual(model.max_context_tokens, 8192)
|
||||||
self.assertEqual(model.prompt_price, 0.06)
|
self.assertEqual(model.prompt_price, 0.06)
|
||||||
self.assertEqual(model.completion_price, 0.12)
|
self.assertEqual(model.completion_price, 0.12)
|
||||||
openai.api_base = old_base
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -1,41 +1,46 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import httpx
|
||||||
import openai
|
import openai
|
||||||
import requests
|
|
||||||
|
|
||||||
from aider.sendchat import send_with_retries
|
from aider.sendchat import send_with_retries
|
||||||
|
|
||||||
|
|
||||||
|
class PrintCalled(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestSendChat(unittest.TestCase):
|
class TestSendChat(unittest.TestCase):
|
||||||
@patch("aider.sendchat.openai.ChatCompletion.create")
|
|
||||||
@patch("builtins.print")
|
@patch("builtins.print")
|
||||||
def test_send_with_retries_rate_limit_error(self, mock_print, mock_chat_completion_create):
|
def test_send_with_retries_rate_limit_error(self, mock_print):
|
||||||
# Set up the mock to raise RateLimitError on
|
mock_client = MagicMock()
|
||||||
# the first call and return None on the second call
|
|
||||||
mock_chat_completion_create.side_effect = [
|
# Set up the mock to raise
|
||||||
openai.error.RateLimitError("Rate limit exceeded"),
|
mock_client.chat.completions.create.side_effect = [
|
||||||
|
openai.RateLimitError(
|
||||||
|
"rate limit exceeded",
|
||||||
|
response=MagicMock(),
|
||||||
|
body=None,
|
||||||
|
),
|
||||||
None,
|
None,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Call the send_with_retries method
|
# Call the send_with_retries method
|
||||||
send_with_retries("model", ["message"], None, False)
|
send_with_retries(mock_client, "model", ["message"], None, False)
|
||||||
|
|
||||||
# Assert that print was called once
|
|
||||||
mock_print.assert_called_once()
|
mock_print.assert_called_once()
|
||||||
|
|
||||||
@patch("aider.sendchat.openai.ChatCompletion.create")
|
@patch("aider.sendchat.openai.ChatCompletion.create")
|
||||||
@patch("builtins.print")
|
@patch("builtins.print")
|
||||||
def test_send_with_retries_connection_error(self, mock_print, mock_chat_completion_create):
|
def test_send_with_retries_connection_error(self, mock_print, mock_chat_completion_create):
|
||||||
# Set up the mock to raise ConnectionError on the first call
|
mock_client = MagicMock()
|
||||||
# and return None on the second call
|
|
||||||
mock_chat_completion_create.side_effect = [
|
# Set up the mock to raise
|
||||||
requests.exceptions.ConnectionError("Connection error"),
|
mock_client.chat.completions.create.side_effect = [
|
||||||
|
httpx.ConnectError("Connection error"),
|
||||||
None,
|
None,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Call the send_with_retries method
|
# Call the send_with_retries method
|
||||||
send_with_retries("model", ["message"], None, False)
|
send_with_retries(mock_client, "model", ["message"], None, False)
|
||||||
|
|
||||||
# Assert that print was called once
|
|
||||||
mock_print.assert_called_once()
|
mock_print.assert_called_once()
|
||||||
|
|
|
@ -32,7 +32,7 @@ class TestWholeFileCoder(unittest.TestCase):
|
||||||
# Initialize WholeFileCoder with the temporary directory
|
# Initialize WholeFileCoder with the temporary directory
|
||||||
io = InputOutput(yes=True)
|
io = InputOutput(yes=True)
|
||||||
|
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io, fnames=[])
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io, fnames=[])
|
||||||
coder.partial_response_content = (
|
coder.partial_response_content = (
|
||||||
'To print "Hello, World!" in most programming languages, you can use the following'
|
'To print "Hello, World!" in most programming languages, you can use the following'
|
||||||
' code:\n\n```python\nprint("Hello, World!")\n```\n\nThis code will output "Hello,'
|
' code:\n\n```python\nprint("Hello, World!")\n```\n\nThis code will output "Hello,'
|
||||||
|
@ -44,7 +44,7 @@ class TestWholeFileCoder(unittest.TestCase):
|
||||||
|
|
||||||
def test_no_files_new_file_should_ask(self):
|
def test_no_files_new_file_should_ask(self):
|
||||||
io = InputOutput(yes=False) # <- yes=FALSE
|
io = InputOutput(yes=False) # <- yes=FALSE
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io, fnames=[])
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io, fnames=[])
|
||||||
coder.partial_response_content = (
|
coder.partial_response_content = (
|
||||||
'To print "Hello, World!" in most programming languages, you can use the following'
|
'To print "Hello, World!" in most programming languages, you can use the following'
|
||||||
' code:\n\nfoo.js\n```python\nprint("Hello, World!")\n```\n\nThis code will output'
|
' code:\n\nfoo.js\n```python\nprint("Hello, World!")\n```\n\nThis code will output'
|
||||||
|
@ -61,7 +61,7 @@ class TestWholeFileCoder(unittest.TestCase):
|
||||||
|
|
||||||
# Initialize WholeFileCoder with the temporary directory
|
# Initialize WholeFileCoder with the temporary directory
|
||||||
io = InputOutput(yes=True)
|
io = InputOutput(yes=True)
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io, fnames=[sample_file])
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io, fnames=[sample_file])
|
||||||
|
|
||||||
# Set the partial response content with the updated content
|
# Set the partial response content with the updated content
|
||||||
coder.partial_response_content = f"{sample_file}\n```\nUpdated content\n```"
|
coder.partial_response_content = f"{sample_file}\n```\nUpdated content\n```"
|
||||||
|
@ -85,7 +85,7 @@ class TestWholeFileCoder(unittest.TestCase):
|
||||||
|
|
||||||
# Initialize WholeFileCoder with the temporary directory
|
# Initialize WholeFileCoder with the temporary directory
|
||||||
io = InputOutput(yes=True)
|
io = InputOutput(yes=True)
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io, fnames=[sample_file])
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io, fnames=[sample_file])
|
||||||
|
|
||||||
# Set the partial response content with the updated content
|
# Set the partial response content with the updated content
|
||||||
coder.partial_response_content = f"{sample_file}\n```\n0\n\1\n2\n"
|
coder.partial_response_content = f"{sample_file}\n```\n0\n\1\n2\n"
|
||||||
|
@ -109,7 +109,7 @@ Quote!
|
||||||
|
|
||||||
# Initialize WholeFileCoder with the temporary directory
|
# Initialize WholeFileCoder with the temporary directory
|
||||||
io = InputOutput(yes=True)
|
io = InputOutput(yes=True)
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io, fnames=[sample_file])
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io, fnames=[sample_file])
|
||||||
|
|
||||||
coder.choose_fence()
|
coder.choose_fence()
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ Quote!
|
||||||
|
|
||||||
# Initialize WholeFileCoder with the temporary directory
|
# Initialize WholeFileCoder with the temporary directory
|
||||||
io = InputOutput(yes=True)
|
io = InputOutput(yes=True)
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io, fnames=[sample_file])
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io, fnames=[sample_file])
|
||||||
|
|
||||||
# Set the partial response content with the updated content
|
# Set the partial response content with the updated content
|
||||||
# With path/to/ prepended onto the filename
|
# With path/to/ prepended onto the filename
|
||||||
|
@ -164,7 +164,7 @@ Quote!
|
||||||
|
|
||||||
# Initialize WholeFileCoder with the temporary directory
|
# Initialize WholeFileCoder with the temporary directory
|
||||||
io = InputOutput(yes=True)
|
io = InputOutput(yes=True)
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io)
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io)
|
||||||
|
|
||||||
# Set the partial response content with the updated content
|
# Set the partial response content with the updated content
|
||||||
coder.partial_response_content = f"{sample_file}\n```\nUpdated content\n```"
|
coder.partial_response_content = f"{sample_file}\n```\nUpdated content\n```"
|
||||||
|
@ -192,7 +192,7 @@ Quote!
|
||||||
|
|
||||||
# Initialize WholeFileCoder with the temporary directory
|
# Initialize WholeFileCoder with the temporary directory
|
||||||
io = InputOutput(yes=True)
|
io = InputOutput(yes=True)
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io, fnames=[sample_file])
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io, fnames=[sample_file])
|
||||||
|
|
||||||
# Set the partial response content with the updated content
|
# Set the partial response content with the updated content
|
||||||
coder.partial_response_content = (
|
coder.partial_response_content = (
|
||||||
|
@ -235,7 +235,7 @@ after b
|
||||||
"""
|
"""
|
||||||
# Initialize WholeFileCoder with the temporary directory
|
# Initialize WholeFileCoder with the temporary directory
|
||||||
io = InputOutput(yes=True)
|
io = InputOutput(yes=True)
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io, fnames=[fname_a, fname_b])
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io, fnames=[fname_a, fname_b])
|
||||||
|
|
||||||
# Set the partial response content with the updated content
|
# Set the partial response content with the updated content
|
||||||
coder.partial_response_content = response
|
coder.partial_response_content = response
|
||||||
|
@ -259,7 +259,7 @@ after b
|
||||||
|
|
||||||
# Initialize WholeFileCoder with the temporary directory
|
# Initialize WholeFileCoder with the temporary directory
|
||||||
io = InputOutput(yes=True)
|
io = InputOutput(yes=True)
|
||||||
coder = WholeFileCoder(main_model=models.GPT35, io=io, fnames=[sample_file])
|
coder = WholeFileCoder(None, main_model=models.GPT35, io=io, fnames=[sample_file])
|
||||||
|
|
||||||
# Set the partial response content with the updated content
|
# Set the partial response content with the updated content
|
||||||
coder.partial_response_content = (
|
coder.partial_response_content = (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue