From 7cf62c54efd0a6ca6a23eacbd7a7bd716962f97f Mon Sep 17 00:00:00 2001 From: Oleksandr Kozachuk Date: Sat, 9 Sep 2023 15:16:17 +0200 Subject: [PATCH 1/3] Allow in question -s for just sourcing file and -S to source file with ``` encapsulation. --- chatmastermind/commands/question.py | 22 ++++++++++++---------- chatmastermind/main.py | 5 ++--- tests/test_question_cmd.py | 22 ++++++++++++++++++---- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/chatmastermind/commands/question.py b/chatmastermind/commands/question.py index 1709a3c..818b1de 100644 --- a/chatmastermind/commands/question.py +++ b/chatmastermind/commands/question.py @@ -15,19 +15,21 @@ def create_message(chat: ChatDB, args: argparse.Namespace) -> Message: question_parts = [] question_list = args.question if args.question is not None else [] source_list = args.source if args.source is not None else [] + code_list = args.source_code if args.source_code is not None else [] - # FIXME: don't surround all sourced files with ``` - # -> do it only if '--source-code-only' is True and no source code - # could be extracted from that file - for question, source in zip_longest(question_list, source_list, fillvalue=None): - if question is not None and source is not None: - with open(source) as r: - question_parts.append(f"{question}\n\n```\n{r.read().strip()}\n```") - elif question is not None: + for question, source, code in zip_longest(question_list, source_list, code_list, fillvalue=None): + if question is not None and len(question.strip()) > 0: question_parts.append(question) - elif source is not None: + if source is not None and len(source) > 0: with open(source) as r: - question_parts.append(f"```\n{r.read().strip()}\n```") + content = r.read().strip() + if len(content) > 0: + question_parts.append(content) + if code is not None and len(code) > 0: + with open(code) as r: + content = r.read().strip() + if len(content) > 0: + question_parts.append(f"```\n{content}\n```") full_question = '\n\n'.join(question_parts) diff --git a/chatmastermind/main.py b/chatmastermind/main.py index 88121b4..f7163ab 100755 --- a/chatmastermind/main.py +++ b/chatmastermind/main.py @@ -67,9 +67,8 @@ def create_parser() -> argparse.ArgumentParser: question_group.add_argument('-p', '--process', nargs='*', help='Process existing questions') question_cmd_parser.add_argument('-O', '--overwrite', help='Overwrite existing messages when repeating them', action='store_true') - question_cmd_parser.add_argument('-s', '--source', nargs='+', help='Source add content of a file to the query') - question_cmd_parser.add_argument('-S', '--source-code-only', help='Add pure source code to the chat history', - action='store_true') + question_cmd_parser.add_argument('-s', '--source', nargs='+', help='Add content of a file to the query') + question_cmd_parser.add_argument('-S', '--source-code', nargs='+', help='Add source code file content to the chat history') # 'hist' command parser hist_cmd_parser = cmdparser.add_parser('hist', parents=[tag_parser], diff --git a/tests/test_question_cmd.py b/tests/test_question_cmd.py index 96b2fdf..06cc527 100644 --- a/tests/test_question_cmd.py +++ b/tests/test_question_cmd.py @@ -23,7 +23,7 @@ class TestMessageCreate(unittest.TestCase): # create arguments mock self.args = MagicMock(spec=argparse.Namespace) self.args.source = None - self.args.source_code_only = False + self.args.source_code = None self.args.ai = None self.args.model = None self.args.output_tags = None @@ -94,11 +94,11 @@ Is it good?""")) # source file contains no source code # -> don't expect any in the question self.assertEqual(len(message.question.source_code()), 0) - self.assertEqual(message.question, Question("""What is this? + self.assertEqual(message.question, Question(f"""What is this? {self.source_file1_content}""")) - def test_single_question_with_embedded_source_code_source(self) -> None: + def test_single_question_with_embedded_source_source(self) -> None: self.args.question = ["What is this?"] self.args.source = [f"{self.source_file2.name}"] message = create_message(self.chat, self.args) @@ -106,6 +106,20 @@ Is it good?""")) # source file contains 1 source code block # -> expect it in the question self.assertEqual(len(message.question.source_code()), 1) - self.assertEqual(message.question, Question("""What is this? + self.assertEqual(message.question, Question(f"""What is this? {self.source_file2_content}""")) + + def test_single_question_with_embedded_source_code_source(self) -> None: + self.args.question = ["What is this?"] + self.args.source_code = [f"{self.source_file2.name}"] + message = create_message(self.chat, self.args) + self.assertIsInstance(message, Message) + # source file contains 1 source code block + # -> expect it in the question + self.assertEqual(len(message.question.source_code()), 2) + self.assertEqual(message.question, Question(f"""What is this? + +``` +{self.source_file2_content} +```""")) From d22877a0f1a206ed7697fe4d773ef576bbf30aa3 Mon Sep 17 00:00:00 2001 From: Oleksandr Kozachuk Date: Sat, 9 Sep 2023 15:38:40 +0200 Subject: [PATCH 2/3] Port print arguments -q/-a/-S from main to restructuring. --- chatmastermind/commands/print.py | 10 +++++++++- chatmastermind/main.py | 6 ++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/chatmastermind/commands/print.py b/chatmastermind/commands/print.py index 51e76f8..3d2b990 100644 --- a/chatmastermind/commands/print.py +++ b/chatmastermind/commands/print.py @@ -13,7 +13,15 @@ def print_cmd(args: argparse.Namespace, config: Config) -> None: try: message = Message.from_file(fname) if message: - print(message.to_str(source_code_only=args.source_code_only)) + if args.question: + print(message.question) + elif args.answer: + print(message.answer) + elif message.answer and args.only_source_code: + for code in message.answer.source_code(): + print(code) + else: + print(message.to_str()) except MessageError: print(f"File is not a valid message: {args.file}") sys.exit(1) diff --git a/chatmastermind/main.py b/chatmastermind/main.py index f7163ab..eadb095 100755 --- a/chatmastermind/main.py +++ b/chatmastermind/main.py @@ -113,8 +113,10 @@ def create_parser() -> argparse.ArgumentParser: aliases=['p']) print_cmd_parser.set_defaults(func=print_cmd) print_cmd_parser.add_argument('-f', '--file', help='File to print', required=True) - print_cmd_parser.add_argument('-S', '--source-code-only', help='Print source code only (from the answer, if available)', - action='store_true') + print_cmd_modes = print_cmd_parser.add_mutually_exclusive_group() + print_cmd_modes.add_argument('-q', '--question', help='Print only question', action='store_true') + print_cmd_modes.add_argument('-a', '--answer', help='Print only answer', action='store_true') + print_cmd_modes.add_argument('-S', '--only-source-code', help='Print only source code', action='store_true') argcomplete.autocomplete(parser) return parser From 39b518a8a60f335fb952995ee151440f899c7f85 Mon Sep 17 00:00:00 2001 From: Oleksandr Kozachuk Date: Sat, 9 Sep 2023 16:05:27 +0200 Subject: [PATCH 3/3] Small fixes. --- chatmastermind/ai_factory.py | 8 ++++---- chatmastermind/commands/question.py | 6 +++--- tests/test_ai_factory.py | 10 +++++----- tests/test_question_cmd.py | 14 +++++++------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/chatmastermind/ai_factory.py b/chatmastermind/ai_factory.py index c4a063a..bc4583c 100644 --- a/chatmastermind/ai_factory.py +++ b/chatmastermind/ai_factory.py @@ -14,11 +14,11 @@ def create_ai(args: argparse.Namespace, config: Config) -> AI: Creates an AI subclass instance from the given arguments and configuration file. """ - if args.ai: + if args.AI: try: - ai_conf = config.ais[args.ai] + ai_conf = config.ais[args.AI] except KeyError: - raise AIError(f"AI ID '{args.ai}' does not exist in this configuration") + raise AIError(f"AI ID '{args.AI}' does not exist in this configuration") elif default_ai_ID in config.ais: ai_conf = config.ais[default_ai_ID] else: @@ -34,4 +34,4 @@ def create_ai(args: argparse.Namespace, config: Config) -> AI: ai.config.temperature = args.temperature return ai else: - raise AIError(f"AI '{args.ai}' is not supported") + raise AIError(f"AI '{args.AI}' is not supported") diff --git a/chatmastermind/commands/question.py b/chatmastermind/commands/question.py index 818b1de..90b782b 100644 --- a/chatmastermind/commands/question.py +++ b/chatmastermind/commands/question.py @@ -13,7 +13,7 @@ def create_message(chat: ChatDB, args: argparse.Namespace) -> Message: Creates (and writes) a new message from the given arguments. """ question_parts = [] - question_list = args.question if args.question is not None else [] + question_list = args.ask if args.ask is not None else [] source_list = args.source if args.source is not None else [] code_list = args.source_code if args.source_code is not None else [] @@ -35,7 +35,7 @@ def create_message(chat: ChatDB, args: argparse.Namespace) -> Message: message = Message(question=Question(full_question), tags=args.output_tags, # FIXME - ai=args.ai, + ai=args.AI, model=args.model) chat.add_to_cache([message]) return message @@ -59,7 +59,7 @@ def question_cmd(args: argparse.Namespace, config: Config) -> None: response: AIResponse = ai.request(message, chat, args.num_answers, # FIXME - args.otags) # FIXME + args.output_tags) # FIXME assert response # TODO: # * add answer to the message above (and create diff --git a/tests/test_ai_factory.py b/tests/test_ai_factory.py index d63970e..d00b319 100644 --- a/tests/test_ai_factory.py +++ b/tests/test_ai_factory.py @@ -10,7 +10,7 @@ from chatmastermind.ais.openai import OpenAI class TestCreateAI(unittest.TestCase): def setUp(self) -> None: self.args = MagicMock(spec=argparse.Namespace) - self.args.ai = 'default' + self.args.AI = 'default' self.args.model = None self.args.max_tokens = None self.args.temperature = None @@ -18,19 +18,19 @@ class TestCreateAI(unittest.TestCase): def test_create_ai_from_args(self) -> None: # Create an AI with the default configuration config = Config() - self.args.ai = 'default' + self.args.AI = 'default' ai = create_ai(self.args, config) self.assertIsInstance(ai, OpenAI) def test_create_ai_from_default(self) -> None: - self.args.ai = None + self.args.AI = None # Create an AI with the default configuration config = Config() ai = create_ai(self.args, config) self.assertIsInstance(ai, OpenAI) def test_create_empty_ai_error(self) -> None: - self.args.ai = None + self.args.AI = None # Create Config with empty AIs config = Config() config.ais = {} @@ -40,7 +40,7 @@ class TestCreateAI(unittest.TestCase): def test_create_unsupported_ai_error(self) -> None: # Mock argparse.Namespace with ai='invalid_ai' - self.args.ai = 'invalid_ai' + self.args.AI = 'invalid_ai' # Create default Config config = Config() # Call create_ai function and assert that it raises AIError diff --git a/tests/test_question_cmd.py b/tests/test_question_cmd.py index 06cc527..aa0dc25 100644 --- a/tests/test_question_cmd.py +++ b/tests/test_question_cmd.py @@ -24,7 +24,7 @@ class TestMessageCreate(unittest.TestCase): self.args = MagicMock(spec=argparse.Namespace) self.args.source = None self.args.source_code = None - self.args.ai = None + self.args.AI = None self.args.model = None self.args.output_tags = None # create some files for sourcing @@ -59,7 +59,7 @@ Language is called 'brainfart'.""" return list(Path(tmp_dir.name).glob('*.[ty]*')) def test_message_file_created(self) -> None: - self.args.question = ["What is this?"] + self.args.ask = ["What is this?"] cache_dir_files = self.message_list(self.cache_path) self.assertEqual(len(cache_dir_files), 0) create_message(self.chat, self.args) @@ -70,14 +70,14 @@ Language is called 'brainfart'.""" self.assertEqual(message.question, Question("What is this?")) # type: ignore [union-attr] def test_single_question(self) -> None: - self.args.question = ["What is this?"] + self.args.ask = ["What is this?"] message = create_message(self.chat, self.args) self.assertIsInstance(message, Message) self.assertEqual(message.question, Question("What is this?")) self.assertEqual(len(message.question.source_code()), 0) def test_multipart_question(self) -> None: - self.args.question = ["What is this", "'bard' thing?", "Is it good?"] + self.args.ask = ["What is this", "'bard' thing?", "Is it good?"] message = create_message(self.chat, self.args) self.assertIsInstance(message, Message) self.assertEqual(message.question, Question("""What is this @@ -87,7 +87,7 @@ Language is called 'brainfart'.""" Is it good?""")) def test_single_question_with_text_only_source(self) -> None: - self.args.question = ["What is this?"] + self.args.ask = ["What is this?"] self.args.source = [f"{self.source_file1.name}"] message = create_message(self.chat, self.args) self.assertIsInstance(message, Message) @@ -99,7 +99,7 @@ Is it good?""")) {self.source_file1_content}""")) def test_single_question_with_embedded_source_source(self) -> None: - self.args.question = ["What is this?"] + self.args.ask = ["What is this?"] self.args.source = [f"{self.source_file2.name}"] message = create_message(self.chat, self.args) self.assertIsInstance(message, Message) @@ -111,7 +111,7 @@ Is it good?""")) {self.source_file2_content}""")) def test_single_question_with_embedded_source_code_source(self) -> None: - self.args.question = ["What is this?"] + self.args.ask = ["What is this?"] self.args.source_code = [f"{self.source_file2.name}"] message = create_message(self.chat, self.args) self.assertIsInstance(message, Message)