From 78e7b5c3dbc94e4034820cff17df6a24767d2c1e Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 08:45:40 -0300 Subject: [PATCH 01/20] wip --- aider/coders/base_coder.py | 2 ++ aider/history.py | 70 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 aider/history.py diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index e54fb1087..9e245bbed 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -353,6 +353,8 @@ class Coder: def move_back_cur_messages(self, message): self.done_messages += self.cur_messages + self.done_messages = summarize_chat_history(self.done_messages) + if message: self.done_messages += [ dict(role="user", content=message), diff --git a/aider/history.py b/aider/history.py new file mode 100644 index 000000000..f38e98e86 --- /dev/null +++ b/aider/history.py @@ -0,0 +1,70 @@ +import json + +import tiktoken +from prompt_toolkit.completion import Completion + +from aider import prompts + +from .dump import dump # noqa: F401 + + +class ChatSummary: + def __init__(self, model, max_tokens=1024): + self.tokenizer = tiktoken.encoding_for_model(model) + self.max_tokens = max_tokens + + def summarize(self, messages): + num = len(messages) + if num < 2: + return messages + + total = 0 + sized = [] + for msg in messages: + tokens = len(self.tokenizer.encode(json.dumps(msg))) + sized.append((tokens, msg)) + total += tokens + + if total <= self.max_tokens: + return messages + + num = num // 2 + + # we want the head to end with an assistant msg + if messages[num]["role"] == "assistant": + num += 1 + + head = messages[:num] + tail = messages[num:] + + summary = self.summarize_all(head) + + tail_tokens = sum(tokens for tokens, msg in sized[num:]) + summary_tokens = len(self.tokenizer.encode(json.dumps(summary))) + + result = summary + tail + if summary_tokens + tail_tokens < self.max_tokens: + return result + + return self.summarize(result) + + def summarize_all(self, messages): + content = "" + for msg in messages: + role = msg["role"].upper() + if role not in ("USER", "ASSISTANT"): + continue + content += f"# {role}\n" + content += msg["content"] + if not content.endswith("\n"): + content += "\n" + + dump(content) + + messages = [ + dict(role="system", content=prompts.summarize), + dict(role="user", content=content), + ] + + summary = simple_send_with_retries(model=models.GPT35.name, messages=messages) + dump(summary) From 6c7b6fc9e4ef14a6d683e58d7e417bff49b0fe3d Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 09:44:58 -0300 Subject: [PATCH 02/20] aider: Added a main function to `aider/history.py` that accepts a filename as a command-line argument, reads the file, and parses the markdown structure. # Aider chat conversation: USER: when run as a script, take a filename on the cmd line. read markdown from the file. parse the markdown structure, so we can iterate through # and #### seactions and do things like discard quoted text > sections easily. ASSISTANT: To achieve this, we need to modify the `aider/history.py` file to include a main function that accepts a filename as a command-line argument, reads the file, and parses the markdown structure. We will use the `argparse` module to handle command-line arguments and the `markdown` module to parse the markdown file. Here are the changes: ```python aider/history.py <<<<<<< ORIGINAL import json import tiktoken from prompt_toolkit.completion import Completion from aider import prompts from .dump import dump # noqa: F401 ======= import argparse import json import markdown import tiktoken from prompt_toolkit.completion import Completion from aider import prompts from .dump import dump # noqa: F401 def main(): parser = argparse.ArgumentParser() parser.add_argument("filename", help="Markdown file to parse") args = parser.parse_args() with open(args.filename, 'r') as f: text = f.read() md = markdown.Markdown() tree = md.parse(text) for element in tree.getiterator(): if element.tag in ['h1', 'h4'] and element.text is not None: print(element.text) elif element.tag == 'blockquote': continue else: print(element.text) if __name__ == "__main__": main() >>>>>>> UPDATED ``` This script will print the text of all `#` and `####` sections and ignore quoted text `>` sections. Other text will be printed as is. If you want to handle other markdown elements differently, you can add more conditions in the for loop. --- aider/history.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/aider/history.py b/aider/history.py index f38e98e86..e0d8d64a8 100644 --- a/aider/history.py +++ b/aider/history.py @@ -1,4 +1,6 @@ +import argparse import json +import markdown import tiktoken from prompt_toolkit.completion import Completion @@ -7,6 +9,28 @@ from aider import prompts from .dump import dump # noqa: F401 +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("filename", help="Markdown file to parse") + args = parser.parse_args() + + with open(args.filename, 'r') as f: + text = f.read() + + md = markdown.Markdown() + tree = md.parse(text) + + for element in tree.getiterator(): + if element.tag in ['h1', 'h4'] and element.text is not None: + print(element.text) + elif element.tag == 'blockquote': + continue + else: + print(element.text) + +if __name__ == "__main__": + main() + class ChatSummary: def __init__(self, model, max_tokens=1024): From c27087bbda5d33848c1066ebaa1f2be20baf2e7d Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 09:45:27 -0300 Subject: [PATCH 03/20] Remove the call to the summarize_chat_history function in move_back_cur_messages method. --- aider/coders/base_coder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 9e245bbed..cc750907f 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -353,7 +353,7 @@ class Coder: def move_back_cur_messages(self, message): self.done_messages += self.cur_messages - self.done_messages = summarize_chat_history(self.done_messages) + #self.done_messages = summarize_chat_history(self.done_messages) if message: self.done_messages += [ From c8e8967d453fe4f72e72544debf4fcd9f596133e Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 09:50:41 -0300 Subject: [PATCH 04/20] wip --- aider/history.py | 15 ++++++++------- aider/prompts.py | 5 +++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/aider/history.py b/aider/history.py index e0d8d64a8..f7518f2ae 100644 --- a/aider/history.py +++ b/aider/history.py @@ -1,33 +1,34 @@ import argparse import json + import markdown - import tiktoken -from prompt_toolkit.completion import Completion -from aider import prompts +from aider import models, prompts +from aider.dump import dump # noqa: F401 +from aider.sendchat import simple_send_with_retries -from .dump import dump # noqa: F401 def main(): parser = argparse.ArgumentParser() parser.add_argument("filename", help="Markdown file to parse") args = parser.parse_args() - with open(args.filename, 'r') as f: + with open(args.filename, "r") as f: text = f.read() md = markdown.Markdown() tree = md.parse(text) for element in tree.getiterator(): - if element.tag in ['h1', 'h4'] and element.text is not None: + if element.tag in ["h1", "h4"] and element.text is not None: print(element.text) - elif element.tag == 'blockquote': + elif element.tag == "blockquote": continue else: print(element.text) + if __name__ == "__main__": main() diff --git a/aider/prompts.py b/aider/prompts.py index c037575c3..3b7c36e04 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -24,3 +24,8 @@ And got this output: {output} """ + +# CHAT HISTORY +summarize = """ +Summarize the conversation. +""" From a2ce9c4403fb8ba2ef07c79f3b7f702145a3bf93 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 09:58:57 -0300 Subject: [PATCH 05/20] feed markdown into summarizer --- aider/history.py | 63 +++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/aider/history.py b/aider/history.py index f7518f2ae..560c3c9b4 100644 --- a/aider/history.py +++ b/aider/history.py @@ -1,7 +1,6 @@ import argparse import json -import markdown import tiktoken from aider import models, prompts @@ -9,30 +8,6 @@ from aider.dump import dump # noqa: F401 from aider.sendchat import simple_send_with_retries -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("filename", help="Markdown file to parse") - args = parser.parse_args() - - with open(args.filename, "r") as f: - text = f.read() - - md = markdown.Markdown() - tree = md.parse(text) - - for element in tree.getiterator(): - if element.tag in ["h1", "h4"] and element.text is not None: - print(element.text) - elif element.tag == "blockquote": - continue - else: - print(element.text) - - -if __name__ == "__main__": - main() - - class ChatSummary: def __init__(self, model, max_tokens=1024): self.tokenizer = tiktoken.encoding_for_model(model) @@ -93,3 +68,41 @@ class ChatSummary: summary = simple_send_with_retries(model=models.GPT35.name, messages=messages) dump(summary) + + return [dict(role="user", content=summary)] + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("filename", help="Markdown file to parse") + args = parser.parse_args() + + with open(args.filename, "r") as f: + text = f.read() + + messages = [] + assistant = [] + for line in text.splitlines(keepends=True): + if line.startswith("# "): + continue + if line.startswith(">"): + continue + + if line.startswith("#### "): + if assistant: + assistant = "".join(assistant) + messages.append(dict(role="assistant", content=assistant)) + assistant = [] + + messages.append(dict(role="user", content=line[5:])) + continue + + assistant.append(line) + + summarizer = ChatSummary(models.GPT35.name) + summary = summarizer.summarize(messages[-40:]) + dump(summary) + + +if __name__ == "__main__": + main() From 133c82528e30546ab3c99b38b51392cb035ed0d4 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 10:02:53 -0300 Subject: [PATCH 06/20] prompt --- aider/prompts.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/aider/prompts.py b/aider/prompts.py index 3b7c36e04..c66d868e7 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -26,6 +26,12 @@ And got this output: """ # CHAT HISTORY -summarize = """ -Summarize the conversation. +summarize = """*Briefly* summarize this partial conversation about programming. +Include less detail about older parts and more detail about the most recent messages. +This is only part of a longer conversation so *DO NOT* conclude the summary with language like "Finally, ...". Because the conversation continues after the summary. +It's *very important* to include relevant code identifiers, file names, etc since this is a conversation about programming. +Phrase the summary with the USER in first person, telling the ASSISTANT about the conversation. +Write *as* the user. +The user should refer to the assistant as *you*. +Start the summary with "I asked you...". """ From 0d0ac4f61f736aef4dc15dd441aa38be5c0277b9 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 10:32:32 -0300 Subject: [PATCH 07/20] works --- aider/history.py | 14 +++++++++----- aider/prompts.py | 7 ++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/aider/history.py b/aider/history.py index 560c3c9b4..214198f96 100644 --- a/aider/history.py +++ b/aider/history.py @@ -25,6 +25,7 @@ class ChatSummary: sized.append((tokens, msg)) total += tokens + dump(total, self.max_tokens) if total <= self.max_tokens: return messages @@ -37,6 +38,7 @@ class ChatSummary: head = messages[:num] tail = messages[num:] + print("=" * 20) summary = self.summarize_all(head) tail_tokens = sum(tokens for tokens, msg in sized[num:]) @@ -59,15 +61,12 @@ class ChatSummary: if not content.endswith("\n"): content += "\n" - dump(content) - messages = [ dict(role="system", content=prompts.summarize), dict(role="user", content=content), ] summary = simple_send_with_retries(model=models.GPT35.name, messages=messages) - dump(summary) return [dict(role="user", content=summary)] @@ -87,14 +86,19 @@ def main(): continue if line.startswith(">"): continue + if line.startswith("#### /"): + continue if line.startswith("#### "): if assistant: assistant = "".join(assistant) - messages.append(dict(role="assistant", content=assistant)) + if assistant.strip(): + messages.append(dict(role="assistant", content=assistant)) assistant = [] - messages.append(dict(role="user", content=line[5:])) + content = line[5:] + if content.strip() and content.strip() != "": + messages.append(dict(role="user", content=line[5:])) continue assistant.append(line) diff --git a/aider/prompts.py b/aider/prompts.py index c66d868e7..fd1a56d59 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -28,8 +28,13 @@ And got this output: # CHAT HISTORY summarize = """*Briefly* summarize this partial conversation about programming. Include less detail about older parts and more detail about the most recent messages. +Start a new paragraph every time the topic changes! + This is only part of a longer conversation so *DO NOT* conclude the summary with language like "Finally, ...". Because the conversation continues after the summary. -It's *very important* to include relevant code identifiers, file names, etc since this is a conversation about programming. +The summary *MUST* include the function names, libraries, packages that are being discussed. +The summary *MUST* include the filenames that are being referenced by the assistant inside the ```...``` fenced code blocks! +The summaries *MUST NOT* include ```...``` fenced code blocks! + Phrase the summary with the USER in first person, telling the ASSISTANT about the conversation. Write *as* the user. The user should refer to the assistant as *you*. From c26917851fa460d0947d3ccd3ec37241f3c02bcd Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 10:34:48 -0300 Subject: [PATCH 08/20] Added a `ChatSummary` object to `Coder` class and used it to summarize chat history. --- aider/coders/base_coder.py | 5 ++++- aider/history.py | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index bca01469b..f0d7ab66f 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -20,6 +20,7 @@ from aider.commands import Commands from aider.repo import GitRepo from aider.repomap import RepoMap from aider.sendchat import send_with_retries +from aider.history import ChatSummary from ..dump import dump # noqa: F401 @@ -199,6 +200,8 @@ class Coder: if self.repo: self.repo.add_new_files(fnames) + self.summarizer = ChatSummary() + # validate the functions jsonschema if self.functions: for function in self.functions: @@ -353,7 +356,7 @@ class Coder: def move_back_cur_messages(self, message): self.done_messages += self.cur_messages - #self.done_messages = summarize_chat_history(self.done_messages) + self.done_messages = self.summarizer.summarize(self.done_messages) if message: self.done_messages += [ diff --git a/aider/history.py b/aider/history.py index 214198f96..6c164a076 100644 --- a/aider/history.py +++ b/aider/history.py @@ -9,7 +9,7 @@ from aider.sendchat import simple_send_with_retries class ChatSummary: - def __init__(self, model, max_tokens=1024): + def __init__(self, model=models.GPT35.name, max_tokens=1024): self.tokenizer = tiktoken.encoding_for_model(model) self.max_tokens = max_tokens @@ -38,7 +38,6 @@ class ChatSummary: head = messages[:num] tail = messages[num:] - print("=" * 20) summary = self.summarize_all(head) tail_tokens = sum(tokens for tokens, msg in sized[num:]) From 69aea3e745474550b93f27115c6496f9c82c1657 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 12:11:01 -0300 Subject: [PATCH 09/20] Add summary prefix --- aider/coders/base_coder.py | 4 ++-- aider/history.py | 3 +++ aider/prompts.py | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index f0d7ab66f..15ca87a63 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -17,10 +17,10 @@ from rich.markdown import Markdown from aider import models, prompts, utils from aider.commands import Commands +from aider.history import ChatSummary from aider.repo import GitRepo from aider.repomap import RepoMap from aider.sendchat import send_with_retries -from aider.history import ChatSummary from ..dump import dump # noqa: F401 @@ -200,7 +200,7 @@ class Coder: if self.repo: self.repo.add_new_files(fnames) - self.summarizer = ChatSummary() + self.summarizer = ChatSummary(self.main_model.name) # validate the functions jsonschema if self.functions: diff --git a/aider/history.py b/aider/history.py index 6c164a076..e2441680f 100644 --- a/aider/history.py +++ b/aider/history.py @@ -15,6 +15,7 @@ class ChatSummary: def summarize(self, messages): num = len(messages) + dump(num) if num < 2: return messages @@ -66,6 +67,8 @@ class ChatSummary: ] summary = simple_send_with_retries(model=models.GPT35.name, messages=messages) + summary = prompts.summary_prefix + summary + dump(summary) return [dict(role="user", content=summary)] diff --git a/aider/prompts.py b/aider/prompts.py index fd1a56d59..79915d519 100644 --- a/aider/prompts.py +++ b/aider/prompts.py @@ -40,3 +40,5 @@ Write *as* the user. The user should refer to the assistant as *you*. Start the summary with "I asked you...". """ + +summary_prefix = "I spoke to you previously about a number of things.\n" From e5ffb78224e3f90a4d1d06c83d28883d9e0e9cff Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Sat, 22 Jul 2023 16:09:57 -0300 Subject: [PATCH 10/20] wip --- aider/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/history.py b/aider/history.py index e2441680f..ca45feab8 100644 --- a/aider/history.py +++ b/aider/history.py @@ -33,7 +33,7 @@ class ChatSummary: num = num // 2 # we want the head to end with an assistant msg - if messages[num]["role"] == "assistant": + while messages[num]["role"] == "assistant" and num < len(messages) - 1: num += 1 head = messages[:num] From 0db9d210d77b58bc4f297695e7b9e77a68394ea3 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Mon, 24 Jul 2023 19:05:57 -0300 Subject: [PATCH 11/20] Advise the user when chat history is being summarized --- aider/coders/base_coder.py | 4 +++- aider/history.py | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 52b274ab7..e64971f47 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -360,7 +360,9 @@ class Coder: def move_back_cur_messages(self, message): self.done_messages += self.cur_messages - self.done_messages = self.summarizer.summarize(self.done_messages) + if self.summarizer.too_big(self.done_messages): + self.io.tool_output("Summarizing chat history...") + self.done_messages = self.summarizer.summarize(self.done_messages) if message: self.done_messages += [ diff --git a/aider/history.py b/aider/history.py index ca45feab8..d342530a7 100644 --- a/aider/history.py +++ b/aider/history.py @@ -13,20 +13,27 @@ class ChatSummary: self.tokenizer = tiktoken.encoding_for_model(model) self.max_tokens = max_tokens + def too_big(self, messages): + sized = self.tokenize(messages) + total = sum(tokens for tokens, _msg in sized) + dump(total, self.max_tokens) + return total > self.max_tokens + + def tokenize(self, messages): + sized = [] + for msg in messages: + tokens = len(self.tokenizer.encode(json.dumps(msg))) + sized.append((tokens, msg)) + return sized + def summarize(self, messages): num = len(messages) dump(num) if num < 2: return messages - total = 0 - sized = [] - for msg in messages: - tokens = len(self.tokenizer.encode(json.dumps(msg))) - sized.append((tokens, msg)) - total += tokens - - dump(total, self.max_tokens) + sized = self.tokenize(messages) + total = sum(tokens for tokens, _msg in sized) if total <= self.max_tokens: return messages From 270ea21c05eb42cf659f107f843417a0049840ce Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Mon, 24 Jul 2023 19:08:13 -0300 Subject: [PATCH 12/20] aider: Change the `summarize` method in the `ChatSummary` class to split the messages so that the tail holds as close as possible but less than `max_tokens`//2 tokens and the head holds the rest. # Aider chat conversation: USER: instead of splitting the history in half based on the msg number, split it in half based on the token count ASSISTANT: To implement this change, we need to modify the `summarize` method in the `ChatSummary` class in the `aider/history.py` file. The current implementation splits the messages in half based on the number of messages. We need to change this to split the messages based on the token count. Here are the steps to implement this change: 1. Calculate the total token count of all messages. 2. Iterate over the messages and keep a running total of the token count. 3. Once the running total exceeds half of the total token count, split the messages at that point. Here is the *edit block* for this change: ```python aider/history.py <<<<<<< ORIGINAL def summarize(self, messages): num = len(messages) dump(num) if num < 2: return messages sized = self.tokenize(messages) total = sum(tokens for tokens, _msg in sized) if total <= self.max_tokens: return messages num = num // 2 # we want the head to end with an assistant msg while messages[num]["role"] == "assistant" and num < len(messages) - 1: num += 1 head = messages[:num] tail = messages[num:] ======= def summarize(self, messages): ^C KeyboardInterrupt USER: instead of splitting the history in half based on the msg number, split it so that the tail holds as close as possible but less than `max_tokens`//2 tokens and head holds the rest ASSISTANT: To implement this change, we need to modify the `summarize` method in the `ChatSummary` class in the `aider/history.py` file. The current implementation splits the messages in half based on the number of messages. We need to change this to split the messages so that the tail holds as close as possible but less than `max_tokens`//2 tokens and head holds the rest. Here are the steps to implement this change: 1. Iterate over the messages in reverse order and keep a running total of the token count. 2. Once the running total exceeds `max_tokens`//2, split the messages at that point. Here is the *edit block* for this change: ```python aider/history.py <<<<<<< ORIGINAL def summarize(self, messages): num = len(messages) dump(num) if num < 2: return messages sized = self.tokenize(messages) total = sum(tokens for tokens, _msg in sized) if total <= self.max_tokens: return messages num = num // 2 # we want the head to end with an assistant msg while messages[num]["role"] == "assistant" and num < len(messages) - 1: num += 1 head = messages[:num] tail = messages[num:] ======= def summarize(self, messages): if len(messages) < 2: return messages sized = self.tokenize(messages) total = sum(tokens for tokens, _msg in sized) if total <= self.max_tokens: return messages tail_tokens = 0 split_index = len(messages) half_max_tokens = self.max_tokens // 2 # Iterate over the messages in reverse order for i in range(len(sized)-1, -1, -1): tokens, _msg = sized[i] if tail_tokens + tokens < half_max_tokens: tail_tokens += tokens split_index = i else: break head = messages[:split_index] tail = messages[split_index:] >>>>>>> UPDATED ``` This change ensures that the tail of the messages holds as close as possible but less than `max_tokens`//2 tokens and the head holds the rest. --- aider/history.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/aider/history.py b/aider/history.py index d342530a7..4a1e2ca3d 100644 --- a/aider/history.py +++ b/aider/history.py @@ -27,9 +27,7 @@ class ChatSummary: return sized def summarize(self, messages): - num = len(messages) - dump(num) - if num < 2: + if len(messages) < 2: return messages sized = self.tokenize(messages) @@ -37,14 +35,21 @@ class ChatSummary: if total <= self.max_tokens: return messages - num = num // 2 + tail_tokens = 0 + split_index = len(messages) + half_max_tokens = self.max_tokens // 2 - # we want the head to end with an assistant msg - while messages[num]["role"] == "assistant" and num < len(messages) - 1: - num += 1 + # Iterate over the messages in reverse order + for i in range(len(sized)-1, -1, -1): + tokens, _msg = sized[i] + if tail_tokens + tokens < half_max_tokens: + tail_tokens += tokens + split_index = i + else: + break - head = messages[:num] - tail = messages[num:] + head = messages[:split_index] + tail = messages[split_index:] summary = self.summarize_all(head) From 3c656fe3be575f83c3bffbe2d90f96bb480ebd58 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Mon, 24 Jul 2023 19:08:51 -0300 Subject: [PATCH 13/20] aider: Ensure the head ends with an assistant message. # Aider chat conversation: USER: but we want the head to end with an assistant msg ASSISTANT: To ensure that the head ends with an assistant message, we need to modify the `summarize` method in the `ChatSummary` class in the `aider/history.py` file. After splitting the messages based on the token count, we need to check if the last message in the head is from the assistant. If not, we need to move messages from the head to the tail until we find an assistant message. Here is the *edit block* for this change: ```python aider/history.py <<<<<<< ORIGINAL def summarize(self, messages): if len(messages) < 2: return messages sized = self.tokenize(messages) total = sum(tokens for tokens, _msg in sized) if total <= self.max_tokens: return messages tail_tokens = 0 split_index = len(messages) half_max_tokens = self.max_tokens // 2 # Iterate over the messages in reverse order for i in range(len(sized)-1, -1, -1): tokens, _msg = sized[i] if tail_tokens + tokens < half_max_tokens: tail_tokens += tokens split_index = i else: break head = messages[:split_index] tail = messages[split_index:] ======= def summarize(self, messages): if len(messages) < 2: return messages sized = self.tokenize(messages) total = sum(tokens for tokens, _msg in sized) if total <= self.max_tokens: return messages tail_tokens = 0 split_index = len(messages) half_max_tokens = self.max_tokens // 2 # Iterate over the messages in reverse order for i in range(len(sized)-1, -1, -1): tokens, _msg = sized[i] if tail_tokens + tokens < half_max_tokens: tail_tokens += tokens split_index = i else: break # Ensure the head ends with an assistant message while messages[split_index - 1]["role"] != "assistant" and split_index < len(messages): split_index += 1 head = messages[:split_index] tail = messages[split_index:] >>>>>>> UPDATED ``` This change ensures that the head of the messages ends with an assistant message. --- aider/history.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aider/history.py b/aider/history.py index 4a1e2ca3d..283ca34ed 100644 --- a/aider/history.py +++ b/aider/history.py @@ -48,6 +48,10 @@ class ChatSummary: else: break + # Ensure the head ends with an assistant message + while messages[split_index - 1]["role"] != "assistant" and split_index < len(messages): + split_index += 1 + head = messages[:split_index] tail = messages[split_index:] From 4b41eb75258b35722d346eaf6c945a83f50b453e Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Mon, 24 Jul 2023 19:10:58 -0300 Subject: [PATCH 14/20] aider: Fix variable name error in summarize method in history.py. # Aider chat conversation: USER: File "/Users/gauthier/Projects/aider/aider/coders/base_coder.py", line 395, in run_loop return self.send_new_user_message(inp) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/gauthier/Projects/aider/aider/coders/base_coder.py", line 479, in send_new_user_message self.move_back_cur_messages(saved_message) File "/Users/gauthier/Projects/aider/aider/coders/base_coder.py", line 365, in move_back_cur_messages self.done_messages = self.summarizer.summarize(self.done_messages) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/gauthier/Projects/aider/aider/history.py", line 60, in summarize tail_tokens = sum(tokens for tokens, msg in sized[num:]) ^^^ NameError: name 'num' is not defined. Did you mean: 'sum'? ASSISTANT: It seems like there's a typo in the code. The variable `num` is not defined in the current scope. I believe it should be `split_index` instead of `num` as it's the index where the messages are split into head and tail. Here's the change that needs to be made in the `aider/history.py` file: ```python aider/history.py <<<<<<< ORIGINAL tail_tokens = sum(tokens for tokens, msg in sized[num:]) ======= tail_tokens = sum(tokens for tokens, msg in sized[split_index:]) >>>>>>> UPDATED ``` Please confirm if this is the change you want to make. --- aider/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/history.py b/aider/history.py index 283ca34ed..6b022d59b 100644 --- a/aider/history.py +++ b/aider/history.py @@ -57,7 +57,7 @@ class ChatSummary: summary = self.summarize_all(head) - tail_tokens = sum(tokens for tokens, msg in sized[num:]) + tail_tokens = sum(tokens for tokens, msg in sized[split_index:]) summary_tokens = len(self.tokenizer.encode(json.dumps(summary))) result = summary + tail From f0ec6cb36aad98ce5344b95ead8a5a490d910f38 Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 25 Jul 2023 06:58:41 -0300 Subject: [PATCH 15/20] Expand the tail to ensure head ends with assistant msg --- aider/history.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aider/history.py b/aider/history.py index 6b022d59b..425579e8e 100644 --- a/aider/history.py +++ b/aider/history.py @@ -40,7 +40,7 @@ class ChatSummary: half_max_tokens = self.max_tokens // 2 # Iterate over the messages in reverse order - for i in range(len(sized)-1, -1, -1): + for i in range(len(sized) - 1, -1, -1): tokens, _msg = sized[i] if tail_tokens + tokens < half_max_tokens: tail_tokens += tokens @@ -49,8 +49,8 @@ class ChatSummary: break # Ensure the head ends with an assistant message - while messages[split_index - 1]["role"] != "assistant" and split_index < len(messages): - split_index += 1 + while messages[split_index - 1]["role"] != "assistant" and split_index > 1: + split_index -= 1 head = messages[:split_index] tail = messages[split_index:] From 4e348102bf977521cfff1ea681b7f2c7d8e0b98b Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 25 Jul 2023 07:46:05 -0300 Subject: [PATCH 16/20] wip --- aider/coders/base_coder.py | 31 ++++++++++++++++++++++++++++--- aider/history.py | 2 +- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index e64971f47..eb28f4097 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -4,6 +4,7 @@ import hashlib import json import os import sys +import threading import time import traceback from json.decoder import JSONDecodeError @@ -205,6 +206,8 @@ class Coder: self.repo.add_new_files(fname for fname in fnames if not Path(fname).is_dir()) self.summarizer = ChatSummary(self.main_model.name) + self.summarizer_thread = None + self.summarized_done_messages = None # validate the functions jsonschema if self.functions: @@ -358,11 +361,33 @@ class Coder: self.last_keyboard_interrupt = now + def summarize_end(self): + if self.summarizer_thread is None: + return + + self.summarizer_thread.join() + self.summarizer_thread = None + + self.done_messages = self.summarized_done_messages + self.summarized_done_messages = None + + def summarize_start(self): + if not self.summarizer.too_big(self.done_messages): + return + + assert self.summarizer_thread is None + assert self.summarized_done_messages is None + self.summarizer_thread = threading.Thread(target=self.summarize_worker) + + def summarize_worker(self): + print("working!") + self.summarized_done_messages = self.summarizer.summarize(self.done_messages) + print("done!") + def move_back_cur_messages(self, message): + self.summarize_end() self.done_messages += self.cur_messages - if self.summarizer.too_big(self.done_messages): - self.io.tool_output("Summarizing chat history...") - self.done_messages = self.summarizer.summarize(self.done_messages) + self.summarize_start() if message: self.done_messages += [ diff --git a/aider/history.py b/aider/history.py index 425579e8e..732a6c9bc 100644 --- a/aider/history.py +++ b/aider/history.py @@ -9,7 +9,7 @@ from aider.sendchat import simple_send_with_retries class ChatSummary: - def __init__(self, model=models.GPT35.name, max_tokens=1024): + def __init__(self, model=models.GPT35.name, max_tokens=128): self.tokenizer = tiktoken.encoding_for_model(model) self.max_tokens = max_tokens From 9050b9bd932618b2227696204547a3e64f1063ee Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 25 Jul 2023 07:57:05 -0300 Subject: [PATCH 17/20] threading --- aider/coders/base_coder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index eb28f4097..87d7ab996 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -377,7 +377,9 @@ class Coder: assert self.summarizer_thread is None assert self.summarized_done_messages is None + print("launching working") self.summarizer_thread = threading.Thread(target=self.summarize_worker) + self.summarizer_thread.start() def summarize_worker(self): print("working!") @@ -385,7 +387,6 @@ class Coder: print("done!") def move_back_cur_messages(self, message): - self.summarize_end() self.done_messages += self.cur_messages self.summarize_start() @@ -439,6 +440,7 @@ class Coder: dict(role="system", content=main_sys), ] + self.summarize_end() messages += self.done_messages messages += self.get_files_messages() messages += self.cur_messages From 05c1b3bc991c1add73118c640066a971eb4cddde Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Tue, 25 Jul 2023 09:13:07 -0300 Subject: [PATCH 18/20] cleanup --- aider/coders/base_coder.py | 20 ++++++++++---------- aider/history.py | 7 +++---- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 87d7ab996..c969c4278 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -361,16 +361,6 @@ class Coder: self.last_keyboard_interrupt = now - def summarize_end(self): - if self.summarizer_thread is None: - return - - self.summarizer_thread.join() - self.summarizer_thread = None - - self.done_messages = self.summarized_done_messages - self.summarized_done_messages = None - def summarize_start(self): if not self.summarizer.too_big(self.done_messages): return @@ -386,6 +376,16 @@ class Coder: self.summarized_done_messages = self.summarizer.summarize(self.done_messages) print("done!") + def summarize_end(self): + if self.summarizer_thread is None: + return + + self.summarizer_thread.join() + self.summarizer_thread = None + + self.done_messages = self.summarized_done_messages + self.summarized_done_messages = None + def move_back_cur_messages(self, message): self.done_messages += self.cur_messages self.summarize_start() diff --git a/aider/history.py b/aider/history.py index 732a6c9bc..e6f1e3c5c 100644 --- a/aider/history.py +++ b/aider/history.py @@ -9,7 +9,7 @@ from aider.sendchat import simple_send_with_retries class ChatSummary: - def __init__(self, model=models.GPT35.name, max_tokens=128): + def __init__(self, model=models.GPT35.name, max_tokens=1024): self.tokenizer = tiktoken.encoding_for_model(model) self.max_tokens = max_tokens @@ -27,8 +27,8 @@ class ChatSummary: return sized def summarize(self, messages): - if len(messages) < 2: - return messages + if len(messages) <= 4: + return self.summarize_all(messages) sized = self.tokenize(messages) total = sum(tokens for tokens, _msg in sized) @@ -84,7 +84,6 @@ class ChatSummary: summary = simple_send_with_retries(model=models.GPT35.name, messages=messages) summary = prompts.summary_prefix + summary - dump(summary) return [dict(role="user", content=summary)] From d1d498bb24c6b04358fea6f779ef805b4a0ce4ed Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 27 Jul 2023 22:34:41 -0300 Subject: [PATCH 19/20] cleanup --- aider/coders/base_coder.py | 8 +++++--- aider/history.py | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 94748617f..3ca6315bb 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -367,14 +367,16 @@ class Coder: assert self.summarizer_thread is None assert self.summarized_done_messages is None - print("launching working") + if self.verbose: + self.io.tool_output("Starting to summarize chat history.") + self.summarizer_thread = threading.Thread(target=self.summarize_worker) self.summarizer_thread.start() def summarize_worker(self): - print("working!") self.summarized_done_messages = self.summarizer.summarize(self.done_messages) - print("done!") + if self.verbose: + self.io.tool_output("Finished summarizing chat history.") def summarize_end(self): if self.summarizer_thread is None: diff --git a/aider/history.py b/aider/history.py index e6f1e3c5c..61a21c491 100644 --- a/aider/history.py +++ b/aider/history.py @@ -16,7 +16,6 @@ class ChatSummary: def too_big(self, messages): sized = self.tokenize(messages) total = sum(tokens for tokens, _msg in sized) - dump(total, self.max_tokens) return total > self.max_tokens def tokenize(self, messages): From 80d5c0b75c27ef73a1c3dc04a0ec56aad466cbdb Mon Sep 17 00:00:00 2001 From: Paul Gauthier Date: Thu, 27 Jul 2023 22:54:26 -0300 Subject: [PATCH 20/20] use 3.5 for summaries --- aider/coders/base_coder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 3ca6315bb..fdfe7c574 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -205,7 +205,7 @@ class Coder: if self.repo: self.repo.add_new_files(fname for fname in fnames if not Path(fname).is_dir()) - self.summarizer = ChatSummary(self.main_model.name) + self.summarizer = ChatSummary() self.summarizer_thread = None self.summarized_done_messages = None